commit d27da12ed52fff90c418c9190c29ad58953b66eb
parent b5a379ec15c88ab6c9846d6bf6dd30205797086c
Author: Nicolas Chevobbe <nchevobbe@mozilla.com>
Date: Tue, 30 Sep 2025 13:41:29 +0000
Bug 1989876 - [devtools] Don't wait for next tick to update UI when clearing Rules view search. r=devtools-reviewers,jdescottes.
Differential Revision: https://phabricator.services.mozilla.com/D266160
Diffstat:
10 files changed, 127 insertions(+), 69 deletions(-)
diff --git a/devtools/client/inspector/rules/rules.js b/devtools/client/inspector/rules/rules.js
@@ -837,79 +837,97 @@ CssRuleView.prototype = {
/**
* Called when the user enters a search term in the filter style search box.
+ * The actual filtering (done in _doFilterStyles) will be throttled if the search input
+ * isn't empty, but will happen immediately when the search gets cleared.
*/
_onFilterStyles() {
if (this._filterChangedTimeout) {
clearTimeout(this._filterChangedTimeout);
}
- const filterTimeout = this.searchValue.length ? FILTER_CHANGED_TIMEOUT : 0;
- this.searchClearButton.hidden = this.searchValue.length === 0;
+ const isSearchEmpty = this.searchValue.length === 0;
+ this.searchClearButton.hidden = isSearchEmpty;
- this._filterChangedTimeout = setTimeout(() => {
- this.searchData = {
- searchPropertyMatch: FILTER_PROP_RE.exec(this.searchValue),
- searchPropertyName: this.searchValue,
- searchPropertyValue: this.searchValue,
- strictSearchValue: "",
- strictSearchPropertyName: false,
- strictSearchPropertyValue: false,
- strictSearchAllValues: false,
- };
+ // If the search is cleared update the UI directly so calls to this function (or any
+ // callsite of it) can assume the UI is up to date directly after the call.
+ if (isSearchEmpty) {
+ this._doFilterStyles();
+ } else {
+ this._filterChangedTimeout = setTimeout(
+ () => this._doFilterStyles(),
+ FILTER_CHANGED_TIMEOUT
+ );
+ }
+ },
- if (this.searchData.searchPropertyMatch) {
- // Parse search value as a single property line and extract the
- // property name and value. If the parsed property name or value is
- // contained in backquotes (`), extract the value within the backquotes
- // and set the corresponding strict search for the property to true.
- if (FILTER_STRICT_RE.test(this.searchData.searchPropertyMatch[1])) {
- this.searchData.strictSearchPropertyName = true;
- this.searchData.searchPropertyName = FILTER_STRICT_RE.exec(
- this.searchData.searchPropertyMatch[1]
- )[1];
- } else {
- this.searchData.searchPropertyName =
- this.searchData.searchPropertyMatch[1];
- }
+ /**
+ * Actually update search data and update the UI to reflect the current search.
+ *
+ * @emits ruleview-filtered
+ */
+ _doFilterStyles() {
+ this.searchData = {
+ searchPropertyMatch: FILTER_PROP_RE.exec(this.searchValue),
+ searchPropertyName: this.searchValue,
+ searchPropertyValue: this.searchValue,
+ strictSearchValue: "",
+ strictSearchPropertyName: false,
+ strictSearchPropertyValue: false,
+ strictSearchAllValues: false,
+ };
- if (FILTER_STRICT_RE.test(this.searchData.searchPropertyMatch[2])) {
- this.searchData.strictSearchPropertyValue = true;
- this.searchData.searchPropertyValue = FILTER_STRICT_RE.exec(
- this.searchData.searchPropertyMatch[2]
- )[1];
- } else {
- this.searchData.searchPropertyValue =
- this.searchData.searchPropertyMatch[2];
- }
+ if (this.searchData.searchPropertyMatch) {
+ // Parse search value as a single property line and extract the
+ // property name and value. If the parsed property name or value is
+ // contained in backquotes (`), extract the value within the backquotes
+ // and set the corresponding strict search for the property to true.
+ if (FILTER_STRICT_RE.test(this.searchData.searchPropertyMatch[1])) {
+ this.searchData.strictSearchPropertyName = true;
+ this.searchData.searchPropertyName = FILTER_STRICT_RE.exec(
+ this.searchData.searchPropertyMatch[1]
+ )[1];
+ } else {
+ this.searchData.searchPropertyName =
+ this.searchData.searchPropertyMatch[1];
+ }
- // Strict search for stylesheets will match the property line regex.
- // Extract the search value within the backquotes to be used
- // in the strict search for stylesheets in _highlightStyleSheet.
- if (FILTER_STRICT_RE.test(this.searchValue)) {
- this.searchData.strictSearchValue = FILTER_STRICT_RE.exec(
- this.searchValue
- )[1];
- }
- } else if (FILTER_STRICT_RE.test(this.searchValue)) {
- // If the search value does not correspond to a property line and
- // is contained in backquotes, extract the search value within the
- // backquotes and set the flag to perform a strict search for all
- // the values (selector, stylesheet, property and computed values).
- const searchValue = FILTER_STRICT_RE.exec(this.searchValue)[1];
- this.searchData.strictSearchAllValues = true;
- this.searchData.searchPropertyName = searchValue;
- this.searchData.searchPropertyValue = searchValue;
- this.searchData.strictSearchValue = searchValue;
+ if (FILTER_STRICT_RE.test(this.searchData.searchPropertyMatch[2])) {
+ this.searchData.strictSearchPropertyValue = true;
+ this.searchData.searchPropertyValue = FILTER_STRICT_RE.exec(
+ this.searchData.searchPropertyMatch[2]
+ )[1];
+ } else {
+ this.searchData.searchPropertyValue =
+ this.searchData.searchPropertyMatch[2];
}
- this._clearHighlight(this.element);
- this._clearRules();
- this._createEditors();
+ // Strict search for stylesheets will match the property line regex.
+ // Extract the search value within the backquotes to be used
+ // in the strict search for stylesheets in _highlightStyleSheet.
+ if (FILTER_STRICT_RE.test(this.searchValue)) {
+ this.searchData.strictSearchValue = FILTER_STRICT_RE.exec(
+ this.searchValue
+ )[1];
+ }
+ } else if (FILTER_STRICT_RE.test(this.searchValue)) {
+ // If the search value does not correspond to a property line and
+ // is contained in backquotes, extract the search value within the
+ // backquotes and set the flag to perform a strict search for all
+ // the values (selector, stylesheet, property and computed values).
+ const searchValue = FILTER_STRICT_RE.exec(this.searchValue)[1];
+ this.searchData.strictSearchAllValues = true;
+ this.searchData.searchPropertyName = searchValue;
+ this.searchData.searchPropertyValue = searchValue;
+ this.searchData.strictSearchValue = searchValue;
+ }
+
+ this._clearHighlight(this.element);
+ this._clearRules();
+ this._createEditors();
- this.inspector.emit("ruleview-filtered");
+ this.inspector.emit("ruleview-filtered");
- this._filterChangeTimeout = null;
- }, filterTimeout);
+ this._filterChangeTimeout = null;
},
/**
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_01.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_01.js
@@ -164,8 +164,9 @@ async function clearSearchAndCheckRules(view) {
const computed = textPropEditor.computed;
info("Clearing the search filter");
+ const onRuleViewFiltered = view.inspector.once("ruleview-filtered");
EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win);
- await view.inspector.once("ruleview-filtered");
+ await onRuleViewFiltered;
info("Check the search filter is cleared and no rules are highlighted");
is(view.element.children.length, 3, "Should have 3 rules.");
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_02.js b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_02.js
@@ -80,8 +80,9 @@ async function testRemoveTextInFilter(inspector, view) {
const searchField = view.searchField;
searchField.focus();
+ const onRuleviewFiltered = inspector.once("ruleview-filtered");
EventUtils.synthesizeKey("VK_BACK_SPACE", {}, win);
- await inspector.once("ruleview-filtered");
+ await onRuleviewFiltered;
info("Check that the correct rules are visible");
is(view.element.children.length, 2, "Should have 2 rules.");
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_01.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_01.js
@@ -85,8 +85,9 @@ async function clearSearchAndCheckRules(view) {
const searchClearButton = view.searchClearButton;
info("Clearing the search filter");
+ const onRuleviewFiltered = view.inspector.once("ruleview-filtered");
EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win);
- await view.inspector.once("ruleview-filtered");
+ await onRuleviewFiltered;
info("Check the search filter is cleared and no rules are highlighted");
is(view.element.children.length, 3, "Should have 3 rules.");
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_04.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_04.js
@@ -55,8 +55,9 @@ async function testRemoveTextInFilter(inspector, view) {
const searchField = view.searchField;
searchField.focus();
+ const onRuleviewFiltered = inspector.once("ruleview-filtered");
EventUtils.synthesizeKey("VK_BACK_SPACE", {}, win);
- await inspector.once("ruleview-filtered");
+ await onRuleviewFiltered;
info("Check that the correct rules are visible");
is(view.element.children.length, 3, "Should have 3 rules.");
diff --git a/devtools/client/inspector/rules/test/browser_rules_search-filter_10.js b/devtools/client/inspector/rules/test/browser_rules_search-filter_10.js
@@ -83,8 +83,9 @@ async function clearSearchAndCheckRules(view) {
const searchClearButton = view.searchClearButton;
info("Clearing the search filter");
+ const onRuleviewFiltered = view.inspector.once("ruleview-filtered");
EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win);
- await view.inspector.once("ruleview-filtered");
+ await onRuleviewFiltered;
info("Check the search filter is cleared and no rules are highlighted");
is(view.element.children.length, 3, "Should have 3 rules.");
diff --git a/devtools/client/inspector/rules/test/browser_rules_strict-search-filter-computed-list_01.js b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter-computed-list_01.js
@@ -192,8 +192,9 @@ async function clearSearchAndCheckRules(view) {
const computed = textPropEditor.computed;
info("Clearing the search filter");
+ const onRuleviewFiltered = view.inspector.once("ruleview-filtered");
EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win);
- await view.inspector.once("ruleview-filtered");
+ await onRuleviewFiltered;
info("Check the search filter is cleared and no rules are highlighted");
is(view.element.children.length, 3, "Should have 3 rules.");
diff --git a/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_01.js b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_01.js
@@ -137,8 +137,9 @@ async function clearSearchAndCheckRules(view) {
const searchClearButton = view.searchClearButton;
info("Clearing the search filter");
+ const onRuleviewFiltered = view.inspector.once("ruleview-filtered");
EventUtils.synthesizeMouseAtCenter(searchClearButton, {}, win);
- await view.inspector.once("ruleview-filtered");
+ await onRuleviewFiltered;
info("Check the search filter is cleared and no rules are highlighted");
is(view.element.children.length, 3, "Should have 3 rules.");
diff --git a/devtools/client/inspector/rules/test/browser_rules_variables-jump-to-definition.js b/devtools/client/inspector/rules/test/browser_rules_variables-jump-to-definition.js
@@ -278,6 +278,11 @@ add_task(async function () {
});
add_task(async function checkClearSearch() {
+ const fillerDeclarations = Array.from({ length: 50 }, (_, i) => ({
+ name: `--x-${i}`,
+ value: i.toString(),
+ }));
+
await addTab(
"data:text/html;charset=utf-8," +
encodeURIComponent(`
@@ -286,11 +291,21 @@ add_task(async function checkClearSearch() {
--my-color-1: tomato;
}
+ h1#title {
+ ${
+ // Add a lot of declaration so the --my-color-1
+ // declaration would be out of view
+ fillerDeclarations
+ .map(({ name, value }) => `${name}: ${value};`)
+ .join("")
+ }
+ }
+
h1 {
--my-unique-var: var(--my-color-1);
}
</style>
- <h1>Filter</h1>
+ <h1 id="title">Filter</h1>
`)
);
@@ -324,6 +339,7 @@ add_task(async function checkClearSearch() {
// check that the rule view is no longer filtered
await checkRuleViewContent(view, [
{ selector: "element", declarations: [] },
+ { selector: "h1#title", declarations: fillerDeclarations },
{
selector: "h1",
declarations: [{ name: "--my-unique-var", value: "var(--my-color-1)" }],
@@ -366,6 +382,7 @@ async function highlightProperty(
expectedPropertyName,
expectedPropertyValue
) {
+ info(`Highlight "${expectedPropertyName}: ${expectedPropertyValue}"`);
const onHighlightProperty = view.once("element-highlighted");
jumpToDefinitionButton.click();
const highlightedElement = await onHighlightProperty;
@@ -379,4 +396,20 @@ async function highlightProperty(
expectedPropertyValue,
"The expected element was highlighted"
);
+
+ // check that the declaration we jumped to is into view
+ ok(
+ isInViewport(highlightedElement, view.styleWindow),
+ `Highlighted element is in view`
+ );
+}
+
+function isInViewport(element, win) {
+ const { top, left, bottom, right } = element.getBoundingClientRect();
+ return (
+ top >= 0 &&
+ bottom <= win.innerHeight &&
+ left >= 0 &&
+ right <= win.innerWidth
+ );
}
diff --git a/devtools/client/inspector/test/shared-head.js b/devtools/client/inspector/test/shared-head.js
@@ -786,11 +786,11 @@ var setSearchFilter = async function (view, searchValue) {
const searchField = view.searchField;
searchField.focus();
+ const onRuleviewFiltered = view.inspector.once("ruleview-filtered");
for (const key of searchValue.split("")) {
EventUtils.synthesizeKey(key, {}, view.styleWindow);
}
-
- await view.inspector.once("ruleview-filtered");
+ await onRuleviewFiltered;
};
/**