Skip to content

Commit

Permalink
Relax selector parsing
Browse files Browse the repository at this point in the history
Neither the proposal nor the test changes are merged yet. Also, the
tests are still failing.

html5lib/html5lib-tests#178
whatwg/html#10557
  • Loading branch information
untitaker committed Oct 30, 2024
1 parent 8ca1bc6 commit 9120820
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 150 deletions.
11 changes: 1 addition & 10 deletions html5ever/src/tree_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,7 @@ where
n
}

/// Pop element until an element with the given name has been popped.
fn pop_until_named(&self, name: LocalName) -> usize {
self.pop_until(|p| *p.ns == ns!(html) && *p.local == name)
}
Expand Down Expand Up @@ -1231,16 +1232,6 @@ where
_ => continue,
};
match *name {
local_name!("select") => {
for ancestor in self.open_elems.borrow()[0..i].iter().rev() {
if self.html_elem_named(ancestor, local_name!("template")) {
return InSelect;
} else if self.html_elem_named(ancestor, local_name!("table")) {
return InSelectInTable;
}
}
return InSelect;
},
local_name!("td") | local_name!("th") => {
if !last {
return InCell;
Expand Down
166 changes: 40 additions & 126 deletions html5ever/src/tree_builder/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,13 @@ where

tag @ <hr> => {
self.close_p_element_in_button_scope();
if self.in_scope_named(default_scope, local_name!("select")) {
self.generate_implied_end_except(local_name!("optgroup"));
if self.in_scope_named(default_scope, local_name!("option")) {
self.sink.parse_error(Borrowed("hr in option"));
}
}

self.insert_and_pop_element_for(tag);
self.frameset_ok.set(false);
DoneAckSelfClosing
Expand Down Expand Up @@ -662,28 +669,50 @@ where
// <noscript> handled in wildcard case below

tag @ <select> => {
if self.in_scope_named(default_scope, local_name!("select")) {
self.sink.parse_error(Borrowed("nested select"));
self.pop_until_named(local_name!("select"));
}

self.reconstruct_formatting();
self.insert_element_for(tag);
self.frameset_ok.set(false);
// NB: mode == InBody but possibly self.mode != mode, if
// we're processing "as in the rules for InBody".
self.mode.set(match self.mode.get() {
InTable | InCaption | InTableBody
| InRow | InCell => InSelectInTable,
_ => InSelect,
});
Done
}

tag @ <optgroup> <option> => {
if self.current_node_named(local_name!("option")) {
self.pop();
tag @ <option> => {
if self.in_scope_named(default_scope, local_name!("select")) {
self.generate_implied_end_except(local_name!("optgroup"));
if self.in_scope_named(default_scope, local_name!("option")) {
self.sink.parse_error(Borrowed("nested options"));
}
} else {
if self.current_node_named(local_name!("option")) {
self.pop();
}
self.reconstruct_formatting();
}
self.reconstruct_formatting();

self.insert_element_for(tag);
Done
}

tag @ <optgroup> => {
if self.in_scope_named(default_scope, local_name!("select")) {
self.generate_implied_end(cursory_implied_end);
// XXX: perf
if self.in_scope_named(default_scope, local_name!("option")) || self.in_scope_named(default_scope, local_name!("optgroup")) {
self.sink.parse_error(Borrowed("nested options"));
}
} else {
if self.current_node_named(local_name!("option")) {
self.pop();
}
self.reconstruct_formatting();
}
Done
}

tag @ <rb> <rtc> => {
if self.in_scope_named(default_scope, local_name!("ruby")) {
self.generate_implied_end(cursory_implied_end);
Expand Down Expand Up @@ -1100,121 +1129,6 @@ where
token => self.step(InBody, token),
}),

//§ parsing-main-inselect
InSelect => match_token!(token {
NullCharacterToken => self.unexpected(&token),
CharacterTokens(_, text) => self.append_text(text),
CommentToken(text) => self.append_comment(text),

<html> => self.step(InBody, token),

tag @ <option> => {
if self.current_node_named(local_name!("option")) {
self.pop();
}
self.insert_element_for(tag);
Done
}

tag @ <optgroup> => {
if self.current_node_named(local_name!("option")) {
self.pop();
}
if self.current_node_named(local_name!("optgroup")) {
self.pop();
}
self.insert_element_for(tag);
Done
}

tag @ <hr> => {
if self.current_node_named(local_name!("option")) {
self.pop();
}
if self.current_node_named(local_name!("optgroup")) {
self.pop();
}
self.insert_element_for(tag);
self.pop();
DoneAckSelfClosing
}

</optgroup> => {
if self.open_elems.borrow().len() >= 2
&& self.current_node_named(local_name!("option"))
&& self.html_elem_named(&self.open_elems.borrow()[self.open_elems.borrow().len() - 2],
local_name!("optgroup")) {
self.pop();
}
if self.current_node_named(local_name!("optgroup")) {
self.pop();
} else {
self.unexpected(&token);
}
Done
}

</option> => {
if self.current_node_named(local_name!("option")) {
self.pop();
} else {
self.unexpected(&token);
}
Done
}

tag @ <select> </select> => {
let in_scope = self.in_scope_named(select_scope, local_name!("select"));

if !in_scope || tag.kind == StartTag {
self.unexpected(&tag);
}

if in_scope {
self.pop_until_named(local_name!("select"));
self.mode.set(self.reset_insertion_mode());
}
Done
}

<input> <keygen> <textarea> => {
self.unexpected(&token);
if self.in_scope_named(select_scope, local_name!("select")) {
self.pop_until_named(local_name!("select"));
Reprocess(self.reset_insertion_mode(), token)
} else {
Done
}
}

<script> <template> </template> => self.step(InHead, token),

EOFToken => self.step(InBody, token),

token => self.unexpected(&token),
}),

//§ parsing-main-inselectintable
InSelectInTable => match_token!(token {
<caption> <table> <tbody> <tfoot> <thead> <tr> <td> <th> => {
self.unexpected(&token);
self.pop_until_named(local_name!("select"));
Reprocess(self.reset_insertion_mode(), token)
}

tag @ </caption> </table> </tbody> </tfoot> </thead> </tr> </td> </th> => {
self.unexpected(&tag);
if self.in_scope_named(table_scope, tag.name.clone()) {
self.pop_until_named(local_name!("select"));
Reprocess(self.reset_insertion_mode(), TagToken(tag))
} else {
Done
}
}

token => self.step(InSelect, token),
}),

//§ parsing-main-intemplate
InTemplate => match_token!(token {
CharacterTokens(_, _) => self.step(InBody, token),
Expand Down
7 changes: 1 addition & 6 deletions html5ever/src/tree_builder/tag_sets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,9 @@ macro_rules! declare_tag_set (
pub(crate) fn empty_set(_: ExpandedName) -> bool {
false
}
#[inline(always)]
pub(crate) fn full_set(_: ExpandedName) -> bool {
true
}

declare_tag_set!(pub html_default_scope =
"applet" "caption" "html" "table" "td" "th" "marquee" "object" "template");
"applet" "caption" "html" "table" "td" "th" "marquee" "object" "select" "template");

#[inline(always)]
pub(crate) fn default_scope(name: ExpandedName) -> bool {
Expand All @@ -66,7 +62,6 @@ pub(crate) fn default_scope(name: ExpandedName) -> bool {
declare_tag_set!(pub list_item_scope = [default_scope] + "ol" "ul");
declare_tag_set!(pub button_scope = [default_scope] + "button");
declare_tag_set!(pub table_scope = "html" "table" "template");
declare_tag_set!(pub select_scope = [full_set] - "optgroup" "option");

declare_tag_set!(pub table_body_context = "tbody" "tfoot" "thead" "template" "html");
declare_tag_set!(pub table_row_context = "tr" "template" "html");
Expand Down
2 changes: 0 additions & 2 deletions html5ever/src/tree_builder/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ pub(crate) enum InsertionMode {
InTableBody,
InRow,
InCell,
InSelect,
InSelectInTable,
InTemplate,
AfterBody,
InFrameset,
Expand Down
9 changes: 4 additions & 5 deletions rcdom/tests/html-tree-builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,14 +189,13 @@ fn make_test_desc_with_scripting_flag(
fields: &HashMap<String, String>,
scripting_enabled: bool,
) -> Test {
let get_field = |key| {
let field = fields.get(key).expect("missing field");
field.trim_end_matches('\n').to_string()
let expect_field = |key| {
fields.get(key).unwrap_or_else(|| panic!("missing field {}, testcase: {:?}", key, fields)).to_string()
};

let mut data = fields.get("data").expect("missing data").to_string();
let mut data = expect_field("data");
data.pop();
let expected = get_field("document");
let expected = expect_field("document").trim_end_matches('\n').to_string();
let context = fields
.get("document-fragment")
.map(|field| context_name(field.trim_end_matches('\n')));
Expand Down

0 comments on commit 9120820

Please sign in to comment.