commit d277e7f5b98c953c914c13a522ea767a7ceda2c4 parent 2a5b565c7ea65bfa248b96d48e48d283aadfb144 Author: Cosmin Sabou <csabou@mozilla.com> Date: Tue, 4 Nov 2025 09:46:24 +0200 Revert "Bug 1635774 part 4: Ensure that DevTools is initialized when about:keyboard is opened. r=devtools-reviewers,mossop,ochameau" for causing failures on browser_CustomKeys.js This reverts commit 42a0e13bf50e7874b86bb6942a176c8f59aef447. Revert "Bug 1635774 part 3: Add about:keyboard UI to customise keyboard shortcuts. r=fluent-reviewers,mossop,bolsson,toolkit-telemetry-reviewers,frontend-codestyle-reviewers" This reverts commit 5eb494e0efb4382b9f53d1d2b2437a8785711022. Revert "Bug 1635774 part 2: Functionality to support customisation of keyboard shortcuts. r=mossop" This reverts commit 3e3e1240b3211f43c916947b5c8838ea183b8aa0. Revert "Bug 1635774 part 1: Add ids to some <key> elements that previously didn't have them. r=Gijs" This reverts commit cf3d3685c671b7975061ee051b6cf37053ace852. Diffstat:
26 files changed, 2 insertions(+), 2085 deletions(-)
diff --git a/browser/base/content/browser-init.js b/browser/base/content/browser-init.js @@ -3,10 +3,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -const { CustomKeys } = ChromeUtils.importESModule( - "resource:///modules/CustomKeys.sys.mjs" -); - let _resolveDelayedStartup; var delayedStartupPromise = new Promise(resolve => { _resolveDelayedStartup = resolve; @@ -272,7 +268,6 @@ var gBrowserInit = { if (gToolbarKeyNavEnabled) { ToolbarKeyboardNavigator.init(); } - CustomKeys.initWindow(window); // Update UI if browser is under remote control. gRemoteControl.updateVisualCue(); @@ -1088,7 +1083,6 @@ var gBrowserInit = { if (gToolbarKeyNavEnabled) { ToolbarKeyboardNavigator.uninit(); } - CustomKeys.uninitWindow(window); // Bug 1952900 to allow switching to unload category without leaking ChromeUtils.importESModule( diff --git a/browser/base/content/browser-sets.inc b/browser/base/content/browser-sets.inc @@ -214,10 +214,10 @@ <key id="goForwardKb2" data-l10n-id="nav-fwd-shortcut-alt" command="Browser:Forward" modifiers="accel"/> #endif <key id="goHome" keycode="VK_HOME" modifiers="alt"/> - <key id="key_reload2" keycode="VK_F5" command="Browser:Reload"/> + <key keycode="VK_F5" command="Browser:Reload"/> #ifndef XP_MACOSX <key id="showAllHistoryKb" data-l10n-id="history-show-all-shortcut" command="Browser:ShowAllHistory" modifiers="accel,shift"/> - <key id="key_reload_skip_cache2" keycode="VK_F5" command="Browser:ReloadSkipCache" modifiers="accel"/> + <key keycode="VK_F5" command="Browser:ReloadSkipCache" modifiers="accel"/> <key id="key_enterFullScreen" keycode="VK_F11" command="View:FullScreen"/> <key id="key_exitFullScreen" keycode="VK_F11" command="View:FullScreen" reserved="true" disabled="true"/> #else diff --git a/browser/components/DesktopActorRegistry.sys.mjs b/browser/components/DesktopActorRegistry.sys.mjs @@ -343,20 +343,6 @@ let JSWINDOWACTORS = { allFrames: true, }, - CustomKeys: { - parent: { - esModuleURI: "resource:///actors/CustomKeysParent.sys.mjs", - }, - child: { - esModuleURI: "resource:///actors/CustomKeysChild.sys.mjs", - events: { - DOMDocElementInserted: { wantUntrusted: true }, - }, - }, - matches: ["about:keyboard"], - remoteTypes: ["privilegedabout"], - }, - DecoderDoctor: { parent: { esModuleURI: "resource:///actors/DecoderDoctorParent.sys.mjs", diff --git a/browser/components/about/AboutRedirector.cpp b/browser/components/about/AboutRedirector.cpp @@ -153,11 +153,6 @@ static const RedirEntry kRedirMap[] = { nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS | nsIAboutModule::HIDE_FROM_ABOUTABOUT}, #endif - {"keyboard", "chrome://browser/content/customkeys/customkeys.html", - nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT | - nsIAboutModule::IS_SECURE_CHROME_UI | nsIAboutModule::ALLOW_SCRIPT | - nsIAboutModule::URI_MUST_LOAD_IN_CHILD | - nsIAboutModule::URI_CAN_LOAD_IN_PRIVILEGEDABOUT_PROCESS}, }; static nsAutoCString GetAboutModuleName(nsIURI* aURI) { diff --git a/browser/components/about/components.conf b/browser/components/about/components.conf @@ -10,7 +10,6 @@ pages = [ 'certerror', 'downloads', 'framecrashed', - 'keyboard', 'logins', 'loginsimportreport', 'firefoxview', diff --git a/browser/components/customkeys/CustomKeys.sys.mjs b/browser/components/customkeys/CustomKeys.sys.mjs @@ -1,273 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import { JSONFile } from "resource://gre/modules/JSONFile.sys.mjs"; - -// All the attributes for a <key> element that might specify the key to press. -// We include data-l10n-id because keys are often specified using Fluent. Note -// that we deliberately remove data-l10n-id when a key is customized because -// otherwise, Fluent would overwrite the customization. -const ATTRS = ["data-l10n-id", "key", "keycode", "modifiers"]; - -// The original keys for any shortcuts that have been customized. This is used -// to identify whether a shortcut has been customized, as well as to reset a -// shortcut. -const original = {}; -// All the browser windows we are handling. Maps from a window to our -// MutationObserver for that window. -const windows = new Map(); - -// The configuration is an Object with the form: -// { "someKeyId": { -// modifiers: "mod1,mod2", -// key: "someKey", -// keycode: "someKeycode" -// } } -// Only one of `key` or `keycode` should be specified for each entry. An entry -// may be empty to indicate that the default key has been cleared; i.e. -// { "someKeyId": {} } -const config = new JSONFile({ - path: PathUtils.join(PathUtils.profileDir, "customKeys.json"), -}); - -function applyToKeyEl(window, keyId, keysets) { - const keyEl = window.document.getElementById(keyId); - if (!keyEl || keyEl.tagName != "key") { - return; - } - if (!original[keyId]) { - // Save the original key in case the user wants to reset. - const orig = (original[keyId] = {}); - for (const attr of ATTRS) { - const val = keyEl.getAttribute(attr); - if (val) { - orig[attr] = val; - } - } - } - for (const attr of ATTRS) { - keyEl.removeAttribute(attr); - } - const data = config.data[keyId]; - for (const attr of ["modifiers", "key", "keycode"]) { - const val = data[attr]; - if (val) { - keyEl.setAttribute(attr, val); - } - } - keysets.add(keyEl.parentElement); -} - -function resetKeyEl(window, keyId, keysets) { - const keyEl = window.document.getElementById(keyId); - if (!keyEl) { - return; - } - const orig = original[keyId]; - for (const attr of ATTRS) { - keyEl.removeAttribute(attr); - const val = orig[attr]; - if (val !== undefined) { - keyEl.setAttribute(attr, val); - } - } - keysets.add(keyEl.parentElement); -} - -async function applyToNewWindow(window) { - await config.load(); - const keysets = new Set(); - for (const keyId in config.data) { - applyToKeyEl(window, keyId, keysets); - } - refreshKeysets(window, keysets); - observe(window); -} - -function refreshKeysets(window, keysets) { - if (keysets.size == 0) { - return; - } - const observer = windows.get(window); - if (observer) { - // We don't want our MutationObserver to process this. - observer.disconnect(); - } - // Gecko doesn't watch for changes to key elements. It only sets up a key - // element when its keyset is bound to the tree. See: - // https://searchfox.org/mozilla-central/rev/7857ea04d142f2abc0d777085f9e54526c7cedf9/dom/xul/nsXULElement.cpp#587 - // Therefore, remove and re-add any modified keysets to apply the changes. - for (const keyset of keysets) { - const parent = keyset.parentElement; - keyset.remove(); - parent.append(keyset); - } - if (observer) { - observe(window); - } -} - -function observe(window) { - let observer = windows.get(window); - if (!observer) { - // A keyset can be added dynamically. For example, DevTools does this during - // delayed startup. Note that key elements cannot be added dynamically. We - // know this because Gecko doesn't watch for changes to key elements. It - // only sets up a key element when its keyset is bound to the tree. See: - // https://searchfox.org/mozilla-central/rev/7857ea04d142f2abc0d777085f9e54526c7cedf9/dom/xul/nsXULElement.cpp#587 - observer = new window.MutationObserver(mutations => { - const keysets = new Set(); - for (const mutation of mutations) { - for (const node of mutation.addedNodes) { - if (node.tagName != "keyset") { - continue; - } - for (const key of node.children) { - if (key.tagName != "key" || !config.data[key.id]) { - continue; - } - applyToKeyEl(window, key.id, keysets); - } - } - } - refreshKeysets(window, keysets); - }); - windows.set(window, observer); - } - for (const node of [window.document, window.document.body]) { - observer.observe(node, { childList: true }); - } - return observer; -} - -export const CustomKeys = { - /** - * Customize a keyboard shortcut. - * - * @param {string} id The id of the key element. - * @param {object} data - * @param {string} data.modifiers The modifiers for the key; e.g. "accel,shift". - * @param {string} data.key The key itself; e.g. "Y". Mutually exclusive with data.keycode. - * @param {string} data.keycode The key code; e.g. VK_F1. Mutually exclusive with data.key. - */ - changeKey(id, { modifiers, key, keycode }) { - const existing = config.data[id]; - if ( - existing && - modifiers == existing.modifiers && - key == existing.key && - keycode == existing.keycode - ) { - return; // No change. - } - const defaultKey = this.getDefaultKey(id); - if ( - defaultKey && - modifiers == defaultKey.modifiers && - key == defaultKey.key && - keycode == defaultKey.keycode - ) { - this.resetKey(id); - return; - } - const data = (config.data[id] = {}); - if (modifiers) { - data.modifiers = modifiers; - } - if (key) { - data.key = key; - } - if (keycode) { - data.keycode = keycode; - } - for (const window of windows.keys()) { - const keysets = new Set(); - applyToKeyEl(window, id, keysets); - refreshKeysets(window, keysets); - } - config.saveSoon(); - }, - - /** - * Reset a keyboard shortcut to its defalt. - * - * @param {string} id The id of the key element. - */ - resetKey(id) { - if (!config.data[id]) { - return; // No change. - } - delete config.data[id]; - for (const window of windows.keys()) { - const keysets = new Set(); - resetKeyEl(window, id, keysets); - refreshKeysets(window, keysets); - } - delete original[id]; - config.saveSoon(); - }, - - /** - * Clear a keyboard shortcut; i.e. so it does nothing. - * - * @param {string} id The id of the key element. - */ - clearKey(id) { - this.changeKey(id, {}); - }, - - /** - * Reset all keyboard shortcuts to their defaults. - */ - resetAll() { - config.data = {}; - for (const window of windows.keys()) { - const keysets = new Set(); - for (const id in original) { - resetKeyEl(window, id, keysets); - } - refreshKeysets(window, keysets); - } - for (const id of Object.keys(original)) { - delete original[id]; - } - config.saveSoon(); - }, - - initWindow(window) { - applyToNewWindow(window); - }, - - uninitWindow(window) { - windows.get(window).disconnect(); - windows.delete(window); - }, - - /** - * Return the default key for this shortcut in the form: - * { modifiers: "mod1,mod2", key: "someKey", keycode: "someKeycode" } - * If this shortcut isn't customized, return null. - * - * @param {string} keyId The id of the key element. - */ - getDefaultKey(keyId) { - const origKey = original[keyId]; - if (!origKey) { - return null; - } - const data = {}; - if (origKey.modifiers) { - data.modifiers = origKey.modifiers; - } - if (origKey.key) { - data.key = origKey.key; - } - if (origKey.keycode) { - data.keycode = origKey.keycode; - } - return data; - }, -}; - -Object.freeze(CustomKeys); diff --git a/browser/components/customkeys/CustomKeysChild.sys.mjs b/browser/components/customkeys/CustomKeysChild.sys.mjs @@ -1,10 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import { RemotePageChild } from "resource://gre/actors/RemotePageChild.sys.mjs"; - -/** - * Empty child actor as most operations are handled by the parent. - */ -export class CustomKeysChild extends RemotePageChild {} diff --git a/browser/components/customkeys/CustomKeysParent.sys.mjs b/browser/components/customkeys/CustomKeysParent.sys.mjs @@ -1,194 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; -import { CustomKeys } from "resource:///modules/CustomKeys.sys.mjs"; -import { ShortcutUtils } from "resource://gre/modules/ShortcutUtils.sys.mjs"; - -/** - * Actor implementation for about:keyboard. - */ -export class CustomKeysParent extends JSWindowActorParent { - getKeyData(id) { - const keyEl = - this.browsingContext.topChromeWindow.document.getElementById(id); - if (!keyEl) { - return null; - } - return { - shortcut: ShortcutUtils.prettifyShortcut(keyEl), - isCustomized: !!CustomKeys.getDefaultKey(id), - }; - } - - // Get keyboard shortcuts in the form: - // { <categoryTitle>: { <keyId>: { title: <title>, shortcut: <String>, isCustomized: <Boolean> } } - // If categoryTitle or keyTitle begins with "customkeys-", it is a Fluent id. - getKeys() { - // Make Dev Tools populate the Browser Tools menu so we can gather those - // shortcuts here. - const topWin = this.browsingContext.topChromeWindow; - Services.obs.notifyObservers(topWin, "customkeys-ui-showing"); - - const add = (category, id, title) => { - const data = this.getKeyData(id); - if (data) { - data.title = title; - category[id] = data; - } - }; - - const keys = {}; - // Gather as many keys as we can from the menu bar menus. - const mainMenuBar = topWin.document.getElementById("main-menubar"); - for (const item of mainMenuBar.querySelectorAll("menuitem[key]")) { - const menu = item.closest("menu"); - if (menu.id == "historyUndoMenu" || menu.id == "historyUndoWindowMenu") { - // The reopen last tab/window commands have the label of the actual - // tab/window they will reopen. We handle those specially later. - continue; - } - if (!keys[menu.label]) { - keys[menu.label] = {}; - } - add(keys[menu.label], item.getAttribute("key"), item.label); - } - - // Add some shortcuts that aren't available in menus. - const historyCat = topWin.document.getElementById("history-menu").label; - let cat = keys[historyCat]; - add( - cat, - "key_restoreLastClosedTabOrWindowOrSession", - "customkeys-history-reopen-tab" - ); - add(cat, "key_undoCloseWindow", "customkeys-history-reopen-window"); - const toolsCat = topWin.document.getElementById("browserToolsMenu").label; - cat = keys[toolsCat]; - add(cat, "key_toggleToolboxF12", "customkeys-dev-tools"); - add(cat, "key_inspector", "customkeys-dev-inspector"); - add(cat, "key_webconsole", "customkeys-dev-webconsole"); - add(cat, "key_jsdebugger", "customkeys-dev-debugger"); - add(cat, "key_netmonitor", "customkeys-dev-network"); - add(cat, "key_styleeditor", "customkeys-dev-style"); - add(cat, "key_performance", "customkeys-dev-performance"); - add(cat, "key_storage", "customkeys-dev-storage"); - add(cat, "key_dom", "customkeys-dev-dom"); - add(cat, "key_accessibility", "customkeys-dev-accessibility"); - add(cat, "key_profilerStartStop", "customkeys-dev-profiler-toggle"); - add( - cat, - "key_profilerStartStopAlternate", - "customkeys-dev-profiler-toggle" - ); - add(cat, "key_profilerCapture", "customkeys-dev-profiler-capture"); - add(cat, "key_profilerCaptureAlternate", "customkeys-dev-profiler-capture"); - cat = keys["customkeys-category-navigation"] = {}; - add(cat, "goBackKb", "customkeys-nav-back"); - add(cat, "goForwardKb", "customkeys-nav-forward"); - add(cat, "goHome", "customkeys-nav-home"); - add(cat, "key_reload", "customkeys-nav-reload"); - add(cat, "key_reload2", "customkeys-nav-reload"); - add(cat, "key_reload_skip_cache", "customkeys-nav-reload-skip-cache"); - add(cat, "key_reload_skip_cache2", "customkeys-nav-reload-skip-cache"); - add(cat, "key_stop", "customkeys-nav-stop"); - - return keys; - } - - prettifyShortcut({ modifiers, key, keycode }) { - // ShortcutUtils.prettifyShortcut needs a key element, but we don't have - // that here. It simply concatenates the prettified modifiers and key - // anyway. - const prettyMods = ShortcutUtils.getModifierString(modifiers); - const prettyKey = ShortcutUtils.getKeyString(keycode, key); - return prettyMods + prettyKey; - } - - async receiveMessage(message) { - switch (message.name) { - case "CustomKeys:CaptureKey": { - if (message.data) { - this.browsingContext.embedderElement.addEventListener( - "keydown", - this - ); - } else { - this.browsingContext.embedderElement.removeEventListener( - "keydown", - this - ); - } - return null; - } - case "CustomKeys:ChangeKey": { - CustomKeys.changeKey(message.data.id, message.data); - return this.getKeyData(message.data.id); - } - case "CustomKeys:ClearKey": { - const id = message.data; - CustomKeys.clearKey(id); - return this.getKeyData(id); - } - case "CustomKeys:GetDefaultKey": { - const data = { id: message.data }; - Object.assign(data, CustomKeys.getDefaultKey(data.id)); - data.shortcut = this.prettifyShortcut(data); - return data; - } - case "CustomKeys:GetKeys": { - return this.getKeys(); - } - case "CustomKeys:ResetAll": { - return CustomKeys.resetAll(); - } - case "CustomKeys:ResetKey": { - CustomKeys.resetKey(message.data); - return this.getKeyData(message.data); - } - } - return null; - } - - handleEvent(event) { - if (event.type == "keydown") { - event.preventDefault(); - event.stopPropagation(); - const data = {}; - let modifiers = []; - const isMac = AppConstants.platform === "macosx"; - if (event.altKey) { - modifiers.push("Alt"); - } - if (event.ctrlKey) { - modifiers.push(isMac ? "MacCtrl" : "Ctrl"); - } - if (isMac && event.metaKey) { - modifiers.push("Command"); - } - if (event.shiftKey) { - modifiers.push("Shift"); - } - data.modifiers = ShortcutUtils.getModifiersAttribute(modifiers); - if ( - event.key == "Alt" || - event.key == "Control" || - event.key == "Meta" || - event.key == "Shift" - ) { - data.isModifier = true; - data.modifierString = ShortcutUtils.getModifierString(data.modifiers); - this.sendAsyncMessage("CustomKeys:CapturedKey", data); - return; - } - if (event.key.length == 1) { - data.key = event.key.toUpperCase(); - } else { - data.keycode = ShortcutUtils.getKeycodeAttribute(event.key); - } - data.shortcut = this.prettifyShortcut(data); - this.sendAsyncMessage("CustomKeys:CapturedKey", data); - } - } -} diff --git a/browser/components/customkeys/content/customkeys.css b/browser/components/customkeys/content/customkeys.css @@ -1,34 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -th { - text-align: start; -} - -.clear { - display: none; -} -.assigned .clear { - display: revert; -} - -.reset { - display: none; -} -.customized .reset { - display: revert; -} - -.newLabel { - display: none; -} - -.editing { - .change { - display: none; - } - .newLabel { - display: revert; - } -} diff --git a/browser/components/customkeys/content/customkeys.html b/browser/components/customkeys/content/customkeys.html @@ -1,35 +0,0 @@ -<!-- This Source Code Form is subject to the terms of the Mozilla Public - - License, v. 2.0. If a copy of the MPL was not distributed with this file, - - You can obtain one at http://mozilla.org/MPL/2.0/. --> - -<!doctype html> -<html> - <head> - <meta - http-equiv="Content-Security-Policy" - content="default-src chrome:; object-src 'none'" - /> - <meta charset="utf-8" /> - <link rel="localization" href="browser/customkeys.ftl" /> - <link - rel="stylesheet" - href="chrome://browser/content/customkeys/customkeys.css" - /> - <link rel="stylesheet" href="chrome://global/skin/in-content/common.css" /> - <title data-l10n-id="customkeys-title"></title> - </head> - <body> - <p> - <em id="cautionMessage" data-l10n-id="customkeys-caution-message"></em> - </p> - <div> - <label for="search" data-l10n-id="customkeys-search"></label> - <input id="search" type="search" /> - </div> - <table id="table"></table> - <div> - <button id="resetAll" data-l10n-id="customkeys-reset-all"></button> - </div> - <script src="chrome://browser/content/customkeys/customkeys.js"></script> - </body> -</html> diff --git a/browser/components/customkeys/content/customkeys.js b/browser/components/customkeys/content/customkeys.js @@ -1,222 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this file, - * You can obtain one at http://mozilla.org/MPL/2.0/. */ - -const table = document.getElementById("table"); - -function setTextContent(element, content) { - if (content.startsWith("customkeys-")) { - element.setAttribute("data-l10n-id", content); - } else { - element.textContent = content; - } -} - -function notifyUpdate() { - window.dispatchEvent(new CustomEvent("CustomKeysUpdate")); -} - -async function buildTable() { - const keys = await RPMSendQuery("CustomKeys:GetKeys"); - for (const category in keys) { - const tbody = document.createElement("tbody"); - table.append(tbody); - let row = document.createElement("tr"); - row.className = "category"; - tbody.append(row); - let cell = document.createElement("td"); - row.append(cell); - cell.setAttribute("colspan", 5); - const heading = document.createElement("h1"); - setTextContent(heading, category); - cell.append(heading); - const categoryKeys = keys[category]; - for (const keyId in categoryKeys) { - row = document.createElement("tr"); - row.className = "key"; - tbody.append(row); - row.setAttribute("data-id", keyId); - cell = document.createElement("th"); - const key = categoryKeys[keyId]; - setTextContent(cell, key.title); - row.append(cell); - cell = document.createElement("td"); - cell.textContent = key.shortcut; - row.append(cell); - cell = document.createElement("td"); - let button = document.createElement("button"); - button.className = "change"; - button.setAttribute("data-l10n-id", "customkeys-change"); - cell.append(button); - let label = document.createElement("label"); - label.className = "newLabel"; - let span = document.createElement("span"); - span.setAttribute("data-l10n-id", "customkeys-new-key"); - label.append(span); - let input = document.createElement("input"); - input.className = "new"; - label.append(input); - cell.append(label); - row.append(cell); - cell = document.createElement("td"); - button = document.createElement("button"); - button.className = "clear"; - button.setAttribute("data-l10n-id", "customkeys-clear"); - cell.append(button); - row.append(cell); - cell = document.createElement("td"); - button = document.createElement("button"); - button.className = "reset"; - button.setAttribute("data-l10n-id", "customkeys-reset"); - cell.append(button); - row.append(cell); - updateKey(row, key); - } - } - notifyUpdate(); -} - -function updateKey(row, data) { - row.children[1].textContent = data.shortcut; - row.classList.toggle("customized", data.isCustomized); - row.classList.toggle("assigned", !!data.shortcut); -} - -// Returns false if the assignment should be cancelled. -async function maybeHandleConflict(data) { - for (const row of table.querySelectorAll(".key")) { - if (data.shortcut != row.children[1].textContent) { - continue; // Not a conflict. - } - const conflictId = row.dataset.id; - if (conflictId == data.id) { - // We're trying to assign this key to the shortcut it is already - // assigned to. We don't need to do anything. - return false; - } - const conflictDesc = row.children[0].textContent; - if ( - window.confirm( - await document.l10n.formatValue("customkeys-conflict-confirm", { - conflict: conflictDesc, - }) - ) - ) { - // Clear the conflicting key. - const newData = await RPMSendQuery("CustomKeys:ClearKey", conflictId); - updateKey(row, newData); - return true; - } - return false; - } - return true; -} - -async function onAction(event) { - const row = event.target.closest("tr"); - const keyId = row.dataset.id; - if (event.target.className == "reset") { - Glean.browserCustomkeys.actions.reset.add(); - const data = await RPMSendQuery("CustomKeys:GetDefaultKey", keyId); - if (await maybeHandleConflict(data)) { - const newData = await RPMSendQuery("CustomKeys:ResetKey", keyId); - updateKey(row, newData); - notifyUpdate(); - } - } else if (event.target.className == "change") { - Glean.browserCustomkeys.actions.change.add(); - // The "editing" class will cause the Change button to be replaced by a - // labelled input for the new key. - row.classList.add("editing"); - // We need to listen for keys in the parent process because we want to - // intercept reserved keys, which we can't do in the content process. - RPMSendAsyncMessage("CustomKeys:CaptureKey", true); - row.querySelector(".new").focus(); - } else if (event.target.className == "clear") { - Glean.browserCustomkeys.actions.clear.add(); - const newData = await RPMSendQuery("CustomKeys:ClearKey", keyId); - updateKey(row, newData); - notifyUpdate(); - } -} - -async function onKey({ data }) { - const input = document.activeElement; - const row = input.closest("tr"); - data.id = row.dataset.id; - if (data.isModifier) { - // This is a modifier. Display it, but don't assign yet. We assign when the - // main key is pressed (below). - input.value = data.modifierString; - // Select the input's text so screen readers will report it. - input.select(); - return; - } - if (await maybeHandleConflict(data)) { - const newData = await RPMSendQuery("CustomKeys:ChangeKey", data); - updateKey(row, newData); - } - RPMSendAsyncMessage("CustomKeys:CaptureKey", false); - row.classList.remove("editing"); - row.querySelector(".change").focus(); - notifyUpdate(); -} - -function onFocusLost(event) { - if (event.target.className == "new") { - // If the input loses focus, cancel editing of the key. - RPMSendAsyncMessage("CustomKeys:CaptureKey", false); - const row = event.target.closest("tr"); - row.classList.remove("editing"); - // Clear any modifiers that were displayed, ready for the next edit. - event.target.value = ""; - } -} - -function onSearchInput(event) { - const query = event.target.value.toLowerCase(); - for (const row of table.querySelectorAll(".key")) { - row.hidden = - query && !row.children[0].textContent.toLowerCase().includes(query); - } - for (const tbody of table.tBodies) { - // Show a category only if it has at least 1 shown key. - tbody.hidden = !tbody.querySelector(".key:not([hidden])"); - } - notifyUpdate(); -} - -async function onResetAll() { - Glean.browserCustomkeys.actions.reset_all.add(); - if ( - !window.confirm( - await document.l10n.formatValue("customkeys-reset-all-confirm") - ) - ) { - return; - } - await RPMSendQuery("CustomKeys:ResetAll"); - const keysByCat = await RPMSendQuery("CustomKeys:GetKeys"); - const keysById = {}; - for (const category in keysByCat) { - const categoryKeys = keysByCat[category]; - for (const keyId in categoryKeys) { - keysById[keyId] = categoryKeys[keyId]; - } - } - for (const row of table.querySelectorAll(".key")) { - const data = keysById[row.dataset.id]; - if (data) { - updateKey(row, data); - } - } - notifyUpdate(); -} - -buildTable(); -table.addEventListener("click", onAction); -RPMAddMessageListener("CustomKeys:CapturedKey", onKey); -table.addEventListener("focusout", onFocusLost); -document.getElementById("search").addEventListener("input", onSearchInput); -document.getElementById("resetAll").addEventListener("click", onResetAll); -Glean.browserCustomkeys.opened.add(); diff --git a/browser/components/customkeys/content/jar.mn b/browser/components/customkeys/content/jar.mn @@ -1,8 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -browser.jar: - content/browser/customkeys/customkeys.css - content/browser/customkeys/customkeys.html - content/browser/customkeys/customkeys.js diff --git a/browser/components/customkeys/content/moz.build b/browser/components/customkeys/content/moz.build @@ -1,7 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -JAR_MANIFESTS += ["jar.mn"] diff --git a/browser/components/customkeys/metrics.yaml b/browser/components/customkeys/metrics.yaml @@ -1,49 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Adding a new metric? We have docs for that! -# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html - ---- -$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0 -$tags: - - 'Firefox :: Keyboard Navigation' - -browser.customkeys: - actions: - type: labeled_counter - description: > - The actions taken in about:keyboard. - labels: - - change - - clear - - reset - - reset_all - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1635774 - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1635774 - data_sensitivity: - - interaction - notification_emails: - - accessibility@mozilla.com - - jteh@mozilla.com - - kbryant@mozilla.com - expires: 154 - - opened: - type: counter - description: > - Number of times about:keyboard has been opened. - bugs: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1635774 - data_reviews: - - https://bugzilla.mozilla.org/show_bug.cgi?id=1635774 - data_sensitivity: - - interaction - notification_emails: - - accessibility@mozilla.com - - jteh@mozilla.com - - kbryant@mozilla.com - expires: 154 diff --git a/browser/components/customkeys/moz.build b/browser/components/customkeys/moz.build @@ -1,23 +0,0 @@ -# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -DIRS += [ - "content", -] - -EXTRA_JS_MODULES += [ - "CustomKeys.sys.mjs", -] - -FINAL_TARGET_FILES.actors += [ - "CustomKeysChild.sys.mjs", - "CustomKeysParent.sys.mjs", -] - -BROWSER_CHROME_MANIFESTS += ["tests/browser/browser.toml"] - -with Files("**"): - BUG_COMPONENT = ("Firefox", "Keyboard Navigation") diff --git a/browser/components/customkeys/tests/browser/browser.toml b/browser/components/customkeys/tests/browser/browser.toml @@ -1,6 +0,0 @@ -[DEFAULT] -support-files = ["head.js"] - -["browser_CustomKeys.js"] - -["browser_aboutKeyboard.js"] diff --git a/browser/components/customkeys/tests/browser/browser_CustomKeys.js b/browser/components/customkeys/tests/browser/browser_CustomKeys.js @@ -1,366 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -/** - * Test the behavior of assigning keys, resetting keys, etc. via the CustomKeys module. The UI is tested separately. - */ - -registerCleanupFunction(() => { - CustomKeys.resetAll(); -}); - -// Test changing a key. -add_task(async function testChangeKey() { - is( - document.activeElement, - gBrowser.selectedBrowser, - "Tab document browser is focused" - ); - - is( - CustomKeys.getDefaultKey("key_gotoHistory"), - null, - "key_gotoHistory is not customized" - ); - info(`Changing key_gotoHistory to ${consts.unusedDisplay}`); - CustomKeys.changeKey("key_gotoHistory", { - modifiers: consts.unusedModifiers, - key: consts.unusedKey, - }); - Assert.deepEqual( - CustomKeys.getDefaultKey("key_gotoHistory"), - { modifiers: consts.historyModifiers, key: "H" }, - "key_gotoHistory is customized" - ); - info(`Pressing ${consts.unusedDisplay}`); - let focused = BrowserTestUtils.waitForEvent(window, "SidebarFocused"); - EventUtils.synthesizeKey(consts.unusedKey, consts.unusedOptions, window); - await focused; - ok(true, "Sidebar got focus"); - is( - SidebarController.currentID, - "viewHistorySidebar", - "History sidebar is open" - ); - info(`Pressing ${consts.unusedDisplay}`); - focused = BrowserTestUtils.waitForEvent(gBrowser.selectedBrowser, "focus"); - EventUtils.synthesizeKey(consts.unusedKey, consts.unusedOptions, window); - await focused; - ok(true, "Tab document browser got focus"); - - is( - CustomKeys.getDefaultKey("viewBookmarksSidebarKb"), - null, - "viewBookmarksSidebarKb is not customized" - ); - info(`Changing viewBookmarksSidebarKb to ${consts.historyDisplay}`); - CustomKeys.changeKey("viewBookmarksSidebarKb", { - modifiers: consts.historyModifiers, - key: "H", - }); - Assert.deepEqual( - CustomKeys.getDefaultKey("viewBookmarksSidebarKb"), - { modifiers: "accel", key: "B" }, - "viewBookmarksSidebarKb is customized" - ); - info(`Pressing ${consts.historyDisplay}`); - focused = BrowserTestUtils.waitForEvent(window, "SidebarFocused"); - EventUtils.synthesizeKey("H", consts.historyOptions, window); - await focused; - ok(true, "Sidebar got focus"); - is( - SidebarController.currentID, - "viewBookmarksSidebar", - "Bookmarks sidebar is open" - ); - info(`Pressing ${consts.historyDisplay}`); - focused = BrowserTestUtils.waitForEvent(gBrowser.selectedBrowser, "focus"); - EventUtils.synthesizeKey("H", consts.historyOptions, window); - await focused; - ok(true, "Tab document browser got focus"); - - info("Resetting all keys"); - CustomKeys.resetAll(); -}); - -// Test clearing a key. -add_task(async function testClearKey() { - is( - document.activeElement, - gBrowser.selectedBrowser, - "Tab document browser is focused" - ); - // Move focus into chrome so that accel+l to focus the URL bar reliably occurs - // immediately. We need this guarantee because we want to test when pressing - // accel+l does nothing, so we can't rely on an event for that test. - info("Focusing selected tab"); - let focused = BrowserTestUtils.waitForEvent(gBrowser.selectedTab, "focus"); - gBrowser.selectedTab.focus(); - await focused; - ok(true, "Selected tab got focus"); - - is( - CustomKeys.getDefaultKey("focusURLBar"), - null, - "focusURLBar is not customized" - ); - info("Pressing accel+L"); - EventUtils.synthesizeKey("L", { accelKey: true }, window); - is(document.activeElement, gURLBar.inputField, "URL bar is focused"); - info("Focusing selected tab"); - focused = BrowserTestUtils.waitForEvent(gBrowser.selectedTab, "focus"); - gBrowser.selectedTab.focus(); - await focused; - ok(true, "Selected tab got focus"); - - info("Clearing focusURLBar"); - CustomKeys.clearKey("focusURLBar"); - Assert.deepEqual( - CustomKeys.getDefaultKey("focusURLBar"), - { modifiers: "accel", key: "L" }, - "focusURLBar is customized" - ); - info("Pressing accel+L"); - EventUtils.synthesizeKey("L", { accelKey: true }, window); - is( - document.activeElement, - gBrowser.selectedTab, - "Selected tab still focused" - ); - - // The tab bar has focus now. We need to move focus back to the document - // because otherwise, the focus will remain on the tab bar for the next test. - info("Focusing tab document browser"); - focused = BrowserTestUtils.waitForEvent(gBrowser.selectedBrowser, "focus"); - gBrowser.selectedBrowser.focus(); - await focused; - ok(true, "Tab document browser got focus"); - info("Resetting all keys"); - CustomKeys.resetAll(); -}); - -// Test resetting a key. -add_task(async function testResetKey() { - is( - document.activeElement, - gBrowser.selectedBrowser, - "Tab document browser is focused" - ); - - is( - CustomKeys.getDefaultKey("focusURLBar"), - null, - "focusURLBar is not customized" - ); - info("Clearing focusURLBar"); - CustomKeys.clearKey("focusURLBar"); - Assert.deepEqual( - CustomKeys.getDefaultKey("focusURLBar"), - { modifiers: "accel", key: "L" }, - "focusURLBar is customized" - ); - - info("Resetting focusURLBar"); - CustomKeys.resetKey("focusURLBar"); - is( - CustomKeys.getDefaultKey("focusURLBar"), - null, - "focusURLBar is not customized" - ); - info("Pressing accel+L"); - let focused = BrowserTestUtils.waitForEvent(gURLBar.inputField, "focus"); - EventUtils.synthesizeKey("L", { accelKey: true }, window); - await focused; - ok(true, "URL bar got focus"); - info("Focusing tab document browser"); - focused = BrowserTestUtils.waitForEvent(gBrowser.selectedBrowser, "focus"); - gBrowser.selectedBrowser.focus(); - await focused; - ok(true, "Tab document browser got focus"); - - info("Resetting all keys"); - CustomKeys.resetAll(); -}); - -// Test resetting all keys. -add_task(async function testResetAll() { - is( - document.activeElement, - gBrowser.selectedBrowser, - "Tab document browser is focused" - ); - - is( - CustomKeys.getDefaultKey("key_gotoHistory"), - null, - "key_gotoHistory is not customized" - ); - info(`Changing key_gotoHistory to ${consts.unusedDisplay}`); - CustomKeys.changeKey("key_gotoHistory", { - modifiers: consts.unusedModifiers, - key: consts.unusedKey, - }); - Assert.deepEqual( - CustomKeys.getDefaultKey("key_gotoHistory"), - { modifiers: consts.historyModifiers, key: "H" }, - "key_gotoHistory is customized" - ); - - is( - CustomKeys.getDefaultKey("focusURLBar"), - null, - "focusURLBar is not customized" - ); - info("Clearing focusURLBar"); - CustomKeys.clearKey("focusURLBar"); - Assert.deepEqual( - CustomKeys.getDefaultKey("focusURLBar"), - { modifiers: "accel", key: "L" }, - "focusURLBar is customized" - ); - - info("Resetting all keys"); - CustomKeys.resetAll(); - - info(`Pressing ${consts.historyDisplay}`); - let focused = BrowserTestUtils.waitForEvent(window, "SidebarFocused"); - EventUtils.synthesizeKey("H", consts.historyOptions, window); - await focused; - ok(true, "Sidebar got focus"); - is( - SidebarController.currentID, - "viewHistorySidebar", - "History sidebar is open" - ); - info(`Pressing ${consts.historyDisplay}`); - focused = BrowserTestUtils.waitForEvent(gBrowser.selectedBrowser, "focus"); - EventUtils.synthesizeKey("H", consts.historyOptions, window); - await focused; - ok(true, "Tab document browser got focus"); - - info("Pressing accel+L"); - focused = BrowserTestUtils.waitForEvent(gURLBar.inputField, "focus"); - EventUtils.synthesizeKey("L", { accelKey: true }, window); - await focused; - ok(true, "URL bar got focus"); - info("Focusing tab document browser"); - focused = BrowserTestUtils.waitForEvent(gBrowser.selectedBrowser, "focus"); - gBrowser.selectedBrowser.focus(); - await focused; - ok(true, "Tab document browser got focus"); -}); - -// Test that changes apply to other windows. -add_task(async function testOtherWindow() { - // Test changing a key before a new window is opened. - is( - CustomKeys.getDefaultKey("key_gotoHistory"), - null, - "key_gotoHistory is not customized" - ); - info(`Changing key_gotoHistory to ${consts.unusedDisplay}`); - CustomKeys.changeKey("key_gotoHistory", { - modifiers: consts.unusedModifiers, - key: consts.unusedKey, - }); - Assert.deepEqual( - CustomKeys.getDefaultKey("key_gotoHistory"), - { modifiers: consts.historyModifiers, key: "H" }, - "key_gotoHistory is customized" - ); - - info("Opening new window"); - const newWin = await BrowserTestUtils.openNewBrowserWindow(); - is( - newWin.document.activeElement, - newWin.gURLBar.inputField, - "URL bar is focused in new window" - ); - - info(`Pressing ${consts.unusedDisplay} in new window`); - let focused = BrowserTestUtils.waitForEvent(newWin, "SidebarFocused"); - EventUtils.synthesizeKey(consts.unusedKey, consts.unusedOptions, newWin); - await focused; - ok(true, "Sidebar got focus in new window"); - is( - newWin.SidebarController.currentID, - "viewHistorySidebar", - "History sidebar is open in new window" - ); - info(`Pressing ${consts.unusedDisplay} in new window`); - focused = BrowserTestUtils.waitForEvent( - newWin.gBrowser.selectedBrowser, - "focus" - ); - EventUtils.synthesizeKey(consts.unusedKey, consts.unusedOptions, newWin); - await focused; - ok(true, "Tab document browser got focus in new window"); - - // Test changing a key after a new window is opened. - is( - CustomKeys.getDefaultKey("viewBookmarksSidebarKb"), - null, - "viewBookmarksSidebarKb is not customized" - ); - info(`Changing viewBookmarksSidebarKb to ${consts.historyDisplay}`); - CustomKeys.changeKey("viewBookmarksSidebarKb", { - modifiers: consts.historyModifiers, - key: "H", - }); - Assert.deepEqual( - CustomKeys.getDefaultKey("viewBookmarksSidebarKb"), - { modifiers: "accel", key: "B" }, - "viewBookmarksSidebarKb is customized" - ); - info(`Pressing ${consts.historyDisplay} in new window`); - focused = BrowserTestUtils.waitForEvent(newWin, "SidebarFocused"); - EventUtils.synthesizeKey("H", consts.historyOptions, newWin); - await focused; - ok(true, "Sidebar got focus in new window"); - is( - newWin.SidebarController.currentID, - "viewBookmarksSidebar", - "Bookmarks sidebar is open" - ); - info(`Pressing ${consts.historyDisplay} in new window`); - focused = BrowserTestUtils.waitForEvent( - newWin.gBrowser.selectedBrowser, - "focus" - ); - EventUtils.synthesizeKey("H", consts.historyOptions, newWin); - await focused; - ok(true, "Tab document browser got focus in new window"); - - // Test resetting keys after a new window is opened. - info("Resetting all keys"); - CustomKeys.resetAll(); - - is( - CustomKeys.getDefaultKey("key_gotoHistory"), - null, - "key_gotoHistory is not customized" - ); - info(`Pressing ${consts.historyDisplay} in new window`); - focused = BrowserTestUtils.waitForEvent(newWin, "SidebarFocused"); - EventUtils.synthesizeKey("H", consts.historyOptions, newWin); - await focused; - ok(true, "Sidebar got focus in new window"); - is( - newWin.SidebarController.currentID, - "viewHistorySidebar", - "History sidebar is open in new window" - ); - info(`Pressing ${consts.historyDisplay} in new window`); - focused = BrowserTestUtils.waitForEvent( - newWin.gBrowser.selectedBrowser, - "focus" - ); - EventUtils.synthesizeKey("H", consts.historyOptions, newWin); - await focused; - ok(true, "Tab document browser got focus in new window"); - - info("Closing new window"); - await BrowserTestUtils.closeWindow(newWin); -}); diff --git a/browser/components/customkeys/tests/browser/browser_aboutKeyboard.js b/browser/components/customkeys/tests/browser/browser_aboutKeyboard.js @@ -1,735 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -const { PromptTestUtils } = ChromeUtils.importESModule( - "resource://testing-common/PromptTestUtils.sys.mjs" -); - -/** - * Test the about:keyboard UI. - */ - -registerCleanupFunction(() => { - CustomKeys.resetAll(); -}); - -function addAboutKbTask(task) { - const wrapped = function () { - return BrowserTestUtils.withNewTab("about:keyboard", async tab => { - await SpecialPowers.spawn(tab, [], async () => { - if (!content.document.getElementById("table").firstElementChild) { - await ContentTaskUtils.waitForEvent( - content, - "CustomKeysUpdate", - false, - null, - true - ); - } - }); - await Services.fog.testFlushAllChildren(); - await task(tab); - }); - }; - // Propagate the name of the task function to our wrapper function so it shows up in test run output. - Object.defineProperty(wrapped, "name", { value: task.name }); - add_task(wrapped); -} - -// Check telemetry before about:keyboard is first opened. -add_task(function testBeforeFirstOpen() { - ok(!Glean.browserCustomkeys.opened.testGetValue(), "No telemetry for opened"); -}); - -// Test initial loading of about:keyboard. -addAboutKbTask(async function testInit(tab) { - is( - Glean.browserCustomkeys.opened.testGetValue(), - 1, - "Correct telemetry for opened" - ); - await SpecialPowers.spawn(tab, [], () => { - Assert.greater( - content.document.querySelectorAll("tbody").length, - 5, - "At least 5 categories" - ); - const numKeys = content.document.querySelectorAll(".key").length; - Assert.greater(numKeys, 50, "At least 50 keys"); - is( - content.document.querySelectorAll("tbody[hidden], tr[hidden]").length, - 0, - "No hidden categories or keys" - ); - is( - content.document.querySelectorAll(".customized").length, - 0, - "No shortcuts are customized" - ); - // Currently, we don't have any unassigned shortcuts. That will probably - // change in future, at which point this next assertion will need to be - // reconsidered. - is( - content.document.querySelectorAll(".assigned").length, - numKeys, - "All keys are assigned" - ); - is( - content.document.querySelectorAll(".editing").length, - 0, - "No keys are being edited" - ); - // Make sure that lazy DevTools items have been added. - ok( - content.document.querySelector('.key[data-id="key_browserConsole"]'), - "key_browserConsole is present" - ); - }); -}); - -// Test searching. -addAboutKbTask(async function testSearch(tab) { - is( - Glean.browserCustomkeys.opened.testGetValue(), - 2, - "Correct telemetry for opened" - ); - await SpecialPowers.spawn(tab, [], async () => { - is( - content.document.querySelectorAll("tbody[hidden], tr[hidden]").length, - 0, - "No hidden categories or keys" - ); - const search = content.document.getElementById("search"); - search.focus(); - - info("Searching for zzz"); - let updated = ContentTaskUtils.waitForEvent( - content, - "CustomKeysUpdate", - false, - null, - true - ); - EventUtils.sendString("zzz", content); - await updated; - is( - content.document.querySelectorAll( - "tbody:not([hidden]), .key:not([hidden])" - ).length, - 0, - "No visible categories or keys" - ); - - info("Clearing search"); - updated = ContentTaskUtils.waitForEvent( - content, - "CustomKeysUpdate", - false, - null, - true - ); - EventUtils.synthesizeKey("KEY_Escape", {}, content); - await updated; - is( - content.document.querySelectorAll("tbody[hidden], tr[hidden]").length, - 0, - "No hidden categories or keys" - ); - - info("Searching for download"); - updated = ContentTaskUtils.waitForEvent( - content, - "CustomKeysUpdate", - false, - null, - true - ); - EventUtils.sendString("download", content); - await updated; - let visibleKeys = content.document.querySelectorAll(".key:not([hidden])"); - is(visibleKeys.length, 1, "1 visible key"); - is( - visibleKeys[0].dataset.id, - "key_openDownloads", - "Visible key is key_openDownloads" - ); - let visibleCategories = content.document.querySelectorAll( - "tbody:not([hidden])" - ); - is(visibleCategories.length, 1, "1 visible category"); - is( - visibleKeys[0].closest("tbody"), - visibleCategories[0], - "Visible key is inside visible category" - ); - ok( - !visibleCategories[0].querySelector(".category").hidden, - "Category header is visible" - ); - - info("Clearing search"); - updated = ContentTaskUtils.waitForEvent( - content, - "CustomKeysUpdate", - false, - null, - true - ); - EventUtils.synthesizeKey("KEY_Escape", {}, content); - await updated; - is( - content.document.querySelectorAll("tbody[hidden], tr[hidden]").length, - 0, - "No hidden categories or keys" - ); - - info("Searching for history"); - updated = ContentTaskUtils.waitForEvent( - content, - "CustomKeysUpdate", - false, - null, - true - ); - EventUtils.sendString("history", content); - await updated; - // This gives us results from both the Sidebars and History categories. - visibleKeys = content.document.querySelectorAll(".key:not([hidden])"); - is(visibleKeys.length, 3, "3 visible keys"); - visibleCategories = content.document.querySelectorAll( - "tbody:not([hidden])" - ); - is(visibleCategories.length, 2, "2 visible categories"); - }); -}); - -// Test a simple change. -addAboutKbTask(async function testChange(tab) { - ok( - !Glean.browserCustomkeys.actions.change.testGetValue(), - "No telemetry for change action" - ); - await SpecialPowers.spawn(tab, [consts], async _consts => { - content.downloadsRow = content.document.querySelector( - '.key[data-id="key_openDownloads"]' - ); - ok( - !content.downloadsRow.classList.contains("customized"), - "key_openDownloads is not customized" - ); - is( - content.downloadsRow.children[1].textContent, - _consts.downloadsDisplay, - "Key is the default key" - ); - info("Clicking Change for key_openDownloads"); - content.input = content.downloadsRow.querySelector(".new"); - let focused = ContentTaskUtils.waitForEvent(content.input, "focus"); - content.change = content.downloadsRow.querySelector(".change"); - content.change.click(); - await focused; - ok(true, "New key input got focus"); - content.selected = ContentTaskUtils.waitForEvent(content.input, "select"); - }); - await Services.fog.testFlushAllChildren(); - is( - Glean.browserCustomkeys.actions.change.testGetValue(), - 1, - "Correct telemetry for change action" - ); - info(`Pressing ${consts.unusedModifiersDisplay}`); - EventUtils.synthesizeKey(...consts.unusedModifiersArgs, window); - await SpecialPowers.spawn(tab, [consts], async _consts => { - await content.selected; - is( - content.input.value, - _consts.unusedModifiersDisplay, - "Input shows modifiers as they're pressed" - ); - info(`Pressing ${_consts.unusedDisplay}`); - content.focused = ContentTaskUtils.waitForEvent(content.change, "focus"); - }); - EventUtils.synthesizeKey(consts.unusedKey, consts.unusedOptions, window); - await SpecialPowers.spawn(tab, [consts], async _consts => { - await content.focused; - ok(true, "Change button got focus"); - ok( - content.downloadsRow.classList.contains("customized"), - "key_openDownloads is customized" - ); - is( - content.downloadsRow.children[1].textContent, - _consts.unusedDisplay, - "Key is the customized key" - ); - }); - // We deliberately let the result of this test leak into the next one. -}); - -// Test resetting a key. This also tests that the change from the previous test -// is reflected when the page is reloaded. -addAboutKbTask(async function testReset(tab) { - ok( - !Glean.browserCustomkeys.actions.reset.testGetValue(), - "No telemetry for reset action" - ); - await SpecialPowers.spawn(tab, [consts], async _consts => { - const downloadsRow = content.document.querySelector( - '.key[data-id="key_openDownloads"]' - ); - ok( - downloadsRow.classList.contains("customized"), - "key_openDownloads is customized" - ); - is( - downloadsRow.children[1].textContent, - _consts.unusedDisplay, - "Key is the customized key" - ); - info("Clicking Reset for key_openDownloads"); - const reset = downloadsRow.querySelector(".reset"); - let updated = ContentTaskUtils.waitForEvent( - content, - "CustomKeysUpdate", - false, - null, - true - ); - reset.click(); - await updated; - ok( - !downloadsRow.classList.contains("customized"), - "key_openDownloads is not customized" - ); - is( - downloadsRow.children[1].textContent, - _consts.downloadsDisplay, - "Key is the default key" - ); - }); - await Services.fog.testFlushAllChildren(); - is( - Glean.browserCustomkeys.actions.reset.testGetValue(), - 1, - "Correct telemetry for reset action" - ); -}); - -// Test clearing a key. -addAboutKbTask(async function testClear(tab) { - ok( - !Glean.browserCustomkeys.actions.clear.testGetValue(), - "No telemetry for clear action" - ); - await SpecialPowers.spawn(tab, [consts], async _consts => { - const downloadsRow = content.document.querySelector( - '.key[data-id="key_openDownloads"]' - ); - ok( - !downloadsRow.classList.contains("customized"), - "key_openDownloads is not customized" - ); - ok( - downloadsRow.classList.contains("assigned"), - "key_openDownloads is assigned" - ); - info("Clicking Clear for key_openDownloads"); - const clear = downloadsRow.querySelector(".clear"); - let updated = ContentTaskUtils.waitForEvent( - content, - "CustomKeysUpdate", - false, - null, - true - ); - clear.click(); - await updated; - ok( - downloadsRow.classList.contains("customized"), - "key_openDownloads is customized" - ); - ok( - !downloadsRow.classList.contains("assigned"), - "key_openDownloads is not assigned" - ); - is(downloadsRow.children[1].textContent, "", "Key is empty"); - }); - await Services.fog.testFlushAllChildren(); - is( - Glean.browserCustomkeys.actions.clear.testGetValue(), - 1, - "Correct telemetry for clear action" - ); - // We deliberately let the result of this test leak into the next one. -}); - -// Test resetting all keys. This depends on the state set up by the previous -// test. -addAboutKbTask(async function testResetAll(tab) { - ok( - !Glean.browserCustomkeys.actions.reset_all.testGetValue(), - "No telemetry for reset all action" - ); - await SpecialPowers.spawn(tab, [consts], async _consts => { - content.downloadsRow = content.document.querySelector( - '.key[data-id="key_openDownloads"]' - ); - ok( - content.downloadsRow.classList.contains("customized"), - "key_openDownloads is customized" - ); - ok( - !content.downloadsRow.classList.contains("assigned"), - "key_openDownloads is not assigned" - ); - }); - - info("Clicking Reset all, then Cancel"); - let handled = PromptTestUtils.handleNextPrompt( - window, - { modalType: Services.prompt.MODAL_TYPE_CONTENT }, - { buttonNumClick: 1 } - ); - await SpecialPowers.spawn(tab, [consts], async _consts => { - content.resetAll = content.document.getElementById("resetAll"); - content.resetAll.click(); - }); - await handled; - await Services.fog.testFlushAllChildren(); - is( - Glean.browserCustomkeys.actions.reset_all.testGetValue(), - 1, - "Correct telemetry for reset all action" - ); - await SpecialPowers.spawn(tab, [consts], async _consts => { - ok( - content.downloadsRow.classList.contains("customized"), - "key_openDownloads is customized" - ); - ok( - !content.downloadsRow.classList.contains("assigned"), - "key_openDownloads is not assigned" - ); - - info("Clicking Reset all, then OK"); - content.updated = ContentTaskUtils.waitForEvent( - content, - "CustomKeysUpdate", - false, - null, - true - ); - }); - - handled = PromptTestUtils.handleNextPrompt( - window, - { modalType: Services.prompt.MODAL_TYPE_CONTENT }, - { buttonNumClick: 0 } - ); - await SpecialPowers.spawn(tab, [consts], async _consts => { - content.resetAll.click(); - }); - await handled; - await Services.fog.testFlushAllChildren(); - is( - Glean.browserCustomkeys.actions.reset_all.testGetValue(), - 2, - "Correct telemetry for reset all action" - ); - await SpecialPowers.spawn(tab, [consts], async _consts => { - await content.updated; - ok( - !content.downloadsRow.classList.contains("customized"), - "key_openDownloads is not customized" - ); - ok( - content.downloadsRow.classList.contains("assigned"), - "key_openDownloads is assigned" - ); - }); -}); - -// Test a change which conflicts with another key. -addAboutKbTask(async function testConflictingChange(tab) { - await SpecialPowers.spawn(tab, [consts], async _consts => { - content.downloadsRow = content.document.querySelector( - '.key[data-id="key_openDownloads"]' - ); - ok( - !content.downloadsRow.classList.contains("customized"), - "key_openDownloads is not customized" - ); - content.historyRow = content.document.querySelector( - '.key[data-id="key_gotoHistory"]' - ); - ok( - !content.historyRow.classList.contains("customized"), - "key_gotoHistory is not customized" - ); - - info("Clicking Change for key_openDownloads"); - content.input = content.downloadsRow.querySelector(".new"); - let focused = ContentTaskUtils.waitForEvent(content.input, "focus"); - content.change = content.downloadsRow.querySelector(".change"); - content.change.click(); - await focused; - ok(true, "New key input got focus"); - content.focused = ContentTaskUtils.waitForEvent(content.change, "focus"); - }); - await Services.fog.testFlushAllChildren(); - is( - Glean.browserCustomkeys.actions.change.testGetValue(), - 2, - "Correct telemetry for change action" - ); - info(`Pressing ${consts.historyDisplay}, then clicking Cancel`); - let handled = PromptTestUtils.handleNextPrompt( - window, - { modalType: Services.prompt.MODAL_TYPE_CONTENT }, - { buttonNumClick: 1 } - ); - EventUtils.synthesizeKey("H", consts.historyOptions, window); - await handled; - await SpecialPowers.spawn(tab, [consts], async _consts => { - await content.focused; - ok(true, "Change button got focus"); - ok( - !content.downloadsRow.classList.contains("customized"), - "key_openDownloads is not customized" - ); - ok( - !content.historyRow.classList.contains("customized"), - "key_gotoHistory is not customized" - ); - - info("Clicking Change for key_openDownloads"); - let focused = ContentTaskUtils.waitForEvent(content.input, "focus"); - content.change.click(); - await focused; - ok(true, "New key input got focus"); - content.focused = ContentTaskUtils.waitForEvent(content.change, "focus"); - }); - await Services.fog.testFlushAllChildren(); - is( - Glean.browserCustomkeys.actions.change.testGetValue(), - 3, - "Correct telemetry for change action" - ); - info(`Pressing ${consts.historyDisplay}, then clicking OK`); - handled = PromptTestUtils.handleNextPrompt( - window, - { modalType: Services.prompt.MODAL_TYPE_CONTENT }, - { buttonNumClick: 0 } - ); - EventUtils.synthesizeKey("H", consts.historyOptions, window); - await handled; - await SpecialPowers.spawn(tab, [consts], async _consts => { - await content.focused; - ok(true, "Change button got focus"); - ok( - content.downloadsRow.classList.contains("customized"), - "key_openDownloads is customized" - ); - ok( - content.downloadsRow.classList.contains("assigned"), - "key_openDownloads is assigned" - ); - is( - content.downloadsRow.children[1].textContent, - _consts.historyDisplay, - "Key is the customized key" - ); - ok( - content.historyRow.classList.contains("customized"), - "key_gotoHistory is customized" - ); - ok( - !content.historyRow.classList.contains("assigned"), - "key_gotoHistory is not assigned" - ); - is(content.historyRow.children[1].textContent, "", "Key is empty"); - }); - // We deliberately let the result of this test leak into the next one. -}); - -// Test a reset which conflicts with another key. This depends on the state set -// up by the previous test. -addAboutKbTask(async function testConflictingReset(tab) { - await SpecialPowers.spawn(tab, [consts], async _consts => { - content.downloadsRow = content.document.querySelector( - '.key[data-id="key_openDownloads"]' - ); - ok( - content.downloadsRow.classList.contains("customized"), - "key_openDownloads is customized" - ); - content.historyRow = content.document.querySelector( - '.key[data-id="key_gotoHistory"]' - ); - ok( - content.historyRow.classList.contains("customized"), - "key_gotoHistory is customized" - ); - }); - - info("Clicking Reset for key_gotoHistory, then Cancel"); - let handled = PromptTestUtils.handleNextPrompt( - window, - { modalType: Services.prompt.MODAL_TYPE_CONTENT }, - { buttonNumClick: 1 } - ); - await SpecialPowers.spawn(tab, [consts], async _consts => { - content.reset = content.historyRow.querySelector(".reset"); - content.reset.click(); - }); - await handled; - await Services.fog.testFlushAllChildren(); - is( - Glean.browserCustomkeys.actions.reset.testGetValue(), - 2, - "Correct telemetry for reset action" - ); - await SpecialPowers.spawn(tab, [consts], async _consts => { - ok( - content.downloadsRow.classList.contains("customized"), - "key_openDownloads is customized" - ); - ok( - content.historyRow.classList.contains("customized"), - "key_gotoHistory is customized" - ); - }); - - info("Clicking Reset for key_gotoHistory, then OK"); - handled = PromptTestUtils.handleNextPrompt( - window, - { modalType: Services.prompt.MODAL_TYPE_CONTENT }, - { buttonNumClick: 0 } - ); - await SpecialPowers.spawn(tab, [], async () => { - content.updated = ContentTaskUtils.waitForEvent( - content, - "CustomKeysUpdate", - false, - null, - true - ); - content.reset.click(); - }); - await handled; - await Services.fog.testFlushAllChildren(); - is( - Glean.browserCustomkeys.actions.reset.testGetValue(), - 3, - "Correct telemetry for reset action" - ); - await SpecialPowers.spawn(tab, [consts], async _consts => { - await content.updated; - ok( - content.downloadsRow.classList.contains("customized"), - "key_openDownloads is customized" - ); - ok( - !content.downloadsRow.classList.contains("assigned"), - "key_openDownloads is not assigned" - ); - is(content.downloadsRow.children[1].textContent, "", "Key is empty"); - ok( - !content.historyRow.classList.contains("customized"), - "key_gotoHistory is not customized" - ); - ok( - content.historyRow.classList.contains("assigned"), - "key_gotoHistory is assigned" - ); - is( - content.historyRow.children[1].textContent, - _consts.historyDisplay, - "Key is the default key" - ); - }); - - CustomKeys.resetAll(); -}); - -// Test that a reserved key is captured correctly. -addAboutKbTask(async function testReservedKey(tab) { - await SpecialPowers.spawn(tab, [consts], async _consts => { - content.downloadsRow = content.document.querySelector( - '.key[data-id="key_openDownloads"]' - ); - info("Clicking Change for key_openDownloads"); - content.input = content.downloadsRow.querySelector(".new"); - let focused = ContentTaskUtils.waitForEvent(content.input, "focus"); - content.change = content.downloadsRow.querySelector(".change"); - content.change.click(); - await focused; - ok(true, "New key input got focus"); - content.focused = ContentTaskUtils.waitForEvent(content.change, "focus"); - }); - await Services.fog.testFlushAllChildren(); - is( - Glean.browserCustomkeys.actions.change.testGetValue(), - 4, - "Correct telemetry for change action" - ); - info(`Pressing ${consts.newWindowDisplay}, then clicking Cancel`); - let handled = PromptTestUtils.handleNextPrompt( - window, - { modalType: Services.prompt.MODAL_TYPE_CONTENT }, - { buttonNumClick: 1 } - ); - EventUtils.synthesizeKey("N", { accelKey: true }, window); - await handled; - await SpecialPowers.spawn(tab, [], async () => { - await content.focused; - ok(true, "Change button got focus"); - }); -}); - -// Test that changing to a function key works correctly; i.e. that we handle key -// vs keycode. -addAboutKbTask(async function testFunctionKey(tab) { - await SpecialPowers.spawn(tab, [consts], async _consts => { - content.downloadsRow = content.document.querySelector( - '.key[data-id="key_openDownloads"]' - ); - ok( - !content.downloadsRow.classList.contains("customized"), - "key_openDownloads is not customized" - ); - info("Clicking Change for key_openDownloads"); - content.input = content.downloadsRow.querySelector(".new"); - let focused = ContentTaskUtils.waitForEvent(content.input, "focus"); - content.change = content.downloadsRow.querySelector(".change"); - content.change.click(); - await focused; - ok(true, "New key input got focus"); - content.focused = ContentTaskUtils.waitForEvent(content.change, "focus"); - }); - await Services.fog.testFlushAllChildren(); - is( - Glean.browserCustomkeys.actions.change.testGetValue(), - 5, - "Correct telemetry for change action" - ); - info("Pressing F1"); - EventUtils.synthesizeKey("KEY_F1", {}, window); - await SpecialPowers.spawn(tab, [consts], async _consts => { - await content.focused; - ok(true, "Change button got focus"); - ok( - content.downloadsRow.classList.contains("customized"), - "key_openDownloads is customized" - ); - is( - content.downloadsRow.children[1].textContent, - "F1", - "Key is the customized key" - ); - }); - CustomKeys.resetAll(); -}); diff --git a/browser/components/customkeys/tests/browser/head.js b/browser/components/customkeys/tests/browser/head.js @@ -1,30 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - * http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -const isMac = AppConstants.platform == "macosx"; -const isLinux = AppConstants.platform == "linux"; - -const consts = { - // The following constants specify the default key combinations for various - // commands. They must be updated if these change in future. - // key_gotoHistory - historyDisplay: isMac ? "⇧⌘H" : "Ctrl+H", - historyModifiers: isMac ? "accel,shift" : "accel", - historyOptions: { accelKey: true, shiftKey: isMac }, - // key_openDownloads - downloadsDisplay: (isMac && "⌘J") || (isLinux && "Ctrl+Shift+Y") || "Ctrl+J", - // key_newNavigator - newWindowDisplay: isMac ? "⌘N" : "Ctrl+N", - - // The following unused* constants specify a key combination which is unused by - // default. This will need to be updated if this key combination is assigned to - // something by default in future. - unusedModifiers: "accel,shift", - unusedOptions: { accelKey: true, shiftKey: true }, - unusedKey: isLinux ? "Q" : "Y", - unusedModifiersDisplay: isMac ? "⇧⌘" : "Ctrl+Shift+", - unusedModifiersArgs: ["KEY_Shift", { accelKey: true }], -}; -consts.unusedDisplay = `${consts.unusedModifiersDisplay}${consts.unusedKey}`; diff --git a/browser/components/moz.build b/browser/components/moz.build @@ -36,7 +36,6 @@ DIRS += [ "contentanalysis", "contextualidentity", "customizableui", - "customkeys", "downloads", "enterprisepolicies", "extensions", diff --git a/browser/locales/en-US/browser/customkeys.ftl b/browser/locales/en-US/browser/customkeys.ftl @@ -1,42 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -customkeys-title = Keyboard Shortcuts -customkeys-search = Search: -customkeys-change = Change -customkeys-reset = Reset -customkeys-clear = Clear -customkeys-new-key = Press new key: -customkeys-reset-all = Reset all shortcuts to defaults - -# Variables -# $conflict (string) - The title of the conflicting shortcut. -customkeys-conflict-confirm = This key is already assigned to { $conflict }. Do you want to replace it? - -customkeys-reset-all-confirm = Are you sure you wish to reset all keyboard shortcuts to their defaults? - -customkeys-history-reopen-tab = Reopen Last Closed Tab -customkeys-history-reopen-window = Reopen Last Closed window -customkeys-dev-tools = Web Developer Tools -customkeys-dev-inspector = DOM and Style Inspector -customkeys-dev-webconsole = Web Console -customkeys-dev-debugger = JavaScript Debugger -customkeys-dev-network = Network Monitor -customkeys-dev-style = Style Editor -customkeys-dev-performance = Performance -customkeys-dev-storage = Storage Inspector -customkeys-dev-dom = DOM -customkeys-dev-accessibility = Accessibility -customkeys-dev-profiler-toggle = Start/Stop the Performance Profiler -customkeys-dev-profiler-capture = Capture a Performance Profile - -customkeys-category-navigation = Navigation -customkeys-nav-back = Back -customkeys-nav-forward = Forward -customkeys-nav-home = Home -customkeys-nav-reload = Reload -customkeys-nav-reload-skip-cache = Reload (Override Cache) -customkeys-nav-stop = Stop - -customkeys-caution-message = This feature is experimental and may not work as expected. diff --git a/devtools/startup/DevToolsStartup.sys.mjs b/devtools/startup/DevToolsStartup.sys.mjs @@ -379,12 +379,6 @@ DevToolsStartup.prototype = { this.onMoreToolsViewShowing, "web-developer-tools-view-showing" ); - // Add DevTools menu items so they can be picked up by the customize - // keyboard shortcuts UI. - Services.obs.addObserver(() => { - // Initialize DevTools to create all menuitems in the system menu. - this.initDevTools("CustomKeysUI"); - }, "customkeys-ui-showing"); /* eslint-enable mozilla/balanced-observers */ if (!this.isDisabledByPolicy()) { diff --git a/eslint-file-globals.config.mjs b/eslint-file-globals.config.mjs @@ -364,7 +364,6 @@ export default [ files: [ "browser/base/content/aboutRestartRequired.js", "browser/base/content/aboutTabCrashed.js", - "browser/components/customkeys/content/customkeys.js", "browser/components/privatebrowsing/content/aboutPrivateBrowsing.js", "browser/components/profiles/content/delete-profile-card.mjs", "browser/components/profiles/content/edit-profile-card.mjs", diff --git a/toolkit/components/glean/metrics_index.py b/toolkit/components/glean/metrics_index.py @@ -121,7 +121,6 @@ firefox_desktop_metrics = [ "browser/components/attribution/metrics.yaml", "browser/components/backup/metrics.yaml", "browser/components/contextualidentity/metrics.yaml", - "browser/components/customkeys/metrics.yaml", "browser/components/downloads/metrics.yaml", "browser/components/extensions/metrics.yaml", "browser/components/firefoxview/metrics.yaml", diff --git a/toolkit/modules/RemotePageAccessManager.sys.mjs b/toolkit/modules/RemotePageAccessManager.sys.mjs @@ -74,18 +74,6 @@ export let RemotePageAccessManager = { "about:certificate": { RPMSendQuery: ["getCertificates"], }, - "about:keyboard": { - RPMAddMessageListener: ["CustomKeys:CapturedKey"], - RPMSendAsyncMessage: ["CustomKeys:CaptureKey"], - RPMSendQuery: [ - "CustomKeys:ChangeKey", - "CustomKeys:ClearKey", - "CustomKeys:GetDefaultKey", - "CustomKeys:GetKeys", - "CustomKeys:ResetAll", - "CustomKeys:ResetKey", - ], - }, "about:neterror": { RPMSendAsyncMessage: [ "Browser:EnableOnlineMode", diff --git a/tools/lint/fluent-lint/exclusions.yml b/tools/lint/fluent-lint/exclusions.yml @@ -145,8 +145,6 @@ CO01: - identity-description-custom-root2 # browser/components/urlbar/content/enUS-searchFeatures.ftl - addressbar-firefox-suggest-online - # browser/customkeys.ftl - - customkeys-dev-network # browser/migrationWizard.ftl - migration-wizard-migrator-display-name-firefox # browser/newtab/onboarding.ftl