mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-11-08 20:28:42 +02:00
This patch ensures that we store in the addon db the url that has triggered an xpi file to install, when available (e.g. when the xpi file installation has been triggered from a website using the installTrigger or mozAddonManager APIs, or when a webpage is navigated to an xpi file url). The url collected is never sent as part of the addons telemetry events, but in a follow up patch it is going to be included in the data submitted for an abuse report of the related addon. Differential Revision: https://phabricator.services.mozilla.com/D62264 --HG-- extra : moz-landing-system : lando
409 lines
11 KiB
JavaScript
409 lines
11 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/. */
|
|
|
|
/**
|
|
* This component serves as integration between the platform and AddonManager.
|
|
* It is responsible for initializing and shutting down the AddonManager as well
|
|
* as passing new installs from webpages to the AddonManager.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
ChromeUtils.defineModuleGetter(
|
|
this,
|
|
"AppConstants",
|
|
"resource://gre/modules/AppConstants.jsm"
|
|
);
|
|
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
);
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
this,
|
|
"separatePrivilegedMozillaWebContentProcess",
|
|
"browser.tabs.remote.separatePrivilegedMozillaWebContentProcess",
|
|
false
|
|
);
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
this,
|
|
"extensionsWebAPITesting",
|
|
"extensions.webapi.testing",
|
|
false
|
|
);
|
|
|
|
// The old XPInstall error codes
|
|
const EXECUTION_ERROR = -203;
|
|
const CANT_READ_ARCHIVE = -207;
|
|
const USER_CANCELLED = -210;
|
|
const DOWNLOAD_ERROR = -228;
|
|
const UNSUPPORTED_TYPE = -244;
|
|
const SUCCESS = 0;
|
|
|
|
const MSG_INSTALL_ENABLED = "WebInstallerIsInstallEnabled";
|
|
const MSG_INSTALL_ADDON = "WebInstallerInstallAddonFromWebpage";
|
|
const MSG_INSTALL_CALLBACK = "WebInstallerInstallCallback";
|
|
|
|
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 CHILD_SCRIPT = "resource://gre/modules/addons/Content.js";
|
|
|
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
|
|
|
var gSingleton = null;
|
|
|
|
var AddonManager, AddonManagerPrivate;
|
|
function amManager() {
|
|
({ AddonManager, AddonManagerPrivate } = ChromeUtils.import(
|
|
"resource://gre/modules/AddonManager.jsm"
|
|
));
|
|
|
|
Services.mm.loadFrameScript(CHILD_SCRIPT, true, true);
|
|
Services.mm.addMessageListener(MSG_INSTALL_ENABLED, this);
|
|
Services.mm.addMessageListener(MSG_PROMISE_REQUEST, this);
|
|
Services.mm.addMessageListener(MSG_INSTALL_CLEANUP, this);
|
|
Services.mm.addMessageListener(MSG_ADDON_EVENT_REQ, this);
|
|
|
|
Services.ppmm.addMessageListener(MSG_INSTALL_ADDON, this);
|
|
|
|
Services.obs.addObserver(this, "message-manager-close");
|
|
Services.obs.addObserver(this, "message-manager-disconnect");
|
|
|
|
AddonManager.webAPI.setEventHandler(this.sendEvent);
|
|
|
|
// Needed so receiveMessage can be called directly by JS callers
|
|
this.wrappedJSObject = this;
|
|
}
|
|
|
|
amManager.prototype = {
|
|
observe(aSubject, aTopic, aData) {
|
|
switch (aTopic) {
|
|
case "addons-startup":
|
|
AddonManagerPrivate.startup();
|
|
break;
|
|
|
|
case "message-manager-close":
|
|
case "message-manager-disconnect":
|
|
this.childClosed(aSubject);
|
|
break;
|
|
}
|
|
},
|
|
|
|
installAddonFromWebpage(aPayload, aBrowser, aCallback) {
|
|
let retval = true;
|
|
|
|
const { mimetype, triggeringPrincipal, hash, icon, name, uri } = aPayload;
|
|
|
|
if (!AddonManager.isInstallAllowed(mimetype, triggeringPrincipal)) {
|
|
aCallback = null;
|
|
retval = false;
|
|
}
|
|
|
|
let telemetryInfo = {
|
|
source: AddonManager.getInstallSourceFromHost(aPayload.sourceHost),
|
|
sourceURL: aPayload.sourceURL,
|
|
};
|
|
|
|
if ("method" in aPayload) {
|
|
telemetryInfo.method = aPayload.method;
|
|
}
|
|
|
|
AddonManager.getInstallForURL(uri, {
|
|
hash,
|
|
name,
|
|
icon,
|
|
browser: aBrowser,
|
|
triggeringPrincipal,
|
|
telemetryInfo,
|
|
sendCookies: true,
|
|
}).then(aInstall => {
|
|
function callCallback(status) {
|
|
try {
|
|
aCallback.onInstallEnded(uri, status);
|
|
} catch (e) {
|
|
Cu.reportError(e);
|
|
}
|
|
}
|
|
|
|
if (!aInstall) {
|
|
aCallback.onInstallEnded(uri, UNSUPPORTED_TYPE);
|
|
return;
|
|
}
|
|
|
|
if (aCallback) {
|
|
aInstall.addListener({
|
|
onDownloadCancelled(aInstall) {
|
|
callCallback(USER_CANCELLED);
|
|
},
|
|
|
|
onDownloadFailed(aInstall) {
|
|
if (aInstall.error == AddonManager.ERROR_CORRUPT_FILE) {
|
|
callCallback(CANT_READ_ARCHIVE);
|
|
} else {
|
|
callCallback(DOWNLOAD_ERROR);
|
|
}
|
|
},
|
|
|
|
onInstallFailed(aInstall) {
|
|
callCallback(EXECUTION_ERROR);
|
|
},
|
|
|
|
onInstallEnded(aInstall, aStatus) {
|
|
callCallback(SUCCESS);
|
|
},
|
|
});
|
|
}
|
|
|
|
AddonManager.installAddonFromWebpage(
|
|
mimetype,
|
|
aBrowser,
|
|
triggeringPrincipal,
|
|
aInstall
|
|
);
|
|
});
|
|
|
|
return retval;
|
|
},
|
|
|
|
notify(aTimer) {
|
|
AddonManagerPrivate.backgroundUpdateTimerHandler();
|
|
},
|
|
|
|
// Maps message manager instances for content processes to the associated
|
|
// AddonListener instances.
|
|
addonListeners: new Map(),
|
|
|
|
_addAddonListener(target) {
|
|
if (!this.addonListeners.has(target)) {
|
|
let handler = (event, id) => {
|
|
target.sendAsyncMessage(MSG_ADDON_EVENT, { event, id });
|
|
};
|
|
let listener = {
|
|
onEnabling: addon => handler("onEnabling", addon.id),
|
|
onEnabled: addon => handler("onEnabled", addon.id),
|
|
onDisabling: addon => handler("onDisabling", addon.id),
|
|
onDisabled: addon => handler("onDisabled", addon.id),
|
|
onInstalling: addon => handler("onInstalling", addon.id),
|
|
onInstalled: addon => handler("onInstalled", addon.id),
|
|
onUninstalling: addon => handler("onUninstalling", addon.id),
|
|
onUninstalled: addon => handler("onUninstalled", addon.id),
|
|
onOperationCancelled: addon =>
|
|
handler("onOperationCancelled", addon.id),
|
|
};
|
|
this.addonListeners.set(target, listener);
|
|
AddonManager.addAddonListener(listener);
|
|
}
|
|
},
|
|
|
|
_removeAddonListener(target) {
|
|
if (this.addonListeners.has(target)) {
|
|
AddonManager.removeAddonListener(this.addonListeners.get(target));
|
|
this.addonListeners.delete(target);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* messageManager callback function.
|
|
*
|
|
* Listens to requests from child processes for InstallTrigger
|
|
* activity, and sends back callbacks.
|
|
*/
|
|
receiveMessage(aMessage) {
|
|
let payload = aMessage.data;
|
|
|
|
switch (aMessage.name) {
|
|
case MSG_INSTALL_ENABLED:
|
|
return AddonManager.isInstallEnabled(payload.mimetype);
|
|
|
|
case MSG_INSTALL_ADDON: {
|
|
let browser = payload.browsingContext.top.embedderElement;
|
|
|
|
let callback = null;
|
|
if (payload.callbackID != -1) {
|
|
let mm = browser.messageManager;
|
|
callback = {
|
|
onInstallEnded(url, status) {
|
|
mm.sendAsyncMessage(MSG_INSTALL_CALLBACK, {
|
|
callbackID: payload.callbackID,
|
|
url,
|
|
status,
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
return this.installAddonFromWebpage(payload, browser, callback);
|
|
}
|
|
|
|
case MSG_PROMISE_REQUEST: {
|
|
if (
|
|
!extensionsWebAPITesting &&
|
|
separatePrivilegedMozillaWebContentProcess &&
|
|
aMessage.target &&
|
|
aMessage.target.remoteType != null &&
|
|
aMessage.target.remoteType !== "privilegedmozilla"
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
let mm = aMessage.target.messageManager;
|
|
let resolve = value => {
|
|
mm.sendAsyncMessage(MSG_PROMISE_RESULT, {
|
|
callbackID: payload.callbackID,
|
|
resolve: value,
|
|
});
|
|
};
|
|
let reject = value => {
|
|
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
|
|
);
|
|
} else {
|
|
reject("Unknown Add-on API request.");
|
|
}
|
|
break;
|
|
}
|
|
|
|
case MSG_INSTALL_CLEANUP: {
|
|
if (
|
|
!extensionsWebAPITesting &&
|
|
separatePrivilegedMozillaWebContentProcess &&
|
|
aMessage.target &&
|
|
aMessage.target.remoteType != null &&
|
|
aMessage.target.remoteType !== "privilegedmozilla"
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
AddonManager.webAPI.clearInstalls(payload.ids);
|
|
break;
|
|
}
|
|
|
|
case MSG_ADDON_EVENT_REQ: {
|
|
if (
|
|
!extensionsWebAPITesting &&
|
|
separatePrivilegedMozillaWebContentProcess &&
|
|
aMessage.target &&
|
|
aMessage.target.remoteType != null &&
|
|
aMessage.target.remoteType !== "privilegedmozilla"
|
|
) {
|
|
return undefined;
|
|
}
|
|
|
|
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(mm, data) {
|
|
mm.sendAsyncMessage(MSG_INSTALL_EVENT, data);
|
|
},
|
|
|
|
classID: Components.ID("{4399533d-08d1-458c-a87a-235f74451cfa}"),
|
|
_xpcom_factory: {
|
|
createInstance(aOuter, aIid) {
|
|
if (aOuter != null) {
|
|
throw Components.Exception(
|
|
"Component does not support aggregation",
|
|
Cr.NS_ERROR_NO_AGGREGATION
|
|
);
|
|
}
|
|
|
|
if (!gSingleton) {
|
|
gSingleton = new amManager();
|
|
}
|
|
return gSingleton.QueryInterface(aIid);
|
|
},
|
|
},
|
|
QueryInterface: ChromeUtils.generateQI([
|
|
Ci.amIAddonManager,
|
|
Ci.nsITimerCallback,
|
|
Ci.nsIObserver,
|
|
]),
|
|
};
|
|
|
|
const BLOCKLIST_JSM = "resource://gre/modules/Blocklist.jsm";
|
|
ChromeUtils.defineModuleGetter(this, "Blocklist", BLOCKLIST_JSM);
|
|
|
|
function BlocklistService() {
|
|
this.wrappedJSObject = this;
|
|
this.pluginQueries = [];
|
|
}
|
|
|
|
BlocklistService.prototype = {
|
|
STATE_NOT_BLOCKED: Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
|
|
STATE_SOFTBLOCKED: Ci.nsIBlocklistService.STATE_SOFTBLOCKED,
|
|
STATE_BLOCKED: Ci.nsIBlocklistService.STATE_BLOCKED,
|
|
STATE_OUTDATED: Ci.nsIBlocklistService.STATE_OUTDATED,
|
|
STATE_VULNERABLE_UPDATE_AVAILABLE:
|
|
Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE,
|
|
STATE_VULNERABLE_NO_UPDATE: Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE,
|
|
|
|
get isLoaded() {
|
|
return Cu.isModuleLoaded(BLOCKLIST_JSM) && Blocklist.isLoaded;
|
|
},
|
|
|
|
async getPluginBlocklistState(plugin, appVersion, toolkitVersion) {
|
|
if (AppConstants.platform == "android") {
|
|
return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
|
|
}
|
|
if (Cu.isModuleLoaded(BLOCKLIST_JSM)) {
|
|
return Blocklist.getPluginBlocklistState(
|
|
plugin,
|
|
appVersion,
|
|
toolkitVersion
|
|
);
|
|
}
|
|
|
|
// Blocklist module isn't loaded yet. Queue the query until it is.
|
|
let request = { plugin, appVersion, toolkitVersion };
|
|
let promise = new Promise(resolve => {
|
|
request.resolve = resolve;
|
|
});
|
|
|
|
this.pluginQueries.push(request);
|
|
return promise;
|
|
},
|
|
|
|
observe(...args) {
|
|
return Blocklist.observe(...args);
|
|
},
|
|
|
|
notify() {
|
|
Blocklist.notify();
|
|
},
|
|
|
|
classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
|
|
QueryInterface: ChromeUtils.generateQI([
|
|
Ci.nsIObserver,
|
|
Ci.nsIBlocklistService,
|
|
Ci.nsITimerCallback,
|
|
]),
|
|
};
|
|
|
|
// eslint-disable-next-line no-unused-vars
|
|
var EXPORTED_SYMBOLS = ["amManager", "BlocklistService"];
|