Web Inspector: group media network entries by the node that triggered the request
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 8 Oct 2018 18:25:52 +0000 (18:25 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 8 Oct 2018 18:25:52 +0000 (18:25 +0000)
https://bugs.webkit.org/show_bug.cgi?id=189606
<rdar://problem/44438527>

Reviewed by Brian Burg.

Source/JavaScriptCore:

* inspector/protocol/Network.json:
Add an optional `nodeId` field to the `Initiator` object that is set it is possible to
determine which ancestor node triggered the load. It may not correspond directly to the node
with the href/src, as that url may only be used by an ancestor for loading.

Source/WebCore:

Test: http/tests/inspector/network/resource-initiatorNode.html

Add extra arguments to functions that create `ResourceRequest` objects for media resources so
that `initiatorNodeIdentifier` can be set for WebInspector frontend to use for grouping.

* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::loadResource):
* html/HTMLVideoElement.cpp:
(WebCore::HTMLVideoElement::setDisplayMode):
* loader/FrameLoader.h:
* loader/FrameLoader.cpp:
(WebCore::FrameLoader::willLoadMediaElementURL):
Handles initial (e.g. DNT) resource requests.

* loader/ImageLoader.cpp:
(ImageLoader::updateFromElement):
Handles "poster" requests.

* loader/MediaResourceLoader.cpp:
(MediaResourceLoader::requestResource):
Handles byte-range requests.

* html/track/LoadableTextTrack.cpp:
(WebCore::LoadableTextTrack::loadTimerFired):
* loader/TextTrackLoader.h:
* loader/TextTrackLoader.cpp:
(WebCore::TextTrackLoader::load):
* html/HTMLTrackElement.h:
Handles <track> (e.g. subtitle) requests.

* inspector/agents/InspectorDOMAgent.cpp:
(WebCore::InspectorDOMAgent::identifierForNode):
* inspector/InspectorInstrumentation.h:
(WebCore::InspectorInstrumentation::identifierForNode):
* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::identifierForNodeImpl):
Allows callers to get a `DOM.nodeId` for the given `Node`, which is (in this patch) attached
to the `ResourceRequest` and later used by `InspectorNetworkAgent`.

* inspector/agents/InspectorNetworkAgent.h:
* inspector/agents/InspectorNetworkAgent.cpp:
(WebCore::InspectorNetworkAgent::willSendRequest):
(WebCore::InspectorNetworkAgent::didLoadResourceFromMemoryCache):
(WebCore::InspectorNetworkAgent::buildInitiatorObject):

* platform/network/ResourceRequestBase.h:
(WebCore::ResourceRequestBase::initiatorNodeIdentifier const):
(WebCore::ResourceRequestBase::setInitiatorNodeIdentifier):
* platform/network/ResourceRequestBase.cpp:
(WebCore::ResourceRequestBase::setAsIsolatedCopy):
* platform/network/cf/ResourceRequestCFNet.cpp:
(WebCore::ResourceRequest::updateFromDelegatePreservingOldProperties):
* loader/cache/CachedResourceLoader.cpp:
(WebCore::CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache):

* inspector/InspectorCanvas.h:
* inspector/InspectorCanvas.cpp:
(WebCore::InspectorCanvas::buildObjectForCanvas):
* inspector/agents/InspectorCanvasAgent.cpp:
(WebCore::InspectorCanvasAgent::enable):
(WebCore::InspectorCanvasAgent::didCreateCanvasRenderingContext):
Don't try to push the canvas' node to the frontend, as this will create a dangling node in
`InspectorDOMAgent` if the canvas' node is detached from the DOM.

Source/WebInspectorUI:

Introduces a `WI.NavigationItem` for changing whether network entries are grouped by the
node that initiated the load (if applicable). When grouped by node, a tree-like layout of
the table cells (including expand/collapse) is used for resources that share the same
initiator node. The values for the node's cell are based on it's initated resources.

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Base/Setting.js:

* UserInterface/Controllers/DOMManager.js:
(WI.DOMManager):
(WI.DOMManager.prototype._mainResourceDidChange): Added.
Whenever the frame navigates, re-request the document so that `NetworkAgent` is able to send
valid `nodeId` for each request's `initiatorNode`. This means that the document should
always be available.

* UserInterface/Views/NetworkTableContentView.js:
(WI.NetworkTableContentView):
(WI.NetworkTableContentView.prototype.get filterNavigationItems):
(WI.NetworkTableContentView.prototype.closed):
(WI.NetworkTableContentView.prototype.reset):
(WI.NetworkTableContentView.prototype.tableSortChanged):
(WI.NetworkTableContentView.prototype.tableSelectedRowChanged):
(WI.NetworkTableContentView.prototype.tablePopulateCell):
(WI.NetworkTableContentView.prototype._populateNameCell.createIconElement): Added.
(WI.NetworkTableContentView.prototype._populateNameCell):
(WI.NetworkTableContentView.prototype._populateDomainCell.createIconAndText): Added.
(WI.NetworkTableContentView.prototype._populateDomainCell):
(WI.NetworkTableContentView.prototype._populateInitiatorCell):
(WI.NetworkTableContentView.prototype._populateTransferSizeCell):
(WI.NetworkTableContentView.prototype._generateSortComparator):
(WI.NetworkTableContentView.prototype._processPendingEntries):
(WI.NetworkTableContentView.prototype._updateEntryForResource.updateExistingEntry): Added.
(WI.NetworkTableContentView.prototype._updateEntryForResource):
(WI.NetworkTableContentView.prototype._insertResourceAndReloadTable):
(WI.NetworkTableContentView.prototype._entryForDOMNode): Added.
(WI.NetworkTableContentView.prototype._tryLinkResourceToDOMNode): Added.
(WI.NetworkTableContentView.prototype._uniqueValuesForDOMNodeEntry): Added.
(WI.NetworkTableContentView.prototype._updateFilteredEntries):
(WI.NetworkTableContentView.prototype._handleGroupByDOMNodeCheckedDidChange): Added.
* UserInterface/Views/NetworkTableContentView.css:
(.network-table .cell.dom-node.name .icon): Added.
(.network-table .cell.dom-node.name .disclosure): Added.
(body[dir=rtl] .network-table .cell.dom-node.name .disclosure): Added.
(.network-table:focus li.selected .cell.dom-node.name .disclosure): Added.
(.network-table .cell.dom-node.name .disclosure.expanded): Added.
(.network-table:focus li.selected .cell.node.name .disclosure.expanded): Added.
(.network-table .cell.grouped-by-node.name): Added.
(body[dir=ltr] .network-table .cell.grouped-by-node.name): Added.
(body[dir=rtl] .network-table .cell.grouped-by-node.name): Added.
(.network-table li:not(.selected) .cell:matches(.cache-type, .multiple)): Added.
(.network-table li.selected .cell.domain > .lock): Added.
(.network-table .cache-type): Deleted.
When two resources are added that share the same `initiatorNode`, insert a node entry into
the `WI.Table` before the first resource entry for that node (based on the current sort).
This node entry is added after the resource entries are filtered, so they won't appear in
the default entries list.

* UserInterface/Models/Resource.js:
(WI.Resource):
(WI.Resource.prototype.initiatorNode): Added.
(WI.Resource.prototype.requestedByteRange): Added.
* UserInterface/Controllers/NetworkManager.js:
(WI.NetworkManager.prototype.resourceRequestWillBeSent):
(WI.NetworkManager.prototype.resourceRequestWasServedFromMemoryCache):
(WI.NetworkManager.prototype._initiatorNodeFromPayload): Added.

* UserInterface/Images/Range.svg: Added.
* UserInterface/Views/ResourceIcons.css:
(.resource-icon.resource-type-range .icon): Added.

LayoutTests:

* http/tests/inspector/network/resource-initiatorNode-expected.txt: Added.
* http/tests/inspector/network/resource-initiatorNode.html: Added.

* inspector/canvas/requestNode.html:
Test case no longer needed since the document is always requested once it's available.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@236927 268f45cc-cd09-0410-ab3c-d52691b4dbfc

