Bug 1293099: Fill panel arrow and arrow content with browser body's background color. r=bwinton ui-r=maritz
MozReview-Commit-ID: 3xf31UgUy2A
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -8,16 +8,20 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
"@mozilla.org/content/style-sheet-service;1",
"nsIStyleSheetService");
+XPCOMUtils.defineLazyGetter(this, "colorUtils", () => {
+ return require("devtools/shared/css-color").colorUtils;
+});
+
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
Cu.import("resource://gre/modules/AppConstants.jsm");
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
// Minimum time between two resizes.
const RESIZE_TIMEOUT = 100;
@@ -90,57 +94,67 @@ class BasePopup {
Services.scriptSecurityManager.DISALLOW_SCRIPT);
this.extension = extension;
this.popupURI = popupURI;
this.viewNode = viewNode;
this.browserStyle = browserStyle;
this.window = viewNode.ownerGlobal;
- this.panel = this.viewNode;
- while (this.panel.localName != "panel") {
- this.panel = this.panel.parentNode;
- }
-
this.contentReady = new Promise(resolve => {
this._resolveContentReady = resolve;
});
this.viewNode.addEventListener(this.DESTROY_EVENT, this);
+ let doc = viewNode.ownerDocument;
+ let arrowContent = doc.getAnonymousElementByAttribute(this.panel, "class", "panel-arrowcontent");
+ this.borderColor = doc.defaultView.getComputedStyle(arrowContent).borderTopColor;
+
this.browser = null;
this.browserReady = this.createBrowser(viewNode, popupURI);
}
destroy() {
this.browserReady.then(() => {
this.browser.removeEventListener("DOMWindowCreated", this, true);
this.browser.removeEventListener("load", this, true);
this.browser.removeEventListener("DOMTitleChanged", this, true);
this.browser.removeEventListener("DOMWindowClose", this, true);
this.browser.removeEventListener("MozScrolledAreaChanged", this, true);
this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
this.viewNode.style.maxHeight = "";
this.browser.remove();
+ this.panel.style.setProperty("--panel-arrowcontent-background", "");
+ this.panel.style.setProperty("--panel-arrow-image-vertical", "");
+
this.browser = null;
this.viewNode = null;
});
}
// Returns the name of the event fired on `viewNode` when the popup is being
// destroyed. This must be implemented by every subclass.
get DESTROY_EVENT() {
throw new Error("Not implemented");
}
get fixedWidth() {
return false;
}
+ get panel() {
+ let panel = this.viewNode;
+ while (panel.localName != "panel") {
+ panel = panel.parentNode;
+ }
+ return panel;
+ }
+
handleEvent(event) {
switch (event.type) {
case this.DESTROY_EVENT:
this.destroy();
break;
case "DOMWindowCreated":
if (event.target === this.browser.contentDocument) {
@@ -258,37 +272,38 @@ class BasePopup {
}
}
_resizeBrowser(clearTimeout = true) {
if (!this.browser) {
return;
}
+ let doc = this.browser.contentDocument;
+ if (!doc || !doc.documentElement) {
+ return;
+ }
+
+ let root = doc.documentElement;
+ let body = doc.body;
+ if (!body || doc.compatMode == "BackCompat") {
+ // In quirks mode, the root element is used as the scroll frame, and the
+ // body lies about its scroll geometry, and returns the values for the
+ // root instead.
+ body = root;
+ }
+
+
if (this.fixedWidth) {
// If we're in a fixed-width area (namely a slide-in subview of the main
// menu panel), we need to calculate the view height based on the
// preferred height of the content document's root scrollable element at the
// current width, rather than the complete preferred dimensions of the
// content window.
- let doc = this.browser.contentDocument;
- if (!doc || !doc.documentElement) {
- return;
- }
-
- let root = doc.documentElement;
- let body = doc.body;
- if (!body || doc.compatMode == "BackCompat") {
- // In quirks mode, the root element is used as the scroll frame, and the
- // body lies about its scroll geometry, and returns the values for the
- // root instead.
- body = root;
- }
-
// Compensate for any offsets (margin, padding, ...) between the scroll
// area of the body and the outer height of the document.
let getHeight = elem => elem.getBoundingClientRect(elem).height;
let bodyPadding = getHeight(root) - getHeight(body);
let height = Math.ceil(body.scrollHeight + bodyPadding);
// Figure out how much extra space we have on the side of the panel
@@ -302,16 +317,42 @@ class BasePopup {
// Set a maximum height on the <panelview> element to our preferred
// maximum height, so that the PanelUI resizing code can make an accurate
// calculation. If we don't do this, the flex sizing logic will prevent us
// from ever reporting a preferred size smaller than the height currently
// available to us in the panel.
height = Math.max(height, this.viewHeight);
this.viewNode.style.maxHeight = `${height}px`;
} else {
+ // Copy the background color of the document's body to the panel if it's
+ // fully opaque.
+ let panelBackground = "";
+ let panelArrow = "";
+
+ let background = doc.defaultView.getComputedStyle(body).backgroundColor;
+ if (background != "transparent") {
+ let bgColor = colorUtils.colorToRGBA(background);
+ if (bgColor.a == 1) {
+ panelBackground = background;
+ let borderColor = this.borderColor || background;
+
+ panelArrow = `url("data:image/svg+xml,${encodeURIComponent(`<?xml version="1.0" encoding="UTF-8"?>
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="10">
+ <path d="M 0,10 L 10,0 20,10 z" fill="${borderColor}"/>
+ <path d="M 1,10 L 10,1 19,10 z" fill="${background}"/>
+ </svg>
+ `)}")`;
+ }
+ }
+
+ this.panel.style.setProperty("--panel-arrowcontent-background", panelBackground);
+ this.panel.style.setProperty("--panel-arrow-image-vertical", panelArrow);
+
+
+ // Adjust the size of the browser based on its content's preferred size.
let width, height;
try {
let w = {}, h = {};
this.browser.docShell.contentViewer.getContentSize(w, h);
width = w.value / this.window.devicePixelRatio;
height = h.value / this.window.devicePixelRatio;
--- a/browser/components/extensions/test/browser/browser.ini
+++ b/browser/components/extensions/test/browser/browser.ini
@@ -39,16 +39,17 @@ support-files =
[browser_ext_incognito_popup.js]
[browser_ext_lastError.js]
[browser_ext_optionsPage_privileges.js]
[browser_ext_pageAction_context.js]
[browser_ext_pageAction_popup.js]
[browser_ext_pageAction_popup_resize.js]
[browser_ext_pageAction_simple.js]
[browser_ext_popup_api_injection.js]
+[browser_ext_popup_background.js]
[browser_ext_popup_corners.js]
[browser_ext_runtime_openOptionsPage.js]
[browser_ext_runtime_openOptionsPage_uninstall.js]
[browser_ext_runtime_setUninstallURL.js]
[browser_ext_simple.js]
[browser_ext_tab_runtimeConnect.js]
[browser_ext_tabs_audio.js]
[browser_ext_tabs_captureVisibleTab.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_popup_background.js
@@ -0,0 +1,147 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+function* awaitPanel(extension, win = window) {
+ let {target} = yield BrowserTestUtils.waitForEvent(win.document, "load", true, (event) => {
+ return event.target.location && event.target.location.href.endsWith("popup.html");
+ });
+
+ return target.defaultView
+ .QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDocShell)
+ .chromeEventHandler;
+}
+
+function* awaitResize(browser) {
+ // Debouncing code makes this a bit racy.
+ // Try to skip the first, early resize, and catch the resize event we're
+ // looking for, but don't wait longer than a few seconds.
+
+ return Promise.race([
+ BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")
+ .then(() => BrowserTestUtils.waitForEvent(browser, "WebExtPopupResized")),
+ new Promise(resolve => setTimeout(resolve, 5000)),
+ ]);
+}
+
+add_task(function* testPopupBackground() {
+ let extension = ExtensionTestUtils.loadExtension({
+ background() {
+ browser.tabs.query({active: true, currentWindow: true}, tabs => {
+ browser.pageAction.show(tabs[0].id);
+ });
+ },
+
+ manifest: {
+ "browser_action": {
+ "default_popup": "popup.html",
+ "browser_style": false,
+ },
+
+ "page_action": {
+ "default_popup": "popup.html",
+ "browser_style": false,
+ },
+ },
+
+ files: {
+ "popup.html": `<!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body style="width: 100px; height: 100px; background-color: green;">
+ </body>
+ </html>`,
+ },
+ });
+
+ yield extension.startup();
+
+ function* testPanel(browser, standAlone) {
+ let panel = getPanelForNode(browser);
+ let arrowContent = document.getAnonymousElementByAttribute(panel, "class", "panel-arrowcontent");
+ let arrow = document.getAnonymousElementByAttribute(panel, "anonid", "arrow");
+
+ let borderColor = getComputedStyle(arrowContent).borderTopColor;
+
+ let checkArrow = (background = null) => {
+ let image = getComputedStyle(arrow).listStyleImage;
+
+ if (background == null || !standAlone) {
+ ok(image.startsWith('url("chrome://'), `We should have the built-in background image (got: ${image})`);
+ return;
+ }
+
+ if (AppConstants.platform == "mac") {
+ // Panels have a drop shadow rather than a border on OS-X, so we extend
+ // the background color through the border area instead.
+ borderColor = background;
+ }
+
+ image = decodeURIComponent(image);
+ let borderIndex = image.indexOf(`fill="${borderColor}"`);
+ let backgroundIndex = image.lastIndexOf(`fill="${background}"`);
+
+ ok(borderIndex >= 0, `Have border fill (index=${borderIndex})`);
+ ok(backgroundIndex >= 0, `Have background fill (index=${backgroundIndex})`);
+ is(getComputedStyle(arrowContent).backgroundColor, background, "Arrow content should have correct background");
+ isnot(borderIndex, backgroundIndex, "Border and background fills are separate elements");
+ };
+
+ let win = browser.contentWindow;
+ let body = win.document.body;
+
+ yield new Promise(resolve => setTimeout(resolve, 100));
+
+ info("Test that initial background color is applied");
+
+ checkArrow(win.getComputedStyle(body).backgroundColor);
+
+ info("Test that dynamically-changed background color is applied");
+
+ body.style.backgroundColor = "black";
+ yield awaitResize(browser);
+
+ checkArrow(win.getComputedStyle(body).backgroundColor);
+
+ info("Test that non-opaque background color results in default styling");
+
+ body.style.backgroundColor = "rgba(1, 2, 3, .9)";
+ yield awaitResize(browser);
+
+ checkArrow(null);
+ }
+
+ {
+ info("Test stand-alone browserAction popup");
+
+ clickBrowserAction(extension);
+ let browser = yield awaitPanel(extension);
+ yield testPanel(browser, true);
+ yield closeBrowserAction(extension);
+ }
+
+ {
+ info("Test menu panel browserAction popup");
+
+ let widget = getBrowserActionWidget(extension);
+ CustomizableUI.addWidgetToArea(widget.id, CustomizableUI.AREA_PANEL);
+
+ clickBrowserAction(extension);
+ let browser = yield awaitPanel(extension);
+ yield testPanel(browser, false);
+ yield closeBrowserAction(extension);
+ }
+
+ {
+ info("Test pageAction popup");
+
+ clickPageAction(extension);
+ let browser = yield awaitPanel(extension);
+ yield testPanel(browser, true);
+ yield closePageAction(extension);
+ }
+
+ yield extension.unload();
+});
--- a/toolkit/themes/linux/global/global.css
+++ b/toolkit/themes/linux/global/global.css
@@ -28,16 +28,24 @@ menulist > menupopup {
progressmeter[mode="undetermined"] {
-moz-binding: url("chrome://global/content/bindings/progressmeter.xml#progressmeter-undetermined");
}
toolbar[type="menubar"]:not([autohide="true"]):not(:-moz-lwtheme):-moz-system-metric(menubar-drag) {
-moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-drag");
}
+/* ::::: Variables ::::: */
+:root {
+ --panel-arrowcontent-padding: 10px;
+ --panel-arrowcontent-background: -moz-field;
+ --panel-arrowcontent-color: -moz-fieldText;
+ --panel-arrowcontent-border: 1px solid ThreeDShadow;
+}
+
/* ::::: root elements ::::: */
window,
page,
dialog,
wizard,
prefwindow {
-moz-appearance: window;
--- a/toolkit/themes/linux/global/popup.css
+++ b/toolkit/themes/linux/global/popup.css
@@ -1,22 +1,14 @@
/* 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/. */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
-/* ::::: Variables ::::: */
-.panel-arrowcontent {
- --panel-arrowcontent-padding: 10px;
- --panel-arrowcontent-background: -moz-field;
- --panel-arrowcontent-color: -moz-fieldText;
- --panel-arrowcontent-border: 1px solid ThreeDShadow;
-}
-
/* ::::: menupopup ::::: */
menupopup,
panel {
-moz-appearance: menupopup;
min-width: 1px;
color: MenuText;
}
@@ -43,17 +35,18 @@ panel[type="arrow"][side="right"] {
padding: var(--panel-arrowcontent-padding);
color: var(--panel-arrowcontent-color);
background: var(--panel-arrowcontent-background);
border: var(--panel-arrowcontent-border);
}
.panel-arrow[side="top"],
.panel-arrow[side="bottom"] {
- list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical-themed.svg");
+ list-style-image: var(--panel-arrow-image-vertical,
+ url("chrome://global/skin/arrow/panelarrow-vertical-themed.svg"));
position: relative;
margin-left: 6px;
margin-right: 6px;
}
.panel-arrow[side="top"] {
margin-bottom: -1px;
}
--- a/toolkit/themes/osx/global/global.css
+++ b/toolkit/themes/osx/global/global.css
@@ -9,16 +9,25 @@
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
/* ::::: XBL bindings ::::: */
menulist > menupopup {
-moz-binding: url("chrome://global/content/bindings/popup.xml#popup-scrollbars");
}
+/* ::::: Variables ::::: */
+:root {
+ --panel-arrowcontent-padding: 16px;
+ --panel-arrowcontent-background: linear-gradient(hsla(0,0%,99%,1), hsla(0,0%,99%,.975) 10%, hsla(0,0%,98%,.975));
+ --panel-arrowcontent-color: hsl(0,0%,10%);
+ --panel-arrowcontent-border: none;
+ --panel-arrowcontent-border-radius: 3.5px;
+}
+
/* ::::: root elements ::::: */
window,
page,
dialog,
wizard,
prefwindow {
-moz-appearance: dialog;
--- a/toolkit/themes/osx/global/popup.css
+++ b/toolkit/themes/osx/global/popup.css
@@ -1,22 +1,14 @@
/* 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/. */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
-.panel-arrowcontent {
- --panel-arrowcontent-padding: 16px;
- --panel-arrowcontent-background: linear-gradient(hsla(0,0%,99%,1), hsla(0,0%,99%,.975) 10%, hsla(0,0%,98%,.975));
- --panel-arrowcontent-color: hsl(0,0%,10%);
- --panel-arrowcontent-border: none;
- --panel-arrowcontent-border-radius: 3.5px;
-}
-
menupopup,
panel {
-moz-appearance: menupopup;
background-color: Menu;
}
menupopup > menu > menupopup {
margin-top: -4px;
@@ -54,17 +46,18 @@ panel[type="arrow"][side="right"] {
box-shadow: 0 0 0 1px hsla(210,4%,10%,.05);
color: var(--panel-arrowcontent-color);
border: var(--panel-arrowcontent-border);
padding: var(--panel-arrowcontent-padding);
margin: 1px;
}
.panel-arrow[side="top"] {
- list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical.png");
+ list-style-image: var(--panel-arrow-image-vertical,
+ url("chrome://global/skin/arrow/panelarrow-vertical.png"));
margin-left: 16px;
margin-right: 16px;
margin-bottom: -1px;
}
.panel-arrow[side="bottom"] {
list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical.png");
-moz-transform: scaleY(-1);
@@ -86,17 +79,18 @@ panel[type="arrow"][side="right"] {
margin-top: 16px;
margin-bottom: 16px;
margin-left: -1px;
}
@media (min-resolution: 2dppx) {
.panel-arrow[side="top"],
.panel-arrow[side="bottom"] {
- list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical@2x.png");
+ list-style-image: var(--panel-arrow-image-vertical,
+ url("chrome://global/skin/arrow/panelarrow-vertical@2x.png"));
width: 18px;
height: 10px;
}
.panel-arrow[side="left"],
.panel-arrow[side="right"] {
list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal@2x.png");
width: 10px;
--- a/toolkit/themes/windows/global/global.css
+++ b/toolkit/themes/windows/global/global.css
@@ -16,16 +16,24 @@
radio {
-moz-binding: url("chrome://global/skin/globalBindings.xml#radio");
}
menulist > menupopup {
-moz-binding: url("chrome://global/content/bindings/popup.xml#popup-scrollbars");
}
+/* ::::: Variables ::::: */
+:root {
+ --panel-arrowcontent-padding: 10px;
+ --panel-arrowcontent-background: -moz-field;
+ --panel-arrowcontent-color: -moz-FieldText;
+ --panel-arrowcontent-border: 1px solid ThreeDShadow;
+}
+
/* ::::: root elements ::::: */
window,
page,
dialog,
wizard,
prefwindow {
-moz-appearance: window;
--- a/toolkit/themes/windows/global/popup.css
+++ b/toolkit/themes/windows/global/popup.css
@@ -1,22 +1,14 @@
/* 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/. */
@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
-/* ::::: Variables ::::: */
-.panel-arrowcontent {
- --panel-arrowcontent-padding: 10px;
- --panel-arrowcontent-background: -moz-field;
- --panel-arrowcontent-color: -moz-FieldText;
- --panel-arrowcontent-border: 1px solid ThreeDShadow;
-}
-
/* ::::: menupopup ::::: */
menupopup,
panel {
border: 3px solid transparent;
-moz-border-top-colors : ThreeDLightShadow ThreeDHighlight ThreeDFace;
-moz-border-left-colors : ThreeDLightShadow ThreeDHighlight ThreeDFace;
-moz-border-right-colors : ThreeDDarkShadow ThreeDShadow ThreeDFace;
@@ -73,17 +65,18 @@ panel[type="arrow"][side="right"] {
border-radius: 4px;
}
%ifdef XP_WIN
}
%endif
.panel-arrow[side="top"],
.panel-arrow[side="bottom"] {
- list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical-themed.svg");
+ list-style-image: var(--panel-arrow-image-vertical,
+ url("chrome://global/skin/arrow/panelarrow-vertical-themed.svg"));
position: relative;
margin-left: 10px;
margin-right: 10px;
}
.panel-arrow[side="top"] {
margin-bottom: -5px;
}
@@ -114,17 +107,18 @@ panel[type="arrow"][side="right"] {
@media (-moz-windows-default-theme) {
.panel-arrowcontent {
--panel-arrowcontent-border: 1px solid hsla(210,4%,10%,.2);
box-shadow: 0 0 4px hsla(210,4%,10%,.2);
}
.panel-arrow[side="top"],
.panel-arrow[side="bottom"] {
- list-style-image: url("chrome://global/skin/arrow/panelarrow-vertical.svg");
+ list-style-image: var(--panel-arrow-image-vertical,
+ url("chrome://global/skin/arrow/panelarrow-vertical.svg"));
}
.panel-arrow[side="left"],
.panel-arrow[side="right"] {
list-style-image: url("chrome://global/skin/arrow/panelarrow-horizontal.svg");
}
}
%endif