Bug 1311180 Switch mozAddonManager to frame message managers r=rhelmer

MozReview-Commit-ID: GbX0VRn4HUF

--HG--
extra : rebase_source : a852a68d0c0b07f7844650e41783c577534c4527
extra : source : 5656093771903569104f411742748777ae3585a5
This commit is contained in:
Andrew Swan 2016-10-11 16:40:24 -07:00
Родитель 6199bfe74b
Коммит 90b062a959
5 изменённых файлов: 151 добавлений и 139 удалений

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

@ -2885,7 +2885,7 @@ var AddonManagerInternal = {
obj.maxProgress = install.maxProgress;
},
makeListener(id, target) {
makeListener(id, mm) {
const events = [
"onDownloadStarted",
"onDownloadProgress",
@ -2903,7 +2903,7 @@ var AddonManagerInternal = {
listener[event] = (install) => {
let data = {event, id};
AddonManager.webAPI.copyProps(install, data);
this.sendEvent(target, data);
this.sendEvent(mm, data);
}
});
return listener;
@ -2944,7 +2944,7 @@ var AddonManagerInternal = {
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});
@ -3011,7 +3011,7 @@ var AddonManagerInternal = {
clearInstallsFrom(mm) {
for (let [id, info] of this.installs) {
if (info.target == mm) {
if (info.target.messageManager == mm) {
this.forgetInstall(id);
}
}

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

@ -38,22 +38,16 @@ 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);
@ -230,14 +224,15 @@ amManager.prototype = {
}
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
});
@ -259,7 +254,7 @@ amManager.prototype = {
}
case MSG_ADDON_EVENT_REQ: {
let target = aMessage.target;
let target = aMessage.target.messageManager;
if (payload.enabled) {
this._addAddonListener(target);
} else {
@ -275,8 +270,8 @@ amManager.prototype = {
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}"),

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

@ -4,7 +4,7 @@
"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");
@ -17,20 +17,20 @@ 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;
@ -64,86 +64,73 @@ const APIBroker = {
}
}
}
},
}
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
@ -154,8 +141,8 @@ function WebAPITask(apiRequest, apiArgs, processor) {
}
let obj = result.resolve;
if (boundProcessor) {
obj = boundProcessor(obj);
if (resultConverter) {
obj = resultConverter(obj);
}
resolve(obj);
}).catch(err => {
@ -166,24 +153,29 @@ function WebAPITask(apiRequest, apiArgs, processor) {
}
}
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);
}
const INSTALL_EVENTS = [
"onDownloadStarted",
"onDownloadProgress",
"onDownloadEnded",
"onDownloadCancelled",
"onDownloadFailed",
"onInstallStarted",
"onInstallEnded",
"onInstallCancelled",
"onInstallFailed",
];
uninstall() {
return this._apiTask("addonUninstall", [this.id]);
}
setEnabled(value) {
return this._apiTask("addonSetEnabled", [this.id, value]);
}
}
class AddonInstall extends APIObject {
constructor(window, broker, properties) {
super();
this.init(window, broker, properties);
broker._installMap.set(properties.id, this);
}
AddonInstall.prototype = {
_dispatch(data) {
// The message for the event includes updated copies of all install
// properties. Use the usual "let webidl filter visible properties" trick.
@ -193,63 +185,85 @@ AddonInstall.prototype = {
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]);

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

@ -12,13 +12,13 @@ function waitForClear() {
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);
});
}

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

@ -1,6 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<p id="result"></p>
<script type="text/javascript">