Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
"use strict";
const { PermissionTestUtils } = ChromeUtils.importESModule(
);
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/sitepermission.xpi`,
AddonTestUtils.createTempXPIFile({
"manifest.json": {
manifest_version: 2,
name: "Install Origins sitepermission test",
version: "1.0",
browser_specific_settings: {
gecko: {
id: "sitepermission@example.com",
},
},
install_origins: ["http://example.com"],
site_permissions: ["midi"],
},
})
);
server.registerFile(
`/addons/sitepermission-suffix.xpi`,
AddonTestUtils.createTempXPIFile({
"manifest.json": {
manifest_version: 2,
name: "Install Origins sitepermission public suffix test",
version: "1.0",
browser_specific_settings: {
gecko: {
id: "sitepermission-suffix@github.io",
},
},
install_origins: ["http://github.io"],
site_permissions: ["midi"],
},
})
);
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(
);
const PRINCIPAL_COM =
ssm.createContentPrincipalFromOrigin("http://example.com");
const SUB_PRINCIPAL_COM = ssm.createContentPrincipalFromOrigin(
);
const THIRDPARTY_PRINCIPAL_COM = ssm.createContentPrincipalFromOrigin(
);
const PRINCIPAL_ORG =
ssm.createContentPrincipalFromOrigin("http://example.org");
const PRINCIPAL_ETLD = ssm.createContentPrincipalFromOrigin("http://github.io");
const TESTS = [
{
name: "Install MV2 with install_origins",
installPrincipal: PRINCIPAL_COM,
expectState: AddonManager.STATE_INSTALLED,
expectTelemetry: {
step: "completed",
addon_id: "origins@example.com",
install_origins: "1",
},
},
{
name: "Install MV2 without install_origins",
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",
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",
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",
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",
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",
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",
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",
installPrincipal: Services.scriptSecurityManager.createNullPrincipal({}),
expectState: AddonManager.STATE_CANCELLED,
expectTelemetry: { step: "site_blocked", install_origins: "0" },
},
{
name: "Install addon with two install_origins",
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",
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",
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",
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",
installPrincipal: PRINCIPAL_AMO,
expectState: AddonManager.STATE_INSTALLED,
expectTelemetry: {
step: "completed",
addon_id: "no_origins@example.com",
install_origins: "1",
},
},
{
name: "Install sitepermission from domain",
installPrincipal: PRINCIPAL_COM,
expectState: AddonManager.STATE_INSTALLED,
expectTelemetry: {
step: "completed",
addon_id: "sitepermission@example.com",
install_origins: "1",
},
},
{
name: "Install sitepermission from subdomain",
installPrincipal: SUB_PRINCIPAL_COM,
expectState: AddonManager.STATE_INSTALLED,
expectTelemetry: {
step: "completed",
addon_id: "sitepermission@example.com",
install_origins: "1",
},
},
{
name: "Install sitepermission from thirdparty domain should fail",
installPrincipal: THIRDPARTY_PRINCIPAL_COM,
expectState: AddonManager.STATE_INSTALL_FAILED,
expectTelemetry: {
step: "failed",
addon_id: "sitepermission@example.com",
error: "ERROR_INVALID_DOMAIN",
install_origins: "1",
},
},
{
name: "Install sitepermission from different domain",
installPrincipal: PRINCIPAL_ORG,
expectState: AddonManager.STATE_INSTALL_FAILED,
expectTelemetry: {
step: "failed",
addon_id: "sitepermission@example.com",
error: "ERROR_INVALID_DOMAIN",
install_origins: "1",
},
},
{
name: "Install sitepermission from public suffix domain",
installPrincipal: PRINCIPAL_ETLD,
expectState: AddonManager.STATE_INSTALL_FAILED,
expectTelemetry: {
step: "failed",
addon_id: "sitepermission-suffix@github.io",
error: "ERROR_INVALID_DOMAIN",
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",
installPrincipal: PRINCIPAL_COM,
expectState: AddonManager.STATE_INSTALLED,
expectTelemetry: {
step: "completed",
addon_id: "v3_no_origins@example.com",
},
});
});