mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 13:51:41 +00:00
256 lines
7.5 KiB
JavaScript
256 lines
7.5 KiB
JavaScript
/* 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;
|
|
|
|
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)) {
|
|
return;
|
|
}
|
|
|
|
let resolve = this._promises.get(payload.callbackID);
|
|
this._promises.delete(payload.callbackID);
|
|
resolve(payload);
|
|
break;
|
|
}
|
|
|
|
case MSG_INSTALL_EVENT: {
|
|
let install = this._installMap.get(payload.id);
|
|
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 => {
|
|
let callbackID = this._nextID++;
|
|
|
|
this._promises.set(callbackID, resolve);
|
|
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) {
|
|
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];
|
|
}
|
|
}
|
|
|
|
function AddonInstall(window, properties) {
|
|
let id = properties.id;
|
|
APIBroker._installMap.set(id, this);
|
|
|
|
this.window = window;
|
|
this.handlers = new Map();
|
|
|
|
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) {
|
|
let win = this.window;
|
|
let boundApiArgs = apiArgs.bind(this);
|
|
let boundProcessor = processor ? processor.bind(this) : null;
|
|
|
|
return new win.Promise((resolve, reject) => {
|
|
Task.spawn(function* () {
|
|
let sendArgs = boundApiArgs(...args);
|
|
let result = yield APIBroker.sendRequest(apiRequest, ...sendArgs);
|
|
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);
|
|
}
|
|
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]; }),
|
|
};
|
|
|
|
const INSTALL_EVENTS = [
|
|
"onDownloadStarted",
|
|
"onDownloadProgress",
|
|
"onDownloadEnded",
|
|
"onDownloadCancelled",
|
|
"onDownloadFailed",
|
|
"onInstallStarted",
|
|
"onInstallEnded",
|
|
"onInstallCancelled",
|
|
"onInstallFailed",
|
|
];
|
|
|
|
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.
|
|
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]; }),
|
|
};
|
|
|
|
function WebAPI() {
|
|
}
|
|
|
|
WebAPI.prototype = {
|
|
init(window) {
|
|
this.window = window;
|
|
this.allInstalls = [];
|
|
this.listenerCount = 0;
|
|
|
|
window.addEventListener("unload", event => {
|
|
APIBroker.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);
|
|
}),
|
|
|
|
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);
|
|
}),
|
|
|
|
eventListenerWasAdded(type) {
|
|
if (this.listenerCount == 0) {
|
|
APIBroker.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);
|
|
}
|
|
},
|
|
|
|
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]);
|