Implement declarative shadow dom (#34964)

* Implement declarative shadow dom

Signed-off-by: batu_hoang <longvatrong111@gmail.com>

* Set allowDeclarativeShadowRoots false for innerHTML

Signed-off-by: batu_hoang <longvatrong111@gmail.com>

* Enable allowDeclarativeShadowRoots for Document

Signed-off-by: batu_hoang <longvatrong111@gmail.com>

* Expose HTMLTemplateElement to js

Signed-off-by: batu_hoang <longvatrong111@gmail.com>

* Implemenet setHTMLUnsafe and add more test cases

Signed-off-by: batu_hoang <longvatrong111@gmail.com>

* Declarative shadow dom: minor updates and expected test result update

Signed-off-by: batu_hoang <longvatrong111@gmail.com>

* Shadow-dom: add more test cases

Signed-off-by: batu_hoang <longvatrong111@gmail.com>

* Update comments according to the spec

Signed-off-by: batu_hoang <longvatrong111@gmail.com>

* Bump html5ever version

Signed-off-by: batu_hoang <longvatrong111@gmail.com>

---------

Signed-off-by: batu_hoang <longvatrong111@gmail.com>
This commit is contained in:
batu_hoang 2025-03-17 17:41:34 +08:00 committed by GitHub
parent f483a3d34b
commit 28c8c1df0c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 360 additions and 1965 deletions

View file

@ -513,6 +513,8 @@ pub(crate) struct Document {
status_code: Option<u16>,
/// <https://html.spec.whatwg.org/multipage/#is-initial-about:blank>
is_initial_about_blank: Cell<bool>,
/// <https://dom.spec.whatwg.org/#document-allow-declarative-shadow-roots>
allow_declarative_shadow_roots: Cell<bool>,
/// <https://w3c.github.io/webappsec-upgrade-insecure-requests/#insecure-requests-policy>
#[no_trace]
inherited_insecure_requests_policy: Cell<Option<InsecureRequestsPolicy>>,
@ -3590,6 +3592,7 @@ impl Document {
status_code: Option<u16>,
canceller: FetchCanceller,
is_initial_about_blank: bool,
allow_declarative_shadow_roots: bool,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
) -> Document {
let url = url.unwrap_or_else(|| ServoUrl::parse("about:blank").unwrap());
@ -3740,6 +3743,7 @@ impl Document {
visibility_state: Cell::new(DocumentVisibilityState::Hidden),
status_code,
is_initial_about_blank: Cell::new(is_initial_about_blank),
allow_declarative_shadow_roots: Cell::new(allow_declarative_shadow_roots),
inherited_insecure_requests_policy: Cell::new(inherited_insecure_requests_policy),
intersection_observer_task_queued: Cell::new(false),
}
@ -3874,6 +3878,7 @@ impl Document {
status_code: Option<u16>,
canceller: FetchCanceller,
is_initial_about_blank: bool,
allow_declarative_shadow_roots: bool,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
can_gc: CanGc,
) -> DomRoot<Document> {
@ -3893,6 +3898,7 @@ impl Document {
status_code,
canceller,
is_initial_about_blank,
allow_declarative_shadow_roots,
inherited_insecure_requests_policy,
can_gc,
)
@ -3915,6 +3921,7 @@ impl Document {
status_code: Option<u16>,
canceller: FetchCanceller,
is_initial_about_blank: bool,
allow_declarative_shadow_roots: bool,
inherited_insecure_requests_policy: Option<InsecureRequestsPolicy>,
can_gc: CanGc,
) -> DomRoot<Document> {
@ -3934,6 +3941,7 @@ impl Document {
status_code,
canceller,
is_initial_about_blank,
allow_declarative_shadow_roots,
inherited_insecure_requests_policy,
)),
window,
@ -4066,6 +4074,7 @@ impl Document {
None,
Default::default(),
false,
self.allow_declarative_shadow_roots(),
Some(self.insecure_requests_policy()),
can_gc,
);
@ -4598,6 +4607,15 @@ impl Document {
pub(crate) fn is_initial_about_blank(&self) -> bool {
self.is_initial_about_blank.get()
}
/// <https://dom.spec.whatwg.org/#document-allow-declarative-shadow-roots>
pub fn allow_declarative_shadow_roots(&self) -> bool {
self.allow_declarative_shadow_roots.get()
}
pub fn set_allow_declarative_shadow_roots(&self, value: bool) {
self.allow_declarative_shadow_roots.set(value)
}
}
impl ProfilerMetadataFactory for Document {
@ -4636,6 +4654,7 @@ impl DocumentMethods<crate::DomTypeHolder> for Document {
None,
Default::default(),
false,
doc.allow_declarative_shadow_roots(),
Some(doc.insecure_requests_policy()),
can_gc,
))

View file

@ -174,6 +174,7 @@ impl DOMImplementationMethods<crate::DomTypeHolder> for DOMImplementation {
None,
Default::default(),
false,
self.document.allow_declarative_shadow_roots(),
Some(self.document.insecure_requests_policy()),
can_gc,
);

View file

@ -88,6 +88,7 @@ impl DOMParserMethods<crate::DomTypeHolder> for DOMParser {
None,
Default::default(),
false,
false,
Some(doc.insecure_requests_policy()),
can_gc,
);
@ -111,6 +112,7 @@ impl DOMParserMethods<crate::DomTypeHolder> for DOMParser {
None,
Default::default(),
false,
false,
Some(doc.insecure_requests_policy()),
can_gc,
);

View file

@ -2076,7 +2076,7 @@ impl Element {
) -> Fallible<DomRoot<DocumentFragment>> {
// Steps 1-2.
// TODO(#11995): XML case.
let new_children = ServoParser::parse_html_fragment(self, markup, can_gc);
let new_children = ServoParser::parse_html_fragment(self, markup, false, can_gc);
// Step 3.
// See https://github.com/w3c/DOM-Parsing/issues/61.
let context_document = {
@ -2857,6 +2857,39 @@ impl ElementMethods<crate::DomTypeHolder> for Element {
self.client_rect(can_gc).size.height
}
/// <https://html.spec.whatwg.org/multipage/#dom-element-sethtmlunsafe>
fn SetHTMLUnsafe(&self, html: DOMString, can_gc: CanGc) {
// Step 2. Let target be this's template contents if this is a template element; otherwise this.
let target = if let Some(template) = self.downcast::<HTMLTemplateElement>() {
DomRoot::upcast(template.Content(can_gc))
} else {
DomRoot::from_ref(self.upcast())
};
// Step 3. Unsafely set HTML given target, this, and compliantHTML.
// Let newChildren be the result of the HTML fragment parsing algorithm.
let new_children = ServoParser::parse_html_fragment(self, html, true, can_gc);
let context_document = {
if let Some(template) = self.downcast::<HTMLTemplateElement>() {
template.Content(can_gc).upcast::<Node>().owner_doc()
} else {
self.owner_document()
}
};
// Let fragment be a new DocumentFragment whose node document is contextElement's node document.
let frag = DocumentFragment::new(&context_document, can_gc);
// For each node in newChildren, append node to fragment.
for child in new_children {
frag.upcast::<Node>().AppendChild(&child).unwrap();
}
// Replace all with fragment within target.
Node::replace_all(Some(frag.upcast()), &target);
}
/// <https://html.spec.whatwg.org/multipage/#dom-element-innerhtml>
fn GetInnerHTML(&self) -> Fallible<DOMString> {
let qname = QualName::new(

View file

@ -3,7 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use html5ever::{LocalName, Prefix, namespace_url};
use js::rust::HandleObject;
use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
@ -11,6 +11,7 @@ use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTem
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::{DomRoot, MutNullableDom};
use crate::dom::bindings::str::DOMString;
use crate::dom::document::Document;
use crate::dom::documentfragment::DocumentFragment;
use crate::dom::htmlelement::HTMLElement;
@ -58,9 +59,44 @@ impl HTMLTemplateElement {
n.upcast::<Node>().set_weird_parser_insertion_mode();
n
}
pub(crate) fn set_contents(&self, document_fragment: Option<&DocumentFragment>) {
self.contents.set(document_fragment);
}
}
#[allow(unused_doc_comments)]
impl HTMLTemplateElementMethods<crate::DomTypeHolder> for HTMLTemplateElement {
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootmode>
make_enumerated_getter!(
ShadowRootMode,
"shadowrootmode",
"open" | "closed",
missing => "",
invalid => ""
);
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootmode>
make_atomic_setter!(SetShadowRootMode, "shadowrootmode");
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootdelegatesfocus>
make_bool_getter!(ShadowRootDelegatesFocus, "shadowrootdelegatesfocus");
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootdelegatesfocus>
make_bool_setter!(SetShadowRootDelegatesFocus, "shadowrootdelegatesfocus");
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootclonable>
make_bool_getter!(ShadowRootClonable, "shadowrootclonable");
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootclonable>
make_bool_setter!(SetShadowRootClonable, "shadowrootclonable");
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootserializable>
make_bool_getter!(ShadowRootSerializable, "shadowrootserializable");
/// <https://html.spec.whatwg.org/multipage/#dom-template-shadowrootserializable>
make_bool_setter!(SetShadowRootSerializable, "shadowrootserializable");
/// <https://html.spec.whatwg.org/multipage/#dom-template-content>
fn Content(&self, can_gc: CanGc) -> DomRoot<DocumentFragment> {
self.contents.or_init(|| {

View file

@ -2637,6 +2637,7 @@ impl Node {
document.status_code(),
Default::default(),
false,
document.allow_declarative_shadow_roots(),
Some(document.insecure_requests_policy()),
can_gc,
);

View file

@ -44,6 +44,9 @@ use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
use crate::dom::bindings::codegen::Bindings::HTMLImageElementBinding::HTMLImageElementMethods;
use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{
ShadowRootMode, SlotAssignmentMode,
};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
@ -53,6 +56,7 @@ use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::characterdata::CharacterData;
use crate::dom::comment::Comment;
use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLDocument};
use crate::dom::documentfragment::DocumentFragment;
use crate::dom::documenttype::DocumentType;
use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator};
use crate::dom::htmlformelement::{FormControlElementHelpers, HTMLFormElement};
@ -64,6 +68,7 @@ use crate::dom::node::{Node, ShadowIncluding};
use crate::dom::performanceentry::PerformanceEntry;
use crate::dom::performancenavigationtiming::PerformanceNavigationTiming;
use crate::dom::processinginstruction::ProcessingInstruction;
use crate::dom::shadowroot::IsUserAgentWidget;
use crate::dom::text::Text;
use crate::dom::virtualmethods::vtable_for;
use crate::network_listener::PreInvoke;
@ -191,6 +196,7 @@ impl ServoParser {
pub(crate) fn parse_html_fragment(
context: &Element,
input: DOMString,
allow_declarative_shadow_roots: bool,
can_gc: CanGc,
) -> impl Iterator<Item = DomRoot<Node>> + use<'_> {
let context_node = context.upcast::<Node>();
@ -218,6 +224,7 @@ impl ServoParser {
None,
Default::default(),
false,
allow_declarative_shadow_roots,
Some(context_document.insecure_requests_policy()),
can_gc,
);
@ -1184,18 +1191,23 @@ impl TreeSink for Sink {
&self,
name: QualName,
attrs: Vec<Attribute>,
_flags: ElementFlags,
flags: ElementFlags,
) -> Dom<Node> {
let attrs = attrs
.into_iter()
.map(|attr| ElementAttribute::new(attr.name, DOMString::from(String::from(attr.value))))
.collect();
let parsing_algorithm = if flags.template {
ParsingAlgorithm::Fragment
} else {
self.parsing_algorithm
};
let element = create_element_for_token(
name,
attrs,
&self.document,
ElementCreator::ParserCreated(self.current_line.get()),
self.parsing_algorithm,
parsing_algorithm,
CanGc::note(),
);
Dom::from_ref(element.upcast())
@ -1381,6 +1393,91 @@ impl TreeSink for Sink {
let node = DomRoot::from_ref(&**node);
vtable_for(&node).pop();
}
fn allow_declarative_shadow_roots(&self, intended_parent: &Dom<Node>) -> bool {
intended_parent.owner_doc().allow_declarative_shadow_roots()
}
/// <https://html.spec.whatwg.org/multipage/#parsing-main-inhead>
/// A start tag whose tag name is "template"
/// Attach shadow path
fn attach_declarative_shadow(
&self,
host: &Dom<Node>,
template: &Dom<Node>,
attrs: Vec<Attribute>,
) -> Result<(), String> {
let host_element = host.downcast::<Element>().unwrap();
if host_element.shadow_root().is_some() {
return Err(String::from("Already in a shadow host"));
}
let template_element = template.downcast::<HTMLTemplateElement>().unwrap();
// Step 3. Let mode be template start tag's shadowrootmode attribute's value.
// Step 4. Let clonable be true if template start tag has a shadowrootclonable attribute; otherwise false.
// Step 5. Let delegatesfocus be true if template start tag
// has a shadowrootdelegatesfocus attribute; otherwise false.
// Step 6. Let serializable be true if template start tag
// has a shadowrootserializable attribute; otherwise false.
let mut shadow_root_mode = ShadowRootMode::Open;
let mut clonable = false;
let mut _delegatesfocus = false;
let mut _serializable = false;
let attrs: Vec<ElementAttribute> = attrs
.clone()
.into_iter()
.map(|attr| ElementAttribute::new(attr.name, DOMString::from(String::from(attr.value))))
.collect();
attrs
.iter()
.for_each(|attr: &ElementAttribute| match attr.name.local {
local_name!("shadowrootmode") => {
if attr.value.str().eq_ignore_ascii_case("open") {
shadow_root_mode = ShadowRootMode::Open;
} else if attr.value.str().eq_ignore_ascii_case("closed") {
shadow_root_mode = ShadowRootMode::Closed;
} else {
unreachable!("shadowrootmode value is not open nor closed");
}
},
local_name!("shadowrootclonable") => {
clonable = true;
},
local_name!("shadowrootdelegatesfocus") => {
_delegatesfocus = true;
},
local_name!("shadowrootserializable") => {
_serializable = true;
},
_ => {},
});
// Step 8.1. Attach a shadow root with declarative shadow host element,
// mode, clonable, serializable, delegatesFocus, and "named".
match host_element.attach_shadow(
IsUserAgentWidget::No,
shadow_root_mode,
clonable,
SlotAssignmentMode::Manual,
CanGc::note(),
) {
Ok(shadow_root) => {
// Step 8.3. Set shadow's declarative to true.
shadow_root.set_declarative(true);
// Set 8.4. Set template's template contents property to shadow.
let shadow = shadow_root.upcast::<DocumentFragment>();
template_element.set_contents(Some(shadow));
Ok(())
},
Err(_) => Err(String::from("Attaching shadow fails")),
}
}
}
/// <https://html.spec.whatwg.org/multipage/#create-an-element-for-the-token>

View file

@ -79,6 +79,9 @@ pub(crate) struct ShadowRoot {
slots: DomRefCell<HashMap<DOMString, Vec<Dom<HTMLSlotElement>>>>,
is_user_agent_widget: bool,
/// <https://dom.spec.whatwg.org/#shadowroot-declarative>
declarative: Cell<bool>,
}
impl ShadowRoot {
@ -113,6 +116,7 @@ impl ShadowRoot {
available_to_element_internals: Cell::new(false),
slots: Default::default(),
is_user_agent_widget: is_user_agent_widget == IsUserAgentWidget::Yes,
declarative: Cell::new(false),
}
}
@ -276,6 +280,10 @@ impl ShadowRoot {
pub(crate) fn is_user_agent_widget(&self) -> bool {
self.is_user_agent_widget
}
pub(crate) fn set_declarative(&self, declarative: bool) {
self.declarative.set(declarative);
}
}
impl ShadowRootMethods<crate::DomTypeHolder> for ShadowRoot {

View file

@ -60,6 +60,7 @@ impl XMLDocument {
None,
Default::default(),
false,
false,
inherited_insecure_requests_policy,
),
}

View file

@ -1508,6 +1508,7 @@ impl XMLHttpRequest {
None,
Default::default(),
false,
false,
Some(doc.insecure_requests_policy()),
can_gc,
)

View file

@ -3193,6 +3193,7 @@ impl ScriptThread {
Some(metadata.status.raw_code()),
incomplete.canceller,
is_initial_about_blank,
true,
incomplete.load_data.inherited_insecure_requests_policy,
can_gc,
);