Declarative shadowrootmode keyword matching should be ASCII case-insensitive

Open Beginner friendly
#814 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
1/5
Estimated time
Under an hour
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
backend

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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from servo/html5ever

All issues in servo/html5ever

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.