Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement declarative shadow DOM #568

Merged
merged 1 commit into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions html5ever/src/tree_builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,47 @@ where
}
//§ END

fn should_attach_declarative_shadow(&self, tag: &Tag) -> bool {
let adjusted_insertion_location = self.appropriate_place_for_insertion(None);

let (intended_parent, _node2) = match adjusted_insertion_location {
LastChild(ref p) | BeforeSibling(ref p) => (p.clone(), None),
TableFosterParenting {
ref element,
ref prev_element,
} => (element.clone(), Some(prev_element.clone())),
};

// 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.to_string() == String::from("open") || attr.value.to_string() == String::from("close"))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we compare attribute values without creating String objects (which requires memory allocation)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, we can avoid String creation in both sides like this:
attr.value.as_ref() == "open" || attr.value.as_ref() == "close"
I'll update with other missing parts of this feature.

});

// Check if intended_parent's document allows declarative shadow roots
let allow_declarative_shadow_roots = self.sink.allow_declarative_shadow_roots(&intended_parent);

// the adjusted current node is not the topmost element in the stack of open elements
let adjusted_current_node_not_topmost = match self.open_elems.borrow().first() {
// The stack grows downwards; the topmost node on the stack is the first one added to the stack
// The current node is the bottommost node in this stack of open elements.
//
// (1) The adjusted current node is the context element if the parser was created as part of the HTML fragment parsing algorithm
// and the stack of open elements has only one element in it (fragment case);
// (2) otherwise, the adjusted current node is the current node (the bottomost node)
//
// => adjusted current node != topmost element in the stack when the stack size > 1
Some(_) => self.open_elems.borrow().len() > 1,
None => true,
};

return is_shadow_root_mode && allow_declarative_shadow_roots && adjusted_current_node_not_topmost;
}

fn attach_declarative_shadow(&self, tag: &Tag) -> Result<(), String> {
self.sink.attach_declarative_shadow(self.open_elems.borrow().last().unwrap(), tag.attrs.clone())
}

fn create_formatting_element_for(&self, tag: Tag) -> Handle {
// FIXME: This really wants unit tests.
let mut first_match = None;
Expand Down
13 changes: 12 additions & 1 deletion html5ever/src/tree_builder/rules.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ where
}),

//§ parsing-main-inhead
// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inhead
InHead => match_token!(token {
CharacterTokens(NotSplit, text) => SplitWhitespace(text),
CharacterTokens(Whitespace, text) => self.append_text(text),
Expand Down Expand Up @@ -153,11 +154,21 @@ where
</body> </html> </br> => else,

tag @ <template> => {
self.insert_element_for(tag);
self.active_formatting.borrow_mut().push(Marker);
self.frameset_ok.set(false);
self.mode.set(InTemplate);
self.template_modes.borrow_mut().push(InTemplate);

if (self.should_attach_declarative_shadow(&tag)) {
if let Err(_) = self.attach_declarative_shadow(&tag) {
// TODO:
// insert at the adjusted insertion location
// with the result of insert a foreign element for template tag
}
} else {
self.insert_element_for(tag);
}

Done
}

Expand Down
13 changes: 13 additions & 0 deletions markup5ever/interface/tree_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,19 @@ pub trait TreeSink {
fn complete_script(&self, _node: &Self::Handle) -> NextParserState {
NextParserState::Continue
}

fn allow_declarative_shadow_roots(&self, _intended_parent: &Self::Handle) -> bool {
return true;
}

/// Attach declarative shadow
fn attach_declarative_shadow(
&self,
_location: &Self::Handle,
_attrs: Vec<Attribute>,
) -> Result<(), String> {
Err(String::from("No implementation for attach_declarative_shadow"))
}
}

/// Trace hooks for a garbage-collected DOM.
Expand Down
4 changes: 4 additions & 0 deletions markup5ever/local_names.txt
Original file line number Diff line number Diff line change
Expand Up @@ -849,6 +849,10 @@ separator
separators
set
setdiff
shadowrootclonable
shadowrootdelegatesfocus
shadowrootmode
shadowrootserializable
shape
shape-rendering
show
Expand Down
Loading