Web Inspector: Implement tracking of active stylesheets in the frontend
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 26 Aug 2015 23:48:49 +0000 (23:48 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Wed, 26 Aug 2015 23:48:49 +0000 (23:48 +0000)
https://bugs.webkit.org/show_bug.cgi?id=105828

Reviewed by Timothy Hatcher.

Source/JavaScriptCore:

* inspector/protocol/CSS.json:
Add new events for when a StyleSheet is added or removed.

Source/WebCore:

Tests: inspector/css/stylesheet-events-basic.html
       inspector/css/stylesheet-events-imports.html
       inspector/css/stylesheet-events-inspector-stylesheet.html

* inspector/InspectorInstrumentation.cpp:
(WebCore::InspectorInstrumentation::documentDetachedImpl):
(WebCore::InspectorInstrumentation::activeStyleSheetsUpdatedImpl):
* inspector/InspectorInstrumentation.h:
(WebCore::InspectorInstrumentation::documentDetached):
(WebCore::InspectorInstrumentation::activeStyleSheetsUpdated):
New hooks for when a document is detached or a document's style sheets are updated.

* dom/Document.cpp:
(WebCore::Document::prepareForDestruction):
Inform the inspector so the CSSAgent can remove document related data.

* dom/DocumentStyleSheetCollection.h:
* dom/DocumentStyleSheetCollection.cpp:
(WebCore::DocumentStyleSheetCollection::updateActiveStyleSheets):
Inform the inspector so the CSSAgent can push stylesheet related events.

(WebCore::DocumentStyleSheetCollection::activeStyleSheetsForInspector): Added.
CSSStyleSheets for the inspector include non-disabled author stylesheets
even if they are empty.

* inspector/InspectorCSSAgent.h:
* inspector/InspectorCSSAgent.cpp:
(WebCore::InspectorCSSAgent::reset):
(WebCore::InspectorCSSAgent::documentDetached):
Handling for the new list of known document to CSSStyleSheets map.

(WebCore::InspectorCSSAgent::enable):
When the CSS domain is enabled, tell the frontend about known stylesheets.

(WebCore::InspectorCSSAgent::activeStyleSheetsUpdated):
(WebCore::InspectorCSSAgent::setActiveStyleSheetsForDocument):
Diff the old list of known stylesheets to the new list of stylesheets
for an individual document. Then send appropriate added/removed events.

(WebCore::InspectorCSSAgent::collectAllStyleSheets):
(WebCore::InspectorCSSAgent::collectAllDocumentStyleSheets):
(WebCore::InspectorCSSAgent::collectStyleSheets):
Collect stylesheets recursively. A stylesheet may link to other stylesheets
through @import statements.

(WebCore::InspectorCSSAgent::getAllStyleSheets):
Use the new methods, this command should go away as it will no longer be useful.

(WebCore::InspectorCSSAgent::unbindStyleSheet):
(WebCore::InspectorCSSAgent::bindStyleSheet):
Create an InspectorStyleSheet from a CSSStyleSheet and add to the appropriate lists.
Likewise, unbinding will remove from the appropriate lists.

(WebCore::InspectorCSSAgent::viaInspectorStyleSheet):
(WebCore::InspectorCSSAgent::detectOrigin):
When creating the inspector stylesheet, which is a <style> element,
it will push a StyleSheetAdded event. In the process of binding this
new stylesheet use the m_creatingViaInspectorStyleSheet to add it to
out list of Inspector Stylesheets.

Source/WebInspectorUI:

* UserInterface/Models/CSSStyleSheet.js:
(WebInspector.CSSStyleSheet):
(WebInspector.CSSStyleSheet.prototype.get origin):
(WebInspector.CSSStyleSheet.prototype.updateInfo):
Add a new origin attribute that has been sent from the backend for a while.

* UserInterface/Controllers/CSSStyleManager.js:
(WebInspector.CSSStyleManager.prototype.styleSheetAdded):
(WebInspector.CSSStyleManager.prototype.styleSheetRemoved):
Handle the new events by managing the new CSSStyleSheets.

(WebInspector.CSSStyleManager):
(WebInspector.CSSStyleManager.prototype._mainResourceDidChange):
Reset the legacy fetching flag. Fetching is only needed for legacy backends.

(WebInspector.CSSStyleManager.prototype._fetchInfoForAllStyleSheets):
Include the new origin property in the legacy updateInfo path.

* UserInterface/Protocol/CSSObserver.js:
(WebInspector.CSSObserver.prototype.styleSheetAdded):
(WebInspector.CSSObserver.prototype.styleSheetRemoved):
Forward to the manager.

* UserInterface/Views/CSSStyleDetailsSidebarPanel.js:
(WebInspector.CSSStyleDetailsSidebarPanel):
Refresh the sidebar when stylesheets are added / removed, as that
may affect the style of the select element.

LayoutTests:

* inspector/css/resources/import-level-1.css: Added.
* inspector/css/resources/import-level-2.css: Added.
* inspector/css/resources/stylesheet-events-subframe.html: Added.
* inspector/css/stylesheet-events-basic-expected.txt: Added.
* inspector/css/stylesheet-events-basic.html: Added.
* inspector/css/stylesheet-events-imports-expected.txt: Added.
* inspector/css/stylesheet-events-imports.html: Added.
* inspector/css/stylesheet-events-inspector-stylesheet-expected.txt: Added.
* inspector/css/stylesheet-events-inspector-stylesheet.html: Added.
* inspector/css/stylesheet-events-multiple-documents-expected.txt: Added.
* inspector/css/stylesheet-events-multiple-documents.html: Added.
Tests for different ways that StyleSheets can be added / removed.

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

28 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/css/resources/import-level-1.css [new file with mode: 0644]
LayoutTests/inspector/css/resources/import-level-2.css [new file with mode: 0644]
LayoutTests/inspector/css/resources/stylesheet-events-subframe.html [new file with mode: 0644]
LayoutTests/inspector/css/stylesheet-events-basic-expected.txt [new file with mode: 0644]
LayoutTests/inspector/css/stylesheet-events-basic.html [new file with mode: 0644]
LayoutTests/inspector/css/stylesheet-events-imports-expected.txt [new file with mode: 0644]
LayoutTests/inspector/css/stylesheet-events-imports.html [new file with mode: 0644]
LayoutTests/inspector/css/stylesheet-events-inspector-stylesheet-expected.txt [new file with mode: 0644]
LayoutTests/inspector/css/stylesheet-events-inspector-stylesheet.html [new file with mode: 0644]
LayoutTests/inspector/css/stylesheet-events-multiple-documents-expected.txt [new file with mode: 0644]
LayoutTests/inspector/css/stylesheet-events-multiple-documents.html [new file with mode: 0644]
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/inspector/protocol/CSS.json
Source/WebCore/ChangeLog
Source/WebCore/dom/Document.cpp
Source/WebCore/dom/DocumentStyleSheetCollection.cpp
Source/WebCore/dom/DocumentStyleSheetCollection.h
Source/WebCore/inspector/InspectorCSSAgent.cpp
Source/WebCore/inspector/InspectorCSSAgent.h
Source/WebCore/inspector/InspectorInstrumentation.cpp
Source/WebCore/inspector/InspectorInstrumentation.h
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Controllers/CSSStyleManager.js
Source/WebInspectorUI/UserInterface/Controllers/DebuggerManager.js
Source/WebInspectorUI/UserInterface/Models/CSSStyleSheet.js
Source/WebInspectorUI/UserInterface/Protocol/CSSObserver.js
Source/WebInspectorUI/UserInterface/Views/CSSStyleDetailsSidebarPanel.js

index d0ebd85..3ef0d7b 100644 (file)
@@ -1,3 +1,23 @@
+2015-08-26  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Implement tracking of active stylesheets in the frontend
+        https://bugs.webkit.org/show_bug.cgi?id=105828
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/css/resources/import-level-1.css: Added.
+        * inspector/css/resources/import-level-2.css: Added.
+        * inspector/css/resources/stylesheet-events-subframe.html: Added.
+        * inspector/css/stylesheet-events-basic-expected.txt: Added.
+        * inspector/css/stylesheet-events-basic.html: Added.
+        * inspector/css/stylesheet-events-imports-expected.txt: Added.
+        * inspector/css/stylesheet-events-imports.html: Added.
+        * inspector/css/stylesheet-events-inspector-stylesheet-expected.txt: Added.
+        * inspector/css/stylesheet-events-inspector-stylesheet.html: Added.
+        * inspector/css/stylesheet-events-multiple-documents-expected.txt: Added.
+        * inspector/css/stylesheet-events-multiple-documents.html: Added.
+        Tests for different ways that StyleSheets can be added / removed.
+
 2015-08-26  Andy Estes  <aestes@apple.com>
 
         Crash when following a Google search link to Twitter with Limit Adult Content enabled
diff --git a/LayoutTests/inspector/css/resources/import-level-1.css b/LayoutTests/inspector/css/resources/import-level-1.css
new file mode 100644 (file)
index 0000000..17dd2d2
--- /dev/null
@@ -0,0 +1,2 @@
+@import url(import-level-2.css);
+body { color: #abc; }
diff --git a/LayoutTests/inspector/css/resources/import-level-2.css b/LayoutTests/inspector/css/resources/import-level-2.css
new file mode 100644 (file)
index 0000000..b85026e
--- /dev/null
@@ -0,0 +1 @@
+body { color: #def; }
diff --git a/LayoutTests/inspector/css/resources/stylesheet-events-subframe.html b/LayoutTests/inspector/css/resources/stylesheet-events-subframe.html
new file mode 100644 (file)
index 0000000..dece10d
--- /dev/null
@@ -0,0 +1 @@
+<link rel="stylesheet" href="external.css">
diff --git a/LayoutTests/inspector/css/stylesheet-events-basic-expected.txt b/LayoutTests/inspector/css/stylesheet-events-basic-expected.txt
new file mode 100644 (file)
index 0000000..8039b1f
--- /dev/null
@@ -0,0 +1,39 @@
+Test for CSS.styleSheetAdded and CSS.styleSheetRemoved events.
+
+
+== Running test suite: CSS.StyleSheetEvents.Basic
+-- Running test case: CheckNoStyleSheets
+PASS: Should be no stylesheets.
+
+-- Running test case: AddStyleTag
+PASS: StyleSheetAdded event should fire.
+
+-- Running test case: DisableStyleTag
+PASS: StyleSheetRemoved event should fire for the same CSSStyleSheet.
+
+-- Running test case: EnableStyleTag
+PASS: StyleSheetAdded event should fire with a new CSSStyleSheet.
+
+-- Running test case: RemoveStyleTag
+PASS: StyleSheetAdded event should fire.
+
+-- Running test case: AddExternalStyleSheet
+PASS: StyleSheetAdded event should fire.
+Added StyleSheet with URL: inspector/css/resources/external.css
+
+-- Running test case: DisableExternalStyleSheet
+PASS: StyleSheetRemoved event should fire for the same CSSStyleSheet.
+
+-- Running test case: EnableExternalStyleSheet
+PASS: StyleSheetAdded event should fire with a new CSSStyleSheet.
+
+-- Running test case: DisableExternalStyleSheetViaMediaAttribute
+PASS: StyleSheetRemoved event should fire for the same CSSStyleSheet.
+
+-- Running test case: EnableExternalStyleSheetViaMediaAttribute
+PASS: StyleSheetAdded event should fire with a new CSSStyleSheet.
+
+-- Running test case: RemoveExternalStyleSheet
+PASS: StyleSheetAdded event should fire.
+Removed StyleSheet with URL: inspector/css/resources/external.css
+
diff --git a/LayoutTests/inspector/css/stylesheet-events-basic.html b/LayoutTests/inspector/css/stylesheet-events-basic.html
new file mode 100644 (file)
index 0000000..635b29f
--- /dev/null
@@ -0,0 +1,178 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function addExternalStyleSheet() {
+    let link = document.createElement("link");
+    link.id = "externalStyleSheetId";
+    link.rel = "stylesheet";
+    link.href = "resources/external.css";
+    document.head.appendChild(link);
+}
+
+function removeExternalStyleSheet() {
+    document.getElementById("externalStyleSheetId").remove();
+}
+
+function disableExternalStyleSheetViaMediaAttribute() {
+    document.getElementById("externalStyleSheetId").media = "print";
+}
+
+function enableExternalStyleSheetViaMediaAttribute() {
+    document.getElementById("externalStyleSheetId").media = "screen";
+}
+
+function addStyleTag() {
+    let style = document.createElement("style");
+    style.id = "inlineStyleId";
+    style.textContent = "body { color: red; }";
+    document.body.appendChild(style);
+}
+
+function removeStyleTag() {
+    document.getElementById("inlineStyleId").remove();
+}
+
+function disableStyleSheet() {
+    document.styleSheets[0].disabled = true;
+}
+
+function enableStyleSheet() {
+    document.styleSheets[0].disabled = false;
+}
+
+function test()
+{
+    function sanitizeURL(url) {
+        return url.replace(/^.*?LayoutTests\//, "");
+    }
+
+    let suite = InspectorTest.createAsyncSuite("CSS.StyleSheetEvents.Basic");
+
+    suite.addTestCase({
+        name: "CheckNoStyleSheets",
+        description: "Ensure there are currently no stylesheets.",
+        test: (resolve, reject) => {
+            InspectorTest.expectThat(WebInspector.cssStyleManager.styleSheets.length === 0, "Should be no stylesheets.");
+            resolve();
+        }
+    });
+
+    function appendStyleSheetTests(args) {
+        let {title, addExpression, removeExpression, checkStyleSheetAddedCallback, checkStyleSheetRemovedCallback, extraEnableDisableTests} = args;
+
+        let addedStyleSheet;
+
+        suite.addTestCase({
+            name: `Add${title}`,
+            test: (resolve, reject) => {
+                InspectorTest.evaluateInPage(addExpression);
+                WebInspector.cssStyleManager.singleFireEventListener(WebInspector.CSSStyleManager.Event.StyleSheetAdded, function(event) {
+                    addedStyleSheet = event.data.styleSheet;
+                    InspectorTest.expectThat(addedStyleSheet instanceof WebInspector.CSSStyleSheet, "StyleSheetAdded event should fire.");
+                    if (checkStyleSheetRemovedCallback)
+                        checkStyleSheetAddedCallback(addedStyleSheet);
+                    resolve();
+                });
+            }
+        });
+
+        suite.addTestCase({
+            name: `Disable${title}`,
+            description: "Disable the newly added stylesheet.",
+            test: (resolve, reject) => {
+                InspectorTest.evaluateInPage("disableStyleSheet()");
+                WebInspector.cssStyleManager.singleFireEventListener(WebInspector.CSSStyleManager.Event.StyleSheetRemoved, function(event) {
+                    InspectorTest.assert(event.data.styleSheet instanceof WebInspector.CSSStyleSheet, "Event data should be a CSSStyleSheet");
+                    InspectorTest.expectThat(addedStyleSheet === event.data.styleSheet, "StyleSheetRemoved event should fire for the same CSSStyleSheet.");
+                    resolve();
+                });
+            }
+        });
+
+        suite.addTestCase({
+            name: `Enable${title}`,
+            description: "Enable the just disabled stylesheet.",
+            test: (resolve, reject) => {
+                InspectorTest.evaluateInPage("enableStyleSheet()");
+                WebInspector.cssStyleManager.singleFireEventListener(WebInspector.CSSStyleManager.Event.StyleSheetAdded, function(event) {
+                    InspectorTest.assert(event.data.styleSheet instanceof WebInspector.CSSStyleSheet, "Event data should be a CSSStyleSheet");
+                    InspectorTest.expectThat(addedStyleSheet !== event.data.styleSheet, "StyleSheetAdded event should fire with a new CSSStyleSheet.");
+                    addedStyleSheet = event.data.styleSheet;
+                    resolve();
+                });
+            }
+        });
+
+        if (extraEnableDisableTests) {
+            let {title: extraDisableTitle, expression: extraDisableExpression} = extraEnableDisableTests.shift();
+            let {title: extraEnableTitle, expression: extraEnableExpression} = extraEnableDisableTests.shift();
+
+            suite.addTestCase({
+                name: extraDisableTitle,
+                test: (resolve, reject) => {
+                    InspectorTest.evaluateInPage(extraDisableExpression);
+                    WebInspector.cssStyleManager.singleFireEventListener(WebInspector.CSSStyleManager.Event.StyleSheetRemoved, function(event) {
+                        InspectorTest.assert(event.data.styleSheet instanceof WebInspector.CSSStyleSheet, "Event data should be a CSSStyleSheet");
+                        InspectorTest.expectThat(addedStyleSheet === event.data.styleSheet, "StyleSheetRemoved event should fire for the same CSSStyleSheet.");
+                        resolve();
+                    });
+                }
+            });
+
+            suite.addTestCase({
+                name: extraEnableTitle,
+                test: (resolve, reject) => {
+                    InspectorTest.evaluateInPage(extraEnableExpression);
+                    WebInspector.cssStyleManager.singleFireEventListener(WebInspector.CSSStyleManager.Event.StyleSheetAdded, function(event) {
+                        InspectorTest.assert(event.data.styleSheet instanceof WebInspector.CSSStyleSheet, "Event data should be a CSSStyleSheet");
+                        InspectorTest.expectThat(addedStyleSheet !== event.data.styleSheet, "StyleSheetAdded event should fire with a new CSSStyleSheet.");
+                        addedStyleSheet = event.data.styleSheet;
+                        resolve();
+                    });
+                }
+            });
+        }
+
+        suite.addTestCase({
+            name: `Remove${title}`,
+            test: (resolve, reject) => {
+                InspectorTest.evaluateInPage(removeExpression);
+                WebInspector.cssStyleManager.singleFireEventListener(WebInspector.CSSStyleManager.Event.StyleSheetRemoved, function(event) {
+                    InspectorTest.expectThat(event.data.styleSheet instanceof WebInspector.CSSStyleSheet, "StyleSheetAdded event should fire.");
+                    if (checkStyleSheetRemovedCallback)
+                        checkStyleSheetRemovedCallback(event.data.styleSheet);
+                    addedStyleSheet = null;
+                    resolve();
+                });
+            }
+        });
+    }
+
+    appendStyleSheetTests({
+        title: "StyleTag",
+        addExpression: "addStyleTag()",
+        removeExpression: "removeStyleTag()",
+    });
+
+    appendStyleSheetTests({
+        title: "ExternalStyleSheet",
+        addExpression: "addExternalStyleSheet()",
+        removeExpression: "removeExternalStyleSheet()",
+        checkStyleSheetAddedCallback: (styleSheet) => { InspectorTest.log("Added StyleSheet with URL: " + sanitizeURL(styleSheet.url)); },
+        checkStyleSheetRemovedCallback: (styleSheet) => { InspectorTest.log("Removed StyleSheet with URL: " + sanitizeURL(styleSheet.url)); },
+        extraEnableDisableTests: [
+            {title: "DisableExternalStyleSheetViaMediaAttribute", expression: "disableExternalStyleSheetViaMediaAttribute()"},
+            {title: "EnableExternalStyleSheetViaMediaAttribute", expression: "enableExternalStyleSheetViaMediaAttribute()"},
+        ],
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Test for CSS.styleSheetAdded and CSS.styleSheetRemoved events.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/css/stylesheet-events-imports-expected.txt b/LayoutTests/inspector/css/stylesheet-events-imports-expected.txt
new file mode 100644 (file)
index 0000000..d6cc855
--- /dev/null
@@ -0,0 +1,24 @@
+Test for CSS.styleSheetAdded and CSS.styleSheetRemoved events with @import rules.
+
+
+== Running test suite: CSS.StyleSheetEvents.Imports
+-- Running test case: CheckStyleSheets
+PASS: Should be 3 stylesheets.
+inspector/css/stylesheet-events-imports.html
+inspector/css/resources/import-level-1.css
+inspector/css/resources/import-level-2.css
+
+-- Running test case: DisablePropagatesThroughImports
+StyleSheetRemoved: inspector/css/resources/import-level-1.css
+StyleSheetRemoved: inspector/css/resources/import-level-2.css
+StyleSheetRemoved: inspector/css/stylesheet-events-imports.html
+
+-- Running test case: EnablePropagatesThroughImports
+StyleSheetAdded: inspector/css/resources/import-level-1.css
+StyleSheetAdded: inspector/css/resources/import-level-2.css
+StyleSheetAdded: inspector/css/stylesheet-events-imports.html
+
+-- Running test case: DeleteImportRemovesStyleSheet
+StyleSheetRemoved: inspector/css/resources/import-level-1.css
+StyleSheetRemoved: inspector/css/resources/import-level-2.css
+
diff --git a/LayoutTests/inspector/css/stylesheet-events-imports.html b/LayoutTests/inspector/css/stylesheet-events-imports.html
new file mode 100644 (file)
index 0000000..ca39a59
--- /dev/null
@@ -0,0 +1,109 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<style id="inlineStyleId">
+@import url(resources/import-level-1.css);
+body { color: blue; }
+</style>
+<script>
+function disableStyleSheet() {
+    document.styleSheets[0].disabled = true;
+}
+
+function enableStyleSheet() {
+    document.styleSheets[0].disabled = false;
+}
+
+function removeImport() {
+    document.getElementById("inlineStyleId").sheet.deleteRule(0);
+}
+
+function test()
+{
+    function sanitizeURL(url) {
+        return url.replace(/^.*?LayoutTests\//, "");
+    }
+
+    function collectEvents(n, then) {
+        let remaining = n;
+        let collection = [];
+        return function(event) {
+            collection.push(event);
+            remaining--;
+            if (!remaining)
+                then(collection);
+        }
+    }
+
+    let currentStyleSheetAddedHandler, currentStyleSheetRemovedHandler;
+    WebInspector.cssStyleManager.addEventListener(WebInspector.CSSStyleManager.Event.StyleSheetAdded, (event) => { currentStyleSheetAddedHandler(event); });
+    WebInspector.cssStyleManager.addEventListener(WebInspector.CSSStyleManager.Event.StyleSheetRemoved, (event) => { currentStyleSheetRemovedHandler(event); });
+
+    let suite = InspectorTest.createAsyncSuite("CSS.StyleSheetEvents.Imports");
+
+    suite.addTestCase({
+        name: "CheckStyleSheets",
+        description: "Ensure there are currently two stylesheets.",
+        test: (resolve, reject) => {
+            let styleSheets = WebInspector.cssStyleManager.styleSheets;
+            InspectorTest.expectThat(styleSheets.length === 3, "Should be 3 stylesheets.");
+            for (let styleSheet of styleSheets)
+                InspectorTest.log(sanitizeURL(styleSheet.url));
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "DisablePropagatesThroughImports",
+        description: "Disabling the top stylesheet containing imports will remove all stylesheets.",
+        test: (resolve, reject) => {
+            currentStyleSheetRemovedHandler = collectEvents(3, function(events) {
+                events.map((e) => sanitizeURL(e.data.styleSheet.url))
+                    .sort()
+                    .forEach((url) => { InspectorTest.log("StyleSheetRemoved: " + url); });
+                currentStyleSheetRemovedHandler = null;
+                resolve();
+            });
+            InspectorTest.evaluateInPage("disableStyleSheet()");
+        }
+    });
+
+    suite.addTestCase({
+        name: "EnablePropagatesThroughImports",
+        description: "Enabling the top stylesheet containing imports will add all stylesheets.",
+        test: (resolve, reject) => {
+            currentStyleSheetAddedHandler = collectEvents(3, function(events) {
+                events.map((e) => sanitizeURL(e.data.styleSheet.url))
+                    .sort()
+                    .forEach((url) => { InspectorTest.log("StyleSheetAdded: " + url); });
+                currentStyleSheetAddedHandler = null;
+                resolve();
+            });
+            InspectorTest.evaluateInPage("enableStyleSheet()");
+        }
+    });
+
+    suite.addTestCase({
+        name: "DeleteImportRemovesStyleSheet",
+        description: "Deleting the @import rule should remove that stylesheet and its imports.",
+        test: (resolve, reject) => {
+            currentStyleSheetRemovedHandler = collectEvents(2, function(events) {
+                events.map((e) => sanitizeURL(e.data.styleSheet.url))
+                    .sort()
+                    .forEach((url) => { InspectorTest.log("StyleSheetRemoved: " + url); });
+                currentStyleSheetRemovedHandler = null;
+                resolve();
+            });
+            InspectorTest.evaluateInPage("removeImport()");
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Test for CSS.styleSheetAdded and CSS.styleSheetRemoved events with @import rules.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/css/stylesheet-events-inspector-stylesheet-expected.txt b/LayoutTests/inspector/css/stylesheet-events-inspector-stylesheet-expected.txt
new file mode 100644 (file)
index 0000000..cd49920
--- /dev/null
@@ -0,0 +1,11 @@
+Test for CSS.styleSheetAdded for Inspector stylesheets.
+
+
+== Running test suite: CSS.StyleSheetEvents.InspectorStyleSheet
+-- Running test case: CheckNoStyleSheets
+PASS: Should be no stylesheets.
+
+-- Running test case: CreateInspectorStyleSheet
+PASS: Should be one stylesheet.
+PASS: StyleSheet origin is inspector.
+
diff --git a/LayoutTests/inspector/css/stylesheet-events-inspector-stylesheet.html b/LayoutTests/inspector/css/stylesheet-events-inspector-stylesheet.html
new file mode 100644 (file)
index 0000000..92be553
--- /dev/null
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+    let suite = InspectorTest.createAsyncSuite("CSS.StyleSheetEvents.InspectorStyleSheet");
+
+    let bodyNodeId;
+
+    suite.addTestCase({
+        name: "CheckNoStyleSheets",
+        description: "Ensure there are currently no stylesheets.",
+        test: (resolve, reject) => {
+            InspectorTest.expectThat(WebInspector.cssStyleManager.styleSheets.length === 0, "Should be no stylesheets.");
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "CreateInspectorStyleSheet",
+        description: "Creating an inspector stylesheet adds a stylesheet.",
+        test: (resolve, reject) => {
+            WebInspector.cssStyleManager.singleFireEventListener(WebInspector.CSSStyleManager.Event.StyleSheetAdded, function(event) {
+                InspectorTest.expectThat(WebInspector.cssStyleManager.styleSheets.length === 1, "Should be one stylesheet.");
+                InspectorTest.assert(event.data.styleSheet instanceof WebInspector.CSSStyleSheet, "Event data should be a CSSStyleSheet");
+                InspectorTest.expectThat(event.data.styleSheet.origin === WebInspector.CSSStyleSheet.Type.Inspector, "StyleSheet origin is inspector.");
+                resolve();
+            });
+
+            // FIXME: Currently the only way to create an inspector stylesheet is through `CSS.addRule`.
+            CSSAgent.addRule(bodyNodeId, "body");
+        }
+    });
+
+    WebInspector.domTreeManager.requestDocument(function(documentNode) {
+        WebInspector.domTreeManager.querySelector(documentNode.id, "body", function(contentNodeId) {
+            bodyNodeId = contentNodeId;
+            suite.runTestCasesAndFinish();
+        });
+    });
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>Test for CSS.styleSheetAdded for Inspector stylesheets.</p>
+</body>
+</html>
diff --git a/LayoutTests/inspector/css/stylesheet-events-multiple-documents-expected.txt b/LayoutTests/inspector/css/stylesheet-events-multiple-documents-expected.txt
new file mode 100644 (file)
index 0000000..6e94785
--- /dev/null
@@ -0,0 +1,19 @@
+CSS.styleSheetAdded and CSS.styleSheetRemoved for multiple documents.
+
+
+== Running test suite: CSS.StyleSheetEvents.MultipleDocuments
+-- Running test case: CheckStyleSheets
+PASS: Should be one stylesheets.
+URL: inspector/css/resources/external.css <mainframe>
+
+-- Running test case: CheckStyleSheetsAfterAddingSubframe
+PASS: Should be two stylesheets.
+PASS: New StyleSheet is for a subframe
+URL: inspector/css/resources/external.css <mainframe>
+URL: inspector/css/resources/external.css <childframe>
+
+-- Running test case: CheckStyleSheetsAfterRemovingSubframe
+PASS: Should be one stylesheet.
+PASS: Removed StyleSheet is for a subframe
+URL: inspector/css/resources/external.css <mainframe>
+
diff --git a/LayoutTests/inspector/css/stylesheet-events-multiple-documents.html b/LayoutTests/inspector/css/stylesheet-events-multiple-documents.html
new file mode 100644 (file)
index 0000000..020ae9a
--- /dev/null
@@ -0,0 +1,78 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<link rel="stylesheet" href="resources/external.css">
+<script>
+function addSubframe() {
+    var iframe = document.createElement("iframe");
+    iframe.id = "subframe";
+    iframe.src = "resources/stylesheet-events-subframe.html";
+    document.body.appendChild(iframe);
+}
+
+function removeSubframe() {
+    document.getElementById("subframe").remove();
+}
+
+function test()
+{
+    function sanitizeURL(url) {
+        return url.replace(/^.*?LayoutTests\//, "");
+    }
+
+    function logStyleSheets() {
+        WebInspector.cssStyleManager.styleSheets.sort((a, b) => a.url.localeCompare(b.url)).forEach(function(styleSheet) {
+            var frameString = styleSheet.parentFrame.id === WebInspector.frameResourceManager.mainFrame.id ? "<mainframe>" : "<childframe>";
+            InspectorTest.log(`URL: ${sanitizeURL(styleSheet.url)} ${frameString}`);
+        });
+    }
+
+    let suite = InspectorTest.createAsyncSuite("CSS.StyleSheetEvents.MultipleDocuments");
+
+    suite.addTestCase({
+        name: "CheckStyleSheets",
+        description: "Ensure there is currently one stylesheet.",
+        test: (resolve, reject) => {
+            InspectorTest.expectThat(WebInspector.cssStyleManager.styleSheets.length === 1, "Should be one stylesheets.");
+            logStyleSheets();
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "CheckStyleSheetsAfterAddingSubframe",
+        description: "Ensure subframe stylesheets are added.",
+        test: (resolve, reject) => {
+            InspectorTest.evaluateInPage("addSubframe()");
+            WebInspector.cssStyleManager.singleFireEventListener(WebInspector.CSSStyleManager.Event.StyleSheetAdded, function(event) {
+                InspectorTest.expectThat(WebInspector.cssStyleManager.styleSheets.length === 2, "Should be two stylesheets.");
+                InspectorTest.expectThat(event.data.styleSheet.parentFrame !== WebInspector.frameResourceManager.mainFrame.id, "New StyleSheet is for a subframe");
+                logStyleSheets();
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "CheckStyleSheetsAfterRemovingSubframe",
+        description: "Ensure subframe stylesheets are removed.",
+        test: (resolve, reject) => {
+            InspectorTest.evaluateInPage("removeSubframe()");
+            WebInspector.cssStyleManager.singleFireEventListener(WebInspector.CSSStyleManager.Event.StyleSheetRemoved, function(event) {
+                InspectorTest.expectThat(WebInspector.cssStyleManager.styleSheets.length === 1, "Should be one stylesheet.");
+                InspectorTest.expectThat(event.data.styleSheet.parentFrame !== WebInspector.frameResourceManager.mainFrame.id, "Removed StyleSheet is for a subframe");
+                logStyleSheets();
+                resolve();
+            });
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+    <p>CSS.styleSheetAdded and CSS.styleSheetRemoved for multiple documents.</p>
+</body>
+</html>
index 0e0c8af..6572585 100644 (file)
@@ -1,3 +1,13 @@
+2015-08-26  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Implement tracking of active stylesheets in the frontend
+        https://bugs.webkit.org/show_bug.cgi?id=105828
+
+        Reviewed by Timothy Hatcher.
+
+        * inspector/protocol/CSS.json:
+        Add new events for when a StyleSheet is added or removed.
+
 2015-08-26  Chris Dumez  <cdumez@apple.com>
 
         Distinguish Web IDL callback interfaces from Web IDL callback functions
index 98dca79..5ade06a 100644 (file)
             "description": "Fired whenever a stylesheet is changed as a result of the client operation."
         },
         {
+            "name": "styleSheetAdded",
+            "parameters": [
+                { "name": "header", "$ref": "CSSStyleSheetHeader", "description": "Added stylesheet metainfo." }
+            ],
+            "description": "Fired whenever an active document stylesheet is added."
+        },
+        {
+            "name": "styleSheetRemoved",
+            "parameters": [
+                { "name": "styleSheetId", "$ref": "StyleSheetId", "description": "Identifier of the removed stylesheet." }
+            ],
+            "description": "Fired whenever an active document stylesheet is removed."
+        },
+        {
             "name": "namedFlowCreated",
             "parameters": [
                 { "name": "namedFlow", "$ref": "NamedFlow", "description": "The new Named Flow." }
index 1db3235..aaf1240 100644 (file)
@@ -1,3 +1,70 @@
+2015-08-26  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: Implement tracking of active stylesheets in the frontend
+        https://bugs.webkit.org/show_bug.cgi?id=105828
+
+        Reviewed by Timothy Hatcher.
+
+        Tests: inspector/css/stylesheet-events-basic.html
+               inspector/css/stylesheet-events-imports.html
+               inspector/css/stylesheet-events-inspector-stylesheet.html
+
+        * inspector/InspectorInstrumentation.cpp:
+        (WebCore::InspectorInstrumentation::documentDetachedImpl):
+        (WebCore::InspectorInstrumentation::activeStyleSheetsUpdatedImpl):
+        * inspector/InspectorInstrumentation.h:
+        (WebCore::InspectorInstrumentation::documentDetached):
+        (WebCore::InspectorInstrumentation::activeStyleSheetsUpdated):
+        New hooks for when a document is detached or a document's style sheets are updated.
+
+        * dom/Document.cpp:
+        (WebCore::Document::prepareForDestruction):
+        Inform the inspector so the CSSAgent can remove document related data.
+
+        * dom/DocumentStyleSheetCollection.h:
+        * dom/DocumentStyleSheetCollection.cpp:
+        (WebCore::DocumentStyleSheetCollection::updateActiveStyleSheets):
+        Inform the inspector so the CSSAgent can push stylesheet related events.
+
+        (WebCore::DocumentStyleSheetCollection::activeStyleSheetsForInspector): Added.
+        CSSStyleSheets for the inspector include non-disabled author stylesheets
+        even if they are empty.
+
+        * inspector/InspectorCSSAgent.h:
+        * inspector/InspectorCSSAgent.cpp:
+        (WebCore::InspectorCSSAgent::reset):
+        (WebCore::InspectorCSSAgent::documentDetached):
+        Handling for the new list of known document to CSSStyleSheets map.
+
+        (WebCore::InspectorCSSAgent::enable):
+        When the CSS domain is enabled, tell the frontend about known stylesheets.
+
+        (WebCore::InspectorCSSAgent::activeStyleSheetsUpdated):
+        (WebCore::InspectorCSSAgent::setActiveStyleSheetsForDocument):
+        Diff the old list of known stylesheets to the new list of stylesheets
+        for an individual document. Then send appropriate added/removed events.
+
+        (WebCore::InspectorCSSAgent::collectAllStyleSheets):
+        (WebCore::InspectorCSSAgent::collectAllDocumentStyleSheets):
+        (WebCore::InspectorCSSAgent::collectStyleSheets):
+        Collect stylesheets recursively. A stylesheet may link to other stylesheets
+        through @import statements.
+
+        (WebCore::InspectorCSSAgent::getAllStyleSheets):
+        Use the new methods, this command should go away as it will no longer be useful.
+
+        (WebCore::InspectorCSSAgent::unbindStyleSheet):
+        (WebCore::InspectorCSSAgent::bindStyleSheet):
+        Create an InspectorStyleSheet from a CSSStyleSheet and add to the appropriate lists.
+        Likewise, unbinding will remove from the appropriate lists.
+
+        (WebCore::InspectorCSSAgent::viaInspectorStyleSheet):
+        (WebCore::InspectorCSSAgent::detectOrigin):
+        When creating the inspector stylesheet, which is a <style> element,
+        it will push a StyleSheetAdded event. In the process of binding this
+        new stylesheet use the m_creatingViaInspectorStyleSheet to add it to
+        out list of Inspector Stylesheets.
+
 2015-08-26  Myles C. Maxfield  <mmaxfield@apple.com>
 
         Add comment to LocaleToScriptMappingDefault.cpp
index ba2520f..ffd4986 100644 (file)
@@ -2251,6 +2251,8 @@ void Document::prepareForDestruction()
         page()->pointerLockController().documentDetached(this);
 #endif
 
+    InspectorInstrumentation::documentDetached(*this);
+
     stopActiveDOMObjects();
     m_eventQueue.close();
 #if ENABLE(FULLSCREEN_API)
index 322ac84..26f154e 100644 (file)
@@ -33,6 +33,7 @@
 #include "HTMLIFrameElement.h"
 #include "HTMLLinkElement.h"
 #include "HTMLStyleElement.h"
+#include "InspectorInstrumentation.h"
 #include "Page.h"
 #include "PageGroup.h"
 #include "ProcessingInstruction.h"
@@ -483,6 +484,8 @@ bool DocumentStyleSheetCollection::updateActiveStyleSheets(UpdateFlag updateFlag
     m_activeAuthorStyleSheets.swap(activeCSSStyleSheets);
     m_styleSheetsForStyleSheetList.swap(activeStyleSheets);
 
+    InspectorInstrumentation::activeStyleSheetsUpdated(m_document);
+
     for (const auto& sheet : m_activeAuthorStyleSheets) {
         if (sheet->contents().usesRemUnits())
             m_usesRemUnits = true;
@@ -494,6 +497,27 @@ bool DocumentStyleSheetCollection::updateActiveStyleSheets(UpdateFlag updateFlag
     return requiresFullStyleRecalc;
 }
 
+const Vector<RefPtr<CSSStyleSheet>> DocumentStyleSheetCollection::activeStyleSheetsForInspector() const
+{
+    Vector<RefPtr<CSSStyleSheet>> result;
+
+    result.appendVector(injectedAuthorStyleSheets());
+    result.appendVector(documentAuthorStyleSheets());
+
+    for (auto& styleSheet : m_styleSheetsForStyleSheetList) {
+        if (!is<CSSStyleSheet>(*styleSheet))
+            continue;
+
+        CSSStyleSheet& sheet = downcast<CSSStyleSheet>(*styleSheet);
+        if (sheet.disabled())
+            continue;
+
+        result.append(&sheet);
+    }
+
+    return result;
+}
+
 bool DocumentStyleSheetCollection::activeStyleSheetsContains(const CSSStyleSheet* sheet) const
 {
     if (!m_weakCopyOfActiveStyleSheetListForFastLookup) {
index 27dfc85..79dde5a 100644 (file)
@@ -59,6 +59,8 @@ public:
 
     const Vector<RefPtr<CSSStyleSheet>>& activeAuthorStyleSheets() const { return m_activeAuthorStyleSheets; }
 
+    const Vector<RefPtr<CSSStyleSheet>> activeStyleSheetsForInspector() const;
+
     CSSStyleSheet* pageUserSheet();
     const Vector<RefPtr<CSSStyleSheet>>& documentUserStyleSheets() const { return m_userStyleSheets; }
     const Vector<RefPtr<CSSStyleSheet>>& documentAuthorStyleSheets() const { return m_authorStyleSheets; }
index 345ae7c..67d1880 100644 (file)
@@ -375,10 +375,12 @@ void InspectorCSSAgent::discardAgent()
 
 void InspectorCSSAgent::reset()
 {
+    // FIXME: Should we be resetting on main frame navigations?
     m_idToInspectorStyleSheet.clear();
     m_cssStyleSheetToInspectorStyleSheet.clear();
     m_nodeToInspectorStyleSheet.clear();
     m_documentToInspectorStyleSheet.clear();
+    m_documentToKnownCSSStyleSheets.clear();
     resetNonPersistentData();
 }
 
@@ -393,6 +395,9 @@ void InspectorCSSAgent::resetNonPersistentData()
 void InspectorCSSAgent::enable(ErrorString&)
 {
     m_instrumentingAgents->setInspectorCSSAgent(this);
+
+    for (auto* document : m_domAgent->documents())
+        activeStyleSheetsUpdated(*document);
 }
 
 void InspectorCSSAgent::disable(ErrorString&)
@@ -400,10 +405,56 @@ void InspectorCSSAgent::disable(ErrorString&)
     m_instrumentingAgents->setInspectorCSSAgent(nullptr);
 }
 
+void InspectorCSSAgent::documentDetached(Document& document)
+{
+    Vector<CSSStyleSheet*> emptyList;
+    setActiveStyleSheetsForDocument(document, emptyList);
+
+    m_documentToKnownCSSStyleSheets.remove(&document);
+}
+
 void InspectorCSSAgent::mediaQueryResultChanged()
 {
-    if (m_frontendDispatcher)
-        m_frontendDispatcher->mediaQueryResultChanged();
+    m_frontendDispatcher->mediaQueryResultChanged();
+}
+
+void InspectorCSSAgent::activeStyleSheetsUpdated(Document& document)
+{
+    Vector<CSSStyleSheet*> cssStyleSheets;
+    collectAllDocumentStyleSheets(document, cssStyleSheets);
+
+    setActiveStyleSheetsForDocument(document, cssStyleSheets);
+}
+
+void InspectorCSSAgent::setActiveStyleSheetsForDocument(Document& document, Vector<CSSStyleSheet*>& activeStyleSheets)
+{
+    HashSet<CSSStyleSheet*>& previouslyKnownActiveStyleSheets = m_documentToKnownCSSStyleSheets.add(&document, HashSet<CSSStyleSheet*>()).iterator->value;
+
+    HashSet<CSSStyleSheet*> removedStyleSheets(previouslyKnownActiveStyleSheets);
+    Vector<CSSStyleSheet*> addedStyleSheets;
+    for (auto& activeStyleSheet : activeStyleSheets) {
+        if (removedStyleSheets.contains(activeStyleSheet))
+            removedStyleSheets.remove(activeStyleSheet);
+        else
+            addedStyleSheets.append(activeStyleSheet);
+    }
+
+    for (auto* cssStyleSheet : removedStyleSheets) {
+        previouslyKnownActiveStyleSheets.remove(cssStyleSheet);
+        RefPtr<InspectorStyleSheet> inspectorStyleSheet = m_cssStyleSheetToInspectorStyleSheet.get(cssStyleSheet);
+        if (m_idToInspectorStyleSheet.contains(inspectorStyleSheet->id())) {
+            String id = unbindStyleSheet(inspectorStyleSheet.get());
+            m_frontendDispatcher->styleSheetRemoved(id);
+        }
+    }
+
+    for (auto* cssStyleSheet : addedStyleSheets) {
+        previouslyKnownActiveStyleSheets.add(cssStyleSheet);
+        if (!m_cssStyleSheetToInspectorStyleSheet.contains(cssStyleSheet)) {
+            InspectorStyleSheet* inspectorStyleSheet = bindStyleSheet(cssStyleSheet);
+            m_frontendDispatcher->styleSheetAdded(inspectorStyleSheet->buildObjectForStyleSheetInfo());
+        }
+    }
 }
 
 void InspectorCSSAgent::didCreateNamedFlow(Document& document, WebKitNamedFlow& namedFlow)
@@ -597,13 +648,39 @@ void InspectorCSSAgent::getComputedStyleForNode(ErrorString& errorString, int no
 void InspectorCSSAgent::getAllStyleSheets(ErrorString&, RefPtr<Inspector::Protocol::Array<Inspector::Protocol::CSS::CSSStyleSheetHeader>>& styleInfos)
 {
     styleInfos = Inspector::Protocol::Array<Inspector::Protocol::CSS::CSSStyleSheetHeader>::create();
-    Vector<Document*> documents = m_domAgent->documents();
-    for (Vector<Document*>::iterator it = documents.begin(); it != documents.end(); ++it) {
-        StyleSheetList& list = (*it)->styleSheets();
-        for (unsigned i = 0; i < list.length(); ++i) {
-            StyleSheet& styleSheet = *list.item(i);
-            if (is<CSSStyleSheet>(styleSheet))
-                collectStyleSheets(&downcast<CSSStyleSheet>(styleSheet), styleInfos.get());
+
+    Vector<InspectorStyleSheet*> inspectorStyleSheets;
+    collectAllStyleSheets(inspectorStyleSheets);
+    for (auto* inspectorStyleSheet : inspectorStyleSheets)
+        styleInfos->addItem(inspectorStyleSheet->buildObjectForStyleSheetInfo());
+}
+
+void InspectorCSSAgent::collectAllStyleSheets(Vector<InspectorStyleSheet*>& result)
+{
+    Vector<CSSStyleSheet*> cssStyleSheets;
+    for (auto* document : m_domAgent->documents())
+        collectAllDocumentStyleSheets(*document, cssStyleSheets);
+
+    for (auto* cssStyleSheet : cssStyleSheets)
+        result.append(bindStyleSheet(cssStyleSheet));
+}
+
+void InspectorCSSAgent::collectAllDocumentStyleSheets(Document& document, Vector<CSSStyleSheet*>& result)
+{
+    Vector<RefPtr<CSSStyleSheet>> cssStyleSheets = document.styleSheetCollection().activeStyleSheetsForInspector();
+    for (auto& cssStyleSheet : cssStyleSheets)
+        collectStyleSheets(cssStyleSheet.get(), result);
+}
+
+void InspectorCSSAgent::collectStyleSheets(CSSStyleSheet* styleSheet, Vector<CSSStyleSheet*>& result)
+{
+    result.append(styleSheet);
+
+    for (unsigned i = 0, size = styleSheet->length(); i < size; ++i) {
+        CSSRule* rule = styleSheet->item(i);
+        if (is<CSSImportRule>(*rule)) {
+            if (CSSStyleSheet* importedStyleSheet = downcast<CSSImportRule>(*rule).styleSheet())
+                collectStyleSheets(importedStyleSheet, result);
         }
     }
 }
@@ -810,17 +887,13 @@ int InspectorCSSAgent::documentNodeWithRequestedFlowsId(Document* document)
     return documentNodeId;
 }
 
-void InspectorCSSAgent::collectStyleSheets(CSSStyleSheet* styleSheet, Inspector::Protocol::Array<Inspector::Protocol::CSS::CSSStyleSheetHeader>* result)
+String InspectorCSSAgent::unbindStyleSheet(InspectorStyleSheet* inspectorStyleSheet)
 {
-    InspectorStyleSheet* inspectorStyleSheet = bindStyleSheet(styleSheet);
-    result->addItem(inspectorStyleSheet->buildObjectForStyleSheetInfo());
-    for (unsigned i = 0, size = styleSheet->length(); i < size; ++i) {
-        CSSRule* rule = styleSheet->item(i);
-        if (is<CSSImportRule>(*rule)) {
-            if (CSSStyleSheet* importedStyleSheet = downcast<CSSImportRule>(*rule).styleSheet())
-                collectStyleSheets(importedStyleSheet, result);
-        }
-    }
+    String id = inspectorStyleSheet->id();
+    m_idToInspectorStyleSheet.remove(id);
+    if (inspectorStyleSheet->pageStyleSheet())
+        m_cssStyleSheetToInspectorStyleSheet.remove(inspectorStyleSheet->pageStyleSheet());
+    return id;
 }
 
 InspectorStyleSheet* InspectorCSSAgent::bindStyleSheet(CSSStyleSheet* styleSheet)
@@ -832,6 +905,8 @@ InspectorStyleSheet* InspectorCSSAgent::bindStyleSheet(CSSStyleSheet* styleSheet
         inspectorStyleSheet = InspectorStyleSheet::create(m_domAgent->pageAgent(), id, styleSheet, detectOrigin(styleSheet, document), InspectorDOMAgent::documentURLString(document), this);
         m_idToInspectorStyleSheet.set(id, inspectorStyleSheet);
         m_cssStyleSheetToInspectorStyleSheet.set(styleSheet, inspectorStyleSheet);
+        if (m_creatingViaInspectorStyleSheet)
+            m_documentToInspectorStyleSheet.add(document, inspectorStyleSheet);
     }
     return inspectorStyleSheet.get();
 }
@@ -864,27 +939,18 @@ InspectorStyleSheet* InspectorCSSAgent::viaInspectorStyleSheet(Document* documen
         else
             return nullptr;
 
+        // Inserting this <style> into the document will trigger activeStyleSheetsUpdated
+        // and we will create an InspectorStyleSheet for this <style>'s CSSStyleSheet.
+        // Set this flag, so when we create it, we put it into the via inspector hash map.
+        m_creatingViaInspectorStyleSheet = true;
         InlineStyleOverrideScope overrideScope(document);
         targetNode->appendChild(styleElement, ec);
+        m_creatingViaInspectorStyleSheet = false;
     }
     if (ec)
         return nullptr;
 
-    CSSStyleSheet* cssStyleSheet = nullptr;
-    if (styleElement->isHTMLElement())
-        cssStyleSheet = downcast<HTMLStyleElement>(*styleElement).sheet();
-    else if (styleElement->isSVGElement())
-        cssStyleSheet = downcast<SVGStyleElement>(*styleElement).sheet();
-
-    if (!cssStyleSheet)
-        return nullptr;
-
-    String id = String::number(m_lastStyleSheetId++);
-    inspectorStyleSheet = InspectorStyleSheet::create(m_domAgent->pageAgent(), id, cssStyleSheet, Inspector::Protocol::CSS::StyleSheetOrigin::Inspector, InspectorDOMAgent::documentURLString(document), this);
-    m_idToInspectorStyleSheet.set(id, inspectorStyleSheet);
-    m_cssStyleSheetToInspectorStyleSheet.set(cssStyleSheet, inspectorStyleSheet);
-    m_documentToInspectorStyleSheet.set(document, inspectorStyleSheet);
-    return inspectorStyleSheet.get();
+    return m_documentToInspectorStyleSheet.get(document);
 }
 
 InspectorStyleSheet* InspectorCSSAgent::assertStyleSheetForId(ErrorString& errorString, const String& styleSheetId)
@@ -899,17 +965,20 @@ InspectorStyleSheet* InspectorCSSAgent::assertStyleSheetForId(ErrorString& error
 
 Inspector::Protocol::CSS::StyleSheetOrigin InspectorCSSAgent::detectOrigin(CSSStyleSheet* pageStyleSheet, Document* ownerDocument)
 {
-    auto origin = Inspector::Protocol::CSS::StyleSheetOrigin::Regular;
+    if (m_creatingViaInspectorStyleSheet)
+        return Inspector::Protocol::CSS::StyleSheetOrigin::Inspector;
+
     if (pageStyleSheet && !pageStyleSheet->ownerNode() && pageStyleSheet->href().isEmpty())
-        origin = Inspector::Protocol::CSS::StyleSheetOrigin::UserAgent;
-    else if (pageStyleSheet && pageStyleSheet->ownerNode() && pageStyleSheet->ownerNode()->nodeName() == "#document")
-        origin = Inspector::Protocol::CSS::StyleSheetOrigin::User;
-    else {
-        InspectorStyleSheet* viaInspectorStyleSheetForOwner = viaInspectorStyleSheet(ownerDocument, false);
-        if (viaInspectorStyleSheetForOwner && pageStyleSheet == viaInspectorStyleSheetForOwner->pageStyleSheet())
-            origin = Inspector::Protocol::CSS::StyleSheetOrigin::Inspector;
-    }
-    return origin;
+        return Inspector::Protocol::CSS::StyleSheetOrigin::UserAgent;
+    
+    if (pageStyleSheet && pageStyleSheet->ownerNode() && pageStyleSheet->ownerNode()->nodeName() == "#document")
+        return Inspector::Protocol::CSS::StyleSheetOrigin::User;
+
+    InspectorStyleSheet* viaInspectorStyleSheetForOwner = viaInspectorStyleSheet(ownerDocument, false);
+    if (viaInspectorStyleSheetForOwner && pageStyleSheet == viaInspectorStyleSheetForOwner->pageStyleSheet())
+        return Inspector::Protocol::CSS::StyleSheetOrigin::Inspector;
+
+    return Inspector::Protocol::CSS::StyleSheetOrigin::Regular;
 }
 
 RefPtr<Inspector::Protocol::CSS::CSSRule> InspectorCSSAgent::buildObjectForRule(StyleRule* styleRule, StyleResolver& styleResolver, Element* element)
index b592f1d..8da74a9 100644 (file)
@@ -100,7 +100,9 @@ public:
     void reset();
 
     // InspectorInstrumentation callbacks.
+    void documentDetached(Document&);
     void mediaQueryResultChanged();
+    void activeStyleSheetsUpdated(Document&);
     void didCreateNamedFlow(Document&, WebKitNamedFlow&);
     void willRemoveNamedFlow(Document&, WebKitNamedFlow&);
     void didChangeRegionOverset(Document&, WebKitNamedFlow&);
@@ -140,8 +142,13 @@ private:
     InspectorStyleSheetForInlineStyle* asInspectorStyleSheet(Element* element);
     Element* elementForId(ErrorString&, int nodeId);
     int documentNodeWithRequestedFlowsId(Document*);
-    void collectStyleSheets(CSSStyleSheet*, Inspector::Protocol::Array<Inspector::Protocol::CSS::CSSStyleSheetHeader>*);
 
+    void collectAllStyleSheets(Vector<InspectorStyleSheet*>&);
+    void collectAllDocumentStyleSheets(Document&, Vector<CSSStyleSheet*>&);
+    void collectStyleSheets(CSSStyleSheet*, Vector<CSSStyleSheet*>&);
+    void setActiveStyleSheetsForDocument(Document&, Vector<CSSStyleSheet*>& activeStyleSheets);
+
+    String unbindStyleSheet(InspectorStyleSheet*);
     InspectorStyleSheet* bindStyleSheet(CSSStyleSheet*);
     InspectorStyleSheet* viaInspectorStyleSheet(Document*, bool createIfAbsent);
     InspectorStyleSheet* assertStyleSheetForId(ErrorString&, const String&);
@@ -172,11 +179,13 @@ private:
     CSSStyleSheetToInspectorStyleSheet m_cssStyleSheetToInspectorStyleSheet;
     NodeToInspectorStyleSheet m_nodeToInspectorStyleSheet;
     DocumentToViaInspectorStyleSheet m_documentToInspectorStyleSheet;
+    HashMap<Document*, HashSet<CSSStyleSheet*>> m_documentToKnownCSSStyleSheets;
     NodeIdToForcedPseudoState m_nodeIdToForcedPseudoState;
     HashSet<int> m_namedFlowCollectionsRequested;
     std::unique_ptr<ChangeRegionOversetTask> m_changeRegionOversetTask;
 
     int m_lastStyleSheetId;
+    bool m_creatingViaInspectorStyleSheet { false };
 };
 
 } // namespace WebCore
index 5eeb0db..e28c1ca 100644 (file)
@@ -184,6 +184,12 @@ void InspectorInstrumentation::didInvalidateStyleAttrImpl(InstrumentingAgents& i
         domDebuggerAgent->didInvalidateStyleAttr(node);
 }
 
+void InspectorInstrumentation::documentDetachedImpl(InstrumentingAgents& instrumentingAgents, Document& document)
+{
+    if (InspectorCSSAgent* cssAgent = instrumentingAgents.inspectorCSSAgent())
+        cssAgent->documentDetached(document);
+}
+
 void InspectorInstrumentation::frameWindowDiscardedImpl(InstrumentingAgents& instrumentingAgents, DOMWindow* window)
 {
     if (WebConsoleAgent* consoleAgent = instrumentingAgents.webConsoleAgent())
@@ -196,6 +202,12 @@ void InspectorInstrumentation::mediaQueryResultChangedImpl(InstrumentingAgents&
         cssAgent->mediaQueryResultChanged();
 }
 
+void InspectorInstrumentation::activeStyleSheetsUpdatedImpl(InstrumentingAgents& instrumentingAgents, Document& document)
+{
+    if (InspectorCSSAgent* cssAgent = instrumentingAgents.inspectorCSSAgent())
+        cssAgent->activeStyleSheetsUpdated(document);
+}
+
 void InspectorInstrumentation::didPushShadowRootImpl(InstrumentingAgents& instrumentingAgents, Element& host, ShadowRoot& root)
 {
     if (InspectorDOMAgent* domAgent = instrumentingAgents.inspectorDOMAgent())
index 61ab4ec..d17cb63 100644 (file)
@@ -122,8 +122,10 @@ public:
     static void didRemoveDOMAttr(Document&, Element&, const AtomicString& name);
     static void characterDataModified(Document&, CharacterData&);
     static void didInvalidateStyleAttr(Document&, Node&);
+    static void documentDetached(Document&);
     static void frameWindowDiscarded(Frame*, DOMWindow*);
     static void mediaQueryResultChanged(Document&);
+    static void activeStyleSheetsUpdated(Document&);
     static void didPushShadowRoot(Element& host, ShadowRoot&);
     static void willPopShadowRoot(Element& host, ShadowRoot&);
     static void pseudoElementCreated(Page*, PseudoElement&);
@@ -303,8 +305,10 @@ private:
     static void didRemoveDOMAttrImpl(InstrumentingAgents&, Element&, const AtomicString& name);
     static void characterDataModifiedImpl(InstrumentingAgents&, CharacterData&);
     static void didInvalidateStyleAttrImpl(InstrumentingAgents&, Node&);
+    static void documentDetachedImpl(InstrumentingAgents&, Document&);
     static void frameWindowDiscardedImpl(InstrumentingAgents&, DOMWindow*);
     static void mediaQueryResultChangedImpl(InstrumentingAgents&);
+    static void activeStyleSheetsUpdatedImpl(InstrumentingAgents&, Document&);
     static void didPushShadowRootImpl(InstrumentingAgents&, Element& host, ShadowRoot&);
     static void willPopShadowRootImpl(InstrumentingAgents&, Element& host, ShadowRoot&);
     static void pseudoElementCreatedImpl(InstrumentingAgents&, PseudoElement&);
@@ -542,6 +546,13 @@ inline void InspectorInstrumentation::didInvalidateStyleAttr(Document& document,
         didInvalidateStyleAttrImpl(*instrumentingAgents, node);
 }
 
+inline void InspectorInstrumentation::documentDetached(Document& document)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForDocument(document))
+        documentDetachedImpl(*instrumentingAgents, document);
+}
+
 inline void InspectorInstrumentation::frameWindowDiscarded(Frame* frame, DOMWindow* domWindow)
 {
     if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForFrame(frame))
@@ -555,6 +566,13 @@ inline void InspectorInstrumentation::mediaQueryResultChanged(Document& document
         mediaQueryResultChangedImpl(*instrumentingAgents);
 }
 
+inline void InspectorInstrumentation::activeStyleSheetsUpdated(Document& document)
+{
+    FAST_RETURN_IF_NO_FRONTENDS(void());
+    if (InstrumentingAgents* instrumentingAgents = instrumentingAgentsForDocument(document))
+        activeStyleSheetsUpdatedImpl(*instrumentingAgents, document);
+}
+
 inline void InspectorInstrumentation::didPushShadowRoot(Element& host, ShadowRoot& root)
 {
     FAST_RETURN_IF_NO_FRONTENDS(void());
index 2ea0935..7a35206 100644 (file)
@@ -1,5 +1,40 @@
 2015-08-26  Joseph Pecoraro  <pecoraro@apple.com>
 
+        Web Inspector: Implement tracking of active stylesheets in the frontend
+        https://bugs.webkit.org/show_bug.cgi?id=105828
+
+        Reviewed by Timothy Hatcher.
+
+        * UserInterface/Models/CSSStyleSheet.js:
+        (WebInspector.CSSStyleSheet):
+        (WebInspector.CSSStyleSheet.prototype.get origin):
+        (WebInspector.CSSStyleSheet.prototype.updateInfo):
+        Add a new origin attribute that has been sent from the backend for a while.
+
+        * UserInterface/Controllers/CSSStyleManager.js:
+        (WebInspector.CSSStyleManager.prototype.styleSheetAdded):
+        (WebInspector.CSSStyleManager.prototype.styleSheetRemoved):
+        Handle the new events by managing the new CSSStyleSheets.
+        
+        (WebInspector.CSSStyleManager):
+        (WebInspector.CSSStyleManager.prototype._mainResourceDidChange):
+        Reset the legacy fetching flag. Fetching is only needed for legacy backends.
+
+        (WebInspector.CSSStyleManager.prototype._fetchInfoForAllStyleSheets):
+        Include the new origin property in the legacy updateInfo path.
+
+        * UserInterface/Protocol/CSSObserver.js:
+        (WebInspector.CSSObserver.prototype.styleSheetAdded):
+        (WebInspector.CSSObserver.prototype.styleSheetRemoved):
+        Forward to the manager.
+
+        * UserInterface/Views/CSSStyleDetailsSidebarPanel.js:
+        (WebInspector.CSSStyleDetailsSidebarPanel):
+        Refresh the sidebar when stylesheets are added / removed, as that
+        may affect the style of the select element.
+
+2015-08-26  Joseph Pecoraro  <pecoraro@apple.com>
+
         Web Inspector: Drop iOS 6 Legacy Remote Inspector Support
         https://bugs.webkit.org/show_bug.cgi?id=148456
 
index c41586f..ac2b658 100644 (file)
@@ -43,10 +43,33 @@ WebInspector.CSSStyleManager = class CSSStyleManager extends WebInspector.Object
 
         this._colorFormatSetting = new WebInspector.Setting("default-color-format", WebInspector.Color.Format.Original);
 
-        this._fetchedInitialStyleSheets = false;
         this._styleSheetIdentifierMap = new Map;
         this._styleSheetFrameURLMap = new Map;
         this._nodeStylesMap = {};
+
+        // COMPATIBILITY (iOS 9): Legacy backends did not send stylesheet
+        // added/removed events and must be fetched manually.
+        this._fetchedInitialStyleSheets = window.CSSAgent && window.CSSAgent.hasEvent("styleSheetAdded");
+    }
+
+    // Static
+
+    static protocolStyleSheetOriginToEnum(origin)
+    {
+        // FIXME: Switch from CSSRule.Type to CSSStyleSheet.Type everywhere.
+        switch (origin) {
+        case CSSAgent.StyleSheetOrigin.Regular:
+            return WebInspector.CSSStyleSheet.Type.Author;
+        case CSSAgent.StyleSheetOrigin.User:
+            return WebInspector.CSSStyleSheet.Type.User;
+        case CSSAgent.StyleSheetOrigin.UserAgent:
+            return WebInspector.CSSStyleSheet.Type.UserAgent;
+        case CSSAgent.StyleSheetOrigin.Inspector:
+            return WebInspector.CSSStyleSheet.Type.Inspector;
+        default:
+            console.assert(false, "Unknown StyleSheetOrigin", origin);
+            return CSSAgent.StyleSheetOrigin.Regular;
+        }
     }
 
     // Public
@@ -152,6 +175,29 @@ WebInspector.CSSStyleManager = class CSSStyleManager extends WebInspector.Object
         this._updateResourceContent(styleSheet);
     }
 
+    styleSheetAdded(styleSheetInfo)
+    {
+        console.assert(!this._styleSheetIdentifierMap.has(styleSheetInfo.styleSheetId), "Attempted to add a CSSStyleSheet but identifier was already in use");
+        let styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId);
+        let parentFrame = WebInspector.frameResourceManager.frameForIdentifier(styleSheetInfo.frameId);
+        let origin = WebInspector.CSSStyleManager.protocolStyleSheetOriginToEnum(styleSheetInfo.origin);
+        styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, origin, styleSheetInfo.isInline, styleSheetInfo.startLine, styleSheetInfo.startColumn);
+
+        this.dispatchEventToListeners(WebInspector.CSSStyleManager.Event.StyleSheetAdded, {styleSheet});
+    }
+
+    styleSheetRemoved(styleSheetIdentifier)
+    {
+        let styleSheet = this._styleSheetIdentifierMap.get(styleSheetIdentifier);
+        console.assert(styleSheet, "Attempted to remove a CSSStyleSheet that was not tracked");
+        if (!styleSheet)
+            return;
+
+        this._styleSheetIdentifierMap.delete(styleSheetIdentifier);
+
+        this.dispatchEventToListeners(WebInspector.CSSStyleManager.Event.StyleSheetRemoved, {styleSheet});
+    }
+
     // Private
 
     _nodePseudoClassesDidChange(event)
@@ -187,7 +233,7 @@ WebInspector.CSSStyleManager = class CSSStyleManager extends WebInspector.Object
 
         // Clear our maps when the main frame navigates.
 
-        this._fetchedInitialStyleSheets = false;
+        this._fetchedInitialStyleSheets = window.CSSAgent && window.CSSAgent.hasEvent("styleSheetAdded");
         this._styleSheetIdentifierMap.clear();
         this._styleSheetFrameURLMap.clear();
         this._nodeStylesMap = {};
@@ -267,6 +313,7 @@ WebInspector.CSSStyleManager = class CSSStyleManager extends WebInspector.Object
 
             for (let styleSheetInfo of styleSheets) {
                 let parentFrame = WebInspector.frameResourceManager.frameForIdentifier(styleSheetInfo.frameId);
+                let origin = WebInspector.CSSStyleManager.protocolStyleSheetOriginToEnum(styleSheetInfo.origin);
 
                 // COMPATIBILITY (iOS 9): The info did not have 'isInline', 'startLine', and 'startColumn', so make false and 0 in these cases.
                 let isInline = styleSheetInfo.isInline || false;
@@ -274,7 +321,7 @@ WebInspector.CSSStyleManager = class CSSStyleManager extends WebInspector.Object
                 let startColumn = styleSheetInfo.startColumn || 0;
 
                 let styleSheet = this.styleSheetForIdentifier(styleSheetInfo.styleSheetId);
-                styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, isInline, startLine, startColumn);
+                styleSheet.updateInfo(styleSheetInfo.sourceURL, parentFrame, origin, isInline, startLine, startColumn);
 
                 let key = this._frameURLMapKey(parentFrame, styleSheetInfo.sourceURL);
                 this._styleSheetFrameURLMap.set(key, styleSheet);
@@ -374,4 +421,9 @@ WebInspector.CSSStyleManager = class CSSStyleManager extends WebInspector.Object
     }
 };
 
+WebInspector.CSSStyleManager.Event = {
+    StyleSheetAdded: "css-style-manager-style-sheet-added",
+    StyleSheetRemoved: "css-style-manager-style-sheet-removed",
+};
+
 WebInspector.CSSStyleManager.ForceablePseudoClasses = ["active", "focus", "hover", "visited"];
index 6a00a80..aac3de5 100644 (file)
@@ -613,7 +613,7 @@ WebInspector.DebuggerManager = class DebuggerManager extends WebInspector.Object
 
     _pauseReasonFromPayload(payload)
     {
-        // FIXME: Handle other backend pause seasons.
+        // FIXME: Handle other backend pause reasons.
         switch (payload) {
         case DebuggerAgent.PausedReason.Assert:
             return WebInspector.DebuggerManager.PauseReason.Assertion;
index e1b1857..b294dcf 100644 (file)
@@ -34,6 +34,7 @@ WebInspector.CSSStyleSheet = class CSSStyleSheet extends WebInspector.SourceCode
         this._id = id || null;
         this._url = null;
         this._parentFrame = null;
+        this._origin = null;
         this._startLineNumber = 0;
         this._startColumnNumber = 0;
 
@@ -62,6 +63,11 @@ WebInspector.CSSStyleSheet = class CSSStyleSheet extends WebInspector.SourceCode
         return this._parentFrame;
     }
 
+    get origin()
+    {
+        return this._origin;
+    }
+
     get url()
     {
         return this._url;
@@ -137,7 +143,7 @@ WebInspector.CSSStyleSheet = class CSSStyleSheet extends WebInspector.SourceCode
 
     // Protected
 
-    updateInfo(url, parentFrame, inlineStyle, startLineNumber, startColumnNumber)
+    updateInfo(url, parentFrame, origin, inlineStyle, startLineNumber, startColumnNumber)
     {
         this._hasInfo = true;
 
@@ -145,6 +151,7 @@ WebInspector.CSSStyleSheet = class CSSStyleSheet extends WebInspector.SourceCode
         this._urlComponents = undefined;
 
         this._parentFrame = parentFrame || null;
+        this._origin = origin;
 
         this._inlineStyleTag = inlineStyle;
         this._startLineNumber = startLineNumber;
@@ -205,3 +212,10 @@ WebInspector.CSSStyleSheet._nextUniqueDisplayNameNumber = 1;
 WebInspector.CSSStyleSheet.Event = {
     ContentDidChange: "stylesheet-content-did-change"
 };
+
+WebInspector.CSSStyleSheet.Type = {
+    Author: "css-stylesheet-type-author",
+    User: "css-stylesheet-type-user",
+    UserAgent: "css-stylesheet-type-user-agent",
+    Inspector: "css-stylesheet-type-inspector"
+};
index 996c966..f8e0570 100644 (file)
@@ -37,14 +37,14 @@ WebInspector.CSSObserver = class CSSObserver
         WebInspector.cssStyleManager.styleSheetChanged(styleSheetId);
     }
 
-    styleSheetAdded(header)
+    styleSheetAdded(styleSheetInfo)
     {
-        // FIXME: Not implemented. <rdar://problem/13213680>
+        WebInspector.cssStyleManager.styleSheetAdded(styleSheetInfo);
     }
 
-    styleSheetRemoved(header)
+    styleSheetRemoved(id)
     {
-        // FIXME: Not implemented. <rdar://problem/13213680>
+        WebInspector.cssStyleManager.styleSheetRemoved(id);
     }
 
     namedFlowCreated(namedFlow)
index 2b034b6..be76b7c 100644 (file)
@@ -101,6 +101,9 @@ WebInspector.CSSStyleDetailsSidebarPanel = class CSSStyleDetailsSidebarPanel ext
         this._filterBar.addEventListener(WebInspector.FilterBar.Event.FilterDidChange, this._filterDidChange, this);
         optionsContainer.appendChild(this._filterBar.element);
 
+        WebInspector.cssStyleManager.addEventListener(WebInspector.CSSStyleManager.Event.StyleSheetAdded, this.refresh, this);
+        WebInspector.cssStyleManager.addEventListener(WebInspector.CSSStyleManager.Event.StyleSheetRemoved, this.refresh, this);
+
         this.element.appendChild(optionsContainer);
     }