mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			311 lines
		
	
	
	
		
			8.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			311 lines
		
	
	
	
		
			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);
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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.
 | 
						|
   *
 | 
						|
   * @param {string} id
 | 
						|
   *   A theme's ID.
 | 
						|
   * @returns {boolean}
 | 
						|
   *   Returns true if the theme is expired. False otherwise.
 | 
						|
   */
 | 
						|
  themeIsExpired(id) {
 | 
						|
    let themeInfo = this.builtInThemeMap.get(id);
 | 
						|
    return themeInfo?.expiry && new Date(themeInfo.expiry) < new Date();
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * @param {string} id
 | 
						|
   *   The theme's id.
 | 
						|
   * @returns {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.
 | 
						|
   * @returns {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} colorwayId
 | 
						|
   *   The ID of the colorway add-on.
 | 
						|
   * @returns {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} colorwayId
 | 
						|
   *   The ID of the colorway add-on.
 | 
						|
   * @returns {string}
 | 
						|
   *   L10nId for intensity value of the colorway with the provided id, null if
 | 
						|
   *   there's none.
 | 
						|
   */
 | 
						|
  getColorwayIntensityL10nId(colorwayId) {
 | 
						|
    const result = ColorwayIntensityIdPostfixToL10nMap.find(([postfix]) =>
 | 
						|
      colorwayId.endsWith(postfix)
 | 
						|
    );
 | 
						|
    return result ? result[1] : null;
 | 
						|
  }
 | 
						|
 | 
						|
  /**
 | 
						|
   * @param {string} colorwayId
 | 
						|
   *   The ID of the colorway add-on.
 | 
						|
   * @returns {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();
 |