Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* Any copyright is dedicated to the Public Domain.
*/
"use strict";
const { ExtensionUtils } = ChromeUtils.importESModule(
);
ChromeUtils.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).
//
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:
applications: {
gecko: {
strict_min_version: "60.0",
strict_max_version: "60.*",
},
},
},
{
version: "60.1.20230309.91233",
update_link:
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:
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.*",
},
},
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.*",
};
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.*",
};
// 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.*",
};
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(
);
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;
});