new file mode 100644
--- /dev/null
+++ b/browser/base/content/contentTheme.js
@@ -0,0 +1,108 @@
+/* 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/. */
+
+"use strict";
+
+{
+
+function _isTextColorDark(r, g, b) {
+ return (0.2125 * r + 0.7154 * g + 0.0721 * b) <= 110;
+}
+
+const inContentVariableMap = [
+ ["--newtab-background-color", {
+ lwtProperty: "ntp_background"
+ }],
+ ["--newtab-text-primary-color", {
+ lwtProperty: "ntp_text",
+ processColor(rgbaChannels, element) {
+ if (!rgbaChannels) {
+ element.removeAttribute("lwt-newtab");
+ element.removeAttribute("lwt-newtab-brighttext");
+ return null;
+ }
+
+ element.setAttribute("lwt-newtab", "true");
+ const {r, g, b, a} = rgbaChannels;
+ if (!_isTextColorDark(r, g, b)) {
+ element.setAttribute("lwt-newtab-brighttext", "true");
+ }
+
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
+ },
+ }],
+];
+
+/**
+ * ContentThemeController handles theme updates sent by the frame script.
+ * To be able to use ContentThemeController, you must add your page to the whitelist
+ * in LightweightThemeChildListener.jsm
+ */
+const ContentThemeController = {
+ /**
+ * Tell the frame script that the page supports theming, and watch for updates
+ * from the frame script.
+ */
+ init() {
+ addEventListener("LightweightTheme:Set", this);
+
+ const event = new CustomEvent("LightweightTheme:Support", {bubbles: true});
+ document.dispatchEvent(event);
+ },
+
+ /**
+ * Handle theme updates from the frame script.
+ * @param {Object} event object containing the theme update.
+ */
+ handleEvent({ detail }) {
+ if (detail.type == "LightweightTheme:Update") {
+ let {data} = detail;
+ if (!data) {
+ data = {};
+ }
+ this._setProperties(document.body, data);
+ }
+ },
+
+ /**
+ * Set a CSS variable to a given value
+ * @param {Element} elem The element where the CSS variable should be added.
+ * @param {string} variableName The CSS variable to set.
+ * @param {string} value The new value of the CSS variable.
+ */
+ _setProperty(elem, variableName, value) {
+ if (value) {
+ elem.style.setProperty(variableName, value);
+ } else {
+ elem.style.removeProperty(variableName);
+ }
+ },
+
+ /**
+ * Apply theme data to an element
+ * @param {Element} root The element where the properties should be applied.
+ * @param {Object} themeData The theme data.
+ */
+ _setProperties(elem, themeData) {
+ for (let [cssVarName, definition] of inContentVariableMap) {
+ const {
+ lwtProperty,
+ processColor,
+ } = definition;
+ let value = themeData[lwtProperty];
+
+ if (processColor) {
+ value = processColor(value, document.body);
+ } else if (value) {
+ const {r, g, b, a} = value;
+ value = `rgba(${r}, ${g}, ${b}, ${a})`;
+ }
+
+ this._setProperty(elem, cssVarName, value);
+ }
+ },
+};
+ContentThemeController.init();
+
+}
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -19,16 +19,18 @@ ChromeUtils.defineModuleGetter(this, "Ut
ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
ChromeUtils.defineModuleGetter(this, "AboutReader",
"resource://gre/modules/AboutReader.jsm");
ChromeUtils.defineModuleGetter(this, "ReaderMode",
"resource://gre/modules/ReaderMode.jsm");
ChromeUtils.defineModuleGetter(this, "PageStyleHandler",
"resource:///modules/PageStyleHandler.jsm");
+ChromeUtils.defineModuleGetter(this, "LightweightThemeChildListener",
+ "resource:///modules/LightweightThemeChildListener.jsm");
// TabChildGlobal
var global = this;
addEventListener("MozDOMPointerLock:Entered", function(aEvent) {
sendAsyncMessage("PointerLock:Entered", {
originNoSuffix: aEvent.target.nodePrincipal.originNoSuffix
@@ -74,16 +76,43 @@ addMessageListener("Browser:Reload", fun
} catch (e) {
}
});
addMessageListener("MixedContent:ReenableProtection", function() {
docShell.mixedContentChannel = null;
});
+var LightweightThemeChildListenerStub = {
+ _childListener: null,
+ get childListener() {
+ if (!this._childListener) {
+ this._childListener = new LightweightThemeChildListener();
+ }
+ return this._childListener;
+ },
+
+ init() {
+ addEventListener("LightweightTheme:Support", this, false, true);
+ addMessageListener("LightweightTheme:Update", this);
+ sendAsyncMessage("LightweightTheme:Request");
+ },
+
+ handleEvent(event) {
+ return this.childListener.handleEvent(event);
+ },
+
+ receiveMessage(msg) {
+ return this.childListener.receiveMessage(msg);
+ },
+};
+
+LightweightThemeChildListenerStub.init();
+
+
var AboutReaderListener = {
_articlePromise: null,
_isLeavingReaderableReaderMode: false,
init() {
addEventListener("AboutReaderContentLoaded", this, false, true);
--- a/browser/base/content/test/performance/browser_startup_content.js
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -51,16 +51,17 @@ const whitelist = {
// Forms and passwords
"resource://formautofill/FormAutofillContent.jsm",
"resource://formautofill/FormAutofillUtils.jsm",
// Browser front-end
"resource:///modules/ContentLinkHandler.jsm",
"resource:///modules/ContentMetaHandler.jsm",
"resource:///modules/PageStyleHandler.jsm",
+ "resource:///modules/LightweightThemeChildListener.jsm",
"resource://gre/modules/BrowserUtils.jsm",
"resource://gre/modules/E10SUtils.jsm",
"resource://gre/modules/PrivateBrowsingUtils.jsm",
"resource://gre/modules/ReaderMode.jsm",
"resource://gre/modules/RemotePageManager.jsm",
// Pocket
"chrome://pocket/content/AboutPocket.jsm",
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -96,16 +96,17 @@ browser.jar:
content/browser/tabbrowser.css (content/tabbrowser.css)
content/browser/tabbrowser.js (content/tabbrowser.js)
content/browser/tabbrowser.xml (content/tabbrowser.xml)
* content/browser/urlbarBindings.xml (content/urlbarBindings.xml)
content/browser/utilityOverlay.js (content/utilityOverlay.js)
content/browser/webext-panels.js (content/webext-panels.js)
* content/browser/webext-panels.xul (content/webext-panels.xul)
content/browser/nsContextMenu.js (content/nsContextMenu.js)
+ content/browser/contentTheme.js (content/contentTheme.js)
#ifdef XP_MACOSX
# XXX: We should exclude this one as well (bug 71895)
* content/browser/hiddenWindow.xul (content/hiddenWindow.xul)
#endif
#ifndef XP_MACOSX
* content/browser/webrtcIndicator.xul (content/webrtcIndicator.xul)
content/browser/webrtcIndicator.js (content/webrtcIndicator.js)
#endif
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -755,16 +755,18 @@ BrowserGlue.prototype = {
iconURL: "resource:///chrome/browser/content/browser/defaultthemes/dark.icon.svg",
textcolor: "white",
accentcolor: "black",
popup: "#4a4a4f",
popup_text: "rgb(249, 249, 250)",
popup_border: "#27272b",
toolbar_field_text: "rgb(249, 249, 250)",
toolbar_field_border: "rgba(249, 249, 250, 0.2)",
+ ntp_background: "#2A2A2E",
+ ntp_text: "rgb(249, 249, 250)",
author: vendorShortName,
}, {
useInDarkMode: true
});
Normandy.init();
// Initialize the default l10n resource sources for L10nRegistry.
new file mode 100644
--- /dev/null
+++ b/browser/modules/LightweightThemeChildListener.jsm
@@ -0,0 +1,82 @@
+/* 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/. */
+
+"use strict";
+
+/**
+ * LightweightThemeChildListener forwards theme updates from LightweightThemeConsumer to
+ * the whitelisted in-content pages
+ */
+class LightweightThemeChildListener {
+ constructor() {
+ /**
+ * The pages that will receive theme updates
+ */
+ this.whitelist = new Set([
+ "about:home",
+ "about:newtab",
+ "about:welcome",
+ ]);
+
+ /**
+ * The last theme data received from LightweightThemeConsumer
+ */
+ this._lastData = null;
+ }
+
+ /**
+ * Handles theme updates from the parent process
+ * @param {Object} message from the parent process.
+ */
+ receiveMessage({ name, data, target }) {
+ if (name == "LightweightTheme:Update") {
+ this._lastData = data;
+ this._update(data, target.content);
+ }
+ }
+
+ /**
+ * Handles events from the content scope.
+ * @param {Object} event The received event.
+ */
+ handleEvent(event) {
+ const content = event.originalTarget.defaultView;
+ if (content != content.top) {
+ return;
+ }
+
+ if (event.type == "LightweightTheme:Support") {
+ this._update(this._lastData, content);
+ }
+ }
+
+ /**
+ * Checks if a given global is allowed to receive theme updates
+ * @param {Object} content The global to check against.
+ * @returns {boolean} Whether the global is allowed to receive updates.
+ */
+ _isContentWhitelisted(content) {
+ return this.whitelist.has(content.document.documentURI);
+ }
+
+ /**
+ * Forward the theme data to the page.
+ * @param {Object} data The theme data to forward
+ * @param {Object} content The receiving global
+ */
+ _update(data, content) {
+ if (this._isContentWhitelisted(content)) {
+ const event = Cu.cloneInto({
+ detail: {
+ type: "LightweightTheme:Update",
+ data,
+ },
+ }, content);
+ content.dispatchEvent(new content.CustomEvent("LightweightTheme:Set",
+ event));
+ }
+ }
+}
+
+var EXPORTED_SYMBOLS = ["LightweightThemeChildListener"];
--- a/browser/modules/ThemeVariableMap.jsm
+++ b/browser/modules/ThemeVariableMap.jsm
@@ -1,13 +1,13 @@
/* 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/. */
-var EXPORTED_SYMBOLS = ["ThemeVariableMap"];
+var EXPORTED_SYMBOLS = ["ThemeVariableMap", "ThemeContentPropertyList"];
const ThemeVariableMap = [
["--lwt-accent-color-inactive", {
lwtProperty: "accentcolorInactive"
}],
["--lwt-background-alignment", {
isColor: false,
lwtProperty: "backgroundsAlignment"
@@ -75,8 +75,13 @@ const ThemeVariableMap = [
}],
["--autocomplete-popup-highlight-background", {
lwtProperty: "popup_highlight"
}],
["--autocomplete-popup-highlight-color", {
lwtProperty: "popup_highlight_text"
}],
];
+
+const ThemeContentPropertyList = [
+ "ntp_background",
+ "ntp_text",
+];
--- a/browser/modules/moz.build
+++ b/browser/modules/moz.build
@@ -65,16 +65,19 @@ with Files("ContentWebRTC.jsm"):
BUG_COMPONENT = ("Firefox", "Device Permissions")
with Files("ExtensionsUI.jsm"):
BUG_COMPONENT = ("WebExtensions", "General")
with Files("LaterRun.jsm"):
BUG_COMPONENT = ("Firefox", "Tours")
+with Files("LightweightThemeChildListener.jsm"):
+ BUG_COMPONENT = ("WebExtensions", "Themes")
+
with Files("LightWeightThemeWebInstallListener.jsm"):
BUG_COMPONENT = ("Firefox", "Theme")
with Files("OpenInTabsUtils.jsm"):
BUG_COMPONENT = ("Firefox", "Tabbed Browser")
with Files("PageInfoListener.jsm"):
BUG_COMPONENT = ("Firefox", "Page Info Window")
@@ -150,16 +153,17 @@ EXTRA_JS_MODULES += [
'ContentSearch.jsm',
'ContentWebRTC.jsm',
'ContextMenu.jsm',
'ExtensionsUI.jsm',
'Feeds.jsm',
'FormSubmitObserver.jsm',
'FormValidationHandler.jsm',
'LaterRun.jsm',
+ 'LightweightThemeChildListener.jsm',
'LightWeightThemeWebInstallListener.jsm',
'NetErrorContent.jsm',
'OpenInTabsUtils.jsm',
'PageActions.jsm',
'PageInfoListener.jsm',
'PageStyleHandler.jsm',
'PermissionUI.jsm',
'PingCentre.jsm',
--- a/toolkit/components/extensions/parent/ext-theme.js
+++ b/toolkit/components/extensions/parent/ext-theme.js
@@ -165,16 +165,18 @@ class Theme {
case "toolbar_vertical_separator":
case "button_background_hover":
case "button_background_active":
case "popup":
case "popup_text":
case "popup_border":
case "popup_highlight":
case "popup_highlight_text":
+ case "ntp_background":
+ case "ntp_text":
this.lwtStyles[color] = cssColor;
break;
}
}
}
/**
* Helper method for loading images found in the extension's manifest.
--- a/toolkit/components/extensions/schemas/theme.json
+++ b/toolkit/components/extensions/schemas/theme.json
@@ -191,16 +191,24 @@
},
"popup_highlight": {
"$ref": "ThemeColor",
"optional": true
},
"popup_highlight_text": {
"$ref": "ThemeColor",
"optional": true
+ },
+ "ntp_background": {
+ "$ref": "ThemeColor",
+ "optional": true
+ },
+ "ntp_text": {
+ "$ref": "ThemeColor",
+ "optional": true
}
},
"additionalProperties": { "$ref": "UnrecognizedProperty" }
},
"icons": {
"type": "object",
"optional": true,
"properties": {
--- a/toolkit/components/extensions/test/browser/browser.ini
+++ b/toolkit/components/extensions/test/browser/browser.ini
@@ -7,16 +7,18 @@ skip-if = verify
[browser_ext_themes_alpha_accentcolor.js]
[browser_ext_themes_chromeparity.js]
[browser_ext_themes_dynamic_getCurrent.js]
[browser_ext_themes_dynamic_onUpdated.js]
[browser_ext_themes_dynamic_updates.js]
[browser_ext_themes_getCurrent_differentExt.js]
[browser_ext_themes_lwtsupport.js]
[browser_ext_themes_multiple_backgrounds.js]
+[browser_ext_themes_ntp_colors.js]
+[browser_ext_themes_ntp_colors_perwindow.js]
[browser_ext_themes_persistence.js]
[browser_ext_themes_separators.js]
[browser_ext_themes_static_onUpdated.js]
[browser_ext_themes_tab_line.js]
[browser_ext_themes_tab_loading.js]
[browser_ext_themes_tab_separators.js]
[browser_ext_themes_tab_text.js]
[browser_ext_themes_toolbar_fields_focus.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors.js
@@ -0,0 +1,103 @@
+"use strict";
+
+// This test checks whether the new tab page color properties work.
+
+/**
+ * Test whether the selected browser has the new tab page theme applied
+ * @param {Object} theme that is applied
+ * @param {boolean} isBrightText whether the brighttext attribute should be set
+ */
+async function test_ntp_theme(theme, isBrightText) {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ theme,
+ },
+ });
+
+ let browser = gBrowser.selectedBrowser;
+
+ let {
+ originalBackground,
+ originalColor,
+ } = await ContentTask.spawn(browser, {}, function() {
+ let doc = content.document;
+ ok(!doc.body.hasAttribute("lwt-newtab"),
+ "New tab page should not have lwt-newtab attribute");
+ ok(!doc.body.hasAttribute("lwt-newtab-brighttext"),
+ `New tab page should not have lwt-newtab-brighttext attribute`);
+
+ return {
+ originalBackground: content.getComputedStyle(doc.body).backgroundColor,
+ originalColor: content.getComputedStyle(doc.querySelector(".outer-wrapper")).color,
+ };
+ });
+
+ await extension.startup();
+
+ await ContentTask.spawn(browser, {
+ isBrightText,
+ background: hexToCSS(theme.colors.ntp_background),
+ color: hexToCSS(theme.colors.ntp_text),
+ }, function({isBrightText, background, color}) {
+ let doc = content.document;
+ ok(doc.body.hasAttribute("lwt-newtab"),
+ "New tab page should have lwt-newtab attribute");
+ is(doc.body.hasAttribute("lwt-newtab-brighttext"), isBrightText,
+ `New tab page should${!isBrightText ? " not" : ""} have lwt-newtab-brighttext attribute`);
+
+ is(content.getComputedStyle(doc.body).backgroundColor, background,
+ "New tab page background should be set.");
+ is(content.getComputedStyle(doc.querySelector(".outer-wrapper")).color, color,
+ "New tab page text color should be set.");
+ });
+
+ await extension.unload();
+
+ await ContentTask.spawn(browser, {
+ originalBackground,
+ originalColor,
+ }, function({originalBackground, originalColor}) {
+ let doc = content.document;
+ ok(!doc.body.hasAttribute("lwt-newtab"),
+ "New tab page should not have lwt-newtab attribute");
+ ok(!doc.body.hasAttribute("lwt-newtab-brighttext"),
+ `New tab page should not have lwt-newtab-brighttext attribute`);
+
+ is(content.getComputedStyle(doc.body).backgroundColor, originalBackground,
+ "New tab page background should be reset.");
+ is(content.getComputedStyle(doc.querySelector(".outer-wrapper")).color, originalColor,
+ "New tab page text color should be reset.");
+ });
+}
+
+add_task(async function test_support_ntp_colors() {
+ // BrowserTestUtils.withNewTab waits for about:newtab to load
+ // so we disable preloading before running the test.
+ SpecialPowers.setBoolPref("browser.newtab.preload", false);
+ registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref("browser.newtab.preload");
+ });
+ gBrowser.removePreloadedBrowser();
+ for (let url of ["about:newtab", "about:home", "about:welcome"]) {
+ info("Opening url: " + url);
+ await BrowserTestUtils.withNewTab({gBrowser, url}, async browser => {
+ await test_ntp_theme({
+ colors: {
+ accentcolor: ACCENT_COLOR,
+ textcolor: TEXT_COLOR,
+ ntp_background: "#add8e6",
+ ntp_text: "#00008b",
+ },
+ }, false, url);
+
+ await test_ntp_theme({
+ colors: {
+ accentcolor: ACCENT_COLOR,
+ textcolor: TEXT_COLOR,
+ ntp_background: "#00008b",
+ ntp_text: "#add8e6",
+ },
+ }, true, url);
+ });
+ }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/browser/browser_ext_themes_ntp_colors_perwindow.js
@@ -0,0 +1,190 @@
+"use strict";
+
+// This test checks whether the new tab page color properties work per-window.
+
+/**
+ * Test whether a given browser has the new tab page theme applied
+ * @param {Object} browser to test against
+ * @param {Object} theme that is applied
+ * @param {boolean} isBrightText whether the brighttext attribute should be set
+ * @returns {Promise} The task as a promise
+ */
+function test_ntp_theme(browser, theme, isBrightText) {
+ return ContentTask.spawn(browser, {
+ isBrightText,
+ background: hexToCSS(theme.colors.ntp_background),
+ color: hexToCSS(theme.colors.ntp_text),
+ }, function({isBrightText, background, color}) {
+ let doc = content.document;
+ ok(doc.body.hasAttribute("lwt-newtab"),
+ "New tab page should have lwt-newtab attribute");
+ is(doc.body.hasAttribute("lwt-newtab-brighttext"), isBrightText,
+ `New tab page should${!isBrightText ? " not" : ""} have lwt-newtab-brighttext attribute`);
+
+ is(content.getComputedStyle(doc.body).backgroundColor, background,
+ "New tab page background should be set.");
+ is(content.getComputedStyle(doc.querySelector(".outer-wrapper")).color, color,
+ "New tab page text color should be set.");
+ });
+}
+
+/**
+ * Test whether a given browser has the default theme applied
+ * @param {Object} browser to test against
+ * @returns {Promise} The task as a promise
+ */
+function test_ntp_default_theme(browser) {
+ return ContentTask.spawn(browser, {
+ background: hexToCSS("#F9F9FA"),
+ color: hexToCSS("#0C0C0D"),
+ }, function({background, color}) {
+ let doc = content.document;
+ ok(!doc.body.hasAttribute("lwt-newtab"),
+ "New tab page should not have lwt-newtab attribute");
+ ok(!doc.body.hasAttribute("lwt-newtab-brighttext"),
+ `New tab page should not have lwt-newtab-brighttext attribute`);
+
+ is(content.getComputedStyle(doc.body).backgroundColor, background,
+ "New tab page background should be reset.");
+ is(content.getComputedStyle(doc.querySelector(".outer-wrapper")).color, color,
+ "New tab page text color should be reset.");
+ });
+}
+
+add_task(async function test_per_window_ntp_theme() {
+ let extension = ExtensionTestUtils.loadExtension({
+ manifest: {
+ permissions: ["theme"],
+ },
+ async background() {
+ function promiseWindowChanged(winId) {
+ return new Promise(resolve => {
+ let listener = windowId => {
+ if (windowId === winId) {
+ browser.windows.onFocusChanged.removeListener(listener);
+ resolve();
+ }
+ };
+ browser.windows.onFocusChanged.addListener(listener);
+ });
+ }
+
+ function promiseWindowChecked() {
+ return new Promise(resolve => {
+ let listener = msg => {
+ if (msg == "checked-window") {
+ browser.test.onMessage.removeListener(listener);
+ resolve();
+ }
+ };
+ browser.test.onMessage.addListener(listener);
+ });
+ }
+
+ function createWindow() {
+ return new Promise(resolve => {
+ let listener = win => {
+ browser.windows.onCreated.removeListener(listener);
+ resolve(win);
+ };
+ browser.windows.onCreated.addListener(listener);
+ browser.windows.create();
+ });
+ }
+
+ function removeWindow(winId) {
+ return new Promise(resolve => {
+ let listener = removedWinId => {
+ if (removedWinId == winId) {
+ browser.windows.onRemoved.removeListener(listener);
+ resolve();
+ }
+ };
+ browser.windows.onRemoved.addListener(listener);
+ browser.windows.remove(winId);
+ });
+ }
+
+ async function checkWindow(theme, isBrightText, winId) {
+ // We query the window again to have the updated focus information
+ let win = await browser.windows.get(winId);
+ if (!win.focused) {
+ let focusChanged = promiseWindowChanged(win.id);
+ await browser.windows.update(win.id, {focused: true});
+ await focusChanged;
+ }
+
+ let windowChecked = promiseWindowChecked();
+ browser.test.sendMessage("check-window", {theme, isBrightText});
+ await windowChecked;
+ }
+
+ const darkTextTheme = {
+ colors: {
+ accentcolor: "#add8e6",
+ textcolor: "#000",
+ ntp_background: "#add8e6",
+ ntp_text: "#000",
+ },
+ };
+
+ const brightTextTheme = {
+ colors: {
+ accentcolor: "#00008b",
+ textcolor: "#add8e6",
+ ntp_background: "#00008b",
+ ntp_text: "#add8e6",
+ },
+ };
+
+ let {id: winId} = await browser.windows.getCurrent();
+ let {id: secondWinId} = await createWindow();
+
+ browser.test.log("Test that single window update works");
+ await browser.theme.update(winId, darkTextTheme);
+ await checkWindow(darkTextTheme, false, winId);
+ await checkWindow(null, false, secondWinId);
+
+ browser.test.log("Test that applying different themes on both windows");
+ await browser.theme.update(secondWinId, brightTextTheme);
+ await checkWindow(darkTextTheme, false, winId);
+ await checkWindow(brightTextTheme, true, secondWinId);
+
+ browser.test.log("Test resetting the theme on one window");
+ await browser.theme.reset(winId);
+ await checkWindow(null, false, winId);
+ await checkWindow(brightTextTheme, true, secondWinId);
+
+ await removeWindow(secondWinId);
+ await checkWindow(null, false, winId);
+ browser.test.notifyPass("perwindow-ntp-theme");
+ },
+ });
+
+ extension.onMessage("check-window", async ({theme, isBrightText}) => {
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ win.gBrowser.removePreloadedBrowser();
+ for (let url of ["about:newtab", "about:home", "about:welcome"]) {
+ info("Opening url: " + url);
+ await BrowserTestUtils.withNewTab({gBrowser: win.gBrowser, url}, async browser => {
+ if (theme) {
+ await test_ntp_theme(browser, theme, isBrightText);
+ } else {
+ await test_ntp_default_theme(browser);
+ }
+ });
+ }
+ extension.sendMessage("checked-window");
+ });
+
+ // BrowserTestUtils.withNewTab waits for about:newtab to load
+ // so we disable preloading before running the test.
+ SpecialPowers.setBoolPref("browser.newtab.preload", false);
+ registerCleanupFunction(() => {
+ SpecialPowers.clearUserPref("browser.newtab.preload");
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("perwindow-ntp-theme");
+ await extension.unload();
+});
--- a/toolkit/modules/LightweightThemeConsumer.jsm
+++ b/toolkit/modules/LightweightThemeConsumer.jsm
@@ -94,33 +94,36 @@ const toolkitVariableMap = [
}],
["--toolbar-field-focus-border-color", {
lwtProperty: "toolbar_field_border_focus"
}],
];
// Get the theme variables from the app resource directory.
// This allows per-app variables.
-ChromeUtils.import("resource:///modules/ThemeVariableMap.jsm");
-
+ChromeUtils.defineModuleGetter(this, "ThemeContentPropertyList",
+ "resource:///modules/ThemeVariableMap.jsm");
+ChromeUtils.defineModuleGetter(this, "ThemeVariableMap",
+ "resource:///modules/ThemeVariableMap.jsm");
ChromeUtils.defineModuleGetter(this, "LightweightThemeImageOptimizer",
"resource://gre/modules/addons/LightweightThemeImageOptimizer.jsm");
function LightweightThemeConsumer(aDocument) {
this._doc = aDocument;
this._win = aDocument.defaultView;
Services.obs.addObserver(this, "lightweight-theme-styling-update");
var temp = {};
ChromeUtils.import("resource://gre/modules/LightweightThemeManager.jsm", temp);
this._update(temp.LightweightThemeManager.currentThemeForDisplay);
this._win.addEventListener("resolutionchange", this);
this._win.addEventListener("unload", this, { once: true });
+ this._win.messageManager.addMessageListener("LightweightTheme:Request", this);
let darkThemeMediaQuery = this._win.matchMedia("(-moz-system-dark-theme)");
darkThemeMediaQuery.addListener(temp.LightweightThemeManager);
temp.LightweightThemeManager.systemThemeChanged(darkThemeMediaQuery);
}
LightweightThemeConsumer.prototype = {
_lastData: null,
@@ -138,16 +141,23 @@ LightweightThemeConsumer.prototype = {
const parsedData = JSON.parse(aData);
if (parsedData && parsedData.window && parsedData.window !== outerWindowID) {
return;
}
this._update(parsedData);
},
+ receiveMessage({ name, target }) {
+ if (name == "LightweightTheme:Request") {
+ let contentThemeData = _getContentProperties(this._doc, this._active, this._lastData);
+ target.messageManager.sendAsyncMessage("LightweightTheme:Update", contentThemeData);
+ }
+ },
+
handleEvent(aEvent) {
switch (aEvent.type) {
case "resolutionchange":
if (this._active) {
this._update(this._lastData);
}
break;
case "unload":
@@ -199,19 +209,39 @@ LightweightThemeConsumer.prototype = {
root.removeAttribute("lwtheme");
root.removeAttribute("lwthemetextcolor");
}
if (active && aData.footerURL)
root.setAttribute("lwthemefooter", "true");
else
root.removeAttribute("lwthemefooter");
+
+ let contentThemeData = _getContentProperties(this._doc, active, aData);
+
+ let browserMessageManager = this._win.getGroupMessageManager("browsers");
+ browserMessageManager.broadcastAsyncMessage(
+ "LightweightTheme:Update", contentThemeData
+ );
}
};
+function _getContentProperties(doc, active, data) {
+ if (!active) {
+ return {};
+ }
+ let properties = {};
+ for (let property in data) {
+ if (ThemeContentPropertyList.includes(property)) {
+ properties[property] = _parseRGBA(_sanitizeCSSColor(doc, data[property]));
+ }
+ }
+ return properties;
+}
+
function _setImage(aRoot, aActive, aVariableName, aURLs) {
if (aURLs && !Array.isArray(aURLs)) {
aURLs = [aURLs];
}
_setProperty(aRoot, aActive, aVariableName, aURLs && aURLs.map(v => `url("${v.replace(/"/g, '\\"')}")`).join(","));
}
function _setProperty(elem, active, variableName, value) {