Web Inspector: Show all elements currently using a given CSS Canvas
authordrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Jul 2017 21:30:47 +0000 (21:30 +0000)
committerdrousso@apple.com <drousso@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 7 Jul 2017 21:30:47 +0000 (21:30 +0000)
https://bugs.webkit.org/show_bug.cgi?id=173965

Reviewed by Joseph Pecoraro.

Source/JavaScriptCore:

* inspector/protocol/Canvas.json:
 - Add `requestCSSCanvasClientNodes` command for getting the node IDs all nodes using this
   canvas via -webkit-canvas.
 - Add `cssCanvasClientNodesChanged` event that is dispatched whenever a node is
   added/removed from the list of -webkit-canvas clients.

Source/WebCore:

Test: inspector/canvas/css-canvas-clients.html

* css/CSSImageGeneratorValue.cpp:
(WebCore::CSSImageGeneratorValue::addClient):
(WebCore::CSSImageGeneratorValue::removeClient):
* css/CSSImageGeneratorValue.h:
(WebCore::CSSImageGeneratorValue::clients):
* html/HTMLCanvasElement.cpp:
(WebCore::HTMLCanvasElement::addObserver):
(WebCore::HTMLCanvasElement::removeObserver):
(WebCore::HTMLCanvasElement::cssCanvasClients):
Each time an observer is added/removed for a given HTMLCanvasElement, send an event to the
inspector frontend that the CSS canvas client nodes have changed. Additionally, anytime a
client/use is added/removed from one of the observing CSSCanvasValue, fire the same event.

* css/CSSCanvasValue.h:
(isType):
* html/HTMLCanvasElement.h:
(WebCore::CanvasObserver::isCSSCanvasValueObserver):
Allows type traits to distinguish CanvasObserver from CSSCanvasValue::CanvasObserverProxy.

* inspector/InspectorCanvasAgent.h:
* inspector/InspectorCanvasAgent.cpp:
(WebCore::InspectorCanvasAgent::requestCSSCanvasClientNodes):
(WebCore::InspectorCanvasAgent::didChangeCSSCanvasClientNodes):
* inspector/InspectorInstrumentation.h:
(WebCore::InspectorInstrumentation::didChangeCSSCanvasClientNodes):
* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::didChangeCSSCanvasClientNodesImpl):
Notify the frontend that the list of client nodes has changed for the given canvas. Let the
frontend request the actual list of node IDs when it needs, possibly at a later time.

Source/WebInspectorUI:

* UserInterface/Controllers/CanvasManager.js:
(WebInspector.CanvasManager.prototype.cssCanvasClientNodesChanged):
* UserInterface/Models/Canvas.js:
(WebInspector.Canvas.prototype.requestCSSCanvasClientNodes):
(WebInspector.Canvas.prototype.cssCanvasClientNodesChanged):
* UserInterface/Protocol/CanvasObserver.js:
(WebInspector.CanvasObserver.prototype.cssCanvasClientNodesChanged):

* Localizations/en.lproj/localizedStrings.js:
* UserInterface/Views/CanvasDetailsSidebarPanel.js:
(WebInspector.CanvasDetailsSidebarPanel):
(WebInspector.CanvasDetailsSidebarPanel.prototype.set canvas):
(WebInspector.CanvasDetailsSidebarPanel.prototype.initialLayout):
(WebInspector.CanvasDetailsSidebarPanel.prototype.layout):
(WebInspector.CanvasDetailsSidebarPanel.prototype._refreshCSSCanvasSection):
(WebInspector.CanvasDetailsSidebarPanel.prototype._formatMemoryRow):
Add CSS section for CSS canvases. Currently displays a list of node links, each of which is
using the selected canvas via -webkit-canvas.

* UserInterface/Main.html:
* UserInterface/Views/CanvasDetailsSidebarPanel.css: Added.
(.sidebar > .panel.details.canvas .details-section > .content .row.simple > .value > .node-link):

* UserInterface/Controllers/DOMTreeManager.js:
(WebInspector.DOMTreeManager.prototype.ensureDocument):
* UserInterface/Models/Canvas.js:
(WebInspector.Canvas.prototype.requestNode):
* UserInterface/Views/SearchSidebarPanel.js:
(WebInspector.SearchSidebarPanel.prototype.performSearch):
Add convenience function that will call DOMAgent.getDocument with an empty function. Should
be used when it is necessary that the document has been sent to the frontend, but the
document node itself is not needed.

LayoutTests:

* inspector/canvas/css-canvas-clients-expected.txt: Added.
* inspector/canvas/css-canvas-clients.html: Added.
* platform/mac/TestExpectations:

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

26 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/canvas/css-canvas-clients-expected.txt [new file with mode: 0644]
LayoutTests/inspector/canvas/css-canvas-clients.html [new file with mode: 0644]
LayoutTests/platform/mac/TestExpectations
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/protocol/Canvas.json
Source/WebCore/ChangeLog
Source/WebCore/css/CSSCanvasValue.h
Source/WebCore/css/CSSImageGeneratorValue.cpp
Source/WebCore/css/CSSImageGeneratorValue.h
Source/WebCore/html/HTMLCanvasElement.cpp
Source/WebCore/html/HTMLCanvasElement.h
Source/WebCore/inspector/InspectorCanvasAgent.cpp
Source/WebCore/inspector/InspectorCanvasAgent.h
Source/WebCore/inspector/InspectorInstrumentation.cpp
Source/WebCore/inspector/InspectorInstrumentation.h
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Controllers/CanvasManager.js
Source/WebInspectorUI/UserInterface/Controllers/DOMTreeManager.js
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/Canvas.js
Source/WebInspectorUI/UserInterface/Protocol/CanvasObserver.js
Source/WebInspectorUI/UserInterface/Views/CanvasDetailsSidebarPanel.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/CanvasDetailsSidebarPanel.js
Source/WebInspectorUI/UserInterface/Views/SearchSidebarPanel.js

