Declarative shadowrootmode keyword matching should be ASCII case-insensitive
Maintainers usually reply within 1 day
Nobody has claimed this yet.
Assessment
- Difficulty
- 1/5
- Estimated time
- Under an hour
- Newbie friendliness
- 78/100
Research direction
In html5ever/src/tree_builder/mod.rs, inspect the is_shadow_root_mode predicate around the referenced line; the issue identifies a one-line change to keyword matching. First read the enumerated-attribute matching rule linked in the issue, then run focused declarative shadow DOM tests. Done when ASCII case variants of open and closed are accepted while whitespace-padded or unknown values remain rejected and the original attribute value is unchanged.
Written by the indexing model from the issue text.
Description
Declarative shadow DOM currently accepts only lowercase open and closed in the tree builder. shadowrootmode is an enumerated attribute, so keyword matching must be ASCII case-insensitive. Whitespace-padded and unknown values must still be rejected, and the original attribute value must not be rewritten.
Sources: shadowrootmode definition, enumerated-attribute matching.
The case-sensitive predicate is present in html5ever 0.40.1 and in current main at a027c283. With a sink that permits declarative shadow roots, OPEN, OpEn, CLOSED, and cLoSeD should select their respective modes. The proposed production change is only eq_ignore_ascii_case for these two keywords.
We validated this against our pinned 0.40.1 consumer: an isolated parser harness passes 7/8 checks before and 8/8 after; native integration probes pass 11/15 before and 15/15 after. The four repaired cases are the mixed/uppercase keywords. Probes also cover invalid/whitespace values, unchanged raw attributes, closed-root attachment, and disabled declarative parsing. These are downstream runs, not a claim that the complete upstream test suite was run.
The exact downstream patch is attached inline below. SHA256: 22695fbe9ba960d48a25eb40aba287dfb6e5e78fc32a5ce280432fa223c54291. Paths are relative to the crate. Its two added tests reuse downstream test scaffolding from a separate fragment-context patch, so the test hunk is not directly applicable to current upstream main. Filing an issue with the exact patch avoids bundling that separate change into a PR for this one-line fix. The production predicate change is independent. We intend to drop our carried patch when a release includes the fix.
Exact carried .patch
--- a/src/tree_builder/mod.rs
+++ b/src/tree_builder/mod.rs
@@ -1476,7 +1476,7 @@
// template start tag's shadowrootmode is not in the none state
let is_shadow_root_mode = tag.attrs.iter().any(|attr| {
attr.name.local == local_name!("shadowrootmode")
- && (attr.value.as_ref() == "open" || attr.value.as_ref() == "closed")
+ && (attr.value.eq_ignore_ascii_case("open") || attr.value.eq_ignore_ascii_case("closed"))
});
// Check if intended_parent's document allows declarative shadow roots
--- a/src/declarative_fragment_tests.rs
+++ b/src/declarative_fragment_tests.rs
@@ -150,3 +150,23 @@
// Head insertion is still allowed through the sink; no attachment may target html.
assert!(sink.attached.borrow().iter().all(|(host, _)| sink.names.borrow().get(host).unwrap().expanded() != expanded_name!(html "html")));
}
+
+#[test]
+fn declarative_modes_accept_ascii_case_insensitive_keywords() {
+ for mode in ["open", "OPEN", "OpEn", "closed", "CLOSED", "cLoSeD"] {
+ let text = format!("<template shadowrootmode=\"{mode}\"><b>shadow</b></template>");
+ let (sink, context) = fragment(&text, true);
+ let attached = sink.attached.borrow();
+ assert_eq!(attached.len(), 1, "mode {mode:?}");
+ assert_eq!(attached[0].0, context, "original context identity for {mode:?}");
+ }
+}
+#[test]
+fn declarative_modes_reject_invalid_or_whitespace_keywords() {
+ for mode in ["", " open", "open ", "open closed", "open\t", "invalid", "openx", "closedx"] {
+ let text = format!("<template shadowrootmode=\"{mode}\"><b>shadow</b></template>");
+ let (sink, _) = fragment(&text, true);
+ assert!(sink.attached.borrow().is_empty(), "mode {mode:?}");
+ assert!(sink.names.borrow().values().any(|name| name.expanded() == expanded_name!(html "template")));
+ }
+}
- Dominant language
- Rust
- Stars
- 2.7k
- Forks
- 303
- Avg merge
- 17h 29m
- Merged PRs (30d)
- 4
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from servo/html5ever
-
Difficulty 2/5 1-3 hours Newbie friendliness 70/100
Maintainers usually reply within 1 day
-
rcdom: selectedcontent lookup reads self.data, option clone never runsPossibly taken @jdm claimed this 79 days ago. Open
Difficulty 1/5 Under an hour Newbie friendliness 90/100
servo/html5ever#776 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 4/5 3-5 days Newbie friendliness 45/100
servo/html5ever#812 · 1 comment ·
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 3/5 1-2 days Newbie friendliness 62/100
Maintainers usually reply within 1 day
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
cloudflare/workers-rs#1130 ·
Maintainers usually reply within 11 days
-
Difficulty 2/5 1-3 hours Newbie friendliness 69/100
vfarcic/dot-agent-deck#2160 · 2 comments ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
RustCrypto/utils#1597 ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
max-sixty/worktrunk#4727 · 7 comments · 1 reaction ·
Maintainers usually reply within 1 day