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;
}
/**