index 807de9d..e46fa1a 100644 (file)
@@ -1,3 +1,14 @@
+2017-07-07  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Show all elements currently using a given CSS Canvas
+        https://bugs.webkit.org/show_bug.cgi?id=173965
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/canvas/css-canvas-clients-expected.txt: Added.
+        * inspector/canvas/css-canvas-clients.html: Added.
+        * platform/mac/TestExpectations:
+
 2017-07-07  Matt Lewis  <jlewis3@apple.com>
 
         Adjusted test expectations for webrtc/video-replace-muted-track.html.
diff --git a/LayoutTests/inspector/canvas/css-canvas-clients-expected.txt b/LayoutTests/inspector/canvas/css-canvas-clients-expected.txt
new file mode 100644 (file)
index 0000000..0e91efb
--- /dev/null
@@ -0,0 +1,22 @@
+Test that CanvasAgent tracks changes in the client nodes of a CSS canvas.
+
+
+== Running test suite: Canvas.CSSCanvasClients
+-- Running test case: Canvas.CSSCanvasClients.InitialLoad
+PASS: CanvasManager should have one canvas.
+PASS: Canvas should have CSS name "css-canvas".
+PASS: There should be no client nodes.
+
+-- Running test case: Canvas.CSSCanvasClients.Create
+PASS: Canvas with created client should have CSS name "css-canvas".
+PASS: There should be one client node.
+PASS: Client node "div" is valid.
+
+-- Running test case: Canvas.CSSCanvasClients.Destroy
+PASS: Canvas with destroyed client should have CSS name "css-canvas".
+PASS: There should be no client nodes.
+
+-- Running test case: Canvas.CSSCanvasClients.InvalidCanvasId
+PASS: Should produce an error.
+Error: Invalid canvas identifier
+
diff --git a/LayoutTests/inspector/canvas/css-canvas-clients.html b/LayoutTests/inspector/canvas/css-canvas-clients.html
new file mode 100644 (file)
index 0000000..c10baf0
--- /dev/null
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function load() {
+    window.context2d = document.getCSSCanvasContext("2d", "css-canvas", 10, 10);
+
+    runTest();
+}
+
+let cssCanvasClients = [];
+
+function createCSSCanvasClient() {
+    cssCanvasClients.push(document.body.appendChild(document.createElement("div")));
+}
+
+function destroyCSSCanvasClients() {
+    for (let cssCanvasClient of cssCanvasClients)
+        cssCanvasClient.remove();
+
+    cssCanvasClients = [];
+
+    setTimeout(() => { GCController.collect(); }, 0);
+}
+
+function test() {
+    let suite = InspectorTest.createAsyncSuite("Canvas.CSSCanvasClients");
+
+    function logClientNodes(clientNodes) {
+        for (let clientNode of clientNodes) {
+            if (clientNode)
+                InspectorTest.pass(`Client node "${clientNode.appropriateSelectorFor()}" is valid.`);
+            else
+                InspectorTest.fail("Invalid client node.");
+        }
+    }
+
+    suite.addTestCase({
+        name: "Canvas.CSSCanvasClients.InitialLoad",
+        description: "Check that the CanvasManager has one CSS canvas initially.",
+        test(resolve, reject) {
+            let canvases = WebInspector.canvasManager.canvases;
+            InspectorTest.expectEqual(canvases.length, 1, "CanvasManager should have one canvas.");
+            if (!canvases.length) {
+                reject("Missing canvas.");
+                return;
+            }
+
+            InspectorTest.expectEqual(canvases[0].cssCanvasName, "css-canvas", `Canvas should have CSS name "css-canvas".`);
+            canvases[0].requestCSSCanvasClientNodes((clientNodes) => {
+                InspectorTest.expectEqual(clientNodes.length, 0, "There should be no client nodes.");
+                logClientNodes(clientNodes);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "Canvas.CSSCanvasClients.Create",
+        description: "Check that creating a CSS canvas client node is tracked correctly.",
+        test(resolve, reject) {
+            WebInspector.Canvas.awaitEvent(WebInspector.Canvas.Event.CSSCanvasClientNodesChanged)
+            .then((event) => {
+                InspectorTest.expectEqual(event.target.cssCanvasName, "css-canvas", `Canvas with created client should have CSS name "css-canvas".`);
+                event.target.requestCSSCanvasClientNodes((clientNodes) => {
+                    InspectorTest.expectEqual(clientNodes.length, 1, "There should be one client node.");
+                    logClientNodes(clientNodes);
+                    resolve();
+                });
+            });
+
+            InspectorTest.evaluateInPage(`createCSSCanvasClient()`);
+        }
+    });
+
+    suite.addTestCase({
+        name: "Canvas.CSSCanvasClients.Destroy",
+        description: "Check that destroying a CSS canvas client node is tracked correctly.",
+        test(resolve, reject) {
+            WebInspector.Canvas.awaitEvent(WebInspector.Canvas.Event.CSSCanvasClientNodesChanged)
+            .then((event) => {
+                InspectorTest.expectEqual(event.target.cssCanvasName, "css-canvas", `Canvas with destroyed client should have CSS name "css-canvas".`);
+                event.target.requestCSSCanvasClientNodes((clientNodes) => {
+                    InspectorTest.expectEqual(clientNodes.length, 0, "There should be no client nodes.");
+                    logClientNodes(clientNodes);
+                    resolve();
+                });
+            });
+
+            InspectorTest.evaluateInPage(`destroyCSSCanvasClients()`);
+        }
+    });
+
+    // ------
+
+    suite.addTestCase({
+        name: "Canvas.CSSCanvasClients.InvalidCanvasId",
+        description: "Invalid canvas identifiers should cause an error.",
+        test(resolve, reject) {
+            const canvasId = "DOES_NOT_EXIST";
+            CanvasAgent.requestCSSCanvasClientNodes(canvasId, (error, clientNodeIds) => {
+                InspectorTest.expectThat(error, "Should produce an error.");
+                InspectorTest.log("Error: " + error);
+                resolve();
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+<style>
+    div {
+        width: 10px;
+        height: 10px;
+        background-image: -webkit-canvas(css-canvas);
+    }
+</style>
+</head>
+<body onload="load()">
+    <p>Test that CanvasAgent tracks changes in the client nodes of a CSS canvas.</p>
+</body>
+</html>
index 63d2013..252076b 100644 (file)
@@ -1141,6 +1141,7 @@ webkit.org/b/148119 [ Yosemite ] fast/text/trak-optimizeLegibility.html [ Failur
 
 webkit.org/b/174066 inspector/canvas/create-context-webgl2.html [ Pass Timeout ]
 webkit.org/b/174066 inspector/canvas/create-context-webgpu.html [ Pass Timeout ]
+webkit.org/b/174272 inspector/canvas/css-canvas-clients.html [ Pass Timeout ]
 webkit.org/b/170615 inspector/codemirror/prettyprinting-css.html [ Pass Timeout ]
 webkit.org/b/153460 inspector/codemirror/prettyprinting-css-rules.html [ Pass Timeout ]
 webkit.org/b/160048 [ Debug ] inspector/codemirror/prettyprinting-javascript.html [ Pass Timeout ]
index e9c31a5..21188b3 100644 (file)
@@ -1,3 +1,16 @@
+2017-07-07  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Show all elements currently using a given CSS Canvas
+        https://bugs.webkit.org/show_bug.cgi?id=173965
+
+        Reviewed by Joseph Pecoraro.
+
+        * inspector/protocol/Canvas.json:
+         - Add `requestCSSCanvasClientNodes` command for getting the node IDs all nodes using this
+           canvas via -webkit-canvas.
+         - Add `cssCanvasClientNodesChanged` event that is dispatched whenever a node is
+           added/removed from the list of -webkit-canvas clients.
+
 2017-07-07  Mark Lam  <mark.lam@apple.com>
 
         \n\r is not the same as \r\n.
index 3e15537..d70d157 100644 (file)
             ]
         },
         {
+            "name": "requestCSSCanvasClientNodes",
+            "description": "Gets all the nodes that are using this canvas via -webkit-canvas.",
+            "parameters": [
+                { "name": "canvasId", "$ref": "CanvasId" }
+            ],
+            "returns": [
+                { "name": "clientNodeIds", "type": "array", "items": { "$ref": "DOM.NodeId" } }
+            ]
+        },
+        {
             "name": "resolveCanvasContext",
             "description": "Resolves JavaScript canvas context object for given canvasId.",
             "parameters": [
                 { "name": "canvasId", "$ref": "CanvasId", "description": "Identifier of canvas that changed." },
                 { "name": "memoryCost", "type": "number", "description": "New memory cost value for the canvas in bytes." }
             ]
+        },
+        {
+            "name": "cssCanvasClientNodesChanged",
+            "parameters": [
+                { "name": "canvasId", "$ref": "CanvasId", "description": "Identifier of canvas that changed." }
+            ]
         }
     ]
 }
index 5870553..5ec39da 100644 (file)
@@ -1,3 +1,42 @@
+2017-07-07  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Show all elements currently using a given CSS Canvas
+        https://bugs.webkit.org/show_bug.cgi?id=173965
+
+        Reviewed by Joseph Pecoraro.
+
+        Test: inspector/canvas/css-canvas-clients.html
+
+        * css/CSSImageGeneratorValue.cpp:
+        (WebCore::CSSImageGeneratorValue::addClient):
+        (WebCore::CSSImageGeneratorValue::removeClient):
+        * css/CSSImageGeneratorValue.h:
+        (WebCore::CSSImageGeneratorValue::clients):
+        * html/HTMLCanvasElement.cpp:
+        (WebCore::HTMLCanvasElement::addObserver):
+        (WebCore::HTMLCanvasElement::removeObserver):
+        (WebCore::HTMLCanvasElement::cssCanvasClients):
+        Each time an observer is added/removed for a given HTMLCanvasElement, send an event to the
+        inspector frontend that the CSS canvas client nodes have changed. Additionally, anytime a
+        client/use is added/removed from one of the observing CSSCanvasValue, fire the same event.
+
+        * css/CSSCanvasValue.h:
+        (isType):
+        * html/HTMLCanvasElement.h:
+        (WebCore::CanvasObserver::isCSSCanvasValueObserver):
+        Allows type traits to distinguish CanvasObserver from CSSCanvasValue::CanvasObserverProxy.
+
+        * inspector/InspectorCanvasAgent.h:
+        * inspector/InspectorCanvasAgent.cpp:
+        (WebCore::InspectorCanvasAgent::requestCSSCanvasClientNodes):
+        (WebCore::InspectorCanvasAgent::didChangeCSSCanvasClientNodes):
+        * inspector/InspectorInstrumentation.h:
+        (WebCore::InspectorInstrumentation::didChangeCSSCanvasClientNodes):
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::InspectorInstrumentation::didChangeCSSCanvasClientNodesImpl):
+        Notify the frontend that the list of client nodes has changed for the given canvas. Let the
+        frontend request the actual list of node IDs when it needs, possibly at a later time.
+
 2017-07-07  Jer Noble  <jer.noble@apple.com>
 
         AVPlayer can continue to be active after released by MediaPlayerPrivateAVFoundationObjC.
index 5c422ce..83b1625 100644 (file)
@@ -44,19 +44,13 @@ public:
     bool isFixedSize() const { return true; }
     FloatSize fixedSize(const RenderElement*);
 
+    HTMLCanvasElement* element() const { return m_element; }
+
     bool isPending() const { return false; }
     void loadSubimages(CachedResourceLoader&, const ResourceLoaderOptions&) { }
 
     bool equals(const CSSCanvasValue&) const;
 
-private:
-    explicit CSSCanvasValue(const String& name)
-        : CSSImageGeneratorValue(CanvasClass)
-        , m_canvasObserver(*this)
-        , m_name(name)
-    {
-    }
-
     // NOTE: We put the CanvasObserver in a member instead of inheriting from it
     // to avoid adding a vptr to CSSCanvasValue.
     class CanvasObserverProxy final : public CanvasObserver {
@@ -70,6 +64,10 @@ private:
         {
         }
 
+        bool isCanvasObserverProxy() const final { return true; }
+
+        const CSSCanvasValue& ownerValue() const { return m_ownerValue; }
+
     private:
         void canvasChanged(HTMLCanvasElement& canvas, const FloatRect& changedRect) final
         {
@@ -87,6 +85,14 @@ private:
         CSSCanvasValue& m_ownerValue;
     };
 
+private:
+    explicit CSSCanvasValue(const String& name)
+        : CSSImageGeneratorValue(CanvasClass)
+        , m_canvasObserver(*this)
+        , m_name(name)
+    {
+    }
+
     void canvasChanged(HTMLCanvasElement&, const FloatRect& changedRect);
     void canvasResized(HTMLCanvasElement&);
     void canvasDestroyed(HTMLCanvasElement&);
@@ -105,3 +111,7 @@ private:
 
 SPECIALIZE_TYPE_TRAITS_CSS_VALUE(CSSCanvasValue, isCanvasValue())
 
+SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::CSSCanvasValue::CanvasObserverProxy)
+    static bool isType(const WebCore::CanvasObserver& canvasObserver) { return canvasObserver.isCanvasObserverProxy(); }
+SPECIALIZE_TYPE_TRAITS_END()
+
index 5e155b5..9849491 100644 (file)
@@ -34,6 +34,8 @@
 #include "CSSImageValue.h"
 #include "CSSNamedImageValue.h"
 #include "GeneratedImage.h"
+#include "HTMLCanvasElement.h"
+#include "InspectorInstrumentation.h"
 #include "RenderElement.h"
 
 namespace WebCore {
@@ -69,13 +71,27 @@ void CSSImageGeneratorValue::addClient(RenderElement& renderer)
 {
     if (m_clients.isEmpty())
         ref();
+
     m_clients.add(&renderer);
+
+    if (is<CSSCanvasValue>(this)) {
+        if (HTMLCanvasElement* canvasElement = downcast<CSSCanvasValue>(this)->element())
+            InspectorInstrumentation::didChangeCSSCanvasClientNodes(*canvasElement);
+    }
 }
 
 void CSSImageGeneratorValue::removeClient(RenderElement& renderer)
 {
     ASSERT(m_clients.contains(&renderer));
-    if (m_clients.remove(&renderer) && m_clients.isEmpty())
+    if (!m_clients.remove(&renderer))
+        return;
+
+    if (is<CSSCanvasValue>(this)) {
+        if (HTMLCanvasElement* canvasElement = downcast<CSSCanvasValue>(this)->element())
+            InspectorInstrumentation::didChangeCSSCanvasClientNodes(*canvasElement);
+    }
+
+    if (m_clients.isEmpty())
         deref();
 }
 
index 2f89446..5eacb4e 100644 (file)
@@ -45,6 +45,7 @@ public:
 
     void addClient(RenderElement&);
     void removeClient(RenderElement&);
+    const HashCountedSet<RenderElement*>& clients() const { return m_clients; }
 
     RefPtr<Image> image(RenderElement&, const FloatSize&);
 
@@ -61,7 +62,6 @@ protected:
 
     GeneratedImage* cachedImageForSize(FloatSize);
     void saveCachedImageForSize(FloatSize, GeneratedImage&);
-    const HashCountedSet<RenderElement*>& clients() const { return m_clients; }
 
     // Helper functions for Crossfade and Filter.
     static CachedImage* cachedImageForCSSValue(CSSValue&, CachedResourceLoader&, const ResourceLoaderOptions&);
index 58a6878..32fea8c 100644 (file)
@@ -30,6 +30,7 @@
 
 #include "Blob.h"
 #include "BlobCallback.h"
+#include "CSSCanvasValue.h"
 #include "CanvasGradient.h"
 #include "CanvasPattern.h"
 #include "CanvasRenderingContext2D.h"
@@ -45,6 +46,7 @@
 #include "ImageData.h"
 #include "InspectorInstrumentation.h"
 #include "MIMETypeRegistry.h"
+#include "RenderElement.h"
 #include "RenderHTMLCanvas.h"
 #include "RuntimeEnabledFeatures.h"
 #include "ScriptController.h"
@@ -168,11 +170,33 @@ bool HTMLCanvasElement::canStartSelection() const
 void HTMLCanvasElement::addObserver(CanvasObserver& observer)
 {
     m_observers.add(&observer);
+
+    if (is<CSSCanvasValue::CanvasObserverProxy>(observer))
+        InspectorInstrumentation::didChangeCSSCanvasClientNodes(*this);
 }
 
 void HTMLCanvasElement::removeObserver(CanvasObserver& observer)
 {
     m_observers.remove(&observer);
+
+    if (is<CSSCanvasValue::CanvasObserverProxy>(observer))
+        InspectorInstrumentation::didChangeCSSCanvasClientNodes(*this);
+}
+
+HashSet<Element*> HTMLCanvasElement::cssCanvasClients() const
+{
+    HashSet<Element*> cssCanvasClients;
+    for (auto& observer : m_observers) {
+        if (!is<CSSCanvasValue::CanvasObserverProxy>(observer))
+            continue;
+
+        auto clients = downcast<CSSCanvasValue::CanvasObserverProxy>(observer)->ownerValue().clients();
+        for (auto& entry : clients) {
+            if (Element* element = entry.key->element())
+                cssCanvasClients.add(element);
+        }
+    }
+    return cssCanvasClients;
 }
 
 void HTMLCanvasElement::setHeight(unsigned value)
index f963362..1fdf8a3 100644 (file)
@@ -32,6 +32,7 @@
 #include "IntSize.h"
 #include <memory>
 #include <wtf/Forward.h>
+#include <wtf/HashSet.h>
 
 #if ENABLE(WEBGL)
 #include "WebGLContextAttributes.h"
@@ -58,6 +59,8 @@ class CanvasObserver {
 public:
     virtual ~CanvasObserver() { }
 
+    virtual bool isCanvasObserverProxy() const { return false; }
+
     virtual void canvasChanged(HTMLCanvasElement&, const FloatRect& changedRect) = 0;
     virtual void canvasResized(HTMLCanvasElement&) = 0;
     virtual void canvasDestroyed(HTMLCanvasElement&) = 0;
@@ -71,6 +74,7 @@ public:
 
     void addObserver(CanvasObserver&);
     void removeObserver(CanvasObserver&);
+    HashSet<Element*> cssCanvasClients() const;
 
     unsigned width() const { return size().width(); }
     unsigned height() const { return size().height(); }
index a6a5dba..20f0baa 100644 (file)
@@ -29,6 +29,7 @@
 #include "CanvasRenderingContext.h"
 #include "CanvasRenderingContext2D.h"
 #include "Document.h"
+#include "Element.h"
 #include "Frame.h"
 #include "InspectorDOMAgent.h"
 #include "InspectorPageAgent.h"
@@ -170,6 +171,21 @@ void InspectorCanvasAgent::requestContent(ErrorString& errorString, const String
         errorString = ASCIILiteral("Unsupported canvas context type");
 }
 
+void InspectorCanvasAgent::requestCSSCanvasClientNodes(ErrorString& errorString, const String& canvasId, RefPtr<Inspector::Protocol::Array<int>>& result)
+{
+    const CanvasEntry* canvasEntry = getCanvasEntry(canvasId);
+    if (!canvasEntry) {
+        errorString = ASCIILiteral("Invalid canvas identifier");
+        return;
+    }
+
+    result = Inspector::Protocol::Array<int>::create();
+    for (Element* element : canvasEntry->element->cssCanvasClients()) {
+        if (int documentNodeId = m_instrumentingAgents.inspectorDOMAgent()->boundNodeId(&element->document()))
+            result->addItem(m_instrumentingAgents.inspectorDOMAgent()->pushNodeToFrontend(errorString, documentNodeId, element));
+    }
+}
+
 static JSC::JSValue contextAsScriptValue(JSC::ExecState& state, CanvasRenderingContext* context)
 {
     JSC::JSLockHolder lock(&state);
@@ -250,6 +266,15 @@ void InspectorCanvasAgent::didCreateCSSCanvas(HTMLCanvasElement& canvasElement,
     m_canvasToCSSCanvasId.set(&canvasElement, name);
 }
 
+void InspectorCanvasAgent::didChangeCSSCanvasClientNodes(HTMLCanvasElement& canvasElement)
+{
+    const CanvasEntry* canvasEntry = getCanvasEntry(canvasElement);
+    if (!canvasEntry)
+        return;
+
+    m_frontendDispatcher->cssCanvasClientNodesChanged(canvasEntry->identifier);
+}
+
 void InspectorCanvasAgent::didCreateCanvasRenderingContext(HTMLCanvasElement& canvasElement)
 {
     if (m_canvasEntries.contains(&canvasElement)) {
index 733073d..0a0ea88 100644 (file)
@@ -63,11 +63,13 @@ public:
     void disable(ErrorString&) override;
     void requestNode(ErrorString&, const String& canvasId, int* nodeId) override;
     void requestContent(ErrorString&, const String& canvasId, String* content) override;
+    void requestCSSCanvasClientNodes(ErrorString&, const String& canvasId, RefPtr<Inspector::Protocol::Array<int>>&) override;
     void resolveCanvasContext(ErrorString&, const String& canvasId, const String* const objectGroup, RefPtr<Inspector::Protocol::Runtime::RemoteObject>&) override;
 
     // InspectorInstrumentation
     void frameNavigated(Frame&);
     void didCreateCSSCanvas(HTMLCanvasElement&, const String&);
+    void didChangeCSSCanvasClientNodes(HTMLCanvasElement&);
     void didCreateCanvasRenderingContext(HTMLCanvasElement&);
     void didChangeCanvasMemory(HTMLCanvasElement&);
 
index 7236442..be597b2 100644 (file)
@@ -999,6 +999,12 @@ void InspectorInstrumentation::didCreateCSSCanvasImpl(InstrumentingAgents* instr
         canvasAgent->didCreateCSSCanvas(canvasElement, name);
 }
 
+void InspectorInstrumentation::didChangeCSSCanvasClientNodesImpl(InstrumentingAgents* instrumentingAgents, HTMLCanvasElement& canvasElement)
+{
+    if (InspectorCanvasAgent* canvasAgent = instrumentingAgents->inspectorCanvasAgent())
+        canvasAgent->didChangeCSSCanvasClientNodes(canvasElement);
+}
+
 void InspectorInstrumentation::didCreateCanvasRenderingContextImpl(InstrumentingAgents* instrumentingAgents, HTMLCanvasElement& canvasElement)
 {
     if (InspectorCanvasAgent* canvasAgent = instrumentingAgents->inspectorCanvasAgent())
index 67ae35f..deeb319 100644 (file)
@@ -247,6 +247,7 @@ public:
 #endif
 
     static void didCreateCSSCanvas(HTMLCanvasElement&, const String&);
+    static void didChangeCSSCanvasClientNodes(HTMLCanvasElement&);
     static void didCreateCanvasRenderingContext(HTMLCanvasElement&);
     static void didChangeCanvasMemory(HTMLCanvasElement&);
 
@@ -423,6 +424,7 @@ private:
     static void updateApplicationCacheStatusImpl(InstrumentingAgents&, Frame&);
 
     static void didCreateCSSCanvasImpl(InstrumentingAgents*, HTMLCanvasElement&, const String&);
+    static void didChangeCSSCanvasClientNodesImpl(InstrumentingAgents*, HTMLCanvasElement&);
     static void didCreateCanvasRenderingContextImpl(InstrumentingAgents*, HTMLCanvasElement&);
     static void didChangeCanvasMemoryImpl(InstrumentingAgents*, HTMLCanvasElement&);
 
@@ -1189,7 +1191,14 @@ inline void InspectorInstrumentation::didCreateCSSCanvas(HTMLCanvasElement& canv
     if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForDocument(&canvasElement.document()))
         didCreateCSSCanvasImpl(instrumentingAgents, canvasElement, name);
 }
-    
+
+inline void InspectorInstrumentation::didChangeCSSCanvasClientNodes(HTMLCanvasElement& canvasElement)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForDocument(&canvasElement.document()))
+        didChangeCSSCanvasClientNodesImpl(instrumentingAgents, canvasElement);
+}
+
 inline void InspectorInstrumentation::didCreateCanvasRenderingContext(HTMLCanvasElement& canvasElement)
 {
     if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForDocument(&canvasElement.document()))
index 84ec85a..faabf83 100644 (file)
@@ -1,3 +1,43 @@
+2017-07-07  Devin Rousso  <drousso@apple.com>
+
+        Web Inspector: Show all elements currently using a given CSS Canvas
+        https://bugs.webkit.org/show_bug.cgi?id=173965
+
+        Reviewed by Joseph Pecoraro.
+
+        * UserInterface/Controllers/CanvasManager.js:
+        (WebInspector.CanvasManager.prototype.cssCanvasClientNodesChanged):
+        * UserInterface/Models/Canvas.js:
+        (WebInspector.Canvas.prototype.requestCSSCanvasClientNodes):
+        (WebInspector.Canvas.prototype.cssCanvasClientNodesChanged):
+        * UserInterface/Protocol/CanvasObserver.js:
+        (WebInspector.CanvasObserver.prototype.cssCanvasClientNodesChanged):
+
+        * Localizations/en.lproj/localizedStrings.js:
+        * UserInterface/Views/CanvasDetailsSidebarPanel.js:
+        (WebInspector.CanvasDetailsSidebarPanel):
+        (WebInspector.CanvasDetailsSidebarPanel.prototype.set canvas):
+        (WebInspector.CanvasDetailsSidebarPanel.prototype.initialLayout):
+        (WebInspector.CanvasDetailsSidebarPanel.prototype.layout):
+        (WebInspector.CanvasDetailsSidebarPanel.prototype._refreshCSSCanvasSection):
+        (WebInspector.CanvasDetailsSidebarPanel.prototype._formatMemoryRow):
+        Add CSS section for CSS canvases. Currently displays a list of node links, each of which is
+        using the selected canvas via -webkit-canvas.
+
+        * UserInterface/Main.html:
+        * UserInterface/Views/CanvasDetailsSidebarPanel.css: Added.
+        (.sidebar > .panel.details.canvas .details-section > .content .row.simple > .value > .node-link):
+
+        * UserInterface/Controllers/DOMTreeManager.js:
+        (WebInspector.DOMTreeManager.prototype.ensureDocument):
+        * UserInterface/Models/Canvas.js:
+        (WebInspector.Canvas.prototype.requestNode):
+        * UserInterface/Views/SearchSidebarPanel.js:
+        (WebInspector.SearchSidebarPanel.prototype.performSearch):
+        Add convenience function that will call DOMAgent.getDocument with an empty function. Should
+        be used when it is necessary that the document has been sent to the frontend, but the
+        document node itself is not needed.
+
 2017-07-07  Joseph Pecoraro  <pecoraro@apple.com>
 
         Web Inspector: Clean up some unnecessary constructors
index c95713e..0529eb5 100644 (file)
@@ -139,6 +139,7 @@ localizedStrings["Breakpoints disabled"] = "Breakpoints disabled";
 localizedStrings["Bubbling"] = "Bubbling";
 localizedStrings["Busy"] = "Busy";
 localizedStrings["CSP Hash"] = "CSP Hash";
+localizedStrings["CSS"] = "CSS";
 localizedStrings["CSS Canvas"] = "CSS Canvas";
 localizedStrings["CSS canvas ā€œ%sā€"] = "CSS canvas ā€œ%sā€";
 localizedStrings["Cached"] = "Cached";
@@ -588,6 +589,7 @@ localizedStrings["No message"] = "No message";
 localizedStrings["No preview available"] = "No preview available";
 localizedStrings["Node"] = "Node";
 localizedStrings["Node Removed"] = "Node Removed";
+localizedStrings["Nodes"] = "Nodes";
 localizedStrings["Not found"] = "Not found";
 localizedStrings["Number"] = "Number";
 localizedStrings["Numeric"] = "Numeric";
index 542cf25..46f322a 100644 (file)
@@ -84,6 +84,18 @@ WebInspector.CanvasManager = class CanvasManager extends WebInspector.Object
         canvas.memoryCost = memoryCost;
     }
 
+    cssCanvasClientNodesChanged(canvasIdentifier)
+    {
+        // Called from WebInspector.CanvasObserver.
+
+        let canvas = this._canvasIdentifierMap.get(canvasIdentifier);
+        console.assert(canvas);
+        if (!canvas)
+            return;
+
+        canvas.cssCanvasClientNodesChanged();
+    }
+
     // Private
 
     _mainResourceDidChange(event)
index c000e0f..5e2d8ac 100644 (file)
@@ -85,6 +85,11 @@ WebInspector.DOMTreeManager = class DOMTreeManager extends WebInspector.Object
         DOMAgent.getDocument(onDocumentAvailable.bind(this));
     }
 
+    ensureDocument()
+    {
+        this.requestDocument(function(){});
+    }
+
     pushNodeToFrontend(objectId, callback)
     {
         this._dispatchWhenDocumentAvailable(DOMAgent.requestNode.bind(DOMAgent, objectId), callback);
index 4c9038e..6243eea 100644 (file)
@@ -46,6 +46,7 @@
     <link rel="stylesheet" href="Views/CallFrameTreeElement.css">
     <link rel="stylesheet" href="Views/CallFrameView.css">
     <link rel="stylesheet" href="Views/CanvasContentView.css">
+    <link rel="stylesheet" href="Views/CanvasDetailsSidebarPanel.css">
     <link rel="stylesheet" href="Views/ChartDetailsSectionRow.css">
     <link rel="stylesheet" href="Views/CircleChart.css">
     <link rel="stylesheet" href="Views/ClusterContentView.css">
index 01e7172..efcb6ca 100644 (file)
@@ -40,6 +40,8 @@ WebInspector.Canvas = class Canvas extends WebInspector.Object
         this._cssCanvasName = cssCanvasName || "";
         this._contextAttributes = contextAttributes || {};
         this._memoryCost = memoryCost || NaN;
+
+        this._cssCanvasClientNodes = null;
     }
 
     // Static
@@ -140,16 +142,16 @@ WebInspector.Canvas = class Canvas extends WebInspector.Object
             return;
         }
 
-        WebInspector.domTreeManager.requestDocument((document) => {
-            CanvasAgent.requestNode(this._identifier, (error, nodeId) => {
-                if (error) {
-                    callback(null);
-                    return;
-                }
+        WebInspector.domTreeManager.ensureDocument();
+
+        CanvasAgent.requestNode(this._identifier, (error, nodeId) => {
+            if (error) {
+                callback(null);
+                return;
+            }
 
-                this._domNode = WebInspector.domTreeManager.nodeForId(nodeId);
-                callback(this._domNode);
-            });
+            this._domNode = WebInspector.domTreeManager.nodeForId(nodeId);
+            callback(this._domNode);
         });
     }
 
@@ -165,6 +167,33 @@ WebInspector.Canvas = class Canvas extends WebInspector.Object
         });
     }
 
+    requestCSSCanvasClientNodes(callback)
+    {
+        if (!this._cssCanvasName) {
+            callback([]);
+            return;
+        }
+
+        if (this._cssCanvasClientNodes) {
+            callback(this._cssCanvasClientNodes);
+            return;
+        }
+
+        WebInspector.domTreeManager.ensureDocument();
+
+        CanvasAgent.requestCSSCanvasClientNodes(this._identifier, (error, clientNodeIds) => {
+            if (error) {
+                callback([]);
+                return;
+            }
+
+            clientNodeIds = Array.isArray(clientNodeIds) ? clientNodeIds : [];
+            this._cssCanvasClientNodes = clientNodeIds.map((clientNodeId) => WebInspector.domTreeManager.nodeForId(clientNodeId));
+
+            callback(this._cssCanvasClientNodes);
+        });
+    }
+
     saveIdentityToCookie(cookie)
     {
         cookie[WebInspector.Canvas.FrameURLCookieKey] = this._frame.url.hash;
@@ -175,6 +204,18 @@ WebInspector.Canvas = class Canvas extends WebInspector.Object
             cookie[WebInspector.Canvas.NodePathCookieKey] = this._domNode.path;
 
     }
+
+    cssCanvasClientNodesChanged()
+    {
+        // Called from WebInspector.CanvasManager.
+
+        if (!this._cssCanvasName)
+            return;
+
+        this._cssCanvasClientNodes = null;
+
+        this.dispatchEventToListeners(WebInspector.Canvas.Event.CSSCanvasClientNodesChanged);
+    }
 };
 
 WebInspector.Canvas._nextUniqueDisplayNameNumber = 1;
@@ -193,4 +234,5 @@ WebInspector.Canvas.ResourceSidebarType = "resource-type-canvas";
 
 WebInspector.Canvas.Event = {
     MemoryChanged: "canvas-memory-changed",
+    CSSCanvasClientNodesChanged: "canvas-css-canvas-client-nodes-changed",
 };
index db38f28..1bd9a41 100644 (file)
@@ -41,4 +41,9 @@ WebInspector.CanvasObserver = class CanvasObserver
     {
         WebInspector.canvasManager.canvasMemoryChanged(canvasId, memoryCost);
     }
+
+    cssCanvasClientNodesChanged(canvasId)
+    {
+        WebInspector.canvasManager.cssCanvasClientNodesChanged(canvasId);
+    }
 };
diff --git a/Source/WebInspectorUI/UserInterface/Views/CanvasDetailsSidebarPanel.css b/Source/WebInspectorUI/UserInterface/Views/CanvasDetailsSidebarPanel.css
new file mode 100644 (file)
index 0000000..864a65f
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.sidebar > .panel.details.canvas .details-section > .content .row.simple > .value > .node-link {
+    display: block;
+}
index f634401..1127b39 100644 (file)
@@ -27,7 +27,7 @@ WebInspector.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends
 {
     constructor()
     {
-        super("canvas-details", WebInspector.UIString("Canvas"));
+        super("canvas", WebInspector.UIString("Canvas"));
 
         this.element.classList.add("canvas");
 
@@ -64,13 +64,17 @@ WebInspector.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends
             this._node = null;
         }
 
-        if (this._canvas)
+        if (this._canvas) {
             this._canvas.removeEventListener(WebInspector.Canvas.Event.MemoryChanged, this._canvasMemoryChanged, this);
+            this._canvas.removeEventListener(WebInspector.Canvas.Event.CSSCanvasClientNodesChanged, this._refreshCSSCanvasSection, this);
+        }
 
         this._canvas = canvas || null;
 
-        if (this._canvas)
+        if (this._canvas) {
             this._canvas.addEventListener(WebInspector.Canvas.Event.MemoryChanged, this._canvasMemoryChanged, this);
+            this._canvas.addEventListener(WebInspector.Canvas.Event.CSSCanvasClientNodesChanged, this._refreshCSSCanvasSection, this);
+        }
 
         this.needsLayout();
     }
@@ -84,6 +88,7 @@ WebInspector.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends
         this._nameRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Name"));
         this._typeRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Type"));
         this._memoryRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Memory"));
+        this._memoryRow.tooltip = WebInspector.UIString("Memory usage of this canvas");
 
         let identitySection = new WebInspector.DetailsSection("canvas-details", WebInspector.UIString("Identity"));
         identitySection.groups = [new WebInspector.DetailsSectionGroup([this._nameRow, this._typeRow, this._memoryRow])];
@@ -104,6 +109,13 @@ WebInspector.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends
         let attributesSection = new WebInspector.DetailsSection("canvas-attributes", WebInspector.UIString("Attributes"));
         attributesSection.groups = [new WebInspector.DetailsSectionGroup([this._attributesDataGridRow])];
         this.contentView.element.appendChild(attributesSection.element);
+
+        this._cssCanvasClientsRow = new WebInspector.DetailsSectionSimpleRow(WebInspector.UIString("Nodes"));
+
+        this._cssCanvasSection = new WebInspector.DetailsSection("canvas-css", WebInspector.UIString("CSS"));
+        this._cssCanvasSection.groups = [new WebInspector.DetailsSectionGroup([this._cssCanvasClientsRow])];
+        this._cssCanvasSection.element.hidden = true;
+        this.contentView.element.appendChild(this._cssCanvasSection.element);
     }
 
     layout()
