Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test gets skipped with pattern: os == 'win' && socketprocess_networking && fission OR os == 'mac' && socketprocess_networking && fission OR os == 'mac' && debug OR os == 'linux' && socketprocess_networking OR os == 'android' OR os == 'win' && debug
- Manifest: toolkit/components/extensions/test/xpcshell/xpcshell-remote.toml includes toolkit/components/extensions/test/xpcshell/xpcshell-common.toml
- Manifest: toolkit/components/extensions/test/xpcshell/xpcshell.toml includes toolkit/components/extensions/test/xpcshell/xpcshell-common.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
AddonTestUtils.init(this);
AddonTestUtils.overrideCertDB();
// LightweightThemeManager.fallbackThemeData setter calls are recorded here:
const gFallbackHistory = [];
// Simulates opening a browser window and retrieves the color.
async function getColorFromAppliedTheme() {
const { LightweightThemeConsumer } = ChromeUtils.importESModule(
"resource://gre/modules/LightweightThemeConsumer.sys.mjs"
);
// The LightweightThemeConsumer class expects a document (browser.xhtml), so
// create a minimal one that looks like it. A blank document works because the
// module does not have any other requirements for the document - the last
const browser = Services.appShell.createWindowlessBrowser(true);
const document = browser.document;
let promise = new Promise(resolve => {
// Will be invoked by LightweightThemeConsumer
document.defaultView.addEventListener("windowlwthemeupdate", resolve, {
once: true,
});
});
new LightweightThemeConsumer(document);
info("Waiting for windowlwthemeupdate from LightweightThemeConsumer");
await promise;
info("Got windowlwthemeupdate from LightweightThemeConsumer");
// This is the color for "colors.frame".
let accentcolor =
document.documentElement.style.getPropertyValue("--lwt-accent-color");
browser.close();
return accentcolor;
}
function loadStaticThemeExtension(manifest) {
const extensionId = manifest.browser_specific_settings.gecko.id;
let extension = ExtensionTestUtils.loadExtension({
manifest,
useAddonManager: "permanent",
});
// When a theme is installed, it starts off in disabled mode, as seen in
// toolkit/mozapps/extensions/test/xpcshell/test_update_theme.js .
// To make sure that ExtensionTestUtils.loadExtension resolves, enable it
// during installation.
extension.onInstalling = function (addon) {
if (addon.id === extensionId) {
equal(addon.userDisabled, true, "Static theme is disabled at install");
// We're modifying the AddonInternal's userDisabled flag instead of
// calling addon.enable(), because using addon.enable() during
// onInstalling results in the Extension's startup() to be called twice,
// which is not realistic in the real world.
addon.__AddonInternal__.userDisabled = false;
equal(addon.userDisabled, false, "Static theme force enabled at install");
}
};
return extension;
}
add_setup(function setup_intercept_fallbackThemeData() {
const { LightweightThemeManager } = ChromeUtils.importESModule(
"resource://gre/modules/LightweightThemeManager.sys.mjs"
);
const fallbackThemeDataPropertyDescriptor = Object.getOwnPropertyDescriptor(
LightweightThemeManager,
"fallbackThemeData"
);
Object.defineProperty(LightweightThemeManager, "fallbackThemeData", {
enumerable: true,
configurable: true,
set: function (value) {
gFallbackHistory.push(structuredClone(value));
console.trace(`Set fallbackThemeData: ${value?.theme?.id}`);
fallbackThemeDataPropertyDescriptor.set.call(this, value);
},
get: fallbackThemeDataPropertyDescriptor.get,
});
});
add_setup(async () => {
await ExtensionTestUtils.startAddonManager();
// In reality, loading a built-in static theme would populate the fallback.
// But we don't have a built-in static theme in this test.
Assert.deepEqual(gFallbackHistory, [], "Fallback history is empty");
});
async function do_test_static_theme_startupData(expectDark) {
const EXPECTED_COLOR = expectDark ? "rgb(65, 43, 21)" : "rgb(12, 34, 56)";
let extension = loadStaticThemeExtension({
browser_specific_settings: { gecko: { id: "@my-theme" } },
theme: { colors: { frame: "rgb(12, 34, 56)" } },
dark_theme: { colors: { frame: "rgb(65, 43, 21)" } },
});
await extension.startup();
let startupDataOriginal = extension.extension.startupData;
let startupDataCopy = structuredClone(extension.extension.startupData);
equal(
await getColorFromAppliedTheme(),
EXPECTED_COLOR,
"Theme applied to simulated browser window"
);
equal(
extension.extension.startupData,
startupDataOriginal,
"startupData should be the same value (extension not unloaded)"
);
Assert.deepEqual(
extension.extension.startupData,
startupDataCopy,
"startupData should not have been changed when a browser window was opened"
);
Assert.deepEqual(
gFallbackHistory,
[startupDataCopy.lwtData],
"Fallback set when theme has loaded"
);
gFallbackHistory.length = 0;
let readyPromise = AddonTestUtils.promiseWebExtensionStartup("@my-theme");
info("Simulating browser restart");
await AddonTestUtils.promiseShutdownManager();
Assert.deepEqual(
gFallbackHistory,
[],
"Should not have bothered with resetting the fallback upon app shutdown"
);
gFallbackHistory.length = 0;
await AddonTestUtils.promiseStartupManager();
info("Waiting for theme to have started again");
await readyPromise;
let startupData = extension.extension.startupData;
// It cannot be the same object because it should have been loaded from disk.
notEqual(startupData, startupDataOriginal, "Not trivially equal");
Assert.deepEqual(
startupDataCopy,
startupData,
"startupData should be identical when the theme loads again"
);
ok(startupData.lwtData, "startupData.lwtData should be set");
// Expected to be set 2x: first as soon as the static theme extension has been
// initialized by the AddonManager, secondly when the extension internals have
// loaded and parsed the actual theme data.
Assert.deepEqual(
gFallbackHistory,
[startupData.lwtData, startupData.lwtData],
"Should have set startupData.lwtData as fallback at startup (2x)"
);
gFallbackHistory.length = 0;
equal(
await getColorFromAppliedTheme(),
EXPECTED_COLOR,
"Theme applied to simulated browser window after a restart"
);
await extension.unload();
Assert.deepEqual(
gFallbackHistory,
[null],
"Should have cleared fallback when static theme unloaded (theme uninstall)"
);
gFallbackHistory.length = 0;
}
add_task(
{ pref_set: [["ui.systemUsesDarkTheme", 0]] },
async function test_static_theme_startupData_light_theme() {
await do_test_static_theme_startupData(/* expectDark */ false);
}
);
add_task(
{ pref_set: [["ui.systemUsesDarkTheme", 1]] },
async function test_static_theme_startupData_dark_theme() {
await do_test_static_theme_startupData(/* expectDark */ true);
}
);
add_task(async function test_dynamic_theme_startupData() {
let extension = ExtensionTestUtils.loadExtension({
manifest: {
manifest_version: 3,
browser_specific_settings: { gecko: { id: "@my-dynamic-theme" } },
permissions: ["theme"],
},
useAddonManager: "permanent",
background() {
browser.runtime.onInstalled.addListener(async () => {
await browser.theme.update({ colors: { frame: "rgb(7, 8, 9)" } });
browser.test.sendMessage("onInstalled");
});
},
});
await extension.startup();
await extension.awaitMessage("onInstalled");
equal(
await getColorFromAppliedTheme(),
"rgb(7, 8, 9)",
"Dynamic theme applies to simulated browser window"
);
let startupData = extension.extension.startupData;
// Notably, startupData.lwtData (among other properties) is not set. In a
// previous test (test_static_theme_startupData) we have confirmed that this
// property is present in startupData of static themes.
Assert.deepEqual(
Object.keys(startupData),
["persistentListeners"],
"startupData should not have unexpected properties from ext-theme.js"
);
assertPersistentListeners(extension, "runtime", "onInstalled", {
primed: false,
persisted: true,
});
await extension.unload();
});
// is not automatically dropped on updates (unlike StartupCache).
// Double-check that this does not break anything.
add_task(async function test_obsolete_theme_in_extension_startupData() {
// We're going to load a fake theme to borrow its startupData.
let theme = loadStaticThemeExtension({
browser_specific_settings: { gecko: { id: "@theme-ext" } },
theme: { colors: { frame: "rgb(1, 33, 7)" } },
});
await theme.startup();
let startupDataFromStaticTheme = structuredClone(theme.extension.startupData);
await theme.unload();
// test_static_theme_startupData already tests the expected behavior for
// static themes, so don't bother checking again and just reset the history.
gFallbackHistory.length = 0;
let extension = ExtensionTestUtils.loadExtension({
manifest: {
browser_specific_settings: { gecko: { id: "@theme-ext" } },
permissions: ["theme"],
},
useAddonManager: "permanent",
});
await extension.startup();
// The extension does not have anything of interest (such as persistent
// listeners), so startupData should be empty.
Assert.deepEqual(extension.extension.startupData, {}, "startupData is empty");
extension.extension.startupData = startupDataFromStaticTheme;
extension.extension.saveStartupData();
let readyPromise = AddonTestUtils.promiseWebExtensionStartup("@theme-ext");
await AddonTestUtils.promiseShutdownManager();
await AddonTestUtils.promiseStartupManager();
await readyPromise;
await extension.unload();
Assert.deepEqual(
gFallbackHistory,
[],
"startupData.lwtData should be ignored for extensions that are not themes"
);
});