Skip to content

Commit

Permalink
Relax <select> parser
Browse files Browse the repository at this point in the history
This patch makes the parser allow additional tags in <select> besides
<option>, <optgroup>, and <hr>, mostly by removing the "in select" and
"in select in table" parser modes.

In order to replicate the behavior where opening a <select> tag within
another open <select> tag inserts a </select> close tag, a traversal
through the stack of open elements was added which I borrowed from the
<button> part of the parser.

This will need test changes to be implemented in html5lib.

Fixes whatwg#10310
  • Loading branch information
josepharhar committed Jul 17, 2024
1 parent baeea82 commit 33cebbe
Showing 1 changed file with 54 additions and 285 deletions.
339 changes: 54 additions & 285 deletions source
Original file line number Diff line number Diff line change
Expand Up @@ -3213,6 +3213,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
<dfn data-x-href="https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-descendant">shadow-including inclusive descendant</dfn>, and
<dfn data-x-href="https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor">shadow-including inclusive ancestor</dfn> concepts</li>
<li>The <dfn data-x-href="https://dom.spec.whatwg.org/#concept-tree-first-child">first child</dfn>,
<dfn data-x-href="https://dom.spec.whatwg.org/#concept-tree-last-child">last child</dfn>,
<dfn data-x-href="https://dom.spec.whatwg.org/#concept-tree-next-sibling">next sibling</dfn>,
<dfn data-x-href="https://dom.spec.whatwg.org/#concept-tree-previous-sibling">previous sibling</dfn>, and
<dfn data-x-href="https://dom.spec.whatwg.org/#concept-tree-parent">parent</dfn> concepts</li>
Expand Down Expand Up @@ -53298,10 +53299,33 @@ interface <dfn interface>HTMLSelectElement</dfn> : <span>HTMLElement</span> {

</div>

<p>The <dfn data-x="concept-select-option-list">list of options</dfn> for a <code>select</code>
element consists of all the <code>option</code> element children of the <code>select</code>
element, and all the <code>option</code> element children of all the <code>optgroup</code> element
children of the <code>select</code> element, in <span>tree order</span>.</p>
<p>To get the <dfn data-x="concept-select-option-list">list of options</dfn> given a
<code>select</code> element <var>select</var>:</p>

<ol>
<li><p>Let <var>options</var> be « ».</p></li>

<li>
<p><span data-x="list iterate">For each</span> <var>node</var> of <var>select</var>'s
<span data-x="descendant">descendants</span> in <span>tree order</span>:</p>

<ol>
<li><p>If <var>node</var> is an <code>option</code> element, then <span
data-x="list append">append</span> <var>node</var> to <var>options</var>.</p></li>

<!-- The below step is intended to make the traversal skip the descendants of node so that we
don't collect nested options or options inside another nested select. If this is not clear and
there is no other way to succinctly describe this, then I will likely have to reimplement a
preorder tree traversal here. -->

<li><p>If <var>node</var> is an <code>option</code> or <code>select</code> element, and
<var>node</var>'s <span>last child</span> is not null, then set <var>node</var> to
<var>node</var>'s <span>last child</span>.</p></li>
</ol>
</li>

<li><p>Return <var>options</var>.</p></li>
</ol>

<p>The <dfn element-attr for="select"><code data-x="attr-select-required">required</code></dfn>
attribute is a <span>boolean attribute</span>. When specified, the user will be required to select
Expand Down Expand Up @@ -124295,25 +124319,22 @@ dictionary <dfn dictionary>StorageEventInit</dfn> : <span>EventInit</span> {
caption">in caption</span>", "<span data-x="insertion mode: in column group">in column
group</span>", "<span data-x="insertion mode: in table body">in table body</span>", "<span
data-x="insertion mode: in row">in row</span>", "<span data-x="insertion mode: in cell">in
cell</span>", "<span data-x="insertion mode: in select">in select</span>", "<span data-x="insertion
mode: in select in table">in select in table</span>", "<span data-x="insertion mode: in
template">in template</span>", "<span data-x="insertion mode: after body">after body</span>",
"<span data-x="insertion mode: in frameset">in frameset</span>", "<span data-x="insertion mode:
after frameset">after frameset</span>", "<span data-x="insertion mode: after after body">after
after body</span>", and "<span data-x="insertion mode: after after frameset">after after
frameset</span>" during the course of the parsing, as described in the <span>tree
construction</span> stage. The insertion mode affects how tokens are processed and whether CDATA
sections are supported.</p>
cell</span>", "<span data-x="insertion mode: in template">in template</span>", "<span
data-x="insertion mode: after body">after body</span>", "<span data-x="insertion mode: in
frameset">in frameset</span>", "<span data-x="insertion mode: after frameset">after
frameset</span>", "<span data-x="insertion mode: after after body">after after body</span>", and
"<span data-x="insertion mode: after after frameset">after after frameset</span>" during the
course of the parsing, as described in the <span>tree construction</span> stage. The insertion
mode affects how tokens are processed and whether CDATA sections are supported.</p>

<p>Several of these modes, namely "<span data-x="insertion mode: in head">in head</span>", "<span
data-x="insertion mode: in body">in body</span>", "<span data-x="insertion mode: in table">in
table</span>", and "<span data-x="insertion mode: in select">in select</span>", are special, in
that the other modes defer to them at various times. When the algorithm below says that the user
agent is to do something "<dfn>using the rules for</dfn> the <var>m</var> insertion
mode", where <var>m</var> is one of these modes, the user agent must use the rules
described under the <var>m</var> <span>insertion mode</span>'s section, but must leave
the <span>insertion mode</span> unchanged unless the rules in <var>m</var> themselves
switch the <span>insertion mode</span> to a new value.</p>
data-x="insertion mode: in body">in body</span>", and "<span data-x="insertion mode: in table">in
table</span>", are special, in that the other modes defer to them at various times. When the
algorithm below says that the user agent is to do something "<dfn>using the rules for</dfn> the
<var>m</var> insertion mode", where <var>m</var> is one of these modes, the user agent must use
the rules described under the <var>m</var> <span>insertion mode</span>'s section, but must leave
the <span>insertion mode</span> unchanged unless the rules in <var>m</var> themselves switch the
<span>insertion mode</span> to a new value.</p>

<p>When the insertion mode is switched to "<span data-x="insertion mode: text">text</span>" or
"<span data-x="insertion mode: in table text">in table text</span>", the <dfn>original insertion
Expand Down Expand Up @@ -124344,37 +124365,6 @@ dictionary <dfn dictionary>StorageEventInit</dfn> : <span>EventInit</span> {
parsing algorithm</span> (<span>fragment case</span>), set <var>node</var> to the <var
data-x="concept-frag-parse-context">context</var> element passed to that algorithm.</p></li>

<li>
<p>If <var>node</var> is a <code>select</code> element, run these substeps:</p>

<ol>
<li><p>If <var>last</var> is true, jump to the step below labeled
<i>done</i>.</p></li>

<li><p>Let <var>ancestor</var> be <var>node</var>.</p></li>

<li><p><i>Loop</i>: If <var>ancestor</var> is the first node in the <span>stack of
open elements</span>, jump to the step below labeled <i>done</i>.</p></li>

<li><p>Let <var>ancestor</var> be the node before <var>ancestor</var> in the
<span>stack of open elements</span>.</p></li>

<li><p>If <var>ancestor</var> is a <code>template</code> node, jump to the step below
labeled <i>done</i>.</p></li>

<li><p>If <var>ancestor</var> is a <code>table</code> node, switch the <span>insertion
mode</span> to "<span data-x="insertion mode: in select in table">in select in table</span>"
and return.</p></li> <!-- consider
<table><tr><td><select><template></template><caption></table>
https://software.hixie.ch/utilities/js/live-dom-viewer/?saved=2374 -->

<li><p>Jump back to the step labeled <i>loop</i>.</p></li>

<li><p><i>Done</i>: Switch the <span>insertion mode</span> to "<span data-x="insertion mode: in
select">in select</span>" and return.</p></li>
</ol>
</li>

<li><p>If <var>node</var> is a <code>td</code> or <code>th</code> element and <var>last</var> is
false, then switch the <span>insertion mode</span> to "<span data-x="insertion mode: in cell">in
cell</span>" and return.</p></li>
Expand Down Expand Up @@ -129334,19 +129324,23 @@ document.body.appendChild(text);

<dt>A start tag whose tag name is "select"</dt>
<dd>
<p>If the <span>stack of open elements</span> <span data-x="has an element in scope">has a
<code>select</code> element in scope</span>, then run these substeps:</p>

<ol>
<li><p><span>Parse error</span>.</p></li>

<li><p><span>Generate implied end tags</span>.</p></li>

<li><p>Pop elements from the <span>stack of open elements</span> until a <code>select</code>
elementhas been popped from the stack.</p></li>
</ol>

<p><span>Reconstruct the active formatting elements</span>, if any.</p>

<p><span>Insert an HTML element</span> for the token.</p>

<p>Set the <span>frameset-ok flag</span> to "not ok".</p>

<p>If the <span>insertion mode</span> is one of "<span data-x="insertion mode: in table">in
table</span>", "<span data-x="insertion mode: in caption">in caption</span>", "<span
data-x="insertion mode: in table body">in table body</span>", "<span data-x="insertion mode: in
row">in row</span>", or "<span data-x="insertion mode: in cell">in cell</span>", then switch the
<span>insertion mode</span> to "<span data-x="insertion mode: in select in table">in select in
table</span>". Otherwise, switch the <span>insertion mode</span> to "<span data-x="insertion
mode: in select">in select</span>".</p>
</dd>

<dt>A start tag whose tag name is one of: "optgroup", "option"</dt>
Expand Down Expand Up @@ -130480,231 +130474,6 @@ document.body.appendChild(text);
same time, nor can it have neither when the <span>close the cell</span> algorithm is invoked.</p>


<h6 id="parsing-main-inselect">The "<dfn data-x="insertion mode: in select">in select</dfn>" insertion mode</h6>

<p>When the user agent is to apply the rules for the "<span data-x="insertion mode: in select">in
select</span>" <span>insertion mode</span>, the user agent must handle the token as follows:</p>

<dl class="switch">

<dt>A character token that is U+0000 NULL</dt>
<dd>
<p><span>Parse error</span>. Ignore the token.</p>
</dd>

<dt>Any other character token</dt>
<dd>
<p><span data-x="insert a character">Insert the token's character</span>.</p>
</dd>

<dt>A comment token</dt>
<dd>
<p><span>Insert a comment</span>.</p>
</dd>

<dt>A DOCTYPE token</dt>
<dd>
<p><span>Parse error</span>. Ignore the token.</p>
</dd>

<dt>A start tag whose tag name is "html"</dt>
<dd>
<p>Process the token <span>using the rules for</span> the "<span data-x="insertion mode: in
body">in body</span>" <span>insertion mode</span>.</p>
</dd>

<dt>A start tag whose tag name is "option"</dt>
<dd>
<!-- fake </option> (maybe) -->
<p>If the <span>current node</span> is an <code>option</code> element, pop that node from the
<span>stack of open elements</span>.</p>
<!-- end of fake </option> -->

<p><span>Insert an HTML element</span> for the token.</p>
</dd>

<dt>A start tag whose tag name is "optgroup"</dt>
<dd>
<!-- fake </option> (maybe) -->
<p>If the <span>current node</span> is an <code>option</code> element, pop that node from the
<span>stack of open elements</span>.</p>
<!-- end of fake </option> -->

<!-- fake </optgroup> (maybe) -->
<p>If the <span>current node</span> is an <code>optgroup</code> element, pop that node from the
<span>stack of open elements</span>.</p>
<!-- end of fake </optgroup> -->

<p><span>Insert an HTML element</span> for the token.</p>
</dd>

<dt>A start tag whose tag name is "hr"</dt>
<dd>
<!-- fake </option> (maybe) -->
<p>If the <span>current node</span> is an <code>option</code> element, pop that node from the
<span>stack of open elements</span>.</p>
<!-- end of fake </option> -->

<!-- fake </optgroup> (maybe) -->
<p>If the <span>current node</span> is an <code>optgroup</code> element, pop that node from the
<span>stack of open elements</span>.</p>
<!-- end of fake </optgroup> -->

<p><span>Insert an HTML element</span> for the token. Immediately pop the <span>current
node</span> off the <span>stack of open elements</span>.</p>

<p><span data-x="acknowledge self-closing flag">Acknowledge the token's <i data-x="self-closing
flag">self-closing flag</i></span>, if it is set.</p>
</dd>

<dt>An end tag whose tag name is "optgroup"</dt>
<dd>
<!-- fake </option> (maybe) -->
<p>First, if the <span>current node</span> is an <code>option</code> element, and the node
immediately before it in the <span>stack of open elements</span> is an <code>optgroup</code>
element, then pop the <span>current node</span> from the <span>stack of open
elements</span>.</p>
<!-- end of fake </option> -->

<p>If the <span>current node</span> is an <code>optgroup</code> element, then pop that node from
the <span>stack of open elements</span>. Otherwise, this is a <span>parse error</span>; ignore
the token.</p>
</dd>

<dt>An end tag whose tag name is "option"</dt>
<dd>
<p>If the <span>current node</span> is an <code>option</code> element, then pop that node from
the <span>stack of open elements</span>. Otherwise, this is a <span>parse error</span>; ignore
the token.</p>
</dd>

<dt>An end tag whose tag name is "select"</dt>
<dd>
<p>If the <span>stack of open elements</span> does not <span data-x="has an element in select
scope">have a <code>select</code> element in select scope</span>, this is a <span>parse
error</span>; ignore the token. (<span>fragment case</span>)</p>

<p>Otherwise:</p>

<p>Pop elements from the <span>stack of open elements</span> until a <code>select</code> element
has been popped from the stack.</p>

<p><span>Reset the insertion mode appropriately</span>.</p>
</dd>

<dt>A start tag whose tag name is "select"</dt>
<dd>
<p><span>Parse error</span>.</p>

<!-- fake </select> -->
<p>If the <span>stack of open elements</span> does not <span data-x="has an element in select
scope">have a <code>select</code> element in select scope</span>, ignore the token.
(<span>fragment case</span>)</p>

<p>Otherwise:</p>

<p>Pop elements from the <span>stack of open elements</span> until a <code>select</code> element
has been popped from the stack.</p>

<p><span>Reset the insertion mode appropriately</span>.</p>
<!-- end of fake </select> -->

<p class="note">It just gets treated like an end tag.</p>
</dd>

<dt>A start tag whose tag name is one of: "input", "keygen", "textarea"</dt>
<dd>
<p><span>Parse error</span>.</p>

<!-- fake </select> -->
<p>If the <span>stack of open elements</span> does not <span data-x="has an element in select
scope">have a <code>select</code> element in select scope</span>, ignore the token.
(<span>fragment case</span>)</p>

<p>Otherwise:</p>

<p>Pop elements from the <span>stack of open elements</span> until a <code>select</code> element
has been popped from the stack.</p>

<p><span>Reset the insertion mode appropriately</span>.</p>
<!-- end of fake </select> -->

<p>Reprocess the token.</p>
</dd>

<dt>A start tag whose tag name is one of: "script", "template"</dt>
<dt>An end tag whose tag name is "template"</dt>
<dd>
<p>Process the token <span>using the rules for</span> the "<span data-x="insertion mode: in
head">in head</span>" <span>insertion mode</span>.</p>
</dd>

<dt>An end-of-file token</dt>
<dd>
<p>Process the token <span>using the rules for</span> the "<span data-x="insertion mode: in
body">in body</span>" <span>insertion mode</span>.</p>
</dd>

<dt>Anything else</dt>
<dd>
<p><span>Parse error</span>. Ignore the token.</p>
</dd>
</dl>


<h6 id="parsing-main-inselectintable">The "<dfn data-x="insertion mode: in select in table">in select in table</dfn>" insertion mode</h6>

<p>When the user agent is to apply the rules for the "<span data-x="insertion mode: in select in
table">in select in table</span>" <span>insertion mode</span>, the user agent must handle the
token as follows:</p>

<dl class="switch">

<dt>A start tag whose tag name is one of: "caption", "table", "tbody", "tfoot", "thead", "tr",
"td", "th"</dt>
<dd>
<p><span>Parse error</span>.</p>

<!-- fake </select> -->
<p>Pop elements from the <span>stack of open elements</span> until a <code>select</code> element
has been popped from the stack.</p>

<p><span>Reset the insertion mode appropriately</span>.</p>
<!-- end of fake </select> -->

<p>Reprocess the token.</p>
</dd>

<dt>An end tag whose tag name is one of: "caption", "table", "tbody", "tfoot", "thead", "tr",
"td", "th"</dt>
<dd>
<p><span>Parse error</span>.</p>

<p>If the <span>stack of open elements</span> does not <span data-x="has an element in table
scope">have an element in table scope</span> that is an <span data-x="HTML elements">HTML
element</span> with the same tag name as that of the token, then ignore the token.</p>

<p>Otherwise:</p>

<!-- fake </select> -->
<p>Pop elements from the <span>stack of open elements</span> until a <code>select</code> element
has been popped from the stack.</p>

<p><span>Reset the insertion mode appropriately</span>.</p>
<!-- end of fake </select> -->

<p>Reprocess the token.</p>
</dd>

<dt>Anything else</dt>
<dd>
<p>Process the token <span>using the rules for</span> the "<span data-x="insertion mode: in
select">in select</span>" <span>insertion mode</span>.</p>
</dd>
</dl>



<h6 id="parsing-main-intemplate">The "<dfn data-x="insertion mode: in template">in template</dfn>" insertion mode</h6>

<p>When the user agent is to apply the rules for the "<span data-x="insertion mode: in template">in
Expand Down

0 comments on commit 33cebbe

Please sign in to comment.