446 lines
12 KiB
JavaScript
446 lines
12 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
"use strict";
|
|
|
|
const { PermissionTestUtils } = ChromeUtils.importESModule(
|
|
"resource://testing-common/PermissionTestUtils.sys.mjs"
|
|
);
|
|
|
|
AddonTestUtils.init(this);
|
|
AddonTestUtils.overrideCertDB();
|
|
AddonTestUtils.usePrivilegedSignatures = false;
|
|
AddonTestUtils.createAppInfo(
|
|
"xpcshell@tests.mozilla.org",
|
|
"XPCShell",
|
|
"42",
|
|
"42"
|
|
);
|
|
|
|
Services.prefs.setBoolPref("extensions.manifestV3.enabled", true);
|
|
// This pref is not set in Thunderbird, and needs to be true for the test to pass.
|
|
Services.prefs.setBoolPref("extensions.postDownloadThirdPartyPrompt", true);
|
|
|
|
let server = AddonTestUtils.createHttpServer({
|
|
hosts: ["example.com", "example.org", "amo.example.com", "github.io"],
|
|
});
|
|
|
|
server.registerFile(
|
|
`/addons/origins.xpi`,
|
|
AddonTestUtils.createTempXPIFile({
|
|
"manifest.json": {
|
|
manifest_version: 2,
|
|
name: "Install Origins test",
|
|
version: "1.0",
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: "origins@example.com",
|
|
},
|
|
},
|
|
install_origins: ["http://example.com"],
|
|
},
|
|
})
|
|
);
|
|
|
|
server.registerFile(
|
|
`/addons/two_origins.xpi`,
|
|
AddonTestUtils.createTempXPIFile({
|
|
"manifest.json": {
|
|
manifest_version: 2,
|
|
name: "Install Origins test",
|
|
version: "1.0",
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: "two_origins@example.com",
|
|
},
|
|
},
|
|
install_origins: ["http://example.com", "http://example.org"],
|
|
},
|
|
})
|
|
);
|
|
|
|
server.registerFile(
|
|
`/addons/no_origins.xpi`,
|
|
AddonTestUtils.createTempXPIFile({
|
|
"manifest.json": {
|
|
manifest_version: 2,
|
|
name: "Install Origins test",
|
|
version: "1.0",
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: "no_origins@example.com",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
);
|
|
|
|
server.registerFile(
|
|
`/addons/empty_origins.xpi`,
|
|
AddonTestUtils.createTempXPIFile({
|
|
"manifest.json": {
|
|
manifest_version: 2,
|
|
name: "Install Origins test",
|
|
version: "1.0",
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: "no_origins@example.com",
|
|
},
|
|
},
|
|
install_origins: [],
|
|
},
|
|
})
|
|
);
|
|
|
|
server.registerFile(
|
|
`/addons/v3_origins.xpi`,
|
|
AddonTestUtils.createTempXPIFile({
|
|
"manifest.json": {
|
|
manifest_version: 3,
|
|
name: "Install Origins test",
|
|
version: "1.0",
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: "v3_origins@example.com",
|
|
},
|
|
},
|
|
install_origins: ["http://example.com"],
|
|
},
|
|
})
|
|
);
|
|
|
|
server.registerFile(
|
|
`/addons/v3_no_origins.xpi`,
|
|
AddonTestUtils.createTempXPIFile({
|
|
"manifest.json": {
|
|
manifest_version: 3,
|
|
name: "Install Origins test",
|
|
version: "1.0",
|
|
browser_specific_settings: {
|
|
gecko: {
|
|
id: "v3_no_origins@example.com",
|
|
},
|
|
},
|
|
},
|
|
})
|
|
);
|
|
|
|
add_setup(() => {
|
|
do_get_profile();
|
|
Services.fog.initializeFOG();
|
|
});
|
|
|
|
function testInstallEvent(expectTelemetry) {
|
|
const snapshot = Services.telemetry.snapshotEvents(
|
|
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
|
true
|
|
);
|
|
|
|
ok(
|
|
snapshot.parent && !!snapshot.parent.length,
|
|
"Got parent telemetry events in the snapshot"
|
|
);
|
|
|
|
let events = snapshot.parent
|
|
.filter(
|
|
([, category, method, , , extra]) =>
|
|
category === "addonsManager" &&
|
|
method == "install" &&
|
|
extra.step == expectTelemetry.step
|
|
)
|
|
.map(event => event[5]);
|
|
equal(events.length, 1, "one event for install completion");
|
|
Assert.deepEqual(events[0], expectTelemetry, "telemetry matches");
|
|
|
|
let gleanEvents = AddonTestUtils.getAMGleanEvents("install", {
|
|
step: expectTelemetry.step,
|
|
});
|
|
Services.fog.testResetFOG();
|
|
|
|
equal(gleanEvents.length, 1, "One glean event for install completion.");
|
|
delete gleanEvents[0].addon_type;
|
|
Assert.deepEqual(gleanEvents[0], expectTelemetry, "Glean telemetry matches.");
|
|
}
|
|
|
|
function promiseCompleteWebInstall(
|
|
install,
|
|
triggeringPrincipal,
|
|
expectPrompts = true
|
|
) {
|
|
let listener;
|
|
return new Promise(_resolve => {
|
|
let resolve = () => {
|
|
install.removeListener(listener);
|
|
_resolve();
|
|
};
|
|
|
|
listener = {
|
|
onDownloadFailed: resolve,
|
|
onDownloadCancelled: resolve,
|
|
onInstallFailed: resolve,
|
|
onInstallCancelled: resolve,
|
|
onInstallEnded: resolve,
|
|
onInstallPostponed: resolve,
|
|
};
|
|
|
|
install.addListener(listener);
|
|
|
|
// Observers to bypass panels and continue install.
|
|
if (expectPrompts) {
|
|
TestUtils.topicObserved("addon-install-blocked").then(([subject]) => {
|
|
let installInfo = subject.wrappedJSObject;
|
|
info(`==== test got addon-install-blocked ${subject} ${installInfo}`);
|
|
installInfo.install();
|
|
});
|
|
|
|
TestUtils.topicObserved("addon-install-confirmation").then(subject => {
|
|
info(`==== test got addon-install-confirmation`);
|
|
let installInfo = subject.wrappedJSObject;
|
|
for (let installer of installInfo.installs) {
|
|
installer.install();
|
|
}
|
|
});
|
|
TestUtils.topicObserved("webextension-permission-prompt").then(
|
|
([subject]) => {
|
|
const { info } = subject.wrappedJSObject || {};
|
|
info.resolve();
|
|
}
|
|
);
|
|
}
|
|
|
|
AddonManager.installAddonFromWebpage(
|
|
"application/x-xpinstall",
|
|
null /* aBrowser */,
|
|
triggeringPrincipal,
|
|
install
|
|
);
|
|
});
|
|
}
|
|
|
|
async function testAddonInstall(test) {
|
|
let { name, xpiUrl, installPrincipal, expectState, expectTelemetry } = test;
|
|
info(`testAddonInstall: ${name}`);
|
|
let expectInstall = expectState == AddonManager.STATE_INSTALLED;
|
|
let install = await AddonManager.getInstallForURL(xpiUrl, {
|
|
triggeringPrincipal: installPrincipal,
|
|
});
|
|
await promiseCompleteWebInstall(install, installPrincipal, expectInstall);
|
|
|
|
// Test origins telemetry
|
|
testInstallEvent(expectTelemetry);
|
|
|
|
if (expectInstall) {
|
|
equal(
|
|
install.state,
|
|
expectState,
|
|
`${name} ${install.addon.id} install was completed`
|
|
);
|
|
// Wait the extension startup to ensure manifest.json has been read,
|
|
// otherwise we get NS_ERROR_FILE_NOT_FOUND log spam.
|
|
await WebExtensionPolicy.getByID(install.addon.id)?.readyPromise;
|
|
await install.addon.uninstall();
|
|
} else {
|
|
equal(
|
|
install.state,
|
|
expectState,
|
|
`${name} ${install.addon?.id} install failed`
|
|
);
|
|
}
|
|
}
|
|
|
|
let ssm = Services.scriptSecurityManager;
|
|
const PRINCIPAL_AMO = ssm.createContentPrincipalFromOrigin(
|
|
"https://amo.example.com"
|
|
);
|
|
const PRINCIPAL_COM =
|
|
ssm.createContentPrincipalFromOrigin("http://example.com");
|
|
const PRINCIPAL_ORG =
|
|
ssm.createContentPrincipalFromOrigin("http://example.org");
|
|
|
|
const TESTS = [
|
|
{
|
|
name: "Install MV2 with install_origins",
|
|
xpiUrl: "http://example.com/addons/origins.xpi",
|
|
installPrincipal: PRINCIPAL_COM,
|
|
expectState: AddonManager.STATE_INSTALLED,
|
|
expectTelemetry: {
|
|
step: "completed",
|
|
addon_id: "origins@example.com",
|
|
install_origins: "1",
|
|
},
|
|
},
|
|
{
|
|
name: "Install MV2 without install_origins",
|
|
xpiUrl: "http://example.com/addons/no_origins.xpi",
|
|
installPrincipal: PRINCIPAL_COM,
|
|
expectState: AddonManager.STATE_INSTALLED,
|
|
expectTelemetry: {
|
|
step: "completed",
|
|
addon_id: "no_origins@example.com",
|
|
install_origins: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "Install valid xpi location from invalid website",
|
|
xpiUrl: "http://example.com/addons/origins.xpi",
|
|
installPrincipal: PRINCIPAL_ORG,
|
|
expectState: AddonManager.STATE_INSTALL_FAILED,
|
|
expectTelemetry: {
|
|
step: "failed",
|
|
addon_id: "origins@example.com",
|
|
error: "ERROR_INVALID_DOMAIN",
|
|
install_origins: "1",
|
|
},
|
|
},
|
|
{
|
|
name: "Install invalid xpi location from valid website",
|
|
xpiUrl: "http://example.org/addons/origins.xpi",
|
|
installPrincipal: PRINCIPAL_COM,
|
|
expectState: AddonManager.STATE_INSTALL_FAILED,
|
|
expectTelemetry: {
|
|
step: "failed",
|
|
addon_id: "origins@example.com",
|
|
error: "ERROR_INVALID_DOMAIN",
|
|
install_origins: "1",
|
|
},
|
|
},
|
|
{
|
|
name: "Install MV3 with install_origins",
|
|
xpiUrl: "http://example.com/addons/v3_origins.xpi",
|
|
installPrincipal: PRINCIPAL_COM,
|
|
expectState: AddonManager.STATE_INSTALLED,
|
|
expectTelemetry: {
|
|
step: "completed",
|
|
addon_id: "v3_origins@example.com",
|
|
install_origins: "1",
|
|
},
|
|
},
|
|
{
|
|
name: "Install MV3 with install_origins from AMO",
|
|
xpiUrl: "http://example.com/addons/v3_origins.xpi",
|
|
installPrincipal: PRINCIPAL_AMO,
|
|
expectState: AddonManager.STATE_INSTALLED,
|
|
expectTelemetry: {
|
|
step: "completed",
|
|
addon_id: "v3_origins@example.com",
|
|
install_origins: "1",
|
|
},
|
|
},
|
|
{
|
|
name: "Install MV3 without install_origins",
|
|
xpiUrl: "http://example.com/addons/v3_no_origins.xpi",
|
|
installPrincipal: PRINCIPAL_COM,
|
|
expectState: AddonManager.STATE_INSTALL_FAILED,
|
|
expectTelemetry: {
|
|
step: "failed",
|
|
addon_id: "v3_no_origins@example.com",
|
|
error: "ERROR_INVALID_DOMAIN",
|
|
install_origins: "0",
|
|
},
|
|
},
|
|
{
|
|
// An installing principal with install permission is
|
|
// considered "AMO" in code, and will always be allowed.
|
|
name: "Install MV3 without install_origins from AMO",
|
|
xpiUrl: "http://example.com/addons/v3_no_origins.xpi",
|
|
installPrincipal: PRINCIPAL_AMO,
|
|
expectState: AddonManager.STATE_INSTALLED,
|
|
expectTelemetry: {
|
|
step: "completed",
|
|
addon_id: "v3_no_origins@example.com",
|
|
install_origins: "0",
|
|
},
|
|
},
|
|
{
|
|
name: "Install MV3 without install_origins from null principal",
|
|
xpiUrl: "http://example.com/addons/v3_no_origins.xpi",
|
|
installPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
|
|
expectState: AddonManager.STATE_CANCELLED,
|
|
expectTelemetry: { step: "site_blocked", install_origins: "0" },
|
|
},
|
|
{
|
|
name: "Install addon with two install_origins",
|
|
xpiUrl: "http://example.com/addons/two_origins.xpi",
|
|
installPrincipal: PRINCIPAL_ORG,
|
|
expectState: AddonManager.STATE_INSTALLED,
|
|
expectTelemetry: {
|
|
step: "completed",
|
|
addon_id: "two_origins@example.com",
|
|
install_origins: "1",
|
|
},
|
|
},
|
|
{
|
|
name: "Install addon with two install_origins",
|
|
xpiUrl: "http://example.com/addons/two_origins.xpi",
|
|
installPrincipal: PRINCIPAL_COM,
|
|
expectState: AddonManager.STATE_INSTALLED,
|
|
expectTelemetry: {
|
|
step: "completed",
|
|
addon_id: "two_origins@example.com",
|
|
install_origins: "1",
|
|
},
|
|
},
|
|
{
|
|
name: "Install from site with empty install_origins",
|
|
xpiUrl: "http://example.com/addons/empty_origins.xpi",
|
|
installPrincipal: PRINCIPAL_COM,
|
|
expectState: AddonManager.STATE_INSTALL_FAILED,
|
|
expectTelemetry: {
|
|
step: "failed",
|
|
addon_id: "no_origins@example.com",
|
|
error: "ERROR_INVALID_DOMAIN",
|
|
install_origins: "1",
|
|
},
|
|
},
|
|
{
|
|
name: "Install from site with empty install_origins",
|
|
xpiUrl: "http://example.com/addons/empty_origins.xpi",
|
|
installPrincipal: PRINCIPAL_ORG,
|
|
expectState: AddonManager.STATE_INSTALL_FAILED,
|
|
expectTelemetry: {
|
|
step: "failed",
|
|
addon_id: "no_origins@example.com",
|
|
error: "ERROR_INVALID_DOMAIN",
|
|
install_origins: "1",
|
|
},
|
|
},
|
|
{
|
|
name: "Install with empty install_origins from AMO",
|
|
xpiUrl: "http://amo.example.com/addons/empty_origins.xpi",
|
|
installPrincipal: PRINCIPAL_AMO,
|
|
expectState: AddonManager.STATE_INSTALLED,
|
|
expectTelemetry: {
|
|
step: "completed",
|
|
addon_id: "no_origins@example.com",
|
|
install_origins: "1",
|
|
},
|
|
},
|
|
];
|
|
|
|
add_task(async function test_install_url() {
|
|
Services.prefs.setBoolPref("extensions.install_origins.enabled", true);
|
|
PermissionTestUtils.add(
|
|
PRINCIPAL_AMO,
|
|
"install",
|
|
Services.perms.ALLOW_ACTION
|
|
);
|
|
await promiseStartupManager();
|
|
|
|
for (let test of TESTS) {
|
|
await testAddonInstall(test);
|
|
}
|
|
});
|
|
|
|
add_task(async function test_install_origins_disabled() {
|
|
Services.prefs.setBoolPref("extensions.install_origins.enabled", false);
|
|
await testAddonInstall({
|
|
name: "Install MV3 without install_origins, verification disabled",
|
|
xpiUrl: "http://example.com/addons/v3_no_origins.xpi",
|
|
installPrincipal: PRINCIPAL_COM,
|
|
expectState: AddonManager.STATE_INSTALLED,
|
|
expectTelemetry: {
|
|
step: "completed",
|
|
addon_id: "v3_no_origins@example.com",
|
|
},
|
|
});
|
|
});
|