Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
ChromeUtils.defineESModuleGetters(this, {
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
});
AddonTestUtils.init(this);
AddonTestUtils.createAppInfo(
"xpcshell@tests.mozilla.org",
"XPCShell",
"1",
"42"
);
async function testManifest(manifest, expectedError) {
ExtensionTestUtils.failOnSchemaWarnings(false);
let normalized = await ExtensionTestUtils.normalizeManifest(manifest);
ExtensionTestUtils.failOnSchemaWarnings(true);
if (expectedError) {
ok(
expectedError.test(normalized.error),
`Should have an error for ${JSON.stringify(manifest)}, got ${
normalized.error
}`
);
} else {
ok(
!normalized.error,
`Should not have an error ${JSON.stringify(manifest)}, ${
normalized.error
}`
);
}
return normalized.errors;
}
async function testIconPaths(icon, manifest, expectedError) {
let normalized = await ExtensionTestUtils.normalizeManifest(manifest);
if (expectedError) {
ok(
expectedError.test(normalized.error),
`Should have an error for ${JSON.stringify(icon)}`
);
} else {
ok(!normalized.error, `Should not have an error ${JSON.stringify(icon)}`);
}
}
add_setup(async () => {
await AddonTestUtils.promiseStartupManager();
});
add_task(async function test_manifest() {
for (let path of badpaths) {
await testIconPaths(
path,
{
icons: path,
},
/Error processing icons/
);
await testIconPaths(
path,
{
icons: {
16: path,
},
},
/Error processing icons/
);
}
let paths = [
"icon.png",
"/icon.png",
"./icon.png",
"path to an icon.png",
" icon.png",
];
for (let path of paths) {
// manifest.icons is an object
await testIconPaths(
path,
{
icons: path,
},
/Error processing icons/
);
await testIconPaths(path, {
icons: {
16: path,
},
});
}
});
add_task(async function test_manifest_warnings_on_unexpected_props() {
let extension = await ExtensionTestUtils.loadExtension({
manifest: {
background: {
scripts: ["bg.js"],
wrong_prop: true,
},
},
files: {
"bg.js": "",
},
});
ExtensionTestUtils.failOnSchemaWarnings(false);
await extension.startup();
ExtensionTestUtils.failOnSchemaWarnings(true);
// Retrieve the warning message collected by the Extension class
// packagingWarning method.
const { warnings } = extension.extension;
equal(warnings.length, 1, "Got the expected number of manifest warnings");
const expectedMessage =
"Reading manifest: Warning processing background.wrong_prop";
ok(
warnings[0].startsWith(expectedMessage),
"Got the expected warning message format"
);
await extension.unload();
});
add_task(async function test_mv2_scripting_permission_always_enabled() {
let warnings = await testManifest({
manifest_version: 2,
permissions: ["scripting"],
});
Assert.deepEqual(warnings, [], "Got no warnings");
});
add_task(
{
pref_set: [["extensions.manifestV3.enabled", true]],
},
async function test_mv3_scripting_permission_always_enabled() {
let warnings = await testManifest({
manifest_version: 3,
permissions: ["scripting"],
});
Assert.deepEqual(warnings, [], "Got no warnings");
}
);
add_task(async function test_simpler_version_format() {
const TEST_CASES = [
// Valid cases
{ version: "0", expectWarning: false },
{ version: "0.0", expectWarning: false },
{ version: "0.0.0", expectWarning: false },
{ version: "0.0.0.0", expectWarning: false },
{ version: "0.0.0.1", expectWarning: false },
{ version: "0.0.0.999999999", expectWarning: false },
{ version: "0.0.1.0", expectWarning: false },
{ version: "0.0.999999999", expectWarning: false },
{ version: "0.1.0.0", expectWarning: false },
{ version: "0.999999999", expectWarning: false },
{ version: "1", expectWarning: false },
{ version: "1.0", expectWarning: false },
{ version: "1.0.0", expectWarning: false },
{ version: "1.0.0.0", expectWarning: false },
{ version: "1.2.3.4", expectWarning: false },
{ version: "999999999", expectWarning: false },
{
version: "999999999.999999999.999999999.999999999",
expectWarning: false,
},
// Invalid cases
{ version: ".", expectWarning: true },
{ version: ".999999999", expectWarning: true },
{ version: "0.0.0.0.0", expectWarning: true },
{ version: "0.0.0.00001", expectWarning: true },
{ version: "0.0.0.0010", expectWarning: true },
{ version: "0.0.00001", expectWarning: true },
{ version: "0.0.001", expectWarning: true },
{ version: "0.0.01.0", expectWarning: true },
{ version: "0.01.0", expectWarning: true },
{ version: "00001", expectWarning: true },
{ version: "0001", expectWarning: true },
{ version: "001", expectWarning: true },
{ version: "01", expectWarning: true },
{ version: "01.0", expectWarning: true },
{ version: "099999", expectWarning: true },
{ version: "0999999999", expectWarning: true },
{ version: "1.00000", expectWarning: true },
{ version: "1.1.-1", expectWarning: true },
{ version: "1.1000000000", expectWarning: true },
{ version: "1.1pre1aa", expectWarning: true },
{ version: "1.2.1000000000", expectWarning: true },
{ version: "1.2.3.4-a", expectWarning: true },
{ version: "1.2.3.4.5", expectWarning: true },
{ version: "1000000000", expectWarning: true },
{ version: "1000000000.0.0.0", expectWarning: true },
{ version: "999999999.", expectWarning: true },
];
for (const { version, expectWarning } of TEST_CASES) {
const normalized = await ExtensionTestUtils.normalizeManifest({ version });
if (expectWarning) {
Assert.deepEqual(
normalized.errors,
[
`Warning processing version: version must be a version string ` +
`consisting of at most 4 integers of at most 9 digits without ` +
`leading zeros, and separated with dots`,
],
`expected warning for version: ${version}`
);
} else {
Assert.deepEqual(
normalized.errors,
[],
`expected no warning for version: ${version}`
);
}
}
});
add_task(async function test_applications() {
const id = "some@id";
let extension = await ExtensionTestUtils.loadExtension({
manifest: {
manifest_version: 2,
applications: {
gecko: { id, update_url: updateURL },
},
},
useAddonManager: "temporary",
});
ExtensionTestUtils.failOnSchemaWarnings(false);
await extension.startup();
ExtensionTestUtils.failOnSchemaWarnings(true);
Assert.deepEqual(extension.extension.warnings, [], "expected no warnings");
const addon = await AddonManager.getAddonByID(extension.id);
ok(addon, "got an add-on");
equal(addon.id, id, "got expected ID");
equal(addon.updateURL, updateURL, "got expected update URL");
await extension.unload();
});
add_task(
{
pref_set: [["extensions.manifestV3.enabled", true]],
},
async function test_applications_key_mv3() {
let warnings = await testManifest({
manifest_version: 3,
applications: {},
});
Assert.deepEqual(
warnings,
[`Property "applications" is unsupported in Manifest Version 3`],
`Manifest v3 with "applications" key logs an error.`
);
}
);
add_task(async function test_bss_gecko_android() {
const addonId = "some@id";
const isAndroid = AppConstants.platform == "android";
const TEST_CASES = [
{
title: "gecko_android overrides gecko",
browser_specific_settings: {
gecko: {
id: addonId,
strict_min_version: "2",
strict_max_version: "2",
},
gecko_android: {
strict_min_version: "1",
strict_max_version: "1",
},
},
expectedError: isAndroid
? `Add-on ${addonId} is not compatible with application version. add-on minVersion: 1. add-on maxVersion: 1.`
: `Add-on ${addonId} is not compatible with application version. add-on minVersion: 2. add-on maxVersion: 2.`,
},
{
title:
"strict_min_version in gecko_android overrides gecko.strict_min_version",
browser_specific_settings: {
gecko: {
id: addonId,
strict_min_version: "2",
strict_max_version: "3",
},
gecko_android: {
strict_min_version: "3",
},
},
expectedError: isAndroid
? `Add-on ${addonId} is not compatible with application version. add-on minVersion: 3. add-on maxVersion: 3.`
: `Add-on ${addonId} is not compatible with application version. add-on minVersion: 2. add-on maxVersion: 3.`,
},
{
title:
"strict_max_version in gecko_android overrides gecko.strict_max_version",
browser_specific_settings: {
gecko: {
id: addonId,
strict_min_version: "2",
strict_max_version: "2",
},
gecko_android: {
strict_max_version: "3",
},
},
expectedError: isAndroid
? `Add-on ${addonId} is not compatible with application version. add-on minVersion: 2. add-on maxVersion: 3.`
: `Add-on ${addonId} is not compatible with application version. add-on minVersion: 2. add-on maxVersion: 2.`,
},
{
title: "no gecko_android",
browser_specific_settings: {
gecko: {
id: addonId,
strict_min_version: "2",
strict_max_version: "2",
},
},
expectedError: `Add-on ${addonId} is not compatible with application version. add-on minVersion: 2. add-on maxVersion: 2.`,
},
{
title: "empty gecko_android",
browser_specific_settings: {
gecko: {
id: addonId,
strict_min_version: "2",
strict_max_version: "2",
},
gecko_android: {},
},
expectedError: `Add-on ${addonId} is not compatible with application version. add-on minVersion: 2. add-on maxVersion: 2.`,
},
{
title: "empty strict min/max versions in gecko_android",
browser_specific_settings: {
gecko: {
id: addonId,
strict_min_version: "2",
strict_max_version: "2",
},
gecko_android: {
strict_min_version: "",
strict_max_version: "",
},
},
expectedError: `Add-on ${addonId} is not compatible with application version. add-on minVersion: 2. add-on maxVersion: 2.`,
},
{
title: "only strict min/max version in gecko_android",
browser_specific_settings: {
gecko: {
id: addonId,
},
gecko_android: {
strict_min_version: "3",
strict_max_version: "4",
},
},
expectedError: isAndroid
? `Add-on ${addonId} is not compatible with application version. add-on minVersion: 3. add-on maxVersion: 4.`
: null,
},
{
title: "only strict_min_version in gecko_android",
browser_specific_settings: {
gecko: {
id: addonId,
},
gecko_android: {
// The app version is set to `42` at the top of the file.
strict_min_version: "100",
},
},
expectedError: isAndroid
? `Add-on ${addonId} is not compatible with application version. add-on minVersion: 100.`
: null,
},
];
for (const {
title,
browser_specific_settings,
expectedError,
} of TEST_CASES) {
info(`verifying: ${title}`);
// This task is mainly about verifying `bss.gecko_android` and some test
// cases require a "valid" compatibility range by default, which would
// break the assumption below (that the install is going to fail). This is
// why we skip null errors, but only on non-Android builds.
if (expectedError === null) {
notEqual(
AppConstants.platform,
"android",
`${title} - expected no error on a non-Android build`
);
continue;
}
const manifest = {
manifest_version: 2,
version: "1.0",
browser_specific_settings,
};
const extension = ExtensionTestUtils.loadExtension({
manifest,
useAddonManager: "temporary",
});
await Assert.rejects(
extension.startup(),
new RegExp(expectedError),
`${title} - expected error: ${expectedError}`
);
const addon = await AddonManager.getAddonByID(addonId);
equal(addon, null, "add-on is not installed");
}
});
add_task(
{ skip_if: AppConstants.platform !== "android" },
async function test_bss_gecko_android_only() {
const extension = ExtensionTestUtils.loadExtension({
manifest: {
manifest_version: 2,
version: "1.0",
browser_specific_settings: {
gecko_android: {
strict_min_version: "0",
},
},
},
});
await extension.startup();
const { manifest } = extension.extension;
equal(
manifest.browser_specific_settings.gecko_android.strict_min_version,
"0",
"expected strict min version"
);
await extension.unload();
}
);
// Test that presence of 'edge' property in 'browser_specific_settings' doesn't
// prevent installation from completing successfully.
add_task(async function test_non_gecko_bss_install() {
const ID = "ms_edge@tests.mozilla.org";
const manifest = {
name: "MS Edge and unknown browser test",
description:
"extension with bss properties for 'edge', and 'unknown_browser'",
manifest_version: 2,
version: "1.0",
applications: { gecko: { id: ID } },
browser_specific_settings: {
edge: {
browser_action_next_to_addressbar: true,
},
unknown_browser: {
unknown_setting: true,
},
},
};
const extension = ExtensionTestUtils.loadExtension({
manifest,
useAddonManager: "temporary",
});
ExtensionTestUtils.failOnSchemaWarnings(false);
await extension.startup();
ExtensionTestUtils.failOnSchemaWarnings(true);
notEqual(extension.addon, null, "Add-on is installed");
Assert.deepEqual(extension.extension.warnings, [], "expected no warnings");
await extension.unload();
});
// Tests that the presence of an unrecognized property in the gecko and
// gecko_android objects does not prevent successful installation.
add_task(async function test_gecko_bss_unrecognized_property() {
const ID = "with-unknown-gecko-prop@tests.mozilla.org";
const manifest = {
browser_specific_settings: {
gecko: {
id: ID,
aPropThatIsNotSupported: "value does not matter",
},
gecko_android: {
aPropThatIsNotSupported: "aPropThatIsNotSupported",
},
},
};
const extension = ExtensionTestUtils.loadExtension({
manifest,
useAddonManager: "temporary",
});
ExtensionTestUtils.failOnSchemaWarnings(false);
await extension.startup();
ExtensionTestUtils.failOnSchemaWarnings(true);
Assert.deepEqual(
extension.extension.warnings,
[
"Reading manifest: Warning processing browser_specific_settings.gecko.aPropThatIsNotSupported: An unexpected property was found in the WebExtension manifest.",
"Reading manifest: Warning processing browser_specific_settings.gecko_android.aPropThatIsNotSupported: An unexpected property was found in the WebExtension manifest.",
],
"Expected warnings about unrecognized properties"
);
notEqual(extension.addon, null, "Add-on is installed");
await extension.unload();
});
add_task(async function test_load_mv3_theme() {
const BACKGROUND =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0" +
"DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
const theme = ExtensionTestUtils.loadExtension({
manifest: {
manifest_version: 3,
theme: {},
},
useAddonManager: "temporary",
files: {
"bg.png": BACKGROUND,
},
});
// This test would hang if there were errors during install.
await theme.startup();
Assert.equal(
theme.extension.state,
"Startup: Complete",
"Expect theme to have reached the startup completed state"
);
info("Verify image assets packaged in the theme can be fetched");
// This is expected to reject if the WebExtensionPolicy instance
// for this theme has been left in a disabled state (policy.active
// set to false).
await fetch(theme.extension.baseURI.resolve("bg.png"));
await theme.unload();
});