зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1303384 - Part 2: Move some extension shortcut utils to ShortcutUtils r=aswan
Differential Revision: https://phabricator.services.mozilla.com/D4506 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
0fc4897c74
Коммит
50cfffaeaa
|
@ -168,14 +168,14 @@ add_task(async function test_update_defined_command() {
|
|||
}
|
||||
|
||||
// Check that the <key> is set for the original shortcut.
|
||||
checkKey(extension.id, "I", "accel shift");
|
||||
checkKey(extension.id, "I", "accel,shift");
|
||||
|
||||
await extension.awaitMessage("ready");
|
||||
extension.sendMessage("run");
|
||||
await extension.awaitFinish("commands");
|
||||
|
||||
// Check that the <keycode> has been updated.
|
||||
checkNumericKey(extension.id, "9", "alt shift");
|
||||
checkNumericKey(extension.id, "9", "alt,shift");
|
||||
|
||||
// Check that the updated command is stored in ExtensionSettingsStore.
|
||||
let storedCommands = ExtensionSettingsStore.getAllForExtension(
|
||||
|
@ -188,7 +188,7 @@ add_task(async function test_update_defined_command() {
|
|||
// Check that the key is updated immediately.
|
||||
extension.sendMessage("update", {name: "foo", shortcut: "Ctrl+Shift+M"});
|
||||
await extension.awaitMessage("updateDone");
|
||||
checkKey(extension.id, "M", "accel shift");
|
||||
checkKey(extension.id, "M", "accel,shift");
|
||||
|
||||
// Ensure all successive updates are stored.
|
||||
// Force the command to only have a description saved.
|
||||
|
@ -206,7 +206,7 @@ add_task(async function test_update_defined_command() {
|
|||
extension.sendMessage("reset", "foo");
|
||||
await extension.awaitMessage("resetDone");
|
||||
|
||||
checkKey(extension.id, "I", "accel shift");
|
||||
checkKey(extension.id, "I", "accel,shift");
|
||||
|
||||
// Check that enable/disable removes the keyset and reloads the saved command.
|
||||
let addon = await AddonManager.getAddonByID(extension.id);
|
||||
|
@ -224,7 +224,7 @@ add_task(async function test_update_defined_command() {
|
|||
// Wait for the keyset to appear (it's async on enable).
|
||||
await TestUtils.waitForCondition(() => extensionKeyset(extension.id));
|
||||
// The keyset is back with the value from ExtensionSettingsStore.
|
||||
checkNumericKey(extension.id, "9", "alt shift");
|
||||
checkNumericKey(extension.id, "9", "alt,shift");
|
||||
|
||||
// Check that an update to a shortcut in the manifest is mapped correctly.
|
||||
updatedExtension = ExtensionTestUtils.loadExtension({
|
||||
|
@ -246,7 +246,7 @@ add_task(async function test_update_defined_command() {
|
|||
|
||||
await TestUtils.waitForCondition(() => extensionKeyset(extension.id));
|
||||
// Shortcut is unchanged since it was previously updated.
|
||||
checkNumericKey(extension.id, "9", "alt shift");
|
||||
checkNumericKey(extension.id, "9", "alt,shift");
|
||||
});
|
||||
|
||||
add_task(async function updateSidebarCommand() {
|
||||
|
|
|
@ -9,6 +9,7 @@ const EXPORTED_SYMBOLS = ["ExtensionShortcuts"];
|
|||
ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
|
||||
ChromeUtils.import("resource://gre/modules/ShortcutUtils.jsm");
|
||||
|
||||
ChromeUtils.defineModuleGetter(this, "ExtensionParent",
|
||||
"resource://gre/modules/ExtensionParent.jsm");
|
||||
|
@ -28,10 +29,7 @@ XPCOMUtils.defineLazyGetter(this, "sidebarActionFor", () => {
|
|||
return ExtensionParent.apiManager.global.sidebarActionFor;
|
||||
});
|
||||
|
||||
const {
|
||||
chromeModifierKeyMap,
|
||||
ExtensionError,
|
||||
} = ExtensionUtils;
|
||||
const {ExtensionError} = ExtensionUtils;
|
||||
const {makeWidgetId} = ExtensionCommon;
|
||||
|
||||
const EXECUTE_PAGE_ACTION = "_execute_page_action";
|
||||
|
@ -338,7 +336,7 @@ class ExtensionShortcuts {
|
|||
let chromeKey = parts.pop();
|
||||
|
||||
// The modifiers are the remaining elements.
|
||||
keyElement.setAttribute("modifiers", this.getModifiersAttribute(parts));
|
||||
keyElement.setAttribute("modifiers", ShortcutUtils.getModifiersAttribute(parts));
|
||||
|
||||
// A keyElement with key "NumpadX" is created above and isn't from the
|
||||
// manifest. The id will be set on the keyElement with key "X" only.
|
||||
|
@ -347,53 +345,12 @@ class ExtensionShortcuts {
|
|||
keyElement.setAttribute("id", id);
|
||||
}
|
||||
|
||||
if (/^[A-Z]$/.test(chromeKey)) {
|
||||
// We use the key attribute for all single digits and characters.
|
||||
keyElement.setAttribute("key", chromeKey);
|
||||
} else {
|
||||
keyElement.setAttribute("keycode", this.getKeycodeAttribute(chromeKey));
|
||||
let [attribute, value] = ShortcutUtils.getKeyAttribute(chromeKey);
|
||||
keyElement.setAttribute(attribute, value);
|
||||
if (attribute == "keycode") {
|
||||
keyElement.setAttribute("event", "keydown");
|
||||
}
|
||||
|
||||
return keyElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the corresponding XUL keycode from the given chrome key.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* input | output
|
||||
* ---------------------------------------
|
||||
* "PageUP" | "VK_PAGE_UP"
|
||||
* "Delete" | "VK_DELETE"
|
||||
*
|
||||
* @param {string} chromeKey The chrome key (e.g. "PageUp", "Space", ...)
|
||||
* @returns {string} The constructed value for the Key's 'keycode' attribute.
|
||||
*/
|
||||
getKeycodeAttribute(chromeKey) {
|
||||
if (/^[0-9]/.test(chromeKey)) {
|
||||
return `VK_${chromeKey}`;
|
||||
}
|
||||
return `VK${chromeKey.replace(/([A-Z])/g, "_$&").toUpperCase()}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the corresponding XUL modifiers from the chrome modifiers.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* input | output
|
||||
* ---------------------------------------
|
||||
* ["Ctrl", "Shift"] | "accel shift"
|
||||
* ["MacCtrl"] | "control"
|
||||
*
|
||||
* @param {Array} chromeModifiers The array of chrome modifiers.
|
||||
* @returns {string} The constructed value for the Key's 'modifiers' attribute.
|
||||
*/
|
||||
getModifiersAttribute(chromeModifiers) {
|
||||
return Array.from(chromeModifiers, modifier => {
|
||||
return chromeModifierKeyMap[modifier];
|
||||
}).join(" ");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -259,17 +259,7 @@ function flushJarCache(jarPath) {
|
|||
Services.obs.notifyObservers(null, "flush-cache-entry", jarPath);
|
||||
}
|
||||
|
||||
const chromeModifierKeyMap = {
|
||||
"Alt": "alt",
|
||||
"Command": "accel",
|
||||
"Ctrl": "accel",
|
||||
"MacCtrl": "control",
|
||||
"Shift": "shift",
|
||||
};
|
||||
|
||||
|
||||
var ExtensionUtils = {
|
||||
chromeModifierKeyMap,
|
||||
flushJarCache,
|
||||
getInnerWindowID,
|
||||
getMessageManager,
|
||||
|
|
|
@ -15,7 +15,6 @@ XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
|
|||
|
||||
ChromeUtils.import("resource://gre/modules/ExtensionUtils.jsm");
|
||||
var {
|
||||
chromeModifierKeyMap,
|
||||
DefaultMap,
|
||||
DefaultWeakMap,
|
||||
} = ExtensionUtils;
|
||||
|
@ -24,6 +23,8 @@ ChromeUtils.defineModuleGetter(this, "ExtensionParent",
|
|||
"resource://gre/modules/ExtensionParent.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
ChromeUtils.defineModuleGetter(this, "ShortcutUtils",
|
||||
"resource://gre/modules/ShortcutUtils.jsm");
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "contentPolicyService",
|
||||
"@mozilla.org/addons/content-policy;1",
|
||||
"nsIAddonContentPolicy");
|
||||
|
@ -993,55 +994,15 @@ const FORMATS = {
|
|||
},
|
||||
|
||||
manifestShortcutKey(string, context) {
|
||||
// A valid shortcut key for a webextension manifest
|
||||
const MEDIA_KEYS = /^(MediaNextTrack|MediaPlayPause|MediaPrevTrack|MediaStop)$/;
|
||||
const BASIC_KEYS = /^([A-Z0-9]|Comma|Period|Home|End|PageUp|PageDown|Space|Insert|Delete|Up|Down|Left|Right)$/;
|
||||
const FUNCTION_KEYS = /^(F[1-9]|F1[0-2])$/;
|
||||
if (ShortcutUtils.validate(string) == ShortcutUtils.IS_VALID) {
|
||||
return string;
|
||||
}
|
||||
let errorMessage = (`Value "${string}" must consist of `
|
||||
+ `either a combination of one or two modifiers, including `
|
||||
+ `a mandatory primary modifier and a key, separated by '+', `
|
||||
+ `or a media key. For details see: `
|
||||
+ `https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/commands#Key_combinations`);
|
||||
if (MEDIA_KEYS.test(string.trim())) {
|
||||
return string;
|
||||
}
|
||||
|
||||
let modifiers = string.split("+").map(s => s.trim());
|
||||
let key = modifiers.pop();
|
||||
|
||||
if (!BASIC_KEYS.test(key) && !FUNCTION_KEYS.test(key)) {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
let chromeModifiers = modifiers.map(m => chromeModifierKeyMap[m]);
|
||||
// If the modifier wasn't found it will be undefined.
|
||||
if (chromeModifiers.some(modifier => !modifier)) {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
switch (modifiers.length) {
|
||||
case 0:
|
||||
// A lack of modifiers is only allowed with function keys.
|
||||
if (!FUNCTION_KEYS.test(key)) {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
// Shift is only allowed on its own with function keys.
|
||||
if (chromeModifiers[0] == "shift" && !FUNCTION_KEYS.test(key)) {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (chromeModifiers[0] == chromeModifiers[1]) {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
|
||||
return string;
|
||||
throw new Error(errorMessage);
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,13 @@ XPCOMUtils.defineLazyGetter(this, "Keys", function() {
|
|||
});
|
||||
|
||||
var ShortcutUtils = {
|
||||
IS_VALID: "valid",
|
||||
INVALID_KEY: "invalid_key",
|
||||
INVALID_MODIFIER: "invalid_modifier",
|
||||
INVALID_COMBINATION: "invalid_combination",
|
||||
DUPLICATE_MODIFIER: "duplicate_modifier",
|
||||
MODIFIER_REQUIRED: "modifier_required",
|
||||
|
||||
/**
|
||||
* Prettifies the modifier keys for an element.
|
||||
*
|
||||
|
@ -31,8 +38,17 @@ var ShortcutUtils = {
|
|||
* A prettified and properly separated modifier keys string.
|
||||
*/
|
||||
prettifyShortcut(aElemKey, aNoCloverLeaf) {
|
||||
let elemString = this.getModifierString(
|
||||
aElemKey.getAttribute("modifiers"),
|
||||
aNoCloverLeaf);
|
||||
let key = this.getKeyString(
|
||||
aElemKey.getAttribute("keycode"),
|
||||
aElemKey.getAttribute("key"));
|
||||
return elemString + key;
|
||||
},
|
||||
|
||||
getModifierString(elemMod, aNoCloverLeaf) {
|
||||
let elemString = "";
|
||||
let elemMod = aElemKey.getAttribute("modifiers");
|
||||
let haveCloverLeaf = false;
|
||||
|
||||
if (elemMod.match("accel")) {
|
||||
|
@ -84,8 +100,11 @@ var ShortcutUtils = {
|
|||
PlatformKeys.GetStringFromName("MODIFIER_SEPARATOR");
|
||||
}
|
||||
|
||||
return elemString;
|
||||
},
|
||||
|
||||
getKeyString(keyCode, keyAttribute) {
|
||||
let key;
|
||||
let keyCode = aElemKey.getAttribute("keycode");
|
||||
if (keyCode) {
|
||||
keyCode = keyCode.toUpperCase();
|
||||
try {
|
||||
|
@ -97,16 +116,161 @@ var ShortcutUtils = {
|
|||
key = keyCode.replace(/^VK_/, "");
|
||||
}
|
||||
} else {
|
||||
key = aElemKey.getAttribute("key");
|
||||
key = key.toUpperCase();
|
||||
key = keyAttribute.toUpperCase();
|
||||
}
|
||||
return elemString + key;
|
||||
|
||||
return key;
|
||||
},
|
||||
|
||||
getKeyAttribute(chromeKey) {
|
||||
if (/^[A-Z]$/.test(chromeKey)) {
|
||||
// We use the key attribute for single characters.
|
||||
return ["key", chromeKey];
|
||||
}
|
||||
return ["keycode", this.getKeycodeAttribute(chromeKey)];
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines the corresponding XUL keycode from the given chrome key.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* input | output
|
||||
* ---------------------------------------
|
||||
* "PageUp" | "VK_PAGE_UP"
|
||||
* "Delete" | "VK_DELETE"
|
||||
*
|
||||
* @param {string} chromeKey The chrome key (e.g. "PageUp", "Space", ...)
|
||||
* @returns {string} The constructed value for the Key's 'keycode' attribute.
|
||||
*/
|
||||
getKeycodeAttribute(chromeKey) {
|
||||
if (/^[0-9]/.test(chromeKey)) {
|
||||
return `VK_${chromeKey}`;
|
||||
}
|
||||
return `VK${chromeKey.replace(/([A-Z])/g, "_$&").toUpperCase()}`;
|
||||
},
|
||||
|
||||
findShortcut(aElemCommand) {
|
||||
let document = aElemCommand.ownerDocument;
|
||||
return document.querySelector("key[command=\"" + aElemCommand.getAttribute("id") + "\"]");
|
||||
},
|
||||
|
||||
chromeModifierKeyMap: {
|
||||
"Alt": "alt",
|
||||
"Command": "accel",
|
||||
"Ctrl": "accel",
|
||||
"MacCtrl": "control",
|
||||
"Shift": "shift",
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines the corresponding XUL modifiers from the chrome modifiers.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* input | output
|
||||
* ---------------------------------------
|
||||
* ["Ctrl", "Shift"] | "accel,shift"
|
||||
* ["MacCtrl"] | "control"
|
||||
*
|
||||
* @param {Array} chromeModifiers The array of chrome modifiers.
|
||||
* @returns {string} The constructed value for the Key's 'modifiers' attribute.
|
||||
*/
|
||||
getModifiersAttribute(chromeModifiers) {
|
||||
return Array.from(chromeModifiers, modifier => {
|
||||
return ShortcutUtils.chromeModifierKeyMap[modifier];
|
||||
}).sort().join(",");
|
||||
},
|
||||
|
||||
/**
|
||||
* Validate if a shortcut string is valid and return an error code if it
|
||||
* isn't valid.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* input | output
|
||||
* ---------------------------------------
|
||||
* "Ctrl+Shift+A" | IS_VALID
|
||||
* "Shift+F" | MODIFIER_REQUIRED
|
||||
* "Command+>" | INVALID_KEY
|
||||
*
|
||||
* @param {string} string The shortcut string.
|
||||
* @returns {string} The code for the validation result.
|
||||
*/
|
||||
validate(string) {
|
||||
// A valid shortcut key for a webextension manifest
|
||||
const MEDIA_KEYS = /^(MediaNextTrack|MediaPlayPause|MediaPrevTrack|MediaStop)$/;
|
||||
const BASIC_KEYS = /^([A-Z0-9]|Comma|Period|Home|End|PageUp|PageDown|Space|Insert|Delete|Up|Down|Left|Right)$/;
|
||||
const FUNCTION_KEYS = /^(F[1-9]|F1[0-2])$/;
|
||||
|
||||
if (MEDIA_KEYS.test(string.trim())) {
|
||||
return this.IS_VALID;
|
||||
}
|
||||
|
||||
let modifiers = string.split("+").map(s => s.trim());
|
||||
let key = modifiers.pop();
|
||||
|
||||
let chromeModifiers = modifiers.map(m => ShortcutUtils.chromeModifierKeyMap[m]);
|
||||
// If the modifier wasn't found it will be undefined.
|
||||
if (chromeModifiers.some(modifier => !modifier)) {
|
||||
return this.INVALID_MODIFIER;
|
||||
}
|
||||
|
||||
switch (modifiers.length) {
|
||||
case 0:
|
||||
// A lack of modifiers is only allowed with function keys.
|
||||
if (!FUNCTION_KEYS.test(key)) {
|
||||
return this.MODIFIER_REQUIRED;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
// Shift is only allowed on its own with function keys.
|
||||
if (chromeModifiers[0] == "shift" && !FUNCTION_KEYS.test(key)) {
|
||||
return this.MODIFIER_REQUIRED;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
if (chromeModifiers[0] == chromeModifiers[1]) {
|
||||
return this.DUPLICATE_MODIFIER;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return this.INVALID_COMBINATION;
|
||||
}
|
||||
|
||||
if (!BASIC_KEYS.test(key) && !FUNCTION_KEYS.test(key)) {
|
||||
return this.INVALID_KEY;
|
||||
}
|
||||
|
||||
return this.IS_VALID;
|
||||
},
|
||||
|
||||
/**
|
||||
* Attempt to find a key for a given shortcut string, such as
|
||||
* "Ctrl+Shift+A" and determine if it is a system shortcut.
|
||||
*
|
||||
* @param {Object} win The window to look for key elements in.
|
||||
* @param {string} value The shortcut string.
|
||||
* @returns {boolean} Whether a system shortcut was found or not.
|
||||
*/
|
||||
isSystem(win, value) {
|
||||
let modifiers = value.split("+");
|
||||
let chromeKey = modifiers.pop();
|
||||
let modifiersString = this.getModifiersAttribute(modifiers);
|
||||
let keycode = this.getKeycodeAttribute(chromeKey);
|
||||
|
||||
let baseSelector = "key";
|
||||
if (modifiers.length > 0) {
|
||||
baseSelector += `[modifiers="${modifiersString}"]`;
|
||||
}
|
||||
|
||||
let keyEl = win.document.querySelector([
|
||||
`${baseSelector}[key="${chromeKey}"]`,
|
||||
`${baseSelector}[key="${chromeKey.toLowerCase()}"]`,
|
||||
`${baseSelector}[keycode="${keycode}"]`,
|
||||
].join(","));
|
||||
return keyEl && !keyEl.closest("keyset").id.startsWith("ext-keyset-id");
|
||||
},
|
||||
};
|
||||
|
||||
Object.freeze(ShortcutUtils);
|
||||
|
|
Загрузка…
Ссылка в новой задаче