Skip to content

Commit 0466829

Browse files
Align relaxed-<select> implementation with spec, in line with review comments
Move selectedcontent cloning from TreeBuilder to DOM-side subclasses: the optionElementPopped() hook now walks the live DOM to find the ancestor <select> and its <selectedcontent>, check selectedness, and clone option children — rather than relying on parser-tracked state. Implemented in all four subclasses (DOMTreeBuilder, SAXTreeBuilder, XOMTreeBuilder, BrowserTreeBuilder). Remove all dead IN_SELECT / IN_SELECT_IN_TABLE mode code (handlers, constants, end-tag special cases) — with <select> “relaxation”, those modes are never entered. Fix resetTheInsertionMode to skip past <select> elements on the stack instead of incorrectly returning IN_BODY — an enclosing td/th now correctly yields IN_CELL, an enclosing table yields IN_TABLE, etc. Align start-tag handling with the spec: - HR: use generateImpliedEndTags() instead of manual isCurrent/pop - OPTION/OPTGROUP: remove redundant pop loops (spec says parse error only), add missing reconstructTheActiveFormattingElements for optgroup, fix optgroup to check both option and optgroup in scope - SELECT: check fragment case first, remove generateImpliedEndTags from nested case, add framesetOk = false - INPUT: add explicit fragment-case guard - Table elements (caption, tbody, tr, td, th): treat as stray start tags, removing the old IN_SELECT_IN_TABLE dual-scope check Fold </select> end tag into the standard block-element group (with div, fieldset, button, etc.) — the dedicated resetTheInsertionMode call is no longer needed. Add spec URLs and verbatim spec-text comments throughout all <select>-related code sections — for traceability and review-ability.
1 parent 87fe37a commit 0466829

File tree

7 files changed

+364
-499
lines changed

7 files changed

+364
-499
lines changed

gwt-src/nu/validator/htmlparser/gwt/BrowserTreeBuilder.java

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -475,20 +475,83 @@ private static native void removeChild(JavaScriptObject parent,
475475
}
476476
}
477477

478+
private static native JavaScriptObject getNextSibling(
479+
JavaScriptObject node) /*-{
480+
return node.nextSibling;
481+
}-*/;
482+
483+
private static native String getLocalName(
484+
JavaScriptObject node) /*-{
485+
return node.localName;
486+
}-*/;
487+
488+
private static native boolean hasAttribute(
489+
JavaScriptObject node, String name) /*-{
490+
return node.hasAttribute(name);
491+
}-*/;
492+
478493
@Override
479-
protected void cloneOptionContentToSelectedContent(
480-
JavaScriptObject option, JavaScriptObject selectedContent)
494+
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-select-option-selectedness
495+
// Implements "maybe clone an option into selectedcontent"
496+
protected void optionElementPopped(JavaScriptObject option)
481497
throws SAXException {
482498
try {
499+
// Find the nearest ancestor <select> element
500+
JavaScriptObject ancestor = getParentNode(option);
501+
JavaScriptObject select = null;
502+
while (ancestor != null && getNodeType(ancestor) == 1) {
503+
if ("select".equals(getLocalName(ancestor))) {
504+
select = ancestor;
505+
break;
506+
}
507+
ancestor = getParentNode(ancestor);
508+
}
509+
if (select == null) {
510+
return;
511+
}
512+
513+
// Find the first <selectedcontent> descendant of <select>
514+
JavaScriptObject selectedContent = findDescendantByLocalName(
515+
select, "selectedcontent");
516+
if (selectedContent == null) {
517+
return;
518+
}
519+
520+
// Check option selectedness
521+
boolean hasSelectedAttr = hasAttribute(option, "selected");
522+
if (!hasSelectedAttr && hasChildNodes(selectedContent)) {
523+
// Not the first option and no explicit selected attr
524+
return;
525+
}
526+
527+
// Clear selectedcontent children and deep-clone option children
483528
while (hasChildNodes(selectedContent)) {
484529
removeChild(selectedContent, getFirstChild(selectedContent));
485530
}
486-
JavaScriptObject clone = cloneNodeDeep(option);
487-
while (hasChildNodes(clone)) {
488-
appendChild(selectedContent, getFirstChild(clone));
531+
for (JavaScriptObject child = getFirstChild(option);
532+
child != null; child = getNextSibling(child)) {
533+
appendChild(selectedContent, cloneNodeDeep(child));
489534
}
490535
} catch (JavaScriptException e) {
491536
fatal(e);
492537
}
493538
}
539+
540+
private JavaScriptObject findDescendantByLocalName(
541+
JavaScriptObject root, String localName) {
542+
for (JavaScriptObject child = getFirstChild(root);
543+
child != null; child = getNextSibling(child)) {
544+
if (getNodeType(child) == 1) {
545+
if (localName.equals(getLocalName(child))) {
546+
return child;
547+
}
548+
JavaScriptObject found = findDescendantByLocalName(
549+
child, localName);
550+
if (found != null) {
551+
return found;
552+
}
553+
}
554+
}
555+
return null;
556+
}
494557
}

