mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 16:22:00 +00:00
Bug 1738325 - Uninstall expired themes from BuiltInThemes.jsm. r=dao
This patch allows us to uninstall expired themes. It includes the uninstall logic in BuiltInThemes.ensureBuiltInThemes(), which runs on idle after startup and when about:addons is opened. It also changes aboutaddons.js so we don't show a blank Colorways section in about:addons if there are none installed. Finally, I removed the hidden pref gate controlling the Colorways feature. I initially included that to account for the possibility that we wouldn't finish the feature in time for 94 and we would've needed to uplift a simple patch to disable the dfeature. Since the feature has now shipped, there is no longer a need for the pref. After colorways expire, the user gets to keep the one they had installed going into the expiry, if any. This is information that would usually be associated with the profile. However, the themes are built-in (i.e. not in the profile). I get around this by saving a list of retained themes in a pref. This has drawbacks, such as making it possible to recover expired themes in about:config. I ran this by Product, and they're okay with it. Differential Revision: https://phabricator.services.mozilla.com/D130517
This commit is contained in:
parent
33d0c385da
commit
dbd64d955d
@ -10,173 +10,42 @@ const { XPCOMUtils } = ChromeUtils.import(
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
AddonManager: "resource://gre/modules/AddonManager.jsm",
|
||||
BuiltInThemeConfig: "resource:///modules/BuiltInThemeConfig.jsm",
|
||||
Services: "resource://gre/modules/Services.jsm",
|
||||
});
|
||||
|
||||
// List of themes built in to the browser. The themes are represented by objects
|
||||
// containing their id, current version, and path relative to
|
||||
// resource://builtin-themes/.
|
||||
const STANDARD_THEMES = new Map([
|
||||
[
|
||||
"firefox-compact-light@mozilla.org",
|
||||
{
|
||||
version: "1.2",
|
||||
path: "light/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"firefox-compact-dark@mozilla.org",
|
||||
{
|
||||
version: "1.2",
|
||||
path: "dark/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"firefox-alpenglow@mozilla.org",
|
||||
{
|
||||
version: "1.4",
|
||||
path: "alpenglow/",
|
||||
},
|
||||
],
|
||||
]);
|
||||
const kActiveThemePref = "extensions.activeThemeID";
|
||||
const kRetainedThemesPref = "browser.theme.retainedExpiredThemes";
|
||||
|
||||
const COLORWAY_THEMES = new Map([
|
||||
[
|
||||
"lush-soft-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/lush/soft/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"lush-balanced-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/lush/balanced/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"lush-bold-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/lush/bold/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"abstract-soft-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/abstract/soft/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"abstract-balanced-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/abstract/balanced/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"abstract-bold-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/abstract/bold/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"elemental-soft-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/elemental/soft/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"elemental-balanced-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/elemental/balanced/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"elemental-bold-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/elemental/bold/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"cheers-soft-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/cheers/soft/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"cheers-balanced-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/cheers/balanced/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"cheers-bold-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/cheers/bold/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"graffiti-soft-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/graffiti/soft/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"graffiti-balanced-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/graffiti/balanced/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"graffiti-bold-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/graffiti/bold/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"foto-soft-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/foto/soft/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"foto-balanced-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/foto/balanced/",
|
||||
},
|
||||
],
|
||||
[
|
||||
"foto-bold-colorway@mozilla.org",
|
||||
{
|
||||
version: "1.0",
|
||||
path: "monochromatic/foto/bold/",
|
||||
},
|
||||
],
|
||||
]);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(
|
||||
this,
|
||||
"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 {
|
||||
constructor() {
|
||||
if (!Services.prefs.getBoolPref("browser.theme.colorways.enabled", true)) {
|
||||
// If the colorways pref is false on startup, then we've pushed out a pref
|
||||
// change to clients, and should remove the themes.
|
||||
this._uninstallColorwayThemes();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* The list of themes to be installed. This is exposed on the class so tests
|
||||
* can set custom config files.
|
||||
*/
|
||||
builtInThemeMap = BuiltInThemeConfig;
|
||||
|
||||
/**
|
||||
* @param {string} id An addon's id string.
|
||||
@ -185,18 +54,9 @@ class _BuiltInThemes {
|
||||
* theme's preview image. Null otherwise.
|
||||
*/
|
||||
previewForBuiltInThemeId(id) {
|
||||
if (STANDARD_THEMES.has(id)) {
|
||||
return `resource://builtin-themes/${
|
||||
STANDARD_THEMES.get(id).path
|
||||
}preview.svg`;
|
||||
}
|
||||
if (
|
||||
Services.prefs.getBoolPref("browser.theme.colorways.enabled", true) &&
|
||||
COLORWAY_THEMES.has(id)
|
||||
) {
|
||||
return `resource://builtin-themes/${
|
||||
COLORWAY_THEMES.get(id).path
|
||||
}preview.svg`;
|
||||
let theme = this.builtInThemeMap.get(id);
|
||||
if (theme) {
|
||||
return `${theme.path}preview.svg`;
|
||||
}
|
||||
|
||||
return null;
|
||||
@ -207,17 +67,11 @@ class _BuiltInThemes {
|
||||
* AddonManager.maybeInstallBuiltinAddon for that theme.
|
||||
*/
|
||||
maybeInstallActiveBuiltInTheme() {
|
||||
let activeThemeID = Services.prefs.getStringPref(
|
||||
"extensions.activeThemeID",
|
||||
const activeThemeID = Services.prefs.getStringPref(
|
||||
kActiveThemePref,
|
||||
"default-theme@mozilla.org"
|
||||
);
|
||||
let activeBuiltInTheme = STANDARD_THEMES.get(activeThemeID);
|
||||
if (
|
||||
!activeBuiltInTheme &&
|
||||
Services.prefs.getBoolPref("browser.theme.colorways.enabled", true)
|
||||
) {
|
||||
activeBuiltInTheme = COLORWAY_THEMES.get(activeThemeID);
|
||||
}
|
||||
let activeBuiltInTheme = this.builtInThemeMap.get(activeThemeID);
|
||||
|
||||
if (activeBuiltInTheme) {
|
||||
AddonManager.maybeInstallBuiltinAddon(
|
||||
@ -229,27 +83,25 @@ class _BuiltInThemes {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures that all built-in themes are installed.
|
||||
* Ensures that all built-in themes are installed and expired themes are
|
||||
* uninstalled.
|
||||
*/
|
||||
async ensureBuiltInThemes() {
|
||||
let installPromises = [];
|
||||
for (let [id, { version, path }] of STANDARD_THEMES.entries()) {
|
||||
installPromises.push(
|
||||
AddonManager.maybeInstallBuiltinAddon(
|
||||
id,
|
||||
version,
|
||||
`resource://builtin-themes/${path}`
|
||||
)
|
||||
);
|
||||
}
|
||||
installPromises.push(this._uninstallExpiredThemes());
|
||||
|
||||
if (Services.prefs.getBoolPref("browser.theme.colorways.enabled", true)) {
|
||||
for (let [id, { version, path }] of COLORWAY_THEMES.entries()) {
|
||||
const now = new Date();
|
||||
for (let [id, themeInfo] of this.builtInThemeMap.entries()) {
|
||||
if (
|
||||
!themeInfo.expiry ||
|
||||
retainedThemes.includes(id) ||
|
||||
new Date(themeInfo.expiry) > now
|
||||
) {
|
||||
installPromises.push(
|
||||
AddonManager.maybeInstallBuiltinAddon(
|
||||
id,
|
||||
version,
|
||||
`resource://builtin-themes/${path}`
|
||||
themeInfo.version,
|
||||
themeInfo.path
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -258,16 +110,54 @@ class _BuiltInThemes {
|
||||
await Promise.all(installPromises);
|
||||
}
|
||||
|
||||
async _uninstallColorwayThemes() {
|
||||
for (let id of COLORWAY_THEMES.keys()) {
|
||||
try {
|
||||
let addon = await AddonManager.getAddonByID(id);
|
||||
await addon.uninstall();
|
||||
} catch (e) {
|
||||
Cu.reportError(`Failed to uninstall colorway theme ${id}`);
|
||||
/**
|
||||
* 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 &&
|
||||
!retainedThemes.includes(id) &&
|
||||
new Date(themeInfo.expiry) <= now
|
||||
);
|
||||
for (let [id] of expiredThemes) {
|
||||
if (id == activeThemeID) {
|
||||
this._retainLimitedTimeTheme(id);
|
||||
} else {
|
||||
try {
|
||||
let addon = await AddonManager.getAddonByID(id);
|
||||
if (addon) {
|
||||
await addon.uninstall();
|
||||
}
|
||||
} catch (e) {
|
||||
Cu.reportError(`Failed to uninstall expired theme ${id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 (!retainedThemes.includes(id)) {
|
||||
retainedThemes.push(id);
|
||||
Services.prefs.setStringPref(
|
||||
kRetainedThemesPref,
|
||||
JSON.stringify(retainedThemes)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var BuiltInThemes = new _BuiltInThemes();
|
||||
|
@ -16,6 +16,10 @@ EXTRA_JS_MODULES += [
|
||||
"ThemeVariableMap.jsm",
|
||||
]
|
||||
|
||||
BROWSER_CHROME_MANIFESTS += [
|
||||
"test/browser/browser.ini",
|
||||
]
|
||||
|
||||
toolkit = CONFIG["MOZ_WIDGET_TOOLKIT"]
|
||||
|
||||
if toolkit == "windows":
|
||||
|
3
browser/themes/test/browser/browser.ini
Normal file
3
browser/themes/test/browser/browser.ini
Normal file
@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
|
||||
[browser_expireThemes.js]
|
108
browser/themes/test/browser/browser_expireThemes.js
Normal file
108
browser/themes/test/browser/browser_expireThemes.js
Normal file
@ -0,0 +1,108 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const { sinon } = ChromeUtils.import("resource://testing-common/Sinon.jsm");
|
||||
|
||||
const { XPCOMUtils } = ChromeUtils.import(
|
||||
"resource://gre/modules/XPCOMUtils.jsm"
|
||||
);
|
||||
|
||||
const { AddonTestUtils } = ChromeUtils.import(
|
||||
"resource://testing-common/AddonTestUtils.jsm"
|
||||
);
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
AddonManager: "resource://gre/modules/AddonManager.jsm",
|
||||
BuiltInThemes: "resource:///modules/BuiltInThemes.jsm",
|
||||
});
|
||||
|
||||
const kLushSoftID = "lush-soft-colorway@mozilla.org";
|
||||
const kLushBoldID = "lush-bold-colorway@mozilla.org";
|
||||
const kRetainedThemesPref = "browser.theme.retainedExpiredThemes";
|
||||
|
||||
add_task(async function retainExpiredActiveTheme() {
|
||||
let today = new Date().toISOString().split("T")[0];
|
||||
let tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
tomorrow = tomorrow.toISOString().split("T")[0];
|
||||
let config = new Map([
|
||||
[
|
||||
kLushSoftID,
|
||||
{
|
||||
version: "1.0",
|
||||
path: "resource://builtin-themes/monochromatic/lush/soft/",
|
||||
expiry: tomorrow,
|
||||
},
|
||||
],
|
||||
[
|
||||
kLushBoldID,
|
||||
{
|
||||
version: "1.0",
|
||||
path: "resource://builtin-themes/monochromatic/lush/bold/",
|
||||
expiry: tomorrow,
|
||||
},
|
||||
],
|
||||
]);
|
||||
const oldBuiltInThemeMap = BuiltInThemes.builtInThemeMap;
|
||||
BuiltInThemes.builtInThemeMap = config;
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref(kRetainedThemesPref, "[]"),
|
||||
"[]",
|
||||
"There are no retained themes."
|
||||
);
|
||||
|
||||
AddonTestUtils.initMochitest(this);
|
||||
registerCleanupFunction(async function() {
|
||||
Services.prefs.clearUserPref(kRetainedThemesPref);
|
||||
BuiltInThemes.builtInThemeMap = oldBuiltInThemeMap;
|
||||
await BuiltInThemes.ensureBuiltInThemes();
|
||||
});
|
||||
|
||||
// Install our test themes and enable Lush (Soft).
|
||||
await BuiltInThemes.ensureBuiltInThemes();
|
||||
let lushSoft = await AddonManager.getAddonByID(kLushSoftID);
|
||||
let lushBold = await AddonManager.getAddonByID(kLushBoldID);
|
||||
await lushSoft.enable();
|
||||
Assert.ok(
|
||||
lushSoft && lushSoft.isActive,
|
||||
"Sanity check: Lush Soft is the active theme."
|
||||
);
|
||||
Assert.ok(
|
||||
lushBold && !lushBold.isActive,
|
||||
"Lush Bold is installed but inactive."
|
||||
);
|
||||
|
||||
// Now, change the expiry dates on the themes to simulate the expiry date
|
||||
// passing.
|
||||
BuiltInThemes.builtInThemeMap.forEach(
|
||||
themeInfo => (themeInfo.expiry = today)
|
||||
);
|
||||
// Normally, ensureBuiltInThemes uninstalls expired themes. We
|
||||
// expect it will not uninstall Lush (Soft) since it is the active theme.
|
||||
await BuiltInThemes.ensureBuiltInThemes();
|
||||
lushSoft = await AddonManager.getAddonByID(kLushSoftID);
|
||||
lushBold = await AddonManager.getAddonByID(kLushBoldID);
|
||||
Assert.ok(
|
||||
lushSoft && lushSoft.isActive,
|
||||
"Lush Soft is still the active theme."
|
||||
);
|
||||
Assert.ok(!lushBold, "Lush Bold has been uninstalled.");
|
||||
Assert.equal(
|
||||
Services.prefs.getStringPref(kRetainedThemesPref, "[]"),
|
||||
JSON.stringify([kLushSoftID]),
|
||||
"Lush Soft is set as a retained theme."
|
||||
);
|
||||
|
||||
// Disable Lush (Soft) and re-run ensureBuiltInThemes. We're checking that
|
||||
// Lush Soft is not uninstalled despite being inactive and expired, since it
|
||||
// is a retained theme.
|
||||
await lushSoft.disable();
|
||||
await BuiltInThemes.ensureBuiltInThemes();
|
||||
lushSoft = await AddonManager.getAddonByID(kLushSoftID);
|
||||
Assert.ok(
|
||||
lushSoft && !lushSoft.isActive,
|
||||
"Lush Soft is installed but inactive."
|
||||
);
|
||||
|
||||
await lushSoft.uninstall();
|
||||
});
|
@ -4599,11 +4599,6 @@ gViewController.defineView("list", async type => {
|
||||
let isMonochromaticTheme = addon =>
|
||||
addon.id.endsWith("-colorway@mozilla.org");
|
||||
|
||||
let monochromaticEnabled = Services.prefs.getBoolPref(
|
||||
"browser.theme.colorways.enabled",
|
||||
true
|
||||
);
|
||||
|
||||
let frag = document.createDocumentFragment();
|
||||
let list = document.createElement("addon-list");
|
||||
list.type = type;
|
||||
@ -4626,7 +4621,7 @@ gViewController.defineView("list", async type => {
|
||||
list.setSections(sections);
|
||||
frag.appendChild(list);
|
||||
|
||||
if (type == "theme" && monochromaticEnabled) {
|
||||
if (type == "theme") {
|
||||
let monochromaticList = document.createElement("addon-list");
|
||||
monochromaticList.classList.add("monochromatic-addon-list");
|
||||
monochromaticList.type = type;
|
||||
|
@ -2,6 +2,10 @@
|
||||
* 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/. */
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||
BuiltInThemes: "resource:///modules/BuiltInThemes.jsm",
|
||||
});
|
||||
|
||||
// Maps add-on descriptors to updated Fluent IDs. Keep it in sync
|
||||
// with the list in XPIDatabase.jsm.
|
||||
const updatedAddonFluentIds = new Map([
|
||||
@ -38,24 +42,19 @@ add_task(async function test_ensure_bundled_addons_are_localized() {
|
||||
}
|
||||
}
|
||||
|
||||
// We verify colorway themes separately so we can more easily remove or modify
|
||||
// this section when they are removed.
|
||||
let colorwayBuiltInThemes = addons.filter(
|
||||
addon =>
|
||||
addon.isBuiltin &&
|
||||
addon.type === "theme" &&
|
||||
addon.id.endsWith("colorway@mozilla.org")
|
||||
);
|
||||
ok(!!colorwayBuiltInThemes.length, "Colorway themes should exist");
|
||||
for (let colorwayTheme of colorwayBuiltInThemes) {
|
||||
let l10nId = colorwayTheme.id.replace("@mozilla.org", "");
|
||||
let colorwayThemes = Array.from(
|
||||
BuiltInThemes.builtInThemeMap.keys()
|
||||
).filter(id => id.endsWith("colorway@mozilla.org"));
|
||||
ok(!!colorwayThemes.length, "Colorway themes should exist");
|
||||
for (let id of colorwayThemes) {
|
||||
let l10nId = id.replace("@mozilla.org", "");
|
||||
let [, variantName] = l10nId.split("-", 2);
|
||||
let defaultFluentId = `extension-colorways-${variantName}-name`;
|
||||
let fluentId =
|
||||
updatedAddonFluentIds.get(defaultFluentId) || defaultFluentId;
|
||||
ok(
|
||||
bundle.hasMessage(fluentId),
|
||||
`l10n id for ${colorwayTheme.id} \"name\" attribute should exist`
|
||||
`l10n id for ${id} \"name\" attribute should exist`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user