Bug 1268077 WIP: expose AddonListener through mozAddonManager draft
authorAndrew Swan <aswan@mozilla.com>
Fri, 27 May 2016 15:43:05 -0700
changeset 372293 6c0e38a557c3f77c4ad6acc76c3993ccd5a634d6
parent 370842 d6d4e8417d2fd71fdf47c319b7a217f6ace9d5a5
child 522153 6a4effa1b5082e8d81a31a44ea0169598d3e4336
push id19497
push useraswan@mozilla.com
push dateFri, 27 May 2016 22:43:37 +0000
bugs1268077
milestone49.0a1
Bug 1268077 WIP: expose AddonListener through mozAddonManager MozReview-Commit-ID: Klw4o0qIvCE
dom/webidl/AddonEvent.webidl
dom/webidl/AddonManager.webidl
dom/webidl/moz.build
toolkit/mozapps/extensions/addonManager.js
toolkit/mozapps/extensions/amWebAPI.js
new file mode 100644
--- /dev/null
+++ b/dom/webidl/AddonEvent.webidl
@@ -0,0 +1,11 @@
+[Constructor(DOMString type, AddonEventInit eventInitDict)]
+interface AddonEvent : Event {
+  readonly attribute DOMString id;
+  readonly attribute boolean needsRestart;
+};
+
+dictionary AddonEventInit : EventInit {
+  required DOMString id;
+  required boolean needsRestart;
+};
+
--- a/dom/webidl/AddonManager.webidl
+++ b/dom/webidl/AddonManager.webidl
@@ -46,17 +46,17 @@ interface AddonInstall : EventTarget {
 dictionary addonInstallOptions {
   required DOMString url;
 };
 
 [HeaderFile="mozilla/AddonManagerWebAPI.h",
  Func="mozilla::AddonManagerWebAPI::IsAPIEnabled",
  NavigatorProperty="mozAddonManager",
  JSImplementation="@mozilla.org/addon-web-api/manager;1"]
-interface AddonManager {
+interface AddonManager : EventTarget {
   /**
    * Gets information about an add-on
    *
    * @param  id
    *         The ID of the add-on to test for.
    * @return A promise. It will resolve to an Addon if the add-on is installed.
    */
   Promise<Addon> getAddonByID(DOMString id);
@@ -65,8 +65,16 @@ interface AddonManager {
    * Creates an AddonInstall object for a given URL.
    *
    * @param options
    *        Only one supported option: 'url', the URL of the addon to install.
    * @return A promise that resolves to an instance of AddonInstall.
    */
   Promise<AddonInstall> createInstall(optional addonInstallOptions options);
 };
+
+// Mozilla Only
+partial interface AddonManager {
+  [ChromeOnly]
+  void eventListenerWasAdded(DOMString type);
+  [ChromeOnly]
+  void eventListenerWasRemoved(DOMString type);
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -753,16 +753,17 @@ else:
     ]
 
 if CONFIG['MOZ_B2G_FM']:
     WEBIDL_FILES += [
         'FMRadio.webidl',
     ]
 
 GENERATED_EVENTS_WEBIDL_FILES = [
+    'AddonEvent.webidl',
     'AnimationPlaybackEvent.webidl',
     'AutocompleteErrorEvent.webidl',
     'BlobEvent.webidl',
     'CallEvent.webidl',
     'CallGroupErrorEvent.webidl',
     'CameraClosedEvent.webidl',
     'CameraConfigurationEvent.webidl',
     'CameraFacesDetectedEvent.webidl',
--- a/toolkit/mozapps/extensions/addonManager.js
+++ b/toolkit/mozapps/extensions/addonManager.js
@@ -23,16 +23,18 @@ const SUCCESS           = 0;
 const MSG_INSTALL_ENABLED  = "WebInstallerIsInstallEnabled";
 const MSG_INSTALL_ADDONS   = "WebInstallerInstallAddonsFromWebpage";
 const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
 
 const MSG_PROMISE_REQUEST  = "WebAPIPromiseRequest";
 const MSG_PROMISE_RESULT   = "WebAPIPromiseResult";
 const MSG_INSTALL_EVENT    = "WebAPIInstallEvent";
 const MSG_INSTALL_CLEANUP  = "WebAPICleanup";
+const MSG_ADDON_EVENT_REQ  = "WebAPIAddonEventRequest";
+const MSG_ADDON_EVENT      = "WebAPIAddonEvent";
 
 const CHILD_SCRIPT = "resource://gre/modules/addons/Content.js";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 var gSingleton = null;
 
@@ -46,16 +48,17 @@ function amManager() {
   let globalMM = Services.mm;
   globalMM.loadFrameScript(CHILD_SCRIPT, true);
   globalMM.addMessageListener(MSG_INSTALL_ADDONS, this);
 
   gParentMM = Services.ppmm;
   gParentMM.addMessageListener(MSG_INSTALL_ENABLED, this);
   gParentMM.addMessageListener(MSG_PROMISE_REQUEST, this);
   gParentMM.addMessageListener(MSG_INSTALL_CLEANUP, this);
+  gParentMM.addMessageListener(MSG_ADDON_EVENT_REQ, this);
 
   Services.obs.addObserver(this, "message-manager-close", false);
   Services.obs.addObserver(this, "message-manager-disconnect", false);
 
   AddonManager.webAPI.setEventHandler(this.sendEvent);
 
   // Needed so receiveMessage can be called directly by JS callers
   this.wrappedJSObject = this;
@@ -156,16 +159,18 @@ amManager.prototype = {
 
     return retval;
   },
 
   notify: function(aTimer) {
     AddonManagerPrivate.backgroundUpdateTimerHandler();
   },
 
+  addonListener: null,
+
   /**
    * messageManager callback function.
    *
    * Listens to requests from child processes for InstallTrigger
    * activity, and sends back callbacks.
    */
   receiveMessage: function(aMessage) {
     let payload = aMessage.data;
@@ -216,16 +221,47 @@ amManager.prototype = {
         }
         break;
       }
 
       case MSG_INSTALL_CLEANUP: {
         AddonManager.webAPI.clearInstalls(payload.ids);
         break;
       }
+
+      case MSG_ADDON_EVENT_REQ: {
+        if (payload.enabled) {
+          if (!this.addonListener) {
+            let target = aMessage.target;
+            let handler = (event, id, needsRestart) => {
+              if (event == "onDisabled" && false) {
+                let frame = Components.stack;
+                while (frame) {
+                  dump(`${frame}\n`);
+                  frame = frame.caller;
+                }
+              }
+              target.sendAsyncMessage(MSG_ADDON_EVENT, {event, id, needsRestart});
+            };
+            this.addonListener = {
+              onEnabling: (addon, needsRestart) => handler("onEnabling", addon.id, needsRestart),
+              onEnabled: (addon) => handler("onEnabled", addon.id, false),
+              onDisabling: (addon, needsRestart) => handler("onDisabling", addon.id, needsRestart),
+              onDisabled: (addon) => handler("onDisabled", addon.id, false),
+              onInstalling: (addon, needsRestart) => handler("onInstalling", addon.id, needsRestart),
+              onInstalled: (addon) => handler("onInstalled", addon.id, false),
+              onUninstalling: (addon, needsRestart) => handler("onUninstalling", addon.id, needsRestart),
+              onUninstalled: (addon) => handler("onUninstalled", addon.id, false),
+            };
+          }
+          AddonManager.addAddonListener(this.addonListener);
+        } else {
+          AddonManager.removeAddonListener(this.addonListener);
+        }
+      }
     }
     return undefined;
   },
 
   sendEvent(target, data) {
     target.sendAsyncMessage(MSG_INSTALL_EVENT, data);
   },
 
--- a/toolkit/mozapps/extensions/amWebAPI.js
+++ b/toolkit/mozapps/extensions/amWebAPI.js
@@ -9,28 +9,32 @@ const {classes: Cc, interfaces: Ci, util
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 const MSG_PROMISE_REQUEST  = "WebAPIPromiseRequest";
 const MSG_PROMISE_RESULT   = "WebAPIPromiseResult";
 const MSG_INSTALL_EVENT    = "WebAPIInstallEvent";
 const MSG_INSTALL_CLEANUP  = "WebAPICleanup";
+const MSG_ADDON_EVENT_REQ  = "WebAPIAddonEventRequest";
+const MSG_ADDON_EVENT      = "WebAPIAddonEvent";
 
 const APIBroker = {
   _nextID: 0,
 
   init() {
     this._promises = new Map();
 
     // _installMap maps integer ids to DOM AddonInstall instances
     this._installMap = new Map();
 
     Services.cpmm.addMessageListener(MSG_PROMISE_RESULT, this);
     Services.cpmm.addMessageListener(MSG_INSTALL_EVENT, this);
+
+    this._eventListener = null;
   },
 
   receiveMessage(message) {
     let payload = message.data;
 
     switch (message.name) {
       case MSG_PROMISE_RESULT: {
         if (!this._promises.has(payload.callbackID)) {
@@ -52,28 +56,45 @@ const APIBroker = {
         if (!install) {
           let err = new Error(`Got install event for unknown install ${payload.id}`);
           Cu.reportError(err);
           return;
         }
         install._dispatch(payload);
         break;
       }
+
+      case MSG_ADDON_EVENT: {
+        if (this._eventListener) {
+          this._eventListener(payload);
+        }
+      }
     }
   },
 
   sendRequest: function(type, ...args) {
     return new Promise((resolve, reject) => {
       let callbackID = this._nextID++;
 
       this._promises.set(callbackID, { resolve, reject });
       Services.cpmm.sendAsyncMessage(MSG_PROMISE_REQUEST, { type, callbackID, args });
     });
   },
 
+  setAddonListener(callback) {
+    this._eventListener = callback;
+    if (callback) {
+      Services.cpmm.addMessageListener(MSG_ADDON_EVENT, this);
+      Services.cpmm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: true});
+    } else {
+      Services.cpmm.removeMessageListener(MSG_ADDON_EVENT, this);
+      Services.cpmm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: false});
+    }
+  },
+
   sendCleanup: function(ids) {
     Services.cpmm.sendAsyncMessage(MSG_INSTALL_CLEANUP, { ids });
   },
 };
 
 APIBroker.init();
 
 function Addon(window, properties) {
@@ -166,16 +187,17 @@ AddonInstall.prototype = {
 
 function WebAPI() {
 }
 
 WebAPI.prototype = {
   init(window) {
     this.window = window;
     this.allInstalls = [];
+    this.listenerCount = 0;
 
     window.addEventListener("unload", event => {
       APIBroker.sendCleanup(this.allInstalls);
     });
   },
 
   getAddonByID: WebAPITask(function*(id) {
     let addonInfo = yield APIBroker.sendRequest("getAddonByID", id);
@@ -187,14 +209,29 @@ WebAPI.prototype = {
     if (!installInfo) {
       return null;
     }
     let install = new AddonInstall(this.window, installInfo);
     this.allInstalls.push(installInfo.id);
     return install;
   }),
 
+  eventListenerWasAdded(type) {
+    if (this.listenerCount++ == 0) {
+      APIBroker.setAddonListener(data => {
+        let event = new this.window.AddonEvent(data.event, data);
+        this.__DOM_IMPL__.dispatchEvent(event);
+      });
+    }
+  },
+
+  eventListenerWasRemoved(type) {
+    if (--this.listenerCount == 0) {
+      APIBroker.setAddonListener(null);
+    }
+  },
+
   classID: Components.ID("{8866d8e3-4ea5-48b7-a891-13ba0ac15235}"),
   contractID: "@mozilla.org/addon-web-api/manager;1",
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, Ci.nsIDOMGlobalPropertyInitializer])
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebAPI]);