--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -2880,17 +2880,17 @@ var AddonManagerInternal = {
// helper to copy (and convert) the properties we care about
copyProps(install, obj) {
obj.state = AddonManager.stateToString(install.state);
obj.error = AddonManager.errorToString(install.error);
obj.progress = install.progress;
obj.maxProgress = install.maxProgress;
},
- makeListener(id, target) {
+ makeListener(id, mm) {
const events = [
"onDownloadStarted",
"onDownloadProgress",
"onDownloadEnded",
"onDownloadCancelled",
"onDownloadFailed",
"onInstallStarted",
"onInstallEnded",
@@ -2898,17 +2898,17 @@ var AddonManagerInternal = {
"onInstallFailed",
];
let listener = {};
events.forEach(event => {
listener[event] = (install) => {
let data = {event, id};
AddonManager.webAPI.copyProps(install, data);
- this.sendEvent(target, data);
+ this.sendEvent(mm, data);
}
});
return listener;
},
forgetInstall(id) {
let info = this.installs.get(id);
if (!info) {
@@ -2939,17 +2939,17 @@ var AddonManagerInternal = {
checkInstallUrl(options.url);
} catch (err) {
reject({message: err.message});
return;
}
let newInstall = install => {
let id = this.nextInstall++;
- let listener = this.makeListener(id, target);
+ let listener = this.makeListener(id, target.messageManager);
install.addListener(listener);
this.installs.set(id, {install, target, listener});
let result = {id};
this.copyProps(install, result);
resolve(result);
};
@@ -3006,17 +3006,17 @@ var AddonManagerInternal = {
clearInstalls(ids) {
for (let id of ids) {
this.forgetInstall(id);
}
},
clearInstallsFrom(mm) {
for (let [id, info] of this.installs) {
- if (info.target == mm) {
+ if (info.target.messageManager == mm) {
this.forgetInstall(id);
}
}
},
},
};
/**
--- a/toolkit/mozapps/extensions/addonManager.js
+++ b/toolkit/mozapps/extensions/addonManager.js
@@ -33,32 +33,26 @@ const MSG_ADDON_EVENT = "WebAPIAddo
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;
-var gParentMM = null;
-
-
function amManager() {
Cu.import("resource://gre/modules/AddonManager.jsm");
/*globals AddonManagerPrivate*/
- let globalMM = Services.mm;
- globalMM.loadFrameScript(CHILD_SCRIPT, true);
- globalMM.addMessageListener(MSG_INSTALL_ENABLED, this);
- globalMM.addMessageListener(MSG_INSTALL_ADDONS, this);
-
- gParentMM = Services.ppmm;
- gParentMM.addMessageListener(MSG_PROMISE_REQUEST, this);
- gParentMM.addMessageListener(MSG_INSTALL_CLEANUP, this);
- gParentMM.addMessageListener(MSG_ADDON_EVENT_REQ, this);
+ Services.mm.loadFrameScript(CHILD_SCRIPT, true);
+ Services.mm.addMessageListener(MSG_INSTALL_ENABLED, this);
+ Services.mm.addMessageListener(MSG_INSTALL_ADDONS, this);
+ Services.mm.addMessageListener(MSG_PROMISE_REQUEST, this);
+ Services.mm.addMessageListener(MSG_INSTALL_CLEANUP, this);
+ Services.mm.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;
@@ -225,24 +219,25 @@ amManager.prototype = {
}
return this.installAddonsFromWebpage(payload.mimetype,
aMessage.target, payload.principalToInherit, payload.uris,
payload.hashes, payload.names, payload.icons, callback);
}
case MSG_PROMISE_REQUEST: {
+ let mm = aMessage.target.messageManager;
let resolve = (value) => {
- aMessage.target.sendAsyncMessage(MSG_PROMISE_RESULT, {
+ mm.sendAsyncMessage(MSG_PROMISE_RESULT, {
callbackID: payload.callbackID,
resolve: value
});
}
let reject = (value) => {
- aMessage.target.sendAsyncMessage(MSG_PROMISE_RESULT, {
+ mm.sendAsyncMessage(MSG_PROMISE_RESULT, {
callbackID: payload.callbackID,
reject: value
});
}
let API = AddonManager.webAPI;
if (payload.type in API) {
API[payload.type](aMessage.target, ...payload.args).then(resolve, reject);
@@ -254,34 +249,34 @@ amManager.prototype = {
}
case MSG_INSTALL_CLEANUP: {
AddonManager.webAPI.clearInstalls(payload.ids);
break;
}
case MSG_ADDON_EVENT_REQ: {
- let target = aMessage.target;
+ let target = aMessage.target.messageManager;
if (payload.enabled) {
this._addAddonListener(target);
} else {
this._removeAddonListener(target);
}
}
}
return undefined;
},
childClosed(target) {
AddonManager.webAPI.clearInstallsFrom(target);
this._removeAddonListener(target);
},
- sendEvent(target, data) {
- target.sendAsyncMessage(MSG_INSTALL_EVENT, data);
+ sendEvent(mm, data) {
+ mm.sendAsyncMessage(MSG_INSTALL_EVENT, data);
},
classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"),
_xpcom_factory: {
createInstance: function(aOuter, aIid) {
if (aOuter != null)
throw Components.Exception("Component does not support aggregation",
Cr.NS_ERROR_NO_AGGREGATION);
--- a/toolkit/mozapps/extensions/amWebAPI.js
+++ b/toolkit/mozapps/extensions/amWebAPI.js
@@ -1,41 +1,41 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
-const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
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,
+class APIBroker {
+ constructor(mm) {
+ this.mm = mm;
- 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.mm.addMessageListener(MSG_PROMISE_RESULT, this);
+ this.mm.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)) {
return;
@@ -59,197 +59,211 @@ const APIBroker = {
}
case MSG_ADDON_EVENT: {
if (this._eventListener) {
this._eventListener(payload);
}
}
}
- },
+ }
- sendRequest: function(type, ...args) {
+ sendRequest(type, ...args) {
return new Promise(resolve => {
- let callbackID = this._nextID++;
+ let callbackID = APIBroker._nextID++;
this._promises.set(callbackID, resolve);
- Services.cpmm.sendAsyncMessage(MSG_PROMISE_REQUEST, { type, callbackID, args });
+ this.mm.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});
+ this.mm.addMessageListener(MSG_ADDON_EVENT, this);
+ this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: true});
} else {
- Services.cpmm.removeMessageListener(MSG_ADDON_EVENT, this);
- Services.cpmm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: false});
+ this.mm.removeMessageListener(MSG_ADDON_EVENT, this);
+ this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, {enabled: false});
}
- },
+ }
- sendCleanup: function(ids) {
+ sendCleanup(ids) {
this.setAddonListener(null);
- Services.cpmm.sendAsyncMessage(MSG_INSTALL_CLEANUP, { ids });
- },
-};
-
-APIBroker.init();
-
-function Addon(window, properties) {
- this.window = window;
-
- // We trust the webidl binding to broker access to our properties.
- for (let key of Object.keys(properties)) {
- this[key] = properties[key];
+ this.mm.sendAsyncMessage(MSG_INSTALL_CLEANUP, { ids });
}
}
-function AddonInstall(window, properties) {
- let id = properties.id;
- APIBroker._installMap.set(id, this);
+APIBroker._nextID = 0;
- this.window = window;
- this.handlers = new Map();
+// Base class for building classes to back content-exposed interfaces.
+class APIObject {
+ init(window, broker, properties) {
+ this.window = window;
+ this.broker = broker;
- for (let key of Object.keys(properties)) {
- this[key] = properties[key];
+ // Copy any provided properties onto this object, webidl bindings
+ // will only expose to content what should be exposed.
+ for (let key of Object.keys(properties)) {
+ this[key] = properties[key];
+ }
}
-}
-/**
- * API methods all return promises from content. They also follow a
- * similar pattern of sending a request to the parent process, then
- * wrapping the returned object or error appropriately for the page.
- * We must take care only to wrap and reject with errors that are meant
- * to be visible to content, and not internal errors.
- * This function is a wrapper to handle the common bits.
- *
- * apiRequest is the name of the command to invoke in the parent process
- * apiArgs is a callable that takes the content-provided args for the
- * method and returns the arguments to send in the request to
- * the parent process.
- * if processor is non-null, it is called on the returned object to
- * convert the result from the parent process back to an
- * object appropriate for content.
- *
- * Both apiArgs and processor are called with "this" bound to the value
- * that is held when the actual api method was called.
- */
-function WebAPITask(apiRequest, apiArgs, processor) {
- return function(...args) {
+ /**
+ * Helper to implement an asychronous method visible to content, where
+ * the method is implemented by sending a message to the parent process
+ * and then wrapping the returned object or error in an appropriate object.
+ * This helper method ensures that:
+ * - Returned Promise objects are from the content window
+ * - Rejected Promises have Error objects from the content window
+ * - Only non-internal errors are exposed to the caller
+ *
+ * @param {string} apiRequest The command to invoke in the parent process.
+ * @param {array<cloneable>} apiArgs The arguments to include with the
+ * request to the parent process.
+ * @param {function} resultConvert If provided, a function called with the
+ * result from the parent process as an
+ * argument. Used to convert the result
+ * into something appropriate for content.
+ * @returns {Promise<any>} A Promise suitable for passing directly to content.
+ */
+ _apiTask(apiRequest, apiArgs, resultConverter) {
let win = this.window;
- let boundApiArgs = apiArgs.bind(this);
- let boundProcessor = processor ? processor.bind(this) : null;
-
+ let broker = this.broker;
return new win.Promise((resolve, reject) => {
- Task.spawn(function* () {
- let sendArgs = boundApiArgs(...args);
- let result = yield APIBroker.sendRequest(apiRequest, ...sendArgs);
+ Task.spawn(function*() {
+ let result = yield broker.sendRequest(apiRequest, ...apiArgs);
if ("reject" in result) {
let err = new win.Error(result.reject.message);
// We don't currently put any other properties onto Errors
// generated by mozAddonManager. If/when we do, they will
// need to get copied here.
reject(err);
return;
}
let obj = result.resolve;
- if (boundProcessor) {
- obj = boundProcessor(obj);
+ if (resultConverter) {
+ obj = resultConverter(obj);
}
resolve(obj);
}).catch(err => {
Cu.reportError(err);
reject(new win.Error("Unexpected internal error"));
});
});
}
}
-Addon.prototype = {
- uninstall: WebAPITask("addonUninstall", function() { return [this.id]; }),
- setEnabled: WebAPITask("addonSetEnabled", function(value) { return [this.id, value]; }),
-};
+class Addon extends APIObject {
+ constructor(...args) {
+ super();
+ this.init(...args);
+ }
+
+ uninstall() {
+ return this._apiTask("addonUninstall", [this.id]);
+ }
-const INSTALL_EVENTS = [
- "onDownloadStarted",
- "onDownloadProgress",
- "onDownloadEnded",
- "onDownloadCancelled",
- "onDownloadFailed",
- "onInstallStarted",
- "onInstallEnded",
- "onInstallCancelled",
- "onInstallFailed",
-];
+ setEnabled(value) {
+ return this._apiTask("addonSetEnabled", [this.id, value]);
+ }
+}
-AddonInstall.prototype = {
+class AddonInstall extends APIObject {
+ constructor(window, broker, properties) {
+ super();
+ this.init(window, broker, properties);
+
+ broker._installMap.set(properties.id, this);
+ }
+
_dispatch(data) {
// The message for the event includes updated copies of all install
// properties. Use the usual "let webidl filter visible properties" trick.
for (let key of Object.keys(data)) {
this[key] = data[key];
}
let event = new this.window.Event(data.event);
this.__DOM_IMPL__.dispatchEvent(event);
- },
+ }
- install: WebAPITask("addonInstallDoInstall", function() { return [this.id]; }),
- cancel: WebAPITask("addonInstallCancel", function() { return [this.id]; }),
-};
+ install() {
+ return this._apiTask("addonInstallDoInstall", [this.id]);
+ }
-function WebAPI() {
+ cancel() {
+ return this._apiTask("addonInstallCancel", [this.id]);
+ }
}
-WebAPI.prototype = {
- init(window) {
- this.window = window;
+class WebAPI extends APIObject {
+ constructor() {
+ super();
this.allInstalls = [];
this.listenerCount = 0;
+ }
+
+ init(window) {
+ let mm = window
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIContentFrameMessageManager);
+ let broker = new APIBroker(mm);
+
+ super.init(window, broker, {});
window.addEventListener("unload", event => {
- APIBroker.sendCleanup(this.allInstalls);
+ this.broker.sendCleanup(this.allInstalls);
});
- },
+ }
- getAddonByID: WebAPITask("getAddonByID", id => [id], function(addonInfo) {
- if (!addonInfo) {
- return null;
- }
- let addon = new Addon(this.window, addonInfo);
- return this.window.Addon._create(this.window, addon);
- }),
+ getAddonByID(id) {
+ return this._apiTask("getAddonByID", [id], addonInfo => {
+ if (!addonInfo) {
+ return null;
+ }
+ let addon = new Addon(this.window, this.broker, addonInfo);
+ return this.window.Addon._create(this.window, addon);
+ });
+ }
- createInstall: WebAPITask("createInstall", options => [options], function(installInfo) {
- if (!installInfo) {
- return null;
- }
- let install = new AddonInstall(this.window, installInfo);
- this.allInstalls.push(installInfo.id);
- return this.window.AddonInstall._create(this.window, install);
- }),
+ createInstall(options) {
+ return this._apiTask("createInstall", [options], installInfo => {
+ if (!installInfo) {
+ return null;
+ }
+ let install = new AddonInstall(this.window, this.broker, installInfo);
+ this.allInstalls.push(installInfo.id);
+ return this.window.AddonInstall._create(this.window, install);
+ });
+ }
eventListenerWasAdded(type) {
if (this.listenerCount == 0) {
- APIBroker.setAddonListener(data => {
+ this.broker.setAddonListener(data => {
let event = new this.window.AddonEvent(data.event, data);
this.__DOM_IMPL__.dispatchEvent(event);
});
}
this.listenerCount++;
- },
+ }
eventListenerWasRemoved(type) {
this.listenerCount--;
if (this.listenerCount == 0) {
- APIBroker.setAddonListener(null);
+ this.broker.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])
-};
+ QueryInterface(iid) {
+ if (iid.equals(WebAPI.classID) || iid.equals(Ci.nsISupports)
+ || iid.equals(Ci.nsIDOMGlobalPropertyInitializer)) {
+ return this;
+ }
+ return Cr.NS_ERROR_NO_INTERFACE;
+ }
+}
+WebAPI.prototype.classID = Components.ID("{8866d8e3-4ea5-48b7-a891-13ba0ac15235}");
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WebAPI]);
--- a/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_webapi_install.js
@@ -7,23 +7,23 @@ const ID = "webapi_install@tests.mozilla
const XPI_LEN = 4782;
function waitForClear() {
const MSG = "WebAPICleanup";
return new Promise(resolve => {
let listener = {
receiveMessage: function(msg) {
if (msg.name == MSG) {
- Services.ppmm.removeMessageListener(MSG, listener);
+ Services.mm.removeMessageListener(MSG, listener);
resolve();
}
}
};
- Services.ppmm.addMessageListener(MSG, listener);
+ Services.mm.addMessageListener(MSG, listener, true);
});
}
add_task(function* setup() {
yield SpecialPowers.pushPrefEnv({
set: [["extensions.webapi.testing", true],
["extensions.install.requireBuiltInCerts", false]],
});
--- a/toolkit/mozapps/extensions/test/browser/webapi_addon_listener.html
+++ b/toolkit/mozapps/extensions/test/browser/webapi_addon_listener.html
@@ -1,11 +1,14 @@
<!DOCTYPE html>
<html>
+<head>
+ <meta charset="utf-8">
+</head>
<body>
<p id="result"></p>
<script type="text/javascript">
let events = [];
let resultEl = document.getElementById("result");
[ "onEnabling",
"onEnabled",
"onDisabling",