@@ -116,6 +128,7 @@ WebInspector.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends
         this._refreshIdentitySection();
         this._refreshSourceSection();
         this._refreshAttributesSection();
+        this._refreshCSSCanvasSection();
     }
 
     sizeDidChange()
@@ -239,6 +252,31 @@ WebInspector.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends
         dataGrid.updateLayoutIfNeeded();
     }
 
+    _refreshCSSCanvasSection()
+    {
+        if (!this._canvas)
+            return;
+
+        if (!this._canvas.cssCanvasName) {
+            this._cssCanvasSection.element.hidden = true;
+            return;
+        }
+
+        this._cssCanvasClientsRow.value = emDash;
+
+        this._cssCanvasSection.element.hidden = false;
+
+        this._canvas.requestCSSCanvasClientNodes((cssCanvasClientNodes) => {
+            if (!cssCanvasClientNodes.length)
+                return;
+
+            let fragment = document.createDocumentFragment();
+            for (let clientNode of cssCanvasClientNodes)
+                fragment.appendChild(WebInspector.linkifyNodeReference(clientNode));
+            this._cssCanvasClientsRow.value = fragment;
+        });
+    }
+
     _formatMemoryRow()
     {
         if (!this._canvas.memoryCost || isNaN(this._canvas.memoryCost)) {
@@ -246,9 +284,7 @@ WebInspector.CanvasDetailsSidebarPanel = class CanvasDetailsSidebarPanel extends
             return;
         }
 
-        let canvasMemory = Number.bytesToString(this._canvas.memoryCost);
-        this._memoryRow.value = canvasMemory;
-        this._memoryRow.tooltip = WebInspector.UIString("Memory usage of this canvas");
+        this._memoryRow.value = Number.bytesToString(this._canvas.memoryCost);
     }
 
     _canvasMemoryChanged(event)
index 38097de..5b1544d 100644 (file)
@@ -278,7 +278,7 @@ WebInspector.SearchSidebarPanel = class SearchSidebarPanel extends WebInspector.
         }
 
         if (window.DOMAgent)
-            WebInspector.domTreeManager.requestDocument(function(){});
+            WebInspector.domTreeManager.ensureDocument();
 
         if (window.PageAgent)
             PageAgent.searchInResources(searchQuery, isCaseSensitive, isRegex, resourcesCallback.bind(this));