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:
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.
//