1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
9 XPCOMUtils.defineLazyPreferenceGetter(
12 "extensions.abuseReport.amWebAPI.enabled",
16 const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest";
17 const MSG_PROMISE_RESULT = "WebAPIPromiseResult";
18 const MSG_INSTALL_EVENT = "WebAPIInstallEvent";
19 const MSG_INSTALL_CLEANUP = "WebAPICleanup";
20 const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest";
21 const MSG_ADDON_EVENT = "WebAPIAddonEvent";
27 this._promises = new Map();
29 // _installMap maps integer ids to DOM AddonInstall instances
30 this._installMap = new Map();
32 this.mm.addMessageListener(MSG_PROMISE_RESULT, this);
33 this.mm.addMessageListener(MSG_INSTALL_EVENT, this);
35 this._eventListener = null;
38 receiveMessage(message) {
39 let payload = message.data;
41 switch (message.name) {
42 case MSG_PROMISE_RESULT: {
43 if (!this._promises.has(payload.callbackID)) {
47 let resolve = this._promises.get(payload.callbackID);
48 this._promises.delete(payload.callbackID);
53 case MSG_INSTALL_EVENT: {
54 let install = this._installMap.get(payload.id);
57 `Got install event for unknown install ${payload.id}`
62 install._dispatch(payload);
66 case MSG_ADDON_EVENT: {
67 if (this._eventListener) {
68 this._eventListener(payload);
74 sendRequest(type, ...args) {
75 return new Promise(resolve => {
76 let callbackID = APIBroker._nextID++;
78 this._promises.set(callbackID, resolve);
79 this.mm.sendAsyncMessage(MSG_PROMISE_REQUEST, { type, callbackID, args });
83 setAddonListener(callback) {
84 this._eventListener = callback;
86 this.mm.addMessageListener(MSG_ADDON_EVENT, this);
87 this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, { enabled: true });
89 this.mm.removeMessageListener(MSG_ADDON_EVENT, this);
90 this.mm.sendAsyncMessage(MSG_ADDON_EVENT_REQ, { enabled: false });
95 this.setAddonListener(null);
96 this.mm.sendAsyncMessage(MSG_INSTALL_CLEANUP, { ids });
100 APIBroker._nextID = 0;
102 // Base class for building classes to back content-exposed interfaces.
104 init(window, broker, properties) {
105 this.window = window;
106 this.broker = broker;
108 // Copy any provided properties onto this object, webidl bindings
109 // will only expose to content what should be exposed.
110 for (let key of Object.keys(properties)) {
111 this[key] = properties[key];
116 * Helper to implement an asychronous method visible to content, where
117 * the method is implemented by sending a message to the parent process
118 * and then wrapping the returned object or error in an appropriate object.
119 * This helper method ensures that:
120 * - Returned Promise objects are from the content window
121 * - Rejected Promises have Error objects from the content window
122 * - Only non-internal errors are exposed to the caller
124 * @param {string} apiRequest The command to invoke in the parent process.
125 * @param {array<cloneable>} apiArgs The arguments to include with the
126 * request to the parent process.
127 * @param {function} resultConvert If provided, a function called with the
128 * result from the parent process as an
129 * argument. Used to convert the result
130 * into something appropriate for content.
131 * @returns {Promise<any>} A Promise suitable for passing directly to content.
133 _apiTask(apiRequest, apiArgs, resultConverter) {
134 let win = this.window;
135 let broker = this.broker;
136 return new win.Promise((resolve, reject) => {
138 let result = await broker.sendRequest(apiRequest, ...apiArgs);
139 if ("reject" in result) {
140 let err = new win.Error(result.reject.message);
141 // We don't currently put any other properties onto Errors
142 // generated by mozAddonManager. If/when we do, they will
143 // need to get copied here.
148 let obj = result.resolve;
149 if (resultConverter) {
150 obj = resultConverter(obj);
155 reject(new win.Error("Unexpected internal error"));
161 class Addon extends APIObject {
162 constructor(...args) {
168 return this._apiTask("addonUninstall", [this.id]);
172 return this._apiTask("addonSetEnabled", [this.id, value]);
176 class AddonInstall extends APIObject {
177 constructor(window, broker, properties) {
179 this.init(window, broker, properties);
181 broker._installMap.set(properties.id, this);
185 // The message for the event includes updated copies of all install
186 // properties. Use the usual "let webidl filter visible properties" trick.
187 for (let key of Object.keys(data)) {
188 this[key] = data[key];
191 let event = new this.window.Event(data.event);
192 this.__DOM_IMPL__.dispatchEvent(event);
196 return this._apiTask("addonInstallDoInstall", [this.id]);
200 return this._apiTask("addonInstallCancel", [this.id]);
204 export class WebAPI extends APIObject {
207 this.allInstalls = [];
208 this.listenerCount = 0;
212 let mm = window.docShell.messageManager;
213 let broker = new APIBroker(mm);
215 super.init(window, broker, {});
217 window.addEventListener("unload", event => {
218 this.broker.sendCleanup(this.allInstalls);
223 return this._apiTask("getAddonByID", [id], addonInfo => {
227 let addon = new Addon(this.window, this.broker, addonInfo);
228 return this.window.Addon._create(this.window, addon);
232 createInstall(options) {
233 if (!Services.prefs.getBoolPref("xpinstall.enabled", true)) {
234 throw new this.window.Error("Software installation is disabled.");
237 const triggeringPrincipal = this.window.document.nodePrincipal;
239 let installOptions = {
242 // Provide the host from which the amWebAPI is being called
243 // (so that we can detect if the API is being used from the disco pane,
244 // AMO, testpilot or another unknown webpage).
245 sourceHost: this.window.location?.host,
246 sourceURL: this.window.location?.href,
248 return this._apiTask("createInstall", [installOptions], installInfo => {
252 let install = new AddonInstall(this.window, this.broker, installInfo);
253 this.allInstalls.push(installInfo.id);
254 return this.window.AddonInstall._create(this.window, install);
259 return this._apiTask("addonReportAbuse", [id]);
262 get abuseReportPanelEnabled() {
263 return lazy.AMO_ABUSEREPORT;
266 eventListenerAdded(type) {
267 if (this.listenerCount == 0) {
268 this.broker.setAddonListener(data => {
269 let event = new this.window.AddonEvent(data.event, data);
270 this.__DOM_IMPL__.dispatchEvent(event);
273 this.listenerCount++;
276 eventListenerRemoved(type) {
277 this.listenerCount--;
278 if (this.listenerCount == 0) {
279 this.broker.setAddonListener(null);
284 WebAPI.prototype.QueryInterface = ChromeUtils.generateQI([
285 "nsIDOMGlobalPropertyInitializer",
287 WebAPI.prototype.classID = Components.ID(
288 "{8866d8e3-4ea5-48b7-a891-13ba0ac15235}"