зеркало из https://github.com/mozilla/gecko-dev.git
308 строки
8.9 KiB
JavaScript
308 строки
8.9 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
|
|
BuiltInThemeConfig: "resource:///modules/BuiltInThemeConfig.sys.mjs",
|
|
});
|
|
|
|
const ColorwayL10n = new Localization(["browser/colorways.ftl"], true);
|
|
|
|
const kActiveThemePref = "extensions.activeThemeID";
|
|
const kRetainedThemesPref = "browser.theme.retainedExpiredThemes";
|
|
|
|
const ColorwayIntensityIdPostfixToL10nMap = [
|
|
["-soft-colorway@mozilla.org", "colorway-intensity-soft"],
|
|
["-balanced-colorway@mozilla.org", "colorway-intensity-balanced"],
|
|
["-bold-colorway@mozilla.org", "colorway-intensity-bold"],
|
|
];
|
|
|
|
XPCOMUtils.defineLazyPreferenceGetter(
|
|
lazy,
|
|
"retainedThemes",
|
|
kRetainedThemesPref,
|
|
null,
|
|
null,
|
|
val => {
|
|
if (!val) {
|
|
return [];
|
|
}
|
|
|
|
let parsedVal;
|
|
try {
|
|
parsedVal = JSON.parse(val);
|
|
} catch (ex) {
|
|
console.log(`${kRetainedThemesPref} has invalid value.`);
|
|
return [];
|
|
}
|
|
|
|
return parsedVal;
|
|
}
|
|
);
|
|
|
|
class _BuiltInThemes {
|
|
/**
|
|
* The list of themes to be installed. This is exposed on the class so tests
|
|
* can set custom config files.
|
|
*/
|
|
builtInThemeMap = lazy.BuiltInThemeConfig;
|
|
|
|
/**
|
|
* @param {string} id An addon's id string.
|
|
* @returns {string}
|
|
* If `id` refers to a built-in theme, returns a path pointing to the
|
|
* theme's preview image. Null otherwise.
|
|
*/
|
|
previewForBuiltInThemeId(id) {
|
|
let theme = this.builtInThemeMap.get(id);
|
|
if (theme) {
|
|
return `${theme.path}preview.svg`;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* If the active theme is built-in, this function calls
|
|
* AddonManager.maybeInstallBuiltinAddon for that theme.
|
|
*/
|
|
maybeInstallActiveBuiltInTheme() {
|
|
const activeThemeID = Services.prefs.getStringPref(
|
|
kActiveThemePref,
|
|
"default-theme@mozilla.org"
|
|
);
|
|
let activeBuiltInTheme = this.builtInThemeMap.get(activeThemeID);
|
|
|
|
if (activeBuiltInTheme) {
|
|
lazy.AddonManager.maybeInstallBuiltinAddon(
|
|
activeThemeID,
|
|
activeBuiltInTheme.version,
|
|
`resource://builtin-themes/${activeBuiltInTheme.path}`
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensures that all built-in themes are installed and expired themes are
|
|
* uninstalled.
|
|
*/
|
|
async ensureBuiltInThemes() {
|
|
let installPromises = [];
|
|
installPromises.push(this._uninstallExpiredThemes());
|
|
|
|
const now = new Date();
|
|
for (let [id, themeInfo] of this.builtInThemeMap.entries()) {
|
|
if (
|
|
!themeInfo.expiry ||
|
|
lazy.retainedThemes.includes(id) ||
|
|
new Date(themeInfo.expiry) > now
|
|
) {
|
|
installPromises.push(
|
|
lazy.AddonManager.maybeInstallBuiltinAddon(
|
|
id,
|
|
themeInfo.version,
|
|
themeInfo.path
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
await Promise.all(installPromises);
|
|
}
|
|
|
|
/**
|
|
* @param {string} id
|
|
* A theme's ID.
|
|
* @returns {boolean}
|
|
* Returns true if the theme is expired. False otherwise.
|
|
* @note This looks up the id in a Map rather than accessing a property on
|
|
* the addon itself. That makes calls to this function O(m) where m is the
|
|
* total number of built-in themes offered now or in the past. Since we
|
|
* are using a Map, calls are O(1) in the average case.
|
|
*/
|
|
themeIsExpired(id) {
|
|
let themeInfo = this.builtInThemeMap.get(id);
|
|
return themeInfo?.expiry && new Date(themeInfo.expiry) < new Date();
|
|
}
|
|
|
|
/**
|
|
* @param {string} id
|
|
* The theme's id.
|
|
* @return {boolean}
|
|
* True if the theme with id `id` is both expired and retained. That is,
|
|
* the user has the ability to use it after its expiry date.
|
|
*/
|
|
isRetainedExpiredTheme(id) {
|
|
return lazy.retainedThemes.includes(id) && this.themeIsExpired(id);
|
|
}
|
|
|
|
/**
|
|
* @param {string} id
|
|
* The theme's id.
|
|
* @return {boolean}
|
|
* True if the theme with id `id` is from the currently active theme.
|
|
*/
|
|
isActiveTheme(id) {
|
|
return (
|
|
id ===
|
|
Services.prefs.getStringPref(
|
|
kActiveThemePref,
|
|
"default-theme@mozilla.org"
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Uninstalls themes after they expire. If the expired theme is active, then
|
|
* it is not uninstalled. Instead, it is saved so that the user can use it
|
|
* indefinitely.
|
|
*/
|
|
async _uninstallExpiredThemes() {
|
|
const activeThemeID = Services.prefs.getStringPref(
|
|
kActiveThemePref,
|
|
"default-theme@mozilla.org"
|
|
);
|
|
const now = new Date();
|
|
const expiredThemes = Array.from(this.builtInThemeMap.entries()).filter(
|
|
([id, themeInfo]) =>
|
|
!!themeInfo.expiry &&
|
|
!lazy.retainedThemes.includes(id) &&
|
|
new Date(themeInfo.expiry) <= now
|
|
);
|
|
for (let [id] of expiredThemes) {
|
|
if (id == activeThemeID) {
|
|
let shouldRetain = true;
|
|
|
|
try {
|
|
let addon = await lazy.AddonManager.getAddonByID(id);
|
|
if (addon) {
|
|
// Only add the id to the retain themes pref if it is
|
|
// also a built-in themes (and don't if it was migrated
|
|
// xpi files installed in the user profile).
|
|
shouldRetain = addon.isBuiltinColorwayTheme;
|
|
}
|
|
} catch (e) {
|
|
console.error(
|
|
`Failed to retrieve active theme AddonWrapper ${id}`,
|
|
e
|
|
);
|
|
}
|
|
|
|
if (shouldRetain) {
|
|
this._retainLimitedTimeTheme(id);
|
|
}
|
|
} else {
|
|
try {
|
|
let addon = await lazy.AddonManager.getAddonByID(id);
|
|
// Only uninstall the expired colorways theme if they are not
|
|
// migrated builtins (because on migrated to xpi files
|
|
// installed in the user profile they are also removed
|
|
// from the retainedExpiredThemes pref).
|
|
if (addon?.isBuiltinColorwayTheme) {
|
|
await addon.uninstall();
|
|
}
|
|
} catch (e) {
|
|
console.error(`Failed to uninstall expired theme ${id}`, e);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set a pref to ensure that the user can continue to use a specified theme
|
|
* past its expiry date.
|
|
* @param {string} id
|
|
* The ID of the theme to retain.
|
|
*/
|
|
_retainLimitedTimeTheme(id) {
|
|
if (!lazy.retainedThemes.includes(id)) {
|
|
lazy.retainedThemes.push(id);
|
|
Services.prefs.setStringPref(
|
|
kRetainedThemesPref,
|
|
JSON.stringify(lazy.retainedThemes)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Removes from the retained expired theme list colorways themes that have been
|
|
* migrated from the one installed in the built-in XPIProvider location
|
|
* to an AMO hosted xpi installed in the user profile XPIProvider location.
|
|
* @param {string} id
|
|
* The ID of the theme to remove from the retained themes list.
|
|
*/
|
|
|
|
unretainMigratedColorwayTheme(id) {
|
|
if (lazy.retainedThemes.includes(id)) {
|
|
const retainedThemes = lazy.retainedThemes.filter(
|
|
retainedThemeId => retainedThemeId !== id
|
|
);
|
|
Services.prefs.setStringPref(
|
|
kRetainedThemesPref,
|
|
JSON.stringify(retainedThemes)
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Colorway collections are usually divided into and presented as "groups".
|
|
* A group either contains closely related colorways, e.g. stemming from the
|
|
* same base color but with different intensities (soft, balanced, and bold),
|
|
* or if the current collection doesn't have intensities, each colorway is
|
|
* their own group. Group name localization is optional.
|
|
* @param {string} id
|
|
* The ID of the colorway add-on.
|
|
* @return {string}
|
|
* Localized colorway group name. null if there's no such name, in which
|
|
* case the caller should fall back on getting a name from the add-on API.
|
|
*/
|
|
getLocalizedColorwayGroupName(colorwayId) {
|
|
return this._getColorwayString(colorwayId, "groupName");
|
|
}
|
|
|
|
/**
|
|
* @param {string} id
|
|
* The ID of the colorway add-on.
|
|
* @return {string}
|
|
* L10nId for intensity value of the colorway with the provided id, null if
|
|
* there's none.
|
|
*/
|
|
getColorwayIntensityL10nId(colorwayId) {
|
|
const result = ColorwayIntensityIdPostfixToL10nMap.find(
|
|
([postfix, l10nId]) => colorwayId.endsWith(postfix)
|
|
);
|
|
return result ? result[1] : null;
|
|
}
|
|
|
|
/**
|
|
* @param {string} id
|
|
* The ID of the colorway add-on.
|
|
* @return {string}
|
|
* Localized description of the colorway with the provided id, null if
|
|
* there's none.
|
|
*/
|
|
getLocalizedColorwayDescription(colorwayId) {
|
|
return this._getColorwayString(colorwayId, "description");
|
|
}
|
|
|
|
_getColorwayString(colorwayId, stringType) {
|
|
let l10nId = this.builtInThemeMap.get(colorwayId)?.l10nId?.[stringType];
|
|
let s;
|
|
if (l10nId) {
|
|
[s] = ColorwayL10n.formatMessagesSync([
|
|
{
|
|
id: l10nId,
|
|
},
|
|
]);
|
|
}
|
|
return s?.value || null;
|
|
}
|
|
}
|
|
|
|
export var BuiltInThemes = new _BuiltInThemes();
|