Bug 1347204 - Implement the colors.ntp_background and colors.ntp_text properties. r=mconley

MozReview-Commit-ID: En8HajryiJS

--HG--
extra : rebase_source : b0b321418a68c134772616cc47ab883f8ffb9078
This commit is contained in:
Tim Nguyen 2018-04-12 16:48:23 -04:00
Родитель 8b5c9e7847
Коммит 7620721be3
14 изменённых файлов: 568 добавлений и 3 удалений

Просмотреть файл

@ -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 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 elem The element where the CSS variable should be added.
* @param variableName The CSS variable to set.
* @param 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 root The element where the properties should be applied.
* @param 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();
}

Просмотреть файл

@ -24,6 +24,8 @@ 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;
@ -79,6 +81,33 @@ 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,

Просмотреть файл

@ -56,6 +56,7 @@ const whitelist = {
"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",

Просмотреть файл

@ -101,6 +101,7 @@ browser.jar:
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)

Просмотреть файл

@ -760,6 +760,8 @@ BrowserGlue.prototype = {
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

Просмотреть файл

@ -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 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 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 content The global to check against.
* @returns true if the global is allowed to receive updates, false otherwise.
*/
_isContentWhitelisted(content) {
return this.whitelist.has(content.document.documentURI);
}
/**
* Forward the theme data to the page.
* @param data The theme data to forward
* @param 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"];

Просмотреть файл

@ -2,7 +2,7 @@
* 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", {
@ -80,3 +80,8 @@ const ThemeVariableMap = [
lwtProperty: "popup_highlight_text"
}],
];
const ThemeContentPropertyList = [
"ntp_background",
"ntp_text",
];

Просмотреть файл

@ -70,6 +70,9 @@ with Files("ExtensionsUI.jsm"):
with Files("LaterRun.jsm"):
BUG_COMPONENT = ("Firefox", "Tours")
with Files("LightweightThemeChildListener.jsm"):
BUG_COMPONENT = ("WebExtensions", "Themes")
with Files("LightWeightThemeWebInstallListener.jsm"):
BUG_COMPONENT = ("Firefox", "Theme")
@ -155,6 +158,7 @@ EXTRA_JS_MODULES += [
'FormSubmitObserver.jsm',
'FormValidationHandler.jsm',
'LaterRun.jsm',
'LightweightThemeChildListener.jsm',
'LightWeightThemeWebInstallListener.jsm',
'NetErrorContent.jsm',
'OpenInTabsUtils.jsm',

Просмотреть файл

@ -170,6 +170,8 @@ class Theme {
case "popup_border":
case "popup_highlight":
case "popup_highlight_text":
case "ntp_background":
case "ntp_text":
this.lwtStyles[color] = cssColor;
break;
}

Просмотреть файл

@ -196,6 +196,14 @@
"popup_highlight_text": {
"$ref": "ThemeColor",
"optional": true
},
"ntp_background": {
"$ref": "ThemeColor",
"optional": true
},
"ntp_text": {
"$ref": "ThemeColor",
"optional": true
}
},
"additionalProperties": { "$ref": "UnrecognizedProperty" }

Просмотреть файл

@ -12,6 +12,8 @@ skip-if = verify
[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]

Просмотреть файл

@ -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 theme that is applied
* @param 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);
});
}
});

Просмотреть файл

@ -0,0 +1,188 @@
"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 browser to test against
* @param theme that is applied
* @param isBrightText whether the brighttext attribute should be set
*/
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 browser to test against
*/
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();
});

Просмотреть файл

@ -99,8 +99,10 @@ const toolkitVariableMap = [
// 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");
@ -116,6 +118,7 @@ function LightweightThemeConsumer(aDocument) {
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);
@ -143,6 +146,13 @@ LightweightThemeConsumer.prototype = {
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":
@ -204,9 +214,29 @@ LightweightThemeConsumer.prototype = {
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];