Bug 1867190 - Add prefs for PHC probablities r=glandium
[gecko.git] / toolkit / mozapps / extensions / amManager.sys.mjs
blob5ce396d6010c97a0b01739e4ec150ffa86dbab0a
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 /**
6  * This component serves as integration between the platform and AddonManager.
7  * It is responsible for initializing and shutting down the AddonManager as well
8  * as passing new installs from webpages to the AddonManager.
9  */
11 import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
13 const lazy = {};
14 XPCOMUtils.defineLazyPreferenceGetter(
15   lazy,
16   "separatePrivilegedMozillaWebContentProcess",
17   "browser.tabs.remote.separatePrivilegedMozillaWebContentProcess",
18   false
20 XPCOMUtils.defineLazyPreferenceGetter(
21   lazy,
22   "extensionsWebAPITesting",
23   "extensions.webapi.testing",
24   false
27 // The old XPInstall error codes
28 const EXECUTION_ERROR = -203;
29 const CANT_READ_ARCHIVE = -207;
30 const USER_CANCELLED = -210;
31 const DOWNLOAD_ERROR = -228;
32 const UNSUPPORTED_TYPE = -244;
33 const SUCCESS = 0;
35 const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled";
36 const MSG_INSTALL_ADDON = "WebInstallerInstallAddonFromWebpage";
37 const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
39 const MSG_PROMISE_REQUEST = "WebAPIPromiseRequest";
40 const MSG_PROMISE_RESULT = "WebAPIPromiseResult";
41 const MSG_INSTALL_EVENT = "WebAPIInstallEvent";
42 const MSG_INSTALL_CLEANUP = "WebAPICleanup";
43 const MSG_ADDON_EVENT_REQ = "WebAPIAddonEventRequest";
44 const MSG_ADDON_EVENT = "WebAPIAddonEvent";
46 var AddonManager, AddonManagerPrivate;
48 export function amManager() {
49   ({ AddonManager, AddonManagerPrivate } = ChromeUtils.importESModule(
50     "resource://gre/modules/AddonManager.sys.mjs"
51   ));
53   Services.mm.addMessageListener(MSG_INSTALL_ENABLED, this);
54   Services.mm.addMessageListener(MSG_PROMISE_REQUEST, this);
55   Services.mm.addMessageListener(MSG_INSTALL_CLEANUP, this);
56   Services.mm.addMessageListener(MSG_ADDON_EVENT_REQ, this);
58   Services.ppmm.addMessageListener(MSG_INSTALL_ADDON, this);
60   Services.obs.addObserver(this, "message-manager-close");
61   Services.obs.addObserver(this, "message-manager-disconnect");
63   AddonManager.webAPI.setEventHandler(this.sendEvent);
65   // Needed so receiveMessage can be called directly by JS callers
66   this.wrappedJSObject = this;
69 amManager.prototype = {
70   observe(aSubject, aTopic, aData) {
71     switch (aTopic) {
72       case "addons-startup":
73         AddonManagerPrivate.startup();
74         break;
76       case "message-manager-close":
77       case "message-manager-disconnect":
78         this.childClosed(aSubject);
79         break;
80     }
81   },
83   installAddonFromWebpage(aPayload, aBrowser, aCallback) {
84     let retval = true;
86     const { mimetype, triggeringPrincipal, hash, icon, name, uri } = aPayload;
88     // NOTE: consider removing this call to isInstallAllowed from here, later it is going to be called
89     // again from inside AddonManager.installAddonFromWebpage as part of the block/allow logic.
90     //
91     // The sole purpose of the call here seems to be "clearing the optional InstallTrigger callback",
92     // which seems to be actually wrong if we are still proceeding to call getInstallForURL and the same
93     // logic used to block the install flow using the exact same method call later on.
94     if (!AddonManager.isInstallAllowed(mimetype, triggeringPrincipal)) {
95       aCallback = null;
96       retval = false;
97     }
99     let telemetryInfo = {
100       source: AddonManager.getInstallSourceFromHost(aPayload.sourceHost),
101       sourceURL: aPayload.sourceURL,
102     };
104     if ("method" in aPayload) {
105       telemetryInfo.method = aPayload.method;
106     }
108     AddonManager.getInstallForURL(uri, {
109       hash,
110       name,
111       icon,
112       browser: aBrowser,
113       triggeringPrincipal,
114       telemetryInfo,
115       sendCookies: true,
116     }).then(aInstall => {
117       function callCallback(status) {
118         try {
119           aCallback?.onInstallEnded(uri, status);
120         } catch (e) {
121           Cu.reportError(e);
122         }
123       }
125       if (!aInstall) {
126         callCallback(UNSUPPORTED_TYPE);
127         return;
128       }
130       if (aCallback) {
131         aInstall.addListener({
132           onDownloadCancelled(aInstall) {
133             callCallback(USER_CANCELLED);
134           },
136           onDownloadFailed(aInstall) {
137             if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE) {
138               callCallback(CANT_READ_ARCHIVE);
139             } else {
140               callCallback(DOWNLOAD_ERROR);
141             }
142           },
144           onInstallFailed(aInstall) {
145             callCallback(EXECUTION_ERROR);
146           },
148           onInstallEnded(aInstall, aStatus) {
149             callCallback(SUCCESS);
150           },
151         });
152       }
154       AddonManager.installAddonFromWebpage(
155         mimetype,
156         aBrowser,
157         triggeringPrincipal,
158         aInstall,
159         {
160           hasCrossOriginAncestor: aPayload.hasCrossOriginAncestor,
161         }
162       );
163     });
165     return retval;
166   },
168   notify(aTimer) {
169     AddonManagerPrivate.backgroundUpdateTimerHandler();
170   },
172   // Maps message manager instances for content processes to the associated
173   // AddonListener instances.
174   addonListeners: new Map(),
176   _addAddonListener(target) {
177     if (!this.addonListeners.has(target)) {
178       let handler = (event, id) => {
179         target.sendAsyncMessage(MSG_ADDON_EVENT, { event, id });
180       };
181       let listener = {
182         onEnabling: addon => handler("onEnabling", addon.id),
183         onEnabled: addon => handler("onEnabled", addon.id),
184         onDisabling: addon => handler("onDisabling", addon.id),
185         onDisabled: addon => handler("onDisabled", addon.id),
186         onInstalling: addon => handler("onInstalling", addon.id),
187         onInstalled: addon => handler("onInstalled", addon.id),
188         onUninstalling: addon => handler("onUninstalling", addon.id),
189         onUninstalled: addon => handler("onUninstalled", addon.id),
190         onOperationCancelled: addon =>
191           handler("onOperationCancelled", addon.id),
192       };
193       this.addonListeners.set(target, listener);
194       AddonManager.addAddonListener(listener);
195     }
196   },
198   _removeAddonListener(target) {
199     if (this.addonListeners.has(target)) {
200       AddonManager.removeAddonListener(this.addonListeners.get(target));
201       this.addonListeners.delete(target);
202     }
203   },
205   /**
206    * messageManager callback function.
207    *
208    * Listens to requests from child processes for InstallTrigger
209    * activity, and sends back callbacks.
210    */
211   receiveMessage(aMessage) {
212     let payload = aMessage.data;
214     switch (aMessage.name) {
215       case MSG_INSTALL_ENABLED:
216         return AddonManager.isInstallEnabled(payload.mimetype);
218       case MSG_INSTALL_ADDON: {
219         let browser = payload.browsingContext.top.embedderElement;
221         let callback = null;
222         if (payload.callbackID != -1) {
223           let mm = browser.messageManager;
224           callback = {
225             onInstallEnded(url, status) {
226               mm.sendAsyncMessage(MSG_INSTALL_CALLBACK, {
227                 callbackID: payload.callbackID,
228                 url,
229                 status,
230               });
231             },
232           };
233         }
235         return this.installAddonFromWebpage(payload, browser, callback);
236       }
238       case MSG_PROMISE_REQUEST: {
239         if (
240           !lazy.extensionsWebAPITesting &&
241           lazy.separatePrivilegedMozillaWebContentProcess &&
242           aMessage.target &&
243           aMessage.target.remoteType != null &&
244           aMessage.target.remoteType !== "privilegedmozilla"
245         ) {
246           return undefined;
247         }
249         let mm = aMessage.target.messageManager;
250         let resolve = value => {
251           mm.sendAsyncMessage(MSG_PROMISE_RESULT, {
252             callbackID: payload.callbackID,
253             resolve: value,
254           });
255         };
256         let reject = value => {
257           mm.sendAsyncMessage(MSG_PROMISE_RESULT, {
258             callbackID: payload.callbackID,
259             reject: value,
260           });
261         };
263         let API = AddonManager.webAPI;
264         if (payload.type in API) {
265           API[payload.type](aMessage.target, ...payload.args).then(
266             resolve,
267             reject
268           );
269         } else {
270           reject("Unknown Add-on API request.");
271         }
272         break;
273       }
275       case MSG_INSTALL_CLEANUP: {
276         if (
277           !lazy.extensionsWebAPITesting &&
278           lazy.separatePrivilegedMozillaWebContentProcess &&
279           aMessage.target &&
280           aMessage.target.remoteType != null &&
281           aMessage.target.remoteType !== "privilegedmozilla"
282         ) {
283           return undefined;
284         }
286         AddonManager.webAPI.clearInstalls(payload.ids);
287         break;
288       }
290       case MSG_ADDON_EVENT_REQ: {
291         if (
292           !lazy.extensionsWebAPITesting &&
293           lazy.separatePrivilegedMozillaWebContentProcess &&
294           aMessage.target &&
295           aMessage.target.remoteType != null &&
296           aMessage.target.remoteType !== "privilegedmozilla"
297         ) {
298           return undefined;
299         }
301         let target = aMessage.target.messageManager;
302         if (payload.enabled) {
303           this._addAddonListener(target);
304         } else {
305           this._removeAddonListener(target);
306         }
307       }
308     }
309     return undefined;
310   },
312   childClosed(target) {
313     AddonManager.webAPI.clearInstallsFrom(target);
314     this._removeAddonListener(target);
315   },
317   sendEvent(mm, data) {
318     mm.sendAsyncMessage(MSG_INSTALL_EVENT, data);
319   },
321   classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"),
322   QueryInterface: ChromeUtils.generateQI([
323     "amIAddonManager",
324     "nsITimerCallback",
325     "nsIObserver",
326   ]),
329 const BLOCKLIST_SYS_MJS = "resource://gre/modules/Blocklist.sys.mjs";
330 ChromeUtils.defineESModuleGetters(lazy, { Blocklist: BLOCKLIST_SYS_MJS });
332 export function BlocklistService() {
333   this.wrappedJSObject = this;
336 BlocklistService.prototype = {
337   STATE_NOT_BLOCKED: Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
338   STATE_SOFTBLOCKED: Ci.nsIBlocklistService.STATE_SOFTBLOCKED,
339   STATE_BLOCKED: Ci.nsIBlocklistService.STATE_BLOCKED,
341   get isLoaded() {
342     return Cu.isESModuleLoaded(BLOCKLIST_SYS_MJS) && lazy.Blocklist.isLoaded;
343   },
345   observe(...args) {
346     return lazy.Blocklist.observe(...args);
347   },
349   notify() {
350     lazy.Blocklist.notify();
351   },
353   classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
354   QueryInterface: ChromeUtils.generateQI([
355     "nsIObserver",
356     "nsIBlocklistService",
357     "nsITimerCallback",
358   ]),