tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 3041b115e90fe11124e645788b3b95cef64bf432
parent e33d186226374e6d24a85182171ae14a683afe40
Author: mailelucks <maile.lucks@gmail.com>
Date:   Mon, 15 Dec 2025 19:05:31 +0000

Bug 2003921 - Create css infrastructure for AI Window - r=Gijs,desktop-theme-reviewers,ai-frontend-reviewers,extension-reviewers,emilio,robwu

Differential Revision: https://phabricator.services.mozilla.com/D274999

Diffstat:
Mbrowser/components/aiwindow/ui/moz.build | 3+--
Abrowser/components/aiwindow/ui/test/xpcshell/theme_aiwindow.js | 48++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/components/aiwindow/ui/test/xpcshell/xpcshell.toml | 2++
Mbrowser/themes/ThemeVariableMap.sys.mjs | 1+
Abrowser/themes/addons/aiwindow/aiwindow-theme.css | 31+++++++++++++++++++++++++++++++
Abrowser/themes/addons/aiwindow/manifest.json | 41+++++++++++++++++++++++++++++++++++++++++
Mbrowser/themes/addons/jar.mn | 3+++
Mtoolkit/modules/LightweightThemeConsumer.sys.mjs | 19+++++++++++++++++--
Mtoolkit/mozapps/extensions/LightweightThemeManager.sys.mjs | 36++++++++++++++++++++++++++++++++++++
9 files changed, 180 insertions(+), 4 deletions(-)

