diff --git a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_ruleview_stylesheet.js b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_ruleview_stylesheet.js index 88583b76b40a..60f30b44b447 100644 --- a/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_ruleview_stylesheet.js +++ b/devtools/client/framework/browser-toolbox/test/browser_browser_toolbox_ruleview_stylesheet.js @@ -32,6 +32,7 @@ add_task(async function () { selectNode, // selectNodeInFrames depends on selectNode, getNodeFront, getNodeFrontInFrames. selectNodeInFrames, + waitUntil, }); // This is a simple test page, which contains a
with a CSS rule `color: red` @@ -63,6 +64,7 @@ add_task(async function () { info("Retrieve the sourceLabel for the rule at index 1"); const ruleView = inspector.getPanel("ruleview").view; + await waitUntil(() => getRuleViewLinkByIndex(ruleView, 1)); const sourceLabelEl = getRuleViewLinkByIndex(ruleView, 1).querySelector( ".ruleview-rule-source-label" ); diff --git a/devtools/client/inspector/rules/rules.js b/devtools/client/inspector/rules/rules.js index d8bfe03986c7..da6af7bfa964 100644 --- a/devtools/client/inspector/rules/rules.js +++ b/devtools/client/inspector/rules/rules.js @@ -2077,7 +2077,10 @@ function RuleViewTool(inspector, window) { this.inspector.styleChangeTracker.on("style-changed", this.refresh); this.inspector.commands.resourceCommand.watchResources( - [this.inspector.commands.resourceCommand.TYPES.DOCUMENT_EVENT], + [ + this.inspector.commands.resourceCommand.TYPES.DOCUMENT_EVENT, + this.inspector.commands.resourceCommand.TYPES.STYLESHEET, + ], { onAvailable: this._onResourceAvailable, ignoreExistingResources: true, @@ -2140,6 +2143,11 @@ RuleViewTool.prototype = { }, _onResourceAvailable(resources) { + if (!this.inspector) { + return; + } + + let hasNewStylesheet = false; for (const resource of resources) { if ( resource.resourceType === @@ -2148,7 +2156,23 @@ RuleViewTool.prototype = { resource.targetFront.isTopLevel ) { this.clearUserProperties(); + continue; } + + if ( + resource.resourceType === + this.inspector.commands.resourceCommand.TYPES.STYLESHEET && + // resource.isNew is only true when the stylesheet was added from DevTools, + // for example when adding a rule in the rule view. In such cases, we're already + // updating the rule view, so ignore those. + !resource.isNew + ) { + hasNewStylesheet = true; + } + } + + if (hasNewStylesheet) { + this.refresh(); } }, diff --git a/devtools/client/inspector/rules/test/browser_part2.ini b/devtools/client/inspector/rules/test/browser_part2.ini index 04b08281cb8a..e7f32804d4ef 100644 --- a/devtools/client/inspector/rules/test/browser_part2.ini +++ b/devtools/client/inspector/rules/test/browser_part2.ini @@ -173,6 +173,7 @@ skip-if = [browser_rules_refresh-no-flicker.js] [browser_rules_refresh-on-attribute-change_01.js] [browser_rules_refresh-on-style-change.js] +[browser_rules_refresh-on-stylesheet-change.js] [browser_rules_search-filter-computed-list_01.js] [browser_rules_search-filter-computed-list_02.js] [browser_rules_search-filter-computed-list_03.js] diff --git a/devtools/client/inspector/rules/test/browser_rules_refresh-on-stylesheet-change.js b/devtools/client/inspector/rules/test/browser_rules_refresh-on-stylesheet-change.js new file mode 100644 index 000000000000..f9624d357091 --- /dev/null +++ b/devtools/client/inspector/rules/test/browser_rules_refresh-on-stylesheet-change.js @@ -0,0 +1,65 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that the rule view refreshes when a stylesheet is added or modified + +const TEST_URI = "

Hello DevTools

"; + +add_task(async function () { + await addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI)); + const { inspector, view } = await openRuleView(); + + await selectNode("h1", inspector); + + info("Add a stylesheet with matching rule for the h1 node"); + let onUpdated = inspector.once("rule-view-refreshed"); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + const addedStylesheet = content.document.createElement("style"); + addedStylesheet.textContent = "h1 { background: tomato }"; + content.document.head.append(addedStylesheet); + }); + await onUpdated; + ok(true, "Rules view was refreshed when adding a stylesheet"); + checkRulesViewSelectors(view, ["element", "h1"]); + is( + getRuleViewPropertyValue(view, "h1", "background"), + "tomato", + "Expected value is displayed for the background property" + ); + + info("Modify the stylesheet added previously"); + onUpdated = inspector.once("rule-view-refreshed"); + await SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => { + const addedStylesheet = content.document.querySelector("style"); + addedStylesheet.textContent = "body h1 { background: gold; color: navy; }"; + }); + await onUpdated; + ok(true, "Rules view was refreshed when updating the stylesheet"); + checkRulesViewSelectors(view, ["element", "body h1"]); + is( + getRuleViewPropertyValue(view, "body h1", "background"), + "gold", + "Expected value is displayed for the background property" + ); + is( + getRuleViewPropertyValue(view, "body h1", "color"), + "navy", + "Expected value is displayed for the color property" + ); +}); + +function checkRulesViewSelectors(view, expectedSelectors) { + Assert.deepEqual( + getRuleSelectors(view), + expectedSelectors, + "Expected selectors are displayed" + ); +} + +function getRuleSelectors(view) { + return Array.from( + view.styleDocument.querySelectorAll(".ruleview-selectorcontainer") + ).map(el => el.textContent); +} diff --git a/devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js b/devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js index 916a6e052614..8c4179bac08c 100644 --- a/devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js +++ b/devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js @@ -185,6 +185,7 @@ async function compareAppliedStylesWithUI(inspector, view, filter) { entries = [...entryMap.values()]; const elementStyle = view._elementStyle; + await waitFor(() => elementStyle.rules.length === entries.length); is( elementStyle.rules.length, entries.length, diff --git a/devtools/client/inspector/test/head.js b/devtools/client/inspector/test/head.js index dc7145be5d27..ae2784b94b76 100644 --- a/devtools/client/inspector/test/head.js +++ b/devtools/client/inspector/test/head.js @@ -1008,9 +1008,14 @@ async function assertTooltipHiddenOnMouseOut(tooltip, target) { * @return {DOMNode} The rule editor if any at this index */ function getRuleViewRuleEditor(view, childrenIndex, nodeIndex) { + const child = view.element.children[childrenIndex]; + if (!child) { + return null; + } + return nodeIndex !== undefined - ? view.element.children[childrenIndex].childNodes[nodeIndex]._ruleEditor - : view.element.children[childrenIndex]._ruleEditor; + ? child.childNodes[nodeIndex]?._ruleEditor + : child._ruleEditor; } /**