summaryrefslogtreecommitdiffstats
path: root/toolkit/mozapps/extensions/test/xpcshell/test_webextension_langpack.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/mozapps/extensions/test/xpcshell/test_webextension_langpack.js')
-rw-r--r--toolkit/mozapps/extensions/test/xpcshell/test_webextension_langpack.js669
1 files changed, 669 insertions, 0 deletions
diff --git a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_langpack.js b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_langpack.js
new file mode 100644
index 0000000000..40b09d3111
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_langpack.js
@@ -0,0 +1,669 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+"use strict";
+
+const { ExtensionUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/ExtensionUtils.sys.mjs"
+);
+
+XPCOMUtils.defineLazyGetter(this, "resourceProtocol", () =>
+ Services.io
+ .getProtocolHandler("resource")
+ .QueryInterface(Ci.nsIResProtocolHandler)
+);
+
+Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+
+const ID = "langpack-und@test.mozilla.org";
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+// Langpacks versions follow the following convention:
+// <firefox major>.<firefox minor>.YYYYMMDD.HHmmss
+// with no leading zeros allowed (as enforced per version format, see MDN doc page at https://mzl.la/3M6L15y).
+//
+// See https://searchfox.org/mozilla-central/rev/26790fe/python/mozbuild/mozbuild/action/langpack_manifest.py#388-398
+var server = AddonTestUtils.createHttpServer({ hosts: ["example.com"] });
+AddonTestUtils.registerJSON(server, "/test_update_langpack.json", {
+ addons: {
+ "langpack-und@test.mozilla.org": {
+ updates: [
+ {
+ version: "58.0.20230105.121014",
+ applications: {
+ gecko: {
+ strict_min_version: "58.0",
+ strict_max_version: "58.*",
+ },
+ },
+ },
+ {
+ version: "60.0.20230207.112555",
+ update_link:
+ "http://example.com/addons/langpack-und@test.mozilla.org.xpi",
+ applications: {
+ gecko: {
+ strict_min_version: "60.0",
+ strict_max_version: "60.*",
+ },
+ },
+ },
+ {
+ version: "60.1.20230309.91233",
+ update_link:
+ "http://example.com/addons/dotrelease/langpack-und@test.mozilla.org.xpi",
+ applications: {
+ gecko: {
+ strict_min_version: "60.0",
+ strict_max_version: "60.*",
+ },
+ },
+ },
+ ],
+ },
+ },
+});
+
+// A second update url, which is included in the last of the langpack
+// version from the previous one (and used to cover the staging of a
+// langpack from one dotrelease to another).
+AddonTestUtils.registerJSON(server, "/test_update_langpack2.json", {
+ addons: {
+ "langpack-und@test.mozilla.org": {
+ updates: [
+ {
+ version: "60.2.20230319.94511",
+ update_link:
+ "http://example.com/addons/dotrelease2/langpack-und@test.mozilla.org.xpi",
+ applications: {
+ gecko: {
+ strict_min_version: "60.0",
+ strict_max_version: "60.*",
+ },
+ },
+ },
+ ],
+ },
+ },
+});
+
+function promisePostponeInstall(install) {
+ return new Promise((resolve, reject) => {
+ let listener = {
+ onDownloadEnded: () => {
+ install.postpone();
+ },
+ onInstallFailed: () => {
+ install.removeListener(listener);
+ reject(new Error("extension installation should not have failed"));
+ },
+ onInstallEnded: () => {
+ install.removeListener(listener);
+ reject(
+ new Error(
+ `extension installation should not have ended for ${install.addon.id}`
+ )
+ );
+ },
+ onInstallPostponed: () => {
+ install.removeListener(listener);
+ resolve();
+ },
+ };
+
+ install.addListener(listener);
+ });
+}
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "58");
+
+const ADDONS = {
+ langpack_1: {
+ "browser/localization/und/browser.ftl":
+ "message-browser = Value from Browser\n",
+ "localization/und/toolkit_test.ftl": "message-id1 = Value 1\n",
+ "chrome/und/locale/und/global/test.properties":
+ "message = Value from .properties\n",
+ "manifest.json": {
+ name: "und Language Pack",
+ version: "58.0.20230105.121014",
+ manifest_version: 2,
+ browser_specific_settings: {
+ gecko: {
+ id: ID,
+ strict_min_version: "58.0",
+ strict_max_version: "58.*",
+ update_url: "http://example.com/test_update_langpack.json",
+ },
+ },
+ sources: {
+ browser: {
+ base_path: "browser/",
+ },
+ },
+ langpack_id: "und",
+ languages: {
+ und: {
+ chrome_resources: {
+ global: "chrome/und/locale/und/global/",
+ },
+ version: "20171001190118",
+ },
+ },
+ author: "Mozilla Localization Task Force",
+ description: "Language pack for Testy for und",
+ },
+ },
+};
+
+// clone the extension so we can create an update.
+const langpack_update = JSON.parse(JSON.stringify(ADDONS.langpack_1));
+langpack_update["manifest.json"].version = "60.0.20230207.112555";
+langpack_update["manifest.json"].browser_specific_settings.gecko = {
+ id: ID,
+ strict_min_version: "60.0",
+ strict_max_version: "60.*",
+ update_url: "http://example.com/test_update_langpack.json",
+};
+
+const langpack_update_dotrelease = JSON.parse(
+ JSON.stringify(ADDONS.langpack_1)
+);
+langpack_update_dotrelease["manifest.json"].version = "60.1.20230309.91233";
+langpack_update_dotrelease["manifest.json"].browser_specific_settings.gecko = {
+ id: ID,
+ strict_min_version: "60.0",
+ strict_max_version: "60.*",
+ update_url: "http://example.com/test_update_langpack2.json",
+};
+
+// Another langpack for another dot release part of the same major version as the previous one.
+const langpack_update_dotrelease2 = JSON.parse(
+ JSON.stringify(ADDONS.langpack_1)
+);
+langpack_update_dotrelease2["manifest.json"].version = "60.2.20230319.94511";
+langpack_update_dotrelease2["manifest.json"].browser_specific_settings.gecko = {
+ id: ID,
+ strict_min_version: "60.0",
+ strict_max_version: "60.*",
+ update_url: "http://example.com/test_update_langpack2.json",
+};
+
+let xpi = AddonTestUtils.createTempXPIFile(langpack_update);
+server.registerFile(`/addons/${ID}.xpi`, xpi);
+
+let xpiDotRelease = AddonTestUtils.createTempXPIFile(
+ langpack_update_dotrelease
+);
+server.registerFile(`/addons/dotrelease/${ID}.xpi`, xpiDotRelease);
+
+let xpiDotRelease2 = AddonTestUtils.createTempXPIFile(
+ langpack_update_dotrelease2
+);
+server.registerFile(`/addons/dotrelease2/${ID}.xpi`, xpiDotRelease2);
+
+function promiseLangpackStartup() {
+ return new Promise(resolve => {
+ const EVENT = "webextension-langpack-startup";
+ Services.obs.addObserver(function observer() {
+ Services.obs.removeObserver(observer, EVENT);
+ resolve();
+ }, EVENT);
+ });
+}
+
+add_task(async function setup() {
+ Services.prefs.clearUserPref("extensions.startupScanScopes");
+});
+
+/**
+ * This is a basic life-cycle test which verifies that
+ * the language pack registers and unregisters correct
+ * languages at various stages.
+ */
+add_task(async function test_basic_lifecycle() {
+ await promiseStartupManager();
+
+ // Make sure that `und` locale is not installed.
+ equal(
+ L10nRegistry.getInstance().getAvailableLocales().includes("und"),
+ false,
+ "und not installed"
+ );
+ equal(
+ Services.locale.availableLocales.includes("und"),
+ false,
+ "und not available"
+ );
+
+ let [, { addon }] = await Promise.all([
+ promiseLangpackStartup(),
+ AddonTestUtils.promiseInstallXPI(ADDONS.langpack_1),
+ ]);
+
+ // Now make sure that `und` locale is available.
+ equal(
+ L10nRegistry.getInstance().getAvailableLocales().includes("und"),
+ true,
+ "und is installed"
+ );
+ equal(
+ Services.locale.availableLocales.includes("und"),
+ true,
+ "und is available"
+ );
+
+ await addon.disable();
+
+ // It is not available after the langpack has been disabled.
+ equal(
+ L10nRegistry.getInstance().getAvailableLocales().includes("und"),
+ false,
+ "und not installed"
+ );
+ equal(
+ Services.locale.availableLocales.includes("und"),
+ false,
+ "und not available"
+ );
+
+ // This quirky code here allows us to handle a scenario where enabling the
+ // addon is synchronous or asynchronous.
+ await Promise.all([promiseLangpackStartup(), addon.enable()]);
+
+ // After re-enabling it, the `und` locale is available again.
+ equal(
+ L10nRegistry.getInstance().getAvailableLocales().includes("und"),
+ true,
+ "und is installed"
+ );
+ equal(
+ Services.locale.availableLocales.includes("und"),
+ true,
+ "und is available"
+ );
+
+ await addon.uninstall();
+
+ // After the langpack has been uninstalled, no more `und` in locales.
+ equal(
+ L10nRegistry.getInstance().getAvailableLocales().includes("und"),
+ false,
+ "und not installed"
+ );
+ equal(
+ Services.locale.availableLocales.includes("und"),
+ false,
+ "und not available"
+ );
+});
+
+/**
+ * This test verifies that registries are able to load and synchronously return
+ * correct strings available in the language pack.
+ */
+add_task(async function test_locale_registries() {
+ let [, { addon }] = await Promise.all([
+ promiseLangpackStartup(),
+ AddonTestUtils.promiseInstallXPI(ADDONS.langpack_1),
+ ]);
+
+ {
+ // Toolkit string
+ let bundles = L10nRegistry.getInstance().generateBundlesSync(
+ ["und"],
+ ["toolkit_test.ftl"]
+ );
+ let bundle0 = bundles.next().value;
+ ok(bundle0);
+ equal(bundle0.hasMessage("message-id1"), true);
+ }
+
+ {
+ // Browser string
+ let bundles = L10nRegistry.getInstance().generateBundlesSync(
+ ["und"],
+ ["browser.ftl"]
+ );
+ let bundle0 = bundles.next().value;
+ ok(bundle0);
+ equal(bundle0.hasMessage("message-browser"), true);
+ }
+
+ {
+ // Test chrome package
+ let reqLocs = Services.locale.requestedLocales;
+ Services.locale.requestedLocales = ["und"];
+
+ let bundle = Services.strings.createBundle(
+ "chrome://global/locale/test.properties"
+ );
+ let entry = bundle.GetStringFromName("message");
+ equal(entry, "Value from .properties");
+
+ Services.locale.requestedLocales = reqLocs;
+ }
+
+ await addon.uninstall();
+});
+
+/**
+ * This test verifies that registries are able to load and asynchronously return
+ * correct strings available in the language pack.
+ */
+add_task(async function test_locale_registries_async() {
+ let [, { addon }] = await Promise.all([
+ promiseLangpackStartup(),
+ AddonTestUtils.promiseInstallXPI(ADDONS.langpack_1),
+ ]);
+
+ {
+ // Toolkit string
+ let bundles = L10nRegistry.getInstance().generateBundles(
+ ["und"],
+ ["toolkit_test.ftl"]
+ );
+ let bundle0 = (await bundles.next()).value;
+ equal(bundle0.hasMessage("message-id1"), true);
+ }
+
+ {
+ // Browser string
+ let bundles = L10nRegistry.getInstance().generateBundles(
+ ["und"],
+ ["browser.ftl"]
+ );
+ let bundle0 = (await bundles.next()).value;
+ equal(bundle0.hasMessage("message-browser"), true);
+ }
+
+ await addon.uninstall();
+ await promiseShutdownManager();
+});
+
+add_task(async function test_langpack_app_shutdown() {
+ let langpackId = `langpack-und-${AppConstants.MOZ_BUILD_APP.replace(
+ "/",
+ "-"
+ )}`;
+ let check = (yes, msg) => {
+ equal(resourceProtocol.hasSubstitution(langpackId), yes, msg);
+ };
+
+ await promiseStartupManager();
+
+ check(false, "no initial resource substitution");
+
+ await Promise.all([
+ promiseLangpackStartup(),
+ AddonTestUtils.promiseInstallXPI(ADDONS.langpack_1),
+ ]);
+
+ check(true, "langpack resource available after startup");
+
+ await promiseShutdownManager();
+
+ check(true, "langpack resource available after app shutdown");
+
+ await promiseStartupManager();
+
+ let addon = await AddonManager.getAddonByID(ID);
+ await addon.uninstall();
+
+ check(false, "langpack resource removed during shutdown for uninstall");
+
+ await promiseShutdownManager();
+});
+
+add_task(async function test_amazing_disappearing_langpacks() {
+ let check = yes => {
+ equal(
+ L10nRegistry.getInstance().getAvailableLocales().includes("und"),
+ yes,
+ "check L10nRegistry"
+ );
+ equal(
+ Services.locale.availableLocales.includes("und"),
+ yes,
+ "check availableLocales"
+ );
+ };
+
+ await promiseStartupManager();
+
+ check(false);
+
+ await Promise.all([
+ promiseLangpackStartup(),
+ AddonTestUtils.promiseInstallXPI(ADDONS.langpack_1),
+ ]);
+
+ check(true);
+
+ await promiseShutdownManager();
+
+ check(false);
+
+ await AddonTestUtils.manuallyUninstall(AddonTestUtils.profileExtensions, ID);
+
+ await promiseStartupManager();
+
+ check(false);
+});
+
+/**
+ * This test verifies that language pack will get disabled after app
+ * gets upgraded.
+ */
+add_task(async function test_disable_after_app_update() {
+ let [, { addon }] = await Promise.all([
+ promiseLangpackStartup(),
+ AddonTestUtils.promiseInstallXPI(ADDONS.langpack_1),
+ ]);
+ Assert.ok(addon.isActive);
+
+ await promiseRestartManager("59");
+
+ addon = await promiseAddonByID(ID);
+ Assert.ok(!addon.isActive);
+ Assert.ok(addon.appDisabled);
+
+ await addon.uninstall();
+ await promiseShutdownManager();
+});
+
+/**
+ * This test verifies that a postponed language pack update will be
+ * applied after a restart.
+ */
+add_task(async function test_after_app_update() {
+ await promiseStartupManager("58");
+ let [, { addon }] = await Promise.all([
+ promiseLangpackStartup(),
+ AddonTestUtils.promiseInstallXPI(ADDONS.langpack_1),
+ ]);
+ Assert.ok(addon.isActive);
+
+ await promiseRestartManager("60");
+
+ addon = await promiseAddonByID(ID);
+ Assert.ok(!addon.isActive);
+ Assert.ok(addon.appDisabled);
+ Assert.equal(addon.version, "58.0.20230105.121014");
+
+ let update = await promiseFindAddonUpdates(addon);
+ Assert.ok(update.updateAvailable, "update is available");
+ let install = update.updateAvailable;
+ let postponed = promisePostponeInstall(install);
+ install.install();
+ await postponed;
+ Assert.equal(
+ install.state,
+ AddonManager.STATE_POSTPONED,
+ "install postponed"
+ );
+
+ await promiseRestartManager();
+
+ addon = await promiseAddonByID(ID);
+ Assert.ok(addon.isActive);
+ Assert.equal(addon.version, "60.1.20230309.91233");
+
+ await addon.uninstall();
+ await promiseShutdownManager();
+});
+
+// Support setting the request locale.
+function promiseLocaleChanged(requestedLocales) {
+ let changed = ExtensionUtils.promiseObserved(
+ "intl:requested-locales-changed"
+ );
+ Services.locale.requestedLocales = requestedLocales;
+ return changed;
+}
+
+/**
+ * This test verifies that an addon update for the next version can be
+ * retrieved and staged for restart.
+ */
+add_task(async function test_staged_langpack_for_app_update() {
+ let originalLocales = Services.locale.requestedLocales;
+
+ await promiseStartupManager("58");
+ let [, { addon }] = await Promise.all([
+ promiseLangpackStartup(),
+ AddonTestUtils.promiseInstallXPI(ADDONS.langpack_1),
+ ]);
+ Assert.ok(addon.isActive);
+ await promiseLocaleChanged(["und"]);
+
+ // Mimick a major release update happening while a
+ // langpack from a dotrelease what already available
+ // (and then assert that the dotrelease langpack
+ // is the one staged and then installed on browser
+ // restart)
+ await AddonManager.stageLangpacksForAppUpdate("60");
+ await promiseRestartManager("60");
+
+ addon = await promiseAddonByID(ID);
+ Assert.ok(addon.isActive);
+ Assert.equal(addon.version, "60.1.20230309.91233");
+
+ // Mimick a second dotrelease update, along with
+ // staging of the langpacks released along with it
+ // (then assert that the langpack for the second
+ // dotrelease is staged and then installed on
+ // browser restart).
+ await promiseRestartManager("60.1");
+ await AddonManager.stageLangpacksForAppUpdate("60.2");
+ await promiseRestartManager("60.2");
+
+ addon = await promiseAddonByID(ID);
+ Assert.ok(addon.isActive);
+ Assert.equal(addon.version, "60.2.20230319.94511");
+
+ await addon.uninstall();
+ await promiseShutdownManager();
+
+ Services.locale.requestedLocales = originalLocales;
+});
+
+/**
+ * This test verifies that an addon update for the next version can be
+ * retrieved and staged for restart, but a restart failure falls back.
+ */
+add_task(async function test_staged_langpack_for_app_update_fail() {
+ let originalLocales = Services.locale.requestedLocales;
+
+ await promiseStartupManager("58");
+ let [, { addon }] = await Promise.all([
+ promiseLangpackStartup(),
+ AddonTestUtils.promiseInstallXPI(ADDONS.langpack_1),
+ ]);
+ Assert.ok(addon.isActive);
+ await promiseLocaleChanged(["und"]);
+
+ await AddonManager.stageLangpacksForAppUpdate("60");
+ await promiseRestartManager();
+
+ addon = await promiseAddonByID(ID);
+ Assert.ok(addon.isActive);
+ Assert.equal(addon.version, "58.0.20230105.121014");
+
+ await addon.uninstall();
+ await promiseShutdownManager();
+ Services.locale.requestedLocales = originalLocales;
+});
+
+/**
+ * This test verifies that an update restart works when the langpack
+ * cannot be updated.
+ */
+add_task(async function test_staged_langpack_for_app_update_not_found() {
+ let originalLocales = Services.locale.requestedLocales;
+
+ await promiseStartupManager("58");
+ let [, { addon }] = await Promise.all([
+ promiseLangpackStartup(),
+ AddonTestUtils.promiseInstallXPI(ADDONS.langpack_1),
+ ]);
+ Assert.ok(addon.isActive);
+ await promiseLocaleChanged(["und"]);
+
+ await AddonManager.stageLangpacksForAppUpdate("59");
+ await promiseRestartManager("59");
+
+ addon = await promiseAddonByID(ID);
+ Assert.ok(!addon.isActive);
+ Assert.equal(addon.version, "58.0.20230105.121014");
+
+ await addon.uninstall();
+ await promiseShutdownManager();
+ Services.locale.requestedLocales = originalLocales;
+});
+
+/**
+ * This test verifies that a compat update with an invalid max_version
+ * will be disabled, at least allowing Firefox to startup without failures.
+ */
+add_task(async function test_staged_langpack_compat_startup() {
+ let originalLocales = Services.locale.requestedLocales;
+
+ await promiseStartupManager("58");
+ let [, { addon }] = await Promise.all([
+ promiseLangpackStartup(),
+ AddonTestUtils.promiseInstallXPI(ADDONS.langpack_1),
+ ]);
+ Assert.ok(addon.isActive);
+ await promiseLocaleChanged(["und"]);
+
+ // Mimick a compatibility update
+ let compatUpdate = {
+ targetApplications: [
+ {
+ id: "toolkit@mozilla.org",
+ minVersion: "58",
+ maxVersion: "*",
+ },
+ ],
+ };
+ addon.__AddonInternal__.applyCompatibilityUpdate(compatUpdate);
+
+ await promiseRestartManager("59");
+
+ addon = await promiseAddonByID(ID);
+ Assert.ok(!addon.isActive, "addon is not active after upgrade");
+ ok(!addon.isCompatible, "compatibility update fixed");
+
+ await promiseRestartManager("58");
+
+ addon = await promiseAddonByID(ID);
+ Assert.ok(addon.isActive, "addon is active after downgrade");
+ ok(addon.isCompatible, "compatibility update fixed");
+
+ await addon.uninstall();
+ await promiseShutdownManager();
+ Services.locale.requestedLocales = originalLocales;
+});