diff options
author | bors-servo <lbergstrom+bors@mozilla.com> | 2019-04-29 08:38:50 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-04-29 08:38:50 -0400 |
commit | 799490a02e9bea575bf34c39f045ef0883539f05 (patch) | |
tree | 278cada683564db36997cf3f96c92513b89c3096 | |
parent | d58ea974baff1b51a43d2e2bf4b287ff11991a8d (diff) | |
parent | 37e88e77cdf00e3555599dd4004d03548bd95dcf (diff) | |
download | servo-799490a02e9bea575bf34c39f045ef0883539f05.tar.gz servo-799490a02e9bea575bf34c39f045ef0883539f05.zip |
Auto merge of #22743 - ferjm:shadowdom, r=emilio
Partial ShadowDOM support
This is just an early WIP, not to take it very seriously yet.
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [x] These changes fix #22719
- [x] There are tests for these changes
<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/22743)
<!-- Reviewable:end -->
75 files changed, 2080 insertions, 701 deletions
diff --git a/components/config/prefs.rs b/components/config/prefs.rs index e8b8b9a1e7e..76e70933bde 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -222,6 +222,9 @@ mod gen { enabled: bool, } }, + shadowdom: { + enabled: bool, + }, svg: { enabled: bool, }, diff --git a/components/layout/query.rs b/components/layout/query.rs index 7a795e387fe..a74eda165e2 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -1050,7 +1050,7 @@ fn inner_text_collection_steps<N: LayoutNode>( // Step 3. let display = style.get_box().display; - if !child.is_in_document() || display == Display::None { + if !child.is_connected() || display == Display::None { continue; } diff --git a/components/layout_thread/dom_wrapper.rs b/components/layout_thread/dom_wrapper.rs index 14c09923aec..88eedef7d17 100644 --- a/components/layout_thread/dom_wrapper.rs +++ b/components/layout_thread/dom_wrapper.rs @@ -40,13 +40,16 @@ use net_traits::image::base::{Image, ImageMetadata}; use range::Range; use script::layout_exports::NodeFlags; use script::layout_exports::PendingRestyle; +use script::layout_exports::ShadowRoot; use script::layout_exports::{ - CharacterDataTypeId, ElementTypeId, HTMLElementTypeId, NodeTypeId, TextTypeId, + CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, HTMLElementTypeId, NodeTypeId, + TextTypeId, }; use script::layout_exports::{Document, Element, Node, Text}; use script::layout_exports::{LayoutCharacterDataHelpers, LayoutDocumentHelpers}; use script::layout_exports::{ - LayoutDom, LayoutElementHelpers, LayoutNodeHelpers, RawLayoutElementHelpers, + LayoutDom, LayoutElementHelpers, LayoutNodeHelpers, LayoutShadowRootHelpers, + RawLayoutElementHelpers, }; use script_layout_interface::wrapper_traits::{ DangerousThreadSafeLayoutNode, GetLayoutData, LayoutNode, @@ -80,10 +83,13 @@ use style::dom::{DomChildren, LayoutIterator, NodeInfo, OpaqueNode}; use style::dom::{TDocument, TElement, TNode, TShadowRoot}; use style::element_state::*; use style::font_metrics::ServoMetricsProvider; +use style::media_queries::Device; use style::properties::{ComputedValues, PropertyDeclarationBlock}; use style::selector_parser::{extended_filtering, PseudoElement, SelectorImpl}; use style::selector_parser::{AttrValue as SelectorAttrValue, Lang, NonTSPseudoClass}; -use style::shared_lock::{Locked as StyleLocked, SharedRwLock as StyleSharedRwLock}; +use style::shared_lock::{ + Locked as StyleLocked, SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard, +}; use style::str::is_whitespace; use style::stylist::CascadeData; use style::CaseSensitivityExt; @@ -161,39 +167,72 @@ impl<'ln> NodeInfo for ServoLayoutNode<'ln> { } #[derive(Clone, Copy, PartialEq)] -enum Impossible {} +pub struct ServoShadowRoot<'a> { + /// The wrapped shadow root. + shadow_root: LayoutDom<ShadowRoot>, -#[derive(Clone, Copy, PartialEq)] -pub struct ShadowRoot<'lr>(Impossible, PhantomData<&'lr ()>); + /// Being chained to a PhantomData prevents `ShadowRoot`s from escaping. + chain: PhantomData<&'a ()>, +} -impl<'lr> TShadowRoot for ShadowRoot<'lr> { +impl<'lr> Debug for ServoShadowRoot<'lr> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.as_node().fmt(f) + } +} + +impl<'lr> TShadowRoot for ServoShadowRoot<'lr> { type ConcreteNode = ServoLayoutNode<'lr>; fn as_node(&self) -> Self::ConcreteNode { - match self.0 {} + ServoLayoutNode::from_layout_js(self.shadow_root.upcast()) } fn host(&self) -> ServoLayoutElement<'lr> { - match self.0 {} + ServoLayoutElement::from_layout_js(unsafe { self.shadow_root.get_host_for_layout() }) } fn style_data<'a>(&self) -> Option<&'a CascadeData> where Self: 'a, { - match self.0 {} + Some(unsafe { + &self + .shadow_root + .get_style_data_for_layout::<ServoLayoutElement>() + .data + }) + } +} + +impl<'lr> ServoShadowRoot<'lr> { + fn from_layout_js(shadow_root: LayoutDom<ShadowRoot>) -> ServoShadowRoot<'lr> { + ServoShadowRoot { + shadow_root, + chain: PhantomData, + } + } + + pub unsafe fn flush_stylesheets( + &self, + device: &Device, + quirks_mode: QuirksMode, + guard: &SharedRwLockReadGuard, + ) { + self.shadow_root + .flush_stylesheets::<ServoLayoutElement>(device, quirks_mode, guard) } } impl<'ln> TNode for ServoLayoutNode<'ln> { type ConcreteDocument = ServoLayoutDocument<'ln>; type ConcreteElement = ServoLayoutElement<'ln>; - type ConcreteShadowRoot = ShadowRoot<'ln>; + type ConcreteShadowRoot = ServoShadowRoot<'ln>; fn parent_node(&self) -> Option<Self> { unsafe { self.node - .parent_node_ref() + .composed_parent_node_ref() .map(|node| self.new_with_this_lifetime(&node)) } } @@ -235,7 +274,11 @@ impl<'ln> TNode for ServoLayoutNode<'ln> { } fn traversal_parent(&self) -> Option<ServoLayoutElement<'ln>> { - self.parent_element() + let parent = self.parent_node()?; + if let Some(shadow) = parent.as_shadow_root() { + return Some(shadow.host()); + }; + parent.as_element() } fn opaque(&self) -> OpaqueNode { @@ -256,8 +299,8 @@ impl<'ln> TNode for ServoLayoutNode<'ln> { .map(ServoLayoutDocument::from_layout_js) } - fn as_shadow_root(&self) -> Option<ShadowRoot<'ln>> { - None + fn as_shadow_root(&self) -> Option<ServoShadowRoot<'ln>> { + self.node.downcast().map(ServoShadowRoot::from_layout_js) } fn is_in_document(&self) -> bool { @@ -293,6 +336,10 @@ impl<'ln> LayoutNode for ServoLayoutNode<'ln> { unsafe fn take_style_and_layout_data(&self) -> OpaqueStyleAndLayoutData { self.get_jsmanaged().take_style_and_layout_data() } + + fn is_connected(&self) -> bool { + unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) } + } } impl<'ln> GetLayoutData for ServoLayoutNode<'ln> { @@ -378,6 +425,36 @@ impl<'ld> ServoLayoutDocument<'ld> { unsafe { self.document.style_shared_lock() } } + pub fn shadow_roots(&self) -> Vec<ServoShadowRoot> { + unsafe { + self.document + .shadow_roots() + .iter() + .map(|sr| { + debug_assert!(sr.upcast::<Node>().get_flag(NodeFlags::IS_CONNECTED)); + ServoShadowRoot::from_layout_js(*sr) + }) + .collect() + } + } + + pub fn flush_shadow_roots_stylesheets( + &self, + device: &Device, + quirks_mode: QuirksMode, + guard: &SharedRwLockReadGuard, + ) { + unsafe { + if !self.document.shadow_roots_styles_changed() { + return; + } + self.document.flush_shadow_roots_stylesheets(); + for shadow_root in self.shadow_roots() { + shadow_root.flush_stylesheets(device, quirks_mode, guard); + } + } + } + pub fn from_layout_js(doc: LayoutDom<Document>) -> ServoLayoutDocument<'ld> { ServoLayoutDocument { document: doc, @@ -414,7 +491,11 @@ impl<'le> TElement for ServoLayoutElement<'le> { } fn traversal_children(&self) -> LayoutIterator<Self::TraversalChildrenIterator> { - LayoutIterator(self.as_node().dom_children()) + LayoutIterator(if let Some(shadow) = self.shadow_root() { + shadow.as_node().dom_children() + } else { + self.as_node().dom_children() + }) } fn is_html_element(&self) -> bool { @@ -488,7 +569,7 @@ impl<'le> TElement for ServoLayoutElement<'le> { } unsafe fn set_dirty_descendants(&self) { - debug_assert!(self.as_node().is_in_document()); + debug_assert!(self.as_node().is_connected()); self.as_node() .node .set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true) @@ -616,12 +697,23 @@ impl<'le> TElement for ServoLayoutElement<'le> { } } - fn shadow_root(&self) -> Option<ShadowRoot<'le>> { - None + /// The shadow root this element is a host of. + fn shadow_root(&self) -> Option<ServoShadowRoot<'le>> { + unsafe { + self.element + .get_shadow_root_for_layout() + .map(ServoShadowRoot::from_layout_js) + } } - fn containing_shadow(&self) -> Option<ShadowRoot<'le>> { - None + /// The shadow root which roots the subtree this element is contained in. + fn containing_shadow(&self) -> Option<ServoShadowRoot<'le>> { + unsafe { + self.element + .upcast() + .containing_shadow_root_for_layout() + .map(ServoShadowRoot::from_layout_js) + } } } @@ -702,15 +794,26 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { } fn parent_element(&self) -> Option<ServoLayoutElement<'le>> { - unsafe { self.element.upcast().parent_node_ref().and_then(as_element) } + unsafe { + self.element + .upcast() + .composed_parent_node_ref() + .and_then(as_element) + } } fn parent_node_is_shadow_root(&self) -> bool { - false + match self.as_node().parent_node() { + None => false, + Some(node) => { + node.script_type_id() == + NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot) + }, + } } fn containing_shadow_host(&self) -> Option<Self> { - None + self.containing_shadow().map(|s| s.host()) } fn prev_sibling_element(&self) -> Option<ServoLayoutElement<'le>> { @@ -985,6 +1088,11 @@ impl<'ln> ThreadSafeLayoutNode for ServoThreadSafeLayoutNode<'ln> { } fn children(&self) -> LayoutIterator<Self::ChildrenIterator> { + if let Some(shadow) = self.node.as_element().and_then(|e| e.shadow_root()) { + return LayoutIterator(ThreadSafeLayoutNodeChildrenIterator::new( + shadow.as_node().to_threadsafe(), + )); + } LayoutIterator(ThreadSafeLayoutNodeChildrenIterator::new(*self)) } diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index 839fac79006..b44bbb54b96 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -103,7 +103,7 @@ use std::time::Duration; use style::animation::Animation; use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters}; use style::context::{SharedStyleContext, ThreadLocalStyleContextCreationInfo}; -use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TElement, TNode}; +use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TDocument, TElement, TNode}; use style::driver; use style::error_reporting::RustLogReporter; use style::global_style_data::{GLOBAL_STYLE_DATA, STYLE_THREAD_POOL}; @@ -1346,6 +1346,18 @@ impl LayoutThread { } } + debug!( + "Shadow roots in document {:?}", + document.shadow_roots().len() + ); + + // Flush shadow roots stylesheets if dirty. + document.flush_shadow_roots_stylesheets( + &self.stylist.device(), + document.quirks_mode(), + guards.author.clone(), + ); + let restyles = document.drain_pending_restyles(); debug!("Draining restyles: {}", restyles.len()); diff --git a/components/malloc_size_of/lib.rs b/components/malloc_size_of/lib.rs index 629cb1f0824..bb4fe1a6adc 100644 --- a/components/malloc_size_of/lib.rs +++ b/components/malloc_size_of/lib.rs @@ -778,6 +778,12 @@ impl<Impl: selectors::parser::SelectorImpl> MallocSizeOf } } +impl MallocSizeOf for selectors::context::QuirksMode { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + 0 + } +} + impl MallocSizeOf for Void { #[inline] fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { diff --git a/components/script/devtools.rs b/components/script/devtools.rs index 7a24d959980..087930042e6 100644 --- a/components/script/devtools.rs +++ b/components/script/devtools.rs @@ -15,7 +15,7 @@ use crate::dom::bindings::str::DOMString; use crate::dom::document::AnimationFrameCallback; use crate::dom::element::Element; use crate::dom::globalscope::GlobalScope; -use crate::dom::node::{window_from_node, Node}; +use crate::dom::node::{window_from_node, Node, ShadowIncluding}; use crate::dom::window::Window; use crate::script_thread::Documents; use devtools_traits::TimelineMarkerType; @@ -103,7 +103,7 @@ fn find_node_by_unique_id( documents.find_document(pipeline).and_then(|document| { document .upcast::<Node>() - .traverse_preorder() + .traverse_preorder(ShadowIncluding::Yes) .find(|candidate| candidate.unique_id() == node_id) }) } diff --git a/components/script/dom/bindings/htmlconstructor.rs b/components/script/dom/bindings/htmlconstructor.rs index 5654b52938b..a13b6fa9ecb 100644 --- a/components/script/dom/bindings/htmlconstructor.rs +++ b/components/script/dom/bindings/htmlconstructor.rs @@ -72,8 +72,8 @@ use crate::dom::bindings::conversions::DerivedFrom; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::root::DomRoot; use crate::dom::create::create_native_html_element; -use crate::dom::customelementregistry::ConstructionStackEntry; -use crate::dom::element::{CustomElementState, Element, ElementCreator}; +use crate::dom::customelementregistry::{ConstructionStackEntry, CustomElementState}; +use crate::dom::element::{Element, ElementCreator}; use crate::dom::htmlelement::HTMLElement; use crate::dom::window::Window; use crate::script_thread::ScriptThread; diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index f1734d74b55..56805e4d9fe 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -118,6 +118,7 @@ use std::sync::atomic::{AtomicBool, AtomicUsize}; use std::sync::{Arc, Mutex}; use std::time::{Instant, SystemTime}; use style::attr::{AttrIdentifier, AttrValue, LengthOrPercentageOrAuto}; +use style::author_styles::AuthorStyles; use style::context::QuirksMode; use style::dom::OpaqueNode; use style::element_state::*; @@ -125,10 +126,11 @@ use style::media_queries::MediaList; use style::properties::PropertyDeclarationBlock; use style::selector_parser::{PseudoElement, Snapshot}; use style::shared_lock::{Locked as StyleLocked, SharedRwLock as StyleSharedRwLock}; -use style::stylesheet_set::DocumentStylesheetSet; +use style::stylesheet_set::{AuthorStylesheetSet, DocumentStylesheetSet}; use style::stylesheets::keyframes_rule::Keyframe; use style::stylesheets::{CssRules, FontFaceRule, KeyframesRule, MediaRule, Stylesheet}; use style::stylesheets::{ImportRule, NamespaceRule, StyleRule, SupportsRule, ViewportRule}; +use style::stylist::CascadeData; use style::values::specified::Length; use tendril::fmt::UTF8; use tendril::stream::LossyDecoder; @@ -497,6 +499,7 @@ unsafe_no_jsmanaged_fields!(HTMLMediaElementFetchContext); unsafe_no_jsmanaged_fields!(Rotation3D<f64>, Transform2D<f32>, Transform3D<f64>); unsafe_no_jsmanaged_fields!(Point2D<f32>, Vector2D<f32>, Rect<Au>); unsafe_no_jsmanaged_fields!(Rect<f32>, RigidTransform3D<f64>); +unsafe_no_jsmanaged_fields!(CascadeData); unsafe impl<'a> JSTraceable for &'a str { #[inline] @@ -715,6 +718,26 @@ where } } +unsafe impl<S> JSTraceable for AuthorStylesheetSet<S> +where + S: JSTraceable + ::style::stylesheets::StylesheetInDocument + PartialEq + 'static, +{ + unsafe fn trace(&self, tracer: *mut JSTracer) { + for s in self.iter() { + s.trace(tracer) + } + } +} + +unsafe impl<S> JSTraceable for AuthorStyles<S> +where + S: JSTraceable + ::style::stylesheets::StylesheetInDocument + PartialEq + 'static, +{ + unsafe fn trace(&self, tracer: *mut JSTracer) { + self.stylesheets.trace(tracer) + } +} + unsafe impl<Sink> JSTraceable for LossyDecoder<Sink> where Sink: JSTraceable + TendrilSink<UTF8>, diff --git a/components/script/dom/create.rs b/components/script/dom/create.rs index 461e37a28de..6ffb38f753a 100644 --- a/components/script/dom/create.rs +++ b/components/script/dom/create.rs @@ -5,9 +5,11 @@ use crate::dom::bindings::error::{report_pending_exception, throw_dom_exception}; use crate::dom::bindings::reflector::DomObject; use crate::dom::bindings::root::DomRoot; -use crate::dom::customelementregistry::{is_valid_custom_element_name, upgrade_element}; +use crate::dom::customelementregistry::{ + is_valid_custom_element_name, upgrade_element, CustomElementState, +}; use crate::dom::document::Document; -use crate::dom::element::{CustomElementCreationMode, CustomElementState, Element, ElementCreator}; +use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator}; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlanchorelement::HTMLAnchorElement; use crate::dom::htmlareaelement::HTMLAreaElement; diff --git a/components/script/dom/cssstyledeclaration.rs b/components/script/dom/cssstyledeclaration.rs index 2dabe431496..d623f46005a 100644 --- a/components/script/dom/cssstyledeclaration.rs +++ b/components/script/dom/cssstyledeclaration.rs @@ -13,7 +13,7 @@ use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::DOMString; use crate::dom::cssrule::CSSRule; use crate::dom::element::Element; -use crate::dom::node::{document_from_node, window_from_node, Node}; +use crate::dom::node::{document_from_node, stylesheets_owner_from_node, window_from_node, Node}; use crate::dom::window::Window; use dom_struct::dom_struct; use servo_arc::Arc; @@ -115,9 +115,7 @@ impl CSSStyleOwner { if changed { // If this is changed, see also // CSSStyleRule::SetSelectorText, which does the same thing. - rule.global() - .as_window() - .Document() + stylesheets_owner_from_node(rule.parent_stylesheet().owner().upcast::<Node>()) .invalidate_stylesheets(); } result @@ -246,7 +244,7 @@ impl CSSStyleDeclaration { }, CSSStyleOwner::Element(ref el) => { let node = el.upcast::<Node>(); - if !node.is_in_doc() { + if !node.is_connected() { // TODO: Node should be matched against the style rules of this window. // Firefox is currently the only browser to implement this. return DOMString::new(); diff --git a/components/script/dom/cssstylerule.rs b/components/script/dom/cssstylerule.rs index c76e3bdd938..14d7cdcdbef 100644 --- a/components/script/dom/cssstylerule.rs +++ b/components/script/dom/cssstylerule.rs @@ -3,7 +3,6 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use crate::dom::bindings::codegen::Bindings::CSSStyleRuleBinding::{self, CSSStyleRuleMethods}; -use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; @@ -11,6 +10,7 @@ use crate::dom::bindings::str::DOMString; use crate::dom::cssrule::{CSSRule, SpecificCSSRule}; use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner}; use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::node::{stylesheets_owner_from_node, Node}; use crate::dom::window::Window; use cssparser::ToCss; use cssparser::{Parser as CssParser, ParserInput as CssParserInput}; @@ -118,11 +118,7 @@ impl CSSStyleRuleMethods for CSSStyleRule { let mut guard = self.cssrule.shared_lock().write(); let stylerule = self.stylerule.write_with(&mut guard); mem::swap(&mut stylerule.selectors, &mut s); - // It seems like we will want to avoid having to invalidate all - // stylesheets eventually! - self.global() - .as_window() - .Document() + stylesheets_owner_from_node(self.cssrule.parent_stylesheet().owner().upcast::<Node>()) .invalidate_stylesheets(); } } diff --git a/components/script/dom/cssstylesheet.rs b/components/script/dom/cssstylesheet.rs index 2def47d7aaa..66d2fd8a339 100644 --- a/components/script/dom/cssstylesheet.rs +++ b/components/script/dom/cssstylesheet.rs @@ -4,13 +4,14 @@ use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding; use crate::dom::bindings::codegen::Bindings::CSSStyleSheetBinding::CSSStyleSheetMethods; -use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowBinding::WindowMethods; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; +use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::bindings::str::DOMString; use crate::dom::cssrulelist::{CSSRuleList, RulesSource}; use crate::dom::element::Element; +use crate::dom::node::{stylesheets_owner_from_node, Node}; use crate::dom::stylesheet::StyleSheet; use crate::dom::window::Window; use dom_struct::dom_struct; @@ -64,6 +65,10 @@ impl CSSStyleSheet { ) } + pub fn owner(&self) -> DomRoot<Element> { + DomRoot::from_ref(&*self.owner) + } + fn rulelist(&self) -> DomRoot<CSSRuleList> { self.rulelist.or_init(|| { let rules = self.style_stylesheet.contents.rules.clone(); @@ -81,10 +86,7 @@ impl CSSStyleSheet { pub fn set_disabled(&self, disabled: bool) { if self.style_stylesheet.set_disabled(disabled) { - self.global() - .as_window() - .Document() - .invalidate_stylesheets(); + stylesheets_owner_from_node(self.owner().upcast::<Node>()).invalidate_stylesheets(); } } diff --git a/components/script/dom/customelementregistry.rs b/components/script/dom/customelementregistry.rs index abefc4eca58..e1b3163bf4a 100644 --- a/components/script/dom/customelementregistry.rs +++ b/components/script/dom/customelementregistry.rs @@ -23,10 +23,10 @@ use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::DOMString; use crate::dom::document::Document; use crate::dom::domexception::{DOMErrorName, DOMException}; -use crate::dom::element::{CustomElementState, Element}; +use crate::dom::element::Element; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlelement::HTMLElement; -use crate::dom::node::{document_from_node, window_from_node, Node}; +use crate::dom::node::{document_from_node, window_from_node, Node, ShadowIncluding}; use crate::dom::promise::Promise; use crate::dom::window::Window; use crate::microtask::Microtask; @@ -47,6 +47,21 @@ use std::ops::Deref; use std::ptr; use std::rc::Rc; +/// <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 Default for CustomElementState { + fn default() -> CustomElementState { + CustomElementState::Uncustomized + } +} + /// <https://html.spec.whatwg.org/multipage/#customelementregistry> #[dom_struct] pub struct CustomElementRegistry { @@ -364,7 +379,7 @@ impl CustomElementRegistryMethods for CustomElementRegistry { // Steps 14-15 for candidate in document .upcast::<Node>() - .traverse_preorder() + .traverse_preorder(ShadowIncluding::Yes) .filter_map(DomRoot::downcast::<Element>) { let is = candidate.get_is(); diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index ed011c8029d..1a6714f5927 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -43,6 +43,7 @@ use crate::dom::cssstylesheet::CSSStyleSheet; use crate::dom::customelementregistry::CustomElementDefinition; use crate::dom::customevent::CustomEvent; use crate::dom::documentfragment::DocumentFragment; +use crate::dom::documentorshadowroot::{DocumentOrShadowRoot, StyleSheetInDocument}; use crate::dom::documenttype::DocumentType; use crate::dom::domimplementation::DOMImplementation; use crate::dom::element::CustomElementCreationMode; @@ -67,16 +68,14 @@ use crate::dom::htmlheadelement::HTMLHeadElement; use crate::dom::htmlhtmlelement::HTMLHtmlElement; use crate::dom::htmliframeelement::HTMLIFrameElement; use crate::dom::htmlimageelement::HTMLImageElement; -use crate::dom::htmlmetaelement::HTMLMetaElement; use crate::dom::htmlscriptelement::{HTMLScriptElement, ScriptResult}; use crate::dom::htmltitleelement::HTMLTitleElement; use crate::dom::keyboardevent::KeyboardEvent; use crate::dom::location::Location; use crate::dom::messageevent::MessageEvent; use crate::dom::mouseevent::MouseEvent; -use crate::dom::node::VecPreOrderInsertionHelper; use crate::dom::node::{self, document_from_node, window_from_node, CloneChildrenFlag}; -use crate::dom::node::{LayoutNodeHelpers, Node, NodeDamage, NodeFlags}; +use crate::dom::node::{LayoutNodeHelpers, Node, NodeDamage, NodeFlags, ShadowIncluding}; use crate::dom::nodeiterator::NodeIterator; use crate::dom::nodelist::NodeList; use crate::dom::pagetransitionevent::PageTransitionEvent; @@ -86,8 +85,9 @@ use crate::dom::progressevent::ProgressEvent; use crate::dom::promise::Promise; use crate::dom::range::Range; use crate::dom::servoparser::ServoParser; +use crate::dom::shadowroot::ShadowRoot; use crate::dom::storageevent::StorageEvent; -use crate::dom::stylesheetlist::StyleSheetList; +use crate::dom::stylesheetlist::{StyleSheetList, StyleSheetListOwner}; use crate::dom::text::Text; use crate::dom::touch::Touch; use crate::dom::touchevent::TouchEvent; @@ -101,6 +101,7 @@ use crate::dom::windowproxy::WindowProxy; use crate::fetch::FetchCanceller; use crate::script_runtime::{CommonScriptMsg, ScriptThreadEventCategory}; use crate::script_thread::{MainThreadScriptMsg, ScriptThread}; +use crate::stylesheet_set::StylesheetSetRef; use crate::task::TaskBox; use crate::task_source::{TaskSource, TaskSourceName}; use crate::timers::OneshotTimerCallback; @@ -113,7 +114,6 @@ use euclid::Point2D; use html5ever::{LocalName, Namespace, QualName}; use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcSender}; -use js::jsapi::JS_GetRuntime; use js::jsapi::{JSContext, JSObject, JSRuntime}; use keyboard_types::{Key, KeyState, Modifiers}; use metrics::{ @@ -132,7 +132,7 @@ use num_traits::ToPrimitive; use profile_traits::ipc as profile_ipc; use profile_traits::time::{TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType}; use ref_slice::ref_slice; -use script_layout_interface::message::{Msg, NodesFromPointQueryType, QueryMsg, ReflowGoal}; +use script_layout_interface::message::{Msg, ReflowGoal}; use script_traits::{AnimationState, DocumentActivity, MouseButton, MouseEventType}; use script_traits::{MsDuration, ScriptMsg, TouchEventType, TouchId, UntrustedNodeAddress}; use servo_arc::Arc; @@ -144,7 +144,6 @@ use std::cell::{Cell, Ref, RefMut}; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::{HashMap, HashSet, VecDeque}; use std::default::Default; -use std::fmt; use std::mem; use std::ptr::NonNull; use std::rc::Rc; @@ -152,12 +151,12 @@ use std::time::{Duration, Instant}; use style::attr::AttrValue; use style::context::QuirksMode; use style::invalidation::element::restyle_hints::RestyleHint; -use style::media_queries::{Device, MediaList, MediaType}; +use style::media_queries::{Device, MediaType}; use style::selector_parser::{RestyleDamage, Snapshot}; -use style::shared_lock::{SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard}; +use style::shared_lock::SharedRwLock as StyleSharedRwLock; use style::str::{split_html_space_chars, str_join}; use style::stylesheet_set::DocumentStylesheetSet; -use style::stylesheets::{CssRule, Origin, OriginSet, Stylesheet}; +use style::stylesheets::{Origin, OriginSet, Stylesheet}; use url::percent_encoding::percent_decode; use url::Host; @@ -220,52 +219,11 @@ impl PendingRestyle { } } -#[derive(Clone, JSTraceable, MallocSizeOf)] -#[must_root] -struct StyleSheetInDocument { - #[ignore_malloc_size_of = "Arc"] - sheet: Arc<Stylesheet>, - owner: Dom<Element>, -} - -impl fmt::Debug for StyleSheetInDocument { - fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - self.sheet.fmt(formatter) - } -} - -impl PartialEq for StyleSheetInDocument { - fn eq(&self, other: &Self) -> bool { - Arc::ptr_eq(&self.sheet, &other.sheet) - } -} - -impl ::style::stylesheets::StylesheetInDocument for StyleSheetInDocument { - fn origin(&self, guard: &SharedRwLockReadGuard) -> Origin { - self.sheet.origin(guard) - } - - fn quirks_mode(&self, guard: &SharedRwLockReadGuard) -> QuirksMode { - self.sheet.quirks_mode(guard) - } - - fn enabled(&self) -> bool { - self.sheet.enabled() - } - - fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { - self.sheet.media(guard) - } - - fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { - self.sheet.rules(guard) - } -} - /// <https://dom.spec.whatwg.org/#document> #[dom_struct] pub struct Document { node: Node, + document_or_shadow_root: DocumentOrShadowRoot, window: Dom<Window>, implementation: MutNullableDom<DOMImplementation>, #[ignore_malloc_size_of = "type from external crate"] @@ -419,6 +377,10 @@ pub struct Document { delayed_tasks: DomRefCell<Vec<Box<dyn TaskBox>>>, /// https://html.spec.whatwg.org/multipage/#completely-loaded completely_loaded: Cell<bool>, + /// Set of shadow roots connected to the document tree. + shadow_roots: DomRefCell<HashSet<Dom<ShadowRoot>>>, + /// Whether any of the shadow roots need the stylesheets flushed. + shadow_roots_styles_changed: Cell<bool>, } #[derive(JSTraceable, MallocSizeOf)] @@ -634,7 +596,7 @@ impl Document { pub fn refresh_base_element(&self) { let base = self .upcast::<Node>() - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast::<HTMLBaseElement>) .find(|element| { element @@ -684,7 +646,7 @@ impl Document { } pub fn content_and_heritage_changed(&self, node: &Node) { - if node.is_in_doc() { + if node.is_connected() { node.note_dirty_descendants(); } @@ -720,55 +682,23 @@ impl Document { /// Remove any existing association between the provided id and any elements in this document. pub fn unregister_named_element(&self, to_unregister: &Element, id: Atom) { - debug!( - "Removing named element from document {:p}: {:p} id={}", - self, to_unregister, id - ); - // Limit the scope of the borrow because id_map might be borrowed again by - // GetElementById through the following sequence of calls - // reset_form_owner_for_listeners -> reset_form_owner -> GetElementById - { - let mut id_map = self.id_map.borrow_mut(); - let is_empty = match id_map.get_mut(&id) { - None => false, - Some(elements) => { - let position = elements - .iter() - .position(|element| &**element == to_unregister) - .expect("This element should be in registered."); - elements.remove(position); - elements.is_empty() - }, - }; - if is_empty { - id_map.remove(&id); - } - } + self.document_or_shadow_root + .unregister_named_element(&self.id_map, to_unregister, &id); self.reset_form_owner_for_listeners(&id); } /// Associate an element present in this document with the provided id. pub fn register_named_element(&self, element: &Element, id: Atom) { - debug!( - "Adding named element to document {:p}: {:p} id={}", - self, element, id - ); - assert!(element.upcast::<Node>().is_in_doc()); - assert!(!id.is_empty()); - let root = self.GetDocumentElement().expect( "The element is in the document, so there must be a document \ element.", ); - - // Limit the scope of the borrow because id_map might be borrowed again by - // GetElementById through the following sequence of calls - // reset_form_owner_for_listeners -> reset_form_owner -> GetElementById - { - let mut id_map = self.id_map.borrow_mut(); - let elements = id_map.entry(id.clone()).or_insert(Vec::new()); - elements.insert_pre_order(element, root.upcast::<Node>()); - } + self.document_or_shadow_root.register_named_element( + &self.id_map, + element, + &id, + DomRoot::from_ref(root.upcast::<Node>()), + ); self.reset_form_owner_for_listeners(&id); } @@ -872,7 +802,7 @@ impl Document { }; let doc_node = self.upcast::<Node>(); doc_node - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast) .find(|node| check_anchor(&node)) .map(DomRoot::upcast) @@ -981,7 +911,7 @@ impl Document { pub fn dirty_all_nodes(&self) { let root = self.upcast::<Node>(); - for node in root.traverse_preorder() { + for node in root.traverse_preorder(ShadowIncluding::Yes) { node.dirty(NodeDamage::OtherNodeDamage) } } @@ -1005,7 +935,7 @@ impl Document { let el = node_address.and_then(|address| { let node = unsafe { node::from_untrusted_node_address(js_runtime, address) }; - node.inclusive_ancestors() + node.inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::<Element>) .next() }); @@ -1189,7 +1119,7 @@ impl Document { let maybe_new_target = node_address.and_then(|address| { let node = unsafe { node::from_untrusted_node_address(js_runtime, address) }; - node.inclusive_ancestors() + node.inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::<Element>) .next() }); @@ -1225,7 +1155,7 @@ impl Document { if !old_target_is_ancestor_of_new_target { for element in old_target .upcast::<Node>() - .inclusive_ancestors() + .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::<Element>) { element.set_hover_state(false); @@ -1243,7 +1173,7 @@ impl Document { if let Some(ref new_target) = maybe_new_target { for element in new_target .upcast::<Node>() - .inclusive_ancestors() + .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::<Element>) { if element.hover_state() { @@ -1285,7 +1215,7 @@ impl Document { let el = node_address.and_then(|address| { let node = unsafe { node::from_untrusted_node_address(js_runtime, address) }; - node.inclusive_ancestors() + node.inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::<Element>) .next() }); @@ -2276,7 +2206,7 @@ impl Document { /// Iterate over all iframes in the document. pub fn iter_iframes(&self) -> impl Iterator<Item = DomRoot<HTMLIFrameElement>> { self.upcast::<Node>() - .traverse_preorder() + .traverse_preorder(ShadowIncluding::Yes) .filter_map(DomRoot::downcast::<HTMLIFrameElement>) } @@ -2395,21 +2325,6 @@ impl Document { !self.has_browsing_context || !url_has_network_scheme(&self.url()) } - pub fn nodes_from_point( - &self, - client_point: &Point2D<f32>, - reflow_goal: NodesFromPointQueryType, - ) -> Vec<UntrustedNodeAddress> { - if !self - .window - .layout_reflow(QueryMsg::NodesFromPointQuery(*client_point, reflow_goal)) - { - return vec![]; - }; - - self.window.layout().nodes_from_point_response() - } - /// <https://html.spec.whatwg.org/multipage/#look-up-a-custom-element-definition> pub fn lookup_custom_element_definition( &self, @@ -2485,6 +2400,9 @@ pub trait LayoutDocumentHelpers { unsafe fn will_paint(&self); unsafe fn quirks_mode(&self) -> QuirksMode; unsafe fn style_shared_lock(&self) -> &StyleSharedRwLock; + unsafe fn shadow_roots(&self) -> Vec<LayoutDom<ShadowRoot>>; + unsafe fn shadow_roots_styles_changed(&self) -> bool; + unsafe fn flush_shadow_roots_stylesheets(&self); } #[allow(unsafe_code)] @@ -2500,12 +2418,12 @@ impl LayoutDocumentHelpers for LayoutDom<Document> { let mut elements = (*self.unsafe_get()) .pending_restyles .borrow_mut_for_layout(); - // Elements were in a document when they were adding to this list, but that + // Elements were in a document when they were added to this list, but that // may no longer be true when the next layout occurs. let result = elements .drain() .map(|(k, v)| (k.to_layout(), v)) - .filter(|&(ref k, _)| k.upcast::<Node>().get_flag(NodeFlags::IS_IN_DOC)) + .filter(|&(ref k, _)| k.upcast::<Node>().get_flag(NodeFlags::IS_CONNECTED)) .collect(); result } @@ -2529,6 +2447,26 @@ impl LayoutDocumentHelpers for LayoutDom<Document> { unsafe fn style_shared_lock(&self) -> &StyleSharedRwLock { (*self.unsafe_get()).style_shared_lock() } + + #[inline] + unsafe fn shadow_roots(&self) -> Vec<LayoutDom<ShadowRoot>> { + (*self.unsafe_get()) + .shadow_roots + .borrow_for_layout() + .iter() + .map(|sr| sr.to_layout()) + .collect() + } + + #[inline] + unsafe fn shadow_roots_styles_changed(&self) -> bool { + (*self.unsafe_get()).shadow_roots_styles_changed() + } + + #[inline] + unsafe fn flush_shadow_roots_stylesheets(&self) { + (*self.unsafe_get()).flush_shadow_roots_stylesheets() + } } // https://html.spec.whatwg.org/multipage/#is-a-registrable-domain-suffix-of-or-is-equal-to @@ -2637,21 +2575,23 @@ impl Document { .and_then(|charset| Encoding::for_label(charset.as_str().as_bytes())) .unwrap_or(UTF_8); + let has_browsing_context = has_browsing_context == HasBrowsingContext::Yes; Document { node: Node::new_document_node(), + document_or_shadow_root: DocumentOrShadowRoot::new(window), window: Dom::from_ref(window), - has_browsing_context: has_browsing_context == HasBrowsingContext::Yes, + has_browsing_context, implementation: Default::default(), content_type, last_modified: last_modified, url: DomRefCell::new(url), // https://dom.spec.whatwg.org/#concept-document-quirks quirks_mode: Cell::new(QuirksMode::NoQuirks), + id_map: DomRefCell::new(HashMap::new()), // https://dom.spec.whatwg.org/#concept-document-encoding encoding: Cell::new(encoding), is_html_document: is_html_document == IsHTMLDocument::HTMLDocument, activity: Cell::new(activity), - id_map: DomRefCell::new(HashMap::new()), tag_map: DomRefCell::new(HashMap::new()), tagns_map: DomRefCell::new(HashMap::new()), classes_map: DomRefCell::new(HashMap::new()), @@ -2689,7 +2629,7 @@ impl Document { deferred_scripts: Default::default(), asap_in_order_scripts_list: Default::default(), asap_scripts_set: Default::default(), - scripting_enabled: has_browsing_context == HasBrowsingContext::Yes, + scripting_enabled: has_browsing_context, animation_frame_ident: Cell::new(0), animation_frame_list: DomRefCell::new(vec![]), running_animation_callbacks: Cell::new(false), @@ -2735,6 +2675,8 @@ impl Document { completely_loaded: Cell::new(false), script_and_layout_blockers: Cell::new(0), delayed_tasks: Default::default(), + shadow_roots: DomRefCell::new(HashSet::new()), + shadow_roots_styles_changed: Cell::new(false), } } @@ -2853,7 +2795,7 @@ impl Document { let maybe_node = doc.deref().map(Castable::upcast::<Node>); let iter = maybe_node .iter() - .flat_map(|node| node.traverse_preorder()) + .flat_map(|node| node.traverse_preorder(ShadowIncluding::No)) .filter(|node| callback(&node)); NodeList::new_simple_list(&self.window, iter) } @@ -2893,93 +2835,10 @@ impl Document { Device::new(MediaType::screen(), viewport_size, device_pixel_ratio) } - /// Remove a stylesheet owned by `owner` from the list of document sheets. - #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. - pub fn remove_stylesheet(&self, owner: &Element, s: &Arc<Stylesheet>) { - self.window() - .layout_chan() - .send(Msg::RemoveStylesheet(s.clone())) - .unwrap(); - - let guard = s.shared_lock.read(); - - // FIXME(emilio): Would be nice to remove the clone, etc. - self.stylesheets.borrow_mut().remove_stylesheet( - None, - StyleSheetInDocument { - sheet: s.clone(), - owner: Dom::from_ref(owner), - }, - &guard, - ); - } - - /// Add a stylesheet owned by `owner` to the list of document sheets, in the - /// correct tree position. - #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. - pub fn add_stylesheet(&self, owner: &Element, sheet: Arc<Stylesheet>) { - // FIXME(emilio): It'd be nice to unify more code between the elements - // that own stylesheets, but StylesheetOwner is more about loading - // them... - debug_assert!( - owner.as_stylesheet_owner().is_some() || owner.is::<HTMLMetaElement>(), - "Wat" - ); - - let mut stylesheets = self.stylesheets.borrow_mut(); - let insertion_point = stylesheets - .iter() - .map(|(sheet, _origin)| sheet) - .find(|sheet_in_doc| { - owner - .upcast::<Node>() - .is_before(sheet_in_doc.owner.upcast()) - }) - .cloned(); - - self.window() - .layout_chan() - .send(Msg::AddStylesheet( - sheet.clone(), - insertion_point.as_ref().map(|s| s.sheet.clone()), - )) - .unwrap(); - - let sheet = StyleSheetInDocument { - sheet, - owner: Dom::from_ref(owner), - }; - - let lock = self.style_shared_lock(); - let guard = lock.read(); - - match insertion_point { - Some(ip) => { - stylesheets.insert_stylesheet_before(None, sheet, ip, &guard); - }, - None => { - stylesheets.append_stylesheet(None, sheet, &guard); - }, - } - } - - /// Returns the number of document stylesheets. - pub fn stylesheet_count(&self) -> usize { - self.stylesheets.borrow().len() - } - pub fn salvageable(&self) -> bool { self.salvageable.get() } - pub fn stylesheet_at(&self, index: usize) -> Option<DomRoot<CSSStyleSheet>> { - let stylesheets = self.stylesheets.borrow(); - - stylesheets - .get(Origin::Author, index) - .and_then(|s| s.owner.upcast::<Node>().get_cssom_stylesheet()) - } - /// <https://html.spec.whatwg.org/multipage/#appropriate-template-contents-owner-document> pub fn appropriate_template_contents_owner_document(&self) -> DomRoot<Document> { self.appropriate_template_contents_owner_document @@ -3270,6 +3129,92 @@ impl Document { } } } + + pub fn register_shadow_root(&self, shadow_root: &ShadowRoot) { + self.shadow_roots + .borrow_mut() + .insert(Dom::from_ref(shadow_root)); + self.invalidate_shadow_roots_stylesheets(); + } + + pub fn unregister_shadow_root(&self, shadow_root: &ShadowRoot) { + let mut shadow_roots = self.shadow_roots.borrow_mut(); + shadow_roots.remove(&Dom::from_ref(shadow_root)); + } + + pub fn invalidate_shadow_roots_stylesheets(&self) { + self.shadow_roots_styles_changed.set(true); + } + + pub fn shadow_roots_styles_changed(&self) -> bool { + self.shadow_roots_styles_changed.get() + } + + pub fn flush_shadow_roots_stylesheets(&self) { + if !self.shadow_roots_styles_changed.get() { + return; + } + self.shadow_roots_styles_changed.set(false); + } + + pub fn stylesheet_count(&self) -> usize { + self.stylesheets.borrow().len() + } + + pub fn stylesheet_at(&self, index: usize) -> Option<DomRoot<CSSStyleSheet>> { + let stylesheets = self.stylesheets.borrow(); + + stylesheets + .get(Origin::Author, index) + .and_then(|s| s.owner.upcast::<Node>().get_cssom_stylesheet()) + } + + /// Add a stylesheet owned by `owner` to the list of document sheets, in the + /// correct tree position. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn add_stylesheet(&self, owner: &Element, sheet: Arc<Stylesheet>) { + let stylesheets = &mut *self.stylesheets.borrow_mut(); + let insertion_point = stylesheets + .iter() + .map(|(sheet, _origin)| sheet) + .find(|sheet_in_doc| { + owner + .upcast::<Node>() + .is_before(sheet_in_doc.owner.upcast()) + }) + .cloned(); + + self.window + .layout_chan() + .send(Msg::AddStylesheet( + sheet.clone(), + insertion_point.as_ref().map(|s| s.sheet.clone()), + )) + .unwrap(); + + DocumentOrShadowRoot::add_stylesheet( + owner, + StylesheetSetRef::Document(stylesheets), + sheet, + insertion_point, + self.style_shared_lock(), + ); + } + + /// Remove a stylesheet owned by `owner` from the list of document sheets. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn remove_stylesheet(&self, owner: &Element, s: &Arc<Stylesheet>) { + self.window + .layout_chan() + .send(Msg::RemoveStylesheet(s.clone())) + .unwrap(); + + DocumentOrShadowRoot::remove_stylesheet( + owner, + s, + StylesheetSetRef::Document(&mut *self.stylesheets.borrow_mut()), + ) + } } impl Element { @@ -3301,8 +3246,12 @@ impl ProfilerMetadataFactory for Document { impl DocumentMethods for Document { // https://drafts.csswg.org/cssom/#dom-document-stylesheets fn StyleSheets(&self) -> DomRoot<StyleSheetList> { - self.stylesheet_list - .or_init(|| StyleSheetList::new(&self.window, Dom::from_ref(&self))) + self.stylesheet_list.or_init(|| { + StyleSheetList::new( + &self.window, + StyleSheetListOwner::Document(Dom::from_ref(self)), + ) + }) } // https://dom.spec.whatwg.org/#dom-document-implementation @@ -3317,16 +3266,11 @@ impl DocumentMethods for Document { // https://html.spec.whatwg.org/multipage/#dom-document-activeelement fn GetActiveElement(&self) -> Option<DomRoot<Element>> { - // TODO: Step 2. - - match self.get_focused_element() { - Some(element) => Some(element), // Step 3. and 4. - None => match self.GetBody() { - // Step 5. - Some(body) => Some(DomRoot::upcast(body)), - None => self.GetDocumentElement(), - }, - } + self.document_or_shadow_root.get_active_element( + self.get_focused_element(), + self.GetBody(), + self.GetDocumentElement(), + ) } // https://html.spec.whatwg.org/multipage/#dom-document-hasfocus @@ -3643,7 +3587,7 @@ impl DocumentMethods for Document { // https://dom.spec.whatwg.org/#dom-document-importnode fn ImportNode(&self, node: &Node, deep: bool) -> Fallible<DomRoot<Node>> { // Step 1. - if node.is::<Document>() { + if node.is::<Document>() || node.is::<ShadowRoot>() { return Err(Error::NotSupported); } @@ -3665,9 +3609,14 @@ impl DocumentMethods for Document { } // Step 2. - Node::adopt(node, self); + if node.is::<ShadowRoot>() { + return Err(Error::HierarchyRequest); + } // Step 3. + Node::adopt(node, self); + + // Step 4. Ok(DomRoot::from_ref(node)) } @@ -3805,7 +3754,7 @@ impl DocumentMethods for Document { } else { // Step 2. root.upcast::<Node>() - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .find(|node| node.is::<HTMLTitleElement>()) } }); @@ -3852,7 +3801,7 @@ impl DocumentMethods for Document { } else if root.namespace() == &ns!(html) { let elem = root .upcast::<Node>() - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .find(|node| node.is::<HTMLTitleElement>()); match elem { Some(elem) => elem, @@ -4219,7 +4168,7 @@ impl DocumentMethods for Document { { // Step 1. let mut elements = root - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .filter(|node| filter_by_name(&name, &node)) .peekable(); if let Some(first) = elements.next() { @@ -4273,82 +4222,24 @@ impl DocumentMethods for Document { SetOnreadystatechange ); - #[allow(unsafe_code)] // https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint fn ElementFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Option<DomRoot<Element>> { - let x = *x as f32; - let y = *y as f32; - let point = &Point2D::new(x, y); - let window = window_from_node(self); - let viewport = window.window_size().initial_viewport; - - if self.browsing_context().is_none() { - return None; - } - - if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height { - return None; - } - - match self - .nodes_from_point(point, NodesFromPointQueryType::Topmost) - .first() - { - Some(address) => { - let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) }; - let node = unsafe { node::from_untrusted_node_address(js_runtime, *address) }; - let parent_node = node.GetParentNode().unwrap(); - let element_ref = node - .downcast::<Element>() - .unwrap_or_else(|| parent_node.downcast::<Element>().unwrap()); - - Some(DomRoot::from_ref(element_ref)) - }, - None => self.GetDocumentElement(), - } + self.document_or_shadow_root.element_from_point( + x, + y, + self.GetDocumentElement(), + self.has_browsing_context, + ) } - #[allow(unsafe_code)] // https://drafts.csswg.org/cssom-view/#dom-document-elementsfrompoint fn ElementsFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Vec<DomRoot<Element>> { - let x = *x as f32; - let y = *y as f32; - let point = &Point2D::new(x, y); - let window = window_from_node(self); - let viewport = window.window_size().initial_viewport; - - if self.browsing_context().is_none() { - return vec![]; - } - - // Step 2 - if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height { - return vec![]; - } - - let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) }; - - // Step 1 and Step 3 - let nodes = self.nodes_from_point(point, NodesFromPointQueryType::All); - let mut elements: Vec<DomRoot<Element>> = nodes - .iter() - .flat_map(|&untrusted_node_address| { - let node = unsafe { - node::from_untrusted_node_address(js_runtime, untrusted_node_address) - }; - DomRoot::downcast::<Element>(node) - }) - .collect(); - - // Step 4 - if let Some(root_element) = self.GetDocumentElement() { - if elements.last() != Some(&root_element) { - elements.push(root_element); - } - } - - // Step 5 - elements + self.document_or_shadow_root.elements_from_point( + x, + y, + self.GetDocumentElement(), + self.has_browsing_context, + ) } // https://html.spec.whatwg.org/multipage/#dom-document-open @@ -4403,7 +4294,10 @@ impl DocumentMethods for Document { } // Step 8 - for node in self.upcast::<Node>().traverse_preorder() { + for node in self + .upcast::<Node>() + .traverse_preorder(ShadowIncluding::Yes) + { node.upcast::<EventTarget>().remove_all_listeners(); } diff --git a/components/script/dom/documentfragment.rs b/components/script/dom/documentfragment.rs index 2e504750947..1561b0d007e 100644 --- a/components/script/dom/documentfragment.rs +++ b/components/script/dom/documentfragment.rs @@ -2,13 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::DocumentFragmentBinding; use crate::dom::bindings::codegen::Bindings::DocumentFragmentBinding::DocumentFragmentMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; use crate::dom::bindings::codegen::UnionTypes::NodeOrString; use crate::dom::bindings::error::{ErrorResult, Fallible}; use crate::dom::bindings::inheritance::Castable; -use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::DOMString; use crate::dom::document::Document; use crate::dom::element::Element; @@ -18,18 +19,22 @@ use crate::dom::nodelist::NodeList; use crate::dom::window::Window; use dom_struct::dom_struct; use servo_atoms::Atom; +use std::collections::HashMap; // https://dom.spec.whatwg.org/#documentfragment #[dom_struct] pub struct DocumentFragment { node: Node, + /// Caches for the getElement methods + id_map: DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>, } impl DocumentFragment { /// Creates a new DocumentFragment. - fn new_inherited(document: &Document) -> DocumentFragment { + pub fn new_inherited(document: &Document) -> DocumentFragment { DocumentFragment { node: Node::new_inherited(document), + id_map: DomRefCell::new(HashMap::new()), } } @@ -46,6 +51,10 @@ impl DocumentFragment { Ok(DocumentFragment::new(&document)) } + + pub fn id_map(&self) -> &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>> { + &self.id_map + } } impl DocumentFragmentMethods for DocumentFragment { @@ -57,16 +66,11 @@ impl DocumentFragmentMethods for DocumentFragment { // https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid fn GetElementById(&self, id: DOMString) -> Option<DomRoot<Element>> { - let node = self.upcast::<Node>(); let id = Atom::from(id); - node.traverse_preorder() - .filter_map(DomRoot::downcast::<Element>) - .find( - |descendant| match descendant.get_attribute(&ns!(), &local_name!("id")) { - None => false, - Some(attr) => *attr.value().as_atom() == id, - }, - ) + self.id_map + .borrow() + .get(&id) + .map(|ref elements| DomRoot::from_ref(&*(*elements)[0])) } // https://dom.spec.whatwg.org/#dom-parentnode-firstelementchild diff --git a/components/script/dom/documentorshadowroot.rs b/components/script/dom/documentorshadowroot.rs new file mode 100644 index 00000000000..8a29d16d013 --- /dev/null +++ b/components/script/dom/documentorshadowroot.rs @@ -0,0 +1,312 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::element::Element; +use crate::dom::htmlelement::HTMLElement; +use crate::dom::htmlmetaelement::HTMLMetaElement; +use crate::dom::node::{self, Node, VecPreOrderInsertionHelper}; +use crate::dom::window::Window; +use crate::stylesheet_set::StylesheetSetRef; +use euclid::Point2D; +use js::jsapi::JS_GetRuntime; +use script_layout_interface::message::{NodesFromPointQueryType, QueryMsg}; +use script_traits::UntrustedNodeAddress; +use servo_arc::Arc; +use servo_atoms::Atom; +use std::collections::HashMap; +use std::fmt; +use style::context::QuirksMode; +use style::invalidation::media_queries::{MediaListKey, ToMediaListKey}; +use style::media_queries::MediaList; +use style::shared_lock::{SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard}; +use style::stylesheets::{CssRule, Origin, Stylesheet}; + +#[derive(Clone, JSTraceable, MallocSizeOf)] +#[must_root] +pub struct StyleSheetInDocument { + #[ignore_malloc_size_of = "Arc"] + pub sheet: Arc<Stylesheet>, + pub owner: Dom<Element>, +} + +impl fmt::Debug for StyleSheetInDocument { + fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + self.sheet.fmt(formatter) + } +} + +impl PartialEq for StyleSheetInDocument { + fn eq(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.sheet, &other.sheet) + } +} + +impl ToMediaListKey for StyleSheetInDocument { + fn to_media_list_key(&self) -> MediaListKey { + self.sheet.to_media_list_key() + } +} + +impl ::style::stylesheets::StylesheetInDocument for StyleSheetInDocument { + fn origin(&self, guard: &SharedRwLockReadGuard) -> Origin { + self.sheet.origin(guard) + } + + fn quirks_mode(&self, guard: &SharedRwLockReadGuard) -> QuirksMode { + self.sheet.quirks_mode(guard) + } + + fn enabled(&self) -> bool { + self.sheet.enabled() + } + + fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> { + self.sheet.media(guard) + } + + fn rules<'a, 'b: 'a>(&'a self, guard: &'b SharedRwLockReadGuard) -> &'a [CssRule] { + self.sheet.rules(guard) + } +} + +// https://w3c.github.io/webcomponents/spec/shadow/#extensions-to-the-documentorshadowroot-mixin +#[must_root] +#[derive(JSTraceable, MallocSizeOf)] +pub struct DocumentOrShadowRoot { + window: Dom<Window>, +} + +impl DocumentOrShadowRoot { + pub fn new(window: &Window) -> Self { + Self { + window: Dom::from_ref(window), + } + } + + pub fn nodes_from_point( + &self, + client_point: &Point2D<f32>, + reflow_goal: NodesFromPointQueryType, + ) -> Vec<UntrustedNodeAddress> { + if !self + .window + .layout_reflow(QueryMsg::NodesFromPointQuery(*client_point, reflow_goal)) + { + return vec![]; + }; + + self.window.layout().nodes_from_point_response() + } + + #[allow(unsafe_code)] + // https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint + pub fn element_from_point( + &self, + x: Finite<f64>, + y: Finite<f64>, + document_element: Option<DomRoot<Element>>, + has_browsing_context: bool, + ) -> Option<DomRoot<Element>> { + let x = *x as f32; + let y = *y as f32; + let point = &Point2D::new(x, y); + let viewport = self.window.window_size().initial_viewport; + + if !has_browsing_context { + return None; + } + + if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height { + return None; + } + + match self + .nodes_from_point(point, NodesFromPointQueryType::Topmost) + .first() + { + Some(address) => { + let js_runtime = unsafe { JS_GetRuntime(self.window.get_cx()) }; + let node = unsafe { node::from_untrusted_node_address(js_runtime, *address) }; + let parent_node = node.GetParentNode().unwrap(); + let element_ref = node + .downcast::<Element>() + .unwrap_or_else(|| parent_node.downcast::<Element>().unwrap()); + + Some(DomRoot::from_ref(element_ref)) + }, + None => document_element, + } + } + + #[allow(unsafe_code)] + // https://drafts.csswg.org/cssom-view/#dom-document-elementsfrompoint + pub fn elements_from_point( + &self, + x: Finite<f64>, + y: Finite<f64>, + document_element: Option<DomRoot<Element>>, + has_browsing_context: bool, + ) -> Vec<DomRoot<Element>> { + let x = *x as f32; + let y = *y as f32; + let point = &Point2D::new(x, y); + let viewport = self.window.window_size().initial_viewport; + + if !has_browsing_context { + return vec![]; + } + + // Step 2 + if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height { + return vec![]; + } + + let js_runtime = unsafe { JS_GetRuntime(self.window.get_cx()) }; + + // Step 1 and Step 3 + let nodes = self.nodes_from_point(point, NodesFromPointQueryType::All); + let mut elements: Vec<DomRoot<Element>> = nodes + .iter() + .flat_map(|&untrusted_node_address| { + let node = unsafe { + node::from_untrusted_node_address(js_runtime, untrusted_node_address) + }; + DomRoot::downcast::<Element>(node) + }) + .collect(); + + // Step 4 + if let Some(root_element) = document_element { + if elements.last() != Some(&root_element) { + elements.push(root_element); + } + } + + // Step 5 + elements + } + + // https://html.spec.whatwg.org/multipage/#dom-document-activeelement + pub fn get_active_element( + &self, + focused_element: Option<DomRoot<Element>>, + body: Option<DomRoot<HTMLElement>>, + document_element: Option<DomRoot<Element>>, + ) -> Option<DomRoot<Element>> { + // TODO: Step 2. + + match focused_element { + Some(element) => Some(element), // Step 3. and 4. + None => match body { + // Step 5. + Some(body) => Some(DomRoot::upcast(body)), + None => document_element, + }, + } + } + + /// Remove a stylesheet owned by `owner` from the list of document sheets. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn remove_stylesheet( + owner: &Element, + s: &Arc<Stylesheet>, + mut stylesheets: StylesheetSetRef<StyleSheetInDocument>, + ) { + let guard = s.shared_lock.read(); + + // FIXME(emilio): Would be nice to remove the clone, etc. + stylesheets.remove_stylesheet( + None, + StyleSheetInDocument { + sheet: s.clone(), + owner: Dom::from_ref(owner), + }, + &guard, + ); + } + + /// Add a stylesheet owned by `owner` to the list of document sheets, in the + /// correct tree position. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn add_stylesheet( + owner: &Element, + mut stylesheets: StylesheetSetRef<StyleSheetInDocument>, + sheet: Arc<Stylesheet>, + insertion_point: Option<StyleSheetInDocument>, + style_shared_lock: &StyleSharedRwLock, + ) { + // FIXME(emilio): It'd be nice to unify more code between the elements + // that own stylesheets, but StylesheetOwner is more about loading + // them... + debug_assert!( + owner.as_stylesheet_owner().is_some() || owner.is::<HTMLMetaElement>(), + "Wat" + ); + + let sheet = StyleSheetInDocument { + sheet, + owner: Dom::from_ref(owner), + }; + + let guard = style_shared_lock.read(); + + match insertion_point { + Some(ip) => { + stylesheets.insert_stylesheet_before(None, sheet, ip, &guard); + }, + None => { + stylesheets.append_stylesheet(None, sheet, &guard); + }, + } + } + + /// Remove any existing association between the provided id and any elements in this document. + pub fn unregister_named_element( + &self, + id_map: &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>, + to_unregister: &Element, + id: &Atom, + ) { + debug!( + "Removing named element {:p}: {:p} id={}", + self, to_unregister, id + ); + let mut id_map = id_map.borrow_mut(); + let is_empty = match id_map.get_mut(&id) { + None => false, + Some(elements) => { + let position = elements + .iter() + .position(|element| &**element == to_unregister) + .expect("This element should be in registered."); + elements.remove(position); + elements.is_empty() + }, + }; + if is_empty { + id_map.remove(&id); + } + } + + /// Associate an element present in this document with the provided id. + pub fn register_named_element( + &self, + id_map: &DomRefCell<HashMap<Atom, Vec<Dom<Element>>>>, + element: &Element, + id: &Atom, + root: DomRoot<Node>, + ) { + debug!("Adding named element {:p}: {:p} id={}", self, element, id); + assert!(element.upcast::<Node>().is_connected()); + assert!(!id.is_empty()); + let mut id_map = id_map.borrow_mut(); + let elements = id_map.entry(id.clone()).or_insert(Vec::new()); + elements.insert_pre_order(element, &root); + } +} 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 diff --git a/components/script/dom/htmlbaseelement.rs b/components/script/dom/htmlbaseelement.rs index 6823925cb37..71aa235df62 100644 --- a/components/script/dom/htmlbaseelement.rs +++ b/components/script/dom/htmlbaseelement.rs @@ -11,7 +11,7 @@ use crate::dom::bindings::str::DOMString; use crate::dom::document::Document; use crate::dom::element::{AttributeMutation, Element}; use crate::dom::htmlelement::HTMLElement; -use crate::dom::node::{document_from_node, Node, UnbindContext}; +use crate::dom::node::{document_from_node, BindContext, Node, UnbindContext}; use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; @@ -64,7 +64,7 @@ impl HTMLBaseElement { /// Update the cached base element in response to binding or unbinding from /// a tree. pub fn bind_unbind(&self, tree_in_doc: bool) { - if !tree_in_doc { + if !tree_in_doc || self.upcast::<Node>().containing_shadow_root().is_some() { return; } @@ -119,9 +119,9 @@ impl VirtualMethods for HTMLBaseElement { } } - fn bind_to_tree(&self, tree_in_doc: bool) { - self.super_type().unwrap().bind_to_tree(tree_in_doc); - self.bind_unbind(tree_in_doc); + fn bind_to_tree(&self, context: &BindContext) { + self.super_type().unwrap().bind_to_tree(context); + self.bind_unbind(context.tree_in_doc); } fn unbind_from_tree(&self, context: &UnbindContext) { diff --git a/components/script/dom/htmlbodyelement.rs b/components/script/dom/htmlbodyelement.rs index 951643c7ea7..f59d163d163 100644 --- a/components/script/dom/htmlbodyelement.rs +++ b/components/script/dom/htmlbodyelement.rs @@ -14,7 +14,7 @@ use crate::dom::document::Document; use crate::dom::element::{AttributeMutation, Element, RawLayoutElementHelpers}; use crate::dom::eventtarget::EventTarget; use crate::dom::htmlelement::HTMLElement; -use crate::dom::node::{document_from_node, window_from_node, Node}; +use crate::dom::node::{document_from_node, window_from_node, BindContext, Node}; use crate::dom::virtualmethods::VirtualMethods; use cssparser::RGBA; use dom_struct::dom_struct; @@ -149,12 +149,12 @@ impl VirtualMethods for HTMLBodyElement { .attribute_affects_presentational_hints(attr) } - 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 !tree_in_doc { + if !context.tree_in_doc { return; } diff --git a/components/script/dom/htmlbuttonelement.rs b/components/script/dom/htmlbuttonelement.rs index 96b51b472c4..a7b8dacd060 100755 --- a/components/script/dom/htmlbuttonelement.rs +++ b/components/script/dom/htmlbuttonelement.rs @@ -18,7 +18,7 @@ use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; use crate::dom::htmlformelement::HTMLFormElement; use crate::dom::htmlformelement::{FormControl, FormDatum, FormDatumValue}; use crate::dom::htmlformelement::{FormSubmitter, ResetFrom, SubmittedFrom}; -use crate::dom::node::{document_from_node, window_from_node, Node, UnbindContext}; +use crate::dom::node::{document_from_node, window_from_node, BindContext, Node, UnbindContext}; use crate::dom::nodelist::NodeList; use crate::dom::validation::Validatable; use crate::dom::validitystate::{ValidationFlags, ValidityState}; @@ -232,9 +232,9 @@ impl VirtualMethods for HTMLButtonElement { } } - 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); } self.upcast::<Element>() diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index ade940c536e..95ac9918996 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -28,7 +28,7 @@ use crate::dom::htmlhtmlelement::HTMLHtmlElement; use crate::dom::htmlinputelement::{HTMLInputElement, InputType}; use crate::dom::htmllabelelement::HTMLLabelElement; use crate::dom::node::{document_from_node, window_from_node}; -use crate::dom::node::{Node, NodeFlags}; +use crate::dom::node::{BindContext, Node, NodeFlags, ShadowIncluding}; use crate::dom::nodelist::NodeList; use crate::dom::text::Text; use crate::dom::virtualmethods::VirtualMethods; @@ -459,7 +459,7 @@ impl HTMLElementMethods for HTMLElement { let element = self.upcast::<Element>(); // Step 1. - let element_not_rendered = !node.is_in_doc() || !element.has_css_layout_box(); + let element_not_rendered = !node.is_connected() || !element.has_css_layout_box(); if element_not_rendered { return node.GetTextContent().unwrap(); } @@ -707,7 +707,7 @@ impl HTMLElement { let root_element = element.root_element(); let root_node = root_element.upcast::<Node>(); let children = root_node - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast::<Element>) .filter(|elem| elem.is::<HTMLLabelElement>()) .filter(|elem| elem.get_string_attribute(&local_name!("for")) == id) @@ -740,9 +740,9 @@ impl VirtualMethods for HTMLElement { } } - 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); } self.update_sequentially_focusable_status(); } diff --git a/components/script/dom/htmlfieldsetelement.rs b/components/script/dom/htmlfieldsetelement.rs index d8e92180e56..4f97f7e66c7 100644 --- a/components/script/dom/htmlfieldsetelement.rs +++ b/components/script/dom/htmlfieldsetelement.rs @@ -13,7 +13,7 @@ use crate::dom::htmlcollection::{CollectionFilter, HTMLCollection}; use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlformelement::{FormControl, HTMLFormElement}; use crate::dom::htmllegendelement::HTMLLegendElement; -use crate::dom::node::{window_from_node, Node}; +use crate::dom::node::{window_from_node, Node, ShadowIncluding}; use crate::dom::validitystate::ValidityState; use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; @@ -128,7 +128,7 @@ impl VirtualMethods for HTMLFieldSetElement { }); let fields = children.flat_map(|child| { child - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .filter(|descendant| match descendant.type_id() { NodeTypeId::Element(ElementTypeId::HTMLElement( HTMLElementTypeId::HTMLButtonElement, diff --git a/components/script/dom/htmlformelement.rs b/components/script/dom/htmlformelement.rs index 31087dc11df..9e988515ac0 100755 --- a/components/script/dom/htmlformelement.rs +++ b/components/script/dom/htmlformelement.rs @@ -41,7 +41,8 @@ use crate::dom::htmloutputelement::HTMLOutputElement; use crate::dom::htmlselectelement::HTMLSelectElement; use crate::dom::htmltextareaelement::HTMLTextAreaElement; use crate::dom::node::{document_from_node, window_from_node}; -use crate::dom::node::{Node, NodeFlags, UnbindContext, VecPreOrderInsertionHelper}; +use crate::dom::node::{Node, NodeFlags, ShadowIncluding}; +use crate::dom::node::{UnbindContext, VecPreOrderInsertionHelper}; use crate::dom::validitystate::ValidationFlags; use crate::dom::virtualmethods::VirtualMethods; use crate::dom::window::Window; @@ -582,7 +583,7 @@ impl HTMLFormElement { // form, refactor this when html5ever's form owner PR lands // Step 1-3 let invalid_controls = node - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .filter_map(|field| { if let Some(el) = field.downcast::<Element>() { if el.disabled_state() { @@ -1100,7 +1101,7 @@ pub trait FormControl: DomObject { let form_id = elem.get_string_attribute(&local_name!("form")); let node = elem.upcast::<Node>(); - if self.is_listed() && !form_id.is_empty() && node.is_in_doc() { + if self.is_listed() && !form_id.is_empty() && node.is_connected() { let doc = document_from_node(node); doc.register_form_id_listener(form_id, self); } diff --git a/components/script/dom/htmlheadelement.rs b/components/script/dom/htmlheadelement.rs index 5daf82fd896..0389ff9b43b 100644 --- a/components/script/dom/htmlheadelement.rs +++ b/components/script/dom/htmlheadelement.rs @@ -10,7 +10,7 @@ use crate::dom::document::{determine_policy_for_token, Document}; use crate::dom::element::Element; use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlmetaelement::HTMLMetaElement; -use crate::dom::node::{document_from_node, Node}; +use crate::dom::node::{document_from_node, BindContext, Node, ShadowIncluding}; use crate::dom::userscripts::load_script; use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; @@ -55,7 +55,7 @@ impl HTMLHeadElement { let node = self.upcast::<Node>(); let candidates = node - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast::<Element>) .filter(|elem| elem.is::<HTMLMetaElement>()) .filter(|elem| elem.get_string_attribute(&local_name!("name")) == "referrer") @@ -81,9 +81,9 @@ impl VirtualMethods for HTMLHeadElement { fn super_type(&self) -> Option<&dyn VirtualMethods> { Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } - 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); } load_script(self); } diff --git a/components/script/dom/htmliframeelement.rs b/components/script/dom/htmliframeelement.rs index 24cea710450..a85b9bfa915 100644 --- a/components/script/dom/htmliframeelement.rs +++ b/components/script/dom/htmliframeelement.rs @@ -19,7 +19,9 @@ use crate::dom::element::{AttributeMutation, Element, RawLayoutElementHelpers}; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlelement::HTMLElement; -use crate::dom::node::{document_from_node, window_from_node, Node, NodeDamage, UnbindContext}; +use crate::dom::node::{ + document_from_node, window_from_node, BindContext, Node, NodeDamage, UnbindContext, +}; use crate::dom::virtualmethods::VirtualMethods; use crate::dom::window::ReflowReason; use crate::dom::windowproxy::WindowProxy; @@ -584,7 +586,7 @@ impl VirtualMethods for HTMLIFrameElement { // may be in a different script thread. Instread, we check to see if the parent // is in a document tree and has a browsing context, which is what causes // the child browsing context to be created. - if self.upcast::<Node>().is_in_doc_with_browsing_context() { + if self.upcast::<Node>().is_connected_with_browsing_context() { debug!("iframe src set while in browsing context."); self.process_the_iframe_attributes(ProcessingMode::NotFirstTime); } @@ -610,11 +612,12 @@ impl VirtualMethods for HTMLIFrameElement { } } - 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); } + let tree_connected = context.tree_connected; let iframe = Trusted::new(self); document_from_node(self).add_delayed_task(task!(IFrameDelayedInitialize: move || { let this = iframe.root(); @@ -624,9 +627,9 @@ impl VirtualMethods for HTMLIFrameElement { // browsing context, set the element's nested browsing context // to the newly-created browsing context, and then process the // iframe attributes for the "first time"." - if this.upcast::<Node>().is_in_doc_with_browsing_context() { + if this.upcast::<Node>().is_connected_with_browsing_context() { debug!("iframe bound to browsing context."); - debug_assert!(tree_in_doc, "is_in_doc_with_bc, but not tree_in_doc"); + debug_assert!(tree_connected, "is_connected_with_bc, but not tree_connected"); this.create_nested_browsing_context(); this.process_the_iframe_attributes(ProcessingMode::FirstTime); } diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index 3848b3d7d2a..62a369579a1 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -32,7 +32,10 @@ use crate::dom::htmlmapelement::HTMLMapElement; use crate::dom::htmlpictureelement::HTMLPictureElement; use crate::dom::htmlsourceelement::HTMLSourceElement; use crate::dom::mouseevent::MouseEvent; -use crate::dom::node::{document_from_node, window_from_node, Node, NodeDamage, UnbindContext}; +use crate::dom::node::UnbindContext; +use crate::dom::node::{ + document_from_node, window_from_node, BindContext, Node, NodeDamage, ShadowIncluding, +}; use crate::dom::performanceresourcetiming::InitiatorType; use crate::dom::progressevent::ProgressEvent; use crate::dom::values::UNSIGNED_LONG_MAX; @@ -1259,7 +1262,7 @@ impl HTMLImageElement { let useMapElements = document_from_node(self) .upcast::<Node>() - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast::<HTMLMapElement>) .find(|n| { n.upcast::<Element>() @@ -1645,12 +1648,12 @@ impl VirtualMethods for HTMLImageElement { } } - 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); } let document = document_from_node(self); - if tree_in_doc { + if context.tree_connected { document.register_responsive_image(self); } diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 94ffadb69fd..e9a3923b5d0 100755 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -35,7 +35,7 @@ use crate::dom::htmlformelement::{ResetFrom, SubmittedFrom}; use crate::dom::keyboardevent::KeyboardEvent; use crate::dom::mouseevent::MouseEvent; use crate::dom::node::{document_from_node, window_from_node}; -use crate::dom::node::{Node, NodeDamage, UnbindContext}; +use crate::dom::node::{BindContext, Node, NodeDamage, UnbindContext}; use crate::dom::nodelist::NodeList; use crate::dom::textcontrol::{TextControlElement, TextControlSelection}; use crate::dom::validation::Validatable; @@ -1422,9 +1422,9 @@ impl VirtualMethods for HTMLInputElement { } } - 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); } self.upcast::<Element>() .check_ancestors_disabled_state_for_form_control(); diff --git a/components/script/dom/htmllabelelement.rs b/components/script/dom/htmllabelelement.rs index 3c9106a589a..11ee09bce94 100644 --- a/components/script/dom/htmllabelelement.rs +++ b/components/script/dom/htmllabelelement.rs @@ -15,7 +15,7 @@ use crate::dom::event::Event; use crate::dom::eventtarget::EventTarget; use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlformelement::{FormControl, FormControlElementHelpers, HTMLFormElement}; -use crate::dom::node::{document_from_node, Node}; +use crate::dom::node::{document_from_node, Node, ShadowIncluding}; use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; @@ -162,7 +162,7 @@ impl VirtualMethods for HTMLLabelElement { impl HTMLLabelElement { pub fn first_labelable_descendant(&self) -> Option<DomRoot<HTMLElement>> { self.upcast::<Node>() - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast::<HTMLElement>) .filter(|elem| elem.is_labelable_element()) .next() diff --git a/components/script/dom/htmllegendelement.rs b/components/script/dom/htmllegendelement.rs index b69a066d9a1..41cf7d3d8ff 100644 --- a/components/script/dom/htmllegendelement.rs +++ b/components/script/dom/htmllegendelement.rs @@ -12,7 +12,7 @@ use crate::dom::element::Element; use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; use crate::dom::htmlformelement::{FormControl, HTMLFormElement}; -use crate::dom::node::{Node, UnbindContext}; +use crate::dom::node::{BindContext, Node, UnbindContext}; use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; @@ -56,9 +56,9 @@ impl VirtualMethods for HTMLLegendElement { Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } - 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); } self.upcast::<Element>() diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index 36bafa58fb5..8dd50c11441 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -18,7 +18,10 @@ use crate::dom::element::{ }; use crate::dom::element::{AttributeMutation, Element, ElementCreator}; use crate::dom::htmlelement::HTMLElement; -use crate::dom::node::{document_from_node, window_from_node, Node, UnbindContext}; +use crate::dom::node::{ + document_from_node, stylesheets_owner_from_node, window_from_node, BindContext, Node, + UnbindContext, +}; use crate::dom::stylesheet::StyleSheet as DOMStyleSheet; use crate::dom::virtualmethods::VirtualMethods; use crate::stylesheet_loader::{StylesheetContextSource, StylesheetLoader, StylesheetOwner}; @@ -107,14 +110,15 @@ impl HTMLLinkElement { // FIXME(emilio): These methods are duplicated with // HTMLStyleElement::set_stylesheet. + #[allow(unrooted_must_root)] pub fn set_stylesheet(&self, s: Arc<Stylesheet>) { - let doc = document_from_node(self); + let stylesheets_owner = stylesheets_owner_from_node(self); if let Some(ref s) = *self.stylesheet.borrow() { - doc.remove_stylesheet(self.upcast(), s) + stylesheets_owner.remove_stylesheet(self.upcast(), s) } *self.stylesheet.borrow_mut() = Some(s.clone()); self.cssom_stylesheet.set(None); - doc.add_stylesheet(self.upcast(), s); + stylesheets_owner.add_stylesheet(self.upcast(), s); } pub fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> { @@ -183,7 +187,7 @@ impl VirtualMethods for HTMLLinkElement { fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation) { self.super_type().unwrap().attribute_mutated(attr, mutation); - if !self.upcast::<Node>().is_in_doc() || mutation.is_removal() { + if !self.upcast::<Node>().is_connected() || mutation.is_removal() { return; } @@ -222,12 +226,12 @@ impl VirtualMethods for HTMLLinkElement { } } - 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 tree_in_doc { + if context.tree_connected { let element = self.upcast(); let rel = get_attr(element, &local_name!("rel")); @@ -252,7 +256,7 @@ impl VirtualMethods for HTMLLinkElement { } if let Some(s) = self.stylesheet.borrow_mut().take() { - document_from_node(self).remove_stylesheet(self.upcast(), &s); + stylesheets_owner_from_node(self).remove_stylesheet(self.upcast(), &s); } } } diff --git a/components/script/dom/htmlmapelement.rs b/components/script/dom/htmlmapelement.rs index 34b73e7e0b9..0b6acb3ba31 100644 --- a/components/script/dom/htmlmapelement.rs +++ b/components/script/dom/htmlmapelement.rs @@ -8,7 +8,7 @@ use crate::dom::bindings::root::DomRoot; use crate::dom::document::Document; use crate::dom::htmlareaelement::HTMLAreaElement; use crate::dom::htmlelement::HTMLElement; -use crate::dom::node::Node; +use crate::dom::node::{Node, ShadowIncluding}; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; @@ -43,7 +43,7 @@ impl HTMLMapElement { pub fn get_area_elements(&self) -> Vec<DomRoot<HTMLAreaElement>> { self.upcast::<Node>() - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast::<HTMLAreaElement>) .collect() } diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 1b5f991ac8e..74c7c7ade62 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -2009,7 +2009,7 @@ impl VirtualMethods for HTMLMediaElement { fn unbind_from_tree(&self, context: &UnbindContext) { self.super_type().unwrap().unbind_from_tree(context); - if context.tree_in_doc { + if context.tree_connected { let task = MediaElementMicrotask::PauseIfNotInDocumentTask { elem: DomRoot::from_ref(self), }; @@ -2061,7 +2061,7 @@ impl MicrotaskRunnable for MediaElementMicrotask { } }, &MediaElementMicrotask::PauseIfNotInDocumentTask { ref elem } => { - if !elem.upcast::<Node>().is_in_doc() { + if !elem.upcast::<Node>().is_connected() { elem.internal_pause_steps(); } }, diff --git a/components/script/dom/htmlmetaelement.rs b/components/script/dom/htmlmetaelement.rs index a8e829c3d1c..11b5706516d 100644 --- a/components/script/dom/htmlmetaelement.rs +++ b/components/script/dom/htmlmetaelement.rs @@ -15,7 +15,10 @@ use crate::dom::document::Document; use crate::dom::element::{AttributeMutation, Element}; use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlheadelement::HTMLHeadElement; -use crate::dom::node::{document_from_node, window_from_node, Node, UnbindContext}; +use crate::dom::node::{ + document_from_node, stylesheets_owner_from_node, window_from_node, BindContext, Node, + UnbindContext, +}; use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; @@ -97,6 +100,7 @@ impl HTMLMetaElement { } } + #[allow(unrooted_must_root)] fn apply_viewport(&self) { if !pref!(layout.viewport.enabled) { return; @@ -106,6 +110,7 @@ impl HTMLMetaElement { let content = content.value(); if !content.is_empty() { if let Some(translated_rule) = ViewportRule::from_meta(&**content) { + let stylesheets_owner = stylesheets_owner_from_node(self); let document = document_from_node(self); let shared_lock = document.style_shared_lock(); let rule = CssRule::Viewport(Arc::new(shared_lock.wrap(translated_rule))); @@ -124,7 +129,7 @@ impl HTMLMetaElement { disabled: AtomicBool::new(false), }); *self.stylesheet.borrow_mut() = Some(sheet.clone()); - document.add_stylesheet(self.upcast(), sheet); + stylesheets_owner.add_stylesheet(self.upcast(), sheet); } } } @@ -171,12 +176,12 @@ impl VirtualMethods for HTMLMetaElement { Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } - 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 tree_in_doc { + if context.tree_connected { self.process_attributes(); } } @@ -204,11 +209,11 @@ impl VirtualMethods for HTMLMetaElement { s.unbind_from_tree(context); } - if context.tree_in_doc { + if context.tree_connected { self.process_referrer_attribute(); if let Some(s) = self.stylesheet.borrow_mut().take() { - document_from_node(self).remove_stylesheet(self.upcast(), &s); + stylesheets_owner_from_node(self).remove_stylesheet(self.upcast(), &s); } } } diff --git a/components/script/dom/htmloptionelement.rs b/components/script/dom/htmloptionelement.rs index 494ff7b0a8c..3893aea95ab 100644 --- a/components/script/dom/htmloptionelement.rs +++ b/components/script/dom/htmloptionelement.rs @@ -19,7 +19,7 @@ use crate::dom::htmlformelement::HTMLFormElement; use crate::dom::htmloptgroupelement::HTMLOptGroupElement; use crate::dom::htmlscriptelement::HTMLScriptElement; use crate::dom::htmlselectelement::HTMLSelectElement; -use crate::dom::node::{Node, UnbindContext}; +use crate::dom::node::{BindContext, Node, ShadowIncluding, UnbindContext}; use crate::dom::text::Text; use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; @@ -235,9 +235,9 @@ impl VirtualMethods for HTMLOptionElement { } } - 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); } self.upcast::<Element>() @@ -251,7 +251,7 @@ impl VirtualMethods for HTMLOptionElement { if let Some(select) = context .parent - .inclusive_ancestors() + .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::<HTMLSelectElement>) .next() { diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 627cfc7e779..8d44c4b9ffd 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -22,7 +22,7 @@ use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlelement::HTMLElement; use crate::dom::node::{document_from_node, window_from_node}; -use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node}; +use crate::dom::node::{BindContext, ChildrenMutation, CloneChildrenFlag, Node}; use crate::dom::performanceresourcetiming::InitiatorType; use crate::dom::virtualmethods::VirtualMethods; use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener}; @@ -372,7 +372,7 @@ impl HTMLScriptElement { } // Step 5. - if !self.upcast::<Node>().is_in_doc() { + if !self.upcast::<Node>().is_connected() { return; } @@ -760,7 +760,7 @@ impl VirtualMethods for HTMLScriptElement { match *attr.local_name() { local_name!("src") => { if let AttributeMutation::Set(_) = mutation { - if !self.parser_inserted.get() && self.upcast::<Node>().is_in_doc() { + if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() { self.prepare(); } } @@ -773,17 +773,17 @@ impl VirtualMethods for HTMLScriptElement { if let Some(ref s) = self.super_type() { s.children_changed(mutation); } - if !self.parser_inserted.get() && self.upcast::<Node>().is_in_doc() { + if !self.parser_inserted.get() && self.upcast::<Node>().is_connected() { self.prepare(); } } - 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 tree_in_doc && !self.parser_inserted.get() { + if context.tree_connected && !self.parser_inserted.get() { let script = Trusted::new(self); document_from_node(self).add_delayed_task(task!(ScriptDelayedInitialize: move || { script.root().prepare(); diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index 50d88710161..c71a5442e73 100755 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -12,7 +12,6 @@ use crate::dom::bindings::codegen::Bindings::HTMLSelectElementBinding::HTMLSelec use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::codegen::UnionTypes::HTMLElementOrLong; use crate::dom::bindings::codegen::UnionTypes::HTMLOptionElementOrHTMLOptGroupElement; -//use dom::bindings::error::ErrorResult; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::{DomRoot, MutNullableDom}; use crate::dom::bindings::str::DOMString; @@ -25,7 +24,7 @@ use crate::dom::htmlformelement::{FormControl, FormDatum, FormDatumValue, HTMLFo use crate::dom::htmloptgroupelement::HTMLOptGroupElement; use crate::dom::htmloptionelement::HTMLOptionElement; use crate::dom::htmloptionscollection::HTMLOptionsCollection; -use crate::dom::node::{window_from_node, Node, UnbindContext}; +use crate::dom::node::{window_from_node, BindContext, Node, UnbindContext}; use crate::dom::nodelist::NodeList; use crate::dom::validation::Validatable; use crate::dom::validitystate::{ValidationFlags, ValidityState}; @@ -382,9 +381,9 @@ impl VirtualMethods for HTMLSelectElement { } } - 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); } self.upcast::<Element>() diff --git a/components/script/dom/htmlsourceelement.rs b/components/script/dom/htmlsourceelement.rs index 783bcde4c80..f93de93599d 100644 --- a/components/script/dom/htmlsourceelement.rs +++ b/components/script/dom/htmlsourceelement.rs @@ -15,7 +15,7 @@ use crate::dom::element::AttributeMutation; use crate::dom::htmlelement::HTMLElement; use crate::dom::htmlimageelement::HTMLImageElement; use crate::dom::htmlmediaelement::HTMLMediaElement; -use crate::dom::node::{Node, UnbindContext}; +use crate::dom::node::{BindContext, Node, UnbindContext}; use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; @@ -82,8 +82,8 @@ impl VirtualMethods for HTMLSourceElement { } /// <https://html.spec.whatwg.org/multipage/#the-source-element:nodes-are-inserted> - fn bind_to_tree(&self, tree_in_doc: bool) { - self.super_type().unwrap().bind_to_tree(tree_in_doc); + fn bind_to_tree(&self, context: &BindContext) { + self.super_type().unwrap().bind_to_tree(context); let parent = self.upcast::<Node>().GetParentNode().unwrap(); if let Some(media) = parent.downcast::<HTMLMediaElement>() { media.handle_source_child_insertion(); diff --git a/components/script/dom/htmlstyleelement.rs b/components/script/dom/htmlstyleelement.rs index b4065d205a8..e483453252a 100644 --- a/components/script/dom/htmlstyleelement.rs +++ b/components/script/dom/htmlstyleelement.rs @@ -13,7 +13,8 @@ use crate::dom::document::Document; use crate::dom::element::{Element, ElementCreator}; use crate::dom::htmlelement::HTMLElement; use crate::dom::node::{ - document_from_node, window_from_node, ChildrenMutation, Node, UnbindContext, + document_from_node, stylesheets_owner_from_node, window_from_node, BindContext, + ChildrenMutation, Node, UnbindContext, }; use crate::dom::stylesheet::StyleSheet as DOMStyleSheet; use crate::dom::virtualmethods::VirtualMethods; @@ -81,7 +82,7 @@ impl HTMLStyleElement { pub fn parse_own_css(&self) { let node = self.upcast::<Node>(); let element = self.upcast::<Element>(); - assert!(node.is_in_doc()); + assert!(node.is_connected()); let window = window_from_node(node); let doc = document_from_node(self); @@ -137,14 +138,15 @@ impl HTMLStyleElement { } // FIXME(emilio): This is duplicated with HTMLLinkElement::set_stylesheet. + #[allow(unrooted_must_root)] pub fn set_stylesheet(&self, s: Arc<Stylesheet>) { - let doc = document_from_node(self); + let stylesheets_owner = stylesheets_owner_from_node(self); if let Some(ref s) = *self.stylesheet.borrow() { - doc.remove_stylesheet(self.upcast(), s) + stylesheets_owner.remove_stylesheet(self.upcast(), s) } *self.stylesheet.borrow_mut() = Some(s.clone()); self.cssom_stylesheet.set(None); - doc.add_stylesheet(self.upcast(), s); + stylesheets_owner.add_stylesheet(self.upcast(), s); } pub fn get_stylesheet(&self) -> Option<Arc<Stylesheet>> { @@ -185,14 +187,14 @@ impl VirtualMethods for HTMLStyleElement { } } - fn bind_to_tree(&self, tree_in_doc: bool) { - self.super_type().unwrap().bind_to_tree(tree_in_doc); + fn bind_to_tree(&self, context: &BindContext) { + self.super_type().unwrap().bind_to_tree(context); // https://html.spec.whatwg.org/multipage/#update-a-style-block // Handles the case when: // "The element is not on the stack of open elements of an HTML parser or XML parser, // and it becomes connected or disconnected." - if tree_in_doc && !self.in_stack_of_open_elements.get() { + if context.tree_connected && !self.in_stack_of_open_elements.get() { self.parse_own_css(); } } @@ -214,9 +216,9 @@ impl VirtualMethods for HTMLStyleElement { s.unbind_from_tree(context); } - if context.tree_in_doc { + if context.tree_connected { if let Some(s) = self.stylesheet.borrow_mut().take() { - document_from_node(self).remove_stylesheet(self.upcast(), &s) + stylesheets_owner_from_node(self).remove_stylesheet(self.upcast(), &s) } } } diff --git a/components/script/dom/htmltextareaelement.rs b/components/script/dom/htmltextareaelement.rs index 9116b26b575..34dd94b7a36 100755 --- a/components/script/dom/htmltextareaelement.rs +++ b/components/script/dom/htmltextareaelement.rs @@ -24,7 +24,9 @@ use crate::dom::htmlfieldsetelement::HTMLFieldSetElement; use crate::dom::htmlformelement::{FormControl, HTMLFormElement}; use crate::dom::keyboardevent::KeyboardEvent; use crate::dom::node::{document_from_node, window_from_node}; -use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, NodeDamage, UnbindContext}; +use crate::dom::node::{ + BindContext, ChildrenMutation, CloneChildrenFlag, Node, NodeDamage, UnbindContext, +}; use crate::dom::nodelist::NodeList; use crate::dom::textcontrol::{TextControlElement, TextControlSelection}; use crate::dom::validation::Validatable; @@ -466,9 +468,9 @@ impl VirtualMethods for HTMLTextAreaElement { } } - 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); } self.upcast::<Element>() diff --git a/components/script/dom/htmltitleelement.rs b/components/script/dom/htmltitleelement.rs index 1aa60373df6..a1e543d34e2 100644 --- a/components/script/dom/htmltitleelement.rs +++ b/components/script/dom/htmltitleelement.rs @@ -10,7 +10,7 @@ use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::document::Document; use crate::dom::htmlelement::HTMLElement; -use crate::dom::node::{ChildrenMutation, Node}; +use crate::dom::node::{BindContext, ChildrenMutation, Node}; use crate::dom::virtualmethods::VirtualMethods; use dom_struct::dom_struct; use html5ever::{LocalName, Prefix}; @@ -74,12 +74,12 @@ impl VirtualMethods for HTMLTitleElement { } } - 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); } let node = self.upcast::<Node>(); - if tree_in_doc { + if context.tree_in_doc { node.owner_doc().title_changed(); } } diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs index a566fee4fb6..021527a286a 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -632,3 +632,31 @@ macro_rules! handle_potential_webgl_error { handle_potential_webgl_error!($context, $call, ()); }; } + +macro_rules! impl_rare_data ( + ($type:ty) => ( + fn rare_data(&self) -> Ref<Option<Box<$type>>> { + self.rare_data.borrow() + } + + #[allow(dead_code)] + fn rare_data_mut(&self) -> RefMut<Option<Box<$type>>> { + self.rare_data.borrow_mut() + } + + fn ensure_rare_data(&self) -> RefMut<Box<$type>> { + let mut rare_data = self.rare_data.borrow_mut(); + if rare_data.is_none() { + *rare_data = Some(Default::default()); + } + RefMut::map(rare_data, |rare_data| { + rare_data.as_mut().unwrap() + }) + } + + #[allow(unsafe_code)] + fn rare_data_for_layout(&self) -> &Option<Box<$type>> { + unsafe { self.rare_data.borrow_for_layout() } + } + ); +); diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 813f4659a08..776d20299c1 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -280,6 +280,7 @@ pub mod dissimilaroriginlocation; pub mod dissimilaroriginwindow; pub mod document; pub mod documentfragment; +pub mod documentorshadowroot; pub mod documenttype; pub mod domexception; pub mod domimplementation; @@ -442,6 +443,7 @@ pub mod promisenativehandler; pub mod promiserejectionevent; pub mod radionodelist; pub mod range; +pub mod raredata; pub mod request; pub mod response; pub mod rtcicecandidate; @@ -454,6 +456,7 @@ pub mod serviceworkercontainer; pub mod serviceworkerglobalscope; pub mod serviceworkerregistration; pub mod servoparser; +pub mod shadowroot; pub mod storage; pub mod storageevent; pub mod stylepropertymapreadonly; diff --git a/components/script/dom/mutationobserver.rs b/components/script/dom/mutationobserver.rs index b25f99d0861..bb87a10f4b2 100644 --- a/components/script/dom/mutationobserver.rs +++ b/components/script/dom/mutationobserver.rs @@ -13,7 +13,7 @@ use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::mutationrecord::MutationRecord; -use crate::dom::node::Node; +use crate::dom::node::{Node, ShadowIncluding}; use crate::dom::window::Window; use crate::microtask::Microtask; use crate::script_thread::ScriptThread; @@ -131,7 +131,7 @@ impl MutationObserver { let mut interested_observers: Vec<(DomRoot<MutationObserver>, Option<DOMString>)> = vec![]; // Step 2 & 3 - for node in target.inclusive_ancestors() { + for node in target.inclusive_ancestors(ShadowIncluding::No) { for registered in &*node.registered_mutation_observers() { if &*node != target && !registered.options.subtree { continue; @@ -318,20 +318,18 @@ impl MutationObserverMethods for MutationObserver { // Step 8 if add_new_observer { - target - .registered_mutation_observers() - .push(RegisteredObserver { - observer: DomRoot::from_ref(self), - options: ObserverOptions { - attributes, - attribute_old_value, - character_data, - character_data_old_value, - subtree, - attribute_filter, - child_list, - }, - }); + target.add_mutation_observer(RegisteredObserver { + observer: DomRoot::from_ref(self), + options: ObserverOptions { + attributes, + attribute_old_value, + character_data, + character_data_old_value, + subtree, + attribute_filter, + child_list, + }, + }); self.node_list.borrow_mut().push(DomRoot::from_ref(target)); } diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index 3331686eef4..814378aca88 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -10,10 +10,14 @@ use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterData use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods; use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods; use crate::dom::bindings::codegen::Bindings::HTMLCollectionBinding::HTMLCollectionMethods; -use crate::dom::bindings::codegen::Bindings::NodeBinding::{NodeConstants, NodeMethods}; +use crate::dom::bindings::codegen::Bindings::NodeBinding::{ + GetRootNodeOptions, NodeConstants, NodeMethods, +}; use crate::dom::bindings::codegen::Bindings::NodeListBinding::NodeListMethods; use crate::dom::bindings::codegen::Bindings::ProcessingInstructionBinding::ProcessingInstructionMethods; +use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootBinding::ShadowRootMethods; use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods; +use crate::dom::bindings::codegen::InheritTypes::DocumentFragmentTypeId; use crate::dom::bindings::codegen::UnionTypes::NodeOrString; use crate::dom::bindings::conversions::{self, DerivedFrom}; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; @@ -49,6 +53,9 @@ use crate::dom::mutationobserver::{Mutation, MutationObserver, RegisteredObserve use crate::dom::nodelist::NodeList; use crate::dom::processinginstruction::ProcessingInstruction; use crate::dom::range::WeakRangeVec; +use crate::dom::raredata::NodeRareData; +use crate::dom::shadowroot::{LayoutShadowRootHelpers, ShadowRoot}; +use crate::dom::stylesheetlist::StyleSheetListOwner; use crate::dom::svgsvgelement::{LayoutSVGSVGElementHelpers, SVGSVGElement}; use crate::dom::text::Text; use crate::dom::virtualmethods::{vtable_for, VirtualMethods}; @@ -76,7 +83,7 @@ use servo_arc::Arc; use servo_url::ServoUrl; use smallvec::SmallVec; use std::borrow::ToOwned; -use std::cell::{Cell, RefMut, UnsafeCell}; +use std::cell::{Cell, Ref, RefMut, UnsafeCell}; use std::cmp; use std::default::Default; use std::iter; @@ -118,6 +125,9 @@ pub struct Node { /// The document that this node belongs to. owner_doc: MutNullableDom<Document>, + /// Rare node data. + rare_data: DomRefCell<Option<Box<NodeRareData>>>, + /// The live list of children return by .childNodes. child_list: MutNullableDom<NodeList>, @@ -142,9 +152,6 @@ pub struct Node { /// node is finalized. style_and_layout_data: Cell<Option<OpaqueStyleAndLayoutData>>, - /// Registered observers for this node. - mutation_observers: DomRefCell<Vec<RegisteredObserver>>, - unique_id: UniqueId, } @@ -181,6 +188,12 @@ bitflags! { /// Whether this element has already handled the stored snapshot. const HANDLED_SNAPSHOT = 1 << 8; + + /// Whether this node participates in a shadow tree. + const IS_IN_SHADOW_TREE = 1 << 9; + + /// Specifies whether this node's shadow-including root is a document. + const IS_CONNECTED = 1 << 10; } } @@ -264,11 +277,25 @@ impl Node { self.children_count.set(self.children_count.get() + 1); let parent_in_doc = self.is_in_doc(); - for node in new_child.traverse_preorder() { + let parent_in_shadow_tree = self.is_in_shadow_tree(); + let parent_is_connected = self.is_connected(); + + for node in new_child.traverse_preorder(ShadowIncluding::No) { + if parent_in_shadow_tree { + if let Some(shadow_root) = self.containing_shadow_root() { + node.set_containing_shadow_root(&*shadow_root); + } + debug_assert!(node.containing_shadow_root().is_some()); + } node.set_flag(NodeFlags::IS_IN_DOC, parent_in_doc); + node.set_flag(NodeFlags::IS_IN_SHADOW_TREE, parent_in_shadow_tree); + node.set_flag(NodeFlags::IS_CONNECTED, parent_is_connected); // Out-of-document elements never have the descendants flag set. debug_assert!(!node.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS)); - vtable_for(&&*node).bind_to_tree(parent_in_doc); + vtable_for(&&*node).bind_to_tree(&BindContext { + tree_connected: parent_is_connected, + tree_in_doc: parent_in_doc, + }); } } @@ -312,17 +339,18 @@ impl Node { child.parent_node.set(None); self.children_count.set(self.children_count.get() - 1); - for node in child.traverse_preorder() { + for node in child.traverse_preorder(ShadowIncluding::Yes) { // Out-of-document elements never have the descendants flag set. node.set_flag( NodeFlags::IS_IN_DOC | + NodeFlags::IS_CONNECTED | NodeFlags::HAS_DIRTY_DESCENDANTS | NodeFlags::HAS_SNAPSHOT | NodeFlags::HANDLED_SNAPSHOT, false, ); } - for node in child.traverse_preorder() { + for node in child.traverse_preorder(ShadowIncluding::Yes) { // This needs to be in its own loop, because unbind_from_tree may // rely on the state of IS_IN_DOC of the context node's descendants, // e.g. when removing a <form>. @@ -403,6 +431,8 @@ impl<'a> Iterator for QuerySelectorIterator { } impl Node { + impl_rare_data!(NodeRareData); + pub fn teardown(&self) { self.style_and_layout_data.get().map(|d| self.dispose(d)); for kid in self.children() { @@ -422,14 +452,26 @@ impl Node { } /// Return all registered mutation observers for this node. + /// XXX(ferjm) This should probably be split into two functions, + /// `registered_mutation_observers`, which returns an Option or + /// an empty slice or something, and doesn't create the rare data, + /// and `registered_mutation_observers_mut`, which does lazily + /// initialize the raredata. pub fn registered_mutation_observers(&self) -> RefMut<Vec<RegisteredObserver>> { - self.mutation_observers.borrow_mut() + RefMut::map(self.ensure_rare_data(), |rare_data| { + &mut rare_data.mutation_observers + }) + } + + /// Add a new mutation observer for a given node. + pub fn add_mutation_observer(&self, observer: RegisteredObserver) { + self.ensure_rare_data().mutation_observers.push(observer); } /// Removes the mutation observer for a given node. pub fn remove_mutation_observer(&self, observer: &MutationObserver) { - self.mutation_observers - .borrow_mut() + self.ensure_rare_data() + .mutation_observers .retain(|reg_obs| &*reg_obs.observer != observer) } @@ -463,6 +505,14 @@ impl Node { self.flags.get().contains(NodeFlags::IS_IN_DOC) } + pub fn is_in_shadow_tree(&self) -> bool { + self.flags.get().contains(NodeFlags::IS_IN_SHADOW_TREE) + } + + pub fn is_connected(&self) -> bool { + self.flags.get().contains(NodeFlags::IS_CONNECTED) + } + /// Returns the type ID of this node. pub fn type_id(&self) -> NodeTypeId { match *self.eventtarget.type_id() { @@ -521,14 +571,16 @@ impl Node { // FIXME(emilio): This and the function below should move to Element. pub fn note_dirty_descendants(&self) { - debug_assert!(self.is_in_doc()); + debug_assert!(self.is_connected()); - for ancestor in self.inclusive_ancestors() { + for ancestor in self.inclusive_ancestors(ShadowIncluding::Yes) { if ancestor.get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) { return; } - ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true); + if ancestor.is::<Element>() { + ancestor.set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true); + } } } @@ -546,7 +598,7 @@ impl Node { self.inclusive_descendants_version(), doc.inclusive_descendants_version(), ) + 1; - for ancestor in self.inclusive_ancestors() { + for ancestor in self.inclusive_ancestors(ShadowIncluding::No) { ancestor.inclusive_descendants_version.set(version); } doc.inclusive_descendants_version.set(version); @@ -554,19 +606,21 @@ impl Node { pub fn dirty(&self, damage: NodeDamage) { self.rev_version(); - if !self.is_in_doc() { + if !self.is_connected() { return; } match self.type_id() { - NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => self - .parent_node - .get() - .unwrap() - .downcast::<Element>() + NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => { + self.parent_node.get().unwrap().dirty(damage) + }, + NodeTypeId::Element(_) => self.downcast::<Element>().unwrap().restyle(damage), + NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot) => self + .downcast::<ShadowRoot>() .unwrap() + .Host() + .upcast::<Element>() .restyle(damage), - NodeTypeId::Element(_) => self.downcast::<Element>().unwrap().restyle(damage), _ => {}, }; } @@ -577,8 +631,8 @@ impl Node { } /// Iterates over this node and all its descendants, in preorder. - pub fn traverse_preorder(&self) -> TreeIterator { - TreeIterator::new(self) + pub fn traverse_preorder(&self, shadow_including: ShadowIncluding) -> TreeIterator { + TreeIterator::new(self, shadow_including) } pub fn inclusively_following_siblings(&self) -> impl Iterator<Item = DomRoot<Node>> { @@ -603,6 +657,11 @@ impl Node { parent.ancestors().any(|ancestor| &*ancestor == self) } + fn is_shadow_including_inclusive_ancestor_of(&self, node: &Node) -> bool { + node.inclusive_ancestors(ShadowIncluding::Yes) + .any(|ancestor| &*ancestor == self) + } + pub fn following_siblings(&self) -> impl Iterator<Item = DomRoot<Node>> { SimpleNodeIterator { current: self.GetNextSibling(), @@ -820,7 +879,7 @@ impl Node { self.owner_doc().quirks_mode(), ); Ok(self - .traverse_preorder() + .traverse_preorder(ShadowIncluding::No) .filter_map(DomRoot::downcast) .find(|element| matches_selector_list(&selectors, element, &mut ctx))) }, @@ -838,7 +897,7 @@ impl Node { Err(_) => Err(Error::Syntax), // Step 3. Ok(selectors) => { - let mut descendants = self.traverse_preorder(); + let mut descendants = self.traverse_preorder(ShadowIncluding::No); // Skip the root of the tree. assert!(&*descendants.next().unwrap() == self); Ok(QuerySelectorIterator::new(descendants, selectors)) @@ -861,10 +920,21 @@ impl Node { } } - pub fn inclusive_ancestors(&self) -> impl Iterator<Item = DomRoot<Node>> { + /// https://dom.spec.whatwg.org/#concept-shadow-including-inclusive-ancestor + pub fn inclusive_ancestors( + &self, + shadow_including: ShadowIncluding, + ) -> impl Iterator<Item = DomRoot<Node>> { SimpleNodeIterator { current: Some(DomRoot::from_ref(self)), - next_node: |n| n.GetParentNode(), + next_node: move |n| { + if shadow_including == ShadowIncluding::Yes { + if let Some(shadow_root) = n.downcast::<ShadowRoot>() { + return Some(DomRoot::from_ref(shadow_root.Host().upcast::<Node>())); + } + } + n.GetParentNode() + }, } } @@ -876,12 +946,24 @@ impl Node { self.owner_doc.set(Some(document)); } + pub fn containing_shadow_root(&self) -> Option<DomRoot<ShadowRoot>> { + self.rare_data() + .as_ref()? + .containing_shadow_root + .as_ref() + .map(|sr| DomRoot::from_ref(&**sr)) + } + + pub fn set_containing_shadow_root(&self, shadow_root: &ShadowRoot) { + self.ensure_rare_data().containing_shadow_root = Some(Dom::from_ref(shadow_root)); + } + pub fn is_in_html_doc(&self) -> bool { self.owner_doc().is_html_document() } - pub fn is_in_doc_with_browsing_context(&self) -> bool { - self.is_in_doc() && self.owner_doc().browsing_context().is_some() + pub fn is_connected_with_browsing_context(&self) -> bool { + self.is_connected() && self.owner_doc().browsing_context().is_some() } pub fn children(&self) -> impl Iterator<Item = DomRoot<Node>> { @@ -1042,6 +1124,27 @@ impl Node { None } } + + /// https://dom.spec.whatwg.org/#retarget + pub fn retarget(&self, b: &Node) -> DomRoot<Node> { + let mut a = DomRoot::from_ref(&*self); + loop { + // Step 1. + let a_root = a.GetRootNode(&GetRootNodeOptions::empty()); + if !a_root.is::<ShadowRoot>() || a_root.is_shadow_including_inclusive_ancestor_of(b) { + return DomRoot::from_ref(&a); + } + + // Step 2. + a = DomRoot::from_ref( + a_root + .downcast::<ShadowRoot>() + .unwrap() + .Host() + .upcast::<Node>(), + ); + } + } } /// Iterate through `nodes` until we find a `Node` that is not in `not_in` @@ -1080,13 +1183,14 @@ pub unsafe fn from_untrusted_node_address( pub trait LayoutNodeHelpers { unsafe fn type_id_for_layout(&self) -> NodeTypeId; - unsafe fn parent_node_ref(&self) -> Option<LayoutDom<Node>>; + unsafe fn composed_parent_node_ref(&self) -> Option<LayoutDom<Node>>; unsafe fn first_child_ref(&self) -> Option<LayoutDom<Node>>; unsafe fn last_child_ref(&self) -> Option<LayoutDom<Node>>; unsafe fn prev_sibling_ref(&self) -> Option<LayoutDom<Node>>; unsafe fn next_sibling_ref(&self) -> Option<LayoutDom<Node>>; unsafe fn owner_doc_for_layout(&self) -> LayoutDom<Document>; + unsafe fn containing_shadow_root_for_layout(&self) -> Option<LayoutDom<ShadowRoot>>; unsafe fn is_element_for_layout(&self) -> bool; unsafe fn get_flag(&self, flag: NodeFlags) -> bool; @@ -1126,8 +1230,14 @@ impl LayoutNodeHelpers for LayoutDom<Node> { #[inline] #[allow(unsafe_code)] - unsafe fn parent_node_ref(&self) -> Option<LayoutDom<Node>> { - (*self.unsafe_get()).parent_node.get_inner_as_layout() + unsafe fn composed_parent_node_ref(&self) -> Option<LayoutDom<Node>> { + let parent = (*self.unsafe_get()).parent_node.get_inner_as_layout(); + if let Some(ref parent) = parent { + if let Some(shadow_root) = parent.downcast::<ShadowRoot>() { + return Some(shadow_root.get_host_for_layout().upcast()); + } + } + parent } #[inline] @@ -1165,6 +1275,17 @@ impl LayoutNodeHelpers for LayoutDom<Node> { #[inline] #[allow(unsafe_code)] + unsafe fn containing_shadow_root_for_layout(&self) -> Option<LayoutDom<ShadowRoot>> { + (*self.unsafe_get()) + .rare_data_for_layout() + .as_ref()? + .containing_shadow_root + .as_ref() + .map(|sr| sr.to_layout()) + } + + #[inline] + #[allow(unsafe_code)] unsafe fn get_flag(&self, flag: NodeFlags) -> bool { (*self.unsafe_get()).flags.get().contains(flag) } @@ -1325,7 +1446,7 @@ impl FollowingNodeIterator { return current.GetNextSibling(); } - for ancestor in current.inclusive_ancestors() { + for ancestor in current.inclusive_ancestors(ShadowIncluding::No) { if self.root == ancestor { break; } @@ -1405,16 +1526,25 @@ where } } +/// Whether a tree traversal should pass shadow tree boundaries. +#[derive(PartialEq)] +pub enum ShadowIncluding { + No, + Yes, +} + pub struct TreeIterator { current: Option<DomRoot<Node>>, depth: usize, + shadow_including: bool, } impl TreeIterator { - fn new(root: &Node) -> TreeIterator { + fn new(root: &Node, shadow_including: ShadowIncluding) -> TreeIterator { TreeIterator { current: Some(DomRoot::from_ref(root)), depth: 0, + shadow_including: shadow_including == ShadowIncluding::Yes, } } @@ -1425,7 +1555,13 @@ impl TreeIterator { } fn next_skipping_children_impl(&mut self, current: DomRoot<Node>) -> Option<DomRoot<Node>> { - for ancestor in current.inclusive_ancestors() { + let iter = current.inclusive_ancestors(if self.shadow_including { + ShadowIncluding::Yes + } else { + ShadowIncluding::No + }); + + for ancestor in iter { if self.depth == 0 { break; } @@ -1445,8 +1581,18 @@ impl Iterator for TreeIterator { type Item = DomRoot<Node>; // https://dom.spec.whatwg.org/#concept-tree-order + // https://dom.spec.whatwg.org/#concept-shadow-including-tree-order fn next(&mut self) -> Option<DomRoot<Node>> { let current = self.current.take()?; + + if !self.shadow_including { + if let Some(element) = current.downcast::<Element>() { + if element.is_shadow_host() { + return self.next_skipping_children_impl(current); + } + } + } + if let Some(first_child) = current.GetFirstChild() { self.current = Some(first_child); self.depth += 1; @@ -1487,7 +1633,10 @@ impl Node { #[allow(unrooted_must_root)] pub fn new_document_node() -> Node { - Node::new_(NodeFlags::new() | NodeFlags::IS_IN_DOC, None) + Node::new_( + NodeFlags::new() | NodeFlags::IS_IN_DOC | NodeFlags::IS_CONNECTED, + None, + ) } #[allow(unrooted_must_root)] @@ -1501,6 +1650,7 @@ impl Node { next_sibling: Default::default(), prev_sibling: Default::default(), owner_doc: MutNullableDom::new(doc), + rare_data: Default::default(), child_list: Default::default(), children_count: Cell::new(0u32), flags: Cell::new(flags), @@ -1509,8 +1659,6 @@ impl Node { style_and_layout_data: Cell::new(None), - mutation_observers: Default::default(), - unique_id: UniqueId::new(), } } @@ -1527,11 +1675,11 @@ impl Node { // Step 3. if &*old_doc != document { // Step 3.1. - for descendant in node.traverse_preorder() { + for descendant in node.traverse_preorder(ShadowIncluding::Yes) { descendant.set_owner_doc(document); } for descendant in node - .traverse_preorder() + .traverse_preorder(ShadowIncluding::Yes) .filter_map(|d| d.as_custom_element()) { // Step 3.2. @@ -1541,7 +1689,7 @@ impl Node { None, ); } - for descendant in node.traverse_preorder() { + for descendant in node.traverse_preorder(ShadowIncluding::Yes) { // Step 3.3. vtable_for(&descendant).adopting_steps(&old_doc); } @@ -1559,7 +1707,9 @@ impl Node { ) -> ErrorResult { // Step 1. match parent.type_id() { - NodeTypeId::Document(_) | NodeTypeId::DocumentFragment | NodeTypeId::Element(..) => (), + NodeTypeId::Document(_) | NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..) => { + () + }, _ => return Err(Error::HierarchyRequest), } @@ -1587,7 +1737,7 @@ impl Node { return Err(Error::HierarchyRequest); } }, - NodeTypeId::DocumentFragment | + NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(_) | NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) | NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => (), @@ -1598,7 +1748,7 @@ impl Node { if parent.is::<Document>() { match node.type_id() { // Step 6.1 - NodeTypeId::DocumentFragment => { + NodeTypeId::DocumentFragment(_) => { // Step 6.1.1(b) if node.children().any(|c| c.is::<Text>()) { return Err(Error::HierarchyRequest); @@ -1723,7 +1873,7 @@ impl Node { } } rooted_vec!(let mut new_nodes); - let new_nodes = if let NodeTypeId::DocumentFragment = node.type_id() { + let new_nodes = if let NodeTypeId::DocumentFragment(_) = node.type_id() { // Step 3. new_nodes.extend(node.children().map(|kid| Dom::from_ref(&*kid))); // Step 4. @@ -1760,7 +1910,7 @@ impl Node { parent.add_child(*kid, child); // Step 7.7. for descendant in kid - .traverse_preorder() + .traverse_preorder(ShadowIncluding::Yes) .filter_map(DomRoot::downcast::<Element>) { // Step 7.7.2. @@ -1809,7 +1959,7 @@ impl Node { // Step 3. rooted_vec!(let mut added_nodes); let added_nodes = if let Some(node) = node.as_ref() { - if let NodeTypeId::DocumentFragment = node.type_id() { + if let NodeTypeId::DocumentFragment(_) = node.type_id() { added_nodes.extend(node.children().map(|child| Dom::from_ref(&*child))); added_nodes.r() } else { @@ -1935,7 +2085,7 @@ impl Node { ); DomRoot::upcast::<Node>(doctype) }, - NodeTypeId::DocumentFragment => { + NodeTypeId::DocumentFragment(_) => { let doc_fragment = DocumentFragment::new(&document); DomRoot::upcast::<Node>(doc_fragment) }, @@ -2068,7 +2218,7 @@ impl Node { .GetDocumentElement() .as_ref() .map_or(ns!(), |elem| elem.locate_namespace(prefix)), - NodeTypeId::DocumentType | NodeTypeId::DocumentFragment => ns!(), + NodeTypeId::DocumentType | NodeTypeId::DocumentFragment(_) => ns!(), _ => node .GetParentElement() .as_ref() @@ -2093,7 +2243,7 @@ impl NodeMethods for Node { NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => NodeConstants::COMMENT_NODE, NodeTypeId::Document(_) => NodeConstants::DOCUMENT_NODE, NodeTypeId::DocumentType => NodeConstants::DOCUMENT_TYPE_NODE, - NodeTypeId::DocumentFragment => NodeConstants::DOCUMENT_FRAGMENT_NODE, + NodeTypeId::DocumentFragment(_) => NodeConstants::DOCUMENT_FRAGMENT_NODE, NodeTypeId::Element(_) => NodeConstants::ELEMENT_NODE, } } @@ -2113,7 +2263,7 @@ impl NodeMethods for Node { }, NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => DOMString::from("#comment"), NodeTypeId::DocumentType => self.downcast::<DocumentType>().unwrap().name().clone(), - NodeTypeId::DocumentFragment => DOMString::from("#document-fragment"), + NodeTypeId::DocumentFragment(_) => DOMString::from("#document-fragment"), NodeTypeId::Document(_) => DOMString::from("#document"), } } @@ -2129,14 +2279,29 @@ impl NodeMethods for Node { NodeTypeId::CharacterData(..) | NodeTypeId::Element(..) | NodeTypeId::DocumentType | - NodeTypeId::DocumentFragment => Some(self.owner_doc()), + NodeTypeId::DocumentFragment(_) => Some(self.owner_doc()), NodeTypeId::Document(_) => None, } } // https://dom.spec.whatwg.org/#dom-node-getrootnode - fn GetRootNode(&self) -> DomRoot<Node> { - self.inclusive_ancestors().last().unwrap() + fn GetRootNode(&self, options: &GetRootNodeOptions) -> DomRoot<Node> { + if let Some(shadow_root) = self.containing_shadow_root() { + return if options.composed { + // shadow-including root. + shadow_root.Host().upcast::<Node>().GetRootNode(options) + } else { + DomRoot::from_ref(shadow_root.upcast::<Node>()) + }; + } + + if self.is_in_doc() { + DomRoot::from_ref(self.owner_doc().upcast::<Node>()) + } else { + self.inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap() + } } // https://dom.spec.whatwg.org/#dom-node-parentnode @@ -2198,8 +2363,9 @@ impl NodeMethods for Node { // https://dom.spec.whatwg.org/#dom-node-textcontent fn GetTextContent(&self) -> Option<DOMString> { match self.type_id() { - NodeTypeId::DocumentFragment | NodeTypeId::Element(..) => { - let content = Node::collect_text_contents(self.traverse_preorder()); + NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..) => { + let content = + Node::collect_text_contents(self.traverse_preorder(ShadowIncluding::No)); Some(content) }, NodeTypeId::CharacterData(..) => { @@ -2214,7 +2380,7 @@ impl NodeMethods for Node { fn SetTextContent(&self, value: Option<DOMString>) { let value = value.unwrap_or_default(); match self.type_id() { - NodeTypeId::DocumentFragment | NodeTypeId::Element(..) => { + NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..) => { // Step 1-2. let node = if value.is_empty() { None @@ -2247,7 +2413,9 @@ impl NodeMethods for Node { fn ReplaceChild(&self, node: &Node, child: &Node) -> Fallible<DomRoot<Node>> { // Step 1. match self.type_id() { - NodeTypeId::Document(_) | NodeTypeId::DocumentFragment | NodeTypeId::Element(..) => (), + NodeTypeId::Document(_) | NodeTypeId::DocumentFragment(_) | NodeTypeId::Element(..) => { + () + }, _ => return Err(Error::HierarchyRequest), } @@ -2277,7 +2445,7 @@ impl NodeMethods for Node { if self.is::<Document>() { match node.type_id() { // Step 6.1 - NodeTypeId::DocumentFragment => { + NodeTypeId::DocumentFragment(_) => { // Step 6.1.1(b) if node.children().any(|c| c.is::<Text>()) { return Err(Error::HierarchyRequest); @@ -2350,7 +2518,10 @@ impl NodeMethods for Node { // Step 12. rooted_vec!(let mut nodes); - let nodes = if node.type_id() == NodeTypeId::DocumentFragment { + let nodes = if node.type_id() == + NodeTypeId::DocumentFragment(DocumentFragmentTypeId::DocumentFragment) || + node.type_id() == NodeTypeId::DocumentFragment(DocumentFragmentTypeId::ShadowRoot) + { nodes.extend(node.children().map(|node| Dom::from_ref(&*node))); nodes.r() } else { @@ -2418,8 +2589,11 @@ impl NodeMethods for Node { } // https://dom.spec.whatwg.org/#dom-node-clonenode - fn CloneNode(&self, deep: bool) -> DomRoot<Node> { - Node::clone( + fn CloneNode(&self, deep: bool) -> Fallible<DomRoot<Node>> { + if deep && self.is::<ShadowRoot>() { + return Err(Error::NotSupported); + } + Ok(Node::clone( self, None, if deep { @@ -2427,7 +2601,7 @@ impl NodeMethods for Node { } else { CloneChildrenFlag::DoNotCloneChildren }, - ) + )) } // https://dom.spec.whatwg.org/#dom-node-isequalnode @@ -2532,8 +2706,12 @@ impl NodeMethods for Node { // FIXME(emilio): This will eventually need to handle attribute nodes. - let mut self_and_ancestors = self.inclusive_ancestors().collect::<SmallVec<[_; 20]>>(); - let mut other_and_ancestors = other.inclusive_ancestors().collect::<SmallVec<[_; 20]>>(); + let mut self_and_ancestors = self + .inclusive_ancestors(ShadowIncluding::No) + .collect::<SmallVec<[_; 20]>>(); + let mut other_and_ancestors = other + .inclusive_ancestors(ShadowIncluding::No) + .collect::<SmallVec<[_; 20]>>(); if self_and_ancestors.last() != other_and_ancestors.last() { let random = as_uintptr(self_and_ancestors.last().unwrap()) < @@ -2612,7 +2790,7 @@ impl NodeMethods for Node { .unwrap() .GetDocumentElement() .and_then(|element| element.lookup_prefix(namespace)), - NodeTypeId::DocumentType | NodeTypeId::DocumentFragment => None, + NodeTypeId::DocumentType | NodeTypeId::DocumentFragment(_) => None, _ => self .GetParentElement() .and_then(|element| element.lookup_prefix(namespace)), @@ -2644,6 +2822,23 @@ pub fn document_from_node<T: DerivedFrom<Node> + DomObject>(derived: &T) -> DomR derived.upcast().owner_doc() } +pub fn containing_shadow_root<T: DerivedFrom<Node> + DomObject>( + derived: &T, +) -> Option<DomRoot<ShadowRoot>> { + derived.upcast().containing_shadow_root() +} + +#[allow(unrooted_must_root)] +pub fn stylesheets_owner_from_node<T: DerivedFrom<Node> + DomObject>( + derived: &T, +) -> StyleSheetListOwner { + if let Some(shadow_root) = containing_shadow_root(derived) { + StyleSheetListOwner::ShadowRoot(Dom::from_ref(&*shadow_root)) + } else { + StyleSheetListOwner::Document(Dom::from_ref(&*document_from_node(derived))) + } +} + pub fn window_from_node<T: DerivedFrom<Node> + DomObject>(derived: &T) -> DomRoot<Window> { let document = document_from_node(derived); DomRoot::from_ref(document.window()) @@ -2853,6 +3048,14 @@ impl<'a> ChildrenMutation<'a> { } } +/// The context of the binding to tree of a node. +pub struct BindContext { + /// Whether the tree is connected. + pub tree_connected: bool, + /// Whether the tree is in the document. + pub tree_in_doc: bool, +} + /// The context of the unbinding from a tree of a node when one of its /// inclusive ancestors is removed. pub struct UnbindContext<'a> { @@ -2864,7 +3067,9 @@ pub struct UnbindContext<'a> { prev_sibling: Option<&'a Node>, /// The next sibling of the inclusive ancestor that was removed. pub next_sibling: Option<&'a Node>, - /// Whether the tree is in a document. + /// Whether the tree is connected. + pub tree_connected: bool, + /// Whether the tree is in doc. pub tree_in_doc: bool, } @@ -2881,6 +3086,7 @@ impl<'a> UnbindContext<'a> { parent: parent, prev_sibling: prev_sibling, next_sibling: next_sibling, + tree_connected: parent.is_connected(), tree_in_doc: parent.is_in_doc(), } } @@ -3028,7 +3234,7 @@ where let elem_node = elem.upcast::<Node>(); let mut head: usize = 0; - for node in tree_root.traverse_preorder() { + for node in tree_root.traverse_preorder(ShadowIncluding::No) { let head_node = DomRoot::upcast::<Node>(DomRoot::from_ref(&*self[head])); if head_node == node { head += 1; diff --git a/components/script/dom/range.rs b/components/script/dom/range.rs index 630c4acd245..de17427664e 100644 --- a/components/script/dom/range.rs +++ b/components/script/dom/range.rs @@ -23,7 +23,7 @@ use crate::dom::document::Document; use crate::dom::documentfragment::DocumentFragment; use crate::dom::element::Element; use crate::dom::htmlscriptelement::HTMLScriptElement; -use crate::dom::node::{Node, UnbindContext}; +use crate::dom::node::{Node, ShadowIncluding, UnbindContext}; use crate::dom::text::Text; use crate::dom::window::Window; use dom_struct::dom_struct; @@ -102,10 +102,10 @@ impl Range { // https://dom.spec.whatwg.org/#partially-contained fn partially_contains(&self, node: &Node) -> bool { self.StartContainer() - .inclusive_ancestors() + .inclusive_ancestors(ShadowIncluding::No) .any(|n| &*n == node) != self.EndContainer() - .inclusive_ancestors() + .inclusive_ancestors(ShadowIncluding::No) .any(|n| &*n == node) } @@ -193,8 +193,14 @@ impl Range { // https://dom.spec.whatwg.org/#dom-range-comparepointnode-offset fn compare_point(&self, node: &Node, offset: u32) -> Fallible<Ordering> { let start_node = self.StartContainer(); - let start_node_root = start_node.inclusive_ancestors().last().unwrap(); - let node_root = node.inclusive_ancestors().last().unwrap(); + let start_node_root = start_node + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); + let node_root = node + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); if start_node_root != node_root { // Step 1. return Err(Error::WrongDocument); @@ -253,7 +259,10 @@ impl RangeMethods for Range { fn CommonAncestorContainer(&self) -> DomRoot<Node> { let end_container = self.EndContainer(); // Step 1. - for container in self.StartContainer().inclusive_ancestors() { + for container in self + .StartContainer() + .inclusive_ancestors(ShadowIncluding::No) + { // Step 2. if container.is_inclusive_ancestor_of(&end_container) { // Step 3. @@ -368,8 +377,16 @@ impl RangeMethods for Range { // Step 1. return Err(Error::NotSupported); } - let this_root = self.StartContainer().inclusive_ancestors().last().unwrap(); - let other_root = other.StartContainer().inclusive_ancestors().last().unwrap(); + let this_root = self + .StartContainer() + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); + let other_root = other + .StartContainer() + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); if this_root != other_root { // Step 2. return Err(Error::WrongDocument); @@ -429,8 +446,15 @@ impl RangeMethods for Range { // https://dom.spec.whatwg.org/#dom-range-intersectsnode fn IntersectsNode(&self, node: &Node) -> bool { let start_node = self.StartContainer(); - let start_node_root = self.StartContainer().inclusive_ancestors().last().unwrap(); - let node_root = node.inclusive_ancestors().last().unwrap(); + let start_node_root = self + .StartContainer() + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); + let node_root = node + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); if start_node_root != node_root { // Step 1. return false; @@ -499,7 +523,7 @@ impl RangeMethods for Range { fragment.upcast::<Node>().AppendChild(&clone)?; } else { // Step 14.1. - let clone = child.CloneNode(false); + let clone = child.CloneNode(/* deep */ false)?; // Step 14.2. fragment.upcast::<Node>().AppendChild(&clone)?; // Step 14.3. @@ -520,7 +544,7 @@ impl RangeMethods for Range { // Step 15. for child in contained_children { // Step 15.1. - let clone = child.CloneNode(true); + let clone = child.CloneNode(/* deep */ true)?; // Step 15.2. fragment.upcast::<Node>().AppendChild(&clone)?; } @@ -536,7 +560,7 @@ impl RangeMethods for Range { fragment.upcast::<Node>().AppendChild(&clone)?; } else { // Step 17.1. - let clone = child.CloneNode(false); + let clone = child.CloneNode(/* deep */ false)?; // Step 17.2. fragment.upcast::<Node>().AppendChild(&clone)?; // Step 17.3. @@ -572,7 +596,7 @@ impl RangeMethods for Range { if end_node == start_node { if let Some(end_data) = end_node.downcast::<CharacterData>() { // Step 4.1. - let clone = end_node.CloneNode(true); + let clone = end_node.CloneNode(/* deep */ true)?; // Step 4.2. let text = end_data.SubstringData(start_offset, end_offset - start_offset); clone @@ -613,7 +637,7 @@ impl RangeMethods for Range { if let Some(start_data) = child.downcast::<CharacterData>() { assert!(child == start_node); // Step 15.1. - let clone = start_node.CloneNode(true); + let clone = start_node.CloneNode(/* deep */ true)?; // Step 15.2. let text = start_data.SubstringData(start_offset, start_node.len() - start_offset); clone @@ -630,7 +654,7 @@ impl RangeMethods for Range { )?; } else { // Step 16.1. - let clone = child.CloneNode(false); + let clone = child.CloneNode(/* deep */ false)?; // Step 16.2. fragment.upcast::<Node>().AppendChild(&clone)?; // Step 16.3. @@ -657,7 +681,7 @@ impl RangeMethods for Range { if let Some(end_data) = child.downcast::<CharacterData>() { assert!(child == end_node); // Step 18.1. - let clone = end_node.CloneNode(true); + let clone = end_node.CloneNode(/* deep */ true)?; // Step 18.2. let text = end_data.SubstringData(0, end_offset); clone @@ -670,7 +694,7 @@ impl RangeMethods for Range { end_data.ReplaceData(0, end_offset, DOMString::new())?; } else { // Step 19.1. - let clone = child.CloneNode(false); + let clone = child.CloneNode(/* deep */ false)?; // Step 19.2. fragment.upcast::<Node>().AppendChild(&clone)?; // Step 19.3. @@ -763,7 +787,7 @@ impl RangeMethods for Range { // Step 11 let new_offset = new_offset + - if node.type_id() == NodeTypeId::DocumentFragment { + if let NodeTypeId::DocumentFragment(_) = node.type_id() { node.len() } else { 1 @@ -868,9 +892,9 @@ impl RangeMethods for Range { let end = self.EndContainer(); if start - .inclusive_ancestors() + .inclusive_ancestors(ShadowIncluding::No) .any(|n| !n.is_inclusive_ancestor_of(&end) && !n.is::<Text>()) || - end.inclusive_ancestors() + end.inclusive_ancestors(ShadowIncluding::No) .any(|n| !n.is_inclusive_ancestor_of(&start) && !n.is::<Text>()) { return Err(Error::InvalidState); @@ -878,7 +902,9 @@ impl RangeMethods for Range { // Step 2. match new_parent.type_id() { - NodeTypeId::Document(_) | NodeTypeId::DocumentType | NodeTypeId::DocumentFragment => { + NodeTypeId::Document(_) | + NodeTypeId::DocumentType | + NodeTypeId::DocumentFragment(_) => { return Err(Error::InvalidNodeType); }, _ => (), @@ -954,7 +980,7 @@ impl RangeMethods for Range { let node = self.StartContainer(); let owner_doc = node.owner_doc(); let element = match node.type_id() { - NodeTypeId::Document(_) | NodeTypeId::DocumentFragment => None, + NodeTypeId::Document(_) | NodeTypeId::DocumentFragment(_) => None, NodeTypeId::Element(_) => Some(DomRoot::downcast::<Element>(node).unwrap()), NodeTypeId::CharacterData(CharacterDataTypeId::Comment) | NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => node.GetParentElement(), @@ -969,7 +995,10 @@ impl RangeMethods for Range { let fragment_node = element.parse_fragment(fragment)?; // Step 4. - for node in fragment_node.upcast::<Node>().traverse_preorder() { + for node in fragment_node + .upcast::<Node>() + .traverse_preorder(ShadowIncluding::No) + { if let Some(script) = node.downcast::<HTMLScriptElement>() { script.set_already_started(false); script.set_parser_inserted(false); @@ -1046,7 +1075,7 @@ fn bp_position(a_node: &Node, a_offset: u32, b_node: &Node, b_offset: u32) -> Op } } else if position & NodeConstants::DOCUMENT_POSITION_CONTAINS != 0 { // Step 3-1, 3-2. - let mut b_ancestors = b_node.inclusive_ancestors(); + let mut b_ancestors = b_node.inclusive_ancestors(ShadowIncluding::No); let child = b_ancestors .find(|child| &*child.GetParentNode().unwrap() == a_node) .unwrap(); diff --git a/components/script/dom/raredata.rs b/components/script/dom/raredata.rs new file mode 100644 index 00000000000..2372b0b89ab --- /dev/null +++ b/components/script/dom/raredata.rs @@ -0,0 +1,42 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::root::Dom; +use crate::dom::customelementregistry::{ + CustomElementDefinition, CustomElementReaction, CustomElementState, +}; +use crate::dom::mutationobserver::RegisteredObserver; +use crate::dom::shadowroot::ShadowRoot; +use std::rc::Rc; + +//XXX(ferjm) Ideally merge NodeRareData and ElementRareData so they share +// storage. + +#[derive(Default, JSTraceable, MallocSizeOf)] +#[must_root] +pub struct NodeRareData { + /// The shadow root the node belongs to. + /// This is None if the node is not in a shadow tree or + /// if it is a ShadowRoot. + pub containing_shadow_root: Option<Dom<ShadowRoot>>, + /// Registered observers for this node. + pub mutation_observers: Vec<RegisteredObserver>, +} + +#[derive(Default, JSTraceable, MallocSizeOf)] +#[must_root] +pub struct ElementRareData { + /// https://dom.spec.whatwg.org/#dom-element-shadowroot + /// The ShadowRoot this element is host of. + /// XXX This is currently not exposed to web content. Only for + /// internal use. + pub shadow_root: Option<Dom<ShadowRoot>>, + /// <https://html.spec.whatwg.org/multipage/#custom-element-reaction-queue> + pub custom_element_reaction_queue: Vec<CustomElementReaction>, + /// <https://dom.spec.whatwg.org/#concept-element-custom-element-definition> + #[ignore_malloc_size_of = "Rc"] + pub custom_element_definition: Option<Rc<CustomElementDefinition>>, + /// <https://dom.spec.whatwg.org/#concept-element-custom-element-state> + pub custom_element_state: CustomElementState, +} diff --git a/components/script/dom/servoparser/html.rs b/components/script/dom/servoparser/html.rs index 965982d8b51..c9e3a06f34f 100644 --- a/components/script/dom/servoparser/html.rs +++ b/components/script/dom/servoparser/html.rs @@ -249,7 +249,7 @@ impl<'a> Serialize for &'a Node { serializer.write_processing_instruction(&pi.target(), &data)?; }, - NodeTypeId::DocumentFragment => {}, + NodeTypeId::DocumentFragment(_) => {}, NodeTypeId::Document(_) => panic!("Can't serialize Document node itself"), NodeTypeId::Element(_) => panic!("Element shouldn't appear here"), diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 3c0d321910c..9f6a2214ed0 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -27,7 +27,7 @@ use crate::dom::htmlformelement::{FormControlElementHelpers, HTMLFormElement}; use crate::dom::htmlimageelement::HTMLImageElement; use crate::dom::htmlscriptelement::{HTMLScriptElement, ScriptResult}; use crate::dom::htmltemplateelement::HTMLTemplateElement; -use crate::dom::node::Node; +use crate::dom::node::{Node, ShadowIncluding}; use crate::dom::performanceentry::PerformanceEntry; use crate::dom::performancenavigationtiming::PerformanceNavigationTiming; use crate::dom::processinginstruction::ProcessingInstruction; @@ -194,7 +194,7 @@ impl ServoParser { // Step 11. let form = context_node - .inclusive_ancestors() + .inclusive_ancestors(ShadowIncluding::No) .find(|element| element.is::<HTMLFormElement>()); let fragment_context = FragmentContext { diff --git a/components/script/dom/shadowroot.rs b/components/script/dom/shadowroot.rs new file mode 100644 index 00000000000..ae108f8109c --- /dev/null +++ b/components/script/dom/shadowroot.rs @@ -0,0 +1,268 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::ShadowRootBinding::ShadowRootMethods; +use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{self, ShadowRootMode}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::reflect_dom_object; +use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::cssstylesheet::CSSStyleSheet; +use crate::dom::document::Document; +use crate::dom::documentfragment::DocumentFragment; +use crate::dom::documentorshadowroot::{DocumentOrShadowRoot, StyleSheetInDocument}; +use crate::dom::element::Element; +use crate::dom::node::{Node, NodeDamage, NodeFlags, ShadowIncluding}; +use crate::dom::stylesheetlist::{StyleSheetList, StyleSheetListOwner}; +use crate::dom::window::Window; +use crate::stylesheet_set::StylesheetSetRef; +use dom_struct::dom_struct; +use selectors::context::QuirksMode; +use servo_arc::Arc; +use servo_atoms::Atom; +use style::author_styles::AuthorStyles; +use style::dom::TElement; +use style::media_queries::Device; +use style::shared_lock::SharedRwLockReadGuard; +use style::stylesheets::Stylesheet; + +// https://dom.spec.whatwg.org/#interface-shadowroot +#[dom_struct] +pub struct ShadowRoot { + document_fragment: DocumentFragment, + document_or_shadow_root: DocumentOrShadowRoot, + document: Dom<Document>, + host: Dom<Element>, + /// List of author styles associated with nodes in this shadow tree. + author_styles: DomRefCell<AuthorStyles<StyleSheetInDocument>>, + stylesheet_list: MutNullableDom<StyleSheetList>, + window: Dom<Window>, +} + +impl ShadowRoot { + #[allow(unrooted_must_root)] + fn new_inherited(host: &Element, document: &Document) -> ShadowRoot { + let document_fragment = DocumentFragment::new_inherited(document); + let node = document_fragment.upcast::<Node>(); + node.set_flag(NodeFlags::IS_IN_SHADOW_TREE, true); + node.set_flag( + NodeFlags::IS_CONNECTED, + host.upcast::<Node>().is_connected(), + ); + ShadowRoot { + document_fragment, + document_or_shadow_root: DocumentOrShadowRoot::new(document.window()), + document: Dom::from_ref(document), + host: Dom::from_ref(host), + author_styles: DomRefCell::new(AuthorStyles::new()), + stylesheet_list: MutNullableDom::new(None), + window: Dom::from_ref(document.window()), + } + } + + pub fn new(host: &Element, document: &Document) -> DomRoot<ShadowRoot> { + reflect_dom_object( + Box::new(ShadowRoot::new_inherited(host, document)), + document.window(), + ShadowRootBinding::Wrap, + ) + } + + pub fn get_focused_element(&self) -> Option<DomRoot<Element>> { + //XXX get retargeted focused element + None + } + + pub fn stylesheet_count(&self) -> usize { + self.author_styles.borrow().stylesheets.len() + } + + pub fn stylesheet_at(&self, index: usize) -> Option<DomRoot<CSSStyleSheet>> { + let stylesheets = &self.author_styles.borrow().stylesheets; + + stylesheets + .get(index) + .and_then(|s| s.owner.upcast::<Node>().get_cssom_stylesheet()) + } + + /// Add a stylesheet owned by `owner` to the list of shadow root sheets, in the + /// correct tree position. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn add_stylesheet(&self, owner: &Element, sheet: Arc<Stylesheet>) { + let stylesheets = &mut self.author_styles.borrow_mut().stylesheets; + let insertion_point = stylesheets + .iter() + .find(|sheet_in_shadow| { + owner + .upcast::<Node>() + .is_before(sheet_in_shadow.owner.upcast()) + }) + .cloned(); + DocumentOrShadowRoot::add_stylesheet( + owner, + StylesheetSetRef::Author(stylesheets), + sheet, + insertion_point, + self.document.style_shared_lock(), + ); + } + + /// Remove a stylesheet owned by `owner` from the list of shadow root sheets. + #[allow(unrooted_must_root)] // Owner needs to be rooted already necessarily. + pub fn remove_stylesheet(&self, owner: &Element, s: &Arc<Stylesheet>) { + DocumentOrShadowRoot::remove_stylesheet( + owner, + s, + StylesheetSetRef::Author(&mut self.author_styles.borrow_mut().stylesheets), + ) + } + + pub fn invalidate_stylesheets(&self) { + self.document.invalidate_shadow_roots_stylesheets(); + self.author_styles.borrow_mut().stylesheets.force_dirty(); + // Mark the host element dirty so a reflow will be performed. + self.host + .upcast::<Node>() + .dirty(NodeDamage::NodeStyleDamaged); + } + + /// Remove any existing association between the provided id and any elements + /// in this shadow tree. + pub fn unregister_named_element(&self, to_unregister: &Element, id: Atom) { + self.document_or_shadow_root.unregister_named_element( + self.document_fragment.id_map(), + to_unregister, + &id, + ); + } + + /// Associate an element present in this shadow tree with the provided id. + pub fn register_named_element(&self, element: &Element, id: Atom) { + let root = self + .upcast::<Node>() + .inclusive_ancestors(ShadowIncluding::No) + .last() + .unwrap(); + self.document_or_shadow_root.register_named_element( + self.document_fragment.id_map(), + element, + &id, + root, + ); + } +} + +impl ShadowRootMethods for ShadowRoot { + // https://html.spec.whatwg.org/multipage/#dom-document-activeelement + fn GetActiveElement(&self) -> Option<DomRoot<Element>> { + self.document_or_shadow_root + .get_active_element(self.get_focused_element(), None, None) + } + + // https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint + fn ElementFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Option<DomRoot<Element>> { + // Return the result of running the retargeting algorithm with context object + // and the original result as input. + match self.document_or_shadow_root.element_from_point( + x, + y, + None, + self.document.has_browsing_context(), + ) { + Some(e) => { + let retargeted_node = self.upcast::<Node>().retarget(e.upcast::<Node>()); + retargeted_node + .downcast::<Element>() + .map(|n| DomRoot::from_ref(n)) + }, + None => None, + } + } + + // https://drafts.csswg.org/cssom-view/#dom-document-elementsfrompoint + fn ElementsFromPoint(&self, x: Finite<f64>, y: Finite<f64>) -> Vec<DomRoot<Element>> { + // Return the result of running the retargeting algorithm with context object + // and the original result as input + let mut elements = Vec::new(); + for e in self + .document_or_shadow_root + .elements_from_point(x, y, None, self.document.has_browsing_context()) + .iter() + { + let retargeted_node = self.upcast::<Node>().retarget(e.upcast::<Node>()); + if let Some(element) = retargeted_node + .downcast::<Element>() + .map(|n| DomRoot::from_ref(n)) + { + elements.push(element); + } + } + elements + } + + /// https://dom.spec.whatwg.org/#dom-shadowroot-mode + fn Mode(&self) -> ShadowRootMode { + ShadowRootMode::Closed + } + + /// https://dom.spec.whatwg.org/#dom-shadowroot-host + fn Host(&self) -> DomRoot<Element> { + DomRoot::from_ref(&self.host) + } + + // https://drafts.csswg.org/cssom/#dom-document-stylesheets + fn StyleSheets(&self) -> DomRoot<StyleSheetList> { + self.stylesheet_list.or_init(|| { + StyleSheetList::new( + &self.window, + StyleSheetListOwner::ShadowRoot(Dom::from_ref(self)), + ) + }) + } +} + +#[allow(unsafe_code)] +pub trait LayoutShadowRootHelpers { + unsafe fn get_host_for_layout(&self) -> LayoutDom<Element>; + unsafe fn get_style_data_for_layout<'a, E: TElement>( + &self, + ) -> &'a AuthorStyles<StyleSheetInDocument>; + unsafe fn flush_stylesheets<E: TElement>( + &self, + device: &Device, + quirks_mode: QuirksMode, + guard: &SharedRwLockReadGuard, + ); +} + +impl LayoutShadowRootHelpers for LayoutDom<ShadowRoot> { + #[inline] + #[allow(unsafe_code)] + unsafe fn get_host_for_layout(&self) -> LayoutDom<Element> { + (*self.unsafe_get()).host.to_layout() + } + + #[inline] + #[allow(unsafe_code)] + unsafe fn get_style_data_for_layout<'a, E: TElement>( + &self, + ) -> &'a AuthorStyles<StyleSheetInDocument> { + (*self.unsafe_get()).author_styles.borrow_for_layout() + } + + #[inline] + #[allow(unsafe_code)] + unsafe fn flush_stylesheets<E: TElement>( + &self, + device: &Device, + quirks_mode: QuirksMode, + guard: &SharedRwLockReadGuard, + ) { + let mut author_styles = (*self.unsafe_get()).author_styles.borrow_mut_for_layout(); + if author_styles.stylesheets.dirty() { + author_styles.flush::<E>(device, quirks_mode, guard); + } + } +} diff --git a/components/script/dom/stylesheetlist.rs b/components/script/dom/stylesheetlist.rs index c1c5c0b2d5f..236f1c9cd9a 100644 --- a/components/script/dom/stylesheetlist.rs +++ b/components/script/dom/stylesheetlist.rs @@ -6,30 +6,85 @@ use crate::dom::bindings::codegen::Bindings::StyleSheetListBinding; use crate::dom::bindings::codegen::Bindings::StyleSheetListBinding::StyleSheetListMethods; use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::cssstylesheet::CSSStyleSheet; use crate::dom::document::Document; +use crate::dom::element::Element; +use crate::dom::shadowroot::ShadowRoot; use crate::dom::stylesheet::StyleSheet; use crate::dom::window::Window; use dom_struct::dom_struct; +use servo_arc::Arc; +use style::stylesheets::Stylesheet; + +#[must_root] +#[derive(JSTraceable, MallocSizeOf)] +pub enum StyleSheetListOwner { + Document(Dom<Document>), + ShadowRoot(Dom<ShadowRoot>), +} + +impl StyleSheetListOwner { + pub fn stylesheet_count(&self) -> usize { + match *self { + StyleSheetListOwner::Document(ref doc) => doc.stylesheet_count(), + StyleSheetListOwner::ShadowRoot(ref shadow_root) => shadow_root.stylesheet_count(), + } + } + + pub fn stylesheet_at(&self, index: usize) -> Option<DomRoot<CSSStyleSheet>> { + match *self { + StyleSheetListOwner::Document(ref doc) => doc.stylesheet_at(index), + StyleSheetListOwner::ShadowRoot(ref shadow_root) => shadow_root.stylesheet_at(index), + } + } + + pub fn add_stylesheet(&self, owner: &Element, sheet: Arc<Stylesheet>) { + match *self { + StyleSheetListOwner::Document(ref doc) => doc.add_stylesheet(owner, sheet), + StyleSheetListOwner::ShadowRoot(ref shadow_root) => { + shadow_root.add_stylesheet(owner, sheet) + }, + } + } + + pub fn remove_stylesheet(&self, owner: &Element, s: &Arc<Stylesheet>) { + match *self { + StyleSheetListOwner::Document(ref doc) => doc.remove_stylesheet(owner, s), + StyleSheetListOwner::ShadowRoot(ref shadow_root) => { + shadow_root.remove_stylesheet(owner, s) + }, + } + } + + pub fn invalidate_stylesheets(&self) { + match *self { + StyleSheetListOwner::Document(ref doc) => doc.invalidate_stylesheets(), + StyleSheetListOwner::ShadowRoot(ref shadow_root) => { + shadow_root.invalidate_stylesheets() + }, + } + } +} #[dom_struct] pub struct StyleSheetList { reflector_: Reflector, - document: Dom<Document>, + document_or_shadow_root: StyleSheetListOwner, } impl StyleSheetList { #[allow(unrooted_must_root)] - fn new_inherited(doc: Dom<Document>) -> StyleSheetList { + fn new_inherited(doc_or_sr: StyleSheetListOwner) -> StyleSheetList { StyleSheetList { reflector_: Reflector::new(), - document: doc, + document_or_shadow_root: doc_or_sr, } } #[allow(unrooted_must_root)] - pub fn new(window: &Window, document: Dom<Document>) -> DomRoot<StyleSheetList> { + pub fn new(window: &Window, doc_or_sr: StyleSheetListOwner) -> DomRoot<StyleSheetList> { reflect_dom_object( - Box::new(StyleSheetList::new_inherited(document)), + Box::new(StyleSheetList::new_inherited(doc_or_sr)), window, StyleSheetListBinding::Wrap, ) @@ -39,14 +94,14 @@ impl StyleSheetList { impl StyleSheetListMethods for StyleSheetList { // https://drafts.csswg.org/cssom/#dom-stylesheetlist-length fn Length(&self) -> u32 { - self.document.stylesheet_count() as u32 + self.document_or_shadow_root.stylesheet_count() as u32 } // https://drafts.csswg.org/cssom/#dom-stylesheetlist-item fn Item(&self, index: u32) -> Option<DomRoot<StyleSheet>> { // XXXManishearth this doesn't handle the origin clean flag and is a // cors vulnerability - self.document + self.document_or_shadow_root .stylesheet_at(index as usize) .map(DomRoot::upcast) } diff --git a/components/script/dom/virtualmethods.rs b/components/script/dom/virtualmethods.rs index 7a41642a201..f3b8b633232 100644 --- a/components/script/dom/virtualmethods.rs +++ b/components/script/dom/virtualmethods.rs @@ -51,7 +51,7 @@ use crate::dom::htmltemplateelement::HTMLTemplateElement; use crate::dom::htmltextareaelement::HTMLTextAreaElement; use crate::dom::htmltitleelement::HTMLTitleElement; use crate::dom::htmlvideoelement::HTMLVideoElement; -use crate::dom::node::{ChildrenMutation, CloneChildrenFlag, Node, UnbindContext}; +use crate::dom::node::{BindContext, ChildrenMutation, CloneChildrenFlag, Node, UnbindContext}; use crate::dom::svgsvgelement::SVGSVGElement; use html5ever::LocalName; use style::attr::AttrValue; @@ -90,15 +90,15 @@ pub trait VirtualMethods { } } - /// Called when a Node is appended to a tree, where 'tree_in_doc' indicates + /// Called when a Node is appended to a tree, where 'tree_connected' indicates /// whether the tree is part of a Document. - 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); } } - /// Called when a Node is removed from a tree, where 'tree_in_doc' + /// Called when a Node is removed from a tree, where 'tree_connected' /// indicates whether the tree is part of a Document. /// Implements removing steps: /// <https://dom.spec.whatwg.org/#concept-node-remove-ext> diff --git a/components/script/dom/webidls/Document.webidl b/components/script/dom/webidls/Document.webidl index ccc3d339377..85c3362f42e 100644 --- a/components/script/dom/webidls/Document.webidl +++ b/components/script/dom/webidls/Document.webidl @@ -132,7 +132,6 @@ partial /*sealed*/ interface Document { // user interaction readonly attribute Window?/*Proxy?*/ defaultView; - readonly attribute Element? activeElement; boolean hasFocus(); // [CEReactions] // attribute DOMString designMode; @@ -199,17 +198,6 @@ partial interface Document { TouchList createTouchList(Touch... touches); }; -// https://drafts.csswg.org/cssom-view/#dom-document-elementfrompoint -partial interface Document { - Element? elementFromPoint(double x, double y); - sequence<Element> elementsFromPoint(double x, double y); -}; - -// https://drafts.csswg.org/cssom/#extensions-to-the-document-interface -partial interface Document { - [SameObject] readonly attribute StyleSheetList styleSheets; -}; - // https://fullscreen.spec.whatwg.org/#api partial interface Document { [LenientSetter] readonly attribute boolean fullscreenEnabled; @@ -221,3 +209,5 @@ partial interface Document { attribute EventHandler onfullscreenchange; attribute EventHandler onfullscreenerror; }; + +Document implements DocumentOrShadowRoot; diff --git a/components/script/dom/webidls/DocumentOrShadowRoot.webidl b/components/script/dom/webidls/DocumentOrShadowRoot.webidl new file mode 100644 index 00000000000..b5a1a1d0cee --- /dev/null +++ b/components/script/dom/webidls/DocumentOrShadowRoot.webidl @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +/* + * The origin of this IDL file is + * https://dom.spec.whatwg.org/#documentorshadowroot + * https://w3c.github.io/webcomponents/spec/shadow/#extensions-to-the-documentorshadowroot-mixin + */ + +[NoInterfaceObject] +interface DocumentOrShadowRoot { + // Selection? getSelection(); + Element? elementFromPoint (double x, double y); + sequence<Element> elementsFromPoint (double x, double y); + // CaretPosition? caretPositionFromPoint (double x, double y); + readonly attribute Element? activeElement; + readonly attribute StyleSheetList styleSheets; +}; diff --git a/components/script/dom/webidls/Element.webidl b/components/script/dom/webidls/Element.webidl index b7de03ac344..af14f81b373 100644 --- a/components/script/dom/webidls/Element.webidl +++ b/components/script/dom/webidls/Element.webidl @@ -81,6 +81,8 @@ interface Element : Node { void insertAdjacentText(DOMString where_, DOMString data); [CEReactions, Throws] void insertAdjacentHTML(DOMString position, DOMString html); + + [Throws, Pref="dom.shadowdom.enabled"] ShadowRoot attachShadow(); }; // http://dev.w3.org/csswg/cssom-view/#extensions-to-the-element-interface diff --git a/components/script/dom/webidls/Node.webidl b/components/script/dom/webidls/Node.webidl index cddb776e416..9433e96efa2 100644 --- a/components/script/dom/webidls/Node.webidl +++ b/components/script/dom/webidls/Node.webidl @@ -32,7 +32,7 @@ interface Node : EventTarget { readonly attribute Document? ownerDocument; [Pure] - Node getRootNode(); + Node getRootNode(optional GetRootNodeOptions options); [Pure] readonly attribute Node? parentNode; @@ -58,7 +58,7 @@ interface Node : EventTarget { [CEReactions] void normalize(); - [CEReactions] + [CEReactions, Throws] Node cloneNode(optional boolean deep = false); [Pure] boolean isEqualNode(Node? node); @@ -92,3 +92,7 @@ interface Node : EventTarget { [CEReactions, Throws] Node removeChild(Node child); }; + +dictionary GetRootNodeOptions { + boolean composed = false; +}; diff --git a/components/script/dom/webidls/ShadowRoot.webidl b/components/script/dom/webidls/ShadowRoot.webidl new file mode 100644 index 00000000000..711d4f853bd --- /dev/null +++ b/components/script/dom/webidls/ShadowRoot.webidl @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +/* + * The origin of this IDL file is: + * https://dom.spec.whatwg.org/#interface-shadowroot + */ + +[Exposed=Window] +interface ShadowRoot : DocumentFragment { + readonly attribute ShadowRootMode mode; + readonly attribute Element host; +}; + +enum ShadowRootMode { "open", "closed"}; + +ShadowRoot implements DocumentOrShadowRoot; diff --git a/components/script/lib.rs b/components/script/lib.rs index a057540ae22..327209d71db 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -69,6 +69,7 @@ pub mod script_thread; mod serviceworker_manager; mod serviceworkerjob; mod stylesheet_loader; +mod stylesheet_set; mod task_manager; mod task_queue; mod task_source; @@ -84,7 +85,9 @@ mod webdriver_handlers; /// /// TODO(emilio): A few of the FooHelpers can go away, presumably... pub mod layout_exports { - pub use crate::dom::bindings::inheritance::{CharacterDataTypeId, ElementTypeId}; + pub use crate::dom::bindings::inheritance::{ + CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, + }; pub use crate::dom::bindings::inheritance::{HTMLElementTypeId, NodeTypeId, TextTypeId}; pub use crate::dom::bindings::root::LayoutDom; pub use crate::dom::characterdata::LayoutCharacterDataHelpers; @@ -92,6 +95,7 @@ pub mod layout_exports { pub use crate::dom::element::{Element, LayoutElementHelpers, RawLayoutElementHelpers}; pub use crate::dom::node::NodeFlags; pub use crate::dom::node::{LayoutNodeHelpers, Node}; + pub use crate::dom::shadowroot::{LayoutShadowRootHelpers, ShadowRoot}; pub use crate::dom::text::Text; } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 84a4182ae3e..4fbaec2b465 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -51,7 +51,9 @@ use crate::dom::globalscope::GlobalScope; use crate::dom::htmlanchorelement::HTMLAnchorElement; use crate::dom::htmliframeelement::{HTMLIFrameElement, NavigationType}; use crate::dom::mutationobserver::MutationObserver; -use crate::dom::node::{from_untrusted_node_address, window_from_node, Node, NodeDamage}; +use crate::dom::node::{ + from_untrusted_node_address, window_from_node, Node, NodeDamage, ShadowIncluding, +}; use crate::dom::performanceentry::PerformanceEntry; use crate::dom::performancepainttiming::PerformancePaintTiming; use crate::dom::serviceworker::TrustedServiceWorkerAddress; @@ -3097,7 +3099,7 @@ impl ScriptThread { if let Some(target) = self.topmost_mouse_over_target.get() { if let Some(anchor) = target .upcast::<Node>() - .inclusive_ancestors() + .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::<HTMLAnchorElement>) .next() { @@ -3121,7 +3123,7 @@ impl ScriptThread { if let Some(target) = prev_mouse_over_target { if let Some(_) = target .upcast::<Node>() - .inclusive_ancestors() + .inclusive_ancestors(ShadowIncluding::No) .filter_map(DomRoot::downcast::<HTMLAnchorElement>) .next() { diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs index 61c89b35f80..69975c7803b 100644 --- a/components/script/stylesheet_loader.rs +++ b/components/script/stylesheet_loader.rs @@ -13,8 +13,9 @@ use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::htmlelement::HTMLElement; use crate::dom::htmllinkelement::{HTMLLinkElement, RequestGenerationId}; -use crate::dom::node::{document_from_node, window_from_node}; +use crate::dom::node::{containing_shadow_root, document_from_node, window_from_node}; use crate::dom::performanceresourcetiming::InitiatorType; +use crate::dom::shadowroot::ShadowRoot; use crate::network_listener::{self, NetworkListener, PreInvoke, ResourceTimingListener}; use cssparser::SourceLocation; use encoding_rs::UTF_8; @@ -81,6 +82,7 @@ pub struct StylesheetContext { data: Vec<u8>, /// The node document for elem when the load was initiated. document: Trusted<Document>, + shadow_root: Option<Trusted<ShadowRoot>>, origin_clean: bool, /// A token which must match the generation id of the `HTMLLinkElement` for it to load the stylesheet. /// This is ignored for `HTMLStyleElement` and imports. @@ -187,7 +189,11 @@ impl FetchResponseListener for StylesheetContext { }, } - document.invalidate_stylesheets(); + if let Some(ref shadow_root) = self.shadow_root { + shadow_root.root().invalidate_stylesheets(); + } else { + document.invalidate_stylesheets(); + } // FIXME: Revisit once consensus is reached at: // https://github.com/whatwg/html/issues/1142 @@ -264,6 +270,7 @@ impl<'a> StylesheetLoader<'a> { integrity_metadata: String, ) { let document = document_from_node(self.elem); + let shadow_root = containing_shadow_root(self.elem).map(|sr| Trusted::new(&*sr)); let gen = self .elem .downcast::<HTMLLinkElement>() @@ -275,6 +282,7 @@ impl<'a> StylesheetLoader<'a> { metadata: None, data: vec![], document: Trusted::new(&*document), + shadow_root, origin_clean: true, request_generation_id: gen, resource_timing: ResourceFetchTiming::new(ResourceTimingType::Resource), diff --git a/components/script/stylesheet_set.rs b/components/script/stylesheet_set.rs new file mode 100644 index 00000000000..ddb2f608e57 --- /dev/null +++ b/components/script/stylesheet_set.rs @@ -0,0 +1,70 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use style::media_queries::Device; +use style::shared_lock::SharedRwLockReadGuard; +use style::stylesheet_set::{AuthorStylesheetSet, DocumentStylesheetSet}; +use style::stylesheets::StylesheetInDocument; + +/// Functionality common to DocumentStylesheetSet and AuthorStylesheetSet. +pub enum StylesheetSetRef<'a, S> +where + S: StylesheetInDocument + PartialEq + 'static, +{ + /// Author stylesheet set. + Author(&'a mut AuthorStylesheetSet<S>), + /// Document stylesheet set. + Document(&'a mut DocumentStylesheetSet<S>), +} + +impl<'a, S> StylesheetSetRef<'a, S> +where + S: StylesheetInDocument + PartialEq + 'static, +{ + /// Appends a new stylesheet to the current set. + /// + /// No device implies not computing invalidations. + pub fn append_stylesheet( + &mut self, + device: Option<&Device>, + sheet: S, + guard: &SharedRwLockReadGuard, + ) { + match self { + StylesheetSetRef::Author(set) => set.append_stylesheet(device, sheet, guard), + StylesheetSetRef::Document(set) => set.append_stylesheet(device, sheet, guard), + } + } + + /// Insert a given stylesheet before another stylesheet in the document. + pub fn insert_stylesheet_before( + &mut self, + device: Option<&Device>, + sheet: S, + before_sheet: S, + guard: &SharedRwLockReadGuard, + ) { + match self { + StylesheetSetRef::Author(set) => { + set.insert_stylesheet_before(device, sheet, before_sheet, guard) + }, + StylesheetSetRef::Document(set) => { + set.insert_stylesheet_before(device, sheet, before_sheet, guard) + }, + } + } + + /// Remove a given stylesheet from the set. + pub fn remove_stylesheet( + &mut self, + device: Option<&Device>, + sheet: S, + guard: &SharedRwLockReadGuard, + ) { + match self { + StylesheetSetRef::Author(set) => set.remove_stylesheet(device, sheet, guard), + StylesheetSetRef::Document(set) => set.remove_stylesheet(device, sheet, guard), + } + } +} diff --git a/components/script/webdriver_handlers.rs b/components/script/webdriver_handlers.rs index a3c143a4faa..ee373126513 100644 --- a/components/script/webdriver_handlers.rs +++ b/components/script/webdriver_handlers.rs @@ -22,7 +22,7 @@ use crate::dom::htmlelement::HTMLElement; use crate::dom::htmliframeelement::HTMLIFrameElement; use crate::dom::htmlinputelement::HTMLInputElement; use crate::dom::htmloptionelement::HTMLOptionElement; -use crate::dom::node::{window_from_node, Node}; +use crate::dom::node::{window_from_node, Node, ShadowIncluding}; use crate::script_thread::Documents; use cookie::Cookie; use euclid::{Point2D, Rect, Size2D}; @@ -50,7 +50,7 @@ fn find_node_by_unique_id( documents.find_document(pipeline).and_then(|document| { document .upcast::<Node>() - .traverse_preorder() + .traverse_preorder(ShadowIncluding::Yes) .find(|candidate| candidate.unique_id() == node_id) }) } diff --git a/components/script_layout_interface/wrapper_traits.rs b/components/script_layout_interface/wrapper_traits.rs index 0a7e080bebb..0e1df2ac4c8 100644 --- a/components/script_layout_interface/wrapper_traits.rs +++ b/components/script_layout_interface/wrapper_traits.rs @@ -104,6 +104,9 @@ pub trait LayoutNode: Debug + GetLayoutData + TNode { fn traverse_preorder(self) -> TreeIterator<Self> { TreeIterator::new(self) } + + /// Returns whether the node is connected. + fn is_connected(&self) -> bool; } pub struct ReverseChildrenIterator<ConcreteNode> diff --git a/components/style/author_styles.rs b/components/style/author_styles.rs index 37eb68ff4f7..eff64ed6c1e 100644 --- a/components/style/author_styles.rs +++ b/components/style/author_styles.rs @@ -18,6 +18,7 @@ use crate::stylist::CascadeData; /// A set of author stylesheets and their computed representation, such as the /// ones used for ShadowRoot and XBL. +#[derive(MallocSizeOf)] pub struct AuthorStyles<S> where S: StylesheetInDocument + PartialEq + 'static, diff --git a/components/style/stylesheet_set.rs b/components/style/stylesheet_set.rs index 409978c3a9e..d2c3ad0f88e 100644 --- a/components/style/stylesheet_set.rs +++ b/components/style/stylesheet_set.rs @@ -585,6 +585,16 @@ where self.collection.len() == 0 } + /// Returns the `index`th stylesheet in the collection of author styles if present. + pub fn get(&self, index: usize) -> Option<&S> { + self.collection.get(index) + } + + /// Returns the number of author stylesheets. + pub fn len(&self) -> usize { + self.collection.len() + } + fn collection_for( &mut self, _sheet: &S, diff --git a/resources/prefs.json b/resources/prefs.json index 1ca4197be44..b882ad9ca46 100644 --- a/resources/prefs.json +++ b/resources/prefs.json @@ -19,6 +19,7 @@ "dom.serviceworker.enabled": false, "dom.serviceworker.timeout_seconds": 60, "dom.servoparser.async_html_tokenizer.enabled": false, + "dom.shadowdom.enabled": false, "dom.svg.enabled": false, "dom.testable_crash.enabled": false, "dom.testbinding.enabled": false, diff --git a/tests/unit/script/size_of.rs b/tests/unit/script/size_of.rs index 4d56b2ab10b..1c5bcace945 100644 --- a/tests/unit/script/size_of.rs +++ b/tests/unit/script/size_of.rs @@ -30,10 +30,10 @@ macro_rules! sizeof_checker ( // Update the sizes here sizeof_checker!(size_event_target, EventTarget, 56); -sizeof_checker!(size_node, Node, 200); -sizeof_checker!(size_element, Element, 448); -sizeof_checker!(size_htmlelement, HTMLElement, 464); -sizeof_checker!(size_div, HTMLDivElement, 464); -sizeof_checker!(size_span, HTMLSpanElement, 464); -sizeof_checker!(size_text, Text, 232); -sizeof_checker!(size_characterdata, CharacterData, 232); +sizeof_checker!(size_node, Node, 184); +sizeof_checker!(size_element, Element, 392); +sizeof_checker!(size_htmlelement, HTMLElement, 408); +sizeof_checker!(size_div, HTMLDivElement, 408); +sizeof_checker!(size_span, HTMLSpanElement, 408); +sizeof_checker!(size_text, Text, 216); +sizeof_checker!(size_characterdata, CharacterData, 216); diff --git a/tests/wpt/metadata/css/cssom/interfaces.html.ini b/tests/wpt/metadata/css/cssom/interfaces.html.ini index d6a28f8765b..8ff4227d3c3 100644 --- a/tests/wpt/metadata/css/cssom/interfaces.html.ini +++ b/tests/wpt/metadata/css/cssom/interfaces.html.ini @@ -1272,6 +1272,3 @@ [CSSStyleDeclaration interface: sheet.cssRules[2\].cssRules[0\].style must inherit property "cssFloat" with the proper type] expected: FAIL - [ShadowRoot interface: attribute styleSheets] - expected: FAIL - diff --git a/tests/wpt/metadata/dom/interfaces.html.ini b/tests/wpt/metadata/dom/interfaces.html.ini index 3fb70edaaca..5586cd6a5b9 100644 --- a/tests/wpt/metadata/dom/interfaces.html.ini +++ b/tests/wpt/metadata/dom/interfaces.html.ini @@ -998,30 +998,6 @@ [Unscopable handled correctly for append([object Object\],[object Object\]) on DocumentFragment] expected: FAIL - [ShadowRoot interface: existence and properties of interface object] - expected: FAIL - - [ShadowRoot interface object length] - expected: FAIL - - [ShadowRoot interface object name] - expected: FAIL - - [ShadowRoot interface: existence and properties of interface prototype object] - expected: FAIL - - [ShadowRoot interface: existence and properties of interface prototype object's "constructor" property] - expected: FAIL - - [ShadowRoot interface: existence and properties of interface prototype object's @@unscopables property] - expected: FAIL - - [ShadowRoot interface: attribute mode] - expected: FAIL - - [ShadowRoot interface: attribute host] - expected: FAIL - [Element interface: existence and properties of interface prototype object's @@unscopables property] expected: FAIL diff --git a/tests/wpt/mozilla/meta/MANIFEST.json b/tests/wpt/mozilla/meta/MANIFEST.json index a29a6bb5c5a..662740e9f0b 100644 --- a/tests/wpt/mozilla/meta/MANIFEST.json +++ b/tests/wpt/mozilla/meta/MANIFEST.json @@ -7301,6 +7301,18 @@ {} ] ], + "mozilla/partial_shadow_dom_layout_style.html": [ + [ + "mozilla/partial_shadow_dom_layout_style.html", + [ + [ + "/_mozilla/mozilla/partial_shadow_dom_layout_style_ref.html", + "==" + ] + ], + {} + ] + ], "mozilla/remove_link_styles.html": [ [ "mozilla/remove_link_styles.html", @@ -10844,6 +10856,11 @@ {} ] ], + "mozilla/partial_shadow_dom_layout_style_ref.html": [ + [ + {} + ] + ], "mozilla/poster.png": [ [ {} @@ -13515,6 +13532,12 @@ {} ] ], + "mozilla/partial_shadow_dom.html": [ + [ + "mozilla/partial_shadow_dom.html", + {} + ] + ], "mozilla/postmessage_closed.html": [ [ "mozilla/postmessage_closed.html", @@ -20283,7 +20306,7 @@ "testharness" ], "mozilla/interfaces.html": [ - "a2b9fd23948319fabc0b5fff550b9565704b6678", + "c7e1929c626007e2ef3cdf9f2276620aa995c5b7", "testharness" ], "mozilla/interfaces.js": [ @@ -20430,6 +20453,18 @@ "5aff666995fe6cd1d4e84e63a9f6019d04387f8e", "testharness" ], + "mozilla/partial_shadow_dom.html": [ + "74e308f94036a6dbf5c4223cd3d229f49ffceb4e", + "testharness" + ], + "mozilla/partial_shadow_dom_layout_style.html": [ + "822e86bc07103351a5ceb2203fd9b6bee0c6367d", + "reftest" + ], + "mozilla/partial_shadow_dom_layout_style_ref.html": [ + "bf40d2cc35b6b2c1e32afffa0651cb1b26e41fe8", + "support" + ], "mozilla/poster.png": [ "33834c3ef095fa9c0080017e1b65b2eb8413eac4", "support" diff --git a/tests/wpt/mozilla/meta/mozilla/partial_shadow_dom.html.ini b/tests/wpt/mozilla/meta/mozilla/partial_shadow_dom.html.ini new file mode 100644 index 00000000000..6b14e5081c1 --- /dev/null +++ b/tests/wpt/mozilla/meta/mozilla/partial_shadow_dom.html.ini @@ -0,0 +1,2 @@ +[partial_shadow_dom.html] + prefs: [dom.shadowdom.enabled:true] diff --git a/tests/wpt/mozilla/meta/mozilla/partial_shadow_dom_layout_style.html.ini b/tests/wpt/mozilla/meta/mozilla/partial_shadow_dom_layout_style.html.ini new file mode 100644 index 00000000000..6612e2fb467 --- /dev/null +++ b/tests/wpt/mozilla/meta/mozilla/partial_shadow_dom_layout_style.html.ini @@ -0,0 +1,2 @@ +[partial_shadow_dom_layout_style.html] + prefs: [dom.shadowdom.enabled:true] diff --git a/tests/wpt/mozilla/tests/mozilla/interfaces.html b/tests/wpt/mozilla/tests/mozilla/interfaces.html index a2b9fd23948..c7e1929c626 100644 --- a/tests/wpt/mozilla/tests/mozilla/interfaces.html +++ b/tests/wpt/mozilla/tests/mozilla/interfaces.html @@ -202,6 +202,7 @@ test_interfaces([ "Request", "Response", "Screen", + "ShadowRoot", "Storage", "StorageEvent", "StyleSheet", diff --git a/tests/wpt/mozilla/tests/mozilla/partial_shadow_dom.html b/tests/wpt/mozilla/tests/mozilla/partial_shadow_dom.html new file mode 100644 index 00000000000..74e308f9403 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/partial_shadow_dom.html @@ -0,0 +1,41 @@ +<!doctype html> +<head> + <meta charset="utf-8"> + <title></title> + <script src="/resources/testharness.js"></script> + <script src="/resources/testharnessreport.js"></script> +</head> +<body> + <h1 id="header" class="header">Not in the shadows</h1> + <div id="host"> + </div> + <script> + test(function() { + // Attach shadow. + var host = document.getElementById('host'); + var shadowRoot = host.attachShadow(); + assert_not_equals(shadowRoot, null); + assert_equals(shadowRoot.host, host); + assert_equals(shadowRoot.mode, 'closed'); + + assert_equals(document.body.getElementsByTagName('h1').length, 1); + assert_equals(document.body.getElementsByClassName('header').length, 1); + assert_equals(document.getElementById('header').textContent, "Not in the shadows"); + assert_equals(document.querySelectorAll('h1').length, 1); + assert_equals(document.body.childNodes.length, 6); + + // Append child to shadow tree and check that its content is encapsulated. + var shadowChild = document.createElement('h1'); + shadowChild.classList.add('header'); + shadowChild.setAttribute('id', 'header'); + shadowChild.textContent = 'In the shadows'; + shadowRoot.appendChild(shadowChild); + assert_equals(document.body.getElementsByTagName('h1').length, 1); + assert_equals(document.body.getElementsByClassName('header').length, 1); + assert_equals(document.querySelectorAll('h1').length, 1); + assert_equals(document.body.childNodes.length, 6); + assert_equals(shadowRoot.querySelectorAll('h1').length, 1); + assert_equals(shadowRoot.getElementById('header').textContent, "In the shadows"); + }); + </script> +</body> diff --git a/tests/wpt/mozilla/tests/mozilla/partial_shadow_dom_layout_style.html b/tests/wpt/mozilla/tests/mozilla/partial_shadow_dom_layout_style.html new file mode 100644 index 00000000000..822e86bc071 --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/partial_shadow_dom_layout_style.html @@ -0,0 +1,22 @@ +<!doctype html> +<html> + <meta charset="utf-8"> + <title>Partial Shadow DOM support - layout & style</title> + <link rel="match" href="partial_shadow_dom_layout_style_ref.html"> + <style> + h1 { color: red; } + </style> + <h1>Not in the shadows</h1> + <div id="host"> + </div> + <script> + var host = document.getElementById('host'); + var shadowRoot = host.attachShadow({ mode: 'closed' }); + var shadowChild = document.createElement('h1'); + shadowChild.textContent = 'In the shadows'; + shadowRoot.appendChild(shadowChild); + var shadowStyles = document.createElement('style'); + shadowStyles.textContent = 'h1 { color: blue; }'; + shadowRoot.appendChild(shadowStyles); + </script> +</html> diff --git a/tests/wpt/mozilla/tests/mozilla/partial_shadow_dom_layout_style_ref.html b/tests/wpt/mozilla/tests/mozilla/partial_shadow_dom_layout_style_ref.html new file mode 100644 index 00000000000..bf40d2cc35b --- /dev/null +++ b/tests/wpt/mozilla/tests/mozilla/partial_shadow_dom_layout_style_ref.html @@ -0,0 +1,11 @@ +<!doctype html> +<meta charset="utf-8"> +<title>Partial Shadow DOM support - layout & style</title> +<style> + h1 { color: red; } + .blue { color: blue; } +</style> +<h1>Not in the shadows</h1> +<div id="host"> + <h1 class='blue'>In the shadows</h1> +</div> |