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:
Mark Striemer 2019-01-11 22:32:39 +00:00
Родитель 0fc4897c74
Коммит 50cfffaeaa
5 изменённых файлов: 187 добавлений и 115 удалений

Просмотреть файл

@ -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);