зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1830909 - Implement <hr> in <select> r=hsivonen,emilio,geckoview-reviewers,desktop-theme-reviewers,Jamie,owlish
Updated HTML parser to allow <hr> in <select>. Updated internal toolkit UI for <select> dropdown to create menuseperators for hrs. Updated WPT expectations: - HTML5Lib WebKit parsing for it now passes 100% Also includes Android support, but Fenix does not support separators in the menus used (single/multiple) yet so they are not rendered. Differential Revision: https://phabricator.services.mozilla.com/D189065
This commit is contained in:
Родитель
a0c893d3ff
Коммит
b7cb7a41f5
|
@ -32,3 +32,5 @@ fail-if = ["a11y_checks"] # Bug 1854233 select may not be labeled
|
|||
["browser_selectpopup_width.js"]
|
||||
|
||||
["browser_selectpopup_xhtml.js"]
|
||||
|
||||
["browser_selectpopup_hr.js"]
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
add_task(async function test_hr() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["dom.forms.select.customstyling", true]],
|
||||
});
|
||||
|
||||
const PAGE_CONTENT = `
|
||||
<!doctype html>
|
||||
<select>
|
||||
<option>One</option>
|
||||
<hr style="color: red; background-color: blue">
|
||||
<option>Two</option>
|
||||
</select>`;
|
||||
|
||||
const pageUrl = "data:text/html," + encodeURIComponent(PAGE_CONTENT);
|
||||
let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
|
||||
|
||||
const selectPopup = await openSelectPopup("click");
|
||||
const menulist = selectPopup.parentNode;
|
||||
|
||||
const optionOne = selectPopup.children[0];
|
||||
const separator = selectPopup.children[1];
|
||||
const optionTwo = selectPopup.children[2];
|
||||
|
||||
is(optionOne.textContent, "One", "First option has expected text content");
|
||||
|
||||
is(separator.tagName, "menuseparator", "Separator is menuseparator");
|
||||
|
||||
const separatorStyle = getComputedStyle(separator);
|
||||
|
||||
is(
|
||||
separatorStyle.color,
|
||||
"rgb(255, 0, 0)",
|
||||
"Separator color is specified CSS color"
|
||||
);
|
||||
|
||||
is(
|
||||
separatorStyle.backgroundColor,
|
||||
"rgba(0, 0, 0, 0)",
|
||||
"Separator background-color is not set to specified CSS color"
|
||||
);
|
||||
|
||||
is(optionTwo.textContent, "Two", "Second option has expected text content");
|
||||
|
||||
is(menulist.activeChild, optionOne, "First option is selected to start");
|
||||
|
||||
EventUtils.synthesizeKey("KEY_ArrowDown");
|
||||
|
||||
is(
|
||||
menulist.activeChild,
|
||||
optionTwo,
|
||||
"Second option is selected after arrow down"
|
||||
);
|
||||
|
||||
BrowserTestUtils.removeTab(tab);
|
||||
});
|
|
@ -101,6 +101,8 @@ export class PromptFactory {
|
|||
} else if (win.HTMLOptionElement.isInstance(child)) {
|
||||
item.label = child.label || child.text;
|
||||
item.selected = child.selected;
|
||||
} else if (win.HTMLHRElement.isInstance(child)) {
|
||||
item.separator = true;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -2573,6 +2573,20 @@ public abstract class TreeBuilder<T> implements TokenHandler,
|
|||
startTagTemplateInHead(elementName, attributes);
|
||||
attributes = null; // CPP
|
||||
break starttagloop;
|
||||
case HR:
|
||||
if (isCurrent("option")) {
|
||||
pop();
|
||||
}
|
||||
if (isCurrent("optgroup")) {
|
||||
pop();
|
||||
}
|
||||
appendVoidElementToCurrent(elementName, attributes);
|
||||
selfClosing = false;
|
||||
// [NOCPP[
|
||||
voidElement = true;
|
||||
// ]NOCPP]
|
||||
attributes = null; // CPP
|
||||
break starttagloop;
|
||||
default:
|
||||
errStrayStartTag(name);
|
||||
break starttagloop;
|
||||
|
@ -5457,6 +5471,25 @@ public abstract class TreeBuilder<T> implements TokenHandler,
|
|||
push(node);
|
||||
}
|
||||
|
||||
private void appendVoidElementToCurrent(
|
||||
ElementName elementName, HtmlAttributes attributes)
|
||||
throws SAXException {
|
||||
@Local String popName = elementName.getName();
|
||||
// [NOCPP[
|
||||
checkAttributes(attributes, "http://www.w3.org/1999/xhtml");
|
||||
if (!elementName.isInterned()) {
|
||||
popName = checkPopName(popName);
|
||||
}
|
||||
// ]NOCPP]
|
||||
T currentNode = nodeFromStackWithBlinkCompat(currentPtr);
|
||||
T elt = createElement("http://www.w3.org/1999/xhtml", popName, attributes, currentNode
|
||||
// CPPONLY: , htmlCreator(elementName.getHtmlCreator())
|
||||
);
|
||||
appendElement(elt, currentNode);
|
||||
elementPushed("http://www.w3.org/1999/xhtml", popName, elt);
|
||||
elementPopped("http://www.w3.org/1999/xhtml", popName, elt);
|
||||
}
|
||||
|
||||
private void appendVoidElementToCurrentMayFoster(
|
||||
ElementName elementName, HtmlAttributes attributes, T form) throws SAXException {
|
||||
@Local String name = elementName.getName();
|
||||
|
|
|
@ -1759,6 +1759,18 @@ starttagloop:
|
|||
attributes = nullptr;
|
||||
NS_HTML5_BREAK(starttagloop);
|
||||
}
|
||||
case HR: {
|
||||
if (isCurrent(nsGkAtoms::option)) {
|
||||
pop();
|
||||
}
|
||||
if (isCurrent(nsGkAtoms::optgroup)) {
|
||||
pop();
|
||||
}
|
||||
appendVoidElementToCurrent(elementName, attributes);
|
||||
selfClosing = false;
|
||||
attributes = nullptr;
|
||||
NS_HTML5_BREAK(starttagloop);
|
||||
}
|
||||
default: {
|
||||
errStrayStartTag(name);
|
||||
NS_HTML5_BREAK(starttagloop);
|
||||
|
@ -4311,6 +4323,18 @@ void nsHtml5TreeBuilder::appendToCurrentNodeAndPushElementMayFoster(
|
|||
push(node);
|
||||
}
|
||||
|
||||
void nsHtml5TreeBuilder::appendVoidElementToCurrent(
|
||||
nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes) {
|
||||
nsAtom* popName = elementName->getName();
|
||||
nsIContentHandle* currentNode = nodeFromStackWithBlinkCompat(currentPtr);
|
||||
nsIContentHandle* elt =
|
||||
createElement(kNameSpaceID_XHTML, popName, attributes, currentNode,
|
||||
htmlCreator(elementName->getHtmlCreator()));
|
||||
appendElement(elt, currentNode);
|
||||
elementPushed(kNameSpaceID_XHTML, popName, elt);
|
||||
elementPopped(kNameSpaceID_XHTML, popName, elt);
|
||||
}
|
||||
|
||||
void nsHtml5TreeBuilder::appendVoidElementToCurrentMayFoster(
|
||||
nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes,
|
||||
nsIContentHandle* form) {
|
||||
|
|
|
@ -477,6 +477,8 @@ class nsHtml5TreeBuilder : public nsAHtml5TreeBuilderState {
|
|||
void appendToCurrentNodeAndPushElementMayFoster(
|
||||
nsHtml5ElementName* elementName, nsHtml5HtmlAttributes* attributes,
|
||||
nsIContentHandle* form);
|
||||
void appendVoidElementToCurrent(nsHtml5ElementName* elementName,
|
||||
nsHtml5HtmlAttributes* attributes);
|
||||
void appendVoidElementToCurrentMayFoster(nsHtml5ElementName* elementName,
|
||||
nsHtml5HtmlAttributes* attributes,
|
||||
nsIContentHandle* form);
|
||||
|
|
|
@ -1,100 +0,0 @@
|
|||
[html5lib_webkit02.html?run_type=write]
|
||||
expected:
|
||||
if (os == "android") and fission: [TIMEOUT, OK]
|
||||
[html5lib_webkit02.html 5463526d91a8677b27b6967866d6605f1bb03aac]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 4879f476053094cf5602d325724675378856a902]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 44c88b90236f01ebc8e0123363b527640a07070c]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html cfb304e8f2d3cbdecc362226e7775cab452d5489]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 3fc625e7cb9b6ea72a9e252ede84c6fdd9680d87]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html ecd089f9b5193fad306c5b475c4711547fe5e209]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html cee2230c74671c594a1140a68d16e3d3e5ae005a]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 22b9fe36797d70a3b71a6aadc6ad7cff23c3fc90]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html a82c3bf49c381b5f58c5c8a4bbbe0cef2458e28a]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 61f8d527795dc8044a95a3e2437de81e16597ceb]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[html5lib_webkit02.html?run_type=uri]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[html5lib_webkit02.html 5463526d91a8677b27b6967866d6605f1bb03aac]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 4879f476053094cf5602d325724675378856a902]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 44c88b90236f01ebc8e0123363b527640a07070c]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html cfb304e8f2d3cbdecc362226e7775cab452d5489]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 3fc625e7cb9b6ea72a9e252ede84c6fdd9680d87]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html ecd089f9b5193fad306c5b475c4711547fe5e209]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html cee2230c74671c594a1140a68d16e3d3e5ae005a]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 22b9fe36797d70a3b71a6aadc6ad7cff23c3fc90]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html a82c3bf49c381b5f58c5c8a4bbbe0cef2458e28a]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 61f8d527795dc8044a95a3e2437de81e16597ceb]
|
||||
expected: FAIL
|
||||
|
||||
|
||||
[html5lib_webkit02.html?run_type=write_single]
|
||||
expected:
|
||||
if (os == "android") and fission: [OK, TIMEOUT]
|
||||
[html5lib_webkit02.html 5463526d91a8677b27b6967866d6605f1bb03aac]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 4879f476053094cf5602d325724675378856a902]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 44c88b90236f01ebc8e0123363b527640a07070c]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html cfb304e8f2d3cbdecc362226e7775cab452d5489]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 3fc625e7cb9b6ea72a9e252ede84c6fdd9680d87]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html ecd089f9b5193fad306c5b475c4711547fe5e209]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html cee2230c74671c594a1140a68d16e3d3e5ae005a]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 22b9fe36797d70a3b71a6aadc6ad7cff23c3fc90]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html a82c3bf49c381b5f58c5c8a4bbbe0cef2458e28a]
|
||||
expected: FAIL
|
||||
|
||||
[html5lib_webkit02.html 61f8d527795dc8044a95a3e2437de81e16597ceb]
|
||||
expected: FAIL
|
|
@ -382,17 +382,46 @@ function uniqueStylesIndex(cs, uniqueStyles) {
|
|||
function buildOptionListForChildren(node, uniqueStyles) {
|
||||
let result = [];
|
||||
|
||||
let lastWasHR = false;
|
||||
for (let child of node.children) {
|
||||
let className = ChromeUtils.getClassName(child);
|
||||
let isOption = className == "HTMLOptionElement";
|
||||
let isOptGroup = className == "HTMLOptGroupElement";
|
||||
if (!isOption && !isOptGroup) {
|
||||
let isHR = className == "HTMLHRElement";
|
||||
if (!isOption && !isOptGroup && !isHR) {
|
||||
continue;
|
||||
}
|
||||
if (child.hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let cs = getComputedStyles(child);
|
||||
|
||||
if (isHR) {
|
||||
// https://html.spec.whatwg.org/#the-select-element-2
|
||||
// "Each sequence of one or more child hr element siblings may be rendered as a single separator."
|
||||
if (lastWasHR) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let info = {
|
||||
index: child.index,
|
||||
display: cs.display,
|
||||
isHR,
|
||||
};
|
||||
|
||||
const defaultHRStyle = node.ownerGlobal.getDefaultComputedStyle(child);
|
||||
if (cs.color != defaultHRStyle.color) {
|
||||
info.color = cs.color;
|
||||
}
|
||||
|
||||
result.push(info);
|
||||
|
||||
lastWasHR = true;
|
||||
continue;
|
||||
}
|
||||
lastWasHR = false;
|
||||
|
||||
// The option code-path should match HTMLOptionElement::GetRenderedLabel.
|
||||
let textContent = isOptGroup
|
||||
? child.getAttribute("label")
|
||||
|
@ -401,7 +430,6 @@ function buildOptionListForChildren(node, uniqueStyles) {
|
|||
textContent = "";
|
||||
}
|
||||
|
||||
let cs = getComputedStyles(child);
|
||||
let info = {
|
||||
index: child.index,
|
||||
isOptGroup,
|
||||
|
|
|
@ -274,7 +274,13 @@ export var SelectParentHelper = {
|
|||
|
||||
this._currentZoom = zoom;
|
||||
this._currentMenulist = menulist;
|
||||
this.populateChildren(menulist, items, uniqueItemStyles, selectedIndex);
|
||||
this.populateChildren(
|
||||
menulist,
|
||||
custom,
|
||||
items,
|
||||
uniqueItemStyles,
|
||||
selectedIndex
|
||||
);
|
||||
},
|
||||
|
||||
open(browser, menulist, rect, isOpenedViaTouch, selectParentActor) {
|
||||
|
@ -476,6 +482,7 @@ export var SelectParentHelper = {
|
|||
*/
|
||||
populateChildren(
|
||||
menulist,
|
||||
custom,
|
||||
options,
|
||||
uniqueOptionStyles,
|
||||
selectedIndex,
|
||||
|
@ -489,26 +496,19 @@ export var SelectParentHelper = {
|
|||
let ariaOwns = "";
|
||||
for (let option of options) {
|
||||
let isOptGroup = option.isOptGroup;
|
||||
let item = element.ownerDocument.createXULElement(
|
||||
isOptGroup ? "menucaption" : "menuitem"
|
||||
);
|
||||
let isHR = option.isHR;
|
||||
|
||||
let xulElement = "menuitem";
|
||||
if (isOptGroup) {
|
||||
item.setAttribute("role", "group");
|
||||
xulElement = "menucaption";
|
||||
}
|
||||
item.setAttribute("label", option.textContent);
|
||||
item.className = `ContentSelectDropdown-item-${option.styleIndex}`;
|
||||
if (isHR) {
|
||||
xulElement = "menuseparator";
|
||||
}
|
||||
|
||||
let item = element.ownerDocument.createXULElement(xulElement);
|
||||
item.hidden =
|
||||
option.display == "none" || (parentElement && parentElement.hidden);
|
||||
// Keep track of which options are hidden by page content, so we can avoid
|
||||
// showing them on search input.
|
||||
item.hiddenByContent = item.hidden;
|
||||
item.setAttribute("tooltiptext", option.tooltip);
|
||||
|
||||
if (uniqueOptionStyles[option.styleIndex].customStyling) {
|
||||
item.setAttribute("customoptionstyling", "true");
|
||||
} else {
|
||||
item.removeAttribute("customoptionstyling");
|
||||
}
|
||||
|
||||
if (parentElement) {
|
||||
// In the menupopup, the optgroup is a sibling of its contained options.
|
||||
|
@ -523,6 +523,30 @@ export var SelectParentHelper = {
|
|||
element.appendChild(item);
|
||||
nthChildIndex++;
|
||||
|
||||
if (isHR) {
|
||||
item.style.color = (custom && option.color) || "";
|
||||
|
||||
// Continue early as HRs do not have other attributes.
|
||||
continue;
|
||||
}
|
||||
|
||||
item.className = `ContentSelectDropdown-item-${option.styleIndex}`;
|
||||
|
||||
if (isOptGroup) {
|
||||
item.setAttribute("role", "group");
|
||||
}
|
||||
item.setAttribute("label", option.textContent);
|
||||
// Keep track of which options are hidden by page content, so we can avoid
|
||||
// showing them on search input.
|
||||
item.hiddenByContent = item.hidden;
|
||||
item.setAttribute("tooltiptext", option.tooltip);
|
||||
|
||||
if (uniqueOptionStyles[option.styleIndex].customStyling) {
|
||||
item.setAttribute("customoptionstyling", "true");
|
||||
} else {
|
||||
item.removeAttribute("customoptionstyling");
|
||||
}
|
||||
|
||||
// A disabled optgroup disables all of its child options.
|
||||
let isDisabled = isGroupDisabled || option.disabled;
|
||||
if (isDisabled) {
|
||||
|
@ -532,6 +556,7 @@ export var SelectParentHelper = {
|
|||
if (isOptGroup) {
|
||||
nthChildIndex = this.populateChildren(
|
||||
menulist,
|
||||
custom,
|
||||
option.children,
|
||||
uniqueOptionStyles,
|
||||
selectedIndex,
|
||||
|
|
|
@ -323,6 +323,11 @@ button.text-link .button-text {
|
|||
background-color: -moz-Combobox;
|
||||
}
|
||||
|
||||
/* Full width separator in select */
|
||||
#ContentSelectDropdown menuseparator {
|
||||
padding-inline: 0;
|
||||
}
|
||||
|
||||
/* Indent options in optgroups */
|
||||
.contentSelectDropdown-ingroup .menu-iconic-text {
|
||||
padding-inline-start: 2em;
|
||||
|
|
Загрузка…
Ссылка в новой задаче