44 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/inspector/network/resource-initiatorNode-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/resource-initiatorNode.html [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/resources/white.mp4 [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/resources/white.png [new file with mode: 0644]
LayoutTests/http/tests/inspector/network/resources/white.vtt [new file with mode: 0644]
LayoutTests/inspector/canvas/requestNode-expected.txt
LayoutTests/inspector/canvas/requestNode.html
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/protocol/Network.json
Source/WebCore/ChangeLog
Source/WebCore/html/HTMLMediaElement.cpp
Source/WebCore/html/HTMLTrackElement.h
Source/WebCore/html/HTMLVideoElement.cpp
Source/WebCore/html/track/LoadableTextTrack.cpp
Source/WebCore/inspector/InspectorCanvas.cpp
Source/WebCore/inspector/InspectorCanvas.h
Source/WebCore/inspector/InspectorInstrumentation.cpp
Source/WebCore/inspector/InspectorInstrumentation.h
Source/WebCore/inspector/agents/InspectorCanvasAgent.cpp
Source/WebCore/inspector/agents/InspectorDOMAgent.cpp
Source/WebCore/inspector/agents/InspectorDOMAgent.h
Source/WebCore/inspector/agents/InspectorNetworkAgent.cpp
Source/WebCore/inspector/agents/InspectorNetworkAgent.h
Source/WebCore/loader/FrameLoader.cpp
Source/WebCore/loader/FrameLoader.h
Source/WebCore/loader/ImageLoader.cpp
Source/WebCore/loader/MediaResourceLoader.cpp
Source/WebCore/loader/TextTrackLoader.cpp
Source/WebCore/loader/TextTrackLoader.h
Source/WebCore/loader/cache/CachedResourceLoader.cpp
Source/WebCore/platform/network/ResourceRequestBase.cpp
Source/WebCore/platform/network/ResourceRequestBase.h
Source/WebCore/platform/network/cf/ResourceRequestCFNet.cpp
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/Setting.js
Source/WebInspectorUI/UserInterface/Controllers/DOMManager.js
Source/WebInspectorUI/UserInterface/Controllers/NetworkManager.js
Source/WebInspectorUI/UserInterface/Images/Range.svg [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Models/Resource.js
Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.css
Source/WebInspectorUI/UserInterface/Views/NetworkTableContentView.js
Source/WebInspectorUI/UserInterface/Views/ResourceIcons.css

index 4337877..9a40033 100644 (file)
@@ -1,3 +1,17 @@
+2018-10-08  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: group media network entries by the node that triggered the request
+        https://bugs.webkit.org/show_bug.cgi?id=189606
+        <rdar://problem/44438527>
+
+        Reviewed by Brian Burg.
+
+        * http/tests/inspector/network/resource-initiatorNode-expected.txt: Added.
+        * http/tests/inspector/network/resource-initiatorNode.html: Added.
+
+        * inspector/canvas/requestNode.html:
+        Test case no longer needed since the document is always requested once it's available.
+
 2018-10-08  Andy Estes  <aestes@apple.com>
 
         [Payment Request] Requests should be aborted after details settle when the user cancels
diff --git a/LayoutTests/http/tests/inspector/network/resource-initiatorNode-expected.txt b/LayoutTests/http/tests/inspector/network/resource-initiatorNode-expected.txt
new file mode 100644 (file)
index 0000000..239cb04
--- /dev/null
@@ -0,0 +1,17 @@
+Tests that Request initiatorNode is set correctly.
+
+
+
+== Running test suite: WI.Resource.initiatorNode
+-- Running test case: WI.Resource.initiatorNode.posterPNG
+PASS: Resource should have an initiatorNode
+PASS: Resource initiatorNode should match video node id
+
+-- Running test case: WI.Resource.initiatorNode.sourceMP4
+PASS: Resource should have an initiatorNode
+PASS: Resource initiatorNode should match video node id
+
+-- Running test case: WI.Resource.initiatorNode.trackVTT
+PASS: Resource should have an initiatorNode
+PASS: Resource initiatorNode should match video node id
+
diff --git a/LayoutTests/http/tests/inspector/network/resource-initiatorNode.html b/LayoutTests/http/tests/inspector/network/resource-initiatorNode.html
new file mode 100644 (file)
index 0000000..0f2f2fc
--- /dev/null
@@ -0,0 +1,137 @@
+<!doctype html>
+<html>
+<head>
+<script src="../resources/inspector-test.js"></script>
+<script>
+
+function replay() {
+    let videoElement = document.getElementById("video");
+    videoElement.pause();
+    videoElement.currentTime = 0;
+    videoElement.load();
+}
+
+function loadPoster(url) {
+    document.getElementById("video").poster = url;
+
+    replay();
+}
+
+function loadSource(url, type) {
+    let sourceElement = document.createElement("source");
+    sourceElement.type = type;
+    sourceElement.src = url;
+
+    document.getElementById("video").appendChild(sourceElement);
+
+    replay();
+}
+
+function loadTrack(url, kind) {
+    let trackElement = document.createElement("track");
+    trackElement.kind = kind;
+    trackElement.default = true;
+    trackElement.src = url;
+
+    trackElement.track.mode = "hidden";
+
+    document.getElementById("video").appendChild(trackElement);
+
+    replay();
+}
+
+function handleVideoEnded(event) {
+    TestPage.dispatchEventToFrontend("TestPage-video-ended");
+}
+
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("WI.Resource.initiatorNode");
+
+    let videoNode = null;
+
+    suite.addTestCase({
+        name: "WI.Resource.initiatorNode.posterPNG",
+        test(resolve, reject) {
+            let file = "white.png";
+
+            WI.Frame.awaitEvent(WI.Frame.Event.ResourceWasAdded)
+            .then((event) => {
+                let resource = event.data.resource;
+
+                InspectorTest.assert(resource.url.endsWith(file), `Resource should be "${file}"`);
+
+                InspectorTest.expectNotNull(resource.initiatorNode, "Resource should have an initiatorNode");
+                if (resource.initiatorNode)
+                    InspectorTest.expectEqual(resource.initiatorNode.id, videoNode.id, "Resource initiatorNode should match video node id");
+            })
+            .then(resolve, reject);
+
+            InspectorTest.evaluateInPage(`loadPoster("resources/${file}")`);
+        }
+    });
+
+    suite.addTestCase({
+        name: "WI.Resource.initiatorNode.sourceMP4",
+        test(resolve, reject) {
+            let file = "white.mp4";
+
+            WI.Frame.awaitEvent(WI.Frame.Event.ResourceWasAdded)
+            .then((event) => {
+                let resource = event.data.resource;
+
+                InspectorTest.assert(resource.url.endsWith(file), `Resource should be "${file}"`);
+
+                InspectorTest.expectNotNull(resource.initiatorNode, "Resource should have an initiatorNode");
+                if (resource.initiatorNode)
+                    InspectorTest.expectEqual(resource.initiatorNode.id, videoNode.id, "Resource initiatorNode should match video node id");
+            })
+            .then(resolve, reject);
+
+            InspectorTest.evaluateInPage(`loadSource("resources/${file}", "video/mp4")`);
+        }
+    });
+
+    suite.addTestCase({
+        name: "WI.Resource.initiatorNode.trackVTT",
+        test(resolve, reject) {
+            let file = "white.vtt";
+
+            // Loading a track causes a bunch of media controls to be loaded.
+            let listener = WI.Frame.addEventListener(WI.Frame.Event.ResourceWasAdded, (event) => {
+                let resource = event.data.resource;
+                if (!resource.url.endsWith(file))
+                    return;
+
+                InspectorTest.expectNotNull(resource.initiatorNode, "Resource should have an initiatorNode");
+                if (resource.initiatorNode)
+                    InspectorTest.expectEqual(resource.initiatorNode.id, videoNode.id, "Resource initiatorNode should match video node id");
+
+                WI.Frame.removeEventListener(WI.Frame.Event.ResourceWasAdded, listener);
+
+                resolve();
+            });
+
+            InspectorTest.evaluateInPage(`loadTrack("resources/${file}", "captions")`);
+        }
+    });
+
+    WI.domManager.requestDocument((documentNode) => {
+        WI.domManager.querySelector(documentNode.id, "video#video", (videoNodeId) => {
+            videoNode = WI.domManager.nodeForId(videoNodeId);
+            if (videoNode)
+                suite.runTestCasesAndFinish();
+            else {
+                InspectorTest.fail(`DOM node for "video#video" not found.`);
+                InspectorTest.completeTest();
+            }
+        });
+    });
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Tests that Request initiatorNode is set correctly.</p>
+    <video id="video" muted autoplay></video>
+</body>
+</html>
diff --git a/LayoutTests/http/tests/inspector/network/resources/white.mp4 b/LayoutTests/http/tests/inspector/network/resources/white.mp4
new file mode 100644 (file)
index 0000000..a7bcd70
Binary files /dev/null and b/LayoutTests/http/tests/inspector/network/resources/white.mp4 differ
diff --git a/LayoutTests/http/tests/inspector/network/resources/white.png b/LayoutTests/http/tests/inspector/network/resources/white.png
new file mode 100644 (file)
index 0000000..ac49a61
Binary files /dev/null and b/LayoutTests/http/tests/inspector/network/resources/white.png differ
diff --git a/LayoutTests/http/tests/inspector/network/resources/white.vtt b/LayoutTests/http/tests/inspector/network/resources/white.vtt
new file mode 100644 (file)
index 0000000..f3e906d
--- /dev/null
@@ -0,0 +1,7 @@
+WEBVTT
+
+00:00:00.000 --> 00:00:00.999
+0.000 to 0.999
+
+00:00:01.000 --> 00:00:01.999
+1.000 to 1.999
index 715c431..b7cdee8 100644 (file)
@@ -2,11 +2,6 @@ Test that CanvasAgent.requestNode can properly resolve the owner canvas node.
 
 
 == Running test suite: Canvas.requestNode
--- Running test case: Canvas.requestNode.missingDocument
-PASS: The page should have three canvases.
-PASS: Should produce an error.
-Error: Document has not been requested
-
 -- Running test case: Canvas.requestNode.validCanvasId
 PASS: Canvas "CSS canvas “css”" has node with valid id.
 PASS: Canvas "CSS canvas “css”" has node with type "CANVAS".
index 32db735..e5c6147 100644 (file)
@@ -15,25 +15,6 @@ function test() {
     let suite = InspectorTest.createAsyncSuite("Canvas.requestNode");
 
     suite.addTestCase({
-        name: "Canvas.requestNode.missingDocument",
-        description: "Getting the canvas node requires that WebInspector knows about the document.",
-        test(resolve, reject) {
-            let canvases = WI.canvasManager.canvases;
-            InspectorTest.expectEqual(canvases.length, 3, "The page should have three canvases.");
-            if (!canvases.length) {
-                reject("Missing canvas.");
-                return;
-            }
-
-            CanvasAgent.requestNode(canvases[0].identifier, (error) => {
-                InspectorTest.expectThat(error, "Should produce an error.");
-                InspectorTest.log("Error: " + error);
-                resolve();
-            });
-        }
-    });
-
-    suite.addTestCase({
         name: "Canvas.requestNode.validCanvasId",
         description: "Get the node id for each canvas on the page.",
         test(resolve, reject) {
index 9acfb85..57dc5c5 100644 (file)
@@ -1,3 +1,16 @@
+2018-10-08  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: group media network entries by the node that triggered the request
+        https://bugs.webkit.org/show_bug.cgi?id=189606
+        <rdar://problem/44438527>
+
+        Reviewed by Brian Burg.
+
+        * inspector/protocol/Network.json:
+        Add an optional `nodeId` field to the `Initiator` object that is set it is possible to
+        determine which ancestor node triggered the load. It may not correspond directly to the node
+        with the href/src, as that url may only be used by an ancestor for loading.
+
 2018-10-07  Yusuke Suzuki  <yusukesuzuki@slowstart.org>
 
         [JSC][Linux] Use non-truncated name for JIT workers in Linux
index 891dac5..091d4cb 100644 (file)
                 { "name": "type", "type": "string", "enum": ["parser", "script", "other"], "description": "Type of this initiator." },
                 { "name": "stackTrace", "type": "array", "items": { "$ref": "Console.CallFrame" }, "optional": true, "description": "Initiator JavaScript stack trace, set for Script only." },
                 { "name": "url", "type": "string", "optional": true, "description": "Initiator URL, set for Parser type only." },
-                { "name": "lineNumber", "type": "number", "optional": true, "description": "Initiator line number, set for Parser type only." }
+                { "name": "lineNumber", "type": "number", "optional": true, "description": "Initiator line number, set for Parser type only." },
+                { "name": "nodeId", "$ref": "DOM.NodeId", "optional": true, "description": "Set if the load was triggered by a DOM node, in addition to the other initiator information." }
             ]
         }
     ],
index d2679c3..661505a 100644 (file)
@@ -1,3 +1,75 @@
+2018-10-08  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: group media network entries by the node that triggered the request
+        https://bugs.webkit.org/show_bug.cgi?id=189606
+        <rdar://problem/44438527>
+
+        Reviewed by Brian Burg.
+
+        Test: http/tests/inspector/network/resource-initiatorNode.html
+
+        Add extra arguments to functions that create `ResourceRequest` objects for media resources so
+        that `initiatorNodeIdentifier` can be set for WebInspector frontend to use for grouping.
+
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::loadResource):
+        * html/HTMLVideoElement.cpp:
+        (WebCore::HTMLVideoElement::setDisplayMode):
+        * loader/FrameLoader.h:
+        * loader/FrameLoader.cpp:
+        (WebCore::FrameLoader::willLoadMediaElementURL):
+        Handles initial (e.g. DNT) resource requests.
+
+        * loader/ImageLoader.cpp:
+        (ImageLoader::updateFromElement):
+        Handles "poster" requests.
+
+        * loader/MediaResourceLoader.cpp:
+        (MediaResourceLoader::requestResource):
+        Handles byte-range requests.
+
+        * html/track/LoadableTextTrack.cpp:
+        (WebCore::LoadableTextTrack::loadTimerFired):
+        * loader/TextTrackLoader.h:
+        * loader/TextTrackLoader.cpp:
+        (WebCore::TextTrackLoader::load):
+        * html/HTMLTrackElement.h:
+        Handles <track> (e.g. subtitle) requests.
+
+        * inspector/agents/InspectorDOMAgent.cpp:
+        (WebCore::InspectorDOMAgent::identifierForNode):
+        * inspector/InspectorInstrumentation.h:
+        (WebCore::InspectorInstrumentation::identifierForNode):
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::InspectorInstrumentation::identifierForNodeImpl):
+        Allows callers to get a `DOM.nodeId` for the given `Node`, which is (in this patch) attached
+        to the `ResourceRequest` and later used by `InspectorNetworkAgent`.
+
+        * inspector/agents/InspectorNetworkAgent.h:
+        * inspector/agents/InspectorNetworkAgent.cpp:
+        (WebCore::InspectorNetworkAgent::willSendRequest):
+        (WebCore::InspectorNetworkAgent::didLoadResourceFromMemoryCache):
+        (WebCore::InspectorNetworkAgent::buildInitiatorObject):
+
+        * platform/network/ResourceRequestBase.h:
+        (WebCore::ResourceRequestBase::initiatorNodeIdentifier const):
+        (WebCore::ResourceRequestBase::setInitiatorNodeIdentifier):
+        * platform/network/ResourceRequestBase.cpp:
+        (WebCore::ResourceRequestBase::setAsIsolatedCopy):
+        * platform/network/cf/ResourceRequestCFNet.cpp:
+        (WebCore::ResourceRequest::updateFromDelegatePreservingOldProperties):
+        * loader/cache/CachedResourceLoader.cpp:
+        (WebCore::CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache):
+
+        * inspector/InspectorCanvas.h:
+        * inspector/InspectorCanvas.cpp:
+        (WebCore::InspectorCanvas::buildObjectForCanvas):
+        * inspector/agents/InspectorCanvasAgent.cpp:
+        (WebCore::InspectorCanvasAgent::enable):
+        (WebCore::InspectorCanvasAgent::didCreateCanvasRenderingContext):
+        Don't try to push the canvas' node to the frontend, as this will create a dangling node in
+        `InspectorDOMAgent` if the canvas' node is detached from the DOM.
+
 2018-10-08  Andy Estes  <aestes@apple.com>
 
         [Payment Request] Requests should be aborted after details settle when the user cancels
index f8bc5b4..e743fa1 100644 (file)
@@ -1592,7 +1592,7 @@ void HTMLMediaElement::loadResource(const URL& initialURL, ContentType& contentT
     }
 
     URL url = initialURL;
-    if (!url.isEmpty() && !frame->loader().willLoadMediaElementURL(url)) {
+    if (!url.isEmpty() && !frame->loader().willLoadMediaElementURL(url, *this)) {
         mediaLoadingFailed(MediaPlayer::FormatError);
         return;
     }
index b9ca948..9cbdadb 100644 (file)
@@ -58,6 +58,7 @@ public:
     enum LoadStatus { Failure, Success };
     void didCompleteLoad(LoadStatus);
 
+    RefPtr<HTMLMediaElement> mediaElement() const;
     const AtomicString& mediaElementCrossOriginAttribute() const;
 
 private:
@@ -73,8 +74,6 @@ private:
 
     void loadTimerFired();
 
-    RefPtr<HTMLMediaElement> mediaElement() const;
-
     // TextTrackClient
     void textTrackModeChanged(TextTrack&) final;
     void textTrackKindChanged(TextTrack&) final;
index f4149b2..8f83a46 100644 (file)
@@ -263,7 +263,7 @@ void HTMLVideoElement::setDisplayMode(DisplayMode mode)
         bool canLoad = true;
         if (!poster.isEmpty()) {
             if (RefPtr<Frame> frame = document().frame())
-                canLoad = frame->loader().willLoadMediaElementURL(poster);
+                canLoad = frame->loader().willLoadMediaElementURL(poster, *this);
         }
         if (canLoad)
             player()->setPoster(poster);
index 238ebe7..a8019f2 100644 (file)
@@ -82,7 +82,7 @@ void LoadableTextTrack::loadTimerFired()
     // mode being the state of the media element's crossorigin content attribute, the origin being the
     // origin of the media element's Document, and the default origin behaviour set to fail.
     m_loader = std::make_unique<TextTrackLoader>(static_cast<TextTrackLoaderClient&>(*this), static_cast<ScriptExecutionContext*>(&m_trackElement->document()));
-    if (!m_loader->load(m_url, m_trackElement->mediaElementCrossOriginAttribute(), m_trackElement->isInUserAgentShadowTree()))
+    if (!m_loader->load(m_url, *m_trackElement))
         m_trackElement->didCompleteLoad(HTMLTrackElement::Failure);
 }
 
index 7ec5751..97a3f8c 100644 (file)
@@ -44,7 +44,6 @@
 #include "ImageBuffer.h"
 #include "ImageData.h"
 #include "InspectorDOMAgent.h"
-#include "InstrumentingAgents.h"
 #include "JSCanvasDirection.h"
 #include "JSCanvasFillRule.h"
 #include "JSCanvasLineCap.h"
@@ -217,7 +216,7 @@ bool InspectorCanvas::hasBufferSpace() const
     return m_bufferUsed < m_bufferLimit;
 }
 
-Ref<Inspector::Protocol::Canvas::Canvas> InspectorCanvas::buildObjectForCanvas(InstrumentingAgents& instrumentingAgents, bool captureBacktrace)
+Ref<Inspector::Protocol::Canvas::Canvas> InspectorCanvas::buildObjectForCanvas(bool captureBacktrace)
 {
     Inspector::Protocol::Canvas::ContextType contextType;
     if (is<CanvasRenderingContext2D>(m_context))
@@ -250,19 +249,8 @@ Ref<Inspector::Protocol::Canvas::Canvas> InspectorCanvas::buildObjectForCanvas(I
         String cssCanvasName = node->document().nameForCSSCanvasElement(*node);
         if (!cssCanvasName.isEmpty())
             canvas->setCssCanvasName(cssCanvasName);
-        else {
-            InspectorDOMAgent* domAgent = instrumentingAgents.inspectorDOMAgent();
-            int nodeId = domAgent->boundNodeId(node);
-            if (!nodeId) {
-                if (int documentNodeId = domAgent->boundNodeId(&node->document())) {
-                    ErrorString ignored;
-                    nodeId = domAgent->pushNodeToFrontend(ignored, documentNodeId, node);
-                }
-            }
 
-            if (nodeId)
-                canvas->setNodeId(nodeId);
-        }
+        // FIXME: <https://webkit.org/b/178282> Web Inspector: send a DOM node with each Canvas payload and eliminate Canvas.requestNode
     }
 
     if (is<ImageBitmapRenderingContext>(m_context)) {
index e102d5a..2f7092d 100644 (file)
@@ -42,7 +42,6 @@ class HTMLImageElement;
 class HTMLVideoElement;
 class ImageBitmap;
 class ImageData;
-class InstrumentingAgents;
 
 class InspectorCanvas final : public RefCounted<InspectorCanvas> {
 public:
@@ -75,7 +74,7 @@ public:
     bool singleFrame() const { return m_singleFrame; }
     void setSingleFrame(bool singleFrame) { m_singleFrame = singleFrame; }
 
-    Ref<Inspector::Protocol::Canvas::Canvas> buildObjectForCanvas(InstrumentingAgents&, bool captureBacktrace);
+    Ref<Inspector::Protocol::Canvas::Canvas> buildObjectForCanvas(bool captureBacktrace);
 
 private:
     InspectorCanvas(CanvasRenderingContext&);
index 7f7f126..61a7c73 100644 (file)
@@ -128,6 +128,13 @@ bool InspectorInstrumentation::isDebuggerPausedImpl(InstrumentingAgents& instrum
     return false;
 }
 
+int InspectorInstrumentation::identifierForNodeImpl(InstrumentingAgents& instrumentingAgents, Node& node)
+{
+    if (InspectorDOMAgent* domAgent = instrumentingAgents.inspectorDOMAgent())
+        return domAgent->identifierForNode(node);
+    return 0;
+}
+
 void InspectorInstrumentation::willInsertDOMNodeImpl(InstrumentingAgents& instrumentingAgents, Node& parent)
 {
     if (InspectorDOMDebuggerAgent* domDebuggerAgent = instrumentingAgents.inspectorDOMDebuggerAgent())
index dde2122..926c97d 100644 (file)
@@ -106,6 +106,7 @@ public:
     static void didClearWindowObjectInWorld(Frame&, DOMWrapperWorld&);
     static bool isDebuggerPaused(Frame*);
 
+    static int identifierForNode(Node&);
     static void willInsertDOMNode(Document&, Node& parent);
     static void didInsertDOMNode(Document&, Node&);
     static void willRemoveDOMNode(Document&, Node&);
@@ -290,6 +291,7 @@ private:
     static void didClearWindowObjectInWorldImpl(InstrumentingAgents&, Frame&, DOMWrapperWorld&);
     static bool isDebuggerPausedImpl(InstrumentingAgents&);
 
+    static int identifierForNodeImpl(InstrumentingAgents&, Node&);
     static void willInsertDOMNodeImpl(InstrumentingAgents&, Node& parent);
     static void didInsertDOMNodeImpl(InstrumentingAgents&, Node&);
     static void willRemoveDOMNodeImpl(InstrumentingAgents&, Node&);
@@ -470,6 +472,14 @@ inline bool InspectorInstrumentation::isDebuggerPaused(Frame* frame)
     return false;
 }
 
+inline int InspectorInstrumentation::identifierForNode(Node& node)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(0);
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForDocument(node.document()))
+        return identifierForNodeImpl(*instrumentingAgents, node);
+    return 0;
+}
+
 inline void InspectorInstrumentation::willInsertDOMNode(Document& document, Node& parent)
 {
     FAST_RETURN_IF_NO_FRONTENDS(void());
index 1546d50..460cbbf 100644 (file)
@@ -99,7 +99,7 @@ void InspectorCanvasAgent::enable(ErrorString&)
 
     const bool captureBacktrace = false;
     for (auto& inspectorCanvas : m_identifierToInspectorCanvas.values()) {
-        m_frontendDispatcher->canvasAdded(inspectorCanvas->buildObjectForCanvas(m_instrumentingAgents, captureBacktrace));
+        m_frontendDispatcher->canvasAdded(inspectorCanvas->buildObjectForCanvas(captureBacktrace));
 
 #if ENABLE(WEBGL)
         if (is<WebGLRenderingContextBase>(inspectorCanvas->context())) {
@@ -431,7 +431,7 @@ void InspectorCanvasAgent::didCreateCanvasRenderingContext(CanvasRenderingContex
 
     if (m_enabled) {
         const bool captureBacktrace = true;
-        m_frontendDispatcher->canvasAdded(inspectorCanvas->buildObjectForCanvas(m_instrumentingAgents, captureBacktrace));
+        m_frontendDispatcher->canvasAdded(inspectorCanvas->buildObjectForCanvas(captureBacktrace));
     }
 
     m_identifierToInspectorCanvas.set(inspectorCanvas->identifier(), WTFMove(inspectorCanvas));
index a8aae0b..ec1aec8 100644 (file)
@@ -2085,6 +2085,11 @@ void InspectorDOMAgent::didCommitLoad(Document* document)
     m_frontendDispatcher->childNodeInserted(parentId, prevId, WTFMove(value));
 }
 
+int InspectorDOMAgent::identifierForNode(Node& node)
+{
+    return pushNodePathToFrontend(&node);
+}
+
 void InspectorDOMAgent::didInsertDOMNode(Node& node)
 {
     if (containsOnlyHTMLWhitespace(&node))
index 93751a2..cd1f5a1 100644 (file)
@@ -157,6 +157,7 @@ public:
 
 
     // InspectorInstrumentation
+    int identifierForNode(Node&);
     void didInsertDOMNode(Node&);
     void didRemoveDOMNode(Node&);
     void willModifyDOMAttr(Element&, const AtomicString& oldValue, const AtomicString& newValue);
index 3331078..c2af8ba 100644 (file)
@@ -45,6 +45,7 @@
 #include "FrameLoader.h"
 #include "HTTPHeaderMap.h"
 #include "HTTPHeaderNames.h"
+#include "InspectorDOMAgent.h"
 #include "InspectorTimelineAgent.h"
 #include "InstrumentingAgents.h"
 #include "JSExecState.h"
@@ -386,7 +387,7 @@ void InspectorNetworkAgent::willSendRequest(unsigned long identifier, DocumentLo
     auto protocolResourceType = InspectorPageAgent::resourceTypeJSON(type);
 
     Document* document = loader && loader->frame() ? loader->frame()->document() : nullptr;
-    auto initiatorObject = buildInitiatorObject(document);
+    auto initiatorObject = buildInitiatorObject(document, request);
 
     String url = loader ? loader->url().string() : request.url();
     m_frontendDispatcher->requestWillBeSent(requestId, frameId, loaderId, url, buildObjectForResourceRequest(request), sendTimestamp, walltime.secondsSinceEpoch().seconds(), initiatorObject, buildObjectForResourceResponse(redirectResponse, nullptr), type != InspectorPageAgent::OtherResource ? &protocolResourceType : nullptr, targetId.isEmpty() ? nullptr : &targetId);
@@ -566,7 +567,7 @@ void InspectorNetworkAgent::didLoadResourceFromMemoryCache(DocumentLoader* loade
 
     m_resourcesData->resourceCreated(requestId, loaderId, resource);
 
-    RefPtr<Inspector::Protocol::Network::Initiator> initiatorObject = buildInitiatorObject(loader->frame() ? loader->frame()->document() : nullptr);
+    auto initiatorObject = buildInitiatorObject(loader->frame() ? loader->frame()->document() : nullptr, resource.resourceRequest());
 
     // FIXME: It would be ideal to generate the Network.Response with the MemoryCache source
     // instead of whatever ResourceResponse::Source the CachedResources's response has.
@@ -636,7 +637,7 @@ void InspectorNetworkAgent::didScheduleStyleRecalculation(Document& document)
         m_styleRecalculationInitiator = buildInitiatorObject(&document);
 }
 
-RefPtr<Inspector::Protocol::Network::Initiator> InspectorNetworkAgent::buildInitiatorObject(Document* document)
+RefPtr<Inspector::Protocol::Network::Initiator> InspectorNetworkAgent::buildInitiatorObject(Document* document, std::optional<const ResourceRequest&> resourceRequest)
 {
     // FIXME: Worker support.
     if (!isMainThread()) {
@@ -645,24 +646,38 @@ RefPtr<Inspector::Protocol::Network::Initiator> InspectorNetworkAgent::buildInit
             .release();
     }
 
+    RefPtr<Inspector::Protocol::Network::Initiator> initiatorObject;
+
     Ref<ScriptCallStack> stackTrace = createScriptCallStack(JSExecState::currentState());
     if (stackTrace->size() > 0) {
-        auto initiatorObject = Inspector::Protocol::Network::Initiator::create()
+        initiatorObject = Inspector::Protocol::Network::Initiator::create()
             .setType(Inspector::Protocol::Network::Initiator::Type::Script)
             .release();
         initiatorObject->setStackTrace(stackTrace->buildInspectorArray());
-        return WTFMove(initiatorObject);
-    }
-
-    if (document && document->scriptableDocumentParser()) {
-        auto initiatorObject = Inspector::Protocol::Network::Initiator::create()
+    } else if (document && document->scriptableDocumentParser()) {
+        initiatorObject = Inspector::Protocol::Network::Initiator::create()
             .setType(Inspector::Protocol::Network::Initiator::Type::Parser)
             .release();
         initiatorObject->setUrl(document->url().string());
         initiatorObject->setLineNumber(document->scriptableDocumentParser()->textPosition().m_line.oneBasedInt());
-        return WTFMove(initiatorObject);
     }
 
+    auto domAgent = m_instrumentingAgents.inspectorDOMAgent();
+    if (domAgent && resourceRequest) {
+        if (auto inspectorInitiatorNodeIdentifier = resourceRequest->inspectorInitiatorNodeIdentifier()) {
+            if (!initiatorObject) {
+                initiatorObject = Inspector::Protocol::Network::Initiator::create()
+                    .setType(Inspector::Protocol::Network::Initiator::Type::Other)
+                    .release();
+            }
+
+            initiatorObject->setNodeId(*inspectorInitiatorNodeIdentifier);
+        }
+    }
+
+    if (initiatorObject)
+        return initiatorObject;
+
     if (m_isRecalculatingStyle && m_styleRecalculationInitiator)
         return m_styleRecalculationInitiator;
 
index 64d0e04..781c004 100644 (file)
@@ -132,7 +132,7 @@ private:
 
     WebSocket* webSocketForRequestId(const String& requestId);
 
-    RefPtr<Inspector::Protocol::Network::Initiator> buildInitiatorObject(Document*);
+    RefPtr<Inspector::Protocol::Network::Initiator> buildInitiatorObject(Document*, std::optional<const ResourceRequest&> = std::nullopt);
     Ref<Inspector::Protocol::Network::ResourceTiming> buildObjectForTiming(const NetworkLoadMetrics&, ResourceLoader&);
     Ref<Inspector::Protocol::Network::Metrics> buildObjectForMetrics(const NetworkLoadMetrics&);
     RefPtr<Inspector::Protocol::Network::Response> buildObjectForResourceResponse(const ResourceResponse&, ResourceLoader*);
index b3ae6ab..f82a677 100644 (file)
@@ -88,6 +88,7 @@
 #include "MemoryRelease.h"
 #include "NavigationDisabler.h"
 #include "NavigationScheduler.h"
+#include "Node.h"
 #include "Page.h"
 #include "PageCache.h"
 #include "PageTransitionEvent.h"
@@ -1644,7 +1645,7 @@ const ResourceRequest& FrameLoader::initialRequest() const
     return activeDocumentLoader()->originalRequest();
 }
 
-bool FrameLoader::willLoadMediaElementURL(URL& url)
+bool FrameLoader::willLoadMediaElementURL(URL& url, Node& initiatorNode)
 {
 #if PLATFORM(IOS)
     // MobileStore depends on the iOS 4.0 era client delegate method because webView:resource:willSendRequest:redirectResponse:fromDataSource
@@ -1654,6 +1655,7 @@ bool FrameLoader::willLoadMediaElementURL(URL& url)
 #endif
 
     ResourceRequest request(url);
+    request.setInspectorInitiatorNodeIdentifier(InspectorInstrumentation::identifierForNode(initiatorNode));
 
     unsigned long identifier;
     ResourceError error;
index 9ed7a5d..2bf0e0a 100644 (file)
@@ -72,6 +72,7 @@ class HistoryController;
 class HistoryItem;
 class NavigationAction;
 class NetworkingContext;
+class Node;
 class Page;
 class PolicyChecker;
 class ResourceError;
@@ -177,7 +178,7 @@ public:
     const ResourceRequest& initialRequest() const;
     void receivedMainResourceError(const ResourceError&);
 
-    bool willLoadMediaElementURL(URL&);
+    bool willLoadMediaElementURL(URL&, Node&);
 
     void handleFallbackContent();
 
index 5c28f82..8aa51c6 100644 (file)
@@ -37,6 +37,7 @@
 #include "HTMLNames.h"
 #include "HTMLObjectElement.h"
 #include "HTMLParserIdioms.h"
+#include "InspectorInstrumentation.h"
 #include "Page.h"
 #include "RenderImage.h"
 #include "RenderSVGImage.h"
@@ -179,7 +180,11 @@ void ImageLoader::updateFromElement()
         options.sameOriginDataURLFlag = SameOriginDataURLFlag::Set;
 
         auto crossOriginAttribute = element().attributeWithoutSynchronization(HTMLNames::crossoriginAttr);
-        auto request = createPotentialAccessControlRequest(document.completeURL(sourceURI(attr)), document, crossOriginAttribute, WTFMove(options));
+
+        ResourceRequest resourceRequest(document.completeURL(sourceURI(attr)));
+        resourceRequest.setInspectorInitiatorNodeIdentifier(InspectorInstrumentation::identifierForNode(m_element));
+
+        auto request = createPotentialAccessControlRequest(WTFMove(resourceRequest), document, crossOriginAttribute, WTFMove(options));
         request.setInitiator(element());
 
         if (m_loadManually) {
index b119133..bce2693 100644 (file)
@@ -35,6 +35,7 @@
 #include "CrossOriginAccessControl.h"
 #include "Document.h"
 #include "HTMLMediaElement.h"
+#include "InspectorInstrumentation.h"
 #include "SecurityOrigin.h"
 #include <wtf/NeverDestroyed.h>
 
@@ -69,6 +70,10 @@ RefPtr<PlatformMediaResource> MediaResourceLoader::requestResource(ResourceReque
     auto cachingPolicy = options & LoadOption::DisallowCaching ? CachingPolicy::DisallowCaching : CachingPolicy::AllowCaching;
 
     request.setRequester(ResourceRequest::Requester::Media);
+
+    if (m_mediaElement)
+        request.setInspectorInitiatorNodeIdentifier(InspectorInstrumentation::identifierForNode(*m_mediaElement));
+
 #if HAVE(AVFOUNDATION_LOADER_DELEGATE) && PLATFORM(MAC)
     // FIXME: Workaround for <rdar://problem/26071607>. We are not able to do CORS checking on 304 responses because they are usually missing the headers we need.
     if (!m_crossOriginMode.isNull())
index a72d0b4..75c9341 100644 (file)
@@ -35,6 +35,8 @@
 #include "CachedTextTrack.h"
 #include "CrossOriginAccessControl.h"
 #include "Document.h"
+#include "HTMLTrackElement.h"
+#include "InspectorInstrumentation.h"
 #include "Logging.h"
 #include "SharedBuffer.h"
 #include "VTTCue.h"
@@ -142,7 +144,7 @@ void TextTrackLoader::notifyFinished(CachedResource& resource)
     cancelLoad();
 }
 
-bool TextTrackLoader::load(const URL& url, const String& crossOriginMode, bool isInitiatingElementInUserAgentShadowTree)
+bool TextTrackLoader::load(const URL& url, HTMLTrackElement& element)
 {
     cancelLoad();
 
@@ -150,9 +152,14 @@ bool TextTrackLoader::load(const URL& url, const String& crossOriginMode, bool i
     Document& document = downcast<Document>(*m_scriptExecutionContext);
 
     ResourceLoaderOptions options = CachedResourceLoader::defaultCachedResourceOptions();
-    options.contentSecurityPolicyImposition = isInitiatingElementInUserAgentShadowTree ? ContentSecurityPolicyImposition::SkipPolicyCheck : ContentSecurityPolicyImposition::DoPolicyCheck;
+    options.contentSecurityPolicyImposition = element.isInUserAgentShadowTree() ? ContentSecurityPolicyImposition::SkipPolicyCheck : ContentSecurityPolicyImposition::DoPolicyCheck;
 
-    auto cueRequest = createPotentialAccessControlRequest(document.completeURL(url), document, crossOriginMode, WTFMove(options));
+    ResourceRequest resourceRequest(document.completeURL(url));
+
+    if (auto mediaElement = element.mediaElement())
+        resourceRequest.setInspectorInitiatorNodeIdentifier(InspectorInstrumentation::identifierForNode(*mediaElement));
+
+    auto cueRequest = createPotentialAccessControlRequest(WTFMove(resourceRequest), document, element.mediaElementCrossOriginAttribute(), WTFMove(options));
     m_resource = document.cachedResourceLoader().requestTextTrack(WTFMove(cueRequest)).value_or(nullptr);
     if (!m_resource)
         return false;
index 6001d5c..ebff82e 100644 (file)
@@ -37,6 +37,7 @@ namespace WebCore {
 
 class CachedTextTrack;
 class Document;
+class HTMLTrackElement;
 class TextTrackLoader;
 class ScriptExecutionContext;
 
@@ -55,8 +56,8 @@ class TextTrackLoader : public CachedResourceClient, private WebVTTParserClient
 public:
     TextTrackLoader(TextTrackLoaderClient&, ScriptExecutionContext*);
     virtual ~TextTrackLoader();
-    
-    bool load(const URL&, const String& crossOriginMode, bool isInitiatingElementInUserAgentShadowTree);
+
+    bool load(const URL&, HTMLTrackElement&);
     void cancelLoad();
     void getNewCues(Vector<RefPtr<TextTrackCue>>& outputCues);
     void getNewRegions(Vector<RefPtr<VTTRegion>>& outputRegions);
index b501a85..7dc49b7 100644 (file)
@@ -600,6 +600,8 @@ bool CachedResourceLoader::shouldContinueAfterNotifyingLoadedFromMemoryCache(con
 
     ResourceRequest newRequest = ResourceRequest(resource.url());
     newRequest.setInitiatorIdentifier(request.resourceRequest().initiatorIdentifier());
+    if (auto inspectorInitiatorNodeIdentifier = request.resourceRequest().inspectorInitiatorNodeIdentifier())
+        newRequest.setInspectorInitiatorNodeIdentifier(*inspectorInitiatorNodeIdentifier);
     if (request.resourceRequest().hiddenFromInspector())
         newRequest.setHiddenFromInspector(true);
     frame()->loader().loadedResourceFromMemoryCache(resource, newRequest, error);
index ff58361..d0c7685 100644 (file)
@@ -67,6 +67,9 @@ void ResourceRequestBase::setAsIsolatedCopy(const ResourceRequest& other)
     setInitiatorIdentifier(other.initiatorIdentifier().isolatedCopy());
     setCachePartition(other.cachePartition().isolatedCopy());
 
+    if (auto inspectorInitiatorNodeIdentifier = other.inspectorInitiatorNodeIdentifier())
+        setInspectorInitiatorNodeIdentifier(*inspectorInitiatorNodeIdentifier);
+
     if (!other.isSameSiteUnspecified()) {
         setIsSameSite(other.isSameSite());
         setIsTopSite(other.isTopSite());
index 3af4352..d31c3bc 100644 (file)
@@ -172,6 +172,10 @@ public:
     String initiatorIdentifier() const { return m_initiatorIdentifier; }
     void setInitiatorIdentifier(const String& identifier) { m_initiatorIdentifier = identifier; }
 
+    // Additional information for the Inspector to be able to identify the node that initiated this request.
+    const std::optional<int>& inspectorInitiatorNodeIdentifier() const { return m_inspectorInitiatorNodeIdentifier; }
+    void setInspectorInitiatorNodeIdentifier(int inspectorInitiatorNodeIdentifier) { m_inspectorInitiatorNodeIdentifier = inspectorInitiatorNodeIdentifier; }
+
 #if USE(SYSTEM_PREVIEW)
     WEBCORE_EXPORT bool isSystemPreview() const;
     WEBCORE_EXPORT void setSystemPreview(bool);
@@ -232,6 +236,7 @@ protected:
     SameSiteDisposition m_sameSiteDisposition { SameSiteDisposition::Unspecified };
     ResourceLoadPriority m_priority { ResourceLoadPriority::Low };
     Requester m_requester { Requester::Unspecified };
+    std::optional<int> m_inspectorInitiatorNodeIdentifier;
     bool m_allowCookies { false };
     mutable bool m_resourceRequestUpdated { false };
     mutable bool m_platformRequestUpdated { false };
index 986721b..03204fc 100644 (file)
@@ -371,6 +371,7 @@ void ResourceRequest::updateFromDelegatePreservingOldProperties(const ResourceRe
     bool isHiddenFromInspector = hiddenFromInspector();
     auto oldRequester = requester();
     auto oldInitiatorIdentifier = initiatorIdentifier();
+    auto oldInspectorInitiatorNodeIdentifier = inspectorInitiatorNodeIdentifier();
 
     *this = delegateProvidedRequest;
 
@@ -379,6 +380,8 @@ void ResourceRequest::updateFromDelegatePreservingOldProperties(const ResourceRe
     setHiddenFromInspector(isHiddenFromInspector);
     setRequester(oldRequester);
     setInitiatorIdentifier(oldInitiatorIdentifier);
+    if (oldInspectorInitiatorNodeIdentifier)
+        setInspectorInitiatorNodeIdentifier(*oldInspectorInitiatorNodeIdentifier);
 }
 
 bool ResourceRequest::httpPipeliningEnabled()
index 2cf298d..8d3fa0b 100644 (file)
@@ -1,5 +1,83 @@
 2018-10-08  Devin Rousso  <drousso@apple.com>
 
+        Web Inspector: group media network entries by the node that triggered the request
+        https://bugs.webkit.org/show_bug.cgi?id=189606
+        <rdar://problem/44438527>
+
+        Reviewed by Brian Burg.
+
+        Introduces a `WI.NavigationItem` for changing whether network entries are grouped by the
+        node that initiated the load (if applicable). When grouped by node, a tree-like layout of
+        the table cells (including expand/collapse) is used for resources that share the same
+        initiator node. The values for the node's cell are based on it's initated resources.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Base/Setting.js:
+
+        * UserInterface/Controllers/DOMManager.js:
+        (WI.DOMManager):
+        (WI.DOMManager.prototype._mainResourceDidChange): Added.
+        Whenever the frame navigates, re-request the document so that `NetworkAgent` is able to send
+        valid `nodeId` for each request's `initiatorNode`. This means that the document should
+        always be available.
+
+        * UserInterface/Views/NetworkTableContentView.js:
+        (WI.NetworkTableContentView):
+        (WI.NetworkTableContentView.prototype.get filterNavigationItems):
+        (WI.NetworkTableContentView.prototype.closed):
+        (WI.NetworkTableContentView.prototype.reset):
+        (WI.NetworkTableContentView.prototype.tableSortChanged):
+        (WI.NetworkTableContentView.prototype.tableSelectedRowChanged):
+        (WI.NetworkTableContentView.prototype.tablePopulateCell):
+        (WI.NetworkTableContentView.prototype._populateNameCell.createIconElement): Added.
+        (WI.NetworkTableContentView.prototype._populateNameCell):
+        (WI.NetworkTableContentView.prototype._populateDomainCell.createIconAndText): Added.
+        (WI.NetworkTableContentView.prototype._populateDomainCell):
+        (WI.NetworkTableContentView.prototype._populateInitiatorCell):
+        (WI.NetworkTableContentView.prototype._populateTransferSizeCell):
+        (WI.NetworkTableContentView.prototype._generateSortComparator):
+        (WI.NetworkTableContentView.prototype._processPendingEntries):
+        (WI.NetworkTableContentView.prototype._updateEntryForResource.updateExistingEntry): Added.
+        (WI.NetworkTableContentView.prototype._updateEntryForResource):
+        (WI.NetworkTableContentView.prototype._insertResourceAndReloadTable):
+        (WI.NetworkTableContentView.prototype._entryForDOMNode): Added.
+        (WI.NetworkTableContentView.prototype._tryLinkResourceToDOMNode): Added.
+        (WI.NetworkTableContentView.prototype._uniqueValuesForDOMNodeEntry): Added.
+        (WI.NetworkTableContentView.prototype._updateFilteredEntries):
+        (WI.NetworkTableContentView.prototype._handleGroupByDOMNodeCheckedDidChange): Added.
+        * UserInterface/Views/NetworkTableContentView.css:
+        (.network-table .cell.dom-node.name .icon): Added.
+        (.network-table .cell.dom-node.name .disclosure): Added.
+        (body[dir=rtl] .network-table .cell.dom-node.name .disclosure): Added.
+        (.network-table:focus li.selected .cell.dom-node.name .disclosure): Added.
+        (.network-table .cell.dom-node.name .disclosure.expanded): Added.
+        (.network-table:focus li.selected .cell.node.name .disclosure.expanded): Added.
+        (.network-table .cell.grouped-by-node.name): Added.
+        (body[dir=ltr] .network-table .cell.grouped-by-node.name): Added.
+        (body[dir=rtl] .network-table .cell.grouped-by-node.name): Added.
+        (.network-table li:not(.selected) .cell:matches(.cache-type, .multiple)): Added.
+        (.network-table li.selected .cell.domain > .lock): Added.
+        (.network-table .cache-type): Deleted.
+        When two resources are added that share the same `initiatorNode`, insert a node entry into
+        the `WI.Table` before the first resource entry for that node (based on the current sort).
+        This node entry is added after the resource entries are filtered, so they won't appear in
+        the default entries list.
+
+        * UserInterface/Models/Resource.js:
+        (WI.Resource):
+        (WI.Resource.prototype.initiatorNode): Added.
+        (WI.Resource.prototype.requestedByteRange): Added.
+        * UserInterface/Controllers/NetworkManager.js:
+        (WI.NetworkManager.prototype.resourceRequestWillBeSent):
+        (WI.NetworkManager.prototype.resourceRequestWasServedFromMemoryCache):
+        (WI.NetworkManager.prototype._initiatorNodeFromPayload): Added.
+
+        * UserInterface/Images/Range.svg: Added.
+        * UserInterface/Views/ResourceIcons.css:
+        (.resource-icon.resource-type-range .icon): Added.
+
+2018-10-08  Devin Rousso  <drousso@apple.com>
+
         Web Inspector: clicking initiator link in Network Tab table doesn't automatically switch to Preview section
         https://bugs.webkit.org/show_bug.cgi?id=190286
 
index c4b46ee..2ce1b27 100644 (file)
@@ -57,6 +57,7 @@ localizedStrings["(async)"] = "(async)";
 localizedStrings["(disk)"] = "(disk)";
 localizedStrings["(many)"] = "(many)";
 localizedStrings["(memory)"] = "(memory)";
+localizedStrings["(multiple)"] = "(multiple)";
 localizedStrings["(program)"] = "(program)";
 localizedStrings["(service worker)"] = "(service worker)";
 localizedStrings["(uninitialized)"] = "(uninitialized)";
@@ -137,6 +138,7 @@ localizedStrings["Breakpoints"] = "Breakpoints";
 localizedStrings["Breakpoints disabled"] = "Breakpoints disabled";
 localizedStrings["Bubbling"] = "Bubbling";
 localizedStrings["Busy"] = "Busy";
+localizedStrings["Byte Range %s\u2013%s"] = "Byte Range %s\u2013%s";
 localizedStrings["Bytes Received"] = "Bytes Received";
 localizedStrings["Bytes Sent"] = "Bytes Sent";
 localizedStrings["CSP Hash"] = "CSP Hash";
index 4dbb798..ece64e0 100644 (file)
@@ -119,6 +119,7 @@ WI.settings = {
     showRulers: new WI.Setting("show-rulers", false),
     showAssertionFailuresBreakpoint: new WI.Setting("show-assertion-failures-breakpoint", true),
     showAllRequestsBreakpoint: new WI.Setting("show-all-requests-breakpoint", true),
+    groupByDOMNode: new WI.Setting("group-by-dom-node", false),
 
     // Experimental
     experimentalEnableMultiplePropertiesSelection: new WI.Setting("experimental-enable-multiple-properties-selection", false),
index 090d7d1..0488da6 100644 (file)
@@ -46,6 +46,8 @@ WI.DOMManager = class DOMManager extends WI.Object
         this._breakpointsForEventListeners = new Map;
 
         WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
+
+        this.ensureDocument();
     }
 
     // Public
@@ -628,8 +630,12 @@ WI.DOMManager = class DOMManager extends WI.Object
 
     _mainResourceDidChange(event)
     {
-        if (event.target.isMainFrame())
-            this._restoreSelectedNodeIsAllowed = true;
+        if (!event.target.isMainFrame())
+            return;
+
+        this._restoreSelectedNodeIsAllowed = true;
+
+        this.ensureDocument();
     }
 };
 
index 91bc4dd..afbf781 100644 (file)
@@ -257,6 +257,7 @@ WI.NetworkManager = class NetworkManager extends WI.Object
             requestSentTimestamp: elapsedTime,
             requestSentWalltime: walltime,
             initiatorSourceCodeLocation: this._initiatorSourceCodeLocationFromPayload(initiator),
+            initiatorNode: this._initiatorNodeFromPayload(initiator),
             originalRequestWillBeSentTimestamp,
         });
 
@@ -400,6 +401,7 @@ WI.NetworkManager = class NetworkManager extends WI.Object
             requestMethod: "GET",
             requestSentTimestamp: elapsedTime,
             initiatorSourceCodeLocation: this._initiatorSourceCodeLocationFromPayload(initiator),
+            initiatorNode: this._initiatorNodeFromPayload(initiator),
         });
         resource.updateForResponse(cachedResourcePayload.url, response.mimeType, cachedResourcePayload.type, response.headers, response.status, response.statusText, elapsedTime, response.timing, responseSource);
         resource.increaseSize(cachedResourcePayload.bodySize, elapsedTime);
@@ -705,6 +707,11 @@ WI.NetworkManager = class NetworkManager extends WI.Object
         return sourceCode.createSourceCodeLocation(lineNumber, columnNumber);
     }
 
+    _initiatorNodeFromPayload(initiatorPayload)
+    {
+        return WI.domManager.nodeForId(initiatorPayload.nodeId);
+    }
+
     _processServiceWorkerConfiguration(error, initializationPayload)
     {
         console.assert(this._waitingForMainFrameResourceTreePayload);
diff --git a/Source/WebInspectorUI/UserInterface/Images/Range.svg b/Source/WebInspectorUI/UserInterface/Images/Range.svg
new file mode 100644 (file)
index 0000000..605dca3
--- /dev/null
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2013 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <path fill="rgb(148, 183, 219)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 Z"/>
+    <path fill="rgb(106, 136, 170)" d="M 13 1 L 3 1 C 1.898438 1 1 1.898438 1 3 L 1 13 C 1 14.101562 1.898438 15 3 15 L 13 15 C 14.101562 15 15 14.101562 15 13 L 15 3 C 15 1.898438 14.101562 1 13 1 M 13 2 C 13.550781 2 14 2.449219 14 3 L 14 13 C 14 13.550781 13.550781 14 13 14 L 3 14 C 2.449219 14 2 13.550781 2 13 L 2 3 C 2 2.449219 2.449219 2 3 2 L 13 2"/>
+    <path fill="white" d="M 7 7.632812 L 7.027344 7.632812 C 8.28125 7.632812 9.0625 7.144531 9.0625 6.167969 C 9.0625 5.453125 8.347656 5.097656 7.230469 5.097656 L 7 5.097656 Z M 5 12 L 5 4 L 8.109375 4 C 9.800781 4 11 4.675781 11 6 C 11 6.496094 10.855469 6.941406 10.570312 7.347656 C 10.285156 7.753906 9.53125 8.050781 9.03125 8.265625 L 11.75 12 L 9.402344 12 L 7.34375 8.730469 L 7 8.730469 L 7 12 Z"/>
+    <path fill="rgb(113, 146, 184)" d="M 8.109375 3 L 4 3 L 4 13 L 8 13 L 8 11.644531 L 8.558594 12.53125 L 8.851562 13 L 13.714844 13 L 12.558594 11.410156 L 10.550781 8.652344 C 10.875 8.460938 11.175781 8.226562 11.386719 7.925781 C 11.792969 7.351562 12 6.703125 12 6 C 12 4.179688 10.472656 3 8.109375 3 M 7 7.632812 L 7.027344 7.632812 C 8.28125 7.632812 9.0625 7.144531 9.0625 6.167969 C 9.0625 5.453125 8.347656 5.097656 7.230469 5.097656 L 7 5.097656 L 7 7.632812 M 8.109375 4 C 9.796875 4 11 4.679688 11 6 C 11 6.492188 10.855469 6.941406 10.570312 7.347656 C 10.285156 7.753906 9.535156 8.050781 9.03125 8.265625 L 11.75 12 L 9.40625 12 L 7.34375 8.730469 L 7 8.730469 L 7 12 L 5 12 L 5 4 L 8.109375 4"/>
+</svg>
index 7f928d7..d4eb98f 100644 (file)
@@ -26,7 +26,7 @@
 
 WI.Resource = class Resource extends WI.SourceCode
 {
-    constructor(url, {mimeType, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, requestSentTimestamp, requestSentWalltime, initiatorSourceCodeLocation, originalRequestWillBeSentTimestamp} = {})
+    constructor(url, {mimeType, type, loaderIdentifier, targetId, requestIdentifier, requestMethod, requestHeaders, requestData, requestSentTimestamp, requestSentWalltime, initiatorSourceCodeLocation, initiatorNode, originalRequestWillBeSentTimestamp} = {})
     {
         super();
 
@@ -52,6 +52,7 @@ WI.Resource = class Resource extends WI.SourceCode
         this._responseCookies = null;
         this._parentFrame = null;
         this._initiatorSourceCodeLocation = initiatorSourceCodeLocation || null;
+        this._initiatorNode = initiatorNode || null;
         this._initiatedResources = [];
         this._originalRequestWillBeSentTimestamp = originalRequestWillBeSentTimestamp || null;
         this._requestSentTimestamp = requestSentTimestamp || NaN;
@@ -289,6 +290,7 @@ WI.Resource = class Resource extends WI.SourceCode
     get requestMethod() { return this._requestMethod; }
     get requestData() { return this._requestData; }
     get initiatorSourceCodeLocation() { return this._initiatorSourceCodeLocation; }
+    get initiatorNode() { return this._initiatorNode; }
     get initiatedResources() { return this._initiatedResources; }
     get originalRequestWillBeSentTimestamp() { return this._originalRequestWillBeSentTimestamp; }
     get statusCode() { return this._statusCode; }
@@ -564,6 +566,27 @@ WI.Resource = class Resource extends WI.SourceCode
         return !!(contentEncoding && /\b(?:gzip|deflate)\b/.test(contentEncoding));
     }
 
+    get requestedByteRange()
+    {
+        let range = this._requestHeaders.valueForCaseInsensitiveKey("Range");
+        if (!range)
+            return null;
+
+        let rangeValues = range.match(/bytes=(\d+)-(\d+)/);
+        if (!rangeValues)
+            return null;
+
+        let start = parseInt(rangeValues[1]);
+        if (isNaN(start))
+            return null;
+
+        let end = parseInt(rangeValues[2]);
+        if (isNaN(end))
+            return null;
+
+        return {start, end};
+    }
+
     get scripts()
     {
         return this._scripts || [];
index 622caf5..eb8c335 100644 (file)
     cursor: pointer;
 }
 
-.network-table .cache-type {
+.network-table .cell.dom-node.name .icon {
+    content: url(../Images/DOMElement.svg);
+}
+
+.network-table .cell.dom-node.name .disclosure {
+    width: 13px;
+    height: 13px;
+    vertical-align: -2px;
+    content: url(../Images/DisclosureTriangles.svg#closed-normal);
+    background-size: 13px 13px;
+    background-repeat: no-repeat;
+}
+
+body[dir=rtl] .network-table .cell.dom-node.name .disclosure {
+    transform: scaleX(-1);
+}
+
+.network-table:focus li.selected .cell.dom-node.name .disclosure {
+    content: url(../Images/DisclosureTriangles.svg#closed-selected);
+}
+
+.network-table .cell.dom-node.name .disclosure.expanded {
+    content: url(../Images/DisclosureTriangles.svg#open-normal);
+}
+
+.network-table:focus li.selected .cell.dom-node.name .disclosure.expanded {
+    content: url(../Images/DisclosureTriangles.svg#open-selected);
+}
+
+.network-table .cell.grouped-by-node.name {
+    --item-padding-start: 19px;
+}
+
+body[dir=ltr] .network-table .cell.grouped-by-node.name {
+    padding-left: var(--item-padding-start);
+}
+
+body[dir=rtl] .network-table .cell.grouped-by-node.name {
+    padding-right: var(--item-padding-start);
+}
+
+.network-table li:not(.selected) .cell:matches(.cache-type, .multiple) {
     color: var(--text-color-gray-medium);
 }
 
     -webkit-margin-end: 5px;
 }
 
+.network-table li.selected .cell.domain > .lock {
+    /* FIXME: <https://webkit.org/b/189773> Web Inspector: create special Network waterfall for media events */
+    filter: invert();
+}
+
 body[dir=ltr] .network-table .cell.name > .status {
     float: right;
     margin-left: 4px;
index cae8669..3e55bac 100644 (file)
@@ -44,6 +44,8 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         this._resourceDetailView = null;
         this._resourceDetailViewMap = new Map;
 
+        this._domNodeEntries = new Map;
+
         this._waterfallStartTime = NaN;
         this._waterfallEndTime = NaN;
         this._waterfallTimelineRuler = null;
@@ -83,6 +85,9 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         this._typeFilterScopeBar = new WI.ScopeBar("network-type-filter-scope-bar", typeFilterScopeBarItems, typeFilterScopeBarItems[0]);
         this._typeFilterScopeBar.addEventListener(WI.ScopeBar.Event.SelectionChanged, this._typeFilterScopeBarSelectionChanged, this);
 
+        this._groupByDOMNodeNavigationItem = new WI.CheckboxNavigationItem("group-by-node", WI.UIString("Group by Node"), WI.settings.groupByDOMNode.value);
+        this._groupByDOMNodeNavigationItem.addEventListener(WI.CheckboxNavigationItem.Event.CheckedDidChange, this._handleGroupByDOMNodeCheckedDidChange, this);
+
         this._urlFilterSearchText = null;
         this._urlFilterSearchRegex = null;
         this._urlFilterIsActive = false;
@@ -201,7 +206,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     get filterNavigationItems()
     {
-        return [this._urlFilterNavigationItem, this._typeFilterScopeBar];
+        return [this._urlFilterNavigationItem, this._typeFilterScopeBar, this._groupByDOMNodeNavigationItem];
     }
 
     get supportsSave()
@@ -241,6 +246,8 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             detailView.dispose();
         this._resourceDetailViewMap.clear();
 
+        this._domNodeEntries.clear();
+
         this._hidePopover();
         this._hideResourceDetailView();
 
@@ -264,6 +271,8 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             detailView.dispose();
         this._resourceDetailViewMap.clear();
 
+        this._domNodeEntries.clear();
+
         this._waterfallStartTime = NaN;
         this._waterfallEndTime = NaN;
         this._updateWaterfallTimelineRuler();
@@ -320,7 +329,11 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
         this._hideResourceDetailView();
 
-        this._entries = this._entries.sort(this._entriesSortComparator);
+        for (let nodeEntry of this._domNodeEntries.values())
+            nodeEntry.initiatedResourceEntries.sort(this._entriesSortComparator);
+
+        this._entries.sort(this._entriesSortComparator);
+
         this._updateFilteredEntries();
         this._table.reloadData();
     }
@@ -361,14 +374,34 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             return;
 
         this._selectedResource = entry.resource;
-        this._showResourceDetailView(this._selectedResource);
+        if (this._selectedResource)
+            this._showResourceDetailView(this._selectedResource);
+        else
+            this._hideResourceDetailView();
     }
 
     tablePopulateCell(table, cell, column, rowIndex)
     {
         let entry = this._filteredEntries[rowIndex];
 
-        cell.classList.toggle("error", entry.resource.hadLoadingError());
+        if (entry.resource)
+            cell.classList.toggle("error", entry.resource.hadLoadingError());
+
+        let setTextContent = (accessor) => {
+            let uniqueValues = this._uniqueValuesForDOMNodeEntry(entry, accessor);
+            if (uniqueValues) {
+                if (uniqueValues.size > 1) {
+                    cell.classList.add("multiple");
+                    cell.textContent = WI.UIString("(multiple)");
+                    return;
+                }
+
+                cell.textContent = uniqueValues.values().next().value || emDash;
+                return;
+            }
+
+            cell.textContent = accessor(entry) || emDash;
+        };
 
         switch (column.identifier) {
         case "name":
@@ -378,45 +411,55 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             this._populateDomainCell(cell, entry);
             break;
         case "type":
-            cell.textContent = entry.displayType || emDash;
+            setTextContent((resourceEntry) => resourceEntry.displayType);
             break;
         case "mimeType":
-            cell.textContent = entry.mimeType || emDash;
+            setTextContent((resourceEntry) => resourceEntry.mimeType);
             break;
         case "method":
-            cell.textContent = entry.method || emDash;
+            setTextContent((resourceEntry) => resourceEntry.method);
             break;
         case "scheme":
-            cell.textContent = entry.scheme || emDash;
+            setTextContent((resourceEntry) => resourceEntry.scheme);
             break;
         case "status":
-            cell.textContent = entry.status || emDash;
+            setTextContent((resourceEntry) => resourceEntry.status);
             break;
         case "protocol":
-            cell.textContent = entry.protocol || emDash;
+            setTextContent((resourceEntry) => resourceEntry.protocol);
             break;
         case "initiator":
             this._populateInitiatorCell(cell, entry);
             break;
         case "priority":
-            cell.textContent = WI.Resource.displayNameForPriority(entry.priority) || emDash;
+            setTextContent((resourceEntry) => WI.Resource.displayNameForPriority(resourceEntry.priority));
             break;
         case "remoteAddress":
-            cell.textContent = entry.remoteAddress || emDash;
+            setTextContent((resourceEntry) => resourceEntry.remoteAddress);
             break;
         case "connectionIdentifier":
-            cell.textContent = entry.connectionIdentifier || emDash;
+            setTextContent((resourceEntry) => resourceEntry.connectionIdentifier);
             break;
-        case "resourceSize":
-            cell.textContent = isNaN(entry.resourceSize) ? emDash : Number.bytesToString(entry.resourceSize);
+        case "resourceSize": {
+            let resourceSize = entry.resourceSize;
+            let resourceEntries = entry.initiatedResourceEntries;
+            if (resourceEntries)
+                resourceSize = resourceEntries.reduce((accumulator, current) => accumulator + (current.resourceSize || 0), 0);
+            cell.textContent = isNaN(resourceSize) ? emDash : Number.bytesToString(resourceSize);
             break;
+        }
         case "transferSize":
             this._populateTransferSizeCell(cell, entry);
             break;
-        case "time":
+        case "time": {
             // FIXME: <https://webkit.org/b/176748> Web Inspector: Frontend sometimes receives resources with negative duration (responseEnd - requestStart)
-            cell.textContent = isNaN(entry.time) ? emDash : Number.secondsToString(Math.max(entry.time, 0));
+            let time = entry.time;
+            let resourceEntries = entry.initiatedResourceEntries;
+            if (resourceEntries)
+                time = resourceEntries.reduce((accumulator, current) => accumulator + (current.time || 0), 0);
+            cell.textContent = isNaN(time) ? emDash : Number.secondsToString(Math.max(time, 0));
             break;
+        }
         case "waterfall":
             this._populateWaterfallGraph(cell, entry);
             break;
@@ -431,6 +474,30 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
     {
         console.assert(!cell.firstChild, "We expect the cell to be empty.", cell, cell.firstChild);
 
+        function createIconElement() {
+            let iconElement = cell.appendChild(document.createElement("img"));
+            iconElement.className = "icon";
+        }
+
+        let domNode = entry.domNode;
+        if (domNode) {
+            let disclosureElement = cell.appendChild(document.createElement("img"));
+            disclosureElement.classList.add("disclosure");
+            disclosureElement.classList.toggle("expanded", !!entry.expanded);
+            disclosureElement.addEventListener("click", (event) => {
+                entry.expanded = !entry.expanded;
+
+                this._updateFilteredEntries();
+                this._table.reloadData();
+            });
+
+            createIconElement();
+
+            cell.classList.add("dom-node");
+            cell.appendChild(WI.linkifyNodeReference(domNode));
+            return;
+        }
+
         let resource = entry.resource;
         if (resource.isLoading()) {
             let statusElement = cell.appendChild(document.createElement("div"));
@@ -439,36 +506,76 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             statusElement.appendChild(spinner.element);
         }
 
-        let iconElement = cell.appendChild(document.createElement("img"));
-        iconElement.className = "icon";
-        cell.classList.add(WI.ResourceTreeElement.ResourceIconStyleClassName, entry.resource.type);
+        createIconElement();
+
+        cell.classList.add(WI.ResourceTreeElement.ResourceIconStyleClassName);
 
         let nameElement = cell.appendChild(document.createElement("span"));
-        nameElement.textContent = entry.name;
 
-        cell.title = entry.resource.url;
+        if (WI.settings.groupByDOMNode.value && resource.initiatorNode) {
+            let nodeEntry = this._domNodeEntries.get(resource.initiatorNode);
+            if (nodeEntry.initiatedResourceEntries.length > 1) {
+                cell.classList.add("grouped-by-node");
+
+                let range = resource.requestedByteRange;
+                if (range) {
+                    cell.classList.add("resource-type-range");
+                    nameElement.textContent = WI.UIString("Byte Range %s\u2013%s").format(range.start, range.end);
+                }
+            }
+        }
+
+        if (!nameElement.textContent) {
+            cell.classList.add(resource.type);
+            nameElement.textContent = entry.name;
+        }
+
+        cell.title = resource.url;
     }
 
     _populateDomainCell(cell, entry)
     {
         console.assert(!cell.firstChild, "We expect the cell to be empty.", cell, cell.firstChild);
 
-        if (!entry.domain) {
-            cell.textContent = emDash;
+        function createIconAndText(scheme, domain) {
+            let secure = scheme === "https" || scheme === "wss";
+            if (secure) {
+                let lockIconElement = cell.appendChild(document.createElement("img"));
+                lockIconElement.className = "lock";
+            }
+
+            cell.append(domain);
+        }
+
+        let uniqueSchemeValues = this._uniqueValuesForDOMNodeEntry(entry, (resourceEntry) => resourceEntry.scheme);
+        let uniqueDomainValues = this._uniqueValuesForDOMNodeEntry(entry, (resourceEntry) => resourceEntry.domain);
+        if (uniqueSchemeValues && uniqueDomainValues) {
+            if (uniqueSchemeValues.size > 1 || uniqueDomainValues.size > 1) {
+                cell.classList.add("multiple");
+                cell.textContent = WI.UIString("(multiple)");
+                return;
+            }
+
+            createIconAndText(uniqueSchemeValues.values().next().value, uniqueDomainValues.values().next().value);
             return;
         }
 
-        let secure = entry.scheme === "https" || entry.scheme === "wss";
-        if (secure) {
-            let lockIconElement = cell.appendChild(document.createElement("img"));
-            lockIconElement.className = "lock";
+        if (!entry.domain) {
+            cell.textContent = emDash;
+            return;
         }
 
-        cell.append(entry.domain);
+        createIconAndText(entry.scheme, entry.domain);
     }
 
     _populateInitiatorCell(cell, entry)
     {
+        let domNode = entry.domNode;
+        if (domNode) {
+            cell.textContent = emDash;
+            return;
+        }
+
         let initiatorLocation = entry.resource.initiatorSourceCodeLocation;
         if (!initiatorLocation) {
             cell.textContent = emDash;
@@ -484,6 +591,31 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
     _populateTransferSizeCell(cell, entry)
     {
+        let resourceEntries = entry.initiatedResourceEntries;
+        if (resourceEntries) {
+            if (resourceEntries.every((resourceEntry) => resourceEntry.resource.responseSource === WI.Resource.ResponseSource.MemoryCache)) {
+                cell.classList.add("cache-type");
+                cell.textContent = WI.UIString("(memory)");
+                return;
+            }
+            if (resourceEntries.every((resourceEntry) => resourceEntry.resource.responseSource === WI.Resource.ResponseSource.DiskCache)) {
+                cell.classList.add("cache-type");
+                cell.textContent = WI.UIString("(disk)");
+                return;
+            }
+            if (resourceEntries.every((resourceEntry) => resourceEntry.resource.responseSource === WI.Resource.ResponseSource.ServiceWorker)) {
+                cell.classList.add("cache-type");
+                cell.textContent = WI.UIString("(service worker)");
+                return;
+            }
+            let transferSize = resourceEntries.reduce((accumulator, current) => accumulator + (current.transferSize || 0), 0);
+            if (isNaN(transferSize))
+                cell.textContent = emDash;
+            else
+                cell.textContent = Number.bytesToString(transferSize);
+            return;
+        }
+
         let responseSource = entry.resource.responseSource;
         if (responseSource === WI.Resource.ResponseSource.MemoryCache) {
             cell.classList.add("cache-type");
@@ -510,6 +642,12 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
     {
         cell.removeChildren();
 
+        let domNode = entry.domNode;
+        if (domNode) {
+            // FIXME: <https://webkit.org/b/189773> Web Inspector: create special Network waterfall for media events
+            return;
+        }
+
         let resource = entry.resource;
         if (!resource.hasResponse()) {
             cell.textContent = zeroWidthSpace;
@@ -667,7 +805,7 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
 
         case "waterfall":
             // Sort by startTime number.
-            comparator = comparator = (a, b) => a.startTime - b.startTime;
+            comparator = (a, b) => a.startTime - b.startTime;
             break;
 
         default:
@@ -676,7 +814,21 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         }
 
         let reverseFactor = this._table.sortOrder === WI.Table.SortOrder.Ascending ? 1 : -1;
-        this._entriesSortComparator = (a, b) => reverseFactor * comparator(a, b);
+
+        // If the entry has an `initiatorNode`, use that node's "first" resource as the value of
+        // `entry`, so long as the entry being compared to doesn't have the same `initiatorNode`.
+        // This will ensure that all resource entries for a given `initiatorNode` will appear right
+        // next to each other, as they will all effectively be sorted by the first resource.
+        let substitute = (entry, other) => {
+            if (WI.settings.groupByDOMNode.value && entry.resource.initiatorNode) {
+                let nodeEntry = this._domNodeEntries.get(entry.resource.initiatorNode);
+                if (!nodeEntry.initiatedResourceEntries.includes(other))
+                    return nodeEntry.initiatedResourceEntries[0];
+            }
+            return entry;
+        };
+
+        this._entriesSortComparator = (a, b) => reverseFactor * comparator(substitute(a, b), substitute(b, a));
     }
 
     // Protected
@@ -884,8 +1036,11 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             return;
         }
 
-        for (let resource of this._pendingInsertions)
-            this._entries.push(this._entryForResource(resource));
+        for (let resource of this._pendingInsertions) {
+            let resourceEntry = this._entryForResource(resource);
+            this._tryLinkResourceToDOMNode(resourceEntry);
+            this._entries.push(resourceEntry);
+        }
         this._pendingInsertions = [];
 
         for (let resource of this._pendingUpdates)
@@ -952,14 +1107,20 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         if (index === -1)
             return;
 
+        // Don't wipe out the previous entry, as it may be used by a node entry.
+        function updateExistingEntry(existingEntry, newEntry) {
+            for (let key in newEntry)
+                existingEntry[key] = newEntry[key];
+        }
+
         let entry = this._entryForResource(resource);
-        this._entries[index] = entry;
+        updateExistingEntry(this._entries[index], entry);
 
         let rowIndex = this._rowIndexForResource(resource);
         if (rowIndex === -1)
             return;
 
-        this._filteredEntries[rowIndex] = entry;
+        updateExistingEntry(this._filteredEntries[rowIndex], entry);
     }
 
     _hidePopover()
@@ -1186,26 +1347,34 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
             return;
         }
 
-        let entry = this._entryForResource(resource);
+        let resourceEntry = this._entryForResource(resource);
 
-        // Default sort has fast path.
-        if (this._isDefaultSort() || !this._entriesSortComparator) {
-            this._entries.push(entry);
-            if (this._passFilter(entry)) {
-                this._filteredEntries.push(entry);
+        this._tryLinkResourceToDOMNode(resourceEntry);
+
+        if (WI.settings.groupByDOMNode.value && resource.initiatorNode) {
+            if (!this._entriesSortComparator)
+                this._generateSortComparator();
+        } else if (this._isDefaultSort() || !this._entriesSortComparator) {
+            // Default sort has fast path.
+            this._entries.push(resourceEntry);
+            if (this._passFilter(resourceEntry)) {
+                this._filteredEntries.push(resourceEntry);
                 this._table.reloadDataAddedToEndOnly();
             }
             return;
         }
 
-        insertObjectIntoSortedArray(entry, this._entries, this._entriesSortComparator);
+        insertObjectIntoSortedArray(resourceEntry, this._entries, this._entriesSortComparator);
 
-        if (this._passFilter(entry)) {
-            insertObjectIntoSortedArray(entry, this._filteredEntries, this._entriesSortComparator);
+        if (this._passFilter(resourceEntry)) {
+            if (WI.settings.groupByDOMNode.value)
+                this._updateFilteredEntries();
+            else
+                insertObjectIntoSortedArray(resourceEntry, this._filteredEntries, this._entriesSortComparator);
 
             // Probably a useless optimization here, but if we only added this row to the end
             // we may avoid recreating all visible rows by saying as such.
-            if (this._filteredEntries.lastValue === entry)
+            if (this._filteredEntries.lastValue === resourceEntry)
                 this._table.reloadDataAddedToEndOnly();
             else
                 this._table.reloadData();
@@ -1240,6 +1409,47 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         };
     }
 
+    _entryForDOMNode(domNode)
+    {
+        return {
+            domNode,
+            initiatedResourceEntries: [],
+            expanded: true,
+        };
+    }
+
+    _tryLinkResourceToDOMNode(resourceEntry)
+    {
+        let resource = resourceEntry.resource;
+        if (!resource || !resource.initiatorNode)
+            return;
+
+        let nodeEntry = this._domNodeEntries.get(resource.initiatorNode);
+        if (!nodeEntry) {
+            nodeEntry = this._entryForDOMNode(resource.initiatorNode, Object.keys(resourceEntry));
+            this._domNodeEntries.set(resource.initiatorNode, nodeEntry);
+        }
+
+        if (!this._entriesSortComparator)
+            this._generateSortComparator();
+
+        insertObjectIntoSortedArray(resourceEntry, nodeEntry.initiatedResourceEntries, this._entriesSortComparator);
+    }
+
+    _uniqueValuesForDOMNodeEntry(nodeEntry, accessor)
+    {
+        let resourceEntries = nodeEntry.initiatedResourceEntries;
+        if (!resourceEntries)
+            return null;
+
+        return resourceEntries.reduce((accumulator, current) => {
+            let value = accessor(current);
+            if (value || typeof value === "number")
+                accumulator.add(value);
+            return accumulator;
+        }, new Set);
+    }
+
     _hasTypeFilter()
     {
         return !!this._activeTypeFilters;
@@ -1291,6 +1501,37 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         else
             this._filteredEntries = this._entries.slice();
 
+        if (WI.settings.groupByDOMNode.value) {
+            for (let nodeEntry of this._domNodeEntries.values()) {
+                if (nodeEntry.initiatedResourceEntries.length < 2)
+                    continue;
+
+                let firstIndex = Infinity;
+                for (let resourceEntry of nodeEntry.initiatedResourceEntries) {
+                    if (this._hasActiveFilter() && !this._passFilter(resourceEntry))
+                        continue;
+
+                    let index = this._filteredEntries.indexOf(resourceEntry);
+                    if (index >= 0 && index < firstIndex)
+                        firstIndex = index;
+                }
+
+                if (!isFinite(firstIndex))
+                    continue;
+
+                this._filteredEntries.insertAtIndex(nodeEntry, firstIndex);
+            }
+
+            this._filteredEntries = this._filteredEntries.filter((entry) => {
+                if (entry.resource && entry.resource.initiatorNode) {
+                    let nodeEntry = this._domNodeEntries.get(entry.resource.initiatorNode);
+                    if (!nodeEntry.expanded)
+                        return false;
+                }
+                return true;
+            });
+        }
+
         this._restoreSelectedRow();
 
         this._updateURLFilterActiveIndicator();
@@ -1362,6 +1603,14 @@ WI.NetworkTableContentView = class NetworkTableContentView extends WI.ContentVie
         this._table.reloadData();
     }
 
+    _handleGroupByDOMNodeCheckedDidChange(event)
+    {
+        WI.settings.groupByDOMNode.value = this._groupByDOMNodeNavigationItem.checked;
+
+        this._updateSortAndFilteredEntries();
+        this._table.reloadData();
+    }
+
     _urlFilterDidChange(event)
     {
         let searchQuery = this._urlFilterNavigationItem.filterBar.filters.text;
index d1544dc..fc0cce7 100644 (file)
     content: url(../Images/Beacon.svg);
 }
 
+.resource-icon.resource-type-range .icon {
+    content: url(../Images/Range.svg);
+}
+
 .large .resource-icon .icon {
     content: image-set(url(../Images/DocumentGenericLarge.png) 1x, url(../Images/DocumentGenericLarge@2x.png) 2x);
 }