tor-browser

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

commit 8b78ccb4f9114fac45965164bc3b73bcfaaf0c65
parent c2d456b45faa46ecd3e1c9991f4c892198876a5d
Author: Nicolas Chevobbe <nchevobbe@mozilla.com>
Date:   Sun, 23 Nov 2025 22:45:19 +0000

Bug 2001656 - [devtools] Disable :hov panel checkboxes when pseudo element node is selected. r=devtools-reviewers,jdescottes.

Since the markup view offers a context menu for changing pseudo classes as well,
we add a method in the inspector that both the markup and rules views can call
to know if pseudo class can be toggled on the currently selected element.
Existing tests for both the markup and rules view are extended to check the state
for text nodes and pseudo elements.

Differential Revision: https://phabricator.services.mozilla.com/D273595

Diffstat:
Mdevtools/client/inspector/inspector.js | 16++++++++++++++++
Mdevtools/client/inspector/markup/markup-context-menu.js | 7++++---
Mdevtools/client/inspector/rules/rules.js | 5++++-
Mdevtools/client/inspector/rules/test/browser_rules_pseudo_lock_options.js | 66+++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
Mdevtools/client/inspector/test/browser_inspector_pseudoclass-menu.js | 60+++++++++++++++++++++++++++++++++++++++++++++++++-----------
5 files changed, 130 insertions(+), 24 deletions(-)

