Skip to content

Commit f275246

Browse files
Implement “relaxed” parsing for “customizable select”
Relax “select”-element parsing to allow more child elements, and handle the “selectedcontent” element for cloning “option”-element content.
1 parent 69af110 commit f275246

8 files changed

Lines changed: 659 additions & 300 deletions

File tree

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

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,4 +474,107 @@ private static native void removeChild(JavaScriptObject parent,
474474
fatal(e);
475475
}
476476
}
477+
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 String getNamespaceURI(
489+
JavaScriptObject node) /*-{
490+
return node.namespaceURI;
491+
}-*/;
492+
493+
private static native boolean hasAttribute(
494+
JavaScriptObject node, String name) /*-{
495+
return node.hasAttribute(name);
496+
}-*/;
497+
498+
@Override
499+
// https://html.spec.whatwg.org/multipage/form-elements.html#maybe-clone-an-option-into-selectedcontent
500+
// Implements "maybe clone an option into selectedcontent"
501+
protected void optionElementPopped(JavaScriptObject option)
502+
throws SAXException {
503+
try {
504+
// Find the nearest ancestor <select> element
505+
JavaScriptObject ancestor = getParentNode(option);
506+
JavaScriptObject select = null;
507+
while (ancestor != null && getNodeType(ancestor) == 1) {
508+
if ("select".equals(getLocalName(ancestor))
509+
&& "http://www.w3.org/1999/xhtml".equals(
510+
getNamespaceURI(ancestor))) {
511+
select = ancestor;
512+
break;
513+
}
514+
ancestor = getParentNode(ancestor);
515+
}
516+
if (select == null) {
517+
return;
518+
}
519+
if (hasAttribute(select, "multiple")) {
520+
return;
521+
}
522+
523+
// Find the first <selectedcontent> descendant of <select>
524+
JavaScriptObject selectedContent = findSelectedContent(
525+
select);
526+
if (selectedContent == null) {
527+
return;
528+
}
529+
530+
// Check option selectedness
531+
boolean hasSelectedAttr = hasAttribute(option, "selected");
532+
if (!hasSelectedAttr && hasChildNodes(selectedContent)) {
533+
// Not the first option and no explicit selected attr
534+
return;
535+
}
536+
537+
// Clear selectedcontent children and deep-clone option children
538+
while (hasChildNodes(selectedContent)) {
539+
removeChild(selectedContent, getFirstChild(selectedContent));
540+
}
541+
for (JavaScriptObject child = getFirstChild(option);
542+
child != null; child = getNextSibling(child)) {
543+
appendChild(selectedContent, cloneNodeDeep(child));
544+
}
545+
} catch (JavaScriptException e) {
546+
fatal(e);
547+
}
548+
}
549+
550+
private JavaScriptObject findSelectedContent(
551+
JavaScriptObject root) {
552+
JavaScriptObject current = getFirstChild(root);
553+
if (current == null) {
554+
return null;
555+
}
556+
JavaScriptObject next;
557+
for (;;) {
558+
if (getNodeType(current) == 1
559+
&& "selectedcontent".equals(getLocalName(current))
560+
&& "http://www.w3.org/1999/xhtml".equals(
561+
getNamespaceURI(current))) {
562+
return current;
563+
}
564+
if ((next = getFirstChild(current)) != null) {
565+
current = next;
566+
continue;
567+
}
568+
for (;;) {
569+
if (current == root) {
570+
return null;
571+
}
572+
if ((next = getNextSibling(current)) != null) {
573+
current = next;
574+
break;
575+
}
576+
current = getParentNode(current);
577+
}
578+
}
579+
}
477580
}

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

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,4 +354,89 @@ protected Element createAndInsertFosterParentedElement(String ns, String name,
354354
fatal(e);
355355
}
356356
}
357+
358+
@Override
359+
// https://html.spec.whatwg.org/multipage/form-elements.html#maybe-clone-an-option-into-selectedcontent
360+
// Implements "maybe clone an option into selectedcontent"
361+
protected void optionElementPopped(Element option) throws SAXException {
362+
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+
if (select.hasAttribute("multiple")) {
382+
return;
383+
}
384+
385+
// Find the first <selectedcontent> descendant of <select>
386+
Element selectedContent = findSelectedContent(select);
387+
if (selectedContent == null) {
388+
return;
389+
}
390+
391+
// Check option selectedness
392+
boolean hasSelected = option.hasAttribute("selected");
393+
if (!hasSelected && selectedContent.hasChildNodes()) {
394+
// Not the first option and no explicit selected attr
395+
return;
396+
}
397+
398+
// Clear selectedcontent children and deep-clone option children
399+
while (selectedContent.hasChildNodes()) {
400+
selectedContent.removeChild(selectedContent.getFirstChild());
401+
}
402+
for (Node child = option.getFirstChild(); child != null;
403+
child = child.getNextSibling()) {
404+
selectedContent.appendChild(child.cloneNode(true));
405+
}
406+
} catch (DOMException e) {
407+
fatal(e);
408+
}
409+
}
410+
411+
private Element findSelectedContent(Element root) {
412+
Node current = root.getFirstChild();
413+
if (current == null) {
414+
return null;
415+
}
416+
Node next;
417+
for (;;) {
418+
if (current.getNodeType() == Node.ELEMENT_NODE) {
419+
Element elt = (Element) current;
420+
if ("selectedcontent".equals(elt.getLocalName())
421+
&& "http://www.w3.org/1999/xhtml".equals(
422+
elt.getNamespaceURI())) {
423+
return elt;
424+
}
425+
}
426+
if ((next = current.getFirstChild()) != null) {
427+
current = next;
428+
continue;
429+
}
430+
for (;;) {
431+
if (current == root) {
432+
return null;
433+
}
434+
if ((next = current.getNextSibling()) != null) {
435+
current = next;
436+
break;
437+
}
438+
current = current.getParentNode();
439+
}
440+
}
441+
}
357442
}

0 commit comments

Comments
 (0)