Bug 1350522: Part 7 - Merge pageAction/browserAction/sidebarAction/commands helper classes into API instances. r=aswan

MozReview-Commit-ID: 6aRQ1uybAmg
This commit is contained in:
Kris Maglione
2017-03-26 18:42:59 -07:00
parent ef300362e4
commit 52b67bf633
5 changed files with 201 additions and 238 deletions

View File

@@ -48,10 +48,15 @@ XPCOMUtils.defineLazyGetter(this, "browserAreas", () => {
}; };
}); });
// Responsible for the browser_action section of the manifest as well this.browserAction = class extends ExtensionAPI {
// as the associated popup. static for(extension) {
function BrowserAction(options, extension) { return browserActionMap.get(extension);
this.extension = extension; }
onManifestEntry(entryName) {
let {extension} = this;
let options = extension.manifest.browser_action;
let widgetId = makeWidgetId(extension.id); let widgetId = makeWidgetId(extension.id);
this.id = `${widgetId}-browser-action`; this.id = `${widgetId}-browser-action`;
@@ -83,9 +88,20 @@ function BrowserAction(options, extension) {
extension); extension);
EventEmitter.decorate(this); EventEmitter.decorate(this);
}
BrowserAction.prototype = { this.build();
browserActionMap.set(extension, this);
}
onShutdown(reason) {
browserActionMap.delete(this.extension);
this.tabContext.shutdown();
CustomizableUI.destroyWidget(this.id);
this.clearPopup();
}
build() { build() {
let widget = CustomizableUI.createWidget({ let widget = CustomizableUI.createWidget({
id: this.id, id: this.id,
@@ -160,7 +176,7 @@ BrowserAction.prototype = {
(evt, tab) => { this.updateWindow(tab.ownerGlobal); }); (evt, tab) => { this.updateWindow(tab.ownerGlobal); });
this.widget = widget; this.widget = widget;
}, }
/** /**
* Triggers this browser action for the given window, with the same effects as * Triggers this browser action for the given window, with the same effects as
@@ -168,8 +184,10 @@ BrowserAction.prototype = {
* *
* This has no effect if the browser action is disabled for, or not * This has no effect if the browser action is disabled for, or not
* present in, the given window. * present in, the given window.
*
* @param {Window} window
*/ */
triggerAction: Task.async(function* (window) { async triggerAction(window) {
let popup = ViewPopup.for(this.extension, window); let popup = ViewPopup.for(this.extension, window);
if (popup) { if (popup) {
popup.closePopup(); popup.closePopup();
@@ -188,7 +206,7 @@ BrowserAction.prototype = {
// Google Chrome onClicked extension API. // Google Chrome onClicked extension API.
if (this.getProperty(tab, "popup")) { if (this.getProperty(tab, "popup")) {
if (this.widget.areaType == CustomizableUI.TYPE_MENU_PANEL) { if (this.widget.areaType == CustomizableUI.TYPE_MENU_PANEL) {
yield window.PanelUI.show(); await window.PanelUI.show();
} }
let event = new window.CustomEvent("command", {bubbles: true, cancelable: true}); let event = new window.CustomEvent("command", {bubbles: true, cancelable: true});
@@ -196,7 +214,7 @@ BrowserAction.prototype = {
} else { } else {
this.emit("click"); this.emit("click");
} }
}), }
handleEvent(event) { handleEvent(event) {
let button = event.target; let button = event.target;
@@ -279,7 +297,7 @@ BrowserAction.prototype = {
} }
break; break;
} }
}, }
/** /**
* Returns a potentially pre-loaded popup for the given URL in the given * Returns a potentially pre-loaded popup for the given URL in the given
@@ -315,7 +333,7 @@ BrowserAction.prototype = {
let fixedWidth = this.widget.areaType == CustomizableUI.TYPE_MENU_PANEL; let fixedWidth = this.widget.areaType == CustomizableUI.TYPE_MENU_PANEL;
return new ViewPopup(this.extension, window, popupURL, this.browserStyle, fixedWidth, blockParser); return new ViewPopup(this.extension, window, popupURL, this.browserStyle, fixedWidth, blockParser);
}, }
/** /**
* Clears any pending pre-loaded popup and related timeouts. * Clears any pending pre-loaded popup and related timeouts.
@@ -330,7 +348,7 @@ BrowserAction.prototype = {
this.pendingPopup = null; this.pendingPopup = null;
} }
this.tabToRevokeDuringClearPopup = null; this.tabToRevokeDuringClearPopup = null;
}, }
/** /**
* Clears any pending timeouts to clear stale, pre-loaded popups. * Clears any pending timeouts to clear stale, pre-loaded popups.
@@ -344,7 +362,7 @@ BrowserAction.prototype = {
clearTimeout(this.pendingPopupTimeout); clearTimeout(this.pendingPopupTimeout);
this.pendingPopupTimeout = null; this.pendingPopupTimeout = null;
} }
}, }
// Update the toolbar button |node| with the tab context data // Update the toolbar button |node| with the tab context data
// in |tabData|. // in |tabData|.
@@ -402,7 +420,7 @@ BrowserAction.prototype = {
--webextension-toolbar-image: url("${IconDetails.escapeUrl(icon)}"); --webextension-toolbar-image: url("${IconDetails.escapeUrl(icon)}");
--webextension-toolbar-image-2x: url("${getIcon(baseSize * 2)}"); --webextension-toolbar-image-2x: url("${getIcon(baseSize * 2)}");
`); `);
}, }
// Update the toolbar button for a given window. // Update the toolbar button for a given window.
updateWindow(window) { updateWindow(window) {
@@ -411,7 +429,7 @@ BrowserAction.prototype = {
let tab = window.gBrowser.selectedTab; let tab = window.gBrowser.selectedTab;
this.updateButton(widget.node, this.tabContext.get(tab)); this.updateButton(widget.node, this.tabContext.get(tab));
} }
}, }
// Update the toolbar button when the extension changes the icon, // Update the toolbar button when the extension changes the icon,
// title, badge, etc. If it only changes a parameter for a single // title, badge, etc. If it only changes a parameter for a single
@@ -426,7 +444,7 @@ BrowserAction.prototype = {
this.updateWindow(window); this.updateWindow(window);
} }
} }
}, }
// tab is allowed to be null. // tab is allowed to be null.
// prop should be one of "icon", "title", "badgeText", "popup", or "badgeBackgroundColor". // prop should be one of "icon", "title", "badgeText", "popup", or "badgeBackgroundColor".
@@ -440,7 +458,7 @@ BrowserAction.prototype = {
} }
this.updateOnChange(tab); this.updateOnChange(tab);
}, }
// tab is allowed to be null. // tab is allowed to be null.
// prop should be one of "title", "badgeText", "popup", or "badgeBackgroundColor". // prop should be one of "title", "badgeText", "popup", or "badgeBackgroundColor".
@@ -449,44 +467,13 @@ BrowserAction.prototype = {
return this.defaults[prop]; return this.defaults[prop];
} }
return this.tabContext.get(tab)[prop]; return this.tabContext.get(tab)[prop];
},
shutdown() {
this.tabContext.shutdown();
CustomizableUI.destroyWidget(this.id);
this.clearPopup();
},
};
BrowserAction.for = (extension) => {
return browserActionMap.get(extension);
};
global.browserActionFor = BrowserAction.for;
this.browserAction = class extends ExtensionAPI {
onManifestEntry(entryName) {
let {extension} = this;
let {manifest} = extension;
this.browserAction = new BrowserAction(manifest.browser_action, extension);
this.browserAction.build();
browserActionMap.set(extension, this.browserAction);
}
onShutdown(reason) {
let {extension} = this;
browserActionMap.delete(extension);
this.browserAction.shutdown();
} }
getAPI(context) { getAPI(context) {
let {extension} = context; let {extension} = context;
let {tabManager} = extension; let {tabManager} = extension;
let {browserAction} = this; let browserAction = this;
function getTab(tabId) { function getTab(tabId) {
if (tabId !== null) { if (tabId !== null) {
@@ -594,3 +581,6 @@ this.browserAction = class extends ExtensionAPI {
}; };
} }
}; };
global.browserActionFor = this.browserAction.for;

View File

@@ -9,22 +9,27 @@ var {
var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
function CommandList(manifest, extension) { this.commands = class extends ExtensionAPI {
this.extension = extension; onManifestEntry(entryName) {
let {extension} = this;
this.id = makeWidgetId(extension.id); this.id = makeWidgetId(extension.id);
this.windowOpenListener = null; this.windowOpenListener = null;
// Map[{String} commandName -> {Object} commandProperties] // Map[{String} commandName -> {Object} commandProperties]
this.commands = this.loadCommandsFromManifest(manifest); this.commands = this.loadCommandsFromManifest(this.extension.manifest);
// WeakMap[Window -> <xul:keyset>] // WeakMap[Window -> <xul:keyset>]
this.keysetsMap = new WeakMap(); this.keysetsMap = new WeakMap();
this.register(); this.register();
EventEmitter.decorate(this); EventEmitter.decorate(this);
} }
onShutdown(reason) {
this.unregister();
}
CommandList.prototype = {
/** /**
* Registers the commands to all open windows and to any which * Registers the commands to all open windows and to any which
* are later created. * are later created.
@@ -41,7 +46,7 @@ CommandList.prototype = {
}; };
windowTracker.addOpenListener(this.windowOpenListener); windowTracker.addOpenListener(this.windowOpenListener);
}, }
/** /**
* Unregisters the commands from all open windows and stops commands * Unregisters the commands from all open windows and stops commands
@@ -55,7 +60,7 @@ CommandList.prototype = {
} }
windowTracker.removeOpenListener(this.windowOpenListener); windowTracker.removeOpenListener(this.windowOpenListener);
}, }
/** /**
* Creates a Map from commands for each command in the manifest.commands object. * Creates a Map from commands for each command in the manifest.commands object.
@@ -78,7 +83,7 @@ CommandList.prototype = {
}); });
} }
return commands; return commands;
}, }
/** /**
* Registers the commands to a document. * Registers the commands to a document.
@@ -96,7 +101,7 @@ CommandList.prototype = {
}); });
doc.documentElement.appendChild(keyset); doc.documentElement.appendChild(keyset);
this.keysetsMap.set(window, keyset); this.keysetsMap.set(window, keyset);
}, }
/** /**
* Builds a XUL Key element and attaches an onCommand listener which * Builds a XUL Key element and attaches an onCommand listener which
@@ -141,7 +146,7 @@ CommandList.prototype = {
/* eslint-enable mozilla/balanced-listeners */ /* eslint-enable mozilla/balanced-listeners */
return keyElement; return keyElement;
}, }
/** /**
* Builds a XUL Key element from the provided shortcut. * Builds a XUL Key element from the provided shortcut.
@@ -171,7 +176,7 @@ CommandList.prototype = {
} }
return keyElement; return keyElement;
}, }
/** /**
* Determines the corresponding XUL keycode from the given chrome key. * Determines the corresponding XUL keycode from the given chrome key.
@@ -188,7 +193,7 @@ CommandList.prototype = {
*/ */
getKeycodeAttribute(chromeKey) { getKeycodeAttribute(chromeKey) {
return `VK${chromeKey.replace(/([A-Z])/g, "_$&").toUpperCase()}`; return `VK${chromeKey.replace(/([A-Z])/g, "_$&").toUpperCase()}`;
}, }
/** /**
* Determines the corresponding XUL modifiers from the chrome modifiers. * Determines the corresponding XUL modifiers from the chrome modifiers.
@@ -214,26 +219,13 @@ CommandList.prototype = {
return Array.from(chromeModifiers, modifier => { return Array.from(chromeModifiers, modifier => {
return modifiersMap[modifier]; return modifiersMap[modifier];
}).join(" "); }).join(" ");
},
};
this.commands = class extends ExtensionAPI {
onManifestEntry(entryName) {
let {extension} = this;
let {manifest} = extension;
this.commandList = new CommandList(manifest, extension);
}
onShutdown(reason) {
this.commandList.unregister();
} }
getAPI(context) { getAPI(context) {
return { return {
commands: { commands: {
getAll: () => { getAll: () => {
let commands = this.commandList.commands; let commands = this.commands;
return Promise.resolve(Array.from(commands, ([name, command]) => { return Promise.resolve(Array.from(commands, ([name, command]) => {
return ({ return ({
name, name,
@@ -246,9 +238,9 @@ this.commands = class extends ExtensionAPI {
let listener = (eventName, commandName) => { let listener = (eventName, commandName) => {
fire.async(commandName); fire.async(commandName);
}; };
this.commandList.on("command", listener); this.on("command", listener);
return () => { return () => {
this.commandList.off("command", listener); this.off("command", listener);
}; };
}).api(), }).api(),
}, },

View File

@@ -15,10 +15,15 @@ var {
// WeakMap[Extension -> PageAction] // WeakMap[Extension -> PageAction]
let pageActionMap = new WeakMap(); let pageActionMap = new WeakMap();
// Handles URL bar icons, including the |page_action| manifest entry this.pageAction = class extends ExtensionAPI {
// and associated API. static for(extension) {
function PageAction(options, extension) { return pageActionMap.get(extension);
this.extension = extension; }
onManifestEntry(entryName) {
let {extension} = this;
let options = extension.manifest.page_action;
this.id = makeWidgetId(extension.id) + "-page-action"; this.id = makeWidgetId(extension.id) + "-page-action";
this.tabManager = extension.tabManager; this.tabManager = extension.tabManager;
@@ -45,14 +50,28 @@ function PageAction(options, extension) {
this.buttons = new WeakMap(); this.buttons = new WeakMap();
EventEmitter.decorate(this); EventEmitter.decorate(this);
}
PageAction.prototype = { pageActionMap.set(extension, this);
}
onShutdown(reason) {
pageActionMap.delete(this.extension);
this.tabContext.shutdown();
for (let window of windowTracker.browserWindows()) {
if (this.buttons.has(window)) {
this.buttons.get(window).remove();
window.document.removeEventListener("popupshowing", this);
}
}
}
// Returns the value of the property |prop| for the given tab, where // Returns the value of the property |prop| for the given tab, where
// |prop| is one of "show", "title", "icon", "popup". // |prop| is one of "show", "title", "icon", "popup".
getProperty(tab, prop) { getProperty(tab, prop) {
return this.tabContext.get(tab)[prop]; return this.tabContext.get(tab)[prop];
}, }
// Sets the value of the property |prop| for the given tab to the // Sets the value of the property |prop| for the given tab to the
// given value, symmetrically to |getProperty|. // given value, symmetrically to |getProperty|.
@@ -69,7 +88,7 @@ PageAction.prototype = {
if (tab.selected) { if (tab.selected) {
this.updateButton(tab.ownerGlobal); this.updateButton(tab.ownerGlobal);
} }
}, }
// Updates the page action button in the given window to reflect the // Updates the page action button in the given window to reflect the
// properties of the currently selected tab: // properties of the currently selected tab:
@@ -111,7 +130,7 @@ PageAction.prototype = {
} }
button.hidden = !tabData.show; button.hidden = !tabData.show;
}, }
// Create an |image| node and add it to the |urlbar-icons| // Create an |image| node and add it to the |urlbar-icons|
// container in the given window. // container in the given window.
@@ -128,7 +147,7 @@ PageAction.prototype = {
document.getElementById("urlbar-icons").appendChild(button); document.getElementById("urlbar-icons").appendChild(button);
return button; return button;
}, }
// Returns the page action button for the given window, creating it if // Returns the page action button for the given window, creating it if
// it doesn't already exist. // it doesn't already exist.
@@ -139,7 +158,7 @@ PageAction.prototype = {
} }
return this.buttons.get(window); return this.buttons.get(window);
}, }
/** /**
* Triggers this page action for the given window, with the same effects as * Triggers this page action for the given window, with the same effects as
@@ -154,7 +173,7 @@ PageAction.prototype = {
if (pageAction.getProperty(window.gBrowser.selectedTab, "show")) { if (pageAction.getProperty(window.gBrowser.selectedTab, "show")) {
pageAction.handleClick(window); pageAction.handleClick(window);
} }
}, }
handleEvent(event) { handleEvent(event) {
const window = event.target.ownerGlobal; const window = event.target.ownerGlobal;
@@ -179,7 +198,7 @@ PageAction.prototype = {
} }
break; break;
} }
}, }
// Handles a click event on the page action button for the given // Handles a click event on the page action button for the given
// window. // window.
@@ -202,52 +221,20 @@ PageAction.prototype = {
} else { } else {
this.emit("click", tab); this.emit("click", tab);
} }
}, }
handleLocationChange(eventType, tab, fromBrowse) { handleLocationChange(eventType, tab, fromBrowse) {
if (fromBrowse) { if (fromBrowse) {
this.tabContext.clear(tab); this.tabContext.clear(tab);
} }
this.updateButton(tab.ownerGlobal); this.updateButton(tab.ownerGlobal);
},
shutdown() {
this.tabContext.shutdown();
for (let window of windowTracker.browserWindows()) {
if (this.buttons.has(window)) {
this.buttons.get(window).remove();
window.document.removeEventListener("popupshowing", this);
}
}
},
};
PageAction.for = extension => {
return pageActionMap.get(extension);
};
global.pageActionFor = PageAction.for;
this.pageAction = class extends ExtensionAPI {
onManifestEntry(entryName) {
let {extension} = this;
let {manifest} = extension;
this.pageAction = new PageAction(manifest.page_action, extension);
pageActionMap.set(extension, this.pageAction);
}
onShutdown(reason) {
pageActionMap.delete(this.extension);
this.pageAction.shutdown();
} }
getAPI(context) { getAPI(context) {
let {extension} = context; let {extension} = context;
const {tabManager} = extension; const {tabManager} = extension;
const {pageAction} = this; const pageAction = this;
return { return {
pageAction: { pageAction: {
@@ -315,3 +302,5 @@ this.pageAction = class extends ExtensionAPI {
}; };
} }
}; };
global.pageActionFor = this.pageAction.for;

View File

@@ -27,9 +27,17 @@ const sidebarURL = "chrome://browser/content/webext-panels.xul";
* Responsible for the sidebar_action section of the manifest as well * Responsible for the sidebar_action section of the manifest as well
* as the associated sidebar browser. * as the associated sidebar browser.
*/ */
class SidebarAction { this.sidebarAction = class extends ExtensionAPI {
constructor(options, extension) { static for(extension) {
this.extension = extension; return sidebarActionMap.get(extension);
}
onManifestEntry(entryName) {
let {extension} = this;
extension.once("ready", this.onReady.bind(this));
let options = extension.manifest.sidebar_action;
// Add the extension to the sidebar menu. The sidebar widget will copy // Add the extension to the sidebar menu. The sidebar widget will copy
// from that when it is viewed, so we shouldn't need to update that. // from that when it is viewed, so we shouldn't need to update that.
@@ -52,6 +60,40 @@ class SidebarAction {
this.createMenuItem(window, this.defaults); this.createMenuItem(window, this.defaults);
}; };
windowTracker.addOpenListener(this.windowOpenListener); windowTracker.addOpenListener(this.windowOpenListener);
sidebarActionMap.set(extension, this);
}
onReady() {
this.build();
}
onShutdown(reason) {
sidebarActionMap.delete(this.this);
this.tabContext.shutdown();
// Don't remove everything on app shutdown so session restore can handle
// restoring open sidebars.
if (reason === "APP_SHUTDOWN") {
return;
}
for (let window of windowTracker.browserWindows()) {
let {document, SidebarUI} = window;
if (SidebarUI.currentID === this.id) {
SidebarUI.hide();
}
let menu = document.getElementById(this.menuId);
if (menu) {
menu.remove();
}
let broadcaster = document.getElementById(this.id);
if (broadcaster) {
broadcaster.remove();
}
}
windowTracker.removeOpenListener(this.windowOpenListener);
} }
build() { build() {
@@ -238,25 +280,6 @@ class SidebarAction {
return this.tabContext.get(nativeTab)[prop]; return this.tabContext.get(nativeTab)[prop];
} }
shutdown() {
this.tabContext.shutdown();
for (let window of windowTracker.browserWindows()) {
let {document, SidebarUI} = window;
if (SidebarUI.currentID === this.id) {
SidebarUI.hide();
}
let menu = document.getElementById(this.menuId);
if (menu) {
menu.remove();
}
let broadcaster = document.getElementById(this.id);
if (broadcaster) {
broadcaster.remove();
}
}
windowTracker.removeOpenListener(this.windowOpenListener);
}
/** /**
* Triggers this sidebar action for the given window, with the same effects as * Triggers this sidebar action for the given window, with the same effects as
* if it were toggled via menu or toolbarbutton by a user. * if it were toggled via menu or toolbarbutton by a user.
@@ -269,44 +292,10 @@ class SidebarAction {
SidebarUI.toggle(this.id); SidebarUI.toggle(this.id);
} }
} }
}
SidebarAction.for = (extension) => {
return sidebarActionMap.get(extension);
};
global.sidebarActionFor = SidebarAction.for;
/* eslint-disable mozilla/balanced-listeners */
extensions.on("ready", (type, extension) => {
// We build sidebars during ready to ensure the background scripts are ready.
if (sidebarActionMap.has(extension)) {
sidebarActionMap.get(extension).build();
}
});
/* eslint-enable mozilla/balanced-listeners */
this.sidebarAction = class extends ExtensionAPI {
onManifestEntry(entryName) {
let {extension} = this;
let {manifest} = extension;
this.sidebarAction = new SidebarAction(manifest.sidebar_action, extension);
sidebarActionMap.set(extension, this.sidebarAction);
}
onShutdown(reason) {
// Don't remove everything on app shutdown so session restore can handle
// restoring open sidebars.
if (reason !== "APP_SHUTDOWN") {
this.sidebarAction.shutdown();
}
sidebarActionMap.delete(this.extension);
}
getAPI(context) { getAPI(context) {
let {extension} = context; let {extension} = context;
const {sidebarAction} = this; const sidebarAction = this;
function getTab(tabId) { function getTab(tabId) {
if (tabId !== null) { if (tabId !== null) {
@@ -368,3 +357,5 @@ this.sidebarAction = class extends ExtensionAPI {
}; };
} }
}; };
global.sidebarActionFor = this.sidebarAction.for;

View File

@@ -966,6 +966,7 @@ this.Extension = class extends ExtensionData {
await this.runManifest(this.manifest); await this.runManifest(this.manifest);
Management.emit("ready", this); Management.emit("ready", this);
this.emit("ready");
} catch (e) { } catch (e) {
dump(`Extension error: ${e.message} ${e.filename || e.fileName}:${e.lineNumber} :: ${e.stack || new Error().stack}\n`); dump(`Extension error: ${e.message} ${e.filename || e.fileName}:${e.lineNumber} :: ${e.stack || new Error().stack}\n`);
Cu.reportError(e); Cu.reportError(e);