Files
sousa-gecko/devtools/client/inspector/rules/test/browser_rules_keybindings.js
T
Alexandre Poirot 01a5850d0d Bug 2032219 - [devtools] Restore focus only once from top-level update code. r=devtools-reviewers,nchevobbe
This prevents having the two distinct focus-restoring logic to compete
with each others. This also prevents having a dedicated event for
waiting for the focus to be correct while waiting for the focus to be
restored in most of the tests (as the focus is restored before emitting
rule-view-refreshed)

Differential Revision: https://phabricator.services.mozilla.com/D294906
2026-07-06 11:41:11 +00:00

298 lines
9.2 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
// Test keyboard navigation in the rule view
add_task(async function () {
await pushPref("devtools.inspector.rule-view.focusNextOnEnter", false);
const tab = await addTab(`data:text/html;charset=utf-8,
<style>h1 {}</style>
<h1>Some header text</h1>`);
let { inspector, view } = await openRuleView();
await selectNode("h1", inspector);
info("Getting the ruleclose brace element for the `h1` rule");
const brace = view.styleDocument.querySelectorAll(".ruleview-ruleclose")[1];
info("Focus the new property editable field to create a color property");
const ruleEditor = getRuleViewRuleEditorAt(view, 1);
await focusNewRuleViewProperty(ruleEditor);
EventUtils.sendString("color");
info("Typing ENTER to focus the next field: property value");
let onFocus = once(brace.parentNode, "focus", true);
let onRuleViewChanged = view.once("ruleview-changed");
EventUtils.sendKey("Return");
await onFocus;
await onRuleViewChanged;
ok(true, "The value field was focused");
info("Entering a property value");
EventUtils.sendString("tomato");
info("Typing Tab again should focus a new property name");
onFocus = once(brace.parentNode, "focus", true);
onRuleViewChanged = view.once("ruleview-changed");
EventUtils.sendKey("Tab");
await onFocus;
await onRuleViewChanged;
ok(true, "The new property name field was focused");
info(
"Filling new property name with background-color and hit Tab to focus value input"
);
EventUtils.sendString("background-color");
onRuleViewChanged = view.once("ruleview-changed");
EventUtils.sendKey("Tab");
await onRuleViewChanged;
ok(true, "The value field was focused");
info("Entering a background color value");
EventUtils.sendString("gold");
info("Typing Enter should close the input and focus the value span");
onRuleViewChanged = view.once("ruleview-changed");
EventUtils.sendKey("Return");
await onRuleViewChanged;
info("Wait until the swatch for the color is created");
const colorSwatchEl = await waitFor(() =>
getRuleViewProperty(
view,
"h1",
"background-color"
)?.valueSpan?.querySelector(".inspector-colorswatch")
);
is(
view.styleDocument.activeElement.textContent,
"gold",
"Value span is focused after pressing Enter"
);
info("Type Tab should focus the color swatch");
EventUtils.sendKey("Tab");
is(
view.styleDocument.activeElement,
colorSwatchEl,
"Focused was moved to color swatch"
);
info("Press Shift Tab");
EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
is(
view.styleDocument.activeElement.textContent,
"gold",
"Focus is moved back to property value"
);
info("Press Shift Tab again");
EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
is(
view.styleDocument.activeElement.textContent,
"background-color",
"Focus is moved back to property name"
);
info("Press Shift Tab once more");
EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true });
ok(
view.styleDocument.activeElement.matches(
"input[type=checkbox].ruleview-enableproperty"
),
"Focus is moved to the prop toggle checkbox"
);
const toggleEl = view.styleDocument.activeElement;
ok(toggleEl.checked, "Checkbox is checked by default");
is(
toggleEl.getAttribute("title"),
"Enable background-color property",
"checkbox has expected label"
);
info("Press Space to uncheck checkbox");
let onRuleViewRefreshed = view.once("ruleview-changed");
EventUtils.sendKey("Space");
await onRuleViewRefreshed;
ok(!toggleEl.checked, "Checkbox is now unchecked");
info("Press Space to check checkbox back");
onRuleViewRefreshed = view.once("ruleview-changed");
EventUtils.sendKey("Space");
await onRuleViewRefreshed;
ok(toggleEl.checked, "Checkbox is checked again");
info("Re-start the toolbox");
await gDevTools.closeToolboxForTab(tab);
({ view } = await openRuleView());
});
// The `element` have specific behavior, so we want to test that keyboard navigation
// also works fine on them.
add_task(async function testKeyboardNavigationInElementRule() {
await pushPref("devtools.inspector.rule-view.focusNextOnEnter", false);
await addTab("data:text/html;charset=utf-8,<h1>Some header text</h1>");
const { inspector, view } = await openRuleView();
await selectNode("h1", inspector);
info("Getting the ruleclose brace element");
const brace = view.styleDocument.querySelector(".ruleview-ruleclose");
info("Focus the new property editable field to create a color property");
const ruleEditor = getRuleViewRuleEditorAt(view, 0);
let editor = await focusNewRuleViewProperty(ruleEditor);
editor.input.value = "color";
info("Typing ENTER to focus the next field: property value");
let onFocus = once(brace.parentNode, "focus", true);
let onRuleViewChanged = view.once("ruleview-changed");
let onStyleAttributeMutation = waitForStyleAttributeMutation(view, `color: `);
EventUtils.sendKey("Return");
await onFocus;
await onRuleViewChanged;
await onStyleAttributeMutation;
ok(true, "The value field was focused");
info("Entering a property value");
onStyleAttributeMutation = waitForStyleAttributeMutation(
view,
`color: green;`
);
editor = getCurrentInplaceEditor(view);
editor.input.value = "green";
info("Typing Tab again should focus a new property name");
onFocus = once(brace.parentNode, "focus", true);
onRuleViewChanged = view.once("ruleview-changed");
EventUtils.sendKey("Tab");
await onFocus;
await onRuleViewChanged;
await onStyleAttributeMutation;
ok(true, "The new property name field was focused");
info(
"Filling new property name with background-color and hit Tab to focus value input"
);
EventUtils.sendString("background-color");
onRuleViewChanged = view.once("ruleview-changed");
onStyleAttributeMutation = waitForStyleAttributeMutation(
view,
`background-color:`
);
EventUtils.sendKey("Tab");
await onRuleViewChanged;
await onStyleAttributeMutation;
ok(true, "The value field was focused");
info("Entering a background color value");
onStyleAttributeMutation = waitForStyleAttributeMutation(
view,
`background-color: tomato;`
);
EventUtils.sendString("tomato", view.styleWindow);
info("Typing Enter should close the input and focus the value span");
const onValueDone = view.inspector.once("rule-view-refreshed");
EventUtils.sendKey("Return");
await onValueDone;
await onStyleAttributeMutation;
is(
view.styleDocument.activeElement,
getRuleViewProperty(view, "element", "background-color").valueSpan,
`background-color value span ("tomato") is focused after pressing Enter`
);
is(
view.styleDocument.activeElement.textContent,
"tomato",
`focused element has expected text`
);
});
// Test keyboard navigation in the rule view when
// devtools.inspector.rule-view.focusNextOnEnter is set to true
add_task(async function () {
await pushPref("devtools.inspector.rule-view.focusNextOnEnter", true);
await addTab(`data:text/html;charset=utf-8,
<style>h1 {}</style>
<h1>Some header text</h1>`);
const { inspector, view } = await openRuleView();
await selectNode("h1", inspector);
info("Getting the ruleclose brace element for the `h1` rule");
const brace = view.styleDocument.querySelectorAll(".ruleview-ruleclose")[1];
info("Focus the new property editable field to create a color property");
const ruleEditor = getRuleViewRuleEditorAt(view, 1);
await focusNewRuleViewProperty(ruleEditor);
EventUtils.sendString("color");
info("Typing ENTER to focus the next field: property value");
let onFocus = once(brace.parentNode, "focus", true);
let onRuleViewChanged = view.once("ruleview-changed");
EventUtils.sendKey("Return");
await onFocus;
await onRuleViewChanged;
ok(true, "The value field was focused");
info("Entering a property value");
EventUtils.sendString("tomato");
info("Typing Enter again should focus a new property name");
onFocus = once(brace.parentNode, "focus", true);
onRuleViewChanged = view.once("ruleview-changed");
EventUtils.sendKey("Return");
await onFocus;
await onRuleViewChanged;
const activeElement = view.styleDocument.activeElement;
is(
`${activeElement.tagName}${[...activeElement.classList]
.map(cls => `.${cls}`)
.join("")}`,
"input.styleinspector-propertyeditor",
"The new property name field was focused"
);
});
function waitForStyleAttributeMutation(view, expectedAttributeValue) {
return new Promise(r => {
view.inspector.walker.on(
"mutations",
function onWalkerMutations(mutations) {
// Wait until we receive a mutation which updates the style attribute
// with the expected value.
const receivedLastMutation = mutations.find(
mut =>
mut.attributeName === "style" &&
mut.newValue.includes(expectedAttributeValue)
);
if (receivedLastMutation) {
view.inspector.walker.off("mutations", onWalkerMutations);
r();
}
}
);
});
}
function getCurrentInplaceEditor(view) {
return inplaceEditor(view.styleDocument.activeElement);
}