diff --git a/browser/components/aiwindow/ui/moz.build b/browser/components/aiwindow/ui/moz.build @@ -6,6 +6,7 @@ with Files("**"): BUG_COMPONENT = ("Core", "Machine Learning: Frontend") BROWSER_CHROME_MANIFESTS += ["test/browser/browser.toml"] +XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell/xpcshell.toml"] MOZ_SRC_FILES += [ "actors/AIChatContentChild.sys.mjs", @@ -21,5 +22,3 @@ MOZ_SRC_FILES += [ ] JAR_MANIFESTS += ["jar.mn"] - -XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell/xpcshell.toml"] diff --git a/browser/components/aiwindow/ui/test/xpcshell/theme_aiwindow.js b/browser/components/aiwindow/ui/test/xpcshell/theme_aiwindow.js @@ -0,0 +1,48 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + AddonManager: "resource://gre/modules/AddonManager.sys.mjs", +}); + +var { AddonTestUtils } = ChromeUtils.importESModule( + "resource://testing-common/AddonTestUtils.sys.mjs" +); + +AddonTestUtils.init(this); +AddonTestUtils.overrideCertDB(); +AddonTestUtils.createAppInfo( + "xpcshell@tests.mozilla.org", + "XPCShell", + "1", + "147" +); + +const AI_WINDOW_THEME_ID = "firefox-aiwindow@mozilla.org"; + +add_setup(async function () { + await AddonTestUtils.promiseStartupManager(); +}); + +// The AI window's theme is loaded from manifest.json by LWT without the +// extension framework, which means that the manifest is not validated against +// extensions/schemas/theme.json. +// To make sure that the aiwindow's manifest.json is valid, we load it as an +// extension here. Any errors (and even warnings) in the manifest.json file +// will cause this test to fail. Warnings are turned into errors because +// extensions.webextensions.warnings-as-errors defaults to true in unit tests. +add_task(async function test_ai_theme_manifest_is_valid() { + info("Validating AI window theme manifest through AddonManager"); + + const themeURI = "resource://builtin-themes/aiwindow/"; + const addon = await AddonManager.installBuiltinAddon(themeURI); + + Assert.ok(addon, "Theme manifest should be valid and loadable"); + Assert.equal(addon.id, AI_WINDOW_THEME_ID, "Theme should have correct ID"); + Assert.equal(addon.type, "theme", "Should be recognized as a theme"); + Assert.equal(addon.name, "Firefox AI Window", "Should have correct name"); + + await addon.uninstall(); +}); diff --git a/browser/components/aiwindow/ui/test/xpcshell/xpcshell.toml b/browser/components/aiwindow/ui/test/xpcshell/xpcshell.toml @@ -12,3 +12,5 @@ run-if = [ ["test_ChatStore.js"] ["test_chat-utils.js"] + +["theme_aiwindow.js"] diff --git a/browser/themes/ThemeVariableMap.sys.mjs b/browser/themes/ThemeVariableMap.sys.mjs @@ -165,4 +165,5 @@ export const ThemeContentPropertyList = [ "sidebar_highlight_text", "sidebar_text", "zap_gradient", + "ai_gradient", ]; diff --git a/browser/themes/addons/aiwindow/aiwindow-theme.css b/browser/themes/addons/aiwindow/aiwindow-theme.css @@ -0,0 +1,31 @@ +/* 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/. */ + +/* Opaque, so that the opaque region is completely covered. Note this is on the + * <body> because some platforms (linux) use appearance on the root element to + * render window decorations like shadows. + * TODO(emilio, bug 2005818): This breaks rounded corners on Linux, unless we also + * enable rounded bottom corners, which is blocked on bug 1979083. */ + +body { + --tabpanel-background-color: transparent; + background-color: white; + background-image: var(--ai-gradient); +} + +/* LightWeightThemeConsumer forces these backgrounds to be opaque, but we want + * to let the <body> background show through. + * FIXME(bug 1952602): Find a solution that doesn't require custom CSS. */ +#navigator-toolbox, +#browser { + background-color: transparent !important; +} + +#tabbrowser-tabs tab[selected] .tab-background { + background-image: linear-gradient(to bottom, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.3)); +} + +#urlbar:not([open]) > .urlbar-background { + border-radius: var(--border-radius-circle); +} diff --git a/browser/themes/addons/aiwindow/manifest.json b/browser/themes/addons/aiwindow/manifest.json @@ -0,0 +1,41 @@ +{ + "manifest_version": 2, + + "browser_specific_settings": { + "gecko": { + "id": "firefox-aiwindow@mozilla.org" + } + }, + + "name": "Firefox AI Window", + "description": "Base theme for AI Window", + "author": "Mozilla", + "version": "1.0.0", + + "theme": { + "properties": { + "color_scheme": "light", + "ai_gradient": "radial-gradient(circle at 50% 90%, rgba(253, 244, 243, 1) 0%, rgba(253, 244, 243, 1) 30%, rgba(216, 202, 247, 1) 100%)" + }, + "colors": { + "frame": "hsla(240, 20%, 98%, 1)", + "toolbar": "transparent", + "tab_background_text": "hsla(257, 55%, 40%, 1)", + "toolbar_text": "hsla(257, 55%, 40%, 1)", + "icon_color": "hsla(257, 55%, 40%, 1)", + "toolbar_field_text": "hsla(257, 55%, 40%, 1)", + "toolbar_field_text_focus": "hsla(257, 55%, 40%, 1)", + "toolbar_bottom_separator": "transparent", + "toolbar_top_separator": "transparent", + "tab_line": "transparent", + "tab_selected": "rgba(255, 255, 255, 0.3)" + } + }, + + "theme_experiment": { + "stylesheet": "aiwindow-theme.css", + "properties": { + "ai_gradient": "--ai-gradient" + } + } +} diff --git a/browser/themes/addons/jar.mn b/browser/themes/addons/jar.mn @@ -15,3 +15,6 @@ browser.jar: content/builtin-themes/light/preview.svg (light/preview.svg) content/builtin-themes/light/icon.svg (light/icon.svg) content/builtin-themes/light/manifest.json (light/manifest.json) + + content/builtin-themes/aiwindow/manifest.json (aiwindow/manifest.json) + content/builtin-themes/aiwindow/aiwindow-theme.css (aiwindow/aiwindow-theme.css) diff --git a/toolkit/modules/LightweightThemeConsumer.sys.mjs b/toolkit/modules/LightweightThemeConsumer.sys.mjs @@ -223,6 +223,7 @@ export function LightweightThemeConsumer(aDocument) { this._doc = aDocument; this._win = aDocument.defaultView; this._winId = this._win.docShell.outerWindowID; + this._isAIWindow = this._doc.documentElement.hasAttribute("ai-window"); XPCOMUtils.defineLazyPreferenceGetter( this, @@ -240,7 +241,19 @@ export function LightweightThemeConsumer(aDocument) { this.forcedColorsMediaQuery = this._win.matchMedia("(forced-colors)"); this.forcedColorsMediaQuery.addListener(this); - this._update(lazy.LightweightThemeManager.themeData); + const manager = lazy.LightweightThemeManager; + const theme = + this._isAIWindow && manager.aiThemeData + ? manager.aiThemeData + : manager.themeData; + + this._update(theme); + + if (this._isAIWindow && !manager.aiThemeData) { + manager.promiseAIThemeData().then(() => { + this._update(manager.aiThemeData); + }); + } this._win.addEventListener("unload", this, { once: true }); } @@ -258,7 +271,9 @@ LightweightThemeConsumer.prototype = { return; } - this._update(data); + if (!this._isAIWindow) { + this._update(data); + } }, handleEvent(aEvent) { diff --git a/toolkit/mozapps/extensions/LightweightThemeManager.sys.mjs b/toolkit/mozapps/extensions/LightweightThemeManager.sys.mjs @@ -215,6 +215,42 @@ function loadDetails(details, experiment, baseURI, id, version, logger) { } export var LightweightThemeManager = { + aiThemeData: null, + _aiThemeDataPromise: null, + + async promiseAIThemeData() { + if (this.aiThemeData) { + return this.aiThemeData; + } + + if (this._aiThemeDataPromise) { + return this._aiThemeDataPromise; + } + + this._aiThemeDataPromise = this._fetchThemeDataFromBuiltinManifest( + "resource://builtin-themes/aiwindow/" + ).then(data => { + this.aiThemeData = data; + this._aiThemeDataPromise = null; + return data; + }); + + return this._aiThemeDataPromise; + }, + async _fetchThemeDataFromBuiltinManifest(baseURI) { + let baseURIObj = Services.io.newURI(baseURI); + let res = await fetch(baseURIObj.resolve("./manifest.json")); + let manifest = await res.json(); + return this.themeDataFrom( + manifest.theme, + manifest.dark_theme, + manifest.theme_experiment, + baseURIObj, + manifest.browser_specific_settings.gecko.id, + manifest.version, + /* logger = */ null + ); + }, // Reads theme data from either an extension manifest or a dynamic theme, // and converts it to an internal format used by our theming code. //