diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /toolkit/mozapps/extensions/test/xpcshell/test_webextension.js | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | toolkit/mozapps/extensions/test/xpcshell/test_webextension.js | 676 |
1 files changed, 676 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js b/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js new file mode 100644 index 0000000000..cd4b376117 --- /dev/null +++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension.js @@ -0,0 +1,676 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +const ID = "webextension1@tests.mozilla.org"; + +const profileDir = gProfD.clone(); +profileDir.append("extensions"); + +const ADDONS = { + webextension_1: { + "manifest.json": { + name: "Web Extension Name", + version: "1.0", + manifest_version: 2, + browser_specific_settings: { + gecko: { + id: "webextension1@tests.mozilla.org", + }, + }, + icons: { + 48: "icon48.png", + 64: "icon64.png", + }, + }, + "chrome.manifest": "content webex ./\n", + }, + webextension_3: { + "manifest.json": { + name: "Web Extensiøn __MSG_name__", + description: "Descriptïon __MSG_desc__ of add-on", + version: "1.0", + manifest_version: 2, + default_locale: "en", + browser_specific_settings: { + gecko: { + id: "webextension3@tests.mozilla.org", + }, + }, + }, + "_locales/en/messages.json": { + name: { + message: "foo ☹", + description: "foo", + }, + desc: { + message: "bar ☹", + description: "bar", + }, + }, + "_locales/fr/messages.json": { + name: { + message: "le foo ☺", + description: "foo", + }, + desc: { + message: "le bar ☺", + description: "bar", + }, + }, + }, +}; + +let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].getService( + Ci.nsIChromeRegistry +); + +createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42"); + +const { + ExtensionParent: { GlobalManager }, +} = ChromeUtils.importESModule( + "resource://gre/modules/ExtensionParent.sys.mjs" +); + +add_task(async function test_1() { + await promiseStartupManager(); + + equal(GlobalManager.extensionMap.size, 0); + + let { addon } = await AddonTestUtils.promiseInstallXPI(ADDONS.webextension_1); + + equal(GlobalManager.extensionMap.size, 1); + ok(GlobalManager.extensionMap.has(ID)); + + Assert.throws( + () => + chromeReg.convertChromeURL( + Services.io.newURI("chrome://webex/content/webex.xul") + ), + error => error.result == Cr.NS_ERROR_FILE_NOT_FOUND, + "Chrome manifest should not have been registered" + ); + + let uri = do_get_addon_root_uri(profileDir, ID); + + checkAddon(ID, addon, { + version: "1.0", + name: "Web Extension Name", + isCompatible: true, + appDisabled: false, + isActive: true, + isSystem: false, + type: "extension", + isWebExtension: true, + signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, + iconURL: `${uri}icon48.png`, + }); + + // Should persist through a restart + await promiseShutdownManager(); + + equal(GlobalManager.extensionMap.size, 0); + + await promiseStartupManager(); + + equal(GlobalManager.extensionMap.size, 1); + ok(GlobalManager.extensionMap.has(ID)); + + addon = await promiseAddonByID(ID); + + uri = do_get_addon_root_uri(profileDir, ID); + + checkAddon(ID, addon, { + version: "1.0", + name: "Web Extension Name", + isCompatible: true, + appDisabled: false, + isActive: true, + isSystem: false, + type: "extension", + signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, + iconURL: `${uri}icon48.png`, + }); + + await addon.disable(); + + equal(GlobalManager.extensionMap.size, 0); + + await addon.enable(); + + equal(GlobalManager.extensionMap.size, 1); + ok(GlobalManager.extensionMap.has(ID)); + + await addon.uninstall(); + + equal(GlobalManager.extensionMap.size, 0); + Assert.ok(!GlobalManager.extensionMap.has(ID)); + + await promiseShutdownManager(); +}); + +// Writing the manifest direct to the profile should work +add_task(async function test_2() { + await promiseWriteWebManifestForExtension( + { + name: "Web Extension Name", + version: "1.0", + manifest_version: 2, + browser_specific_settings: { + gecko: { + id: ID, + }, + }, + }, + profileDir + ); + + await promiseStartupManager(); + + let addon = await promiseAddonByID(ID); + checkAddon(ID, addon, { + version: "1.0", + name: "Web Extension Name", + isCompatible: true, + appDisabled: false, + isActive: true, + isSystem: false, + type: "extension", + signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, + }); + + await addon.uninstall(); + + await promiseRestartManager(); +}); + +add_task(async function test_manifest_localization() { + const extensionId = "webextension3@tests.mozilla.org"; + + let { addon } = await AddonTestUtils.promiseInstallXPI(ADDONS.webextension_3); + + await addon.disable(); + + checkAddon(ID, addon, { + name: "Web Extensiøn foo ☹", + description: "Descriptïon bar ☹ of add-on", + }); + + await restartWithLocales(["fr-FR"]); + + addon = await promiseAddonByID(extensionId); + checkAddon(ID, addon, { + name: "Web Extensiøn le foo ☺", + description: "Descriptïon le bar ☺ of add-on", + }); + + await restartWithLocales(["de"]); + + addon = await promiseAddonByID(extensionId); + checkAddon(ID, addon, { + name: "Web Extensiøn foo ☹", + description: "Descriptïon bar ☹ of add-on", + }); + + await addon.uninstall(); +}); + +// Missing version should cause a failure +add_task(async function test_3() { + await promiseWriteWebManifestForExtension( + { + name: "Web Extension Name", + manifest_version: 2, + browser_specific_settings: { + gecko: { + id: ID, + }, + }, + }, + profileDir + ); + + await promiseRestartManager(); + + let addon = await promiseAddonByID(ID); + Assert.equal(addon, null); + + let file = getFileForAddon(profileDir, ID); + Assert.ok(!file.exists()); + + await promiseRestartManager(); +}); + +// Incorrect manifest version should cause a failure +add_task(async function test_4() { + await promiseWriteWebManifestForExtension( + { + name: "Web Extension Name", + version: "1.0", + manifest_version: 1, + browser_specific_settings: { + gecko: { + id: ID, + }, + }, + }, + profileDir + ); + + await promiseRestartManager(); + + let addon = await promiseAddonByID(ID); + Assert.equal(addon, null); + + let file = getFileForAddon(profileDir, ID); + Assert.ok(!file.exists()); + + await promiseRestartManager(); +}); + +// Test that the "options_ui" manifest section is processed correctly. +add_task(async function test_options_ui() { + let OPTIONS_RE = + /^moz-extension:\/\/[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}\/options\.html$/; + + const extensionId = "webextension@tests.mozilla.org"; + let addon = await promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { gecko: { id: extensionId } }, + options_ui: { + page: "options.html", + }, + }, + }); + + checkAddon(extensionId, addon, { + optionsType: AddonManager.OPTIONS_TYPE_INLINE_BROWSER, + }); + + ok( + OPTIONS_RE.test(addon.optionsURL), + "Addon should have a moz-extension: options URL for /options.html" + ); + + await addon.uninstall(); + + const ID2 = "webextension2@tests.mozilla.org"; + addon = await promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { gecko: { id: ID2 } }, + options_ui: { + page: "options.html", + open_in_tab: true, + }, + }, + }); + + checkAddon(ID2, addon, { + optionsType: AddonManager.OPTIONS_TYPE_TAB, + }); + + ok( + OPTIONS_RE.test(addon.optionsURL), + "Addon should have a moz-extension: options URL for /options.html" + ); + + await addon.uninstall(); +}); + +// Test that experiments permissions add the appropriate dependencies. +add_task(async function test_experiments_dependencies() { + let addon = await promiseInstallWebExtension({ + manifest: { + browser_specific_settings: { gecko: { id: "meh@experiment" } }, + permissions: ["experiments.meh"], + }, + }); + + checkAddon(addon.id, addon, { + dependencies: ["meh@experiments.addons.mozilla.org"], + // Add-on should be app disabled due to missing dependencies + appDisabled: true, + }); + + await addon.uninstall(); +}); + +add_task(async function developerShouldOverride() { + let addon = await promiseInstallWebExtension({ + manifest: { + default_locale: "en", + developer: { + name: "__MSG_name__", + url: "__MSG_url__", + }, + author: "Will be overridden by developer", + homepage_url: "https://will.be.overridden", + }, + files: { + "_locales/en/messages.json": `{ + "name": { + "message": "en name" + }, + "url": { + "message": "https://example.net/en" + } + }`, + }, + }); + + checkAddon(ID, addon, { + creator: "en name", + homepageURL: "https://example.net/en", + }); + + await addon.uninstall(); +}); + +add_task(async function test_invalid_developer_does_not_override() { + for (const { type, manifestProps, files } of [ + { + type: "dictionary", + manifestProps: { + dictionaries: { + "en-US": "en-US.dic", + }, + }, + files: { + "en-US.dic": "", + "en-US.aff": "", + }, + }, + { + type: "theme", + manifestProps: { + theme: { + colors: { + frame: "#FFF", + tab_background_text: "#000", + }, + }, + }, + }, + { + type: "locale", + manifestProps: { + langpack_id: "und", + languages: { + und: { + chrome_resources: { + global: "chrome/und/locale/und/global", + }, + version: "20190326174300", + }, + }, + }, + }, + ]) { + const id = `${type}@mozilla.com`; + const creator = "Some author"; + const homepageURL = "https://example.net"; + + info(`== loading add-on with id=${id} ==`); + + for (let developer of [{}, null, { name: null, url: null }]) { + let addon = await promiseInstallWebExtension({ + manifest: { + author: creator, + homepage_url: homepageURL, + developer, + browser_specific_settings: { gecko: { id } }, + ...manifestProps, + }, + files, + }); + + checkAddon(id, addon, { type, creator, homepageURL }); + + await addon.uninstall(); + } + } +}); + +add_task(async function authorNotString() { + ExtensionTestUtils.failOnSchemaWarnings(false); + for (let author of [{}, [], 42]) { + let addon = await promiseInstallWebExtension({ + manifest: { + author, + manifest_version: 2, + name: "Web Extension Name", + version: "1.0", + }, + }); + + checkAddon(ID, addon, { + creator: null, + }); + + await addon.uninstall(); + } + ExtensionTestUtils.failOnSchemaWarnings(true); +}); + +add_task(async function testThemeExtension() { + let addon = await promiseInstallWebExtension({ + manifest: { + author: "Some author", + manifest_version: 2, + name: "Web Extension Name", + version: "1.0", + theme: { images: { theme_frame: "example.png" } }, + }, + }); + + checkAddon(ID, addon, { + creator: "Some author", + version: "1.0", + name: "Web Extension Name", + isCompatible: true, + appDisabled: false, + isActive: false, + userDisabled: true, + isSystem: false, + type: "theme", + isWebExtension: true, + signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, + }); + + await addon.uninstall(); + + // Also test one without a proper 'theme' section. + ExtensionTestUtils.failOnSchemaWarnings(false); + addon = await promiseInstallWebExtension({ + manifest: { + author: "Some author", + manifest_version: 2, + name: "Web Extension Name", + version: "1.0", + theme: null, + }, + }); + ExtensionTestUtils.failOnSchemaWarnings(true); + + checkAddon(ID, addon, { + type: "extension", + isWebExtension: true, + }); + + await addon.uninstall(); +}); + +// Test that we can update from a webextension to a webextension-theme +add_task(async function test_theme_upgrade() { + // First install a regular webextension + let addon = await promiseInstallWebExtension({ + manifest: { + version: "1.0", + name: "Test WebExtension 1 (temporary)", + browser_specific_settings: { + gecko: { + id: ID, + }, + }, + }, + }); + + checkAddon(ID, addon, { + version: "1.0", + name: "Test WebExtension 1 (temporary)", + isCompatible: true, + appDisabled: false, + isActive: true, + type: "extension", + signedState: AddonManager.SIGNEDSTATE_PRIVILEGED, + }); + + // Create a webextension theme with the same ID + addon = await promiseInstallWebExtension({ + manifest: { + version: "2.0", + name: "Test WebExtension 1 (temporary)", + browser_specific_settings: { + gecko: { + id: ID, + }, + }, + theme: { images: { theme_frame: "example.png" } }, + }, + }); + + checkAddon(ID, addon, { + version: "2.0", + name: "Test WebExtension 1 (temporary)", + isCompatible: true, + appDisabled: false, + isActive: true, + // This is what we're really interested in: + type: "theme", + isWebExtension: true, + }); + + await addon.uninstall(); + + addon = await promiseAddonByID(ID); + Assert.equal(addon, null); +}); + +add_task(async function test_developer_properties() { + const name = "developer-name"; + const url = "https://example.org"; + + for (const { type, manifestProps, files } of [ + { + type: "dictionary", + manifestProps: { + dictionaries: { + "en-US": "en-US.dic", + }, + }, + files: { + "en-US.dic": "", + "en-US.aff": "", + }, + }, + { + type: "statictheme", + manifestProps: { + theme: { + colors: { + frame: "#FFF", + tab_background_text: "#000", + }, + }, + }, + }, + { + type: "langpack", + manifestProps: { + langpack_id: "und", + languages: { + und: { + chrome_resources: { + global: "chrome/und/locale/und/global", + }, + version: "20190326174300", + }, + }, + }, + }, + ]) { + const id = `${type}@mozilla.com`; + + info(`== loading add-on with id=${id} ==`); + + let addon = await promiseInstallWebExtension({ + manifest: { + developer: { + name, + url, + }, + author: "Will be overridden by developer", + homepage_url: "https://will.be.overridden", + browser_specific_settings: { gecko: { id } }, + ...manifestProps, + }, + files, + }); + + checkAddon(id, addon, { creator: name, homepageURL: url }); + + await addon.uninstall(); + } +}); + +add_task(async function test_invalid_homepage_and_developer_urls() { + const INVALID_URLS = [ + "chrome://browser/content/", + "data:text/json,...", + "javascript:;", + "/", + "not-an-url", + ]; + const EXPECTED_ERROR_RE = + /Access denied for URL|may not load or link to|is not a valid URL/; + + for (let url of INVALID_URLS) { + // First, we verify `homepage_url`, which has a `url` "format" defined + // since it exists. + let normalized = await ExtensionTestUtils.normalizeManifest({ + homepage_url: url, + }); + ok( + EXPECTED_ERROR_RE.test(normalized.error), + `got expected error for ${url}` + ); + + // The `developer.url` now has a "format" but it was a late addition so we + // are only raising a warning instead of an error. + ExtensionTestUtils.failOnSchemaWarnings(false); + normalized = await ExtensionTestUtils.normalizeManifest({ + developer: { url }, + }); + ok(!normalized.error, "expected no error"); + ok( + // Despites this prop being named `errors`, we are checking the warnings + // here. + EXPECTED_ERROR_RE.test(normalized.errors[0]), + `got expected warning for ${url}` + ); + ExtensionTestUtils.failOnSchemaWarnings(true); + } +}); + +add_task(async function test_valid_homepage_and_developer_urls() { + let normalized = await ExtensionTestUtils.normalizeManifest({ + developer: { url: "https://example.com" }, + }); + ok(!normalized.error, "expected no error"); + + normalized = await ExtensionTestUtils.normalizeManifest({ + homepage_url: "https://example.com", + }); + ok(!normalized.error, "expected no error"); +}); |