diff --git a/components/script/dom/servoparser/async_html.rs b/components/script/dom/servoparser/async_html.rs index 1d6f4af0b01..f917a68a8c1 100644 --- a/components/script/dom/servoparser/async_html.rs +++ b/components/script/dom/servoparser/async_html.rs @@ -37,7 +37,9 @@ use crate::dom::htmlscriptelement::HTMLScriptElement; use crate::dom::htmltemplateelement::HTMLTemplateElement; use crate::dom::node::Node; use crate::dom::processinginstruction::ProcessingInstruction; -use crate::dom::servoparser::{ElementAttribute, ParsingAlgorithm, create_element_for_token}; +use crate::dom::servoparser::{ + ElementAttribute, ParsingAlgorithm, attach_declarative_shadow_inner, create_element_for_token, +}; use crate::dom::virtualmethods::vtable_for; use crate::script_runtime::CanGc; @@ -139,6 +141,15 @@ enum ParseOperation { #[no_trace] mode: ServoQuirksMode, }, + + AttachDeclarativeShadowRoot { + location: ParseNodeId, + template: ParseNodeId, + attributes: Vec, + /// Used to notify the parser thread whether or not attaching the shadow root succeeded + #[no_trace] + sender: Sender, + }, } #[derive(MallocSizeOf)] @@ -562,6 +573,26 @@ impl Tokenizer { ParseOperation::SetQuirksMode { mode } => { document.set_quirks_mode(mode); }, + ParseOperation::AttachDeclarativeShadowRoot { + location, + template, + attributes, + sender, + } => { + let location = self.get_node(&location); + let template = self.get_node(&template); + let attributes = attributes + .into_iter() + .map(|attribute| HtmlAttribute { + name: attribute.name, + value: StrTendril::from(attribute.value), + }) + .collect(); + + let did_succeed = + attach_declarative_shadow_inner(&location, &template, attributes).is_ok(); + sender.send(did_succeed).unwrap(); + }, } } } @@ -920,4 +951,40 @@ impl TreeSink for Sink { fn pop(&self, node: &Self::Handle) { self.send_op(ParseOperation::Pop { node: node.id }); } + + /// Attach declarative shadow + fn attach_declarative_shadow( + &self, + location: &Self::Handle, + template: &Self::Handle, + attributes: Vec, + ) -> Result<(), String> { + let attributes = attributes + .into_iter() + .map(|attribute| Attribute { + name: attribute.name, + value: String::from(attribute.value), + }) + .collect(); + + // Unfortunately the parser can only proceed after it knows whether attaching the shadow root + // succeeded or failed. Attaching a shadow root can fail for many different reasons, + // and so we need to block until the script thread has processed this operation. + let (sender, receiver) = unbounded(); + self.send_op(ParseOperation::AttachDeclarativeShadowRoot { + location: location.id, + template: template.id, + attributes, + sender, + }); + + let did_succeed = receiver.recv().unwrap(); + + // TODO: This api is silly, we shouldn't have to return a string here + if did_succeed { + Ok(()) + } else { + Err("Attaching declarative shadow root failed".to_owned()) + } + } } diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 981ec22dc7f..e6f71b19f47 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -1441,81 +1441,7 @@ impl TreeSink for Sink { template: &Dom, attrs: Vec, ) -> Result<(), String> { - let host_element = host.downcast::().unwrap(); - - if host_element.shadow_root().is_some() { - return Err(String::from("Already in a shadow host")); - } - - let template_element = template.downcast::().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 = 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, - serializable, - delegatesfocus, - SlotAssignmentMode::Named, - 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::(); - template_element.set_contents(Some(shadow)); - - // Step 8.5. Set shadow’s available to element internals to true. - shadow_root.set_available_to_element_internals(true); - - Ok(()) - }, - Err(_) => Err(String::from("Attaching shadow fails")), - } + attach_declarative_shadow_inner(host, template, attrs) } } @@ -1652,3 +1578,85 @@ impl TendrilSink for NetworkSink { self.output } } + +fn attach_declarative_shadow_inner( + host: &Node, + template: &Node, + attrs: Vec, +) -> Result<(), String> { + let host_element = host.downcast::().unwrap(); + + if host_element.shadow_root().is_some() { + return Err(String::from("Already in a shadow host")); + } + + let template_element = template.downcast::().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 = 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, + serializable, + delegatesfocus, + SlotAssignmentMode::Named, + 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::(); + template_element.set_contents(Some(shadow)); + + // Step 8.5. Set shadow’s available to element internals to true. + shadow_root.set_available_to_element_internals(true); + + Ok(()) + }, + Err(_) => Err(String::from("Attaching shadow fails")), + } +}