[Pkg-mozext-commits] [requestpolicy] 242/257: [ref] keyboard shortcut: create class + controller
David Prévot
taffit at moszumanska.debian.org
Thu Jan 28 03:20:18 UTC 2016
This is an automated email from the git hooks/post-receive script.
taffit pushed a commit to branch master
in repository requestpolicy.
commit c204fe4a77e2ad2ea6a5edafbc6db01c48b098d6
Author: Martin Kimmerle <dev at 256k.de>
Date: Fri Dec 25 09:26:00 2015 +0100
[ref] keyboard shortcut: create class + controller
---
src/content/controllers/keyboard-shortcuts.jsm | 57 ++++++
src/content/lib/classes/keyboard-shortcut.jsm | 196 +++++++++++++++++++++
src/content/lib/environment.process.js | 7 +-
src/content/lib/utils/javascript.jsm | 44 +++++
src/content/lib/utils/xul.jsm | 75 ++++++++
src/content/models/windows.jsm | 8 +-
src/content/ui/xul-trees.js | 13 +-
tests/marionette/rp_puppeteer/tests/test_menu.py | 15 +-
tests/marionette/rp_puppeteer/ui/menu.py | 15 +-
tests/xpcshell/test_jsutils.js | 20 +++
tests/xpcshell/test_xulutils_keyboard_shortcuts.js | 66 +++++++
tests/xpcshell/xpcshell.ini | 2 +
12 files changed, 496 insertions(+), 22 deletions(-)
diff --git a/src/content/controllers/keyboard-shortcuts.jsm b/src/content/controllers/keyboard-shortcuts.jsm
new file mode 100644
index 0000000..891fc3a
--- /dev/null
+++ b/src/content/controllers/keyboard-shortcuts.jsm
@@ -0,0 +1,57 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ *
+ * RequestPolicy - A Firefox extension for control over cross-site requests.
+ * Copyright (c) 2015 Martin Kimmerle
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+/* global Components */
+const {utils: Cu} = Components;
+
+/* exported KeyboardShortcuts */
+this.EXPORTED_SYMBOLS = ["KeyboardShortcuts"];
+
+let {ScriptLoader: {importModule}} = Cu.import(
+ "chrome://rpcontinued/content/lib/script-loader.jsm", {});
+let {KeyboardShortcut} = importModule("lib/classes/keyboard-shortcut");
+
+//==============================================================================
+// KeyboardShortcuts
+//==============================================================================
+
+var KeyboardShortcuts = (function() {
+ let self = {};
+
+ let keyboardShortcuts = [];
+
+ self.startup = function() {
+ keyboardShortcuts.push(new KeyboardShortcut("openMenu", "alt shift r",
+ function(window) {
+ window.rpcontinued.overlay.openMenu();
+ }));
+ };
+
+ self.shutdown = function() {
+ keyboardShortcuts.forEach(ks => {
+ ks.destroy();
+ });
+ keyboardShortcuts.length = 0;
+ };
+
+ return self;
+}());
diff --git a/src/content/lib/classes/keyboard-shortcut.jsm b/src/content/lib/classes/keyboard-shortcut.jsm
new file mode 100644
index 0000000..e4c3cab
--- /dev/null
+++ b/src/content/lib/classes/keyboard-shortcut.jsm
@@ -0,0 +1,196 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ *
+ * RequestPolicy - A Firefox extension for control over cross-site requests.
+ * Copyright (c) 2015 Martin Kimmerle
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+/* global Components */
+const {utils: Cu} = Components;
+
+/* exported KeyboardShortcut */
+this.EXPORTED_SYMBOLS = ["KeyboardShortcut"];
+
+let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
+
+let {ScriptLoader: {importModule}} = Cu.import(
+ "chrome://rpcontinued/content/lib/script-loader.jsm", {});
+let {Windows} = importModule("models/windows");
+let {XULUtils} = importModule("lib/utils/xul");
+
+//==============================================================================
+// KeyboardShortcut
+//==============================================================================
+
+/**
+ * Overview of events:
+ *
+ * Init:
+ * - determine attributes
+ * - create element (all windows) \ load into
+ * - set element attributes (all windows) / window
+ *
+ * New window:
+ * - create element (this window) \ load into
+ * - set element attributes (this window) / window
+ *
+ * Pref changed:
+ * - determine attributes
+ * - set element attributes (all windows)
+ */
+
+function KeyboardShortcut(aID, aDefaultCombo, aCallback) {
+ //----------------------------------------------------------------------------
+ // initialize properties
+ //----------------------------------------------------------------------------
+
+ this._id = aID;
+ this._elementID = "rpKey_" + this._id;
+ this._defaultCombo = aDefaultCombo;
+ this._callback = aCallback;
+
+ this._elementAttributes = {
+ disabled: null,
+ modifiers: null,
+ key: null
+ };
+
+ this._listeners = {
+ onWindowLoad: this._loadIntoWindow.bind(this),
+ onWindowUnload: this._unloadFromWindow.bind(this),
+ onKeyCommand: this._onPress.bind(this)
+ // prefChanged: this._onPrefChange.bind(this)
+ };
+
+ //----------------------------------------------------------------------------
+ // initialize
+ //----------------------------------------------------------------------------
+
+ this._determineElementAttributes();
+ Windows.forEachOpenWindow(this._loadIntoWindow.bind(this));
+
+ Windows.addListener("load", this._listeners.onWindowLoad);
+ Windows.addListener("unload", this._listeners.onWindowUnload);
+}
+
+//------------------------------------------------------------------------------
+// main methods
+//------------------------------------------------------------------------------
+
+KeyboardShortcut.prototype.destroy = function() {
+ Windows.forEachOpenWindow(this._unloadFromWindow.bind(this));
+ Windows.removeListener("load", this._listeners.onWindowLoad);
+ Windows.removeListener("unload", this._listeners.onWindowUnload);
+};
+
+KeyboardShortcut.prototype._loadIntoWindow = function(window) {
+ this._createElement(window);
+ this._setElementAttributes(window);
+};
+
+KeyboardShortcut.prototype._unloadFromWindow = function(window) {
+ this._removeElement(window);
+};
+
+KeyboardShortcut.prototype._onPress = function(event) {
+ let window = event.target.ownerDocument.defaultView;
+ this._callback.call(null, window);
+};
+
+// KeyboardShortcut.prototype._onPrefChange = function() {
+// this._determineElementAttributes();
+// Windows.forEachOpenWindow(this._loadIntoWindow.bind(this));
+// };
+
+//------------------------------------------------------------------------------
+// assisting methods
+//------------------------------------------------------------------------------
+
+KeyboardShortcut.prototype._createElement = function(window) {
+ let keyset = window.document.getElementById("rpcontinuedKeyset");
+ let element = window.document.createElement("key");
+ element.setAttribute("id", this._elementID);
+ element.setAttribute("disabled", "true");
+ // "oncommand" (or "command") is required! See
+ // https://stackoverflow.com/questions/16779316/how-to-set-an-xul-key-dynamically-and-securely/16786770#16786770
+ element.setAttribute("oncommand", "void(0);");
+ element.addEventListener("command", this._listeners.onKeyCommand, false);
+ keyset.appendChild(element);
+};
+
+KeyboardShortcut.prototype._setElementAttributes = function(window) {
+ let element = window.document.getElementById(this._elementID);
+
+ // disable temporarily
+ element.setAttribute("disabled", "true");
+
+ element.setAttribute("modifiers", this._elementAttributes.modifiers);
+ element.setAttribute("key", this._elementAttributes.key);
+ element.setAttribute("disabled", this._elementAttributes.disabled);
+};
+
+KeyboardShortcut.prototype._removeElement = function(window) {
+ let element = window.document.getElementById(this._elementID);
+ element.removeEventListener("command", this._listeners.onKeyCommand, false);
+ element.remove();
+};
+
+Object.defineProperty(KeyboardShortcut.prototype, "userCombo", {
+ get: function() {
+ // will be changed to pref
+ return "default";
+ }
+});
+
+Object.defineProperty(KeyboardShortcut.prototype, "userEnabled", {
+ get: function() {
+ // will be changed to pref
+ return true;
+ }
+});
+
+const ELEMENT_ATTRIBUTES_WHEN_DISABLED = {
+ disabled: "true",
+ modifiers: "",
+ key: ""
+};
+
+KeyboardShortcut.prototype._determineElementAttributes = function() {
+ if (!this.userEnabled) {
+ this._elementAttributes = ELEMENT_ATTRIBUTES_WHEN_DISABLED;
+ return;
+ }
+
+ let combo = this.userCombo;
+ if (combo === "default") {
+ combo = this._defaultCombo;
+ }
+
+ let rv = XULUtils.keyboardShortcuts.getKeyAttributesFromCombo(combo);
+ if (false === rv.success) {
+ console.error("Error parsing keyboard combination for shortcut \"" +
+ this._id + "\": " + rv.errorMessage);
+ this._elementAttributes = ELEMENT_ATTRIBUTES_WHEN_DISABLED;
+ return;
+ }
+ this._elementAttributes = {
+ disabled: "false",
+ modifiers: rv.modifiers,
+ key: rv.key
+ };
+};
diff --git a/src/content/lib/environment.process.js b/src/content/lib/environment.process.js
index f74ca61..ff795f7 100644
--- a/src/content/lib/environment.process.js
+++ b/src/content/lib/environment.process.js
@@ -107,8 +107,10 @@ var ProcessEnvironment = (function() {
Object.defineProperty(ParentProcessEnvironment.prototype, "controllers", {
get: function() {
+ let {importModule} = ScriptLoader;
return [
- ScriptLoader.importModule("controllers/old-rules").OldRulesController
+ importModule("controllers/keyboard-shortcuts").KeyboardShortcuts,
+ importModule("controllers/old-rules").OldRulesController,
];
}
});
@@ -142,6 +144,9 @@ var ProcessEnvironment = (function() {
// other back end modules are loaded / initialized.
}
+ // TODO: Initialize the "models" first. Then initialize the other
+ // controllers, which is currently "rpService", "ContentPolicy" etc.
+
// import main modules:
ScriptLoader.importModules([
"main/requestpolicy-service",
diff --git a/src/content/lib/utils/javascript.jsm b/src/content/lib/utils/javascript.jsm
new file mode 100644
index 0000000..cd547a8
--- /dev/null
+++ b/src/content/lib/utils/javascript.jsm
@@ -0,0 +1,44 @@
+/*
+ * ***** BEGIN LICENSE BLOCK *****
+ *
+ * RequestPolicy - A Firefox extension for control over cross-site requests.
+ * Copyright (c) 2015 Martin Kimmerle
+ *
+ * This program is free software: you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free Software
+ * Foundation, either version 3 of the License, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * ***** END LICENSE BLOCK *****
+ */
+
+/* exported JSUtils */
+this.EXPORTED_SYMBOLS = ["JSUtils"];
+
+//==============================================================================
+// JSUtils
+//==============================================================================
+
+var JSUtils = (function() {
+ "use strict";
+ let self = {};
+
+ self.arrayIncludes = function(array, searchElement) {
+ for (let element of array) {
+ if (element === searchElement) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ return self;
+}());
diff --git a/src/content/lib/utils/xul.jsm b/src/content/lib/utils/xul.jsm
index b4411c1..74ca999 100644
--- a/src/content/lib/utils/xul.jsm
+++ b/src/content/lib/utils/xul.jsm
@@ -33,6 +33,7 @@ let {ScriptLoader: {importModule}} = Cu.import(
"chrome://rpcontinued/content/lib/script-loader.jsm", {});
let {Logger} = importModule("lib/logger");
let {StringUtils} = importModule("lib/utils/strings");
+let {JSUtils} = importModule("lib/utils/javascript");
let {C} = importModule("lib/utils/constants");
//==============================================================================
@@ -323,3 +324,77 @@ XULUtils.removeTreeElementsFromWindow = function(aWin, aTreeName) {
}
}
};
+
+XULUtils.keyboardShortcuts = (function() {
+ let self = {};
+
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XUL/Attribute/modifiers
+ // See also the SDK Hotkeys API
+ // https://developer.mozilla.org/en-US/Add-ons/SDK/High-Level_APIs/hotkeys
+ const VALID_MODIFIERS = [
+ "shift",
+ "alt",
+ "meta",
+ "control",
+ "accel"
+ ];
+
+ function error(msg) {
+ return {success: false, errorMessage: msg};
+ }
+
+ function success(returnValue) {
+ returnValue.success = true;
+ return returnValue;
+ }
+
+ function isValidModifier(aModifier) {
+ return JSUtils.arrayIncludes(VALID_MODIFIERS, aModifier);
+ }
+
+ let keyRegEx = /^[a-z]$/;
+
+ function _getKeyAttributesFromCombo(aCombo) {
+ if (typeof aCombo !== "string") {
+ return error("Not a string!");
+ }
+ if (aCombo === "") {
+ return error("The string must not be empty.");
+ }
+
+ let parts = aCombo.split(" ");
+ // Take the last element as the key
+ let key = parts.slice(-1)[0];
+ // Take all elements except the last one as the modifiers.
+ let modifiers = parts.slice(0, -1);
+ // Remove duplicates
+ modifiers = [...new Set(modifiers)];
+
+ for (let modifier of modifiers) {
+ if (false === isValidModifier(modifier)) {
+ return error("Invalid modifier: \"" + modifier + "\"");
+ }
+ }
+
+ if (!keyRegEx.test(key)) {
+ return error("Invalid key: \"" + key + "\"");
+ }
+
+ return success({
+ modifiers: modifiers.join(" "),
+ key: key
+ });
+ }
+
+ /**
+ * Check if the <key modifiers="..."> string is valid.
+ *
+ * @param {string} aCombo
+ * @return {Object}
+ */
+ self.getKeyAttributesFromCombo = function(aCombo) {
+ return _getKeyAttributesFromCombo(aCombo);
+ };
+
+ return self;
+}());
diff --git a/src/content/models/windows.jsm b/src/content/models/windows.jsm
index 0c04937..9645c48 100644
--- a/src/content/models/windows.jsm
+++ b/src/content/models/windows.jsm
@@ -127,8 +127,12 @@ var Windows = (function() {
function onEvent(eventType, window) {
if (topicsToListeners.has(eventType)) {
- let listeners = topicsToListeners.get(eventType);
- for (let listener of listeners.values()) {
+ let listeners = [...topicsToListeners.get(eventType)];
+ if (eventType === "unload") {
+ // the most recent listeners should be called first
+ listeners.reverse();
+ }
+ for (let listener of listeners) {
listener.call(null, window);
}
}
diff --git a/src/content/ui/xul-trees.js b/src/content/ui/xul-trees.js
index f063791..da2b469 100644
--- a/src/content/ui/xul-trees.js
+++ b/src/content/ui/xul-trees.js
@@ -95,18 +95,7 @@ exports.mainTree = [
parent: {special: {type: "__window__"}},
tag: "keyset",
- attributes: {id: "rpcontinuedKeyset"},
- children: [
- {
- tag: "key",
- // "oncommand" (or "command") is required! See
- // https://stackoverflow.com/questions/16779316/how-to-set-an-xul-key-dynamically-and-securely/16786770#16786770
- attributes: {key: "r",
- modifiers: "alt shift",
- oncommand: "void(0);"},
- events: {command: ["overlay", "openMenu"]}
- }
- ]
+ attributes: {id: "rpcontinuedKeyset"}
},
{
diff --git a/tests/marionette/rp_puppeteer/tests/test_menu.py b/tests/marionette/rp_puppeteer/tests/test_menu.py
index 78dd100..fe198de 100644
--- a/tests/marionette/rp_puppeteer/tests/test_menu.py
+++ b/tests/marionette/rp_puppeteer/tests/test_menu.py
@@ -14,11 +14,16 @@ class TestMenu(RequestPolicyTestCase):
super(RequestPolicyTestCase, self).tearDown()
def test_open_close(self):
- self.assertFalse(self.menu.is_open())
- self.menu.open()
- self.assertTrue(self.menu.is_open())
- self.menu.close()
- self.assertFalse(self.menu.is_open())
+ def test(trigger):
+ self.assertFalse(self.menu.is_open())
+ self.menu.open(trigger=trigger)
+ self.assertTrue(self.menu.is_open())
+ self.menu.close()
+ self.assertFalse(self.menu.is_open())
+
+ test("api")
+ test("button")
+ test("shortcut")
def test_total_num_requests(self):
with self.marionette.using_context("content"):
diff --git a/tests/marionette/rp_puppeteer/ui/menu.py b/tests/marionette/rp_puppeteer/ui/menu.py
index f243513..52ec0c7 100644
--- a/tests/marionette/rp_puppeteer/ui/menu.py
+++ b/tests/marionette/rp_puppeteer/ui/menu.py
@@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from firefox_puppeteer.base import BaseLib
+from firefox_puppeteer.ui.windows import Windows
from marionette_driver.wait import Wait
import re
@@ -20,8 +21,18 @@ class Menu(BaseLib):
match = re.match(r"^(\d+) \((\d+)\+(\d+)\)$", text)
return int(match.group(1))
- def open(self):
- self._ensure_popup_state("open")
+ def open(self, trigger="api"):
+ if trigger == "button":
+ self._toolbar_button.click()
+ Wait(self.marionette).until(lambda _: self._popup_state == "open")
+ elif trigger == "shortcut":
+ window = Windows(lambda: self.marionette).current
+ window.send_shortcut("r", alt=True, shift=True)
+ Wait(self.marionette).until(lambda _: self._popup_state == "open")
+ elif trigger == "api":
+ self._ensure_popup_state("open")
+ else:
+ raise ValueError("Unknown opening method: \"{}\"".format(trigger))
def is_open(self):
return self._popup_state == "open"
diff --git a/tests/xpcshell/test_jsutils.js b/tests/xpcshell/test_jsutils.js
new file mode 100644
index 0000000..25169d9
--- /dev/null
+++ b/tests/xpcshell/test_jsutils.js
@@ -0,0 +1,20 @@
+Cu.import("chrome://rpcontinued/content/lib/utils/javascript.jsm");
+
+function run_test() {
+ "use strict";
+
+ test_arrayIncludes();
+}
+
+function test_arrayIncludes() {
+ "use strict";
+
+ let {arrayIncludes} = JSUtils;
+
+ strictEqual(true, arrayIncludes(["a", "b"], "a"));
+ strictEqual(true, arrayIncludes(["a", "b"], "b"));
+ strictEqual(false, arrayIncludes(["a", "b"], "c"));
+
+ strictEqual(true, arrayIncludes([0], 0));
+ strictEqual(false, arrayIncludes([0], "0"));
+}
diff --git a/tests/xpcshell/test_xulutils_keyboard_shortcuts.js b/tests/xpcshell/test_xulutils_keyboard_shortcuts.js
new file mode 100644
index 0000000..2c4763e
--- /dev/null
+++ b/tests/xpcshell/test_xulutils_keyboard_shortcuts.js
@@ -0,0 +1,66 @@
+Cu.import("chrome://rpcontinued/content/lib/utils/xul.jsm");
+
+function run_test() {
+ "use strict";
+
+ test_getKeyAttributesFromCombo();
+}
+
+function test_getKeyAttributesFromCombo() {
+ "use strict";
+
+ function testSuccess(combo, expectedModifiers, expectedKey) {
+ let rv = XULUtils.keyboardShortcuts.getKeyAttributesFromCombo(combo);
+ strictEqual(true, rv.success);
+ strictEqual(expectedModifiers, rv.modifiers);
+ strictEqual(expectedKey, rv.key);
+ }
+
+ function testFailure(combo) {
+ let rv = XULUtils.keyboardShortcuts.getKeyAttributesFromCombo(combo);
+ strictEqual(false, rv.success);
+ strictEqual("string", typeof rv.errorMessage);
+ }
+
+ testSuccess("shift a", "shift", "a");
+ testSuccess("alt b", "alt", "b");
+ testSuccess("meta c", "meta", "c");
+ testSuccess("control d", "control", "d");
+ testSuccess("accel e", "accel", "e");
+
+ // multiple modifiers
+ testSuccess("alt shift f", "alt shift", "f");
+ testSuccess("control alt g", "control alt", "g");
+ testSuccess("control alt shift h", "control alt shift", "h");
+
+ // no modifier
+ testSuccess("r", "", "r");
+
+ // Disallow these modifiers. They are allowed for <key> but
+ // not allowed in the SDK Hotkeys API.
+ testFailure("os r");
+ testFailure("access r");
+ testFailure("any r");
+
+ // redundant modifier
+ testSuccess("alt alt r", "alt", "r");
+ testSuccess("alt shift alt r", "alt shift", "r");
+
+ // invalid modifiers
+ testFailure("ctrl r");
+ testFailure("foobar r");
+
+ // No key
+ testFailure("alt");
+ testFailure("control shift");
+ // invalid key
+ testFailure("alt _");
+ testFailure("alt 0");
+ testFailure("alt rr");
+
+ // empty string
+ testFailure("");
+ // invalid separator
+ testFailure("alt,x");
+ testFailure("alt-y");
+}
diff --git a/tests/xpcshell/xpcshell.ini b/tests/xpcshell/xpcshell.ini
index d5bcf70..ca598ab 100644
--- a/tests/xpcshell/xpcshell.ini
+++ b/tests/xpcshell/xpcshell.ini
@@ -3,9 +3,11 @@ head = head_loadrp.js head_utils.js
tail =
[test_fileutil.js]
+[test_jsutils.js]
[test_map_of_sets.js]
[test_oldrules.js]
;[test_policymanager.js]
[test_policystorage.js]
;[test_subscription.js]
[test_wrap_function.js]
+[test_xulutils_keyboard_shortcuts.js]
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mozext/requestpolicy.git
More information about the Pkg-mozext-commits
mailing list