Bug 1450388 Part 1 Refactor EventManager r=kmag

As we add more behaviors to EventManager, the signature of the constructor
is going to get really clumsy.  Head that off by converting it to take a
general parameters object.

This introduces a compatibility problem for existing webextension experiments,
put in a backward-compatibility shim for now.

MozReview-Commit-ID: 72QDfiwRm5j

--HG--
extra : rebase_source : 31c3fd561f373a5d75c4336de830aa5a2abfe797
This commit is contained in:
Andrew Swan 2018-03-14 14:52:44 -07:00
Родитель efcb51ab74
Коммит e71d1d5a82
42 изменённых файлов: 1386 добавлений и 1096 удалений

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

@ -33,21 +33,25 @@ this.devtools_network = class extends ExtensionAPI {
return {
devtools: {
network: {
onRequestFinished: new EventManager(context, "devtools.network.onRequestFinished", fire => {
let onFinished = (data) => {
const loader = new ChildNetworkResponseLoader(context, data.requestId);
const harEntry = {...data.harEntry, ...loader.api()};
const result = Cu.cloneInto(harEntry, context.cloneScope, {
cloneFunctions: true,
});
fire.asyncWithoutClone(result);
};
onRequestFinished: new EventManager({
context,
name: "devtools.network.onRequestFinished",
register: fire => {
let onFinished = (data) => {
const loader = new ChildNetworkResponseLoader(context, data.requestId);
const harEntry = {...data.harEntry, ...loader.api()};
const result = Cu.cloneInto(harEntry, context.cloneScope, {
cloneFunctions: true,
});
fire.asyncWithoutClone(result);
};
let parent = context.childManager.getParentEvent("devtools.network.onRequestFinished");
parent.addListener(onFinished);
return () => {
parent.removeListener(onFinished);
};
let parent = context.childManager.getParentEvent("devtools.network.onRequestFinished");
parent.addListener(onFinished);
return () => {
parent.removeListener(onFinished);
};
},
}).api(),
},
},

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

@ -94,8 +94,10 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
api() {
return {
onShown: new EventManager(
this.context, "devtoolsPanel.onShown", fire => {
onShown: new EventManager({
context: this.context,
name: "devtoolsPanel.onShown",
register: fire => {
const listener = (eventName, panelContentWindow) => {
fire.asyncWithoutClone(panelContentWindow);
};
@ -103,10 +105,13 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
return () => {
this.off("shown", listener);
};
}).api(),
},
}).api(),
onHidden: new EventManager(
this.context, "devtoolsPanel.onHidden", fire => {
onHidden: new EventManager({
context: this.context,
name: "devtoolsPanel.onHidden",
register: fire => {
const listener = () => {
fire.async();
};
@ -114,7 +119,8 @@ class ChildDevToolsPanel extends ExtensionUtils.EventEmitter {
return () => {
this.off("hidden", listener);
};
}).api(),
},
}).api(),
// TODO(rpl): onSearch event and createStatusBarButton method
};
@ -189,8 +195,10 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
const {context, id} = this;
return {
onShown: new EventManager(
context, "devtoolsInspectorSidebar.onShown", fire => {
onShown: new EventManager({
context,
name: "devtoolsInspectorSidebar.onShown",
register: fire => {
const listener = (eventName, panelContentWindow) => {
fire.asyncWithoutClone(panelContentWindow);
};
@ -198,10 +206,13 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
return () => {
this.off("shown", listener);
};
}).api(),
},
}).api(),
onHidden: new EventManager(
context, "devtoolsInspectorSidebar.onHidden", fire => {
onHidden: new EventManager({
context,
name: "devtoolsInspectorSidebar.onHidden",
register: fire => {
const listener = () => {
fire.async();
};
@ -209,7 +220,8 @@ class ChildDevToolsInspectorSidebar extends ExtensionUtils.EventEmitter {
return () => {
this.off("hidden", listener);
};
}).api(),
},
}).api(),
setObject(jsonObject, rootTitle) {
return context.cloneScope.Promise.resolve().then(() => {
@ -279,8 +291,10 @@ this.devtools_panels = class extends ExtensionAPI {
get themeName() {
return themeChangeObserver.themeName;
},
onThemeChanged: new EventManager(
context, "devtools.panels.onThemeChanged", fire => {
onThemeChanged: new EventManager({
context,
name: "devtools.panels.onThemeChanged",
register: fire => {
const listener = (eventName, themeName) => {
fire.async(themeName);
};
@ -288,7 +302,8 @@ this.devtools_panels = class extends ExtensionAPI {
return () => {
themeChangeObserver.off("themeChanged", listener);
};
}).api(),
},
}).api(),
},
},
};

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

@ -159,17 +159,21 @@ this.menusInternal = class extends ExtensionAPI {
return context.childManager.callParentAsyncFunction("menusInternal.removeAll", []);
},
onClicked: new EventManager(context, "menus.onClicked", fire => {
let listener = (info, tab) => {
withHandlingUserInput(context.contentWindow,
() => fire.sync(info, tab));
};
onClicked: new EventManager({
context,
name: "menus.onClicked",
register: fire => {
let listener = (info, tab) => {
withHandlingUserInput(context.contentWindow,
() => fire.sync(info, tab));
};
let event = context.childManager.getParentEvent("menusInternal.onClicked");
event.addListener(listener);
return () => {
event.removeListener(listener);
};
let event = context.childManager.getParentEvent("menusInternal.onClicked");
event.addListener(listener);
return () => {
event.removeListener(listener);
};
},
}).api(),
},
};

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

@ -6,19 +6,23 @@ this.omnibox = class extends ExtensionAPI {
getAPI(context) {
return {
omnibox: {
onInputChanged: new EventManager(context, "omnibox.onInputChanged", fire => {
let listener = (text, id) => {
fire.asyncWithoutClone(text, suggestions => {
context.childManager.callParentFunctionNoReturn("omnibox.addSuggestions", [
id,
suggestions,
]);
});
};
context.childManager.getParentEvent("omnibox.onInputChanged").addListener(listener);
return () => {
context.childManager.getParentEvent("omnibox.onInputChanged").removeListener(listener);
};
onInputChanged: new EventManager({
context,
name: "omnibox.onInputChanged",
register: fire => {
let listener = (text, id) => {
fire.asyncWithoutClone(text, suggestions => {
context.childManager.callParentFunctionNoReturn("omnibox.addSuggestions", [
id,
suggestions,
]);
});
};
context.childManager.getParentEvent("omnibox.onInputChanged").addListener(listener);
return () => {
context.childManager.getParentEvent("omnibox.onInputChanged").removeListener(listener);
};
},
}).api(),
},
};

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

@ -7,7 +7,6 @@ module.exports = {
"Tab": true,
"TabContext": true,
"Window": true,
"WindowEventManager": true,
"actionContextMenu": true,
"browserActionFor": true,
"getContainerForCookieStoreId": true,

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

@ -329,56 +329,72 @@ this.bookmarks = class extends ExtensionAPI {
}
},
onCreated: new EventManager(context, "bookmarks.onCreated", fire => {
let listener = (event, bookmark) => {
fire.sync(bookmark.id, bookmark);
};
onCreated: new EventManager({
context,
name: "bookmarks.onCreated",
register: fire => {
let listener = (event, bookmark) => {
fire.sync(bookmark.id, bookmark);
};
observer.on("created", listener);
incrementListeners();
return () => {
observer.off("created", listener);
decrementListeners();
};
observer.on("created", listener);
incrementListeners();
return () => {
observer.off("created", listener);
decrementListeners();
};
},
}).api(),
onRemoved: new EventManager(context, "bookmarks.onRemoved", fire => {
let listener = (event, data) => {
fire.sync(data.guid, data.info);
};
onRemoved: new EventManager({
context,
name: "bookmarks.onRemoved",
register: fire => {
let listener = (event, data) => {
fire.sync(data.guid, data.info);
};
observer.on("removed", listener);
incrementListeners();
return () => {
observer.off("removed", listener);
decrementListeners();
};
observer.on("removed", listener);
incrementListeners();
return () => {
observer.off("removed", listener);
decrementListeners();
};
},
}).api(),
onChanged: new EventManager(context, "bookmarks.onChanged", fire => {
let listener = (event, data) => {
fire.sync(data.guid, data.info);
};
onChanged: new EventManager({
context,
name: "bookmarks.onChanged",
register: fire => {
let listener = (event, data) => {
fire.sync(data.guid, data.info);
};
observer.on("changed", listener);
incrementListeners();
return () => {
observer.off("changed", listener);
decrementListeners();
};
observer.on("changed", listener);
incrementListeners();
return () => {
observer.off("changed", listener);
decrementListeners();
};
},
}).api(),
onMoved: new EventManager(context, "bookmarks.onMoved", fire => {
let listener = (event, data) => {
fire.sync(data.guid, data.info);
};
onMoved: new EventManager({
context,
name: "bookmarks.onMoved",
register: fire => {
let listener = (event, data) => {
fire.sync(data.guid, data.info);
};
observer.on("moved", listener);
incrementListeners();
return () => {
observer.off("moved", listener);
decrementListeners();
};
observer.on("moved", listener);
incrementListeners();
return () => {
observer.off("moved", listener);
decrementListeners();
};
},
}).api(),
},
};

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

@ -169,34 +169,6 @@ class WindowTracker extends WindowTrackerBase {
}
}
/**
* An event manager API provider which listens for a DOM event in any browser
* window, and calls the given listener function whenever an event is received.
* That listener function receives a `fire` object, which it can use to dispatch
* events to the extension, and a DOM event object.
*
* @param {BaseContext} context
* The extension context which the event manager belongs to.
* @param {string} name
* The API name of the event manager, e.g.,"runtime.onMessage".
* @param {string} event
* The name of the DOM event to listen for.
* @param {function} listener
* The listener function to call when a DOM event is received.
*/
global.WindowEventManager = class extends EventManager {
constructor(context, name, event, listener) {
super(context, name, fire => {
let listener2 = listener.bind(null, fire);
windowTracker.addListener(event, listener2);
return () => {
windowTracker.removeListener(event, listener2);
};
});
}
};
class TabTracker extends TabTrackerBase {
constructor() {
super();

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

@ -584,15 +584,20 @@ this.browserAction = class extends ExtensionAPI {
return {
browserAction: {
onClicked: new InputEventManager(context, "browserAction.onClicked", fire => {
let listener = (event, browser) => {
context.withPendingBrowser(browser, () =>
fire.sync(tabManager.convert(tabTracker.activeTab)));
};
browserAction.on("click", listener);
return () => {
browserAction.off("click", listener);
};
onClicked: new EventManager({
context,
name: "browserAction.onClicked",
inputHandling: true,
register: fire => {
let listener = (event, browser) => {
context.withPendingBrowser(browser, () =>
fire.sync(tabManager.convert(tabTracker.activeTab)));
};
browserAction.on("click", listener);
return () => {
browserAction.off("click", listener);
};
},
}).api(),
enable: function(tabId) {

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

@ -364,14 +364,18 @@ this.commands = class extends ExtensionAPI {
this.registerKeys(commands);
}
},
onCommand: new EventManager(context, "commands.onCommand", fire => {
let listener = (eventName, commandName) => {
fire.async(commandName);
};
this.on("command", listener);
return () => {
this.off("command", listener);
};
onCommand: new EventManager({
context,
name: "commands.onCommand",
register: fire => {
let listener = (eventName, commandName) => {
fire.async(commandName);
};
this.on("command", listener);
return () => {
this.off("command", listener);
};
},
}).api(),
},
};

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

@ -15,37 +15,45 @@ this.devtools_network = class extends ExtensionAPI {
return {
devtools: {
network: {
onNavigated: new EventManager(context, "devtools.onNavigated", fire => {
let listener = data => {
fire.async(data.url);
};
onNavigated: new EventManager({
context,
name: "devtools.onNavigated",
register: fire => {
let listener = data => {
fire.async(data.url);
};
let targetPromise = getDevToolsTargetForContext(context);
targetPromise.then(target => {
target.on("navigate", listener);
});
return () => {
let targetPromise = getDevToolsTargetForContext(context);
targetPromise.then(target => {
target.off("navigate", listener);
target.on("navigate", listener);
});
};
return () => {
targetPromise.then(target => {
target.off("navigate", listener);
});
};
},
}).api(),
getHAR: function() {
return context.devToolsToolbox.getHARFromNetMonitor();
},
onRequestFinished: new EventManager(context, "devtools.network.onRequestFinished", fire => {
const listener = (data) => {
fire.async(data);
};
onRequestFinished: new EventManager({
context,
name: "devtools.network.onRequestFinished",
register: fire => {
const listener = (data) => {
fire.async(data);
};
const toolbox = context.devToolsToolbox;
toolbox.addRequestFinishedListener(listener);
const toolbox = context.devToolsToolbox;
toolbox.addRequestFinishedListener(listener);
return () => {
toolbox.removeRequestFinishedListener(listener);
};
return () => {
toolbox.removeRequestFinishedListener(listener);
};
},
}).api(),
// The following method is used internally to allow the request API

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

@ -525,8 +525,10 @@ this.devtools_panels = class extends ExtensionAPI {
devtools: {
panels: {
elements: {
onSelectionChanged: new EventManager(
context, "devtools.panels.elements.onSelectionChanged", fire => {
onSelectionChanged: new EventManager({
context,
name: "devtools.panels.elements.onSelectionChanged",
register: fire => {
const listener = (eventName) => {
fire.async();
};
@ -534,7 +536,8 @@ this.devtools_panels = class extends ExtensionAPI {
return () => {
toolboxSelectionObserver.off("selectionChanged", listener);
};
}).api(),
},
}).api(),
createSidebarPane(title) {
const id = `devtools-inspector-sidebar-${makeWidgetId(newBasePanelId())}`;

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

@ -473,11 +473,15 @@ this.geckoProfiler = class extends ExtensionAPI {
throw new Error(`Ran out of options to get symbols from library ${debugName} ${breakpadId}.`);
},
onRunning: new EventManager(context, "geckoProfiler.onRunning", fire => {
isRunningObserver.addObserver(fire.async);
return () => {
isRunningObserver.removeObserver(fire.async);
};
onRunning: new EventManager({
context,
name: "geckoProfiler.onRunning",
register: fire => {
isRunningObserver.addObserver(fire.async);
return () => {
isRunningObserver.removeObserver(fire.async);
};
},
}).api(),
},
};

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

@ -220,37 +220,49 @@ this.history = class extends ExtensionAPI {
return Promise.resolve(results);
},
onVisited: new EventManager(context, "history.onVisited", fire => {
let listener = (event, data) => {
fire.sync(data);
};
onVisited: new EventManager({
context,
name: "history.onVisited",
register: fire => {
let listener = (event, data) => {
fire.sync(data);
};
getHistoryObserver().on("visited", listener);
return () => {
getHistoryObserver().off("visited", listener);
};
getHistoryObserver().on("visited", listener);
return () => {
getHistoryObserver().off("visited", listener);
};
},
}).api(),
onVisitRemoved: new EventManager(context, "history.onVisitRemoved", fire => {
let listener = (event, data) => {
fire.sync(data);
};
onVisitRemoved: new EventManager({
context,
name: "history.onVisitRemoved",
register: fire => {
let listener = (event, data) => {
fire.sync(data);
};
getHistoryObserver().on("visitRemoved", listener);
return () => {
getHistoryObserver().off("visitRemoved", listener);
};
getHistoryObserver().on("visitRemoved", listener);
return () => {
getHistoryObserver().off("visitRemoved", listener);
};
},
}).api(),
onTitleChanged: new EventManager(context, "history.onTitleChanged", fire => {
let listener = (event, data) => {
fire.sync(data);
};
onTitleChanged: new EventManager({
context,
name: "history.onTitleChanged",
register: fire => {
let listener = (event, data) => {
fire.sync(data);
};
getHistoryObserver().on("titleChanged", listener);
return () => {
getHistoryObserver().off("titleChanged", listener);
};
getHistoryObserver().on("titleChanged", listener);
return () => {
getHistoryObserver().off("titleChanged", listener);
};
},
}).api(),
},
};

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

@ -813,40 +813,48 @@ this.menusInternal = class extends ExtensionAPI {
gMenuBuilder.rebuildMenu(extension);
},
onShown: new EventManager(context, "menus.onShown", fire => {
let listener = (event, menuIds, contextData) => {
let info = {
menuIds,
contexts: Array.from(getMenuContexts(contextData)),
};
onShown: new EventManager({
context,
name: "menus.onShown",
register: fire => {
let listener = (event, menuIds, contextData) => {
let info = {
menuIds,
contexts: Array.from(getMenuContexts(contextData)),
};
// The menus.onShown event is fired before the user has consciously
// interacted with an extension, so we require permissions before
// exposing sensitive contextual data.
let includeSensitiveData =
// The menus.onShown event is fired before the user has consciously
// interacted with an extension, so we require permissions before
// exposing sensitive contextual data.
let includeSensitiveData =
extension.tabManager.hasActiveTabPermission(contextData.tab) ||
extension.whiteListedHosts.matches(contextData.inFrame ? contextData.frameUrl : contextData.pageUrl);
addMenuEventInfo(info, contextData, includeSensitiveData);
addMenuEventInfo(info, contextData, includeSensitiveData);
let tab = extension.tabManager.convert(contextData.tab);
fire.sync(info, tab);
};
gOnShownSubscribers.add(extension);
extension.on("webext-menu-shown", listener);
return () => {
gOnShownSubscribers.delete(extension);
extension.off("webext-menu-shown", listener);
};
let tab = extension.tabManager.convert(contextData.tab);
fire.sync(info, tab);
};
gOnShownSubscribers.add(extension);
extension.on("webext-menu-shown", listener);
return () => {
gOnShownSubscribers.delete(extension);
extension.off("webext-menu-shown", listener);
};
},
}).api(),
onHidden: new EventManager(context, "menus.onHidden", fire => {
let listener = () => {
fire.sync();
};
extension.on("webext-menu-hidden", listener);
return () => {
extension.off("webext-menu-hidden", listener);
};
onHidden: new EventManager({
context,
name: "menus.onHidden",
register: fire => {
let listener = () => {
fire.sync();
};
extension.on("webext-menu-hidden", listener);
return () => {
extension.off("webext-menu-hidden", listener);
};
},
}).api(),
};
@ -883,18 +891,22 @@ this.menusInternal = class extends ExtensionAPI {
}
},
onClicked: new EventManager(context, "menusInternal.onClicked", fire => {
let listener = (event, info, nativeTab) => {
let {linkedBrowser} = nativeTab || tabTracker.activeTab;
let tab = nativeTab && extension.tabManager.convert(nativeTab);
context.withPendingBrowser(linkedBrowser,
() => fire.sync(info, tab));
};
onClicked: new EventManager({
context,
name: "menusInternal.onClicked",
register: fire => {
let listener = (event, info, nativeTab) => {
let {linkedBrowser} = nativeTab || tabTracker.activeTab;
let tab = nativeTab && extension.tabManager.convert(nativeTab);
context.withPendingBrowser(linkedBrowser,
() => fire.sync(info, tab));
};
extension.on("webext-menu-menuitem-click", listener);
return () => {
extension.off("webext-menu-menuitem-click", listener);
};
extension.on("webext-menu-menuitem-click", listener);
return () => {
extension.off("webext-menu-menuitem-click", listener);
};
},
}).api(),
},
};

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

@ -37,34 +37,46 @@ this.omnibox = class extends ExtensionAPI {
}
},
onInputStarted: new EventManager(context, "omnibox.onInputStarted", fire => {
let listener = (eventName) => {
fire.sync();
};
extension.on(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
};
onInputStarted: new EventManager({
context,
name: "omnibox.onInputStarted",
register: fire => {
let listener = (eventName) => {
fire.sync();
};
extension.on(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_STARTED, listener);
};
},
}).api(),
onInputCancelled: new EventManager(context, "omnibox.onInputCancelled", fire => {
let listener = (eventName) => {
fire.sync();
};
extension.on(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
};
onInputCancelled: new EventManager({
context,
name: "omnibox.onInputCancelled",
register: fire => {
let listener = (eventName) => {
fire.sync();
};
extension.on(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_CANCELLED, listener);
};
},
}).api(),
onInputEntered: new EventManager(context, "omnibox.onInputEntered", fire => {
let listener = (eventName, text, disposition) => {
fire.sync(text, disposition);
};
extension.on(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
};
onInputEntered: new EventManager({
context,
name: "omnibox.onInputEntered",
register: fire => {
let listener = (eventName, text, disposition) => {
fire.sync(text, disposition);
};
extension.on(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_ENTERED, listener);
};
},
}).api(),
// Internal APIs.
@ -77,14 +89,18 @@ this.omnibox = class extends ExtensionAPI {
}
},
onInputChanged: new EventManager(context, "omnibox.onInputChanged", fire => {
let listener = (eventName, text, id) => {
fire.sync(text, id);
};
extension.on(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
};
onInputChanged: new EventManager({
context,
name: "omnibox.onInputChanged",
register: fire => {
let listener = (eventName, text, id) => {
fire.sync(text, id);
};
extension.on(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
return () => {
extension.off(ExtensionSearchHandler.MSG_INPUT_CHANGED, listener);
};
},
}).api(),
},
};

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

@ -346,16 +346,21 @@ this.pageAction = class extends ExtensionAPI {
return {
pageAction: {
onClicked: new InputEventManager(context, "pageAction.onClicked", fire => {
let listener = (evt, tab) => {
context.withPendingBrowser(tab.linkedBrowser, () =>
fire.sync(tabManager.convert(tab)));
};
onClicked: new EventManager({
context,
name: "pageAction.onClicked",
inputHandling: true,
register: fire => {
let listener = (evt, tab) => {
context.withPendingBrowser(tab.linkedBrowser, () =>
fire.sync(tabManager.convert(tab)));
};
pageAction.on("click", listener);
return () => {
pageAction.off("click", listener);
};
pageAction.on("click", listener);
return () => {
pageAction.off("click", listener);
};
},
}).api(),
show(tabId) {

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

@ -204,15 +204,19 @@ this.sessions = class extends ExtensionAPI {
SessionStore.deleteWindowValue(win, encodedKey);
},
onChanged: new EventManager(context, "sessions.onChanged", fire => {
let observer = () => {
fire.async();
};
onChanged: new EventManager({
context,
name: "sessions.onChanged",
register: fire => {
let observer = () => {
fire.async();
};
Services.obs.addObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
return () => {
Services.obs.removeObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
};
Services.obs.addObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
return () => {
Services.obs.removeObserver(observer, SS_ON_CLOSED_OBJECTS_CHANGED);
};
},
}).api(),
},
};

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

@ -110,7 +110,7 @@ const allProperties = new Set([
const restricted = new Set(["url", "favIconUrl", "title"]);
class TabsUpdateFilterEventManager extends EventManager {
constructor(context, eventName) {
constructor(context) {
let {extension} = context;
let {tabManager} = extension;
@ -286,7 +286,11 @@ class TabsUpdateFilterEventManager extends EventManager {
};
};
super(context, eventName, register);
super({
context,
name: "tabs.onUpdated",
register,
});
}
addListener(callback, filter) {
@ -338,26 +342,34 @@ this.tabs = class extends ExtensionAPI {
let self = {
tabs: {
onActivated: new EventManager(context, "tabs.onActivated", fire => {
let listener = (eventName, event) => {
fire.async(event);
};
onActivated: new EventManager({
context,
name: "tabs.onActivated",
register: fire => {
let listener = (eventName, event) => {
fire.async(event);
};
tabTracker.on("tab-activated", listener);
return () => {
tabTracker.off("tab-activated", listener);
};
tabTracker.on("tab-activated", listener);
return () => {
tabTracker.off("tab-activated", listener);
};
},
}).api(),
onCreated: new EventManager(context, "tabs.onCreated", fire => {
let listener = (eventName, event) => {
fire.async(tabManager.convert(event.nativeTab, event.currentTab));
};
onCreated: new EventManager({
context,
name: "tabs.onCreated",
register: fire => {
let listener = (eventName, event) => {
fire.async(tabManager.convert(event.nativeTab, event.currentTab));
};
tabTracker.on("tab-created", listener);
return () => {
tabTracker.off("tab-created", listener);
};
tabTracker.on("tab-created", listener);
return () => {
tabTracker.off("tab-created", listener);
};
},
}).api(),
/**
@ -366,98 +378,122 @@ this.tabs = class extends ExtensionAPI {
* the tabId in an array to match the API.
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
*/
onHighlighted: new EventManager(context, "tabs.onHighlighted", fire => {
let listener = (eventName, event) => {
fire.async({tabIds: [event.tabId], windowId: event.windowId});
};
onHighlighted: new EventManager({
context,
name: "tabs.onHighlighted",
register: fire => {
let listener = (eventName, event) => {
fire.async({tabIds: [event.tabId], windowId: event.windowId});
};
tabTracker.on("tab-activated", listener);
return () => {
tabTracker.off("tab-activated", listener);
};
tabTracker.on("tab-activated", listener);
return () => {
tabTracker.off("tab-activated", listener);
};
},
}).api(),
onAttached: new EventManager(context, "tabs.onAttached", fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
};
onAttached: new EventManager({
context,
name: "tabs.onAttached",
register: fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
};
tabTracker.on("tab-attached", listener);
return () => {
tabTracker.off("tab-attached", listener);
};
tabTracker.on("tab-attached", listener);
return () => {
tabTracker.off("tab-attached", listener);
};
},
}).api(),
onDetached: new EventManager(context, "tabs.onDetached", fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
};
onDetached: new EventManager({
context,
name: "tabs.onDetached",
register: fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
};
tabTracker.on("tab-detached", listener);
return () => {
tabTracker.off("tab-detached", listener);
};
tabTracker.on("tab-detached", listener);
return () => {
tabTracker.off("tab-detached", listener);
};
},
}).api(),
onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
};
onRemoved: new EventManager({
context,
name: "tabs.onRemoved",
register: fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
};
tabTracker.on("tab-removed", listener);
return () => {
tabTracker.off("tab-removed", listener);
};
tabTracker.on("tab-removed", listener);
return () => {
tabTracker.off("tab-removed", listener);
};
},
}).api(),
onReplaced: new EventManager(context, "tabs.onReplaced", fire => {
return () => {};
onReplaced: new EventManager({
context,
name: "tabs.onReplaced",
register: fire => {
return () => {};
},
}).api(),
onMoved: new EventManager(context, "tabs.onMoved", fire => {
// There are certain circumstances where we need to ignore a move event.
//
// Namely, the first time the tab is moved after it's created, we need
// to report the final position as the initial position in the tab's
// onAttached or onCreated event. This is because most tabs are inserted
// in a temporary location and then moved after the TabOpen event fires,
// which generates a TabOpen event followed by a TabMove event, which
// does not match the contract of our API.
let ignoreNextMove = new WeakSet();
onMoved: new EventManager({
context,
name: "tabs.onMoved",
register: fire => {
// There are certain circumstances where we need to ignore a move event.
//
// Namely, the first time the tab is moved after it's created, we need
// to report the final position as the initial position in the tab's
// onAttached or onCreated event. This is because most tabs are inserted
// in a temporary location and then moved after the TabOpen event fires,
// which generates a TabOpen event followed by a TabMove event, which
// does not match the contract of our API.
let ignoreNextMove = new WeakSet();
let openListener = event => {
ignoreNextMove.add(event.target);
// Remove the tab from the set on the next tick, since it will already
// have been moved by then.
Promise.resolve().then(() => {
ignoreNextMove.delete(event.target);
});
};
let openListener = event => {
ignoreNextMove.add(event.target);
// Remove the tab from the set on the next tick, since it will already
// have been moved by then.
Promise.resolve().then(() => {
ignoreNextMove.delete(event.target);
});
};
let moveListener = event => {
let nativeTab = event.originalTarget;
let moveListener = event => {
let nativeTab = event.originalTarget;
if (ignoreNextMove.has(nativeTab)) {
ignoreNextMove.delete(nativeTab);
return;
}
if (ignoreNextMove.has(nativeTab)) {
ignoreNextMove.delete(nativeTab);
return;
}
fire.async(tabTracker.getId(nativeTab), {
windowId: windowTracker.getId(nativeTab.ownerGlobal),
fromIndex: event.detail,
toIndex: nativeTab._tPos,
});
};
fire.async(tabTracker.getId(nativeTab), {
windowId: windowTracker.getId(nativeTab.ownerGlobal),
fromIndex: event.detail,
toIndex: nativeTab._tPos,
});
};
windowTracker.addListener("TabMove", moveListener);
windowTracker.addListener("TabOpen", openListener);
return () => {
windowTracker.removeListener("TabMove", moveListener);
windowTracker.removeListener("TabOpen", openListener);
};
windowTracker.addListener("TabMove", moveListener);
windowTracker.addListener("TabOpen", openListener);
return () => {
windowTracker.removeListener("TabMove", moveListener);
windowTracker.removeListener("TabOpen", openListener);
};
},
}).api(),
onUpdated: new TabsUpdateFilterEventManager(context, "tabs.onUpdated").api(),
onUpdated: new TabsUpdateFilterEventManager(context).api(),
create(createProperties) {
return new Promise((resolve, reject) => {
@ -896,75 +932,79 @@ this.tabs = class extends ExtensionAPI {
return Promise.resolve();
},
onZoomChange: new EventManager(context, "tabs.onZoomChange", fire => {
let getZoomLevel = browser => {
let {ZoomManager} = browser.ownerGlobal;
onZoomChange: new EventManager({
context,
name: "tabs.onZoomChange",
register: fire => {
let getZoomLevel = browser => {
let {ZoomManager} = browser.ownerGlobal;
return ZoomManager.getZoomForBrowser(browser);
};
return ZoomManager.getZoomForBrowser(browser);
};
// Stores the last known zoom level for each tab's browser.
// WeakMap[<browser> -> number]
let zoomLevels = new WeakMap();
// Stores the last known zoom level for each tab's browser.
// WeakMap[<browser> -> number]
let zoomLevels = new WeakMap();
// Store the zoom level for all existing tabs.
for (let window of windowTracker.browserWindows()) {
for (let nativeTab of window.gBrowser.tabs) {
let browser = nativeTab.linkedBrowser;
// Store the zoom level for all existing tabs.
for (let window of windowTracker.browserWindows()) {
for (let nativeTab of window.gBrowser.tabs) {
let browser = nativeTab.linkedBrowser;
zoomLevels.set(browser, getZoomLevel(browser));
}
}
let tabCreated = (eventName, event) => {
let browser = event.nativeTab.linkedBrowser;
zoomLevels.set(browser, getZoomLevel(browser));
}
}
let tabCreated = (eventName, event) => {
let browser = event.nativeTab.linkedBrowser;
zoomLevels.set(browser, getZoomLevel(browser));
};
};
let zoomListener = event => {
let browser = event.originalTarget;
let zoomListener = event => {
let browser = event.originalTarget;
// For non-remote browsers, this event is dispatched on the document
// rather than on the <browser>.
if (browser instanceof Ci.nsIDOMDocument) {
browser = browser.docShell.chromeEventHandler;
}
// For non-remote browsers, this event is dispatched on the document
// rather than on the <browser>.
if (browser instanceof Ci.nsIDOMDocument) {
browser = browser.docShell.chromeEventHandler;
}
let {gBrowser} = browser.ownerGlobal;
let nativeTab = gBrowser.getTabForBrowser(browser);
if (!nativeTab) {
// We only care about zoom events in the top-level browser of a tab.
return;
}
let {gBrowser} = browser.ownerGlobal;
let nativeTab = gBrowser.getTabForBrowser(browser);
if (!nativeTab) {
// We only care about zoom events in the top-level browser of a tab.
return;
}
let oldZoomFactor = zoomLevels.get(browser);
let newZoomFactor = getZoomLevel(browser);
let oldZoomFactor = zoomLevels.get(browser);
let newZoomFactor = getZoomLevel(browser);
if (oldZoomFactor != newZoomFactor) {
zoomLevels.set(browser, newZoomFactor);
if (oldZoomFactor != newZoomFactor) {
zoomLevels.set(browser, newZoomFactor);
let tabId = tabTracker.getId(nativeTab);
fire.async({
tabId,
oldZoomFactor,
newZoomFactor,
zoomSettings: self.tabs._getZoomSettings(tabId),
});
}
};
let tabId = tabTracker.getId(nativeTab);
fire.async({
tabId,
oldZoomFactor,
newZoomFactor,
zoomSettings: self.tabs._getZoomSettings(tabId),
});
}
};
tabTracker.on("tab-attached", tabCreated);
tabTracker.on("tab-created", tabCreated);
tabTracker.on("tab-attached", tabCreated);
tabTracker.on("tab-created", tabCreated);
windowTracker.addListener("FullZoomChange", zoomListener);
windowTracker.addListener("TextZoomChange", zoomListener);
return () => {
tabTracker.off("tab-attached", tabCreated);
tabTracker.off("tab-created", tabCreated);
windowTracker.addListener("FullZoomChange", zoomListener);
windowTracker.addListener("TextZoomChange", zoomListener);
return () => {
tabTracker.off("tab-attached", tabCreated);
tabTracker.off("tab-created", tabCreated);
windowTracker.removeListener("FullZoomChange", zoomListener);
windowTracker.removeListener("TextZoomChange", zoomListener);
};
windowTracker.removeListener("FullZoomChange", zoomListener);
windowTracker.removeListener("TextZoomChange", zoomListener);
};
},
}).api(),
print() {

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

@ -16,6 +16,36 @@ const onXULFrameLoaderCreated = ({target}) => {
target.messageManager.sendAsyncMessage("AllowScriptsToClose", {});
};
/**
* An event manager API provider which listens for a DOM event in any browser
* window, and calls the given listener function whenever an event is received.
* That listener function receives a `fire` object, which it can use to dispatch
* events to the extension, and a DOM event object.
*
* @param {BaseContext} context
* The extension context which the event manager belongs to.
* @param {string} name
* The API name of the event manager, e.g.,"runtime.onMessage".
* @param {string} event
* The name of the DOM event to listen for.
* @param {function} listener
* The listener function to call when a DOM event is received.
*
* @returns {object} An injectable api for the new event.
*/
function WindowEventManager(context, name, event, listener) {
let register = fire => {
let listener2 = listener.bind(null, fire);
windowTracker.addListener(event, listener2);
return () => {
windowTracker.removeListener(event, listener2);
};
};
return new EventManager({context, name, register}).api();
}
this.windows = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
@ -24,38 +54,40 @@ this.windows = class extends ExtensionAPI {
return {
windows: {
onCreated:
new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
onCreated: WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
fire.async(windowManager.convert(window));
}).api(),
}),
onRemoved:
new WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
onRemoved: WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
fire.async(windowTracker.getId(window));
}).api(),
}),
onFocusChanged: new EventManager(context, "windows.onFocusChanged", fire => {
// Keep track of the last windowId used to fire an onFocusChanged event
let lastOnFocusChangedWindowId;
onFocusChanged: new EventManager({
context,
name: "windows.onFocusChanged",
register: fire => {
// Keep track of the last windowId used to fire an onFocusChanged event
let lastOnFocusChangedWindowId;
let listener = event => {
// Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
// event when switching focus between two Firefox windows.
Promise.resolve().then(() => {
let window = Services.focus.activeWindow;
let windowId = window ? windowTracker.getId(window) : Window.WINDOW_ID_NONE;
if (windowId !== lastOnFocusChangedWindowId) {
fire.async(windowId);
lastOnFocusChangedWindowId = windowId;
}
});
};
windowTracker.addListener("focus", listener);
windowTracker.addListener("blur", listener);
return () => {
windowTracker.removeListener("focus", listener);
windowTracker.removeListener("blur", listener);
};
let listener = event => {
// Wait a tick to avoid firing a superfluous WINDOW_ID_NONE
// event when switching focus between two Firefox windows.
Promise.resolve().then(() => {
let window = Services.focus.activeWindow;
let windowId = window ? windowTracker.getId(window) : Window.WINDOW_ID_NONE;
if (windowId !== lastOnFocusChangedWindowId) {
fire.async(windowId);
lastOnFocusChangedWindowId = windowId;
}
});
};
windowTracker.addListener("focus", listener);
windowTracker.addListener("blur", listener);
return () => {
windowTracker.removeListener("focus", listener);
windowTracker.removeListener("blur", listener);
};
},
}).api(),
get: function(windowId, getInfo) {

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

@ -156,14 +156,18 @@ this.browserAction = class extends ExtensionAPI {
return {
browserAction: {
onClicked: new EventManager(context, "browserAction.onClicked", fire => {
let listener = (event, tab) => {
fire.async(tabManager.convert(tab));
};
browserActionMap.get(extension).on("click", listener);
return () => {
browserActionMap.get(extension).off("click", listener);
};
onClicked: new EventManager({
context,
name: "browserAction.onClicked",
register: fire => {
let listener = (event, tab) => {
fire.async(tabManager.convert(tab));
};
browserActionMap.get(extension).on("click", listener);
return () => {
browserActionMap.get(extension).off("click", listener);
};
},
}).api(),
setTitle: function(details) {

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

@ -232,14 +232,18 @@ this.pageAction = class extends ExtensionAPI {
return {
pageAction: {
onClicked: new EventManager(context, "pageAction.onClicked", fire => {
let listener = (event, tab) => {
fire.async(tabManager.convert(tab));
};
pageActionMap.get(extension).on("click", listener);
return () => {
pageActionMap.get(extension).off("click", listener);
};
onClicked: new EventManager({
context,
name: "pageAction.onClicked",
register: fire => {
let listener = (event, tab) => {
fire.async(tabManager.convert(tab));
};
pageActionMap.get(extension).on("click", listener);
return () => {
pageActionMap.get(extension).off("click", listener);
};
},
}).api(),
show(tabId) {

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

@ -103,21 +103,25 @@ this.tabs = class extends ExtensionAPI {
let self = {
tabs: {
onActivated: new GlobalEventManager(context, "tabs.onActivated", "Tab:Selected", (fire, data) => {
onActivated: makeGlobalEvent(context, "tabs.onActivated", "Tab:Selected", (fire, data) => {
let tab = tabManager.get(data.id);
fire.async({tabId: tab.id, windowId: tab.windowId});
}).api(),
}),
onCreated: new EventManager(context, "tabs.onCreated", fire => {
let listener = (eventName, event) => {
fire.async(tabManager.convert(event.nativeTab));
};
onCreated: new EventManager({
context,
name: "tabs.onCreated",
register: fire => {
let listener = (eventName, event) => {
fire.async(tabManager.convert(event.nativeTab));
};
tabTracker.on("tab-created", listener);
return () => {
tabTracker.off("tab-created", listener);
};
tabTracker.on("tab-created", listener);
return () => {
tabTracker.off("tab-created", listener);
};
},
}).api(),
/**
@ -126,114 +130,130 @@ this.tabs = class extends ExtensionAPI {
* the tabId in an array to match the API.
* @see https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
*/
onHighlighted: new GlobalEventManager(context, "tabs.onHighlighted", "Tab:Selected", (fire, data) => {
onHighlighted: makeGlobalEvent(context, "tabs.onHighlighted", "Tab:Selected", (fire, data) => {
let tab = tabManager.get(data.id);
fire.async({tabIds: [tab.id], windowId: tab.windowId});
}),
onAttached: new EventManager({
context,
name: "tabs.onAttached",
register: fire => { return () => {}; },
}).api(),
onAttached: new EventManager(context, "tabs.onAttached", fire => {
return () => {};
onDetached: new EventManager({
context,
name: "tabs.onDetached",
register: fire => { return () => {}; },
}).api(),
onDetached: new EventManager(context, "tabs.onDetached", fire => {
return () => {};
onRemoved: new EventManager({
context,
name: "tabs.onRemoved",
register: fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
};
tabTracker.on("tab-removed", listener);
return () => {
tabTracker.off("tab-removed", listener);
};
},
}).api(),
onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
let listener = (eventName, event) => {
fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
};
tabTracker.on("tab-removed", listener);
return () => {
tabTracker.off("tab-removed", listener);
};
onReplaced: new EventManager({
context,
name: "tabs.onReplaced",
register: fire => { return () => {}; },
}).api(),
onReplaced: new EventManager(context, "tabs.onReplaced", fire => {
return () => {};
onMoved: new EventManager({
context,
name: "tabs.onMoved",
register: fire => { return () => {}; },
}).api(),
onMoved: new EventManager(context, "tabs.onMoved", fire => {
return () => {};
}).api(),
onUpdated: new EventManager({
context,
name: "tabs.onUpdated",
register: fire => {
const restricted = ["url", "favIconUrl", "title"];
onUpdated: new EventManager(context, "tabs.onUpdated", fire => {
const restricted = ["url", "favIconUrl", "title"];
function sanitize(extension, changeInfo) {
let result = {};
let nonempty = false;
for (let prop in changeInfo) {
if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
nonempty = true;
result[prop] = changeInfo[prop];
function sanitize(extension, changeInfo) {
let result = {};
let nonempty = false;
for (let prop in changeInfo) {
if (extension.hasPermission("tabs") || !restricted.includes(prop)) {
nonempty = true;
result[prop] = changeInfo[prop];
}
}
return [nonempty, result];
}
return [nonempty, result];
}
let fireForTab = (tab, changed) => {
let [needed, changeInfo] = sanitize(extension, changed);
if (needed) {
fire.async(tab.id, changeInfo, tab.convert());
}
};
let fireForTab = (tab, changed) => {
let [needed, changeInfo] = sanitize(extension, changed);
if (needed) {
fire.async(tab.id, changeInfo, tab.convert());
}
};
let listener = event => {
let needed = [];
let nativeTab;
switch (event.type) {
case "DOMTitleChanged": {
let {BrowserApp} = getBrowserWindow(event.target.ownerGlobal);
let listener = event => {
let needed = [];
let nativeTab;
switch (event.type) {
case "DOMTitleChanged": {
let {BrowserApp} = getBrowserWindow(event.target.ownerGlobal);
nativeTab = BrowserApp.getTabForWindow(event.target.ownerGlobal);
needed.push("title");
break;
nativeTab = BrowserApp.getTabForWindow(event.target.ownerGlobal);
needed.push("title");
break;
}
case "DOMAudioPlaybackStarted":
case "DOMAudioPlaybackStopped": {
let {BrowserApp} = event.target.ownerGlobal;
nativeTab = BrowserApp.getTabForBrowser(event.originalTarget);
needed.push("audible");
break;
}
}
case "DOMAudioPlaybackStarted":
case "DOMAudioPlaybackStopped": {
let {BrowserApp} = event.target.ownerGlobal;
nativeTab = BrowserApp.getTabForBrowser(event.originalTarget);
needed.push("audible");
break;
}
}
if (!nativeTab) {
return;
}
let tab = tabManager.getWrapper(nativeTab);
let changeInfo = {};
for (let prop of needed) {
changeInfo[prop] = tab[prop];
}
fireForTab(tab, changeInfo);
};
let statusListener = ({browser, status, url}) => {
let {BrowserApp} = browser.ownerGlobal;
let nativeTab = BrowserApp.getTabForBrowser(browser);
if (nativeTab) {
let changed = {status};
if (url) {
changed.url = url;
if (!nativeTab) {
return;
}
fireForTab(tabManager.wrapTab(nativeTab), changed);
}
};
let tab = tabManager.getWrapper(nativeTab);
let changeInfo = {};
for (let prop of needed) {
changeInfo[prop] = tab[prop];
}
windowTracker.addListener("status", statusListener);
windowTracker.addListener("DOMTitleChanged", listener);
return () => {
windowTracker.removeListener("status", statusListener);
windowTracker.removeListener("DOMTitleChanged", listener);
};
fireForTab(tab, changeInfo);
};
let statusListener = ({browser, status, url}) => {
let {BrowserApp} = browser.ownerGlobal;
let nativeTab = BrowserApp.getTabForBrowser(browser);
if (nativeTab) {
let changed = {status};
if (url) {
changed.url = url;
}
fireForTab(tabManager.wrapTab(nativeTab), changed);
}
};
windowTracker.addListener("status", statusListener);
windowTracker.addListener("DOMTitleChanged", listener);
return () => {
windowTracker.removeListener("status", statusListener);
windowTracker.removeListener("DOMTitleChanged", listener);
};
},
}).api(),
async create(createProperties) {

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

@ -187,11 +187,11 @@ class WindowTracker extends WindowTrackerBase {
}
/**
* An event manager API provider which listens for an event in the Android
* global EventDispatcher, and calls the given listener function whenever an event
* is received. That listener function receives a `fire` object, which it can
* use to dispatch events to the extension, and an object detailing the
* EventDispatcher event that was received.
* Helper to create an event manager which listens for an event in the Android
* global EventDispatcher, and calls the given listener function whenever the
* event is received. That listener function receives a `fire` object,
* which it can use to dispatch events to the extension, and an object
* detailing the EventDispatcher event that was received.
*
* @param {BaseContext} context
* The extension context which the event manager belongs to.
@ -202,10 +202,14 @@ class WindowTracker extends WindowTrackerBase {
* @param {function} listener
* The listener function to call when an EventDispatcher event is
* recieved.
*
* @returns {object} An injectable api for the new event.
*/
global.GlobalEventManager = class extends EventManager {
constructor(context, name, event, listener) {
super(context, name, fire => {
global.makeGlobalEvent = function makeGlobalEvent(context, name, event, listener) {
return new EventManager({
context,
name,
register: fire => {
let listener2 = {
onEvent(event, data, callback) {
listener(fire, data);
@ -216,36 +220,8 @@ global.GlobalEventManager = class extends EventManager {
return () => {
GlobalEventDispatcher.unregisterListener(listener2, [event]);
};
});
}
};
/**
* An event manager API provider which listens for a DOM event in any browser
* window, and calls the given listener function whenever an event is received.
* That listener function receives a `fire` object, which it can use to dispatch
* events to the extension, and a DOM event object.
*
* @param {BaseContext} context
* The extension context which the event manager belongs to.
* @param {string} name
* The API name of the event manager, e.g.,"runtime.onMessage".
* @param {string} event
* The name of the DOM event to listen for.
* @param {function} listener
* The listener function to call when a DOM event is received.
*/
global.WindowEventManager = class extends EventManager {
constructor(context, name, event, listener) {
super(context, name, fire => {
let listener2 = listener.bind(null, fire);
windowTracker.addListener(event, listener2);
return () => {
windowTracker.removeListener(event, listener2);
};
});
}
},
}).api();
};
class TabTracker extends TabTrackerBase {

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

@ -179,19 +179,27 @@ class Port {
this.postMessage(json);
},
onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => {
return this.registerOnDisconnect(holder => {
let error = holder && holder.deserialize(this.context.cloneScope);
portError = error && this.context.normalizeError(error);
fire.asyncWithoutClone(portObj);
});
onDisconnect: new EventManager({
context: this.context,
name: "Port.onDisconnect",
register: fire => {
return this.registerOnDisconnect(holder => {
let error = holder && holder.deserialize(this.context.cloneScope);
portError = error && this.context.normalizeError(error);
fire.asyncWithoutClone(portObj);
});
},
}).api(),
onMessage: new EventManager(this.context, "Port.onMessage", fire => {
return this.registerOnMessage(holder => {
let msg = holder.deserialize(this.context.cloneScope);
fire.asyncWithoutClone(msg, portObj);
});
onMessage: new EventManager({
context: this.context,
name: "Port.onMessage",
register: fire => {
return this.registerOnMessage(holder => {
let msg = holder.deserialize(this.context.cloneScope);
fire.asyncWithoutClone(msg, portObj);
});
},
}).api(),
get error() {
@ -403,63 +411,67 @@ class Messenger {
}
_onMessage(name, filter) {
return new EventManager(this.context, name, fire => {
const caller = this.context.getCaller();
return new EventManager({
context: this.context,
name,
register: fire => {
const caller = this.context.getCaller();
let listener = {
messageFilterPermissive: this.optionalFilter,
messageFilterStrict: this.filter,
let listener = {
messageFilterPermissive: this.optionalFilter,
messageFilterStrict: this.filter,
filterMessage: (sender, recipient) => {
// Exclude messages coming from content scripts for the devtools extension contexts
// (See Bug 1383310).
if (this.excludeContentScriptSender && sender.envType === "content_child") {
return false;
}
filterMessage: (sender, recipient) => {
// Exclude messages coming from content scripts for the devtools extension contexts
// (See Bug 1383310).
if (this.excludeContentScriptSender && sender.envType === "content_child") {
return false;
}
// Ignore the message if it was sent by this Messenger.
return (sender.contextId !== this.context.contextId &&
filter(sender, recipient));
},
// Ignore the message if it was sent by this Messenger.
return (sender.contextId !== this.context.contextId &&
filter(sender, recipient));
},
receiveMessage: ({target, data: holder, sender, recipient, channelId}) => {
if (!this.context.active) {
return;
}
receiveMessage: ({target, data: holder, sender, recipient, channelId}) => {
if (!this.context.active) {
return;
}
let sendResponse;
let response = undefined;
let promise = new Promise(resolve => {
sendResponse = value => {
resolve(value);
response = promise;
};
});
let sendResponse;
let response = undefined;
let promise = new Promise(resolve => {
sendResponse = value => {
resolve(value);
response = promise;
};
});
let message = holder.deserialize(this.context.cloneScope);
holder = null;
let message = holder.deserialize(this.context.cloneScope);
holder = null;
sender = Cu.cloneInto(sender, this.context.cloneScope);
sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
sender = Cu.cloneInto(sender, this.context.cloneScope);
sendResponse = Cu.exportFunction(sendResponse, this.context.cloneScope);
// Note: We intentionally do not use runSafe here so that any
// errors are propagated to the message sender.
let result = fire.raw(message, sender, sendResponse);
message = null;
// Note: We intentionally do not use runSafe here so that any
// errors are propagated to the message sender.
let result = fire.raw(message, sender, sendResponse);
message = null;
if (result instanceof this.context.cloneScope.Promise) {
return StrongPromise.wrap(result, channelId, caller);
} else if (result === true) {
return StrongPromise.wrap(promise, channelId, caller);
}
return response;
},
};
if (result instanceof this.context.cloneScope.Promise) {
return StrongPromise.wrap(result, channelId, caller);
} else if (result === true) {
return StrongPromise.wrap(promise, channelId, caller);
}
return response;
},
};
MessageChannel.addListener(this.messageManagers, "Extension:Message", listener);
return () => {
MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
};
MessageChannel.addListener(this.messageManagers, "Extension:Message", listener);
return () => {
MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
};
},
}).api();
}
@ -506,41 +518,45 @@ class Messenger {
}
_onConnect(name, filter) {
return new EventManager(this.context, name, fire => {
let listener = {
messageFilterPermissive: this.optionalFilter,
messageFilterStrict: this.filter,
return new EventManager({
context: this.context,
name,
register: fire => {
let listener = {
messageFilterPermissive: this.optionalFilter,
messageFilterStrict: this.filter,
filterMessage: (sender, recipient) => {
// Exclude messages coming from content scripts for the devtools extension contexts
// (See Bug 1383310).
if (this.excludeContentScriptSender && sender.envType === "content_child") {
return false;
}
filterMessage: (sender, recipient) => {
// Exclude messages coming from content scripts for the devtools extension contexts
// (See Bug 1383310).
if (this.excludeContentScriptSender && sender.envType === "content_child") {
return false;
}
// Ignore the port if it was created by this Messenger.
return (sender.contextId !== this.context.contextId &&
filter(sender, recipient));
},
// Ignore the port if it was created by this Messenger.
return (sender.contextId !== this.context.contextId &&
filter(sender, recipient));
},
receiveMessage: ({target, data: message, sender}) => {
let {name, portId} = message;
let mm = getMessageManager(target);
let recipient = Object.assign({}, sender);
if (recipient.tab) {
recipient.tabId = recipient.tab.id;
delete recipient.tab;
}
let port = new Port(this.context, mm, this.messageManagers, name, portId, sender, recipient);
fire.asyncWithoutClone(port.api());
return true;
},
};
receiveMessage: ({target, data: message, sender}) => {
let {name, portId} = message;
let mm = getMessageManager(target);
let recipient = Object.assign({}, sender);
if (recipient.tab) {
recipient.tabId = recipient.tab.id;
delete recipient.tab;
}
let port = new Port(this.context, mm, this.messageManagers, name, portId, sender, recipient);
fire.asyncWithoutClone(port.api());
return true;
},
};
MessageChannel.addListener(this.messageManagers, "Extension:Connect", listener);
return () => {
MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener);
};
MessageChannel.addListener(this.messageManagers, "Extension:Connect", listener);
return () => {
MessageChannel.removeListener(this.messageManagers, "Extension:Connect", listener);
};
},
}).api();
}

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

@ -1735,46 +1735,62 @@ defineLazyGetter(LocaleData.prototype, "availableLocales", function() {
});
/**
* This is a generic class for managing event listeners.
* This is a generic class for managing event listeners.
*
* @example
* new EventManager(context, "api.subAPI", fire => {
* let listener = (...) => {
* // Fire any listeners registered with addListener.
* fire.async(arg1, arg2);
* };
* // Register the listener.
* SomehowRegisterListener(listener);
* return () => {
* // Return a way to unregister the listener.
* SomehowUnregisterListener(listener);
* };
* new EventManager({
* context,
* name: "api.subAPI",
* register: fire => {
* let listener = (...) => {
* // Fire any listeners registered with addListener.
* fire.async(arg1, arg2);
* };
* // Register the listener.
* SomehowRegisterListener(listener);
* return () => {
* // Return a way to unregister the listener.
* SomehowUnregisterListener(listener);
* };
* }
* }).api()
*
* The result is an object with addListener, removeListener, and
* hasListener methods. `context` is an add-on scope (either an
* ExtensionContext in the chrome process or ExtensionContext in a
* content process). `name` is for debugging. `register` is a function
* to register the listener. `register` should return an
* unregister function that will unregister the listener.
* @constructor
*
* @param {BaseContext} context
* An object representing the extension instance using this event.
* @param {string} name
* A name used only for debugging.
* @param {function} register
* A function called whenever a new listener is added.
* content process).
*/
function EventManager(context, name, register) {
this.context = context;
this.name = name;
this.register = register;
this.unregister = new Map();
this.inputHandling = false;
}
class EventManager {
/*
* @param {object} params
* Parameters that control this EventManager.
* @param {BaseContext} params.context
* An object representing the extension instance using this event.
* @param {string} params.name
* A name used only for debugging.
* @param {functon} params.register
* A function called whenever a new listener is added.
* @param {boolean} [params.inputHandling=false]
* If true, the "handling user input" flag is set while handlers
* for this event are executing.
*/
constructor(params) {
// Maintain compatibility with the old EventManager API in which
// the constructor took parameters (contest, name, register).
// Remove this in bug 1451212.
if (arguments.length > 1) {
[this.context, this.name, this.register] = arguments;
this.inputHandling = false;
} else {
let {context, name, register, inputHandling = false} = params;
this.context = context;
this.name = name;
this.register = register;
this.inputHandling = inputHandling;
}
this.unregister = new Map();
}
EventManager.prototype = {
addListener(callback, ...args) {
if (this.unregister.has(callback)) {
return;
@ -1819,11 +1835,10 @@ EventManager.prototype = {
},
};
let unregister = this.register(fire, ...args);
this.unregister.set(callback, unregister);
this.context.callOnClose(this);
},
}
removeListener(callback) {
if (!this.unregister.has(callback)) {
@ -1840,21 +1855,21 @@ EventManager.prototype = {
if (this.unregister.size == 0) {
this.context.forgetOnClose(this);
}
},
}
hasListener(callback) {
return this.unregister.has(callback);
},
}
revoke() {
for (let callback of this.unregister.keys()) {
this.removeListener(callback);
}
},
}
close() {
this.revoke();
},
}
api() {
return {
@ -1864,8 +1879,8 @@ EventManager.prototype = {
setUserInput: this.inputHandling,
[Schemas.REVOKE]: () => this.revoke(),
};
},
};
}
}
// Simple API for event listeners where events never fire.
function ignoreEvent(context, name) {

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

@ -148,20 +148,24 @@ this.storage = class extends ExtensionAPI {
},
},
onChanged: new EventManager(context, "storage.onChanged", fire => {
let onChanged = (data, area) => {
let changes = new context.cloneScope.Object();
for (let [key, value] of Object.entries(data)) {
changes[key] = deserialize(value);
}
fire.raw(changes, area);
};
onChanged: new EventManager({
context,
name: "storage.onChanged",
register: fire => {
let onChanged = (data, area) => {
let changes = new context.cloneScope.Object();
for (let [key, value] of Object.entries(data)) {
changes[key] = deserialize(value);
}
fire.raw(changes, area);
};
let parent = context.childManager.getParentEvent("storage.onChanged");
parent.addListener(onChanged);
return () => {
parent.removeListener(onChanged);
};
let parent = context.childManager.getParentEvent("storage.onChanged");
parent.addListener(onChanged);
return () => {
parent.removeListener(onChanged);
};
},
}).api(),
},
};

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

@ -177,15 +177,19 @@ this.test = class extends ExtensionAPI {
}
},
onMessage: new TestEventManager(context, "test.onMessage", fire => {
let handler = (event, ...args) => {
fire.async(...args);
};
onMessage: new TestEventManager({
context,
name: "test.onMessage",
register: fire => {
let handler = (event, ...args) => {
fire.async(...args);
};
extension.on("test-harness-message", handler);
return () => {
extension.off("test-harness-message", handler);
};
extension.on("test-harness-message", handler);
return () => {
extension.off("test-harness-message", handler);
};
},
}).api(),
},
};

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

@ -125,15 +125,19 @@ this.alarms = class extends ExtensionAPI {
return Promise.resolve(cleared);
},
onAlarm: new EventManager(context, "alarms.onAlarm", fire => {
let callback = alarm => {
fire.sync(alarm.data);
};
onAlarm: new EventManager({
context,
name: "alarms.onAlarm",
register: fire => {
let callback = alarm => {
fire.sync(alarm.data);
};
self.callbacks.add(callback);
return () => {
self.callbacks.delete(callback);
};
self.callbacks.add(callback);
return () => {
self.callbacks.delete(callback);
};
},
}).api(),
},
};

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

@ -224,46 +224,58 @@ this.contextualIdentities = class extends ExtensionAPI {
return convertedIdentity;
},
onCreated: new EventManager(context, "contextualIdentities.onCreated", fire => {
let observer = (subject, topic) => {
let convertedIdentity = convertIdentityFromObserver(subject);
if (convertedIdentity) {
fire.async({contextualIdentity: convertedIdentity});
}
};
onCreated: new EventManager({
context,
name: "contextualIdentities.onCreated",
register: fire => {
let observer = (subject, topic) => {
let convertedIdentity = convertIdentityFromObserver(subject);
if (convertedIdentity) {
fire.async({contextualIdentity: convertedIdentity});
}
};
Services.obs.addObserver(observer, "contextual-identity-created");
return () => {
Services.obs.removeObserver(observer, "contextual-identity-created");
};
Services.obs.addObserver(observer, "contextual-identity-created");
return () => {
Services.obs.removeObserver(observer, "contextual-identity-created");
};
},
}).api(),
onUpdated: new EventManager(context, "contextualIdentities.onUpdated", fire => {
let observer = (subject, topic) => {
let convertedIdentity = convertIdentityFromObserver(subject);
if (convertedIdentity) {
fire.async({contextualIdentity: convertedIdentity});
}
};
onUpdated: new EventManager({
context,
name: "contextualIdentities.onUpdated",
register: fire => {
let observer = (subject, topic) => {
let convertedIdentity = convertIdentityFromObserver(subject);
if (convertedIdentity) {
fire.async({contextualIdentity: convertedIdentity});
}
};
Services.obs.addObserver(observer, "contextual-identity-updated");
return () => {
Services.obs.removeObserver(observer, "contextual-identity-updated");
};
Services.obs.addObserver(observer, "contextual-identity-updated");
return () => {
Services.obs.removeObserver(observer, "contextual-identity-updated");
};
},
}).api(),
onRemoved: new EventManager(context, "contextualIdentities.onRemoved", fire => {
let observer = (subject, topic) => {
let convertedIdentity = convertIdentityFromObserver(subject);
if (convertedIdentity) {
fire.async({contextualIdentity: convertedIdentity});
}
};
onRemoved: new EventManager({
context,
name: "contextualIdentities.onRemoved",
register: fire => {
let observer = (subject, topic) => {
let convertedIdentity = convertIdentityFromObserver(subject);
if (convertedIdentity) {
fire.async({contextualIdentity: convertedIdentity});
}
};
Services.obs.addObserver(observer, "contextual-identity-deleted");
return () => {
Services.obs.removeObserver(observer, "contextual-identity-deleted");
};
Services.obs.addObserver(observer, "contextual-identity-deleted");
return () => {
Services.obs.removeObserver(observer, "contextual-identity-deleted");
};
},
}).api(),
},

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

@ -412,48 +412,52 @@ this.cookies = class extends ExtensionAPI {
return Promise.resolve(result);
},
onChanged: new EventManager(context, "cookies.onChanged", fire => {
let observer = (subject, topic, data) => {
let notify = (removed, cookie, cause) => {
cookie.QueryInterface(Ci.nsICookie2);
onChanged: new EventManager({
context,
name: "cookies.onChanged",
register: fire => {
let observer = (subject, topic, data) => {
let notify = (removed, cookie, cause) => {
cookie.QueryInterface(Ci.nsICookie2);
if (extension.whiteListedHosts.matchesCookie(cookie)) {
fire.async({removed, cookie: convertCookie({cookie, isPrivate: topic == "private-cookie-changed"}), cause});
if (extension.whiteListedHosts.matchesCookie(cookie)) {
fire.async({removed, cookie: convertCookie({cookie, isPrivate: topic == "private-cookie-changed"}), cause});
}
};
// We do our best effort here to map the incompatible states.
switch (data) {
case "deleted":
notify(true, subject, "explicit");
break;
case "added":
notify(false, subject, "explicit");
break;
case "changed":
notify(true, subject, "overwrite");
notify(false, subject, "explicit");
break;
case "batch-deleted":
subject.QueryInterface(Ci.nsIArray);
for (let i = 0; i < subject.length; i++) {
let cookie = subject.queryElementAt(i, Ci.nsICookie2);
if (!cookie.isSession && cookie.expiry * 1000 <= Date.now()) {
notify(true, cookie, "expired");
} else {
notify(true, cookie, "evicted");
}
}
break;
}
};
// We do our best effort here to map the incompatible states.
switch (data) {
case "deleted":
notify(true, subject, "explicit");
break;
case "added":
notify(false, subject, "explicit");
break;
case "changed":
notify(true, subject, "overwrite");
notify(false, subject, "explicit");
break;
case "batch-deleted":
subject.QueryInterface(Ci.nsIArray);
for (let i = 0; i < subject.length; i++) {
let cookie = subject.queryElementAt(i, Ci.nsICookie2);
if (!cookie.isSession && cookie.expiry * 1000 <= Date.now()) {
notify(true, cookie, "expired");
} else {
notify(true, cookie, "evicted");
}
}
break;
}
};
Services.obs.addObserver(observer, "cookie-changed");
Services.obs.addObserver(observer, "private-cookie-changed");
return () => {
Services.obs.removeObserver(observer, "cookie-changed");
Services.obs.removeObserver(observer, "private-cookie-changed");
};
Services.obs.addObserver(observer, "cookie-changed");
Services.obs.addObserver(observer, "private-cookie-changed");
return () => {
Services.obs.removeObserver(observer, "cookie-changed");
Services.obs.removeObserver(observer, "private-cookie-changed");
};
},
}).api(),
},
};

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

@ -765,60 +765,72 @@ this.downloads = class extends ExtensionAPI {
// ...
// }
onChanged: new EventManager(context, "downloads.onChanged", fire => {
const handler = (what, item) => {
let changes = {};
const noundef = val => (val === undefined) ? null : val;
DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => {
if (item[fld] != item.prechange[fld]) {
changes[fld] = {
previous: noundef(item.prechange[fld]),
current: noundef(item[fld]),
};
onChanged: new EventManager({
context,
name: "downloads.onChanged",
register: fire => {
const handler = (what, item) => {
let changes = {};
const noundef = val => (val === undefined) ? null : val;
DOWNLOAD_ITEM_CHANGE_FIELDS.forEach(fld => {
if (item[fld] != item.prechange[fld]) {
changes[fld] = {
previous: noundef(item.prechange[fld]),
current: noundef(item[fld]),
};
}
});
if (Object.keys(changes).length > 0) {
changes.id = item.id;
fire.async(changes);
}
});
if (Object.keys(changes).length > 0) {
changes.id = item.id;
fire.async(changes);
}
};
};
let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("change", handler);
});
return () => {
registerPromise.then(() => {
DownloadMap.off("change", handler);
let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("change", handler);
});
};
return () => {
registerPromise.then(() => {
DownloadMap.off("change", handler);
});
};
},
}).api(),
onCreated: new EventManager(context, "downloads.onCreated", fire => {
const handler = (what, item) => {
fire.async(item.serialize());
};
let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("create", handler);
});
return () => {
registerPromise.then(() => {
DownloadMap.off("create", handler);
onCreated: new EventManager({
context,
name: "downloads.onCreated",
register: fire => {
const handler = (what, item) => {
fire.async(item.serialize());
};
let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("create", handler);
});
};
return () => {
registerPromise.then(() => {
DownloadMap.off("create", handler);
});
};
},
}).api(),
onErased: new EventManager(context, "downloads.onErased", fire => {
const handler = (what, item) => {
fire.async(item.id);
};
let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("erase", handler);
});
return () => {
registerPromise.then(() => {
DownloadMap.off("erase", handler);
onErased: new EventManager({
context,
name: "downloads.onErased",
register: fire => {
const handler = (what, item) => {
fire.async(item.id);
};
let registerPromise = DownloadMap.getDownloadList().then(() => {
DownloadMap.on("erase", handler);
});
};
return () => {
registerPromise.then(() => {
DownloadMap.off("erase", handler);
});
};
},
}).api(),
onDeterminingFilename: ignoreEvent(context, "downloads.onDeterminingFilename"),

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

@ -70,15 +70,19 @@ this.idle = class extends ExtensionAPI {
setDetectionInterval: function(detectionIntervalInSeconds) {
setDetectionInterval(extension, context, detectionIntervalInSeconds);
},
onStateChanged: new EventManager(context, "idle.onStateChanged", fire => {
let listener = (event, data) => {
fire.sync(data);
};
onStateChanged: new EventManager({
context,
name: "idle.onStateChanged",
register: fire => {
let listener = (event, data) => {
fire.sync(data);
};
getIdleObserver(extension, context).on("stateChanged", listener);
return () => {
getIdleObserver(extension, context).off("stateChanged", listener);
};
getIdleObserver(extension, context).on("stateChanged", listener);
return () => {
getIdleObserver(extension, context).off("stateChanged", listener);
};
},
}).api(),
},
};

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

@ -230,48 +230,64 @@ this.management = class extends ExtensionAPI {
addon.userDisabled = !enabled;
},
onDisabled: new EventManager(context, "management.onDisabled", fire => {
let listener = (event, data) => {
fire.async(data);
};
onDisabled: new EventManager({
context,
name: "management.onDisabled",
register: fire => {
let listener = (event, data) => {
fire.async(data);
};
getManagementListener(extension, context).on("onDisabled", listener);
return () => {
getManagementListener(extension, context).off("onDisabled", listener);
};
getManagementListener(extension, context).on("onDisabled", listener);
return () => {
getManagementListener(extension, context).off("onDisabled", listener);
};
},
}).api(),
onEnabled: new EventManager(context, "management.onEnabled", fire => {
let listener = (event, data) => {
fire.async(data);
};
onEnabled: new EventManager({
context,
name: "management.onEnabled",
register: fire => {
let listener = (event, data) => {
fire.async(data);
};
getManagementListener(extension, context).on("onEnabled", listener);
return () => {
getManagementListener(extension, context).off("onEnabled", listener);
};
getManagementListener(extension, context).on("onEnabled", listener);
return () => {
getManagementListener(extension, context).off("onEnabled", listener);
};
},
}).api(),
onInstalled: new EventManager(context, "management.onInstalled", fire => {
let listener = (event, data) => {
fire.async(data);
};
onInstalled: new EventManager({
context,
name: "management.onInstalled",
register: fire => {
let listener = (event, data) => {
fire.async(data);
};
getManagementListener(extension, context).on("onInstalled", listener);
return () => {
getManagementListener(extension, context).off("onInstalled", listener);
};
getManagementListener(extension, context).on("onInstalled", listener);
return () => {
getManagementListener(extension, context).off("onInstalled", listener);
};
},
}).api(),
onUninstalled: new EventManager(context, "management.onUninstalled", fire => {
let listener = (event, data) => {
fire.async(data);
};
onUninstalled: new EventManager({
context,
name: "management.onUninstalled",
register: fire => {
let listener = (event, data) => {
fire.async(data);
};
getManagementListener(extension, context).on("onUninstalled", listener);
return () => {
getManagementListener(extension, context).off("onUninstalled", listener);
};
getManagementListener(extension, context).on("onUninstalled", listener);
return () => {
getManagementListener(extension, context).off("onUninstalled", listener);
};
},
}).api(),
},

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

@ -117,38 +117,50 @@ this.notifications = class extends ExtensionAPI {
return Promise.resolve(result);
},
onClosed: new EventManager(context, "notifications.onClosed", fire => {
let listener = (event, notificationId) => {
// TODO Bug 1413188, Support the byUser argument.
fire.async(notificationId, true);
};
onClosed: new EventManager({
context,
name: "notifications.onClosed",
register: fire => {
let listener = (event, notificationId) => {
// TODO Bug 1413188, Support the byUser argument.
fire.async(notificationId, true);
};
notificationsMap.on("closed", listener);
return () => {
notificationsMap.off("closed", listener);
};
notificationsMap.on("closed", listener);
return () => {
notificationsMap.off("closed", listener);
};
},
}).api(),
onClicked: new EventManager(context, "notifications.onClicked", fire => {
let listener = (event, notificationId) => {
fire.async(notificationId, true);
};
onClicked: new EventManager({
context,
name: "notifications.onClicked",
register: fire => {
let listener = (event, notificationId) => {
fire.async(notificationId, true);
};
notificationsMap.on("clicked", listener);
return () => {
notificationsMap.off("clicked", listener);
};
notificationsMap.on("clicked", listener);
return () => {
notificationsMap.off("clicked", listener);
};
},
}).api(),
onShown: new EventManager(context, "notifications.onShown", fire => {
let listener = (event, notificationId) => {
fire.async(notificationId, true);
};
onShown: new EventManager({
context,
name: "notifications.onShown",
register: fire => {
let listener = (event, notificationId) => {
fire.async(notificationId, true);
};
notificationsMap.on("shown", listener);
return () => {
notificationsMap.off("shown", listener);
};
notificationsMap.on("shown", listener);
return () => {
notificationsMap.off("shown", listener);
};
},
}).api(),
// TODO Bug 1190681, implement button support.

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

@ -43,7 +43,7 @@ class ProxyFilterEventManager extends EventManager {
};
};
super(context, name, register);
super({context, name, register});
}
}
@ -61,14 +61,18 @@ this.proxy = class extends ExtensionAPI {
getAPI(context) {
let {extension} = context;
let onError = new EventManager(context, "proxy.onError", fire => {
let listener = (name, error) => {
fire.async(error);
};
extension.on("proxy-error", listener);
return () => {
extension.off("proxy-error", listener);
};
let onError = new EventManager({
context,
name: "proxy.onError",
register: fire => {
let listener = (name, error) => {
fire.async(error);
};
extension.on("proxy-error", listener);
return () => {
extension.off("proxy-error", listener);
};
},
}).api();
return {

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

@ -16,64 +16,76 @@ this.runtime = class extends ExtensionAPI {
let {extension} = context;
return {
runtime: {
onStartup: new EventManager(context, "runtime.onStartup", fire => {
if (context.incognito) {
// This event should not fire if we are operating in a private profile.
return () => {};
}
let listener = () => {
if (extension.startupReason === "APP_STARTUP") {
fire.sync();
onStartup: new EventManager({
context,
name: "runtime.onStartup",
register: fire => {
if (context.incognito) {
// This event should not fire if we are operating in a private profile.
return () => {};
}
};
extension.on("startup", listener);
return () => {
extension.off("startup", listener);
};
}).api(),
onInstalled: new EventManager(context, "runtime.onInstalled", fire => {
let temporary = !!extension.addonData.temporarilyInstalled;
let listener = () => {
switch (extension.startupReason) {
case "APP_STARTUP":
if (AddonManagerPrivate.browserUpdated) {
fire.sync({reason: "browser_update", temporary});
}
break;
case "ADDON_INSTALL":
fire.sync({reason: "install", temporary});
break;
case "ADDON_UPGRADE":
fire.sync({
reason: "update",
previousVersion: extension.addonData.oldVersion,
temporary,
});
break;
}
};
extension.on("startup", listener);
return () => {
extension.off("startup", listener);
};
}).api(),
onUpdateAvailable: new EventManager(context, "runtime.onUpdateAvailable", fire => {
let instanceID = extension.addonData.instanceID;
AddonManager.addUpgradeListener(instanceID, upgrade => {
extension.upgrade = upgrade;
let details = {
version: upgrade.version,
let listener = () => {
if (extension.startupReason === "APP_STARTUP") {
fire.sync();
}
};
fire.sync(details);
});
return () => {
AddonManager.removeUpgradeListener(instanceID).catch(e => {
// This can happen if we try this after shutdown is complete.
extension.on("startup", listener);
return () => {
extension.off("startup", listener);
};
},
}).api(),
onInstalled: new EventManager({
context,
name: "runtime.onInstalled",
register: fire => {
let temporary = !!extension.addonData.temporarilyInstalled;
let listener = () => {
switch (extension.startupReason) {
case "APP_STARTUP":
if (AddonManagerPrivate.browserUpdated) {
fire.sync({reason: "browser_update", temporary});
}
break;
case "ADDON_INSTALL":
fire.sync({reason: "install", temporary});
break;
case "ADDON_UPGRADE":
fire.sync({
reason: "update",
previousVersion: extension.addonData.oldVersion,
temporary,
});
break;
}
};
extension.on("startup", listener);
return () => {
extension.off("startup", listener);
};
},
}).api(),
onUpdateAvailable: new EventManager({
context,
name: "runtime.onUpdateAvailable",
register: fire => {
let instanceID = extension.addonData.instanceID;
AddonManager.addUpgradeListener(instanceID, upgrade => {
extension.upgrade = upgrade;
let details = {
version: upgrade.version,
};
fire.sync(details);
});
};
return () => {
AddonManager.removeUpgradeListener(instanceID).catch(e => {
// This can happen if we try this after shutdown is complete.
});
};
},
}).api(),
reload: () => {

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

@ -89,20 +89,24 @@ this.storage = class extends ExtensionAPI {
},
},
onChanged: new EventManager(context, "storage.onChanged", fire => {
let listenerLocal = changes => {
fire.raw(changes, "local");
};
let listenerSync = changes => {
fire.async(changes, "sync");
};
onChanged: new EventManager({
context,
name: "storage.onChanged",
register: fire => {
let listenerLocal = changes => {
fire.raw(changes, "local");
};
let listenerSync = changes => {
fire.async(changes, "sync");
};
ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
extensionStorageSync.addOnChangedListener(extension, listenerSync, context);
return () => {
ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
extensionStorageSync.removeOnChangedListener(extension, listenerSync);
};
ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
extensionStorageSync.addOnChangedListener(extension, listenerSync, context);
return () => {
ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
extensionStorageSync.removeOnChangedListener(extension, listenerSync);
};
},
}).api(),
},
};

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

@ -411,19 +411,23 @@ this.theme = class extends ExtensionAPI {
Theme.unload(windowId);
},
onUpdated: new EventManager(context, "theme.onUpdated", fire => {
let callback = (event, theme, windowId) => {
if (windowId) {
fire.async({theme, windowId});
} else {
fire.async({theme});
}
};
onUpdated: new EventManager({
context,
name: "theme.onUpdated",
register: fire => {
let callback = (event, theme, windowId) => {
if (windowId) {
fire.async({theme, windowId});
} else {
fire.async({theme});
}
};
onUpdatedEmitter.on("theme-updated", callback);
return () => {
onUpdatedEmitter.off("theme-updated", callback);
};
onUpdatedEmitter.on("theme-updated", callback);
return () => {
onUpdatedEmitter.off("theme-updated", callback);
};
},
}).api(),
},
};

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

@ -21,12 +21,6 @@ ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm");
global.EventEmitter = ExtensionUtils.EventEmitter;
global.EventManager = ExtensionCommon.EventManager;
global.InputEventManager = class extends EventManager {
constructor(...args) {
super(...args);
this.inputHandling = true;
}
};
/* globals DEFAULT_STORE, PRIVATE_STORE, CONTAINER_STORE */

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

@ -90,61 +90,61 @@ const fillTransitionProperties = (eventName, src, dst) => {
};
// Similar to WebRequestEventManager but for WebNavigation.
function WebNavigationEventManager(context, eventName) {
let name = `webNavigation.${eventName}`;
let register = (fire, urlFilters) => {
// Don't create a MatchURLFilters instance if the listener does not include any filter.
let filters = urlFilters ? new MatchURLFilters(urlFilters.url) : null;
class WebNavigationEventManager extends EventManager {
constructor(context, eventName) {
let name = `webNavigation.${eventName}`;
let register = (fire, urlFilters) => {
// Don't create a MatchURLFilters instance if the listener does not include any filter.
let filters = urlFilters ? new MatchURLFilters(urlFilters.url) : null;
let listener = data => {
if (!data.browser) {
return;
}
let listener = data => {
if (!data.browser) {
return;
}
let data2 = {
url: data.url,
timeStamp: Date.now(),
let data2 = {
url: data.url,
timeStamp: Date.now(),
};
if (eventName == "onErrorOccurred") {
data2.error = data.error;
}
if (data.frameId != undefined) {
data2.frameId = data.frameId;
data2.parentFrameId = data.parentFrameId;
}
if (data.sourceFrameId != undefined) {
data2.sourceFrameId = data.sourceFrameId;
}
// Fills in tabId typically.
Object.assign(data2, tabTracker.getBrowserData(data.browser));
if (data2.tabId < 0) {
return;
}
if (data.sourceTabBrowser) {
data2.sourceTabId = tabTracker.getBrowserData(data.sourceTabBrowser).tabId;
}
fillTransitionProperties(eventName, data, data2);
fire.async(data2);
};
if (eventName == "onErrorOccurred") {
data2.error = data.error;
}
if (data.frameId != undefined) {
data2.frameId = data.frameId;
data2.parentFrameId = data.parentFrameId;
}
if (data.sourceFrameId != undefined) {
data2.sourceFrameId = data.sourceFrameId;
}
// Fills in tabId typically.
Object.assign(data2, tabTracker.getBrowserData(data.browser));
if (data2.tabId < 0) {
return;
}
if (data.sourceTabBrowser) {
data2.sourceTabId = tabTracker.getBrowserData(data.sourceTabBrowser).tabId;
}
fillTransitionProperties(eventName, data, data2);
fire.async(data2);
WebNavigation[eventName].addListener(listener, filters);
return () => {
WebNavigation[eventName].removeListener(listener);
};
};
WebNavigation[eventName].addListener(listener, filters);
return () => {
WebNavigation[eventName].removeListener(listener);
};
};
return EventManager.call(this, context, name, register);
super({context, name, register});
}
}
WebNavigationEventManager.prototype = Object.create(EventManager.prototype);
const convertGetFrameResult = (tabId, data) => {
return {
errorOccurred: data.errorOccurred,
@ -161,8 +161,12 @@ this.webNavigation = class extends ExtensionAPI {
return {
webNavigation: {
onTabReplaced: new EventManager(context, "webNavigation.onTabReplaced", fire => {
return () => {};
onTabReplaced: new EventManager({
context,
name: "webNavigation.onTabReplaced",
register: fire => {
return () => {};
},
}).api(),
onBeforeNavigate: new WebNavigationEventManager(context, "onBeforeNavigate").api(),
onCommitted: new WebNavigationEventManager(context, "onCommitted").api(),

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

@ -81,24 +81,22 @@ function WebRequestEventManager(context, eventName) {
};
};
return EventManager.call(this, context, name, register);
return new EventManager({context, name, register}).api();
}
WebRequestEventManager.prototype = Object.create(EventManager.prototype);
this.webRequest = class extends ExtensionAPI {
getAPI(context) {
return {
webRequest: {
onBeforeRequest: new WebRequestEventManager(context, "onBeforeRequest").api(),
onBeforeSendHeaders: new WebRequestEventManager(context, "onBeforeSendHeaders").api(),
onSendHeaders: new WebRequestEventManager(context, "onSendHeaders").api(),
onHeadersReceived: new WebRequestEventManager(context, "onHeadersReceived").api(),
onAuthRequired: new WebRequestEventManager(context, "onAuthRequired").api(),
onBeforeRedirect: new WebRequestEventManager(context, "onBeforeRedirect").api(),
onResponseStarted: new WebRequestEventManager(context, "onResponseStarted").api(),
onErrorOccurred: new WebRequestEventManager(context, "onErrorOccurred").api(),
onCompleted: new WebRequestEventManager(context, "onCompleted").api(),
onBeforeRequest: WebRequestEventManager(context, "onBeforeRequest"),
onBeforeSendHeaders: WebRequestEventManager(context, "onBeforeSendHeaders"),
onSendHeaders: WebRequestEventManager(context, "onSendHeaders"),
onHeadersReceived: WebRequestEventManager(context, "onHeadersReceived"),
onAuthRequired: WebRequestEventManager(context, "onAuthRequired"),
onBeforeRedirect: WebRequestEventManager(context, "onBeforeRedirect"),
onResponseStarted: WebRequestEventManager(context, "onResponseStarted"),
onErrorOccurred: WebRequestEventManager(context, "onErrorOccurred"),
onCompleted: WebRequestEventManager(context, "onCompleted"),
handlerBehaviorChanged: function() {
// TODO: Flush all caches.
},

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

@ -65,11 +65,15 @@ add_task(async function test_post_unload_listeners() {
let context = new StubContext();
let fire;
let manager = new EventManager(context, "EventManager", _fire => {
fire = () => {
_fire.async();
};
return () => {};
let manager = new EventManager({
context,
name: "EventManager",
register: _fire => {
fire = () => {
_fire.async();
};
return () => {};
},
});
let fail = event => {