src/nu/validator/htmlparser/dom/DOMTreeBuilder.java

Lines changed: 57 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -356,19 +356,71 @@ protected Element createAndInsertFosterParentedElement(String ns, String name,
356356
}
357357

358358
@Override
359-
protected void cloneOptionContentToSelectedContent(Element option,
360-
Element selectedContent) throws SAXException {
359+
// https://html.spec.whatwg.org/multipage/form-elements.html#concept-select-option-selectedness
360+
// Implements "maybe clone an option into selectedcontent"
361+
protected void optionElementPopped(Element option) throws SAXException {
361362
try {
363+
// Find the nearest ancestor <select> element
364+
Node ancestor = option.getParentNode();
365+
Element select = null;
366+
while (ancestor != null) {
367+
if (ancestor.getNodeType() == Node.ELEMENT_NODE) {
368+
Element elt = (Element) ancestor;
369+
if ("select".equals(elt.getLocalName())
370+
&& "http://www.w3.org/1999/xhtml".equals(
371+
elt.getNamespaceURI())) {
372+
select = elt;
373+
break;
374+
}
375+
}
376+
ancestor = ancestor.getParentNode();
377+
}
378+
if (select == null) {
379+
return;
380+
}
381+
382+
// Find the first <selectedcontent> descendant of <select>
383+
Element selectedContent = findSelectedContent(select);
384+
if (selectedContent == null) {
385+
return;
386+
}
387+
388+
// Check option selectedness
389+
boolean hasSelected = option.hasAttribute("selected");
390+
if (!hasSelected && selectedContent.hasChildNodes()) {
391+
// Not the first option and no explicit selected attr
392+
return;
393+
}
394+
395+
// Clear selectedcontent children and deep-clone option children
362396
while (selectedContent.hasChildNodes()) {
363397
selectedContent.removeChild(selectedContent.getFirstChild());
364398
}
365-
Node child = option.getFirstChild();
366-
while (child != null) {
399+
for (Node child = option.getFirstChild(); child != null;
400+
child = child.getNextSibling()) {
367401
selectedContent.appendChild(child.cloneNode(true));
368-
child = child.getNextSibling();
369402
}
370403
} catch (DOMException e) {
371404
fatal(e);
372405
}
373406
}
407+
408+
private Element findSelectedContent(Element root) {
409+
for (Node child = root.getFirstChild(); child != null;
410+
child = child.getNextSibling()) {
411+
if (child.getNodeType() == Node.ELEMENT_NODE) {
412+
Element elt = (Element) child;
413+
if ("selectedcontent".equals(elt.getLocalName())
414+
&& "http://www.w3.org/1999/xhtml".equals(
415+
elt.getNamespaceURI())) {
416+
return elt;
417+
}
418+
Element found = findSelectedContent(elt);
419+
if (found != null) {
420+
return found;
421+
}
422+
}
423+
}
424+
return null;
425+
}
374426
}

src/nu/validator/htmlparser/impl/ElementName.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1428,7 +1428,7 @@ public void destructor() {
14281428
public static final ElementName SELECTEDCONTENT = new ElementName("selectedcontent", "selectedcontent",
14291429
// CPPONLY: NS_NewHTMLElement,
14301430
// CPPONLY: NS_NewSVGUnknownElement,
1431-
TreeBuilder.SELECTEDCONTENT);
1431+
TreeBuilder.OTHER);
14321432
public static final ElementName SLOT = new ElementName("slot", "slot",
14331433
// CPPONLY: NS_NewHTMLSlotElement,
14341434
// CPPONLY: NS_NewSVGUnknownElement,

0 commit comments

Comments
 (0)