Bug 171904 - Web Inspector: Web Sockets: Unable to inspect a WebSocket that receives >50 messages per second
Summary: Web Inspector: Web Sockets: Unable to inspect a WebSocket that receives >50 m...
Status: RESOLVED FIXED
Alias: None
Product: WebKit
Classification: Unclassified
Component: Web Inspector (show other bugs)
Version: WebKit Nightly Build
Hardware: All All
: P2 Normal
Assignee: Nikita Vasilyev
URL:
Keywords: InRadar
Depends on:
Blocks:
 
Reported: 2017-05-09 18:55 PDT by Nikita Vasilyev
Modified: 2017-06-01 17:50 PDT (History)
6 users (show)

See Also:


Attachments
Patch (3.89 KB, patch)
2017-05-18 17:54 PDT, Nikita Vasilyev
no flags Details | Formatted Diff | Diff
Patch (3.72 KB, patch)
2017-05-22 16:11 PDT, Nikita Vasilyev
nvasilyev: review-
Details | Formatted Diff | Diff
Patch (5.62 KB, patch)
2017-05-23 16:56 PDT, Nikita Vasilyev
no flags Details | Formatted Diff | Diff
Patch (6.53 KB, patch)
2017-05-31 16:33 PDT, Nikita Vasilyev
mattbaker: review+
Details | Formatted Diff | Diff
Patch (6.65 KB, patch)
2017-06-01 14:51 PDT, Nikita Vasilyev
mattbaker: commit-queue-
Details | Formatted Diff | Diff
Patch (6.67 KB, patch)
2017-06-01 15:11 PDT, Nikita Vasilyev
no flags Details | Formatted Diff | Diff

Note You need to log in before you can comment on or make changes to this bug.
Description Nikita Vasilyev 2017-05-09 18:55:44 PDT
Steps:
1. In Web Inspector, open Network tab.
2. Navigate to http://demo2.kaazing.com/demo/forex/
3. Filter Web Sockets.
4. Click on the go to arrow right next to the WebSocket connection ("jms").

Expected:
WebSocket content view is shown.

Actual:
Nothing happens.

Notes:
This page sends a lot of WebSocket messages. You may not be able to reproduce the bug on a slow network connection.
Comment 1 Radar WebKit Bug Importer 2017-05-09 18:56:18 PDT
<rdar://problem/32095863>
Comment 2 Nikita Vasilyev 2017-05-12 19:13:58 PDT
This issue is similar to Bug 170036 - Web Inspector: Adding a WebSocket message may change the currently selected resource. Resource#increaseSize causes a chain reaction that results in ContentBrowser#showContentViewForRepresentedObject being called.

    showContentViewForRepresentedObject - ContentBrowser.js:146
    showRepresentedObject - ContentBrowserTabContentView.js:191
    showRepresentedObject - Main.js:1175
    _treeSelectionDidChange - NetworkGridContentView.js:397
    event: WebInspector.TreeOutline.Event.SelectionDidChange
    select - TreeElement.js:511
    _refreshDirtyDataGridNodes - TimelineDataGrid.js:279
    --- requestAnimationFrame ---
    dataGridNodeNeedsRefresh - TimelineDataGrid.js:198
    _needsRefresh - ResourceTimelineDataGridNode.js:277
    event: WebInspector.Resource.Event.SizeDidChange
    increaseSize - Resource.js:755
    addFrame - WebSocketResource.js:79
Comment 3 Nikita Vasilyev 2017-05-14 20:49:24 PDT
TimelineDataGrid#_refreshDirtyDataGridNodes is called every time a resource size changes. This is unnecessary expensive.

