Bug 1203330 Part 2 Convert EventManager uses to SingletonEventManager draft
authorAndrew Swan <aswan@mozilla.com>
Thu, 26 Jan 2017 11:27:03 -0800
changeset 467098 aa0b2a64e4f4d3976b216721e21ae9ed95dde3f6
parent 467097 7f3116c0c84cf48b9d8e1656f451c4e764299917
child 467099 f49fd5060cbcb1a25035d6d20008f0207204aa53
push id43100
push useraswan@mozilla.com
push dateFri, 27 Jan 2017 04:03:36 +0000
bugs1203330
milestone54.0a1
Bug 1203330 Part 2 Convert EventManager uses to SingletonEventManager MozReview-Commit-ID: A12TPwAYzTS
browser/components/extensions/ext-browserAction.js
browser/components/extensions/ext-commands.js
browser/components/extensions/ext-contextMenus.js
browser/components/extensions/ext-pageAction.js
browser/components/extensions/ext-tabs.js
browser/components/extensions/ext-utils.js
browser/components/extensions/ext-windows.js
toolkit/components/extensions/ExtensionChild.jsm
toolkit/components/extensions/ext-alarms.js
toolkit/components/extensions/ext-cookies.js
toolkit/components/extensions/ext-notifications.js
toolkit/components/extensions/ext-storage.js
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -13,17 +13,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "@mozilla.org/inspector/dom-utils;1",
                                    "inIDOMUtils");
 
 Cu.import("resource://devtools/shared/event-emitter.js");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 var {
-  EventManager,
+  SingletonEventManager,
   IconDetails,
 } = ExtensionUtils;
 
 const POPUP_PRELOAD_TIMEOUT_MS = 200;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 function isAncestorOrSelf(target, node) {
@@ -441,20 +441,20 @@ extensions.on("shutdown", (type, extensi
   }
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 extensions.registerSchemaAPI("browserAction", "addon_parent", context => {
   let {extension} = context;
   return {
     browserAction: {
-      onClicked: new EventManager(context, "browserAction.onClicked", fire => {
+      onClicked: new SingletonEventManager(context, "browserAction.onClicked", fire => {
         let listener = () => {
           let tab = TabManager.activeTab;
-          fire(TabManager.convert(extension, tab));
+          fire.async(TabManager.convert(extension, tab));
         };
         BrowserAction.for(extension).on("click", listener);
         return () => {
           BrowserAction.for(extension).off("click", listener);
         };
       }).api(),
 
       enable: function(tabId) {
--- a/browser/components/extensions/ext-commands.js
+++ b/browser/components/extensions/ext-commands.js
@@ -1,17 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 Cu.import("resource://devtools/shared/event-emitter.js");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
-  EventManager,
+  SingletonEventManager,
   PlatformInfo,
 } = ExtensionUtils;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 // WeakMap[Extension -> CommandList]
 var commandsMap = new WeakMap();
 
@@ -240,19 +240,19 @@ extensions.registerSchemaAPI("commands",
         return Promise.resolve(Array.from(commands, ([name, command]) => {
           return ({
             name,
             description: command.description,
             shortcut: command.shortcut,
           });
         }));
       },
-      onCommand: new EventManager(context, "commands.onCommand", fire => {
+      onCommand: new SingletonEventManager(context, "commands.onCommand", fire => {
         let listener = (eventName, commandName) => {
-          fire(commandName);
+          fire.async(commandName);
         };
         commandsMap.get(extension).on("command", listener);
         return () => {
           commandsMap.get(extension).off("command", listener);
         };
       }).api(),
     },
   };
--- a/browser/components/extensions/ext-contextMenus.js
+++ b/browser/components/extensions/ext-contextMenus.js
@@ -3,19 +3,19 @@
 "use strict";
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/MatchPattern.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var {
-  EventManager,
   ExtensionError,
   IconDetails,
+  SingletonEventManager,
 } = ExtensionUtils;
 
 const ACTION_MENU_TOP_LEVEL_LIMIT = 6;
 
 // Map[Extension -> Map[ID -> MenuItem]]
 // Note: we want to enumerate all the menu items so
 // this cannot be a weak map.
 var gContextMenuMap = new Map();
@@ -620,19 +620,19 @@ extensions.registerSchemaAPI("contextMen
 
       removeAll: function() {
         let root = gRootItems.get(extension);
         if (root) {
           root.remove();
         }
       },
 
-      onClicked: new EventManager(context, "contextMenus.onClicked", fire => {
+      onClicked: new SingletonEventManager(context, "contextMenus.onClicked", fire => {
         let listener = (event, info, tab) => {
-          fire(info, tab);
+          fire.async(info, tab);
         };
 
         extension.on("webext-contextmenu-menuitem-click", listener);
         return () => {
           extension.off("webext-contextmenu-menuitem-click", listener);
         };
       }).api(),
     },
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -1,16 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
-  EventManager,
+  SingletonEventManager,
   IconDetails,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> PageAction]
 var pageActionMap = new WeakMap();
 
 // Handles URL bar icons, including the |page_action| manifest entry
 // and associated API.
@@ -239,19 +239,19 @@ PageAction.for = extension => {
 };
 
 global.pageActionFor = PageAction.for;
 
 extensions.registerSchemaAPI("pageAction", "addon_parent", context => {
   let {extension} = context;
   return {
     pageAction: {
-      onClicked: new EventManager(context, "pageAction.onClicked", fire => {
+      onClicked: new SingletonEventManager(context, "pageAction.onClicked", fire => {
         let listener = (evt, tab) => {
-          fire(TabManager.convert(extension, tab));
+          fire.async(TabManager.convert(extension, tab));
         };
         let pageAction = PageAction.for(extension);
 
         pageAction.on("click", listener);
         return () => {
           pageAction.off("click", listener);
         };
       }).api(),
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -13,17 +13,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
-  EventManager,
+  SingletonEventManager,
   ignoreEvent,
 } = ExtensionUtils;
 
 // This function is pretty tightly tied to Extension.jsm.
 // Its job is to fill in the |tab| property of the sender.
 function getSender(extension, target, sender) {
   let tabId;
   if ("tabId" in sender) {
@@ -278,22 +278,22 @@ extensions.on("startup", () => {
 extensions.registerSchemaAPI("tabs", "addon_parent", context => {
   let {extension} = context;
   let self = {
     tabs: {
       onActivated: new WindowEventManager(context, "tabs.onActivated", "TabSelect", (fire, event) => {
         let tab = event.originalTarget;
         let tabId = TabManager.getId(tab);
         let windowId = WindowManager.getId(tab.ownerGlobal);
-        fire({tabId, windowId});
+        fire.async({tabId, windowId});
       }).api(),
 
-      onCreated: new EventManager(context, "tabs.onCreated", fire => {
+      onCreated: new SingletonEventManager(context, "tabs.onCreated", fire => {
         let listener = (eventName, event) => {
-          fire(TabManager.convert(extension, event.tab));
+          fire.async(TabManager.convert(extension, event.tab));
         };
 
         tabListener.on("tab-created", listener);
         return () => {
           tabListener.off("tab-created", listener);
         };
       }).api(),
 
@@ -302,55 +302,55 @@ extensions.registerSchemaAPI("tabs", "ad
        * essentially acts an alias for self.tabs.onActivated but returns
        * the tabId in an array to match the API.
        * @see  https://developer.mozilla.org/en-US/Add-ons/WebExtensions/API/Tabs/onHighlighted
       */
       onHighlighted: new WindowEventManager(context, "tabs.onHighlighted", "TabSelect", (fire, event) => {
         let tab = event.originalTarget;
         let tabIds = [TabManager.getId(tab)];
         let windowId = WindowManager.getId(tab.ownerGlobal);
-        fire({tabIds, windowId});
+        fire.async({tabIds, windowId});
       }).api(),
 
-      onAttached: new EventManager(context, "tabs.onAttached", fire => {
+      onAttached: new SingletonEventManager(context, "tabs.onAttached", fire => {
         let listener = (eventName, event) => {
-          fire(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
+          fire.async(event.tabId, {newWindowId: event.newWindowId, newPosition: event.newPosition});
         };
 
         tabListener.on("tab-attached", listener);
         return () => {
           tabListener.off("tab-attached", listener);
         };
       }).api(),
 
-      onDetached: new EventManager(context, "tabs.onDetached", fire => {
+      onDetached: new SingletonEventManager(context, "tabs.onDetached", fire => {
         let listener = (eventName, event) => {
-          fire(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
+          fire.async(event.tabId, {oldWindowId: event.oldWindowId, oldPosition: event.oldPosition});
         };
 
         tabListener.on("tab-detached", listener);
         return () => {
           tabListener.off("tab-detached", listener);
         };
       }).api(),
 
-      onRemoved: new EventManager(context, "tabs.onRemoved", fire => {
+      onRemoved: new SingletonEventManager(context, "tabs.onRemoved", fire => {
         let listener = (eventName, event) => {
-          fire(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
+          fire.async(event.tabId, {windowId: event.windowId, isWindowClosing: event.isWindowClosing});
         };
 
         tabListener.on("tab-removed", listener);
         return () => {
           tabListener.off("tab-removed", listener);
         };
       }).api(),
 
       onReplaced: ignoreEvent(context, "tabs.onReplaced"),
 
-      onMoved: new EventManager(context, "tabs.onMoved", fire => {
+      onMoved: new SingletonEventManager(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.
@@ -368,32 +368,32 @@ extensions.registerSchemaAPI("tabs", "ad
         let moveListener = event => {
           let tab = event.originalTarget;
 
           if (ignoreNextMove.has(tab)) {
             ignoreNextMove.delete(tab);
             return;
           }
 
-          fire(TabManager.getId(tab), {
+          fire.async(TabManager.getId(tab), {
             windowId: WindowManager.getId(tab.ownerGlobal),
             fromIndex: event.detail,
             toIndex: tab._tPos,
           });
         };
 
         AllWindowEvents.addListener("TabMove", moveListener);
         AllWindowEvents.addListener("TabOpen", openListener);
         return () => {
           AllWindowEvents.removeListener("TabMove", moveListener);
           AllWindowEvents.removeListener("TabOpen", openListener);
         };
       }).api(),
 
-      onUpdated: new EventManager(context, "tabs.onUpdated", fire => {
+      onUpdated: new SingletonEventManager(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;
@@ -405,17 +405,17 @@ extensions.registerSchemaAPI("tabs", "ad
 
         let fireForBrowser = (browser, changed) => {
           let [needed, changeInfo] = sanitize(extension, changed);
           if (needed) {
             let gBrowser = browser.ownerGlobal.gBrowser;
             let tabElem = gBrowser.getTabForBrowser(browser);
 
             let tab = TabManager.convert(extension, tabElem);
-            fire(tab.id, changeInfo, tab);
+            fire.async(tab.id, changeInfo, tab);
           }
         };
 
         let listener = event => {
           let needed = [];
           if (event.type == "TabAttrModified") {
             let changed = event.detail.changed;
             if (changed.includes("image")) {
@@ -442,17 +442,17 @@ extensions.registerSchemaAPI("tabs", "ad
 
           if (needed.length) {
             let tab = TabManager.convert(extension, event.originalTarget);
 
             let changeInfo = {};
             for (let prop of needed) {
               changeInfo[prop] = tab[prop];
             }
-            fire(tab.id, changeInfo, tab);
+            fire.async(tab.id, changeInfo, tab);
           }
         };
         let progressListener = {
           onStateChange(browser, webProgress, request, stateFlags, statusCode) {
             if (!webProgress.isTopLevel) {
               return;
             }
 
@@ -1020,17 +1020,17 @@ extensions.registerSchemaAPI("tabs", "ad
         let currentSettings = this._getZoomSettings(tab.id);
 
         if (!Object.keys(settings).every(key => settings[key] === currentSettings[key])) {
           return Promise.reject(`Unsupported zoom settings: ${JSON.stringify(settings)}`);
         }
         return Promise.resolve();
       },
 
-      onZoomChange: new EventManager(context, "tabs.onZoomChange", fire => {
+      onZoomChange: new SingletonEventManager(context, "tabs.onZoomChange", fire => {
         let getZoomLevel = browser => {
           let {ZoomManager} = browser.ownerGlobal;
 
           return ZoomManager.getZoomForBrowser(browser);
         };
 
         // Stores the last known zoom level for each tab's browser.
         // WeakMap[<browser> -> number]
@@ -1070,17 +1070,17 @@ extensions.registerSchemaAPI("tabs", "ad
 
           let oldZoomFactor = zoomLevels.get(browser);
           let newZoomFactor = getZoomLevel(browser);
 
           if (oldZoomFactor != newZoomFactor) {
             zoomLevels.set(browser, newZoomFactor);
 
             let tabId = TabManager.getId(tab);
-            fire({
+            fire.async({
               tabId,
               oldZoomFactor,
               newZoomFactor,
               zoomSettings: self.tabs._getZoomSettings(tabId),
             });
           }
         };
 
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -23,18 +23,18 @@ Cu.import("resource://gre/modules/Extens
 Cu.import("resource://gre/modules/AppConstants.jsm");
 
 const POPUP_LOAD_TIMEOUT_MS = 200;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 var {
   DefaultWeakMap,
-  EventManager,
   promiseEvent,
+  SingletonEventManager,
 } = ExtensionUtils;
 
 // This file provides some useful code for the |tabs| and |windows|
 // modules. All of the code is installed on |global|, which is a scope
 // shared among the different ext-*.js scripts.
 
 global.makeWidgetId = id => {
   id = id.toLowerCase();
@@ -1276,21 +1276,21 @@ global.AllWindowEvents = {
         this.addWindowListener(window, eventType, listener);
       }
     }
   },
 };
 
 AllWindowEvents.openListener = AllWindowEvents.openListener.bind(AllWindowEvents);
 
-// Subclass of EventManager where we just need to call
+// Subclass of SingletonEventManager where we just need to call
 // add/removeEventListener on each XUL window.
-global.WindowEventManager = function(context, name, event, listener) {
-  EventManager.call(this, context, name, fire => {
-    let listener2 = (...args) => listener(fire, ...args);
-    AllWindowEvents.addListener(event, listener2);
-    return () => {
-      AllWindowEvents.removeListener(event, listener2);
-    };
-  });
+global.WindowEventManager = class extends SingletonEventManager {
+  constructor(context, name, event, listener) {
+    super(context, name, fire => {
+      let listener2 = (...args) => listener(fire, ...args);
+      AllWindowEvents.addListener(event, listener2);
+      return () => {
+        AllWindowEvents.removeListener(event, listener2);
+      };
+    });
+  }
 };
-
-WindowEventManager.prototype = Object.create(EventManager.prototype);
--- a/browser/components/extensions/ext-windows.js
+++ b/browser/components/extensions/ext-windows.js
@@ -7,50 +7,50 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "nsIAboutNewTabService");
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
-  EventManager,
+  SingletonEventManager,
   promiseObserved,
 } = ExtensionUtils;
 
 function onXULFrameLoaderCreated({target}) {
   target.messageManager.sendAsyncMessage("AllowScriptsToClose", {});
 }
 
 extensions.registerSchemaAPI("windows", "addon_parent", context => {
   let {extension} = context;
   return {
     windows: {
       onCreated:
       new WindowEventManager(context, "windows.onCreated", "domwindowopened", (fire, window) => {
-        fire(WindowManager.convert(extension, window));
+        fire.async(WindowManager.convert(extension, window));
       }).api(),
 
       onRemoved:
       new WindowEventManager(context, "windows.onRemoved", "domwindowclosed", (fire, window) => {
-        fire(WindowManager.getId(window));
+        fire.async(WindowManager.getId(window));
       }).api(),
 
-      onFocusChanged: new EventManager(context, "windows.onFocusChanged", fire => {
+      onFocusChanged: new SingletonEventManager(context, "windows.onFocusChanged", 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 ? WindowManager.getId(window) : WindowManager.WINDOW_ID_NONE;
             if (windowId !== lastOnFocusChangedWindowId) {
-              fire(windowId);
+              fire.async(windowId);
               lastOnFocusChangedWindowId = windowId;
             }
           });
         };
         AllWindowEvents.addListener("focus", listener);
         AllWindowEvents.addListener("blur", listener);
         return () => {
           AllWindowEvents.removeListener("focus", listener);
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -36,17 +36,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 const CATEGORY_EXTENSION_SCRIPTS_ADDON = "webextension-scripts-addon";
 const CATEGORY_EXTENSION_SCRIPTS_DEVTOOLS = "webextension-scripts-devtools";
 
 Cu.import("resource://gre/modules/ExtensionCommon.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 const {
   DefaultMap,
-  EventManager,
   SingletonEventManager,
   SpreadArgs,
   defineLazyGetter,
   getInnerWindowID,
   getMessageManager,
   getUniqueId,
   injectAPI,
   promiseEvent,
@@ -114,27 +113,27 @@ class Port {
       disconnect: () => {
         this.disconnect();
       },
 
       postMessage: json => {
         this.postMessage(json);
       },
 
-      onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => {
+      onDisconnect: new SingletonEventManager(this.context, "Port.onDisconnect", fire => {
         return this.registerOnDisconnect(error => {
           portError = error && this.context.normalizeError(error);
-          fire.withoutClone(portObj);
+          fire.asyncWithoutClone(portObj);
         });
       }).api(),
 
-      onMessage: new EventManager(this.context, "Port.onMessage", fire => {
+      onMessage: new SingletonEventManager(this.context, "Port.onMessage", fire => {
         return this.registerOnMessage(msg => {
           msg = Cu.cloneInto(msg, this.context.cloneScope);
-          fire.withoutClone(msg, portObj);
+          fire.asyncWithoutClone(msg, portObj);
         });
       }).api(),
 
       get error() {
         return portError;
       },
     };
 
--- a/toolkit/components/extensions/ext-alarms.js
+++ b/toolkit/components/extensions/ext-alarms.js
@@ -1,15 +1,15 @@
 "use strict";
 
 var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
-  EventManager,
+  SingletonEventManager,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> Map[name -> Alarm]]
 var alarmsMap = new WeakMap();
 
 // WeakMap[Extension -> Set[callback]]
 var alarmCallbacksMap = new WeakMap();
 
@@ -135,19 +135,19 @@ extensions.registerSchemaAPI("alarms", "
         let cleared = false;
         for (let alarm of alarmsMap.get(extension).values()) {
           alarm.clear();
           cleared = true;
         }
         return Promise.resolve(cleared);
       },
 
-      onAlarm: new EventManager(context, "alarms.onAlarm", fire => {
+      onAlarm: new SingletonEventManager(context, "alarms.onAlarm", fire => {
         let callback = alarm => {
-          fire(alarm.data);
+          fire.sync(alarm.data);
         };
 
         alarmCallbacksMap.get(extension).add(callback);
         return () => {
           alarmCallbacksMap.get(extension).delete(callback);
         };
       }).api(),
     },
--- a/toolkit/components/extensions/ext-cookies.js
+++ b/toolkit/components/extensions/ext-cookies.js
@@ -4,17 +4,17 @@ const {interfaces: Ci, utils: Cu} = Comp
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
                                   "resource://gre/modules/ContextualIdentityService.jsm");
 
 var {
-  EventManager,
+  SingletonEventManager,
 } = ExtensionUtils;
 
 var DEFAULT_STORE = "firefox-default";
 var PRIVATE_STORE = "firefox-private";
 var CONTAINER_STORE = "firefox-container-";
 
 global.getCookieStoreIdForTab = function(data, tab) {
   if (data.incognito) {
@@ -433,23 +433,23 @@ extensions.registerSchemaAPI("cookies", 
 
         let result = [];
         for (let key in data) {
           result.push({id: key, tabIds: data[key], incognito: key == PRIVATE_STORE});
         }
         return Promise.resolve(result);
       },
 
-      onChanged: new EventManager(context, "cookies.onChanged", fire => {
+      onChanged: new SingletonEventManager(context, "cookies.onChanged", fire => {
         let observer = (subject, topic, data) => {
           let notify = (removed, cookie, cause) => {
             cookie.QueryInterface(Ci.nsICookie2);
 
             if (extension.whiteListedHosts.matchesCookie(cookie)) {
-              fire({removed, cookie: convert({cookie, isPrivate: topic == "private-cookie-changed"}), cause});
+              fire.async({removed, cookie: convert({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;
--- a/toolkit/components/extensions/ext-notifications.js
+++ b/toolkit/components/extensions/ext-notifications.js
@@ -3,17 +3,17 @@
 var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "EventEmitter",
                                   "resource://devtools/shared/event-emitter.js");
 
 var {
-  EventManager,
+  SingletonEventManager,
   ignoreEvent,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> Map[id -> Notification]]
 var notificationsMap = new WeakMap();
 
 // Manages a notification popup (notifications API) created by the extension.
 function Notification(extension, id, options) {
@@ -126,31 +126,31 @@ extensions.registerSchemaAPI("notificati
       getAll: function() {
         let result = {};
         notificationsMap.get(extension).forEach((value, key) => {
           result[key] = value.options;
         });
         return Promise.resolve(result);
       },
 
-      onClosed: new EventManager(context, "notifications.onClosed", fire => {
+      onClosed: new SingletonEventManager(context, "notifications.onClosed", fire => {
         let listener = (event, notificationId) => {
           // FIXME: Support the byUser argument.
-          fire(notificationId, true);
+          fire.async(notificationId, true);
         };
 
         notificationsMap.get(extension).on("closed", listener);
         return () => {
           notificationsMap.get(extension).off("closed", listener);
         };
       }).api(),
 
-      onClicked: new EventManager(context, "notifications.onClicked", fire => {
+      onClicked: new SingletonEventManager(context, "notifications.onClicked", fire => {
         let listener = (event, notificationId) => {
-          fire(notificationId, true);
+          fire.async(notificationId, true);
         };
 
         notificationsMap.get(extension).on("clicked", listener);
         return () => {
           notificationsMap.get(extension).off("clicked", listener);
         };
       }).api(),
 
--- a/toolkit/components/extensions/ext-storage.js
+++ b/toolkit/components/extensions/ext-storage.js
@@ -6,18 +6,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/ExtensionStorage.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorageSync",
                                   "resource://gre/modules/ExtensionStorageSync.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                   "resource://gre/modules/AddonManager.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
-  EventManager,
   ExtensionError,
+  SingletonEventManager,
 } = ExtensionUtils;
 
 function enforceNoTemporaryAddon(extensionId) {
   const EXCEPTION_MESSAGE =
         "The storage API will not work with a temporary addon ID. " +
         "Please add an explicit addon ID to your manifest. " +
         "For more information see https://bugzil.la/1323228.";
   if (AddonManagerPrivate.isTemporaryInstallID(extensionId)) {
@@ -58,22 +58,22 @@ function storageApiFactory(context) {
           return ExtensionStorageSync.remove(extension, keys, context);
         },
         clear: function() {
           enforceNoTemporaryAddon(extension.id);
           return ExtensionStorageSync.clear(extension, context);
         },
       },
 
-      onChanged: new EventManager(context, "storage.onChanged", fire => {
+      onChanged: new SingletonEventManager(context, "storage.onChanged", fire => {
         let listenerLocal = changes => {
-          fire(changes, "local");
+          fire.async(changes, "local");
         };
         let listenerSync = changes => {
-          fire(changes, "sync");
+          fire.async(changes, "sync");
         };
 
         ExtensionStorage.addOnChangedListener(extension.id, listenerLocal);
         ExtensionStorageSync.addOnChangedListener(extension, listenerSync, context);
         return () => {
           ExtensionStorage.removeOnChangedListener(extension.id, listenerLocal);
           ExtensionStorageSync.removeOnChangedListener(extension, listenerSync);
         };