Bug 1246035 - Add support for _execute_page_action r=kmag

MozReview-Commit-ID: LPQAC7uJTkr
This commit is contained in:
Matthew Wein 2016-03-14 14:54:57 +01:00
Родитель a5bb09fb90
Коммит 863d9f6ce1
7 изменённых файлов: 216 добавлений и 46 удалений

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

@ -7,6 +7,7 @@
"EventEmitter": true,
"IconDetails": true,
"makeWidgetId": true,
"pageActionFor: true,
"PanelPopup": true,
"TabContext": true,
"ViewPopup": true,

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

@ -17,10 +17,6 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// WeakMap[Extension -> BrowserAction]
var browserActionMap = new WeakMap();
function browserActionOf(extension) {
return browserActionMap.get(extension);
}
// Responsible for the browser_action section of the manifest as well
// as the associated popup.
function BrowserAction(options, extension) {
@ -203,6 +199,12 @@ BrowserAction.prototype = {
},
};
BrowserAction.for = (extension) => {
return browserActionMap.get(extension);
};
global.browserActionFor = BrowserAction.for;
/* eslint-disable mozilla/balanced-listeners */
extensions.on("manifest_browser_action", (type, directive, extension, manifest) => {
let browserAction = new BrowserAction(manifest.browser_action, extension);
@ -226,20 +228,20 @@ extensions.registerSchemaAPI("browserAction", null, (extension, context) => {
let tab = TabManager.activeTab;
fire(TabManager.convert(extension, tab));
};
browserActionOf(extension).on("click", listener);
BrowserAction.for(extension).on("click", listener);
return () => {
browserActionOf(extension).off("click", listener);
BrowserAction.for(extension).off("click", listener);
};
}).api(),
enable: function(tabId) {
let tab = tabId !== null ? TabManager.getTab(tabId) : null;
browserActionOf(extension).setProperty(tab, "enabled", true);
BrowserAction.for(extension).setProperty(tab, "enabled", true);
},
disable: function(tabId) {
let tab = tabId !== null ? TabManager.getTab(tabId) : null;
browserActionOf(extension).setProperty(tab, "enabled", false);
BrowserAction.for(extension).setProperty(tab, "enabled", false);
},
setTitle: function(details) {
@ -250,30 +252,30 @@ extensions.registerSchemaAPI("browserAction", null, (extension, context) => {
if (tab && title == "") {
title = null;
}
browserActionOf(extension).setProperty(tab, "title", title);
BrowserAction.for(extension).setProperty(tab, "title", title);
},
getTitle: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let title = browserActionOf(extension).getProperty(tab, "title");
let title = BrowserAction.for(extension).getProperty(tab, "title");
return Promise.resolve(title);
},
setIcon: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let icon = IconDetails.normalize(details, extension, context);
browserActionOf(extension).setProperty(tab, "icon", icon);
BrowserAction.for(extension).setProperty(tab, "icon", icon);
return Promise.resolve();
},
setBadgeText: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
browserActionOf(extension).setProperty(tab, "badgeText", details.text);
BrowserAction.for(extension).setProperty(tab, "badgeText", details.text);
},
getBadgeText: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let text = browserActionOf(extension).getProperty(tab, "badgeText");
let text = BrowserAction.for(extension).getProperty(tab, "badgeText");
return Promise.resolve(text);
},
@ -285,23 +287,23 @@ extensions.registerSchemaAPI("browserAction", null, (extension, context) => {
// For internal consistency, we currently resolve both relative to the
// calling context.
let url = details.popup && context.uri.resolve(details.popup);
browserActionOf(extension).setProperty(tab, "popup", url);
BrowserAction.for(extension).setProperty(tab, "popup", url);
},
getPopup: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let popup = browserActionOf(extension).getProperty(tab, "popup");
let popup = BrowserAction.for(extension).getProperty(tab, "popup");
return Promise.resolve(popup);
},
setBadgeBackgroundColor: function(details) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
browserActionOf(extension).setProperty(tab, "badgeBackgroundColor", details.color);
BrowserAction.for(extension).setProperty(tab, "badgeBackgroundColor", details.color);
},
getBadgeBackgroundColor: function(details, callback) {
let tab = details.tabId !== null ? TabManager.getTab(details.tabId) : null;
let color = browserActionOf(extension).getProperty(tab, "badgeBackgroundColor");
let color = BrowserAction.for(extension).getProperty(tab, "badgeBackgroundColor");
return Promise.resolve(color);
},
},

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