I think the right strategy would be to avoid calling TimelineDataGrid#_refreshDirtyDataGridNodes when TimelineDataGrid isn't visible. We should, however, call this method when a TimelineDataGrid becomes visible.
Comment 4 BJ Burg 2017-05-15 09:25:58 PDT
(In reply to Nikita Vasilyev from comment #3)
> TimelineDataGrid#_refreshDirtyDataGridNodes is called every time a resource
> size changes. This is unnecessary expensive.
> 
> I think the right strategy would be to avoid calling
> TimelineDataGrid#_refreshDirtyDataGridNodes when TimelineDataGrid isn't
> visible. We should, however, call this method when a TimelineDataGrid
> becomes visible.

We shouldn't be doing layout work / accessing DOM elements unless underneath an override of View.prototype.{initialLayout,layout}. Does DataGrid normally violate this, or is this code doing something special to cause this to happen?
Comment 5 Nikita Vasilyev 2017-05-15 12:17:16 PDT
(In reply to Brian Burg from comment #4)
> (In reply to Nikita Vasilyev from comment #3)
> > TimelineDataGrid#_refreshDirtyDataGridNodes is called every time a resource
> > size changes. This is unnecessary expensive.
> > 
> > I think the right strategy would be to avoid calling
> > TimelineDataGrid#_refreshDirtyDataGridNodes when TimelineDataGrid isn't
> > visible. We should, however, call this method when a TimelineDataGrid
> > becomes visible.
> 
> We shouldn't be doing layout work / accessing DOM elements unless underneath
> an override of View.prototype.{initialLayout,layout}. Does DataGrid normally
> violate this, or is this code doing something special to cause this to
> happen?

DataGrid is fairly old and has a lot of layout work / DOM access outside of {initialLayout,layout}. Only TimelineDataGrid has _refreshDirtyDataGridNodes method.  I'm not entirely sure what are you asking.
Comment 6 Nikita Vasilyev 2017-05-16 20:14:14 PDT
(In reply to Nikita Vasilyev from comment #3)
> TimelineDataGrid#_refreshDirtyDataGridNodes is called every time a resource
> size changes. This is unnecessary expensive.
> 
> I think the right strategy would be to avoid calling
> TimelineDataGrid#_refreshDirtyDataGridNodes when TimelineDataGrid isn't
> visible. We should, however, call this method when a TimelineDataGrid
> becomes visible.

This alone didn't fix the problem :(

Another issue is that Resource#increaseSize causes NetworkSidebarPanel#treeElementAddedOrChanged 
to be called. Every increase of size in a resource causes a sidebar item to be removed and attached
to the DOM. On http://demo2.kaazing.com/demo/forex/ you can see that by hovering over the WebSocket
resource in the sidebar — the go-to arrow flickers!

Every resource, not just a WebSocket resource, that frequently increases its size must be unclickable.
Comment 7 Nikita Vasilyev 2017-05-18 17:54:51 PDT
Created attachment 310583 [details]
Patch
Comment 8 Matt Baker 2017-05-19 11:17:05 PDT
Comment on attachment 310583 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=310583&action=review

> Source/WebInspectorUI/UserInterface/Views/TreeOutline.js:349
> +    reattachIfIndexChanged(treeElement, insertionIndex)

If I understand correctly, this function reinserts the tree element if the index is changing OR the status element hasn't been initialized. Maybe the function should be called `reattachIfNeeded`, since the check involves more than just the index?

> Source/WebInspectorUI/UserInterface/Views/TreeOutline.js:354
> +            // insertionIndex.status and it can be triggered by re-attaching treeElement.

`insertionIndex.status` seems wrong. This comment could also be more clear: I don't understand why having a falsey status means the element has to be re-inserted.
Comment 9 Nikita Vasilyev 2017-05-22 13:18:41 PDT
(In reply to Matt Baker from comment #8)
> Comment on attachment 310583 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=310583&action=review
> 
> > Source/WebInspectorUI/UserInterface/Views/TreeOutline.js:349
> > +    reattachIfIndexChanged(treeElement, insertionIndex)
> 
> If I understand correctly, this function reinserts the tree element if the
> index is changing OR the status element hasn't been initialized. Maybe the
> function should be called `reattachIfNeeded`, since the check involves more
> than just the index?
> 
> > Source/WebInspectorUI/UserInterface/Views/TreeOutline.js:354
> > +            // insertionIndex.status and it can be triggered by re-attaching treeElement.
> 
> `insertionIndex.status` seems wrong. This comment could also be more clear:
> I don't understand why having a falsey status means the element has to be
> re-inserted.

treeElement.status has the go-to button and close button elements. Re-inserting treeElement calls
NetworkSidebarPanel#treeElementAddedOrChanged, which adds these go-to button and close button elements.

    treeElementAddedOrChanged(treeElement)
    {
        if (treeElement.status || !treeElement.treeOutline)
            return;

        var fragment = document.createDocumentFragment();

        var closeButton = new WebInspector.TreeElementStatusButton(useSVGSymbol("Images/Close.svg", null, WebInspector.UIString("Close resource view")));
        closeButton.element.classList.add("close");
        closeButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._treeElementCloseButtonClicked, this);
        fragment.appendChild(closeButton.element);

        let goToButton = new WebInspector.TreeElementStatusButton(WebInspector.createGoToArrowButton());
        goToButton[WebInspector.NetworkSidebarPanel.TreeElementSymbol] = treeElement;
        goToButton.addEventListener(WebInspector.TreeElementStatusButton.Event.Clicked, this._treeElementGoToArrowWasClicked, this);
        fragment.appendChild(goToButton.element);

        treeElement.status = fragment;
    }

https://github.com/WebKit/webkit/blob/f59f78f7e62bae5cd899a7e6943f5696b764e4cd/Source/WebInspectorUI/UserInterface/Views/NetworkSidebarPanel.js#L154

Now that I'm thinking about it, there should be no need to re-insert treeElements, they should have the go-to & close buttons already.
I'll try to implement that instead.
Comment 10 Nikita Vasilyev 2017-05-22 16:11:05 PDT
Created attachment 310948 [details]
Patch

(In reply to Nikita Vasilyev from comment #9)
> Now that I'm thinking about it, there should be no need to re-insert
> treeElements, they should have the go-to & close buttons already.
> I'll try to implement that instead.

This isn't necessarily a good idea because both Network and Resources sidebar use the same TreeElement classes,
but tree elements in Network sidebar have the go-to arrows.

I think in the future we should re-consider the go-to arrow and close button UI in Network tab. I've seen at least 3 people
confused not knowing how to close a content view in the Network tab.
Comment 11 Joseph Pecoraro 2017-05-23 14:46:19 PDT
Comment on attachment 310948 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=310948&action=review

> Source/WebInspectorUI/ChangeLog:16
> +        This patch makes sure treeElement gets re-attached if:
> +        - Its position (index) changes.
> +        - treeElement.status, which contains the go-to arrow and the close resource icon, is not yet added to the DOM.

I understand re-attaching if the position changes. I do not understand why status has any affect on reattaching.

Why do we do the extra work if there is not a status icon?

> Source/WebInspectorUI/UserInterface/Views/TreeOutline.js:354
> +        if (treeElement.status && this.children.indexOf(treeElement) === insertionIndex)

This appears to be O(n) for what should be a single O(1) check.

Instead of:

    this.children.indexOf(treeElement) === insertionIndex

Can you just check:

    this.children[insertionIndex] === treeElement
Comment 12 Nikita Vasilyev 2017-05-23 16:22:57 PDT
Comment on attachment 310948 [details]
Patch

(In reply to Joseph Pecoraro from comment #11)
> Comment on attachment 310948 [details]
> Patch
> 
> View in context:
> https://bugs.webkit.org/attachment.cgi?id=310948&action=review
> 
> > Source/WebInspectorUI/ChangeLog:16
> > +        This patch makes sure treeElement gets re-attached if:
> > +        - Its position (index) changes.
> > +        - treeElement.status, which contains the go-to arrow and the close resource icon, is not yet added to the DOM.
> 
> I understand re-attaching if the position changes. I do not understand why
> status has any affect on reattaching.
> 
> Why do we do the extra work if there is not a status icon?

Without this treeElement.status check, there was no go-to/close icon. I think I finally figured it why.

Were these icons removed or never added?

    ResourceTreeElement#_updateStatus:
        treeElement.status = <div class="indeterminate-progress-spinner">

    ResourceTreeElement#_updateStatus:
        treeElement.status = ""

Turns out they were removed by ResourceTreeElement#_updateStatus.

WebSocketResourceTreeElement#_updateConnectionStatus is used in Resources tab, but not Network tab.
Comment 13 Nikita Vasilyev 2017-05-23 16:56:29 PDT
Created attachment 311077 [details]
Patch
Comment 14 Nikita Vasilyev 2017-05-31 16:33:22 PDT
Created attachment 311653 [details]
Patch

The previous patch sometimes didn't show the go-to buttons.
Comment 15 Matt Baker 2017-06-01 14:13:03 PDT
Comment on attachment 311653 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=311653&action=review

r=me, with a comment.

> Source/WebInspectorUI/UserInterface/Views/NetworkSidebarPanel.js:156
> +        if (treeElement.status instanceof DocumentFragment || !treeElement.treeOutline)

The check for status instanceOf DocumentFragment is a little fragile, since status could be technically be a DocumentFragment other than the goto/close fragment that gets added below. This could be more future-proof by adding a property to the status element:

treeElementAddedOrChanged(treeElement)
{
    if (!treeElement.treeOutline)
        return;

    if (treeElement.status && treeElement.status[WebInspector.NetworkSidebarPanel.TreeElementStatusButtonSymbol])
        return;

    treeElement.status[WebInspector.NetworkSidebarPanel.TreeElementStatusButtonSymbol] = true;
    ...
}

Putting the property on status instead of treeElement also ensures that the property can't get out of sync by someone setting the status elsewhere.
Comment 16 Nikita Vasilyev 2017-06-01 14:17:57 PDT
Comment on attachment 311653 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=311653&action=review

>> Source/WebInspectorUI/UserInterface/Views/NetworkSidebarPanel.js:156
>> +        if (treeElement.status instanceof DocumentFragment || !treeElement.treeOutline)
> 
> The check for status instanceOf DocumentFragment is a little fragile, since status could be technically be a DocumentFragment other than the goto/close fragment that gets added below. This could be more future-proof by adding a property to the status element:
> 
> treeElementAddedOrChanged(treeElement)
> {
>     if (!treeElement.treeOutline)
>         return;
> 
>     if (treeElement.status && treeElement.status[WebInspector.NetworkSidebarPanel.TreeElementStatusButtonSymbol])
>         return;
> 
>     treeElement.status[WebInspector.NetworkSidebarPanel.TreeElementStatusButtonSymbol] = true;
>     ...
> }
> 
> Putting the property on status instead of treeElement also ensures that the property can't get out of sync by someone setting the status elsewhere.

Agreed. I like the use a symbol.
Comment 17 Nikita Vasilyev 2017-06-01 14:51:02 PDT
Created attachment 311762 [details]
Patch
Comment 18 Matt Baker 2017-06-01 15:05:35 PDT
Comment on attachment 311762 [details]
Patch

View in context: https://bugs.webkit.org/attachment.cgi?id=311762&action=review

> Source/WebInspectorUI/UserInterface/Views/NetworkSidebarPanel.js:160
> +        if (treeElement.status[WebInspector.NetworkSidebarPanel.TreeElementStatusButtonSymbol] || !treeElement.treeOutline)

You also need to check that status is defined before accessing the property.
Comment 19 Nikita Vasilyev 2017-06-01 15:11:06 PDT
Created attachment 311764 [details]
Patch
Comment 20 WebKit Commit Bot 2017-06-01 17:50:20 PDT
Comment on attachment 311764 [details]
Patch

Clearing flags on attachment: 311764

Committed r217690: <http://trac.webkit.org/changeset/217690>
Comment 21 WebKit Commit Bot 2017-06-01 17:50:22 PDT
All reviewed patches have been landed.  Closing bug.