diff --git a/devtools/client/inspector/inspector.js b/devtools/client/inspector/inspector.js @@ -1994,6 +1994,22 @@ class Inspector extends EventEmitter { } /** + * Returns true if the "Change pseudo class" (either via the ":hov" panel checkboxes, + * or the markup view context menu entries) can be performed for the currently selected node. + * + * @returns {boolean} + */ + canTogglePseudoClassForSelectedNode() { + if (!this.selection) { + return false; + } + + return ( + this.selection.isElementNode() && !this.selection.isPseudoElementNode() + ); + } + + /** * Initiate screenshot command on selected node. */ async screenshotNode() { diff --git a/devtools/client/inspector/markup/markup-context-menu.js b/devtools/client/inspector/markup/markup-context-menu.js @@ -683,8 +683,9 @@ class MarkupContextMenu { return pasteSubmenu; } - _getPseudoClassSubmenu(isElement) { + _getPseudoClassSubmenu() { const menu = new Menu(); + const enabled = this.inspector.canTogglePseudoClassForSelectedNode(); // Set the pseudo classes for (const name of PSEUDO_CLASSES) { @@ -695,7 +696,7 @@ class MarkupContextMenu { click: () => this.inspector.togglePseudoClass(name), }); - if (isElement) { + if (enabled) { const checked = this.selection.nodeFront.hasPseudoClassLock(name); menuitem.checked = checked; } else { @@ -855,7 +856,7 @@ class MarkupContextMenu { menu.append( new MenuItem({ label: INSPECTOR_L10N.getStr("inspectorPseudoClassSubmenu.label"), - submenu: this._getPseudoClassSubmenu(isElement), + submenu: this._getPseudoClassSubmenu(), }) ); diff --git a/devtools/client/inspector/rules/rules.js b/devtools/client/inspector/rules/rules.js @@ -1243,7 +1243,10 @@ CssRuleView.prototype = { * Update the pseudo class options for the currently highlighted element. */ refreshPseudoClassPanel() { - if (!this._elementStyle || !this.inspector.selection.isElementNode()) { + if ( + !this._elementStyle || + !this.inspector.canTogglePseudoClassForSelectedNode() + ) { this.pseudoClassCheckboxes.forEach(checkbox => { checkbox.disabled = true; }); diff --git a/devtools/client/inspector/rules/test/browser_rules_pseudo_lock_options.js b/devtools/client/inspector/rules/test/browser_rules_pseudo_lock_options.js @@ -8,6 +8,8 @@ const { PSEUDO_CLASSES, } = require("resource://devtools/shared/css/constants.js"); +const nodeConstants = require("resource://devtools/shared/dom-node-constants.js"); + const TEST_URI = ` <style type='text/css'> div { @@ -34,8 +36,12 @@ const TEST_URI = ` div:target { color: crimson; } + aside::after { + content: "-"; + } </style> <div>test div</div> + <aside>test pseudo</aside> `; add_task(async function () { @@ -86,17 +92,45 @@ add_task(async function () { await togglePseudoClass(inspector, view, ":target"); await assertPseudoRemoved(inspector, view, 2); - info("Select a null element"); + info( + "Check that all pseudo locks are unchecked and disabled when selection is null" + ); await view.selectElement(null); + assertPseudoClassCheckboxesState(view, false); - info("Check that all pseudo locks are unchecked and disabled"); - for (const pseudo of PSEUDO_CLASSES) { - const checkbox = getPseudoClassCheckbox(view, pseudo); - ok( - !checkbox.checked && checkbox.disabled, - `${pseudo} checkbox is unchecked and disabled` - ); - } + info("Check that selecting an element again re-enable the checkboxes"); + await selectNode("aside", inspector); + assertPseudoClassCheckboxesState(view, true); + + info( + "Check that all pseudo locks are unchecked and disabled when a text node is selected" + ); + const asideNodeFront = await getNodeFront("aside", inspector); + const asideChildren = await inspector.walker.children(asideNodeFront); + const [textNodeFront, afterNodeFront] = asideChildren.nodes; + await selectNode(textNodeFront, inspector); + // sanity check + is( + inspector.selection.nodeFront.nodeType, + nodeConstants.TEXT_NODE, + "We selected the text node" + ); + assertPseudoClassCheckboxesState(view, false); + + info("Check that selecting an element again re-enable the checkboxes"); + await selectNode("aside", inspector); + assertPseudoClassCheckboxesState(view, true); + + info( + "Check that all pseudo locks are unchecked and disabled when a pseudo element is selected" + ); + await selectNode(afterNodeFront, inspector); + is( + inspector.selection.nodeFront.displayName, + "::after", + "We selected the ::after pseudo element" + ); + assertPseudoClassCheckboxesState(view, false); info("Toggle the pseudo class panel close"); view.pseudoClassToggle.click(); @@ -179,3 +213,17 @@ function assertPseudoPanelClosed(view) { ); } } + +function assertPseudoClassCheckboxesState(view, enabled) { + for (const pseudo of PSEUDO_CLASSES) { + const checkbox = getPseudoClassCheckbox(view, pseudo); + if (enabled) { + ok(!checkbox.disabled, `${pseudo} checkbox is not disabled`); + } else { + ok( + !checkbox.checked && checkbox.disabled, + `${pseudo} checkbox is unchecked and disabled` + ); + } + } +} diff --git a/devtools/client/inspector/test/browser_inspector_pseudoclass-menu.js b/devtools/client/inspector/test/browser_inspector_pseudoclass-menu.js @@ -8,30 +8,68 @@ const { PSEUDO_CLASSES, } = require("resource://devtools/shared/css/constants.js"); -const TEST_URI = - "data:text/html;charset=UTF-8," + - "pseudo-class lock node menu tests" + - "<div>test div</div>"; +const nodeConstants = require("resource://devtools/shared/dom-node-constants.js"); + +const TEST_URI = `data:text/html;charset=UTF-8, + <style> + div::after { + content: "-"; + } + </style> + <h1>pseudo-class lock node menu tests</h1> + <div>test div</div>`; // Strip the colon prefix from pseudo-classes (:before => before) const PSEUDOS = PSEUDO_CLASSES.map(pseudo => pseudo.substr(1)); add_task(async function () { - const { inspector, highlighterTestFront } = - await openInspectorForURL(TEST_URI); - await selectNode("div", inspector); + const { inspector } = await openInspectorForURL(TEST_URI); + const divNodeFront = await getNodeFront("div", inspector); + const divChildren = await inspector.walker.children(divNodeFront); + + info("Check pseudo element context menu on regular node"); + await selectNode(divNodeFront, inspector); + let allMenuItems = openContextMenuAndGetAllItems(inspector); + await testMenuItems(allMenuItems, inspector, true); + + const [textNodeFront, afterNodeFront] = divChildren.nodes; - const allMenuItems = openContextMenuAndGetAllItems(inspector); + info("Check pseudo element context menu on text node"); + await selectNode(textNodeFront, inspector); + // sanity check + is( + inspector.selection.nodeFront.nodeType, + nodeConstants.TEXT_NODE, + "We selected the text node" + ); + allMenuItems = openContextMenuAndGetAllItems(inspector); + await testMenuItems(allMenuItems, inspector, false); - await testMenuItems(highlighterTestFront, allMenuItems, inspector); + info("Check pseudo element context menu on pseudo element node"); + await selectNode(afterNodeFront, inspector); + is( + inspector.selection.nodeFront.displayName, + "::after", + "We selected the ::after pseudo element" + ); + allMenuItems = openContextMenuAndGetAllItems(inspector); + await testMenuItems(allMenuItems, inspector, false); }); -async function testMenuItems(highlighterTestFront, allMenuItems, inspector) { +async function testMenuItems(allMenuItems, inspector, enabled) { for (const pseudo of PSEUDOS) { const menuItem = allMenuItems.find( item => item.id === "node-menu-pseudo-" + pseudo ); ok(menuItem, ":" + pseudo + " menuitem exists"); - is(menuItem.disabled, false, ":" + pseudo + " menuitem is enabled"); + is( + menuItem.disabled, + !enabled, + `:${pseudo} menuitem is ${enabled ? "enabled" : "disabled"} for "${inspector.selection.nodeFront.displayName}"` + ); + + if (!enabled) { + continue; + } // Give the inspector panels a chance to update when the pseudoclass changes const onPseudo = inspector.selection.once("pseudoclass");