@ -15,10 +15,17 @@ const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// WeakMap[Extension -> CommandList]
var commandsMap = new WeakMap();
function CommandList(commandsObj, extensionID) {
this.commands = this.loadCommandsFromManifest(commandsObj);
this.keysetID = `ext-keyset-id-${makeWidgetId(extensionID)}`;
function CommandList(manifest, extension) {
this.extension = extension;
this.id = makeWidgetId(extension.id);
this.windowOpenListener = null;
// Map[{String} commandName -> {Object} commandProperties]
this.commands = this.loadCommandsFromManifest(manifest);
// WeakMap[Window -> <xul:keyset>]
this.keysetsMap = new WeakMap();
this.register();
EventEmitter.decorate(this);
}
@ -30,11 +37,13 @@ CommandList.prototype = {
*/
register() {
for (let window of WindowListManager.browserWindows()) {
this.registerKeysToDocument(window.document);
this.registerKeysToDocument(window);
}
this.windowOpenListener = (window) => {
this.registerKeysToDocument(window.document);
if (!this.keysetsMap.has(window)) {
this.registerKeysToDocument(window);
}
};
WindowListManager.addOpenListener(this.windowOpenListener);
@ -46,9 +55,8 @@ CommandList.prototype = {
*/
unregister() {
for (let window of WindowListManager.browserWindows()) {
let keyset = window.document.getElementById(this.keysetID);
if (keyset) {
keyset.remove();
if (this.keysetsMap.has(window)) {
this.keysetsMap.get(window).remove();
}
}
@ -57,15 +65,15 @@ CommandList.prototype = {
/**
* Creates a Map from commands for each command in the manifest.commands object.
* @param {Object} commandsObj The manifest.commands JSON object.
* @param {Object} manifest The manifest JSON object.
*/
loadCommandsFromManifest(commandsObj) {
loadCommandsFromManifest(manifest) {
let commands = new Map();
// For Windows, chrome.runtime expects 'win' while chrome.commands
// expects 'windows'. We can special case this for now.
let os = PlatformInfo.os == "win" ? "windows" : PlatformInfo.os;
for (let name of Object.keys(commandsObj)) {
let command = commandsObj[name];
for (let name of Object.keys(manifest.commands)) {
let command = manifest.commands[name];
commands.set(name, {
description: command.description,
shortcut: command.suggested_key[os] || command.suggested_key.default,
@ -76,16 +84,18 @@ CommandList.prototype = {
/**
* Registers the commands to a document.
* @param {Document} doc The XUL document to insert the Keyset.
* @param {ChromeWindow} window The XUL window to insert the Keyset.
*/
registerKeysToDocument(doc) {
registerKeysToDocument(window) {
let doc = window.document;
let keyset = doc.createElementNS(XUL_NS, "keyset");
keyset.id = this.keysetID;
keyset.id = `ext-keyset-id-${this.id}`;
this.commands.forEach((command, name) => {
let keyElement = this.buildKey(doc, name, command.shortcut);
keyset.appendChild(keyElement);
});
doc.documentElement.appendChild(keyset);
this.keysetsMap.set(window, keyset);
},
/**
@ -110,7 +120,12 @@ CommandList.prototype = {
// We remove all references to the key elements when the extension is shutdown,
// therefore the listeners for these elements will be garbage collected.
keyElement.addEventListener("command", (event) => {
this.emit("command", name);
if (name == "_execute_page_action") {
let win = event.target.ownerDocument.defaultView;
pageActionFor(this.extension).triggerAction(win);
} else {
this.emit("command", name);
}
});
/* eslint-enable mozilla/balanced-listeners */
@ -195,7 +210,7 @@ CommandList.prototype = {
/* eslint-disable mozilla/balanced-listeners */
extensions.on("manifest_commands", (type, directive, extension, manifest) => {
commandsMap.set(extension, new CommandList(manifest.commands, extension.id));
commandsMap.set(extension, new CommandList(manifest, extension));
});
extensions.on("shutdown", (type, extension) => {

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

@ -2,6 +2,7 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
Cu.import("resource://gre/modules/Task.jsm");
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
var {
EventManager,
@ -10,7 +11,6 @@ var {
// WeakMap[Extension -> PageAction]
var pageActionMap = new WeakMap();
// Handles URL bar icons, including the |page_action| manifest entry
// and associated API.
function PageAction(options, extension) {
@ -123,6 +123,19 @@ PageAction.prototype = {
return this.buttons.get(window);
},
/**
* Triggers this page action for the given window, with the same effects as
* if it were clicked by a user.
*
* This has no effect if the page action is hidden for the selected tab.
*/
triggerAction(window) {
let pageAction = pageActionMap.get(this.extension);
if (pageAction.getProperty(window.gBrowser.selectedTab, "show")) {
pageAction.handleClick(window);
}
},
// Handles a click event on the page action button for the given
// window.
// If the page action has a |popup| property, a panel is opened to
@ -163,11 +176,6 @@ PageAction.prototype = {
},
};
PageAction.for = extension => {
return pageActionMap.get(extension);
};
/* eslint-disable mozilla/balanced-listeners */
extensions.on("manifest_page_action", (type, directive, extension, manifest) => {
let pageAction = new PageAction(manifest.page_action, extension);
@ -182,6 +190,11 @@ extensions.on("shutdown", (type, extension) => {
});
/* eslint-enable mozilla/balanced-listeners */
PageAction.for = extension => {
return pageActionMap.get(extension);
};
global.pageActionFor = PageAction.for;
extensions.registerSchemaAPI("pageAction", null, (extension, context) => {
return {

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

@ -24,6 +24,7 @@ support-files =
[browser_ext_browserAction_popup.js]
[browser_ext_popup_api_injection.js]
[browser_ext_contextMenus.js]
[browser_ext_commands_execute_page_action.js]
[browser_ext_commands_getAll.js]
[browser_ext_commands_onCommand.js]
[browser_ext_getViews.js]

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

@ -0,0 +1,135 @@
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* test_execute_page_action_without_popup() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"commands": {
"_execute_page_action": {
"suggested_key": {
"default": "Alt+Shift+J",
},
},
"send-keys-command": {
"suggested_key": {
"default": "Alt+Shift+3",
},
},
},
"page_action": {},
},
background: function() {
let isShown = false;
browser.commands.onCommand.addListener((commandName) => {
if (commandName == "_execute_page_action") {
browser.test.fail(`The onCommand listener should never fire for ${commandName}.`);
} else if (commandName == "send-keys-command") {
if (!isShown) {
isShown = true;
browser.tabs.query({currentWindow: true, active: true}, tabs => {
tabs.forEach(tab => {
browser.pageAction.show(tab.id);
});
browser.test.sendMessage("send-keys");
});
}
}
});
browser.pageAction.onClicked.addListener(() => {
browser.test.assertTrue(isShown, "The onClicked event should fire if the page action is shown.");
browser.test.notifyPass("page-action-without-popup");
});
browser.test.sendMessage("send-keys");
},
});
yield extension.startup();
extension.onMessage("send-keys", () => {
EventUtils.synthesizeKey("j", {altKey: true, shiftKey: true});
EventUtils.synthesizeKey("3", {altKey: true, shiftKey: true});
});
yield extension.awaitFinish("page-action-without-popup");
yield extension.unload();
});
add_task(function* test_execute_page_action_with_popup() {
let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}"></script></head><body>Test Popup</body></html>`;
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"commands": {
"_execute_page_action": {
"suggested_key": {
"default": "Alt+Shift+J",
},
},
"send-keys-command": {
"suggested_key": {
"default": "Alt+Shift+3",
},
},
},
"page_action": {
"default_popup": "popup.html",
},
},
files: {
"popup.html": scriptPage("popup.js"),
"popup.js": function() {
browser.runtime.sendMessage("popup-opened");
},
},
background: function() {
let isShown = false;
browser.commands.onCommand.addListener((message) => {
if (message == "_execute_page_action") {
browser.test.fail(`The onCommand listener should never fire for ${message}.`);
}
if (message == "send-keys-command") {
if (!isShown) {
isShown = true;
browser.tabs.query({currentWindow: true, active: true}, tabs => {
tabs.forEach(tab => {
browser.pageAction.show(tab.id);
});
browser.test.sendMessage("send-keys");
});
}
}
});
browser.pageAction.onClicked.addListener(() => {
browser.test.fail(`The onClicked listener should never fire when the pageAction has a popup.`);
});
browser.runtime.onMessage.addListener(msg => {
browser.test.assertEq(msg, "popup-opened", "expected popup opened");
browser.test.assertTrue(isShown, "The onClicked event should fire if the page action is shown.");
browser.test.notifyPass("page-action-with-popup");
});
browser.test.sendMessage("send-keys");
},
});
yield extension.startup();
extension.onMessage("send-keys", () => {
EventUtils.synthesizeKey("j", {altKey: true, shiftKey: true});
EventUtils.synthesizeKey("3", {altKey: true, shiftKey: true});
});
yield extension.awaitFinish("page-action-with-popup");
yield extension.unload();
});

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

@ -2,7 +2,7 @@
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
add_task(function* () {
add_task(function* test_user_defined_commands() {
// Create a window before the extension is loaded.
let win1 = yield BrowserTestUtils.openNewBrowserWindow();
yield BrowserTestUtils.loadURI(win1.gBrowser.selectedBrowser, "about:robots");
@ -10,7 +10,6 @@ add_task(function* () {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
"name": "Commands Extension",
"commands": {
"toggle-feature-using-alt-shift-3": {
"suggested_key": {
@ -27,8 +26,8 @@ add_task(function* () {
},
background: function() {
browser.commands.onCommand.addListener((message) => {
browser.test.sendMessage("oncommand", message);
browser.commands.onCommand.addListener((commandName) => {
browser.test.sendMessage("oncommand", commandName);
});
browser.test.sendMessage("ready");
},
@ -53,10 +52,12 @@ add_task(function* () {
// Confirm the keysets have been added to both windows.
let keysetID = `ext-keyset-id-${makeWidgetId(extension.id)}`;
let keyset = win1.document.getElementById(keysetID);
is(keyset.childNodes.length, 2, "Expected keyset to exist and have 2 children");
ok(keyset != null, "Expected keyset to exist");
is(keyset.childNodes.length, 2, "Expected keyset to have 2 children");
keyset = win2.document.getElementById(keysetID);
is(keyset.childNodes.length, 2, "Expected keyset to exist and have 2 children");
ok(keyset != null, "Expected keyset to exist");
is(keyset.childNodes.length, 2, "Expected keyset to have 2 children");
// Confirm that the commands are registered to both windows.
yield focusWindow(win1);
@ -84,3 +85,5 @@ add_task(function* () {
SimpleTest.endMonitorConsole();
yield waitForConsole;
});