"use strict"; /** * This file contains test for 'theme' type WebExtension addons. Tests focus mostly * on interoperability between the different theme formats (XUL and LWT) and * Addon Manager integration. * * Coverage may overlap with other tests in this folder. */ const THEME_IDS = [ "theme3@tests.mozilla.org", "theme2@personas.mozilla.org", // Unused. Legacy. Evil. "default-theme@mozilla.org", ]; const REAL_THEME_IDS = [THEME_IDS[0], THEME_IDS[2]]; const DEFAULT_THEME = THEME_IDS[2]; const profileDir = gProfD.clone(); profileDir.append("extensions"); Services.prefs.setIntPref( "extensions.enabledScopes", AddonManager.SCOPE_PROFILE | AddonManager.SCOPE_APPLICATION ); // We remember the last/ currently active theme for tracking events. var gActiveTheme = null; add_task(async function setup_to_default_browserish_state() { createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2"); await promiseWriteWebManifestForExtension( { author: "Some author", manifest_version: 2, name: "Web Extension Name", version: "1.0", theme: { images: { theme_frame: "example.png" } }, browser_specific_settings: { gecko: { id: THEME_IDS[0], }, }, }, profileDir ); await promiseStartupManager(); if (AppConstants.MOZ_DEV_EDITION) { // Developer Edition selects the wrong theme by default. let defaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME); await defaultTheme.enable(); } let [t1, t2, d] = await promiseAddonsByIDs(THEME_IDS); Assert.ok(t1, "Theme addon should exist"); Assert.equal(t2, null, "Theme addon is not a thing anymore"); Assert.ok(d, "Theme addon should exist"); await t1.disable(); await new Promise(executeSoon); Assert.ok(!t1.isActive, "Theme should be disabled"); Assert.ok(d.isActive, "Default theme should be active"); await promiseRestartManager(); [t1, t2, d] = await promiseAddonsByIDs(THEME_IDS); Assert.ok(!t1.isActive, "Theme should still be disabled"); Assert.ok(d.isActive, "Default theme should still be active"); gActiveTheme = d.id; }); /** * Set the `userDisabled` property of one specific theme and check if the theme * switching works as expected by checking the state of all installed themes. * * @param {String} which ID of the addon to set the `userDisabled` property on * @param {Boolean} disabled Flag value to switch to */ async function setDisabledStateAndCheck(which, disabled = false) { if (disabled) { Assert.equal(which, gActiveTheme, "Only the active theme can be disabled"); } let themeToDisable = disabled ? which : gActiveTheme; let themeToEnable = disabled ? DEFAULT_THEME : which; let expectedStates = { [themeToDisable]: true, [themeToEnable]: false, }; let addonEvents = { [themeToDisable]: [{ event: "onDisabling" }, { event: "onDisabled" }], [themeToEnable]: [{ event: "onEnabling" }, { event: "onEnabled" }], }; // Set the state of the theme to change. let theme = await promiseAddonByID(which); await expectEvents({ addonEvents }, () => { if (disabled) { theme.disable(); } else { theme.enable(); } }); let isDisabled; for (theme of await promiseAddonsByIDs(REAL_THEME_IDS)) { isDisabled = theme.id in expectedStates ? expectedStates[theme.id] : true; Assert.equal( theme.userDisabled, isDisabled, `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled` ); Assert.equal( theme.pendingOperations, AddonManager.PENDING_NONE, "There should be no pending operations when no restart is expected" ); Assert.equal( theme.isActive, !isDisabled, `Theme '${theme.id} should be ${isDisabled ? "in" : ""}active` ); } await promiseRestartManager(); // All should still be good after a restart of the Addon Manager. for (theme of await promiseAddonsByIDs(REAL_THEME_IDS)) { isDisabled = theme.id in expectedStates ? expectedStates[theme.id] : true; Assert.equal( theme.userDisabled, isDisabled, `Theme '${theme.id}' should be ${isDisabled ? "dis" : "en"}abled` ); Assert.equal( theme.isActive, !isDisabled, `Theme '${theme.id}' should be ${isDisabled ? "in" : ""}active` ); Assert.equal( theme.pendingOperations, AddonManager.PENDING_NONE, "There should be no pending operations left" ); if (!isDisabled) { gActiveTheme = theme.id; } } } add_task(async function test_WebExtension_themes() { // Enable the WebExtension theme. await setDisabledStateAndCheck(THEME_IDS[0]); // Disabling WebExtension should revert to the default theme. await setDisabledStateAndCheck(THEME_IDS[0], true); // Enable it again. await setDisabledStateAndCheck(THEME_IDS[0]); }); add_task(async function test_default_theme() { // Explicitly enable the default theme. await setDisabledStateAndCheck(DEFAULT_THEME); // Swith to the WebExtension theme. await setDisabledStateAndCheck(THEME_IDS[0]); // Enable it again. await setDisabledStateAndCheck(DEFAULT_THEME); }); add_task(async function uninstall_offers_undo() { let defaultTheme = await AddonManager.getAddonByID(DEFAULT_THEME); const ID = THEME_IDS[0]; let theme = await promiseAddonByID(ID); Assert.ok(theme, "Webextension theme is present"); async function promiseAddonEvent(event, id) { let [addon] = await AddonTestUtils.promiseAddonEvent(event); if (id) { Assert.equal(addon.id, id, `Got event for expected addon (${event})`); } } async function uninstallTheme() { let uninstallingPromise = promiseAddonEvent("onUninstalling", ID); await theme.uninstall(true); await uninstallingPromise; Assert.ok( hasFlag(theme.pendingOperations, AddonManager.PENDING_UNINSTALL), "Theme being uninstalled has PENDING_UNINSTALL flag" ); } async function cancelUninstallTheme() { let cancelPromise = promiseAddonEvent("onOperationCancelled", ID); theme.cancelUninstall(); await cancelPromise; Assert.equal( theme.pendingOperations, AddonManager.PENDING_NONE, "PENDING_UNINSTALL flag is cleared when uninstall is canceled" ); } // A theme should still be disabled if the uninstallation of a disabled theme // is undone. Assert.ok(!theme.isActive, "Webextension theme is not active"); Assert.ok(defaultTheme.isActive, "Default theme is active"); await uninstallTheme(); await cancelUninstallTheme(); Assert.ok(!theme.isActive, "Webextension theme is still not active"); Assert.ok(defaultTheme.isActive, "Default theme is still active"); // Enable theme, the previously active theme should be disabled. await Promise.all([ promiseAddonEvent("onDisabled", DEFAULT_THEME), promiseAddonEvent("onEnabled", ID), theme.enable(), ]); Assert.ok(theme.isActive, "Webextension theme is active after enabling"); Assert.ok(!defaultTheme.isActive, "Default theme is not active any more"); // Uninstall active theme, default theme should become active. await Promise.all([ // Note: no listener for onDisabled & ID because the uninstall is pending. promiseAddonEvent("onEnabled", DEFAULT_THEME), uninstallTheme(), ]); Assert.ok(!theme.isActive, "Webextension theme is not active upon uninstall"); Assert.ok(defaultTheme.isActive, "Default theme is active again"); // Undo uninstall, default theme should be deactivated. await Promise.all([ // Note: no listener for onEnabled & ID because the uninstall was pending. promiseAddonEvent("onDisabled", DEFAULT_THEME), cancelUninstallTheme(), ]); Assert.ok(theme.isActive, "Webextension theme is active upon undo uninstall"); Assert.ok(!defaultTheme.isActive, "Default theme is not active again"); // Immediately remove the theme. Default theme should be activated. await Promise.all([ promiseAddonEvent("onEnabled", DEFAULT_THEME), theme.uninstall(), ]); await promiseRestartManager(); }); // Test that default_locale works with WE themes add_task(async function default_locale_themes() { let addon = await promiseInstallWebExtension({ manifest: { default_locale: "en", name: "__MSG_name__", description: "__MSG_description__", theme: { colors: { frame: "black", tab_background_text: "white", }, }, }, files: { "_locales/en/messages.json": `{ "name": { "message": "the name" }, "description": { "message": "the description" } }`, }, }); addon = await promiseAddonByID(addon.id); equal(addon.name, "the name"); equal(addon.description, "the description"); equal(addon.type, "theme"); await addon.uninstall(); }); add_task(async function test_theme_update() { let addon = await AddonManager.getAddonByID(DEFAULT_THEME); ok(!addon.userDisabled, "default theme is enabled"); await AddonTestUtils.promiseRestartManager("2"); addon = await AddonManager.getAddonByID(DEFAULT_THEME); ok(!addon.userDisabled, "default theme is enabled after upgrade"); }); add_task(async function test_builtin_theme_permissions() { const ADDON_ID = "mytheme@mozilla.org"; let themeDef = { manifest: { browser_specific_settings: { gecko: { id: ADDON_ID } }, version: "1.0", theme: {}, }, }; function checkPerms(addon) { // builtin themes enable or disable based on disabled state Assert.equal( addon.userDisabled, hasFlag(addon.permissions, AddonManager.PERM_CAN_ENABLE), "enable permission is correct" ); Assert.equal( !addon.userDisabled, hasFlag(addon.permissions, AddonManager.PERM_CAN_DISABLE), "disable permission is correct" ); // builtin themes do not get any other permission Assert.ok( !hasFlag(addon.permissions, AddonManager.PERM_CAN_INSTALL), "cannot install by user" ); Assert.ok( !hasFlag(addon.permissions, AddonManager.PERM_CAN_UPGRADE), "cannot upgrade" ); Assert.ok( !hasFlag(addon.permissions, AddonManager.PERM_CAN_UNINSTALL), "cannot uninstall" ); Assert.ok( !hasFlag( addon.permissions, AddonManager.PERM_CAN_CHANGE_PRIVATEBROWSING_ACCESS ), "can change private browsing access" ); Assert.ok( hasFlag(addon.permissions, AddonManager.PERM_API_CAN_UNINSTALL), "can uninstall via API" ); } await setupBuiltinExtension(themeDef, "first-loc", false); await AddonManager.maybeInstallBuiltinAddon( ADDON_ID, "1.0", "resource://first-loc/" ); let addon = await AddonManager.getAddonByID(ADDON_ID); checkPerms(addon); await addon.enable(); checkPerms(addon); await addon.uninstall(); });