Bug 1335483 - Compare the user agent value for the select option styling to determine if content has opted-in to custom styling. r=mconley

MozReview-Commit-ID: 8wqclzJr2si

--HG--
extra : rebase_source : 38999f77e1c39ee465f8ddb783bae58138adfce2
This commit is contained in:
Jared Wein 2017-01-31 15:58:27 -05:00
parent f83e36ea12
commit 3de08e5fac
5 changed files with 87 additions and 60 deletions

View File

@ -81,11 +81,19 @@ const PAGECONTENT_TRANSLATED =
"</div></body></html>";
const PAGECONTENT_COLORS =
"<html><head><style>.blue { color: #fff; background-color: #00f; } .green { color: #800080; background-color: green; }</style>" +
"<html><head><style>" +
" .blue { color: #fff; background-color: #00f; }" +
" .green { color: #800080; background-color: green; }" +
" .defaultColor { color: -moz-ComboboxText; }" +
" .defaultBackground { background-color: -moz-Combobox; }" +
"</style>" +
"<body><select id='one'>" +
' <option value="One" style="color: #fff; background-color: #f00;">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgb(255, 0, 0)"}</option>' +
' <option value="Two" class="blue">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgb(0, 0, 255)"}</option>' +
' <option value="Three" class="green">{"color": "rgb(128, 0, 128)", "backgroundColor": "rgb(0, 128, 0)"}</option>' +
' <option value="Four" class="defaultColor defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
' <option value="Five" class="defaultColor">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
' <option value="Six" class="defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
"</select></body></html>";
function openSelectPopup(selectPopup, mode = "key", selector = "select", win = window) {
@ -738,11 +746,6 @@ add_task(function* test_somehidden() {
});
add_task(function* test_colors_applied_to_popup() {
function inverseRGBString(rgbString) {
let [, r, g, b] = rgbString.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
return `rgb(${255 - r}, ${255 - g}, ${255 - b})`;
}
const pageUrl = "data:text/html," + escape(PAGECONTENT_COLORS);
let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
@ -754,62 +757,39 @@ add_task(function* test_colors_applied_to_popup() {
// The label contains a JSON string of the expected colors for
// `color` and `background-color`.
is(selectPopup.parentNode.itemCount, 3, "Correct number of items");
is(selectPopup.parentNode.itemCount, 6, "Correct number of items");
let child = selectPopup.firstChild;
let idx = 1;
ok(child.selected, "The first child should be selected");
while (child) {
let expectedColors = JSON.parse(child.label);
let expected = JSON.parse(child.label);
// We need to use Canvas here to get the actual pixel color
// because the computedStyle will only tell us the 'color' or
// 'backgroundColor' of the element, but not what the displayed
// color is due to composition of various CSS rules such as
// 'filter' which is applied when elements have custom background
// or foreground elements.
let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
canvas = document.documentElement.appendChild(canvas);
let rect = child.getBoundingClientRect();
canvas.setAttribute("width", rect.width);
canvas.setAttribute("height", rect.height);
canvas.mozOpaque = true;
let ctx = canvas.getContext("2d");
ctx.drawWindow(window, rect.x + rect.left, rect.y + rect.top, rect.width, rect.height, "#000", ctx.DRAWWINDOW_USE_WIDGET_LAYERS);
let frame = ctx.getImageData(0, 0, rect.width, rect.height);
let pixels = frame.data.length / 4;
// Assume the inverse backgroundColor is the color of the first pixel.
let [inverseBgR, inverseBgG, inverseBgB] = frame.data;
let inverseBackgroundColor = `rgb(${inverseBgR}, ${inverseBgG}, ${inverseBgB})`;
// Use the next different pixel color as the foreground color, assuming
// no anti-aliasing.
let inverseColor = inverseBackgroundColor;
for (let i = 0; i < pixels; i++) {
if (inverseBgR != frame.data[i * 4 + 0] &&
inverseBgG != frame.data[i * 4 + 1] &&
inverseBgB != frame.data[i * 4 + 2]) {
inverseColor = `rgb(${frame.data[i * 4 + 0]}, ${frame.data[i * 4 + 1]}, ${frame.data[i * 4 + 2]})`;
for (let color of Object.keys(expected)) {
if (color.toLowerCase().includes("color") &&
!expected[color].startsWith("rgb")) {
// Need to convert system color to RGB color.
let textarea = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
textarea.style.color = expected[color];
expected[color] = getComputedStyle(textarea).color;
}
}
// The canvas code above isn't getting the right colors for the pixels,
// it always returns rgb(255,255,255).
todo_is(inverseColor, inverseRGBString(getComputedStyle(child).color),
"Item " + (idx) + " has correct inverse foreground color when selected");
todo_is(inverseBackgroundColor, inverseRGBString(getComputedStyle(child).backgroundColor),
"Item " + (idx) + " has correct inverse background color when selected");
canvas.remove();
// Press Down to move the selected item to the next item in the
// list and check the colors of this item when it's not selected.
EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
is(getComputedStyle(child).color, expectedColors.color,
"Item " + (idx) + " has correct foreground color");
is(getComputedStyle(child).backgroundColor, expectedColors.backgroundColor,
"Item " + (idx++) + " has correct background color");
if (expected.unstyled) {
ok(!child.hasAttribute("customoptionstyling"),
`Item ${idx} should not have any custom option styling`);
} else {
is(getComputedStyle(child).color, expected.color,
"Item " + (idx) + " has correct foreground color");
is(getComputedStyle(child).backgroundColor, expected.backgroundColor,
"Item " + (idx) + " has correct background color");
}
idx++;
child = child.nextSibling;
}

View File

@ -471,7 +471,8 @@
let zoom = Services.prefs.getBoolPref("browser.zoom.full") ||
this.isSyntheticDocument ? this._fullZoom : this._textZoom;
this._selectParentHelper.populate(menulist, data.options, data.selectedIndex, zoom);
this._selectParentHelper.populate(menulist, data.options, data.selectedIndex,
zoom, data.uaBackgroundColor, data.uaColor);
this._selectParentHelper.open(this, menulist, data.rect, data.isOpenedViaTouch);
break;
}

View File

@ -36,6 +36,8 @@ this.SelectContentHelper = function(aElement, aOptions, aGlobal) {
this.global = aGlobal;
this.closedWithEnter = false;
this.isOpenedViaTouch = aOptions.isOpenedViaTouch;
this._uaBackgroundColor = null;
this._uaColor = null;
this.init();
this.showDropDown();
this._updateTimer = new DeferredTask(this._update.bind(this), 0);
@ -91,7 +93,9 @@ this.SelectContentHelper.prototype = {
options: this._buildOptionList(),
selectedIndex: this.element.selectedIndex,
direction: getComputedStyles(this.element).direction,
isOpenedViaTouch: this.isOpenedViaTouch
isOpenedViaTouch: this.isOpenedViaTouch,
uaBackgroundColor: this.uaBackgroundColor,
uaColor: this.uaColor,
});
gOpen = true;
},
@ -110,9 +114,37 @@ this.SelectContentHelper.prototype = {
this.global.sendAsyncMessage("Forms:UpdateDropDown", {
options: this._buildOptionList(),
selectedIndex: this.element.selectedIndex,
uaBackgroundColor: this.uaBackgroundColor,
uaColor: this.uaColor,
});
},
// Determine user agent background-color and color.
// This is used to skip applying the custom color if it matches
// the user agent values.
_calculateUAColors() {
let dummy = this.element.ownerDocument.createElement("option");
dummy.style.color = "-moz-comboboxtext";
dummy.style.backgroundColor = "-moz-combobox";
let dummyCS = this.element.ownerGlobal.getComputedStyle(dummy);
this._uaBackgroundColor = dummyCS.backgroundColor;
this._uaColor = dummyCS.color;
},
get uaBackgroundColor() {
if (!this._uaBackgroundColor) {
this._calculateUAColors();
}
return this._uaBackgroundColor;
},
get uaColor() {
if (!this._uaColor) {
this._calculateUAColors();
}
return this._uaColor;
},
dispatchMouseEvent(win, target, eventName) {
let mouseEvent = new win.MouseEvent(eventName, {
view: win,

View File

@ -25,12 +25,13 @@ var closedWithEnter = false;
var selectRect;
this.SelectParentHelper = {
populate(menulist, items, selectedIndex, zoom) {
populate(menulist, items, selectedIndex, zoom, uaBackgroundColor, uaColor) {
// Clear the current contents of the popup
menulist.menupopup.textContent = "";
currentZoom = zoom;
currentMenulist = menulist;
populateChildren(menulist, items, selectedIndex, zoom);
populateChildren(menulist, items, selectedIndex, zoom,
uaBackgroundColor, uaColor);
},
open(browser, menulist, rect, isOpenedViaTouch) {
@ -139,7 +140,10 @@ this.SelectParentHelper = {
let options = msg.data.options;
let selectedIndex = msg.data.selectedIndex;
this.populate(currentMenulist, options, selectedIndex, currentZoom);
let uaBackgroundColor = msg.data.uaBackgroundColor;
let uaColor = msg.data.uaColor;
this.populate(currentMenulist, options, selectedIndex,
currentZoom, uaBackgroundColor, uaColor);
}
},
@ -168,15 +172,15 @@ this.SelectParentHelper = {
};
function populateChildren(menulist, options, selectedIndex, zoom,
uaBackgroundColor, uaColor,
parentElement = null, isGroupDisabled = false,
adjustedTextSize = -1, addSearch = true) {
let element = menulist.menupopup;
let win = element.ownerGlobal;
// -1 just means we haven't calculated it yet. When we recurse through this function
// we will pass in adjustedTextSize to save on recalculations.
if (adjustedTextSize == -1) {
let win = element.ownerGlobal;
// Grab the computed text size and multiply it by the remote browser's fullZoom to ensure
// the popup's text size is matched with the content's. We can't just apply a CSS transform
// here as the popup's preferred size is calculated pre-transform.
@ -197,16 +201,21 @@ function populateChildren(menulist, options, selectedIndex, zoom,
item.hiddenByContent = item.hidden;
item.setAttribute("tooltiptext", option.tooltip);
if (option.backgroundColor && option.backgroundColor != "transparent") {
let customOptionStylingUsed = false;
if (option.backgroundColor &&
option.backgroundColor != "transparent" &&
option.backgroundColor != uaBackgroundColor) {
item.style.backgroundColor = option.backgroundColor;
customOptionStylingUsed = true;
}
if (option.color && option.color != "transparent") {
if (option.color &&
option.color != uaColor) {
item.style.color = option.color;
customOptionStylingUsed = true;
}
if ((option.backgroundColor && option.backgroundColor != "transparent") ||
(option.color && option.color != "rgb(0, 0, 0)")) {
if (customOptionStylingUsed) {
item.setAttribute("customoptionstyling", "true");
} else {
item.removeAttribute("customoptionstyling");
@ -222,6 +231,7 @@ function populateChildren(menulist, options, selectedIndex, zoom,
if (isOptGroup) {
populateChildren(menulist, option.children, selectedIndex, zoom,
uaBackgroundColor, uaColor,
item, isDisabled, adjustedTextSize, false);
} else {
if (option.index == selectedIndex) {

View File

@ -32,6 +32,10 @@ menuitem[_moz-menuactive="true"] {
background-color: -moz-menuhover;
}
menuitem[customoptionstyling="true"] {
-moz-appearance: none;
}
menuitem[_moz-menuactive="true"][customoptionstyling="true"] {
filter: invert(100%);
}