diff options
Diffstat (limited to 'components/script/dom/element.rs')
-rw-r--r-- | components/script/dom/element.rs | 261 |
1 files changed, 200 insertions, 61 deletions
diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index c3d5fbb55d0..2d12e93774d 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -15,6 +15,7 @@ use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods; use crate::dom::bindings::codegen::Bindings::FunctionBinding::Function; use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; +use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootBinding::ShadowRootMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::{ScrollBehavior, ScrollToOptions}; use crate::dom::bindings::codegen::UnionTypes::NodeOrString; @@ -32,7 +33,7 @@ use crate::dom::bindings::xmlname::{ use crate::dom::characterdata::CharacterData; use crate::dom::create::create_element; use crate::dom::customelementregistry::{ - CallbackReaction, CustomElementDefinition, CustomElementReaction, + CallbackReaction, CustomElementDefinition, CustomElementReaction, CustomElementState, }; use crate::dom::document::{Document, LayoutDocumentHelpers}; use crate::dom::documentfragment::DocumentFragment; @@ -71,11 +72,13 @@ use crate::dom::htmltextareaelement::{HTMLTextAreaElement, LayoutHTMLTextAreaEle use crate::dom::mutationobserver::{Mutation, MutationObserver}; use crate::dom::namednodemap::NamedNodeMap; use crate::dom::node::{document_from_node, window_from_node}; -use crate::dom::node::{ChildrenMutation, LayoutNodeHelpers, Node}; -use crate::dom::node::{NodeDamage, NodeFlags, UnbindContext}; +use crate::dom::node::{BindContext, NodeDamage, NodeFlags, UnbindContext}; +use crate::dom::node::{ChildrenMutation, LayoutNodeHelpers, Node, ShadowIncluding}; use crate::dom::nodelist::NodeList; use crate::dom::promise::Promise; +use crate::dom::raredata::ElementRareData; use crate::dom::servoparser::ServoParser; +use crate::dom::shadowroot::ShadowRoot; use crate::dom::text::Text; use crate::dom::validation::Validatable; use crate::dom::virtualmethods::{vtable_for, VirtualMethods}; @@ -103,7 +106,7 @@ use selectors::Element as SelectorsElement; use servo_arc::Arc; use servo_atoms::Atom; use std::borrow::Cow; -use std::cell::{Cell, Ref}; +use std::cell::{Cell, Ref, RefMut}; use std::default::Default; use std::fmt; use std::mem; @@ -163,13 +166,7 @@ pub struct Element { /// when it has exclusive access to the element. #[ignore_malloc_size_of = "bitflags defined in rust-selectors"] selector_flags: Cell<ElementSelectorFlags>, - /// <https://html.spec.whatwg.org/multipage/#custom-element-reaction-queue> - custom_element_reaction_queue: DomRefCell<Vec<CustomElementReaction>>, - /// <https://dom.spec.whatwg.org/#concept-element-custom-element-definition> - #[ignore_malloc_size_of = "Rc"] - custom_element_definition: DomRefCell<Option<Rc<CustomElementDefinition>>>, - /// <https://dom.spec.whatwg.org/#concept-element-custom-element-state> - custom_element_state: Cell<CustomElementState>, + rare_data: DomRefCell<Option<Box<ElementRareData>>>, } impl fmt::Debug for Element { @@ -199,15 +196,6 @@ pub enum CustomElementCreationMode { Asynchronous, } -/// <https://dom.spec.whatwg.org/#concept-element-custom-element-state> -#[derive(Clone, Copy, Eq, JSTraceable, MallocSizeOf, PartialEq)] -pub enum CustomElementState { - Undefined, - Failed, - Uncustomized, - Custom, -} - impl ElementCreator { pub fn is_parser_created(&self) -> bool { match *self { @@ -244,6 +232,13 @@ impl FromStr for AdjacentPosition { } } +/// Whether a shadow root hosts an User Agent widget. +#[derive(PartialEq)] +pub enum IsUserAgentWidget { + No, + Yes, +} + // // Element methods // @@ -294,9 +289,7 @@ impl Element { class_list: Default::default(), state: Cell::new(state), selector_flags: Cell::new(ElementSelectorFlags::empty()), - custom_element_reaction_queue: Default::default(), - custom_element_definition: Default::default(), - custom_element_state: Cell::new(CustomElementState::Uncustomized), + rare_data: Default::default(), } } @@ -315,6 +308,8 @@ impl Element { ) } + impl_rare_data!(ElementRareData); + pub fn restyle(&self, damage: NodeDamage) { let doc = self.node.owner_doc(); let mut restyle = doc.ensure_pending_restyle(self); @@ -337,49 +332,60 @@ impl Element { } pub fn set_custom_element_state(&self, state: CustomElementState) { - self.custom_element_state.set(state); + self.ensure_rare_data().custom_element_state = state; } pub fn get_custom_element_state(&self) -> CustomElementState { - self.custom_element_state.get() + if let Some(rare_data) = self.rare_data().as_ref() { + return rare_data.custom_element_state; + } + CustomElementState::Undefined } pub fn set_custom_element_definition(&self, definition: Rc<CustomElementDefinition>) { - *self.custom_element_definition.borrow_mut() = Some(definition); + self.ensure_rare_data().custom_element_definition = Some(definition); } pub fn get_custom_element_definition(&self) -> Option<Rc<CustomElementDefinition>> { - (*self.custom_element_definition.borrow()).clone() + self.rare_data().as_ref()?.custom_element_definition.clone() } pub fn push_callback_reaction(&self, function: Rc<Function>, args: Box<[Heap<JSVal>]>) { - self.custom_element_reaction_queue - .borrow_mut() + self.ensure_rare_data() + .custom_element_reaction_queue .push(CustomElementReaction::Callback(function, args)); } pub fn push_upgrade_reaction(&self, definition: Rc<CustomElementDefinition>) { - self.custom_element_reaction_queue - .borrow_mut() + self.ensure_rare_data() + .custom_element_reaction_queue .push(CustomElementReaction::Upgrade(definition)); } pub fn clear_reaction_queue(&self) { - self.custom_element_reaction_queue.borrow_mut().clear(); + if let Some(ref mut rare_data) = *self.rare_data_mut() { + rare_data.custom_element_reaction_queue.clear(); + } } pub fn invoke_reactions(&self) { - // TODO: This is not spec compliant, as this will allow some reactions to be processed - // after clear_reaction_queue has been called. - rooted_vec!(let mut reactions); - while !self.custom_element_reaction_queue.borrow().is_empty() { - mem::swap( - &mut *reactions, - &mut *self.custom_element_reaction_queue.borrow_mut(), - ); + loop { + rooted_vec!(let mut reactions); + match *self.rare_data_mut() { + Some(ref mut data) => { + mem::swap(&mut *reactions, &mut data.custom_element_reaction_queue) + }, + None => break, + }; + + if reactions.is_empty() { + break; + } + for reaction in reactions.iter() { reaction.invoke(self); } + reactions.clear(); } } @@ -436,6 +442,71 @@ impl Element { box_.clone_overflow_y() == overflow_y::computed_value::T::Hidden }) } + + fn shadow_root(&self) -> Option<DomRoot<ShadowRoot>> { + self.rare_data() + .as_ref()? + .shadow_root + .as_ref() + .map(|sr| DomRoot::from_ref(&**sr)) + } + + pub fn is_shadow_host(&self) -> bool { + self.shadow_root().is_some() + } + + /// https://dom.spec.whatwg.org/#dom-element-attachshadow + /// XXX This is not exposed to web content yet. It is meant to be used + /// for UA widgets only. + pub fn attach_shadow(&self, is_ua_widget: IsUserAgentWidget) -> Fallible<DomRoot<ShadowRoot>> { + // Step 1. + if self.namespace != ns!(html) { + return Err(Error::NotSupported); + } + + // Step 2. + match self.local_name() { + &local_name!("article") | + &local_name!("aside") | + &local_name!("blockquote") | + &local_name!("body") | + &local_name!("div") | + &local_name!("footer") | + &local_name!("h1") | + &local_name!("h2") | + &local_name!("h3") | + &local_name!("h4") | + &local_name!("h5") | + &local_name!("h6") | + &local_name!("header") | + &local_name!("main") | + &local_name!("nav") | + &local_name!("p") | + &local_name!("section") | + &local_name!("span") => {}, + &local_name!("video") | &local_name!("audio") + if is_ua_widget == IsUserAgentWidget::Yes => {}, + _ => return Err(Error::NotSupported), + }; + + // Step 3. + if self.is_shadow_host() { + return Err(Error::InvalidState); + } + + // Steps 4, 5 and 6. + let shadow_root = ShadowRoot::new(self, &*self.node.owner_doc()); + self.ensure_rare_data().shadow_root = Some(Dom::from_ref(&*shadow_root)); + shadow_root + .upcast::<Node>() + .set_containing_shadow_root(&shadow_root); + + if self.is_connected() { + self.node.owner_doc().register_shadow_root(&*shadow_root); + } + + Ok(shadow_root) + } } #[allow(unsafe_code)] @@ -534,6 +605,9 @@ pub trait LayoutElementHelpers { fn get_state_for_layout(&self) -> ElementState; fn insert_selector_flags(&self, flags: ElementSelectorFlags); fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool; + /// The shadow root this element is a host of. + #[allow(unsafe_code)] + unsafe fn get_shadow_root_for_layout(&self) -> Option<LayoutDom<ShadowRoot>>; } impl LayoutElementHelpers for LayoutDom<Element> { @@ -932,7 +1006,7 @@ impl LayoutElementHelpers for LayoutDom<Element> { unsafe { let mut current_node = Some(self.upcast::<Node>()); while let Some(node) = current_node { - current_node = node.parent_node_ref(); + current_node = node.composed_parent_node_ref(); match node.downcast::<Element>().map(|el| el.unsafe_get()) { Some(elem) => { if let Some(attr) = @@ -996,6 +1070,17 @@ impl LayoutElementHelpers for LayoutDom<Element> { fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool { unsafe { (*self.unsafe_get()).selector_flags.get().contains(flags) } } + + #[inline] + #[allow(unsafe_code)] + unsafe fn get_shadow_root_for_layout(&self) -> Option<LayoutDom<ShadowRoot>> { + (*self.unsafe_get()) + .rare_data_for_layout() + .as_ref()? + .shadow_root + .as_ref() + .map(|sr| sr.to_layout()) + } } impl Element { @@ -1040,7 +1125,7 @@ impl Element { let inclusive_ancestor_elements = self .upcast::<Node>() - .inclusive_ancestors() + .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::<Self>); // Steps 3-4. @@ -1156,7 +1241,7 @@ impl Element { .unwrap() } else { self.upcast::<Node>() - .inclusive_ancestors() + .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast) .last() .expect("We know inclusive_ancestors will return `self` which is an element") @@ -1165,7 +1250,10 @@ impl Element { // https://dom.spec.whatwg.org/#locate-a-namespace-prefix pub fn lookup_prefix(&self, namespace: Namespace) -> Option<DOMString> { - for node in self.upcast::<Node>().inclusive_ancestors() { + for node in self + .upcast::<Node>() + .inclusive_ancestors(ShadowIncluding::No) + { let element = node.downcast::<Element>()?; // Step 1. if *element.namespace() == namespace { @@ -2372,7 +2460,7 @@ impl ElementMethods for Element { NodeTypeId::Document(_) => return Err(Error::NoModificationAllowed), // Step 4. - NodeTypeId::DocumentFragment => { + NodeTypeId::DocumentFragment(_) => { let body_elem = Element::create( QualName::new(None, ns!(html), local_name!("body")), None, @@ -2586,6 +2674,13 @@ impl ElementMethods for Element { let doc = document_from_node(self); doc.enter_fullscreen(self) } + + // XXX Hidden under dom.shadowdom.enabled pref. Only exposed to be able + // to test partial Shadow DOM support for UA widgets. + // https://dom.spec.whatwg.org/#dom-element-attachshadow + fn AttachShadow(&self) -> Fallible<DomRoot<ShadowRoot>> { + self.attach_shadow(IsUserAgentWidget::No) + } } impl VirtualMethods for Element { @@ -2658,21 +2753,34 @@ impl VirtualMethods for Element { None } }); - if node.is_in_doc() { + let containing_shadow_root = self.upcast::<Node>().containing_shadow_root(); + if node.is_connected() { let value = attr.value().as_atom().clone(); match mutation { AttributeMutation::Set(old_value) => { if let Some(old_value) = old_value { let old_value = old_value.as_atom().clone(); - doc.unregister_named_element(self, old_value); + if let Some(ref shadow_root) = containing_shadow_root { + shadow_root.unregister_named_element(self, old_value); + } else { + doc.unregister_named_element(self, old_value); + } } if value != atom!("") { - doc.register_named_element(self, value); + if let Some(ref shadow_root) = containing_shadow_root { + shadow_root.register_named_element(self, value); + } else { + doc.register_named_element(self, value); + } } }, AttributeMutation::Removed => { if value != atom!("") { - doc.unregister_named_element(self, value); + if let Some(ref shadow_root) = containing_shadow_root { + shadow_root.unregister_named_element(self, value); + } else { + doc.unregister_named_element(self, value); + } } }, } @@ -2704,22 +2812,37 @@ impl VirtualMethods for Element { } } - fn bind_to_tree(&self, tree_in_doc: bool) { + fn bind_to_tree(&self, context: &BindContext) { if let Some(ref s) = self.super_type() { - s.bind_to_tree(tree_in_doc); + s.bind_to_tree(context); } if let Some(f) = self.as_maybe_form_control() { f.bind_form_control_to_tree(); } - if !tree_in_doc { + let doc = document_from_node(self); + + if let Some(ref shadow_root) = self.shadow_root() { + doc.register_shadow_root(&shadow_root); + let shadow_root = shadow_root.upcast::<Node>(); + shadow_root.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected); + for node in shadow_root.children() { + node.set_flag(NodeFlags::IS_CONNECTED, context.tree_connected); + node.bind_to_tree(context); + } + } + + if !context.tree_connected { return; } - let doc = document_from_node(self); if let Some(ref value) = *self.id_attribute.borrow() { - doc.register_named_element(self, value.clone()); + if let Some(shadow_root) = self.upcast::<Node>().containing_shadow_root() { + shadow_root.register_named_element(self, value.clone()); + } else { + doc.register_named_element(self, value.clone()); + } } // This is used for layout optimization. doc.increment_dom_count(); @@ -2732,11 +2855,22 @@ impl VirtualMethods for Element { f.unbind_form_control_from_tree(); } - if !context.tree_in_doc { + if !context.tree_connected { return; } let doc = document_from_node(self); + + if let Some(ref shadow_root) = self.shadow_root() { + doc.unregister_shadow_root(&shadow_root); + let shadow_root = shadow_root.upcast::<Node>(); + shadow_root.set_flag(NodeFlags::IS_CONNECTED, false); + for node in shadow_root.children() { + node.set_flag(NodeFlags::IS_CONNECTED, false); + node.unbind_from_tree(context); + } + } + let fullscreen = doc.GetFullscreenElement(); if fullscreen.deref() == Some(self) { doc.exit_fullscreen(); @@ -2797,11 +2931,18 @@ impl<'a> SelectorsElement for DomRoot<Element> { } fn parent_node_is_shadow_root(&self) -> bool { - false + match self.upcast::<Node>().GetParentNode() { + None => false, + Some(node) => node.is::<ShadowRoot>(), + } } fn containing_shadow_host(&self) -> Option<Self> { - None + if let Some(shadow_root) = self.upcast::<Node>().containing_shadow_root() { + Some(shadow_root.Host()) + } else { + None + } } fn match_pseudo_element( @@ -3111,7 +3252,7 @@ impl Element { // https://html.spec.whatwg.org/multipage/#language pub fn get_lang(&self) -> String { self.upcast::<Node>() - .inclusive_ancestors() + .inclusive_ancestors(ShadowIncluding::No) .filter_map(|node| { node.downcast::<Element>().and_then(|el| { el.get_attribute(&ns!(xml), &local_name!("lang")) @@ -3229,9 +3370,7 @@ impl Element { /// <https://dom.spec.whatwg.org/#connected> pub fn is_connected(&self) -> bool { - let node = self.upcast::<Node>(); - let root = node.GetRootNode(); - root.is::<Document>() + self.upcast::<Node>().is_connected() } // https://html.spec.whatwg.org/multipage/#cannot-navigate |