tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mdevtools/client/inspector/rules/rules.js | 136+++++++++++++++++++++++++++++++++++++++++++++----------------------------------
Mdevtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_01.js | 3++-
Mdevtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_02.js | 3++-
Mdevtools/client/inspector/rules/test/browser_rules_search-filter_01.js | 3++-
Mdevtools/client/inspector/rules/test/browser_rules_search-filter_04.js | 3++-
Mdevtools/client/inspector/rules/test/browser_rules_search-filter_10.js | 3++-
Mdevtools/client/inspector/rules/test/browser_rules_strict-search-filter-computed-list_01.js | 3++-
Mdevtools/client/inspector/rules/test/browser_rules_strict-search-filter_01.js | 3++-
Mdevtools/client/inspector/rules/test/browser_rules_variables-jump-to-definition.js | 35++++++++++++++++++++++++++++++++++-
Mdevtools/client/inspector/test/shared-head.js | 4++--
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; }; /**