From eaabe2e896a156d1df4fb2aa1002aea9758f5bc3 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Wed, 23 Apr 2025 15:17:29 +0000 Subject: [PATCH] Bug 1815898 - Inform users about Colorway theme removal - part 1 - notification bar, r=fluent-reviewers,bolsson,rpl Differential Revision: https://phabricator.services.mozilla.com/D243441 --- browser/components/BrowserComponents.manifest | 1 + .../en-US/toolkit/global/extensions.ftl | 5 + .../extensions/ColorwayThemeMigration.sys.mjs | 102 +++++++ toolkit/mozapps/extensions/moz.build | 1 + .../extensions/test/browser/browser.toml | 3 + .../browser_colorwaybuiltins_notification.js | 269 ++++++++++++++++++ 6 files changed, 381 insertions(+) create mode 100644 toolkit/mozapps/extensions/ColorwayThemeMigration.sys.mjs create mode 100644 toolkit/mozapps/extensions/test/browser/browser_colorwaybuiltins_notification.js diff --git a/browser/components/BrowserComponents.manifest b/browser/components/BrowserComponents.manifest index abcf9b4ba8ba..2217b44978fb 100644 --- a/browser/components/BrowserComponents.manifest +++ b/browser/components/BrowserComponents.manifest @@ -47,6 +47,7 @@ category browser-idle-startup resource:///modules/UrlbarSearchTermsPersistence.s category browser-idle-startup resource:///modules/ShoppingUtils.sys.mjs ShoppingUtils.init category browser-idle-startup moz-src:///browser/components/search/SERPCategorization.sys.mjs SERPCategorization.init category browser-idle-startup resource://gre/modules/ContentRelevancyManager.sys.mjs ContentRelevancyManager.init +category browser-idle-startup resource://gre/modules/ColorwayThemeMigration.sys.mjs ColorwayThemeMigration.maybeWarn #ifdef MOZ_UPDATER category browser-idle-startup resource://gre/modules/UpdateListener.sys.mjs UpdateListener.maybeShowUnsupportedNotification #endif diff --git a/toolkit/locales/en-US/toolkit/global/extensions.ftl b/toolkit/locales/en-US/toolkit/global/extensions.ftl index 384822c96e59..ca2ddeb3c2b2 100644 --- a/toolkit/locales/en-US/toolkit/global/extensions.ftl +++ b/toolkit/locales/en-US/toolkit/global/extensions.ftl @@ -108,3 +108,8 @@ webext-site-perms-header-unsigned-with-perms = Add { $extension }? This extensio webext-site-perms-midi = Access MIDI devices webext-site-perms-midi-sysex = Access MIDI devices with SysEx support + +## Colorway theme migration + +webext-colorway-theme-migration-notification-message = Your colorway theme was removed. { -brand-shorter-name } updated its colorways collection. You can find the latest versions on the add-ons site. +webext-colorway-theme-migration-notification-button = Get updated colorways diff --git a/toolkit/mozapps/extensions/ColorwayThemeMigration.sys.mjs b/toolkit/mozapps/extensions/ColorwayThemeMigration.sys.mjs new file mode 100644 index 000000000000..16d72725ad71 --- /dev/null +++ b/toolkit/mozapps/extensions/ColorwayThemeMigration.sys.mjs @@ -0,0 +1,102 @@ +/* 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/. */ + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + AddonManager: "resource://gre/modules/AddonManager.sys.mjs", + AddonSettings: "resource://gre/modules/addons/AddonSettings.sys.mjs", + BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs", +}); + +const CLEANUP_UNKNOWN = 0; +const CLEANUP_COMPLETED = 1; +const CLEANUP_COMPLETED_WITH_BUILTIN = 2; + +// Cleanup any colorway builtin theme that may still be installed. +async function uninstallAllColorwayBuiltinThemes(activeThemeID) { + const CLEANUP_PREF = "extensions.colorway-builtin-themes-cleanup"; + + if ( + Services.prefs.getIntPref(CLEANUP_PREF, CLEANUP_UNKNOWN) != CLEANUP_UNKNOWN + ) { + return false; + } + + let builtinColorwayThemeFound = false; + let activeThemeUninstalling = false; + + const themes = await lazy.AddonManager.getAddonsByTypes(["theme"]); + for (const theme of themes) { + if (theme.isBuiltinColorwayTheme) { + builtinColorwayThemeFound = true; + if (theme.id === activeThemeID) { + activeThemeUninstalling = true; + } + + theme.uninstall(); + } + } + + Services.prefs.setIntPref( + CLEANUP_PREF, + builtinColorwayThemeFound + ? CLEANUP_COMPLETED_WITH_BUILTIN + : CLEANUP_COMPLETED + ); + + return activeThemeUninstalling; +} + +export const ColorwayThemeMigration = { + maybeWarn: async () => { + const activeThemeID = Services.prefs.getCharPref( + "extensions.activeThemeID", + "" + ); + + // Let's remove all the existing colorwy builtin themes. + const activeThemeUninstalled = await uninstallAllColorwayBuiltinThemes( + activeThemeID + ).catch(err => { + console.warn("Error on uninstalling all colorways builtin themes", err); + }); + + if (!activeThemeUninstalled) { + return; + } + + // This can go async. + lazy.AddonManager.getAddonByID(lazy.AddonSettings.DEFAULT_THEME_ID).then( + addon => addon.enable() + ); + + const win = lazy.BrowserWindowTracker.getTopWindow(); + win.MozXULElement.insertFTLIfNeeded("toolkit/global/extensions.ftl"); + + win.gNotificationBox.appendNotification( + "colorway-theme-migration", + { + label: { + "l10n-id": "webext-colorway-theme-migration-notification-message", + }, + priority: win.gNotificationBox.PRIORITY_INFO_MEDIUM, + }, + [ + { + supportPage: "colorways", + }, + { + "l10n-id": "webext-colorway-theme-migration-notification-button", + callback: () => { + win.openTrustedLinkIn( + "https://addons.mozilla.org/firefox/collections/4757633/colorways/", + "tab" + ); + }, + }, + ] + ); + }, +}; diff --git a/toolkit/mozapps/extensions/moz.build b/toolkit/mozapps/extensions/moz.build index 357d249fcd08..2f8d0543464c 100644 --- a/toolkit/mozapps/extensions/moz.build +++ b/toolkit/mozapps/extensions/moz.build @@ -71,6 +71,7 @@ EXTRA_JS_MODULES += [ "amManager.sys.mjs", "amWebAPI.sys.mjs", "Blocklist.sys.mjs", + "ColorwayThemeMigration.sys.mjs", "LightweightThemeManager.sys.mjs", ] diff --git a/toolkit/mozapps/extensions/test/browser/browser.toml b/toolkit/mozapps/extensions/test/browser/browser.toml index c2c0f5e6af78..a776ee6ca474 100644 --- a/toolkit/mozapps/extensions/test/browser/browser.toml +++ b/toolkit/mozapps/extensions/test/browser/browser.toml @@ -64,6 +64,9 @@ support-files = ["head_abuse_report.js"] ["browser_colorwaybuiltins_migration.js"] run-if = ["appname == 'firefox'"] +["browser_colorwaybuiltins_notification.js"] +run-if = ["appname == 'firefox'"] + ["browser_dragdrop.js"] skip-if = ["true"] # Bug 1626824 diff --git a/toolkit/mozapps/extensions/test/browser/browser_colorwaybuiltins_notification.js b/toolkit/mozapps/extensions/test/browser/browser_colorwaybuiltins_notification.js new file mode 100644 index 000000000000..14887d8598a7 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/browser_colorwaybuiltins_notification.js @@ -0,0 +1,269 @@ +"use strict"; + +const { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +const { ColorwayThemeMigration } = ChromeUtils.importESModule( + "resource://gre/modules/ColorwayThemeMigration.sys.mjs" +); + +AddonTestUtils.initMochitest(this); + +const CLEANUP_PREF = "extensions.colorway-builtin-themes-cleanup"; +const NON_COLORWAY_THEME_ID = "test-non@colorway.org"; +const COLORWAY_THEME_ID = "test-colorway@mozilla.org"; + +function mockAsyncUninstallMethod(mockProviderAddon) { + // Override the MockAddon uninstall method to mock the behavior + // of uninstalling an XPIProvider add-on, for which uninstalling + // is asynchonous and the add-on may not be gone right away + // (unlike the MockProvider uninstall method which synchonously remove + // the addon from the MockProvider). + const mockUninstall = mockProviderAddon.uninstall.bind(mockProviderAddon); + mockProviderAddon.uninstall = async () => { + // + // eslint-disable-next-line mozilla/no-arbitrary-setTimeout + await new Promise(resolve => setTimeout(resolve, 1000)); + return mockUninstall(); + }; +} + +function hasNotification() { + return !!window.gNotificationBox.getNotificationWithValue( + "colorway-theme-migration" + ); +} + +function closeNotification() { + const notification = window.gNotificationBox.getNotificationWithValue( + "colorway-theme-migration" + ); + if (notification) { + window.gNotificationBox.removeNotification(notification); + } +} + +async function checkColorwayBuiltinTheme(colorwayThemeExists) { + const colorwayTheme = await AddonManager.getAddonByID(COLORWAY_THEME_ID); + is(!!colorwayTheme, colorwayThemeExists, "The colorway theme exists"); +} + +async function checkNonBuiltinTheme(nonColorwayThemeExists) { + const nonColorwayTheme = await AddonManager.getAddonByID( + NON_COLORWAY_THEME_ID + ); + is( + !!nonColorwayTheme, + nonColorwayThemeExists, + "The non colorway theme exists" + ); +} + +let gProvider; +async function installThemes() { + if (!gProvider) { + gProvider = new MockProvider(["extension"]); + } + + const [mockNonColorwayTheme, mockColorwayTheme] = gProvider.createAddons([ + { + id: NON_COLORWAY_THEME_ID, + name: "Test Non Colorway theme", + creator: { name: "Artist", url: "https://example.com/artist" }, + description: "A nice tree", + type: "theme", + isBuiltinColorwayTheme: false, + isBuiltin: true, + screenshots: [], + }, + { + id: COLORWAY_THEME_ID, + name: "Test Colorway theme", + creator: { name: "Artist", url: "https://example.com/artist" }, + description: "A nice tree", + type: "theme", + isBuiltinColorwayTheme: true, + isBuiltin: true, + screenshots: [], + }, + ]); + + mockAsyncUninstallMethod(mockNonColorwayTheme); + mockAsyncUninstallMethod(mockColorwayTheme); + + await checkColorwayBuiltinTheme(true); + await checkNonBuiltinTheme(true); +} + +add_setup(() => { + // Make sure we do close the notificationbox when this test file has been fully run + // (prevents the notificationbox to stay open when other mochitests may run on the + // same application instance and trigger unexpected failures). + registerCleanupFunction(closeNotification); +}); + +add_task(async function no_colorway_themes() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:blank", + }, + async function () { + // Before running the test, let's close existing notifications. + closeNotification(); + ok(!hasNotification(), "No notification found when the test is starting"); + + await SpecialPowers.pushPrefEnv({ + set: [[CLEANUP_PREF, 0]], + }); + + await ColorwayThemeMigration.maybeWarn(); + ok(!hasNotification(), "No notification shown with the default theme"); + + is(SpecialPowers.getIntPref(CLEANUP_PREF), 1, "The cleanup pref is set"); + await SpecialPowers.popPrefEnv(); + } + ); +}); + +add_task(async function default_theme_no_notification() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:blank", + }, + async function () { + // Before running the test, let's close existing notifications. + closeNotification(); + ok(!hasNotification(), "No notification found when the test is starting"); + + await installThemes(); + + await SpecialPowers.pushPrefEnv({ + set: [[CLEANUP_PREF, 0]], + }); + + // Default theme should not trigger the notification. + const defaultTheme = await AddonManager.getAddonByID( + "default-theme@mozilla.org" + ); + ok(!!defaultTheme, "The default theme exists"); + await defaultTheme.enable(); + + const promiseUninstalled = + AddonTestUtils.promiseAddonEvent("onUninstalled"); + + await ColorwayThemeMigration.maybeWarn(); + ok(!hasNotification(), "No notification shown with the default theme"); + + // No notification shown, but the colorway themes are gone. + await promiseUninstalled; + await checkColorwayBuiltinTheme(false); + await checkNonBuiltinTheme(true); + + is( + SpecialPowers.getIntPref(CLEANUP_PREF), + 2, + "The cleanup pref is set (builtin add-ons found)" + ); + await SpecialPowers.popPrefEnv(); + } + ); +}); + +add_task(async function non_colorway_theme_no_notification() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:blank", + }, + async function () { + // Before running the test, let's close existing notifications. + closeNotification(); + ok(!hasNotification(), "No notification found when the test is starting"); + + await installThemes(); + + await SpecialPowers.pushPrefEnv({ + set: [[CLEANUP_PREF, 0]], + }); + + // Let's force a non-colorway theme. + await (await AddonManager.getAddonByID(NON_COLORWAY_THEME_ID)).enable(); + await SpecialPowers.pushPrefEnv({ + set: [["extensions.activeThemeID", NON_COLORWAY_THEME_ID]], + }); + + const promiseUninstalled = + AddonTestUtils.promiseAddonEvent("onUninstalled"); + + await ColorwayThemeMigration.maybeWarn(); + ok( + !hasNotification(), + "No notification shown with a non-existing theme != colorway" + ); + + // No notification shown, but the colorway themes are gone. + await promiseUninstalled; + await checkColorwayBuiltinTheme(false); + await checkNonBuiltinTheme(true); + + is( + SpecialPowers.getIntPref(CLEANUP_PREF), + 2, + "The cleanup pref is set (builtin add-ons found)" + ); + await SpecialPowers.popPrefEnv(); + } + ); +}); + +add_task(async function colorway_theme_notification() { + await BrowserTestUtils.withNewTab( + { + gBrowser, + url: "about:blank", + }, + async function () { + // Before running the test, let's close existing notifications. + closeNotification(); + ok(!hasNotification(), "No notification found when the test is starting"); + + await installThemes(); + + await SpecialPowers.pushPrefEnv({ + set: [[CLEANUP_PREF, 0]], + }); + + // Mock an active colorway builtin theme. + const mockColorwayTheme = + await AddonManager.getAddonByID(COLORWAY_THEME_ID); + await mockColorwayTheme.enable(); + + await SpecialPowers.pushPrefEnv({ + set: [["extensions.activeThemeID", COLORWAY_THEME_ID]], + }); + + const promiseUninstalled = + AddonTestUtils.promiseAddonEvent("onUninstalled"); + + await ColorwayThemeMigration.maybeWarn(); + ok( + hasNotification(), + "Notification shown with an active colorway builtin theme" + ); + + await promiseUninstalled; + await checkColorwayBuiltinTheme(false); + await checkNonBuiltinTheme(true); + + is( + SpecialPowers.getIntPref(CLEANUP_PREF), + 2, + "The cleanup pref is set (builtin add-ons found)" + ); + await SpecialPowers.popPrefEnv(); + } + ); +});