From 2d31d4301d66bce4678af848e80905a35b9c02aa Mon Sep 17 00:00:00 2001 From: Martin Robinson Date: Tue, 2 May 2023 08:35:33 +0200 Subject: [PATCH] Eliminate duplicate Layout DOM wrappers There are duplicate sets of Layout DOM wrappers: one for Layout 2013 and one for Layout 2020. As part of cleaning up and simplifying the wrappers, this change parameterizes them on the specific layout data they contain. This allows them to be shared again. In addition, various small cleanups are included. Fixes #29691. --- Cargo.lock | 3 + components/layout/data.rs | 8 +- components/layout_2020/element_data.rs | 3 + components/layout_thread/lib.rs | 40 +- components/layout_thread_2020/dom_wrapper.rs | 1537 ----------------- components/layout_thread_2020/lib.rs | 40 +- components/script/Cargo.toml | 3 + components/script/layout_dom/document.rs | 114 ++ .../layout_dom/element.rs} | 918 ++-------- components/script/layout_dom/mod.rs | 32 + components/script/layout_dom/node.rs | 591 +++++++ components/script/layout_dom/shadow_root.rs | 84 + components/script/lib.rs | 22 +- .../script_layout_interface/wrapper_traits.rs | 2 + 14 files changed, 1035 insertions(+), 2362 deletions(-) delete mode 100644 components/layout_thread_2020/dom_wrapper.rs create mode 100644 components/script/layout_dom/document.rs rename components/{layout_thread/dom_wrapper.rs => script/layout_dom/element.rs} (50%) create mode 100644 components/script/layout_dom/mod.rs create mode 100644 components/script/layout_dom/node.rs create mode 100644 components/script/layout_dom/shadow_root.rs diff --git a/Cargo.lock b/Cargo.lock index 3c99cb089ed..d945407f3ec 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5242,6 +5242,7 @@ dependencies = [ "accountable-refcell", "app_units", "arrayvec 0.7.1", + "atomic_refcell", "backtrace", "base64 0.10.1", "bitflags", @@ -5263,6 +5264,7 @@ dependencies = [ "euclid", "fnv", "fxhash", + "gfx_traits", "headers", "html5ever", "http", @@ -5295,6 +5297,7 @@ dependencies = [ "phf_shared 0.8.0", "pixels", "profile_traits", + "range", "ref_filter_map", "regex", "script_layout_interface", diff --git a/components/layout/data.rs b/components/layout/data.rs index 65d96bae244..7744b08a573 100644 --- a/components/layout/data.rs +++ b/components/layout/data.rs @@ -4,6 +4,7 @@ use crate::construct::ConstructionResult; use atomic_refcell::AtomicRefCell; +use script_layout_interface::wrapper_traits::LayoutDataTrait; use script_layout_interface::StyleData; pub struct StyleAndLayoutData<'dom> { @@ -14,6 +15,7 @@ pub struct StyleAndLayoutData<'dom> { } /// Data that layout associates with a node. +#[derive(Clone)] pub struct LayoutData { /// The current results of flow construction for this node. This is either a /// flow or a `ConstructionItem`. See comments in `construct.rs` for more @@ -32,9 +34,11 @@ pub struct LayoutData { pub flags: LayoutDataFlags, } -impl LayoutData { +impl LayoutDataTrait for LayoutData {} + +impl Default for LayoutData { /// Creates new layout data. - pub fn new() -> LayoutData { + fn default() -> LayoutData { Self { flow_construction_result: ConstructionResult::None, before_flow_construction_result: ConstructionResult::None, diff --git a/components/layout_2020/element_data.rs b/components/layout_2020/element_data.rs index f0b1b198432..8963f191d6f 100644 --- a/components/layout_2020/element_data.rs +++ b/components/layout_2020/element_data.rs @@ -6,6 +6,7 @@ use crate::cell::ArcRefCell; use crate::flexbox::FlexLevelBox; use crate::flow::inline::InlineLevelBox; use crate::flow::BlockLevelBox; +use script_layout_interface::wrapper_traits::LayoutDataTrait; #[derive(Default)] pub struct LayoutDataForElement { @@ -20,3 +21,5 @@ pub(super) enum LayoutBox { InlineLevel(ArcRefCell), FlexLevel(ArcRefCell), } + +impl LayoutDataTrait for LayoutDataForElement {} diff --git a/components/layout_thread/lib.rs b/components/layout_thread/lib.rs index d6fb814b758..bf4ff1b85bc 100644 --- a/components/layout_thread/lib.rs +++ b/components/layout_thread/lib.rs @@ -11,8 +11,6 @@ #[macro_use] extern crate crossbeam_channel; #[macro_use] -extern crate html5ever; -#[macro_use] extern crate layout; #[macro_use] extern crate lazy_static; @@ -21,9 +19,6 @@ extern crate log; #[macro_use] extern crate profile_traits; -mod dom_wrapper; - -use crate::dom_wrapper::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use app_units::Au; use crossbeam_channel::{Receiver, Sender}; use embedder_traits::resources::{self, Resource}; @@ -47,7 +42,6 @@ use layout::display_list::{IndexableText, ToLayout}; use layout::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils, MutableOwnedFlowUtils}; use layout::flow_ref::FlowRef; use layout::incremental::{RelayoutMode, SpecialRestyleDamage}; -use layout::layout_debug; use layout::parallel; use layout::query::{ process_client_rect_query, process_content_box_request, process_content_boxes_request, @@ -62,6 +56,7 @@ use layout::traversal::{ RecalcStyleAndConstructFlows, }; use layout::wrapper::LayoutNodeLayoutData; +use layout::{layout_debug, LayoutData}; use layout_traits::LayoutThreadFactory; use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use metrics::{PaintTimeMetrics, ProfilerMetadataFactory, ProgressiveWebMetric}; @@ -75,6 +70,7 @@ use parking_lot::RwLock; use profile_traits::mem::{self as profile_mem, Report, ReportKind, ReportsChan}; use profile_traits::time::{self as profile_time, profile, TimerMetadata}; use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType}; +use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use script_layout_interface::message::{LayoutThreadInit, Msg, NodesFromPointQueryType, Reflow}; use script_layout_interface::message::{QueryMsg, ReflowComplete, ReflowGoal, ScriptReflow}; use script_layout_interface::rpc::TextIndexResponse; @@ -976,7 +972,7 @@ impl LayoutThread { &self, data: &Reflow, reflow_goal: &ReflowGoal, - document: Option<&ServoLayoutDocument>, + document: Option<&ServoLayoutDocument>, layout_root: &mut dyn Flow, layout_context: &mut LayoutContext, rw_data: &mut LayoutThreadData, @@ -1302,11 +1298,16 @@ impl LayoutThread { let elements_with_snapshot: Vec<_> = restyles .iter() .filter(|r| r.1.snapshot.is_some()) - .map(|r| unsafe { ServoLayoutNode::new(&r.0).as_element().unwrap() }) + .map(|r| unsafe { + ServoLayoutNode::::new(&r.0) + .as_element() + .unwrap() + }) .collect(); for (el, restyle) in restyles { - let el = unsafe { ServoLayoutNode::new(&el).as_element().unwrap() }; + let el: ServoLayoutElement = + unsafe { ServoLayoutNode::new(&el).as_element().unwrap() }; // If we haven't styled this node yet, we don't need to track a // restyle. @@ -1357,10 +1358,9 @@ impl LayoutThread { let traversal = RecalcStyleAndConstructFlows::new(layout_context); let token = { - let shared = - >::shared_context( - &traversal, - ); + let shared = , + >>::shared_context(&traversal); RecalcStyleAndConstructFlows::pre_traverse(dirty_root, shared) }; @@ -1373,7 +1373,7 @@ impl LayoutThread { || { // Perform CSS selector matching and flow construction. let root = driver::traverse_dom::< - ServoLayoutElement, + ServoLayoutElement, RecalcStyleAndConstructFlows, >(&traversal, token, thread_pool); unsafe { @@ -1482,17 +1482,17 @@ impl LayoutThread { process_node_scroll_area_request(node, root_flow); }, &QueryMsg::NodeScrollIdQuery(node) => { - let node = unsafe { ServoLayoutNode::new(&node) }; + let node: ServoLayoutNode = unsafe { ServoLayoutNode::new(&node) }; rw_data.scroll_id_response = Some(process_node_scroll_id_request(self.id, node)); }, &QueryMsg::ResolvedStyleQuery(node, ref pseudo, ref property) => { - let node = unsafe { ServoLayoutNode::new(&node) }; + let node: ServoLayoutNode = unsafe { ServoLayoutNode::new(&node) }; rw_data.resolved_style_response = process_resolved_style_request(context, node, pseudo, property, root_flow); }, &QueryMsg::ResolvedFontStyleQuery(node, ref property, ref value) => { - let node = unsafe { ServoLayoutNode::new(&node) }; + let node: ServoLayoutNode = unsafe { ServoLayoutNode::new(&node) }; let url = self.url.clone(); rw_data.resolved_font_style_response = process_resolved_font_style_request( context, @@ -1528,7 +1528,7 @@ impl LayoutThread { results.iter().map(|result| result.node).collect() }, &QueryMsg::ElementInnerTextQuery(node) => { - let node = unsafe { ServoLayoutNode::new(&node) }; + let node: ServoLayoutNode = unsafe { ServoLayoutNode::new(&node) }; rw_data.element_inner_text_response = process_element_inner_text_query(node, &rw_data.indexable_text); }, @@ -1633,7 +1633,7 @@ impl LayoutThread { root_flow: &mut FlowRef, data: &Reflow, reflow_goal: &ReflowGoal, - document: Option<&ServoLayoutDocument>, + document: Option<&ServoLayoutDocument>, rw_data: &mut LayoutThreadData, context: &mut LayoutContext, ) { @@ -1742,7 +1742,7 @@ impl LayoutThread { data: &Reflow, mut root_flow: &mut FlowRef, reflow_goal: &ReflowGoal, - document: Option<&ServoLayoutDocument>, + document: Option<&ServoLayoutDocument>, rw_data: &mut LayoutThreadData, layout_context: &mut LayoutContext, ) { diff --git a/components/layout_thread_2020/dom_wrapper.rs b/components/layout_thread_2020/dom_wrapper.rs deleted file mode 100644 index 2b5f30f3cd9..00000000000 --- a/components/layout_thread_2020/dom_wrapper.rs +++ /dev/null @@ -1,1537 +0,0 @@ -/* 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/. */ - -//! A safe wrapper for DOM nodes that prevents layout from mutating the DOM, from letting DOM nodes -//! escape, and from generally doing anything that it isn't supposed to. This is accomplished via -//! a simple whitelist of allowed operations, along with some lifetime magic to prevent nodes from -//! escaping. -//! -//! As a security wrapper is only as good as its whitelist, be careful when adding operations to -//! this list. The cardinal rules are: -//! -//! 1. Layout is not allowed to mutate the DOM. -//! -//! 2. Layout is not allowed to see anything with `LayoutDom` in the name, because it could hang -//! onto these objects and cause use-after-free. -//! -//! When implementing wrapper functions, be careful that you do not touch the borrow flags, or you -//! will race and cause spurious thread failure. (Note that I do not believe these races are -//! exploitable, but they'll result in brokenness nonetheless.) -//! -//! Rules of the road for this file: -//! -//! * Do not call any methods on DOM nodes without checking to see whether they use borrow flags. -//! -//! o Instead of `get_attr()`, use `.get_attr_val_for_layout()`. -//! -//! o Instead of `html_element_in_html_document()`, use -//! `html_element_in_html_document_for_layout()`. - -#![allow(unsafe_code)] - -use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; -use gfx_traits::ByteIndex; -use html5ever::{LocalName, Namespace}; -use layout::element_data::LayoutDataForElement; -use layout::wrapper::GetStyleAndLayoutData; -use msg::constellation_msg::{BrowsingContextId, PipelineId}; -use net_traits::image::base::{Image, ImageMetadata}; -use range::Range; -use script::layout_exports::NodeFlags; -use script::layout_exports::ShadowRoot; -use script::layout_exports::{ - 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, LayoutShadowRootHelpers, -}; -use script_layout_interface::wrapper_traits::{ - DangerousThreadSafeLayoutNode, GetStyleAndOpaqueLayoutData, LayoutNode, -}; -use script_layout_interface::wrapper_traits::{ - PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode, -}; -use script_layout_interface::{ - HTMLCanvasData, HTMLMediaData, LayoutNodeType, StyleAndOpaqueLayoutData, -}; -use script_layout_interface::{SVGSVGData, StyleData, TrustedNodeAddress}; -use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; -use selectors::matching::VisitedHandlingMode; -use selectors::matching::{ElementSelectorFlags, MatchingContext, QuirksMode}; -use selectors::sink::Push; -use servo_arc::{Arc, ArcBorrow}; -use servo_atoms::Atom; -use servo_url::ServoUrl; -use std::borrow::Cow; -use std::fmt; -use std::fmt::Debug; -use std::hash::{Hash, Hasher}; -use std::sync::atomic::Ordering; -use std::sync::Arc as StdArc; -use style::animation::AnimationSetKey; -use style::applicable_declarations::ApplicableDeclarationBlock; -use style::attr::AttrValue; -use style::context::SharedStyleContext; -use style::data::ElementData; -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, SharedRwLockReadGuard, -}; -use style::str::is_whitespace; -use style::stylist::CascadeData; -use style::values::{AtomIdent, AtomString}; -use style::CaseSensitivityExt; - -#[derive(Clone, Copy)] -pub struct ServoLayoutNode<'dom> { - /// The wrapped node. - node: LayoutDom<'dom, Node>, -} - -// Those are supposed to be sound, but they aren't because the entire system -// between script and layout so far has been designed to work around their -// absence. Switching the entire thing to the inert crate infra will help. - -unsafe impl Send for ServoLayoutNode<'_> {} -unsafe impl Sync for ServoLayoutNode<'_> {} - -impl<'ln> Debug for ServoLayoutNode<'ln> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(el) = self.as_element() { - el.fmt(f) - } else { - if self.is_text_node() { - write!(f, " ({:#x})", self.opaque().0) - } else { - write!(f, " ({:#x})", self.opaque().0) - } - } - } -} - -impl<'a> PartialEq for ServoLayoutNode<'a> { - #[inline] - fn eq(&self, other: &ServoLayoutNode) -> bool { - self.node == other.node - } -} - -impl<'ln> ServoLayoutNode<'ln> { - fn from_layout_js(n: LayoutDom<'ln, Node>) -> Self { - ServoLayoutNode { node: n } - } - - pub unsafe fn new(address: &TrustedNodeAddress) -> Self { - ServoLayoutNode::from_layout_js(LayoutDom::from_trusted_node_address(*address)) - } - - fn script_type_id(&self) -> NodeTypeId { - self.node.type_id_for_layout() - } -} - -impl<'ln> NodeInfo for ServoLayoutNode<'ln> { - fn is_element(&self) -> bool { - self.node.is_element_for_layout() - } - - fn is_text_node(&self) -> bool { - self.script_type_id() == - NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) - } -} - -#[derive(Clone, Copy, PartialEq)] -pub struct ServoShadowRoot<'dom> { - /// The wrapped shadow root. - shadow_root: LayoutDom<'dom, ShadowRoot>, -} - -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 { - ServoLayoutNode::from_layout_js(self.shadow_root.upcast()) - } - - fn host(&self) -> ServoLayoutElement<'lr> { - ServoLayoutElement::from_layout_js(self.shadow_root.get_host_for_layout()) - } - - fn style_data<'a>(&self) -> Option<&'a CascadeData> - where - Self: 'a, - { - Some(&self.shadow_root.get_style_data_for_layout()) - } -} - -impl<'lr> ServoShadowRoot<'lr> { - fn from_layout_js(shadow_root: LayoutDom<'lr, ShadowRoot>) -> Self { - ServoShadowRoot { shadow_root } - } - - pub unsafe fn flush_stylesheets( - &self, - device: &Device, - quirks_mode: QuirksMode, - guard: &SharedRwLockReadGuard, - ) { - self.shadow_root - .flush_stylesheets::(device, quirks_mode, guard) - } -} - -impl<'ln> TNode for ServoLayoutNode<'ln> { - type ConcreteDocument = ServoLayoutDocument<'ln>; - type ConcreteElement = ServoLayoutElement<'ln>; - type ConcreteShadowRoot = ServoShadowRoot<'ln>; - - fn parent_node(&self) -> Option { - self.node - .composed_parent_node_ref() - .map(Self::from_layout_js) - } - - fn first_child(&self) -> Option { - self.node.first_child_ref().map(Self::from_layout_js) - } - - fn last_child(&self) -> Option { - self.node.last_child_ref().map(Self::from_layout_js) - } - - fn prev_sibling(&self) -> Option { - self.node.prev_sibling_ref().map(Self::from_layout_js) - } - - fn next_sibling(&self) -> Option { - self.node.next_sibling_ref().map(Self::from_layout_js) - } - - fn owner_doc(&self) -> Self::ConcreteDocument { - ServoLayoutDocument::from_layout_js(self.node.owner_doc_for_layout()) - } - - fn traversal_parent(&self) -> Option> { - let parent = self.parent_node()?; - if let Some(shadow) = parent.as_shadow_root() { - return Some(shadow.host()); - }; - parent.as_element() - } - - fn opaque(&self) -> OpaqueNode { - unsafe { self.get_jsmanaged().opaque() } - } - - fn debug_id(self) -> usize { - self.opaque().0 - } - - fn as_element(&self) -> Option> { - as_element(self.node) - } - - fn as_document(&self) -> Option> { - self.node - .downcast() - .map(ServoLayoutDocument::from_layout_js) - } - - fn as_shadow_root(&self) -> Option> { - self.node.downcast().map(ServoShadowRoot::from_layout_js) - } - - fn is_in_document(&self) -> bool { - unsafe { self.node.get_flag(NodeFlags::IS_IN_DOC) } - } -} - -impl<'ln> LayoutNode<'ln> for ServoLayoutNode<'ln> { - type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'ln>; - - fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode { - ServoThreadSafeLayoutNode::new(*self) - } - - fn type_id(&self) -> LayoutNodeType { - self.script_type_id().into() - } - - unsafe fn initialize_data(&self) { - if self.get_style_and_layout_data().is_none() { - let opaque = StyleAndOpaqueLayoutData::new( - StyleData::new(), - AtomicRefCell::new(LayoutDataForElement::default()), - ); - self.init_style_and_opaque_layout_data(opaque); - }; - } - - unsafe fn init_style_and_opaque_layout_data(&self, data: Box) { - self.get_jsmanaged().init_style_and_opaque_layout_data(data); - } - - unsafe fn take_style_and_opaque_layout_data(&self) -> Box { - self.get_jsmanaged().take_style_and_opaque_layout_data() - } - - fn is_connected(&self) -> bool { - unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) } - } -} - -impl<'dom> GetStyleAndOpaqueLayoutData<'dom> for ServoLayoutNode<'dom> { - fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { - unsafe { self.get_jsmanaged().get_style_and_opaque_layout_data() } - } -} - -impl<'dom> GetStyleAndOpaqueLayoutData<'dom> for ServoLayoutElement<'dom> { - fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { - self.as_node().get_style_and_opaque_layout_data() - } -} - -impl<'dom> GetStyleAndOpaqueLayoutData<'dom> for ServoThreadSafeLayoutNode<'dom> { - fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { - self.node.get_style_and_opaque_layout_data() - } -} - -impl<'dom> GetStyleAndOpaqueLayoutData<'dom> for ServoThreadSafeLayoutElement<'dom> { - fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { - self.element.as_node().get_style_and_opaque_layout_data() - } -} - -impl<'ln> ServoLayoutNode<'ln> { - /// Returns the interior of this node as a `LayoutDom`. This is highly unsafe for layout to - /// call and as such is marked `unsafe`. - pub unsafe fn get_jsmanaged(&self) -> LayoutDom<'ln, Node> { - self.node - } -} - -// A wrapper around documents that ensures ayout can only ever access safe properties. -#[derive(Clone, Copy)] -pub struct ServoLayoutDocument<'dom> { - document: LayoutDom<'dom, Document>, -} - -impl<'ld> TDocument for ServoLayoutDocument<'ld> { - type ConcreteNode = ServoLayoutNode<'ld>; - - fn as_node(&self) -> Self::ConcreteNode { - ServoLayoutNode::from_layout_js(self.document.upcast()) - } - - fn quirks_mode(&self) -> QuirksMode { - self.document.quirks_mode() - } - - fn is_html_document(&self) -> bool { - self.document.is_html_document_for_layout() - } - - fn shared_lock(&self) -> &StyleSharedRwLock { - self.document.style_shared_lock() - } -} - -impl<'ld> ServoLayoutDocument<'ld> { - pub fn root_element(&self) -> Option> { - self.as_node() - .dom_children() - .flat_map(|n| n.as_element()) - .next() - } - - pub fn needs_paint_from_layout(&self) { - unsafe { self.document.needs_paint_from_layout() } - } - - pub fn will_paint(&self) { - unsafe { self.document.will_paint() } - } - - pub fn style_shared_lock(&self) -> &StyleSharedRwLock { - self.document.style_shared_lock() - } - - pub fn shadow_roots(&self) -> Vec { - unsafe { - self.document - .shadow_roots() - .iter() - .map(|sr| { - debug_assert!(sr.upcast::().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<'ld, Document>) -> Self { - ServoLayoutDocument { document: doc } - } -} - -/// A wrapper around elements that ensures layout can only ever access safe properties. -#[derive(Clone, Copy)] -pub struct ServoLayoutElement<'dom> { - element: LayoutDom<'dom, Element>, -} - -impl<'le> fmt::Debug for ServoLayoutElement<'le> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "<{}", self.element.local_name())?; - if let Some(id) = self.id() { - write!(f, " id={}", id)?; - } - write!(f, "> ({:#x})", self.as_node().opaque().0) - } -} - -impl<'dom> ServoLayoutElement<'dom> { - /// Returns true if this element is the body child of an html element root element. - fn is_body_element_of_html_element_root(&self) -> bool { - if self.element.local_name() != &local_name!("body") { - return false; - } - - self.parent_element() - .map(|element| { - element.is_root() && element.element.local_name() == &local_name!("html") - }) - .unwrap_or(false) - } - - /// Returns the parent element of this element, if it has one. - fn parent_element(&self) -> Option { - self.element - .upcast() - .composed_parent_node_ref() - .and_then(as_element) - } - - // Returns true is this is the root element. - fn is_root(&self) -> bool { - match self.as_node().parent_node() { - None => false, - Some(node) => match node.script_type_id() { - NodeTypeId::Document(_) => true, - _ => false, - }, - } - } -} - -impl<'le> TElement for ServoLayoutElement<'le> { - type ConcreteNode = ServoLayoutNode<'le>; - type TraversalChildrenIterator = DomChildren; - - type FontMetricsProvider = ServoMetricsProvider; - - fn as_node(&self) -> ServoLayoutNode<'le> { - ServoLayoutNode::from_layout_js(self.element.upcast()) - } - - fn traversal_children(&self) -> LayoutIterator { - 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 { - self.element.is_html_element() - } - - fn is_mathml_element(&self) -> bool { - *self.element.namespace() == ns!(mathml) - } - - fn is_svg_element(&self) -> bool { - *self.element.namespace() == ns!(svg) - } - - fn has_part_attr(&self) -> bool { - false - } - - fn exports_any_part(&self) -> bool { - false - } - - fn style_attribute(&self) -> Option>> { - unsafe { - (*self.element.style_attribute()) - .as_ref() - .map(|x| x.borrow_arc()) - } - } - - fn may_have_animations(&self) -> bool { - true - } - - fn animation_rule( - &self, - context: &SharedStyleContext, - ) -> Option>> { - let node = self.as_node(); - let document = node.owner_doc(); - context.animations.get_animation_declarations( - &AnimationSetKey::new_for_non_pseudo(node.opaque()), - context.current_time_for_animations, - document.style_shared_lock(), - ) - } - - fn transition_rule( - &self, - context: &SharedStyleContext, - ) -> Option>> { - let node = self.as_node(); - let document = node.owner_doc(); - context.animations.get_transition_declarations( - &AnimationSetKey::new_for_non_pseudo(node.opaque()), - context.current_time_for_animations, - document.style_shared_lock(), - ) - } - - fn state(&self) -> ElementState { - self.element.get_state_for_layout() - } - - #[inline] - fn has_attr(&self, namespace: &style::Namespace, attr: &style::LocalName) -> bool { - self.get_attr(namespace, attr).is_some() - } - - #[inline] - fn id(&self) -> Option<&Atom> { - unsafe { (*self.element.id_attribute()).as_ref() } - } - - #[inline(always)] - fn each_class(&self, mut callback: F) - where - F: FnMut(&AtomIdent), - { - if let Some(ref classes) = self.element.get_classes_for_layout() { - for class in *classes { - callback(AtomIdent::cast(class)) - } - } - } - - fn has_dirty_descendants(&self) -> bool { - unsafe { - self.as_node() - .node - .get_flag(NodeFlags::HAS_DIRTY_DESCENDANTS) - } - } - - fn has_snapshot(&self) -> bool { - unsafe { self.as_node().node.get_flag(NodeFlags::HAS_SNAPSHOT) } - } - - fn handled_snapshot(&self) -> bool { - unsafe { self.as_node().node.get_flag(NodeFlags::HANDLED_SNAPSHOT) } - } - - unsafe fn set_handled_snapshot(&self) { - self.as_node() - .node - .set_flag(NodeFlags::HANDLED_SNAPSHOT, true); - } - - unsafe fn set_dirty_descendants(&self) { - debug_assert!(self.as_node().is_connected()); - self.as_node() - .node - .set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, true) - } - - unsafe fn unset_dirty_descendants(&self) { - self.as_node() - .node - .set_flag(NodeFlags::HAS_DIRTY_DESCENDANTS, false) - } - - fn store_children_to_process(&self, n: isize) { - let data = self.get_style_data().unwrap(); - data.parallel - .children_to_process - .store(n, Ordering::Relaxed); - } - - fn did_process_child(&self) -> isize { - let data = self.get_style_data().unwrap(); - let old_value = data - .parallel - .children_to_process - .fetch_sub(1, Ordering::Relaxed); - debug_assert!(old_value >= 1); - old_value - 1 - } - - unsafe fn clear_data(&self) { - if self.get_style_and_layout_data().is_some() { - drop(self.as_node().take_style_and_opaque_layout_data()); - } - } - - unsafe fn ensure_data(&self) -> AtomicRefMut { - self.as_node().initialize_data(); - self.mutate_data().unwrap() - } - - /// Whether there is an ElementData container. - fn has_data(&self) -> bool { - self.get_style_data().is_some() - } - - /// Immutably borrows the ElementData. - fn borrow_data(&self) -> Option> { - self.get_style_data().map(|data| data.element_data.borrow()) - } - - /// Mutably borrows the ElementData. - fn mutate_data(&self) -> Option> { - self.get_style_data() - .map(|data| data.element_data.borrow_mut()) - } - - fn skip_item_display_fixup(&self) -> bool { - false - } - - unsafe fn set_selector_flags(&self, flags: ElementSelectorFlags) { - self.element.insert_selector_flags(flags); - } - - fn has_selector_flags(&self, flags: ElementSelectorFlags) -> bool { - self.element.has_selector_flags(flags) - } - - fn has_animations(&self, context: &SharedStyleContext) -> bool { - // This is not used for pseudo elements currently so we can pass None. - return self.has_css_animations(context, /* pseudo_element = */ None) || - self.has_css_transitions(context, /* pseudo_element = */ None); - } - - fn has_css_animations( - &self, - context: &SharedStyleContext, - pseudo_element: Option, - ) -> bool { - let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element); - context.animations.has_active_animations(&key) - } - - fn has_css_transitions( - &self, - context: &SharedStyleContext, - pseudo_element: Option, - ) -> bool { - let key = AnimationSetKey::new(self.as_node().opaque(), pseudo_element); - context.animations.has_active_transitions(&key) - } - - #[inline] - fn lang_attr(&self) -> Option { - self.get_attr(&ns!(xml), &local_name!("lang")) - .or_else(|| self.get_attr(&ns!(), &local_name!("lang"))) - .map(|v| SelectorAttrValue::from(v as &str)) - } - - fn match_element_lang( - &self, - override_lang: Option>, - value: &Lang, - ) -> bool { - // Servo supports :lang() from CSS Selectors 4, which can take a comma- - // separated list of language tags in the pseudo-class, and which - // performs RFC 4647 extended filtering matching on them. - // - // FIXME(heycam): This is wrong, since extended_filtering accepts - // a string containing commas (separating each language tag in - // a list) but the pseudo-class instead should be parsing and - // storing separate or s for each language tag. - // - // FIXME(heycam): Look at `element`'s document's Content-Language - // HTTP header for language tags to match `value` against. To - // do this, we should make `get_lang_for_layout` return an Option, - // so we can decide when to fall back to the Content-Language check. - let element_lang = match override_lang { - Some(Some(lang)) => lang, - Some(None) => AtomString::default(), - None => AtomString::from(&*self.element.get_lang_for_layout()), - }; - extended_filtering(&element_lang, &*value) - } - - fn is_html_document_body_element(&self) -> bool { - self.is_body_element_of_html_element_root() - } - - fn synthesize_presentational_hints_for_legacy_attributes( - &self, - _visited_handling: VisitedHandlingMode, - hints: &mut V, - ) where - V: Push, - { - self.element - .synthesize_presentational_hints_for_legacy_attributes(hints); - } - - /// The shadow root this element is a host of. - fn shadow_root(&self) -> Option> { - self.element - .get_shadow_root_for_layout() - .map(ServoShadowRoot::from_layout_js) - } - - /// The shadow root which roots the subtree this element is contained in. - fn containing_shadow(&self) -> Option> { - self.element - .upcast() - .containing_shadow_root_for_layout() - .map(ServoShadowRoot::from_layout_js) - } - - fn local_name(&self) -> &LocalName { - self.element.local_name() - } - - fn namespace(&self) -> &Namespace { - self.element.namespace() - } -} - -impl<'le> PartialEq for ServoLayoutElement<'le> { - fn eq(&self, other: &Self) -> bool { - self.as_node() == other.as_node() - } -} - -impl<'le> Hash for ServoLayoutElement<'le> { - fn hash(&self, state: &mut H) { - self.element.hash(state); - } -} - -impl<'le> Eq for ServoLayoutElement<'le> {} - -impl<'le> ServoLayoutElement<'le> { - fn from_layout_js(el: LayoutDom<'le, Element>) -> Self { - ServoLayoutElement { element: el } - } - - #[inline] - fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> { - self.element.get_attr_for_layout(namespace, name) - } - - #[inline] - fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str> { - self.element.get_attr_val_for_layout(namespace, name) - } - - fn get_style_data(&self) -> Option<&StyleData> { - self.get_style_and_opaque_layout_data() - .map(|data| &data.style_data) - } - - pub unsafe fn unset_snapshot_flags(&self) { - self.as_node() - .node - .set_flag(NodeFlags::HAS_SNAPSHOT | NodeFlags::HANDLED_SNAPSHOT, false); - } - - pub unsafe fn set_has_snapshot(&self) { - self.as_node().node.set_flag(NodeFlags::HAS_SNAPSHOT, true); - } -} - -fn as_element<'dom>(node: LayoutDom<'dom, Node>) -> Option> { - node.downcast().map(ServoLayoutElement::from_layout_js) -} - -impl<'le> ::selectors::Element for ServoLayoutElement<'le> { - type Impl = SelectorImpl; - - fn opaque(&self) -> ::selectors::OpaqueElement { - ::selectors::OpaqueElement::new(unsafe { &*(self.as_node().opaque().0 as *const ()) }) - } - - fn parent_element(&self) -> Option> { - self.element - .upcast() - .composed_parent_node_ref() - .and_then(as_element) - } - - fn parent_node_is_shadow_root(&self) -> bool { - 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.containing_shadow().map(|s| s.host()) - } - - fn prev_sibling_element(&self) -> Option> { - let mut node = self.as_node(); - while let Some(sibling) = node.prev_sibling() { - if let Some(element) = sibling.as_element() { - return Some(element); - } - node = sibling; - } - None - } - - fn next_sibling_element(&self) -> Option> { - let mut node = self.as_node(); - while let Some(sibling) = node.next_sibling() { - if let Some(element) = sibling.as_element() { - return Some(element); - } - node = sibling; - } - None - } - - fn attr_matches( - &self, - ns: &NamespaceConstraint<&style::Namespace>, - local_name: &style::LocalName, - operation: &AttrSelectorOperation<&AtomString>, - ) -> bool { - match *ns { - NamespaceConstraint::Specific(ref ns) => self - .get_attr_enum(ns, local_name) - .map_or(false, |value| value.eval_selector(operation)), - NamespaceConstraint::Any => self - .element - .get_attr_vals_for_layout(local_name) - .iter() - .any(|value| value.eval_selector(operation)), - } - } - - fn is_root(&self) -> bool { - ServoLayoutElement::is_root(self) - } - - fn is_empty(&self) -> bool { - self.as_node() - .dom_children() - .all(|node| match node.script_type_id() { - NodeTypeId::Element(..) => false, - NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) => { - node.node.downcast().unwrap().data_for_layout().is_empty() - }, - _ => true, - }) - } - - #[inline] - fn has_local_name(&self, name: &LocalName) -> bool { - self.element.local_name() == name - } - - #[inline] - fn has_namespace(&self, ns: &Namespace) -> bool { - self.element.namespace() == ns - } - - #[inline] - fn is_same_type(&self, other: &Self) -> bool { - self.element.local_name() == other.element.local_name() && - self.element.namespace() == other.element.namespace() - } - - fn is_pseudo_element(&self) -> bool { - false - } - - fn match_pseudo_element( - &self, - _pseudo: &PseudoElement, - _context: &mut MatchingContext, - ) -> bool { - false - } - - fn match_non_ts_pseudo_class( - &self, - pseudo_class: &NonTSPseudoClass, - _: &mut MatchingContext, - _: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { - match *pseudo_class { - // https://github.com/servo/servo/issues/8718 - NonTSPseudoClass::Link | NonTSPseudoClass::AnyLink => self.is_link(), - NonTSPseudoClass::Visited => false, - - NonTSPseudoClass::Lang(ref lang) => self.match_element_lang(None, &*lang), - - NonTSPseudoClass::ServoNonZeroBorder => { - match self - .element - .get_attr_for_layout(&ns!(), &local_name!("border")) - { - None | Some(&AttrValue::UInt(_, 0)) => false, - _ => true, - } - }, - NonTSPseudoClass::ReadOnly => !self - .element - .get_state_for_layout() - .contains(pseudo_class.state_flag()), - - NonTSPseudoClass::Active | - NonTSPseudoClass::Focus | - NonTSPseudoClass::Fullscreen | - NonTSPseudoClass::Hover | - NonTSPseudoClass::Defined | - NonTSPseudoClass::Enabled | - NonTSPseudoClass::Disabled | - NonTSPseudoClass::Checked | - NonTSPseudoClass::Indeterminate | - NonTSPseudoClass::ReadWrite | - NonTSPseudoClass::PlaceholderShown | - NonTSPseudoClass::Target => self - .element - .get_state_for_layout() - .contains(pseudo_class.state_flag()), - } - } - - #[inline] - fn is_link(&self) -> bool { - match self.as_node().script_type_id() { - // https://html.spec.whatwg.org/multipage/#selector-link - NodeTypeId::Element(ElementTypeId::HTMLElement( - HTMLElementTypeId::HTMLAnchorElement, - )) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLAreaElement)) | - NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLLinkElement)) => { - self.element - .get_attr_val_for_layout(&ns!(), &local_name!("href")) - .is_some() - }, - _ => false, - } - } - - #[inline] - fn has_id(&self, id: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - unsafe { - (*self.element.id_attribute()) - .as_ref() - .map_or(false, |atom| case_sensitivity.eq_atom(atom, id)) - } - } - - #[inline] - fn is_part(&self, _name: &AtomIdent) -> bool { - false - } - - fn imported_part(&self, _: &AtomIdent) -> Option { - None - } - - #[inline] - fn has_class(&self, name: &AtomIdent, case_sensitivity: CaseSensitivity) -> bool { - self.element.has_class_for_layout(name, case_sensitivity) - } - - fn is_html_slot_element(&self) -> bool { - self.element.is_html_element() && self.local_name() == &local_name!("slot") - } - - fn is_html_element_in_html_document(&self) -> bool { - self.element.is_html_element() && self.as_node().owner_doc().is_html_document() - } -} - -#[derive(Clone, Copy, Debug)] -pub struct ServoThreadSafeLayoutNode<'ln> { - /// The wrapped node. - node: ServoLayoutNode<'ln>, - - /// The pseudo-element type, with (optionally) - /// a specified display value to override the stylesheet. - pseudo: PseudoElementType, -} - -impl<'a> PartialEq for ServoThreadSafeLayoutNode<'a> { - #[inline] - fn eq(&self, other: &ServoThreadSafeLayoutNode<'a>) -> bool { - self.node == other.node - } -} - -impl<'ln> DangerousThreadSafeLayoutNode<'ln> for ServoThreadSafeLayoutNode<'ln> { - unsafe fn dangerous_first_child(&self) -> Option { - self.get_jsmanaged() - .first_child_ref() - .map(ServoLayoutNode::from_layout_js) - .map(Self::new) - } - unsafe fn dangerous_next_sibling(&self) -> Option { - self.get_jsmanaged() - .next_sibling_ref() - .map(ServoLayoutNode::from_layout_js) - .map(Self::new) - } -} - -impl<'ln> ServoThreadSafeLayoutNode<'ln> { - /// Creates a new `ServoThreadSafeLayoutNode` from the given `ServoLayoutNode`. - pub fn new(node: ServoLayoutNode<'ln>) -> Self { - ServoThreadSafeLayoutNode { - node: node.clone(), - pseudo: PseudoElementType::Normal, - } - } - - /// Returns the interior of this node as a `LayoutDom`. This is highly unsafe for layout to - /// call and as such is marked `unsafe`. - unsafe fn get_jsmanaged(&self) -> LayoutDom<'ln, Node> { - self.node.get_jsmanaged() - } -} - -impl<'ln> NodeInfo for ServoThreadSafeLayoutNode<'ln> { - fn is_element(&self) -> bool { - self.node.is_element() - } - - fn is_text_node(&self) -> bool { - self.node.is_text_node() - } -} - -impl<'ln> ThreadSafeLayoutNode<'ln> for ServoThreadSafeLayoutNode<'ln> { - type ConcreteNode = ServoLayoutNode<'ln>; - type ConcreteThreadSafeLayoutElement = ServoThreadSafeLayoutElement<'ln>; - type ConcreteElement = ServoLayoutElement<'ln>; - type ChildrenIterator = ThreadSafeLayoutNodeChildrenIterator; - - fn opaque(&self) -> OpaqueNode { - unsafe { self.get_jsmanaged().opaque() } - } - - fn type_id(&self) -> Option { - if self.pseudo == PseudoElementType::Normal { - Some(self.node.type_id()) - } else { - None - } - } - - fn parent_style(&self) -> Arc { - let parent = self.node.parent_node().unwrap().as_element().unwrap(); - let parent_data = parent.borrow_data().unwrap(); - parent_data.styles.primary().clone() - } - - fn debug_id(self) -> usize { - self.node.debug_id() - } - - fn children(&self) -> LayoutIterator { - 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)) - } - - fn as_element(&self) -> Option> { - self.node - .as_element() - .map(|el| ServoThreadSafeLayoutElement { - element: el, - pseudo: self.pseudo, - }) - } - - fn get_style_and_opaque_layout_data(self) -> Option<&'ln StyleAndOpaqueLayoutData> { - self.node.get_style_and_opaque_layout_data() - } - - fn is_ignorable_whitespace(&self, context: &SharedStyleContext) -> bool { - unsafe { - let text: LayoutDom = match self.get_jsmanaged().downcast() { - Some(text) => text, - None => return false, - }; - - if !is_whitespace(text.upcast().data_for_layout()) { - return false; - } - - // NB: See the rules for `white-space` here: - // - // http://www.w3.org/TR/CSS21/text.html#propdef-white-space - // - // If you implement other values for this property, you will almost certainly - // want to update this check. - !self - .style(context) - .get_inherited_text() - .white_space - .preserve_newlines() - } - } - - unsafe fn unsafe_get(self) -> Self::ConcreteNode { - self.node - } - - fn node_text_content(self) -> Cow<'ln, str> { - unsafe { self.get_jsmanaged().text_content() } - } - - fn selection(&self) -> Option> { - let this = unsafe { self.get_jsmanaged() }; - - this.selection().map(|range| { - Range::new( - ByteIndex(range.start as isize), - ByteIndex(range.len() as isize), - ) - }) - } - - fn image_url(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.image_url() - } - - fn image_density(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.image_density() - } - - fn image_data(&self) -> Option<(Option>, Option)> { - let this = unsafe { self.get_jsmanaged() }; - this.image_data() - } - - fn canvas_data(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.canvas_data() - } - - fn media_data(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.media_data() - } - - fn svg_data(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.svg_data() - } - - // Can return None if the iframe has no nested browsing context - fn iframe_browsing_context_id(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.iframe_browsing_context_id() - } - - // Can return None if the iframe has no nested browsing context - fn iframe_pipeline_id(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.iframe_pipeline_id() - } - - fn get_colspan(&self) -> u32 { - unsafe { - self.get_jsmanaged() - .downcast::() - .unwrap() - .get_colspan() - } - } - - fn get_rowspan(&self) -> u32 { - unsafe { - self.get_jsmanaged() - .downcast::() - .unwrap() - .get_rowspan() - } - } -} - -pub struct ThreadSafeLayoutNodeChildrenIterator { - current_node: Option, - parent_node: ConcreteNode, -} - -impl<'dom, ConcreteNode> ThreadSafeLayoutNodeChildrenIterator -where - ConcreteNode: DangerousThreadSafeLayoutNode<'dom>, -{ - pub fn new(parent: ConcreteNode) -> Self { - let first_child: Option = match parent.get_pseudo_element_type() { - PseudoElementType::Normal => parent - .get_before_pseudo() - .or_else(|| parent.get_details_summary_pseudo()) - .or_else(|| unsafe { parent.dangerous_first_child() }), - PseudoElementType::DetailsContent | PseudoElementType::DetailsSummary => unsafe { - parent.dangerous_first_child() - }, - _ => None, - }; - ThreadSafeLayoutNodeChildrenIterator { - current_node: first_child, - parent_node: parent, - } - } -} - -impl<'dom, ConcreteNode> Iterator for ThreadSafeLayoutNodeChildrenIterator -where - ConcreteNode: DangerousThreadSafeLayoutNode<'dom>, -{ - type Item = ConcreteNode; - fn next(&mut self) -> Option { - use selectors::Element; - match self.parent_node.get_pseudo_element_type() { - PseudoElementType::Before | PseudoElementType::After => None, - - PseudoElementType::DetailsSummary => { - let mut current_node = self.current_node.clone(); - loop { - let next_node = if let Some(ref node) = current_node { - if let Some(element) = node.as_element() { - if element.has_local_name(&local_name!("summary")) && - element.has_namespace(&ns!(html)) - { - self.current_node = None; - return Some(node.clone()); - } - } - unsafe { node.dangerous_next_sibling() } - } else { - self.current_node = None; - return None; - }; - current_node = next_node; - } - }, - - PseudoElementType::DetailsContent => { - let node = self.current_node.clone(); - let node = node.and_then(|node| { - if node.is_element() && - node.as_element() - .unwrap() - .has_local_name(&local_name!("summary")) && - node.as_element().unwrap().has_namespace(&ns!(html)) - { - unsafe { node.dangerous_next_sibling() } - } else { - Some(node) - } - }); - self.current_node = node.and_then(|node| unsafe { node.dangerous_next_sibling() }); - node - }, - - PseudoElementType::Normal => { - let node = self.current_node.clone(); - if let Some(ref node) = node { - self.current_node = match node.get_pseudo_element_type() { - PseudoElementType::Before => self - .parent_node - .get_details_summary_pseudo() - .or_else(|| unsafe { self.parent_node.dangerous_first_child() }) - .or_else(|| self.parent_node.get_after_pseudo()), - PseudoElementType::Normal => unsafe { node.dangerous_next_sibling() } - .or_else(|| self.parent_node.get_after_pseudo()), - PseudoElementType::DetailsSummary => { - self.parent_node.get_details_content_pseudo() - }, - PseudoElementType::DetailsContent => self.parent_node.get_after_pseudo(), - PseudoElementType::After => None, - }; - } - node - }, - } - } -} - -/// A wrapper around elements that ensures layout can only -/// ever access safe properties and cannot race on elements. -#[derive(Clone, Copy, Debug)] -pub struct ServoThreadSafeLayoutElement<'le> { - element: ServoLayoutElement<'le>, - - /// The pseudo-element type, with (optionally) - /// a specified display value to override the stylesheet. - pseudo: PseudoElementType, -} - -impl<'le> ThreadSafeLayoutElement<'le> for ServoThreadSafeLayoutElement<'le> { - type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'le>; - type ConcreteElement = ServoLayoutElement<'le>; - - fn as_node(&self) -> ServoThreadSafeLayoutNode<'le> { - ServoThreadSafeLayoutNode { - node: self.element.as_node(), - pseudo: self.pseudo.clone(), - } - } - - fn get_pseudo_element_type(&self) -> PseudoElementType { - self.pseudo - } - - fn with_pseudo(&self, pseudo: PseudoElementType) -> Self { - ServoThreadSafeLayoutElement { - element: self.element.clone(), - pseudo, - } - } - - fn type_id(&self) -> Option { - self.as_node().type_id() - } - - unsafe fn unsafe_get(self) -> ServoLayoutElement<'le> { - self.element - } - - fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> { - self.element.get_attr_enum(namespace, name) - } - - fn get_attr<'a>(&'a self, namespace: &Namespace, name: &LocalName) -> Option<&'a str> { - self.element.get_attr(namespace, name) - } - - fn style_data(&self) -> AtomicRef { - match self.element.borrow_data() { - Some(data) => data, - None => panic!("could not find styles for <{}>", self.element.local_name()), - } - } - - fn is_shadow_host(&self) -> bool { - self.element.shadow_root().is_some() - } - - fn is_body_element_of_html_element_root(&self) -> bool { - self.element.is_body_element_of_html_element_root() - } -} - -/// This implementation of `::selectors::Element` is used for implementing lazy -/// pseudo-elements. -/// -/// Lazy pseudo-elements in Servo only allows selectors using safe properties, -/// i.e., local_name, attributes, so they can only be used for **private** -/// pseudo-elements (like `::-servo-details-content`). -/// -/// Probably a few more of this functions can be implemented (like `has_class`, etc.), -/// but they have no use right now. -/// -/// Note that the element implementation is needed only for selector matching, -/// not for inheritance (styles are inherited appropiately). -impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { - type Impl = SelectorImpl; - - fn opaque(&self) -> ::selectors::OpaqueElement { - ::selectors::OpaqueElement::new(unsafe { &*(self.as_node().opaque().0 as *const ()) }) - } - - fn is_pseudo_element(&self) -> bool { - false - } - - fn parent_element(&self) -> Option { - warn!("ServoThreadSafeLayoutElement::parent_element called"); - None - } - - fn parent_node_is_shadow_root(&self) -> bool { - false - } - - fn containing_shadow_host(&self) -> Option { - None - } - - // Skips non-element nodes - fn prev_sibling_element(&self) -> Option { - warn!("ServoThreadSafeLayoutElement::prev_sibling_element called"); - None - } - - // Skips non-element nodes - fn next_sibling_element(&self) -> Option { - warn!("ServoThreadSafeLayoutElement::next_sibling_element called"); - None - } - - fn is_html_slot_element(&self) -> bool { - self.element.is_html_slot_element() - } - - fn is_html_element_in_html_document(&self) -> bool { - debug!("ServoThreadSafeLayoutElement::is_html_element_in_html_document called"); - true - } - - #[inline] - fn has_local_name(&self, name: &LocalName) -> bool { - self.element.local_name() == name - } - - #[inline] - fn has_namespace(&self, ns: &Namespace) -> bool { - self.element.namespace() == ns - } - - #[inline] - fn is_same_type(&self, other: &Self) -> bool { - self.element.local_name() == other.element.local_name() && - self.element.namespace() == other.element.namespace() - } - - fn match_pseudo_element( - &self, - _pseudo: &PseudoElement, - _context: &mut MatchingContext, - ) -> bool { - false - } - - fn attr_matches( - &self, - ns: &NamespaceConstraint<&style::Namespace>, - local_name: &style::LocalName, - operation: &AttrSelectorOperation<&AtomString>, - ) -> bool { - match *ns { - NamespaceConstraint::Specific(ref ns) => self - .get_attr_enum(ns, local_name) - .map_or(false, |value| value.eval_selector(operation)), - NamespaceConstraint::Any => { - let values = self.element.element.get_attr_vals_for_layout(local_name); - values.iter().any(|v| v.eval_selector(operation)) - }, - } - } - - fn match_non_ts_pseudo_class( - &self, - _: &NonTSPseudoClass, - _: &mut MatchingContext, - _: &mut F, - ) -> bool - where - F: FnMut(&Self, ElementSelectorFlags), - { - // NB: This could maybe be implemented - warn!("ServoThreadSafeLayoutElement::match_non_ts_pseudo_class called"); - false - } - - fn is_link(&self) -> bool { - warn!("ServoThreadSafeLayoutElement::is_link called"); - false - } - - fn has_id(&self, _id: &AtomIdent, _case_sensitivity: CaseSensitivity) -> bool { - debug!("ServoThreadSafeLayoutElement::has_id called"); - false - } - - #[inline] - fn is_part(&self, _name: &AtomIdent) -> bool { - debug!("ServoThreadSafeLayoutElement::is_part called"); - false - } - - fn imported_part(&self, _: &AtomIdent) -> Option { - debug!("ServoThreadSafeLayoutElement::imported_part called"); - None - } - - fn has_class(&self, _name: &AtomIdent, _case_sensitivity: CaseSensitivity) -> bool { - debug!("ServoThreadSafeLayoutElement::has_class called"); - false - } - - fn is_empty(&self) -> bool { - warn!("ServoThreadSafeLayoutElement::is_empty called"); - false - } - - fn is_root(&self) -> bool { - warn!("ServoThreadSafeLayoutElement::is_root called"); - false - } -} diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index df7f2e90827..ddfbc69222f 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -11,17 +11,12 @@ #[macro_use] extern crate crossbeam_channel; #[macro_use] -extern crate html5ever; -#[macro_use] extern crate lazy_static; #[macro_use] extern crate log; #[macro_use] extern crate profile_traits; -mod dom_wrapper; - -use crate::dom_wrapper::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use app_units::Au; use crossbeam_channel::{Receiver, Sender}; use embedder_traits::resources::{self, Resource}; @@ -35,6 +30,7 @@ use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; use layout::context::LayoutContext; use layout::display_list::{DisplayListBuilder, WebRenderImageInfo}; +use layout::element_data::LayoutDataForElement; use layout::layout_debug; use layout::query::{ process_content_box_request, process_content_boxes_request, process_resolved_font_style_query, @@ -60,6 +56,7 @@ use parking_lot::RwLock; use profile_traits::mem::{self as profile_mem, Report, ReportKind, ReportsChan}; use profile_traits::time::{self as profile_time, profile, TimerMetadata}; use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType}; +use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use script_layout_interface::message::{LayoutThreadInit, Msg, NodesFromPointQueryType}; use script_layout_interface::message::{QueryMsg, ReflowComplete, ReflowGoal, ScriptReflow}; use script_layout_interface::rpc::TextIndexResponse; @@ -804,7 +801,7 @@ impl LayoutThread { data: &mut ScriptReflowResult, possibly_locked_rw_data: &mut RwData<'a, 'b>, ) { - let document = unsafe { ServoLayoutNode::new(&data.document) }; + let document = unsafe { ServoLayoutNode::::new(&data.document) }; let document = document.as_document().unwrap(); let mut rw_data = possibly_locked_rw_data.lock(); @@ -959,11 +956,19 @@ impl LayoutThread { let elements_with_snapshot: Vec<_> = restyles .iter() .filter(|r| r.1.snapshot.is_some()) - .map(|r| unsafe { ServoLayoutNode::new(&r.0).as_element().unwrap() }) + .map(|r| unsafe { + ServoLayoutNode::::new(&r.0) + .as_element() + .unwrap() + }) .collect(); for (el, restyle) in restyles { - let el = unsafe { ServoLayoutNode::new(&el).as_element().unwrap() }; + let el = unsafe { + ServoLayoutNode::::new(&el) + .as_element() + .unwrap() + }; // If we haven't styled this node yet, we don't need to track a // restyle. @@ -999,14 +1004,16 @@ impl LayoutThread { ); let dirty_root = unsafe { - ServoLayoutNode::new(&data.dirty_root.unwrap()) + ServoLayoutNode::::new(&data.dirty_root.unwrap()) .as_element() .unwrap() }; let traversal = RecalcStyle::new(layout_context); let token = { - let shared = DomTraversal::::shared_context(&traversal); + let shared = DomTraversal::>::shared_context( + &traversal, + ); RecalcStyle::pre_traverse(dirty_root, shared) }; @@ -1014,7 +1021,8 @@ impl LayoutThread { let rayon_pool = rayon_pool.as_ref(); if token.should_traverse() { - let dirty_root = driver::traverse_dom(&traversal, token, rayon_pool).as_node(); + let dirty_root: ServoLayoutNode = + driver::traverse_dom(&traversal, token, rayon_pool).as_node(); let root_node = root_element.as_node(); let mut box_tree = self.box_tree.borrow_mut(); @@ -1126,12 +1134,12 @@ impl LayoutThread { process_node_scroll_area_request(node, self.fragment_tree.borrow().clone()); }, &QueryMsg::NodeScrollIdQuery(node) => { - let node = unsafe { ServoLayoutNode::new(&node) }; + let node = unsafe { ServoLayoutNode::::new(&node) }; rw_data.scroll_id_response = Some(process_node_scroll_id_request(self.id, node)); }, &QueryMsg::ResolvedStyleQuery(node, ref pseudo, ref property) => { - let node = unsafe { ServoLayoutNode::new(&node) }; + let node = unsafe { ServoLayoutNode::::new(&node) }; let fragment_tree = self.fragment_tree.borrow().clone(); rw_data.resolved_style_response = process_resolved_style_request( context, @@ -1142,7 +1150,7 @@ impl LayoutThread { ); }, &QueryMsg::ResolvedFontStyleQuery(node, ref property, ref value) => { - let node = unsafe { ServoLayoutNode::new(&node) }; + let node = unsafe { ServoLayoutNode::::new(&node) }; rw_data.resolved_font_style_response = process_resolved_font_style_query(node, property, value); }, @@ -1172,7 +1180,7 @@ impl LayoutThread { results.iter().map(|result| result.node).collect() }, &QueryMsg::ElementInnerTextQuery(node) => { - let node = unsafe { ServoLayoutNode::new(&node) }; + let node = unsafe { ServoLayoutNode::::new(&node) }; rw_data.element_inner_text_response = process_element_inner_text_query(node); }, &QueryMsg::InnerWindowDimensionsQuery(_browsing_context_id) => { @@ -1232,7 +1240,7 @@ impl LayoutThread { &self, fragment_tree: Arc, reflow_goal: &ReflowGoal, - document: Option<&ServoLayoutDocument>, + document: Option<&ServoLayoutDocument>, context: &mut LayoutContext, ) { Self::cancel_animations_for_nodes_not_in_fragment_tree( diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml index 94a8a3f33b4..bbe1f4f78bc 100644 --- a/components/script/Cargo.toml +++ b/components/script/Cargo.toml @@ -33,6 +33,7 @@ serde_json = "1.0" accountable-refcell = { version = "0.2.0", optional = true } app_units = "0.7" arrayvec = "0.7" +atomic_refcell = "0.1" backtrace = "0.3" base64 = "0.10.1" bitflags = "1.0" @@ -54,6 +55,7 @@ enum-iterator = "0.3" euclid = "0.22" fnv = "1.0" fxhash = "0.2" +gfx_traits = { path = "../gfx_traits" } headers = "0.3" html5ever = "0.26" http = "0.2" @@ -83,6 +85,7 @@ percent-encoding = "2.0" phf = "0.8" pixels = { path = "../pixels" } profile_traits = { path = "../profile_traits" } +range = { path = "../range" } ref_filter_map = "1.0.1" regex = "1.1" script_layout_interface = { path = "../script_layout_interface" } diff --git a/components/script/layout_dom/document.rs b/components/script/layout_dom/document.rs new file mode 100644 index 00000000000..e723f731cab --- /dev/null +++ b/components/script/layout_dom/document.rs @@ -0,0 +1,114 @@ +/* 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::LayoutDom; +use crate::dom::document::{Document, LayoutDocumentHelpers}; +use crate::dom::node::{LayoutNodeHelpers, Node, NodeFlags}; +use crate::layout_dom::ServoLayoutElement; +use crate::layout_dom::ServoLayoutNode; +use crate::layout_dom::ServoShadowRoot; +use script_layout_interface::wrapper_traits::LayoutDataTrait; +use selectors::matching::QuirksMode; +use std::marker::PhantomData; +use style::dom::{TDocument, TNode}; +use style::media_queries::Device; +use style::shared_lock::{ + SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard as StyleSharedRwLockReadGuard, +}; + +// A wrapper around documents that ensures ayout can only ever access safe properties. +pub struct ServoLayoutDocument<'dom, LayoutDataType: LayoutDataTrait> { + /// The wrapped private DOM Document + document: LayoutDom<'dom, Document>, + + /// A PhantomData that is used to track the type of the stored layout data. + phantom: PhantomData, +} + +impl<'dom, LayoutDataType: LayoutDataTrait> Clone for ServoLayoutDocument<'dom, LayoutDataType> { + fn clone(&self) -> Self { + *self + } +} +impl<'dom, LayoutDataType: LayoutDataTrait> Copy for ServoLayoutDocument<'dom, LayoutDataType> {} + +impl<'ld, LayoutDataType: LayoutDataTrait> ::style::dom::TDocument + for ServoLayoutDocument<'ld, LayoutDataType> +{ + type ConcreteNode = ServoLayoutNode<'ld, LayoutDataType>; + + fn as_node(&self) -> Self::ConcreteNode { + ServoLayoutNode::from_layout_js(self.document.upcast()) + } + + fn quirks_mode(&self) -> QuirksMode { + self.document.quirks_mode() + } + + fn is_html_document(&self) -> bool { + self.document.is_html_document_for_layout() + } + + fn shared_lock(&self) -> &StyleSharedRwLock { + self.document.style_shared_lock() + } +} + +impl<'ld, LayoutDataType: LayoutDataTrait> ServoLayoutDocument<'ld, LayoutDataType> { + pub fn root_element(&self) -> Option> { + self.as_node() + .dom_children() + .flat_map(|n| n.as_element()) + .next() + } + + pub fn needs_paint_from_layout(&self) { + unsafe { self.document.needs_paint_from_layout() } + } + + pub fn will_paint(&self) { + unsafe { self.document.will_paint() } + } + + pub fn style_shared_lock(&self) -> &StyleSharedRwLock { + self.document.style_shared_lock() + } + + pub fn shadow_roots(&self) -> Vec> { + unsafe { + self.document + .shadow_roots() + .iter() + .map(|sr| { + debug_assert!(sr.upcast::().get_flag(NodeFlags::IS_CONNECTED)); + ServoShadowRoot::from_layout_js(*sr) + }) + .collect() + } + } + + pub fn flush_shadow_roots_stylesheets( + &self, + device: &Device, + quirks_mode: QuirksMode, + guard: &StyleSharedRwLockReadGuard, + ) { + 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<'ld, Document>) -> Self { + ServoLayoutDocument { + document: doc, + phantom: PhantomData, + } + } +} diff --git a/components/layout_thread/dom_wrapper.rs b/components/script/layout_dom/element.rs similarity index 50% rename from components/layout_thread/dom_wrapper.rs rename to components/script/layout_dom/element.rs index 35e7fa3b8ec..9b9f8c4790b 100644 --- a/components/layout_thread/dom_wrapper.rs +++ b/components/script/layout_dom/element.rs @@ -2,415 +2,85 @@ * 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/. */ -//! A safe wrapper for DOM nodes that prevents layout from mutating the DOM, from letting DOM nodes -//! escape, and from generally doing anything that it isn't supposed to. This is accomplished via -//! a simple whitelist of allowed operations, along with some lifetime magic to prevent nodes from -//! escaping. -//! -//! As a security wrapper is only as good as its whitelist, be careful when adding operations to -//! this list. The cardinal rules are: -//! -//! 1. Layout is not allowed to mutate the DOM. -//! -//! 2. Layout is not allowed to see anything with `LayoutDom` in the name, because it could hang -//! onto these objects and cause use-after-free. -//! -//! When implementing wrapper functions, be careful that you do not touch the borrow flags, or you -//! will race and cause spurious thread failure. (Note that I do not believe these races are -//! exploitable, but they'll result in brokenness nonetheless.) -//! -//! Rules of the road for this file: -//! -//! * Do not call any methods on DOM nodes without checking to see whether they use borrow flags. -//! -//! o Instead of `get_attr()`, use `.get_attr_val_for_layout()`. -//! -//! o Instead of `html_element_in_html_document()`, use -//! `html_element_in_html_document_for_layout()`. - -#![allow(unsafe_code)] - -use atomic_refcell::{AtomicRef, AtomicRefCell, AtomicRefMut}; -use gfx_traits::ByteIndex; +use crate::dom::bindings::inheritance::{ + CharacterDataTypeId, DocumentFragmentTypeId, ElementTypeId, +}; +use crate::dom::bindings::inheritance::{HTMLElementTypeId, NodeTypeId, TextTypeId}; +use crate::dom::bindings::root::LayoutDom; +use crate::dom::characterdata::LayoutCharacterDataHelpers; +use crate::dom::element::{Element, LayoutElementHelpers}; +use crate::dom::node::{LayoutNodeHelpers, NodeFlags}; +use atomic_refcell::{AtomicRef, AtomicRefMut}; use html5ever::{LocalName, Namespace}; -use layout::data::LayoutData; -use layout::wrapper::GetStyleAndLayoutData; -use msg::constellation_msg::{BrowsingContextId, PipelineId}; -use net_traits::image::base::{Image, ImageMetadata}; -use range::Range; -use script::layout_exports::NodeFlags; -use script::layout_exports::ShadowRoot; -use script::layout_exports::{ - 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, LayoutShadowRootHelpers, -}; use script_layout_interface::wrapper_traits::{ - DangerousThreadSafeLayoutNode, GetStyleAndOpaqueLayoutData, LayoutNode, + GetStyleAndOpaqueLayoutData, LayoutDataTrait, LayoutNode, PseudoElementType, + ThreadSafeLayoutElement, ThreadSafeLayoutNode, }; -use script_layout_interface::wrapper_traits::{ - PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode, -}; -use script_layout_interface::{ - HTMLCanvasData, HTMLMediaData, LayoutNodeType, StyleAndOpaqueLayoutData, -}; -use script_layout_interface::{SVGSVGData, StyleData, TrustedNodeAddress}; +use script_layout_interface::{LayoutNodeType, StyleAndOpaqueLayoutData, StyleData}; use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint}; -use selectors::matching::VisitedHandlingMode; -use selectors::matching::{ElementSelectorFlags, MatchingContext, QuirksMode}; +use selectors::matching::{ElementSelectorFlags, MatchingContext, VisitedHandlingMode}; use selectors::sink::Push; use servo_arc::{Arc, ArcBorrow}; use servo_atoms::Atom; -use servo_url::ServoUrl; -use std::borrow::Cow; use std::fmt; -use std::fmt::Debug; use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; use std::sync::atomic::Ordering; -use std::sync::Arc as StdArc; use style::animation::AnimationSetKey; use style::applicable_declarations::ApplicableDeclarationBlock; use style::attr::AttrValue; use style::context::SharedStyleContext; use style::data::ElementData; -use style::dom::{DomChildren, LayoutIterator, NodeInfo, OpaqueNode}; -use style::dom::{TDocument, TElement, TNode, TShadowRoot}; +use style::dom::{DomChildren, LayoutIterator, 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, SharedRwLockReadGuard, +use style::properties::PropertyDeclarationBlock; +use style::selector_parser::{ + extended_filtering, AttrValue as SelectorAttrValue, Lang, NonTSPseudoClass, PseudoElement, + SelectorImpl, }; -use style::str::is_whitespace; -use style::stylist::CascadeData; +use style::shared_lock::Locked as StyleLocked; use style::values::{AtomIdent, AtomString}; use style::CaseSensitivityExt; -#[derive(Clone, Copy)] -pub struct ServoLayoutNode<'dom> { - /// The wrapped node. - node: LayoutDom<'dom, Node>, -} - -impl<'ln> Debug for ServoLayoutNode<'ln> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(el) = self.as_element() { - el.fmt(f) - } else { - if self.is_text_node() { - write!(f, " ({:#x})", self.opaque().0) - } else { - write!(f, " ({:#x})", self.opaque().0) - } - } - } -} - -impl<'a> PartialEq for ServoLayoutNode<'a> { - #[inline] - fn eq(&self, other: &ServoLayoutNode) -> bool { - self.node == other.node - } -} - -impl<'ln> ServoLayoutNode<'ln> { - fn from_layout_js(n: LayoutDom<'ln, Node>) -> Self { - ServoLayoutNode { node: n } - } - - pub unsafe fn new(address: &TrustedNodeAddress) -> Self { - ServoLayoutNode::from_layout_js(LayoutDom::from_trusted_node_address(*address)) - } - - fn script_type_id(&self) -> NodeTypeId { - self.node.type_id_for_layout() - } -} - -impl<'ln> NodeInfo for ServoLayoutNode<'ln> { - fn is_element(&self) -> bool { - self.node.is_element_for_layout() - } - - fn is_text_node(&self) -> bool { - self.script_type_id() == - NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) - } -} - -#[derive(Clone, Copy, PartialEq)] -pub struct ServoShadowRoot<'dom> { - /// The wrapped shadow root. - shadow_root: LayoutDom<'dom, ShadowRoot>, -} - -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 { - ServoLayoutNode::from_layout_js(self.shadow_root.upcast()) - } - - fn host(&self) -> ServoLayoutElement<'lr> { - ServoLayoutElement::from_layout_js(self.shadow_root.get_host_for_layout()) - } - - fn style_data<'a>(&self) -> Option<&'a CascadeData> - where - Self: 'a, - { - Some(&self.shadow_root.get_style_data_for_layout()) - } -} - -impl<'lr> ServoShadowRoot<'lr> { - fn from_layout_js(shadow_root: LayoutDom<'lr, ShadowRoot>) -> Self { - ServoShadowRoot { shadow_root } - } - - pub unsafe fn flush_stylesheets( - &self, - device: &Device, - quirks_mode: QuirksMode, - guard: &SharedRwLockReadGuard, - ) { - self.shadow_root - .flush_stylesheets::(device, quirks_mode, guard) - } -} - -impl<'ln> TNode for ServoLayoutNode<'ln> { - type ConcreteDocument = ServoLayoutDocument<'ln>; - type ConcreteElement = ServoLayoutElement<'ln>; - type ConcreteShadowRoot = ServoShadowRoot<'ln>; - - fn parent_node(&self) -> Option { - self.node - .composed_parent_node_ref() - .map(Self::from_layout_js) - } - - fn first_child(&self) -> Option { - self.node.first_child_ref().map(Self::from_layout_js) - } - - fn last_child(&self) -> Option { - self.node.last_child_ref().map(Self::from_layout_js) - } - - fn prev_sibling(&self) -> Option { - self.node.prev_sibling_ref().map(Self::from_layout_js) - } - - fn next_sibling(&self) -> Option { - self.node.next_sibling_ref().map(Self::from_layout_js) - } - - fn owner_doc(&self) -> Self::ConcreteDocument { - ServoLayoutDocument::from_layout_js(self.node.owner_doc_for_layout()) - } - - fn traversal_parent(&self) -> Option> { - let parent = self.parent_node()?; - if let Some(shadow) = parent.as_shadow_root() { - return Some(shadow.host()); - }; - parent.as_element() - } - - fn opaque(&self) -> OpaqueNode { - self.get_jsmanaged().opaque() - } - - fn debug_id(self) -> usize { - self.opaque().0 - } - - fn as_element(&self) -> Option> { - as_element(self.node) - } - - fn as_document(&self) -> Option> { - self.node - .downcast() - .map(ServoLayoutDocument::from_layout_js) - } - - fn as_shadow_root(&self) -> Option> { - self.node.downcast().map(ServoShadowRoot::from_layout_js) - } - - fn is_in_document(&self) -> bool { - unsafe { self.node.get_flag(NodeFlags::IS_IN_DOC) } - } -} - -impl<'ln> LayoutNode<'ln> for ServoLayoutNode<'ln> { - type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'ln>; - - fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode { - ServoThreadSafeLayoutNode::new(*self) - } - - fn type_id(&self) -> LayoutNodeType { - self.script_type_id().into() - } - - unsafe fn initialize_data(&self) { - if self.get_style_and_layout_data().is_none() { - let opaque = StyleAndOpaqueLayoutData::new( - StyleData::new(), - AtomicRefCell::new(LayoutData::new()), - ); - self.init_style_and_opaque_layout_data(opaque); - }; - } - - unsafe fn init_style_and_opaque_layout_data(&self, data: Box) { - self.get_jsmanaged().init_style_and_opaque_layout_data(data); - } - - unsafe fn take_style_and_opaque_layout_data(&self) -> Box { - self.get_jsmanaged().take_style_and_opaque_layout_data() - } - - fn is_connected(&self) -> bool { - unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) } - } -} - -impl<'dom> GetStyleAndOpaqueLayoutData<'dom> for ServoLayoutNode<'dom> { - fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { - self.get_jsmanaged().get_style_and_opaque_layout_data() - } -} - -impl<'dom> GetStyleAndOpaqueLayoutData<'dom> for ServoLayoutElement<'dom> { - fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { - self.as_node().get_style_and_opaque_layout_data() - } -} - -impl<'dom> GetStyleAndOpaqueLayoutData<'dom> for ServoThreadSafeLayoutNode<'dom> { - fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { - self.node.get_style_and_opaque_layout_data() - } -} - -impl<'dom> GetStyleAndOpaqueLayoutData<'dom> for ServoThreadSafeLayoutElement<'dom> { - fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { - self.element.as_node().get_style_and_opaque_layout_data() - } -} - -impl<'dom> ServoLayoutNode<'dom> { - /// Returns the interior of this node as a `LayoutDom`. - pub fn get_jsmanaged(self) -> LayoutDom<'dom, Node> { - self.node - } -} - -// A wrapper around documents that ensures ayout can only ever access safe properties. -#[derive(Clone, Copy)] -pub struct ServoLayoutDocument<'dom> { - document: LayoutDom<'dom, Document>, -} - -impl<'ld> TDocument for ServoLayoutDocument<'ld> { - type ConcreteNode = ServoLayoutNode<'ld>; - - fn as_node(&self) -> Self::ConcreteNode { - ServoLayoutNode::from_layout_js(self.document.upcast()) - } - - fn quirks_mode(&self) -> QuirksMode { - self.document.quirks_mode() - } - - fn is_html_document(&self) -> bool { - self.document.is_html_document_for_layout() - } - - fn shared_lock(&self) -> &StyleSharedRwLock { - self.document.style_shared_lock() - } -} - -impl<'ld> ServoLayoutDocument<'ld> { - pub fn root_element(&self) -> Option> { - self.as_node() - .dom_children() - .flat_map(|n| n.as_element()) - .next() - } - - pub fn needs_paint_from_layout(&self) { - unsafe { self.document.needs_paint_from_layout() } - } - - pub fn will_paint(&self) { - unsafe { self.document.will_paint() } - } - - pub fn style_shared_lock(&self) -> &StyleSharedRwLock { - self.document.style_shared_lock() - } - - pub fn shadow_roots(&self) -> Vec { - unsafe { - self.document - .shadow_roots() - .iter() - .map(|sr| { - debug_assert!(sr.upcast::().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<'ld, Document>) -> Self { - ServoLayoutDocument { document: doc } - } -} +use crate::layout_dom::ServoLayoutNode; +use crate::layout_dom::ServoShadowRoot; +use crate::layout_dom::ServoThreadSafeLayoutNode; /// A wrapper around elements that ensures layout can only ever access safe properties. -#[derive(Clone, Copy)] -pub struct ServoLayoutElement<'dom> { +pub struct ServoLayoutElement<'dom, LayoutDataType: LayoutDataTrait> { + /// The wrapped private DOM Element. element: LayoutDom<'dom, Element>, + + /// A PhantomData that is used to track the type of the stored layout data. + phantom: PhantomData, } -impl<'le> fmt::Debug for ServoLayoutElement<'le> { +// These impls are required because `derive` has trouble with PhantomData. +// See https://github.com/rust-lang/rust/issues/52079 +impl<'dom, LayoutDataType: LayoutDataTrait> Clone for ServoLayoutElement<'dom, LayoutDataType> { + fn clone(&self) -> Self { + *self + } +} +impl<'dom, LayoutDataType: LayoutDataTrait> Copy for ServoLayoutElement<'dom, LayoutDataType> {} +impl<'dom, LayoutDataType: LayoutDataTrait> PartialEq for ServoLayoutElement<'dom, LayoutDataType> { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.as_node() == other.as_node() + } +} + +// `Hash` + `Eq` + `Debug` are required by ::style::dom::TElement. +impl<'dom, LayoutDataType: LayoutDataTrait> Eq for ServoLayoutElement<'dom, LayoutDataType> {} +impl<'dom, LayoutDataType: LayoutDataTrait> Hash for ServoLayoutElement<'dom, LayoutDataType> { + fn hash(&self, state: &mut H) { + self.element.hash(state); + } +} +impl<'dom, LayoutDataType: LayoutDataTrait> fmt::Debug + for ServoLayoutElement<'dom, LayoutDataType> +{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "<{}", self.element.local_name())?; if let Some(id) = self.id() { @@ -420,7 +90,39 @@ impl<'le> fmt::Debug for ServoLayoutElement<'le> { } } -impl<'dom> ServoLayoutElement<'dom> { +impl<'dom, LayoutDataType: LayoutDataTrait> ServoLayoutElement<'dom, LayoutDataType> { + pub(super) fn from_layout_js(el: LayoutDom<'dom, Element>) -> Self { + ServoLayoutElement { + element: el, + phantom: PhantomData, + } + } + + #[inline] + fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> { + self.element.get_attr_for_layout(namespace, name) + } + + #[inline] + fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str> { + self.element.get_attr_val_for_layout(namespace, name) + } + + fn get_style_data(&self) -> Option<&StyleData> { + self.get_style_and_opaque_layout_data() + .map(|data| &data.style_data) + } + + pub unsafe fn unset_snapshot_flags(&self) { + self.as_node() + .node + .set_flag(NodeFlags::HAS_SNAPSHOT | NodeFlags::HANDLED_SNAPSHOT, false); + } + + pub unsafe fn set_has_snapshot(&self) { + self.as_node().node.set_flag(NodeFlags::HAS_SNAPSHOT, true); + } + /// Returns true if this element is the body child of an html element root element. fn is_body_element_of_html_element_root(&self) -> bool { if self.element.local_name() != &local_name!("body") { @@ -439,7 +141,7 @@ impl<'dom> ServoLayoutElement<'dom> { self.element .upcast() .composed_parent_node_ref() - .and_then(as_element) + .and_then(|node| node.downcast().map(ServoLayoutElement::from_layout_js)) } fn is_root(&self) -> bool { @@ -453,13 +155,23 @@ impl<'dom> ServoLayoutElement<'dom> { } } -impl<'le> TElement for ServoLayoutElement<'le> { - type ConcreteNode = ServoLayoutNode<'le>; +impl<'dom, LayoutDataType: LayoutDataTrait> GetStyleAndOpaqueLayoutData<'dom> + for ServoLayoutElement<'dom, LayoutDataType> +{ + fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { + self.as_node().get_style_and_opaque_layout_data() + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::TElement + for ServoLayoutElement<'dom, LayoutDataType> +{ + type ConcreteNode = ServoLayoutNode<'dom, LayoutDataType>; type TraversalChildrenIterator = DomChildren; type FontMetricsProvider = ServoMetricsProvider; - fn as_node(&self) -> ServoLayoutNode<'le> { + fn as_node(&self) -> ServoLayoutNode<'dom, LayoutDataType> { ServoLayoutNode::from_layout_js(self.element.upcast()) } @@ -608,7 +320,7 @@ impl<'le> TElement for ServoLayoutElement<'le> { } unsafe fn clear_data(&self) { - if self.get_style_and_layout_data().is_some() { + if self.get_style_and_opaque_layout_data().is_some() { drop(self.as_node().take_style_and_opaque_layout_data()); } } @@ -719,14 +431,14 @@ impl<'le> TElement for ServoLayoutElement<'le> { } /// The shadow root this element is a host of. - fn shadow_root(&self) -> Option> { + fn shadow_root(&self) -> Option> { self.element .get_shadow_root_for_layout() .map(ServoShadowRoot::from_layout_js) } /// The shadow root which roots the subtree this element is contained in. - fn containing_shadow(&self) -> Option> { + fn containing_shadow(&self) -> Option> { self.element .upcast() .containing_shadow_root_for_layout() @@ -742,63 +454,16 @@ impl<'le> TElement for ServoLayoutElement<'le> { } } -impl<'le> PartialEq for ServoLayoutElement<'le> { - fn eq(&self, other: &Self) -> bool { - self.as_node() == other.as_node() - } -} - -impl<'le> Hash for ServoLayoutElement<'le> { - fn hash(&self, state: &mut H) { - self.element.hash(state); - } -} - -impl<'le> Eq for ServoLayoutElement<'le> {} - -impl<'le> ServoLayoutElement<'le> { - fn from_layout_js(el: LayoutDom<'le, Element>) -> Self { - ServoLayoutElement { element: el } - } - - #[inline] - fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> { - self.element.get_attr_for_layout(namespace, name) - } - - #[inline] - fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str> { - self.element.get_attr_val_for_layout(namespace, name) - } - - fn get_style_data(&self) -> Option<&StyleData> { - self.get_style_and_opaque_layout_data() - .map(|data| &data.style_data) - } - - pub unsafe fn unset_snapshot_flags(&self) { - self.as_node() - .node - .set_flag(NodeFlags::HAS_SNAPSHOT | NodeFlags::HANDLED_SNAPSHOT, false); - } - - pub unsafe fn set_has_snapshot(&self) { - self.as_node().node.set_flag(NodeFlags::HAS_SNAPSHOT, true); - } -} - -fn as_element<'dom>(node: LayoutDom<'dom, Node>) -> Option> { - node.downcast().map(ServoLayoutElement::from_layout_js) -} - -impl<'le> ::selectors::Element for ServoLayoutElement<'le> { +impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element + for ServoLayoutElement<'dom, LayoutDataType> +{ type Impl = SelectorImpl; fn opaque(&self) -> ::selectors::OpaqueElement { ::selectors::OpaqueElement::new(unsafe { &*(self.as_node().opaque().0 as *const ()) }) } - fn parent_element(&self) -> Option> { + fn parent_element(&self) -> Option { ServoLayoutElement::parent_element(self) } @@ -816,7 +481,7 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { self.containing_shadow().map(|s| s.host()) } - fn prev_sibling_element(&self) -> Option> { + fn prev_sibling_element(&self) -> Option { let mut node = self.as_node(); while let Some(sibling) = node.prev_sibling() { if let Some(element) = sibling.as_element() { @@ -827,7 +492,7 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { None } - fn next_sibling_element(&self) -> Option> { + fn next_sibling_element(&self) -> Option> { let mut node = self.as_node(); while let Some(sibling) = node.next_sibling() { if let Some(element) = sibling.as_element() { @@ -997,335 +662,44 @@ impl<'le> ::selectors::Element for ServoLayoutElement<'le> { } } -#[derive(Clone, Copy, Debug)] -pub struct ServoThreadSafeLayoutNode<'ln> { - /// The wrapped node. - node: ServoLayoutNode<'ln>, - - /// The pseudo-element type, with (optionally) - /// a specified display value to override the stylesheet. - pseudo: PseudoElementType, -} - -impl<'a> PartialEq for ServoThreadSafeLayoutNode<'a> { - #[inline] - fn eq(&self, other: &ServoThreadSafeLayoutNode<'a>) -> bool { - self.node == other.node - } -} - -impl<'ln> DangerousThreadSafeLayoutNode<'ln> for ServoThreadSafeLayoutNode<'ln> { - unsafe fn dangerous_first_child(&self) -> Option { - self.get_jsmanaged() - .first_child_ref() - .map(ServoLayoutNode::from_layout_js) - .map(Self::new) - } - unsafe fn dangerous_next_sibling(&self) -> Option { - self.get_jsmanaged() - .next_sibling_ref() - .map(ServoLayoutNode::from_layout_js) - .map(Self::new) - } -} - -impl<'ln> ServoThreadSafeLayoutNode<'ln> { - /// Creates a new `ServoThreadSafeLayoutNode` from the given `ServoLayoutNode`. - pub fn new(node: ServoLayoutNode<'ln>) -> Self { - ServoThreadSafeLayoutNode { - node: node.clone(), - pseudo: PseudoElementType::Normal, - } - } - - /// Returns the interior of this node as a `LayoutDom`. This is highly unsafe for layout to - /// call and as such is marked `unsafe`. - unsafe fn get_jsmanaged(&self) -> LayoutDom<'ln, Node> { - self.node.get_jsmanaged() - } -} - -impl<'ln> NodeInfo for ServoThreadSafeLayoutNode<'ln> { - fn is_element(&self) -> bool { - self.node.is_element() - } - - fn is_text_node(&self) -> bool { - self.node.is_text_node() - } -} - -impl<'ln> ThreadSafeLayoutNode<'ln> for ServoThreadSafeLayoutNode<'ln> { - type ConcreteNode = ServoLayoutNode<'ln>; - type ConcreteThreadSafeLayoutElement = ServoThreadSafeLayoutElement<'ln>; - type ConcreteElement = ServoLayoutElement<'ln>; - type ChildrenIterator = ThreadSafeLayoutNodeChildrenIterator; - - fn opaque(&self) -> OpaqueNode { - unsafe { self.get_jsmanaged().opaque() } - } - - fn type_id(&self) -> Option { - if self.pseudo == PseudoElementType::Normal { - Some(self.node.type_id()) - } else { - None - } - } - - fn parent_style(&self) -> Arc { - let parent = self.node.parent_node().unwrap().as_element().unwrap(); - let parent_data = parent.borrow_data().unwrap(); - parent_data.styles.primary().clone() - } - - fn debug_id(self) -> usize { - self.node.debug_id() - } - - fn children(&self) -> LayoutIterator { - 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)) - } - - fn as_element(&self) -> Option> { - self.node - .as_element() - .map(|el| ServoThreadSafeLayoutElement { - element: el, - pseudo: self.pseudo, - }) - } - - fn get_style_and_opaque_layout_data(self) -> Option<&'ln StyleAndOpaqueLayoutData> { - self.node.get_style_and_opaque_layout_data() - } - - fn is_ignorable_whitespace(&self, context: &SharedStyleContext) -> bool { - unsafe { - let text: LayoutDom = match self.get_jsmanaged().downcast() { - Some(text) => text, - None => return false, - }; - - if !is_whitespace(text.upcast().data_for_layout()) { - return false; - } - - // NB: See the rules for `white-space` here: - // - // http://www.w3.org/TR/CSS21/text.html#propdef-white-space - // - // If you implement other values for this property, you will almost certainly - // want to update this check. - !self - .style(context) - .get_inherited_text() - .white_space - .preserve_newlines() - } - } - - unsafe fn unsafe_get(self) -> Self::ConcreteNode { - self.node - } - - fn node_text_content(self) -> Cow<'ln, str> { - unsafe { self.get_jsmanaged().text_content() } - } - - fn selection(&self) -> Option> { - let this = unsafe { self.get_jsmanaged() }; - - this.selection().map(|range| { - Range::new( - ByteIndex(range.start as isize), - ByteIndex(range.len() as isize), - ) - }) - } - - fn image_url(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.image_url() - } - - fn image_density(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.image_density() - } - - fn image_data(&self) -> Option<(Option>, Option)> { - let this = unsafe { self.get_jsmanaged() }; - this.image_data() - } - - fn canvas_data(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.canvas_data() - } - - fn media_data(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.media_data() - } - - fn svg_data(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.svg_data() - } - - // Can return None if the iframe has no nested browsing context - fn iframe_browsing_context_id(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.iframe_browsing_context_id() - } - - // Can return None if the iframe has no nested browsing context - fn iframe_pipeline_id(&self) -> Option { - let this = unsafe { self.get_jsmanaged() }; - this.iframe_pipeline_id() - } - - fn get_colspan(&self) -> u32 { - unsafe { - self.get_jsmanaged() - .downcast::() - .unwrap() - .get_colspan() - } - } - - fn get_rowspan(&self) -> u32 { - unsafe { - self.get_jsmanaged() - .downcast::() - .unwrap() - .get_rowspan() - } - } -} - -pub struct ThreadSafeLayoutNodeChildrenIterator { - current_node: Option, - parent_node: ConcreteNode, -} - -impl<'dom, ConcreteNode> ThreadSafeLayoutNodeChildrenIterator -where - ConcreteNode: DangerousThreadSafeLayoutNode<'dom>, -{ - pub fn new(parent: ConcreteNode) -> Self { - let first_child: Option = match parent.get_pseudo_element_type() { - PseudoElementType::Normal => parent - .get_before_pseudo() - .or_else(|| parent.get_details_summary_pseudo()) - .or_else(|| unsafe { parent.dangerous_first_child() }), - PseudoElementType::DetailsContent | PseudoElementType::DetailsSummary => unsafe { - parent.dangerous_first_child() - }, - _ => None, - }; - ThreadSafeLayoutNodeChildrenIterator { - current_node: first_child, - parent_node: parent, - } - } -} - -impl<'dom, ConcreteNode> Iterator for ThreadSafeLayoutNodeChildrenIterator -where - ConcreteNode: DangerousThreadSafeLayoutNode<'dom>, -{ - type Item = ConcreteNode; - fn next(&mut self) -> Option { - use selectors::Element; - match self.parent_node.get_pseudo_element_type() { - PseudoElementType::Before | PseudoElementType::After => None, - - PseudoElementType::DetailsSummary => { - let mut current_node = self.current_node.clone(); - loop { - let next_node = if let Some(ref node) = current_node { - if let Some(element) = node.as_element() { - if element.has_local_name(&local_name!("summary")) && - element.has_namespace(&ns!(html)) - { - self.current_node = None; - return Some(node.clone()); - } - } - unsafe { node.dangerous_next_sibling() } - } else { - self.current_node = None; - return None; - }; - current_node = next_node; - } - }, - - PseudoElementType::DetailsContent => { - let node = self.current_node.clone(); - let node = node.and_then(|node| { - if node.is_element() && - node.as_element() - .unwrap() - .has_local_name(&local_name!("summary")) && - node.as_element().unwrap().has_namespace(&ns!(html)) - { - unsafe { node.dangerous_next_sibling() } - } else { - Some(node) - } - }); - self.current_node = node.and_then(|node| unsafe { node.dangerous_next_sibling() }); - node - }, - - PseudoElementType::Normal => { - let node = self.current_node.clone(); - if let Some(ref node) = node { - self.current_node = match node.get_pseudo_element_type() { - PseudoElementType::Before => self - .parent_node - .get_details_summary_pseudo() - .or_else(|| unsafe { self.parent_node.dangerous_first_child() }) - .or_else(|| self.parent_node.get_after_pseudo()), - PseudoElementType::Normal => unsafe { node.dangerous_next_sibling() } - .or_else(|| self.parent_node.get_after_pseudo()), - PseudoElementType::DetailsSummary => { - self.parent_node.get_details_content_pseudo() - }, - PseudoElementType::DetailsContent => self.parent_node.get_after_pseudo(), - PseudoElementType::After => None, - }; - } - node - }, - } - } -} - /// A wrapper around elements that ensures layout can only /// ever access safe properties and cannot race on elements. -#[derive(Clone, Copy, Debug)] -pub struct ServoThreadSafeLayoutElement<'le> { - element: ServoLayoutElement<'le>, +pub struct ServoThreadSafeLayoutElement<'dom, LayoutDataType: LayoutDataTrait> { + pub(super) element: ServoLayoutElement<'dom, LayoutDataType>, /// The pseudo-element type, with (optionally) /// a specified display value to override the stylesheet. - pseudo: PseudoElementType, + pub(super) pseudo: PseudoElementType, } -impl<'le> ThreadSafeLayoutElement<'le> for ServoThreadSafeLayoutElement<'le> { - type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'le>; - type ConcreteElement = ServoLayoutElement<'le>; +// These impls are required because `derive` has trouble with PhantomData. +// See https://github.com/rust-lang/rust/issues/52079 +impl<'dom, LayoutDataType: LayoutDataTrait> Clone + for ServoThreadSafeLayoutElement<'dom, LayoutDataType> +{ + fn clone(&self) -> Self { + *self + } +} +impl<'dom, LayoutDataType: LayoutDataTrait> Copy + for ServoThreadSafeLayoutElement<'dom, LayoutDataType> +{ +} +impl<'dom, LayoutDataType: LayoutDataTrait> fmt::Debug + for ServoThreadSafeLayoutElement<'dom, LayoutDataType> +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.element.fmt(f) + } +} - fn as_node(&self) -> ServoThreadSafeLayoutNode<'le> { +impl<'dom, LayoutDataType: LayoutDataTrait> ThreadSafeLayoutElement<'dom> + for ServoThreadSafeLayoutElement<'dom, LayoutDataType> +{ + type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'dom, LayoutDataType>; + type ConcreteElement = ServoLayoutElement<'dom, LayoutDataType>; + + fn as_node(&self) -> ServoThreadSafeLayoutNode<'dom, LayoutDataType> { ServoThreadSafeLayoutNode { node: self.element.as_node(), pseudo: self.pseudo.clone(), @@ -1347,7 +721,7 @@ impl<'le> ThreadSafeLayoutElement<'le> for ServoThreadSafeLayoutElement<'le> { self.as_node().type_id() } - unsafe fn unsafe_get(self) -> ServoLayoutElement<'le> { + unsafe fn unsafe_get(self) -> ServoLayoutElement<'dom, LayoutDataType> { self.element } @@ -1384,7 +758,9 @@ impl<'le> ThreadSafeLayoutElement<'le> for ServoThreadSafeLayoutElement<'le> { /// /// Note that the element implementation is needed only for selector matching, /// not for inheritance (styles are inherited appropriately). -impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { +impl<'dom, LayoutDataType: LayoutDataTrait> ::selectors::Element + for ServoThreadSafeLayoutElement<'dom, LayoutDataType> +{ type Impl = SelectorImpl; fn opaque(&self) -> ::selectors::OpaqueElement { @@ -1522,3 +898,11 @@ impl<'le> ::selectors::Element for ServoThreadSafeLayoutElement<'le> { false } } + +impl<'dom, LayoutDataType: LayoutDataTrait> GetStyleAndOpaqueLayoutData<'dom> + for ServoThreadSafeLayoutElement<'dom, LayoutDataType> +{ + fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { + self.element.as_node().get_style_and_opaque_layout_data() + } +} diff --git a/components/script/layout_dom/mod.rs b/components/script/layout_dom/mod.rs new file mode 100644 index 00000000000..0a7c951c7d2 --- /dev/null +++ b/components/script/layout_dom/mod.rs @@ -0,0 +1,32 @@ +/* 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/. */ + +//! A safe wrapper for DOM nodes that prevents layout from mutating the DOM, from letting DOM nodes +//! escape, and from generally doing anything that it isn't supposed to. This is accomplished via +//! a simple whitelist of allowed operations, along with some lifetime magic to prevent nodes from +//! escaping. +//! +//! As a security wrapper is only as good as its whitelist, be careful when adding operations to +//! this list. The cardinal rules are: +//! +//! 1. Layout is not allowed to mutate the DOM. +//! +//! 2. Layout is not allowed to see anything with `LayoutDom` in the name, because it could hang +//! onto these objects and cause use-after-free. +//! +//! When implementing wrapper functions, be careful that you do not touch the borrow flags, or you +//! will race and cause spurious thread failure. (Note that I do not believe these races are +//! exploitable, but they'll result in brokenness nonetheless.) + +#![allow(unsafe_code)] + +mod document; +mod element; +mod node; +mod shadow_root; + +pub use document::*; +pub use element::*; +pub use node::*; +pub use shadow_root::*; diff --git a/components/script/layout_dom/node.rs b/components/script/layout_dom/node.rs new file mode 100644 index 00000000000..6d5eaecd5f1 --- /dev/null +++ b/components/script/layout_dom/node.rs @@ -0,0 +1,591 @@ +/* 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/. */ + +#![allow(unsafe_code)] + +use super::ServoLayoutDocument; +use super::ServoLayoutElement; +use super::ServoShadowRoot; +use super::ServoThreadSafeLayoutElement; +use crate::dom::bindings::inheritance::CharacterDataTypeId; +use crate::dom::bindings::inheritance::{NodeTypeId, TextTypeId}; +use crate::dom::bindings::root::LayoutDom; +use crate::dom::characterdata::LayoutCharacterDataHelpers; +use crate::dom::element::{Element, LayoutElementHelpers}; +use crate::dom::node::{LayoutNodeHelpers, Node, NodeFlags}; +use crate::dom::text::Text; +use atomic_refcell::AtomicRefCell; +use gfx_traits::ByteIndex; +use msg::constellation_msg::{BrowsingContextId, PipelineId}; +use net_traits::image::base::{Image, ImageMetadata}; +use range::Range; +use script_layout_interface::wrapper_traits::{ + DangerousThreadSafeLayoutNode, GetStyleAndOpaqueLayoutData, LayoutDataTrait, LayoutNode, + PseudoElementType, ThreadSafeLayoutNode, +}; +use script_layout_interface::{ + HTMLCanvasData, HTMLMediaData, LayoutNodeType, SVGSVGData, StyleAndOpaqueLayoutData, StyleData, + TrustedNodeAddress, +}; +use servo_arc::Arc; +use servo_url::ServoUrl; +use std::borrow::Cow; +use std::fmt; +use std::marker::PhantomData; +use std::sync::Arc as StdArc; +use style; +use style::context::SharedStyleContext; +use style::dom::{NodeInfo, TElement, TNode, TShadowRoot}; +use style::properties::ComputedValues; +use style::str::is_whitespace; + +/// A wrapper around a `LayoutDom` which provides a safe interface that +/// can be used during layout. This implements the `LayoutNode` trait as well as +/// several style and selectors traits for use during layout. This version +/// should only be used on a single thread. If you need to use nodes across +/// threads use ServoThreadSafeLayoutNode. +pub struct ServoLayoutNode<'dom, LayoutDataType: LayoutDataTrait> { + /// The wrapped private DOM node. + pub(super) node: LayoutDom<'dom, Node>, + + /// A PhantomData that is used to track the type of the stored layout data. + pub(super) phantom: PhantomData, +} + +//// Those are supposed to be sound, but they aren't because the entire system +//// between script and layout so far has been designed to work around their +//// absence. Switching the entire thing to the inert crate infra will help. +/// +//// FIXME(mrobinson): These are required because Layout 2020 sends non-threadsafe +//// nodes to different threads. This should be adressed in a comprehensive way. +unsafe impl Send for ServoLayoutNode<'_, LayoutDataType> {} +unsafe impl Sync for ServoLayoutNode<'_, LayoutDataType> {} + +// These impls are required because `derive` has trouble with PhantomData. +// See https://github.com/rust-lang/rust/issues/52079 +impl<'dom, LayoutDataType: LayoutDataTrait> Clone for ServoLayoutNode<'dom, LayoutDataType> { + fn clone(&self) -> Self { + *self + } +} +impl<'dom, LayoutDataType: LayoutDataTrait> Copy for ServoLayoutNode<'dom, LayoutDataType> {} +impl<'a, LayoutDataType: LayoutDataTrait> PartialEq for ServoLayoutNode<'a, LayoutDataType> { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.node == other.node + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> fmt::Debug for ServoLayoutNode<'dom, LayoutDataType> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(el) = self.as_element() { + el.fmt(f) + } else { + if self.is_text_node() { + write!(f, " ({:#x})", self.opaque().0) + } else { + write!(f, " ({:#x})", self.opaque().0) + } + } + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> ServoLayoutNode<'dom, LayoutDataType> { + pub(super) fn from_layout_js(n: LayoutDom<'dom, Node>) -> Self { + ServoLayoutNode { + node: n, + phantom: PhantomData, + } + } + + pub unsafe fn new(address: &TrustedNodeAddress) -> Self { + ServoLayoutNode::from_layout_js(LayoutDom::from_trusted_node_address(*address)) + } + + pub(super) fn script_type_id(&self) -> NodeTypeId { + self.node.type_id_for_layout() + } + + /// Returns the interior of this node as a `LayoutDom`. + pub fn get_jsmanaged(self) -> LayoutDom<'dom, Node> { + self.node + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::NodeInfo + for ServoLayoutNode<'dom, LayoutDataType> +{ + fn is_element(&self) -> bool { + self.node.is_element_for_layout() + } + + fn is_text_node(&self) -> bool { + self.script_type_id() == + NodeTypeId::CharacterData(CharacterDataTypeId::Text(TextTypeId::Text)) + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::TNode + for ServoLayoutNode<'dom, LayoutDataType> +{ + type ConcreteDocument = ServoLayoutDocument<'dom, LayoutDataType>; + type ConcreteElement = ServoLayoutElement<'dom, LayoutDataType>; + type ConcreteShadowRoot = ServoShadowRoot<'dom, LayoutDataType>; + + fn parent_node(&self) -> Option { + self.node + .composed_parent_node_ref() + .map(Self::from_layout_js) + } + + fn first_child(&self) -> Option { + self.node.first_child_ref().map(Self::from_layout_js) + } + + fn last_child(&self) -> Option { + self.node.last_child_ref().map(Self::from_layout_js) + } + + fn prev_sibling(&self) -> Option { + self.node.prev_sibling_ref().map(Self::from_layout_js) + } + + fn next_sibling(&self) -> Option { + self.node.next_sibling_ref().map(Self::from_layout_js) + } + + fn owner_doc(&self) -> Self::ConcreteDocument { + ServoLayoutDocument::from_layout_js(self.node.owner_doc_for_layout()) + } + + fn traversal_parent(&self) -> Option> { + let parent = self.parent_node()?; + if let Some(shadow) = parent.as_shadow_root() { + return Some(shadow.host()); + }; + parent.as_element() + } + + fn opaque(&self) -> style::dom::OpaqueNode { + self.get_jsmanaged().opaque() + } + + fn debug_id(self) -> usize { + self.opaque().0 + } + + fn as_element(&self) -> Option> { + self.node.downcast().map(ServoLayoutElement::from_layout_js) + } + + fn as_document(&self) -> Option> { + self.node + .downcast() + .map(ServoLayoutDocument::from_layout_js) + } + + fn as_shadow_root(&self) -> Option> { + self.node.downcast().map(ServoShadowRoot::from_layout_js) + } + + fn is_in_document(&self) -> bool { + unsafe { self.node.get_flag(NodeFlags::IS_IN_DOC) } + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> LayoutNode<'dom> + for ServoLayoutNode<'dom, LayoutDataType> +{ + type ConcreteThreadSafeLayoutNode = ServoThreadSafeLayoutNode<'dom, LayoutDataType>; + + fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode { + ServoThreadSafeLayoutNode::new(*self) + } + + fn type_id(&self) -> LayoutNodeType { + self.script_type_id().into() + } + + unsafe fn initialize_data(&self) { + if self.get_style_and_opaque_layout_data().is_none() { + let opaque = StyleAndOpaqueLayoutData::new( + StyleData::new(), + AtomicRefCell::new(LayoutDataType::default()), + ); + self.init_style_and_opaque_layout_data(opaque); + }; + } + + unsafe fn init_style_and_opaque_layout_data(&self, data: Box) { + self.get_jsmanaged().init_style_and_opaque_layout_data(data); + } + + unsafe fn take_style_and_opaque_layout_data(&self) -> Box { + self.get_jsmanaged().take_style_and_opaque_layout_data() + } + + fn is_connected(&self) -> bool { + unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) } + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> GetStyleAndOpaqueLayoutData<'dom> + for ServoLayoutNode<'dom, LayoutDataType> +{ + fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { + self.get_jsmanaged().get_style_and_opaque_layout_data() + } +} + +/// A wrapper around a `ServoLayoutNode` that can be used safely on different threads. +/// It's very important that this never mutate anything except this wrapped node and +/// never access any other node apart from its parent. +pub struct ServoThreadSafeLayoutNode<'dom, LayoutDataType: LayoutDataTrait> { + /// The wrapped `ServoLayoutNode`. + pub(super) node: ServoLayoutNode<'dom, LayoutDataType>, + + /// The pseudo-element type, with (optionally) + /// a specified display value to override the stylesheet. + pub(super) pseudo: PseudoElementType, +} + +// These impls are required because `derive` has trouble with PhantomData. +// See https://github.com/rust-lang/rust/issues/52079 +impl<'dom, LayoutDataType: LayoutDataTrait> Clone + for ServoThreadSafeLayoutNode<'dom, LayoutDataType> +{ + fn clone(&self) -> Self { + *self + } +} +impl<'dom, LayoutDataType: LayoutDataTrait> Copy + for ServoThreadSafeLayoutNode<'dom, LayoutDataType> +{ +} +impl<'a, LayoutDataType: LayoutDataTrait> PartialEq + for ServoThreadSafeLayoutNode<'a, LayoutDataType> +{ + #[inline] + fn eq(&self, other: &Self) -> bool { + self.node == other.node + } +} + +impl<'lr, LayoutDataType: LayoutDataTrait> fmt::Debug + for ServoThreadSafeLayoutNode<'lr, LayoutDataType> +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.node.fmt(f) + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> DangerousThreadSafeLayoutNode<'dom> + for ServoThreadSafeLayoutNode<'dom, LayoutDataType> +{ + unsafe fn dangerous_first_child(&self) -> Option { + self.get_jsmanaged() + .first_child_ref() + .map(ServoLayoutNode::from_layout_js) + .map(Self::new) + } + unsafe fn dangerous_next_sibling(&self) -> Option { + self.get_jsmanaged() + .next_sibling_ref() + .map(ServoLayoutNode::from_layout_js) + .map(Self::new) + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> ServoThreadSafeLayoutNode<'dom, LayoutDataType> { + /// Creates a new `ServoThreadSafeLayoutNode` from the given `ServoLayoutNode`. + pub fn new(node: ServoLayoutNode<'dom, LayoutDataType>) -> Self { + ServoThreadSafeLayoutNode { + node: node.clone(), + pseudo: PseudoElementType::Normal, + } + } + + /// Returns the interior of this node as a `LayoutDom`. This is highly unsafe for layout to + /// call and as such is marked `unsafe`. + unsafe fn get_jsmanaged(&self) -> LayoutDom<'dom, Node> { + self.node.get_jsmanaged() + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> style::dom::NodeInfo + for ServoThreadSafeLayoutNode<'dom, LayoutDataType> +{ + fn is_element(&self) -> bool { + self.node.is_element() + } + + fn is_text_node(&self) -> bool { + self.node.is_text_node() + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> ThreadSafeLayoutNode<'dom> + for ServoThreadSafeLayoutNode<'dom, LayoutDataType> +{ + type ConcreteNode = ServoLayoutNode<'dom, LayoutDataType>; + type ConcreteThreadSafeLayoutElement = ServoThreadSafeLayoutElement<'dom, LayoutDataType>; + type ConcreteElement = ServoLayoutElement<'dom, LayoutDataType>; + type ChildrenIterator = ThreadSafeLayoutNodeChildrenIterator; + + fn opaque(&self) -> style::dom::OpaqueNode { + unsafe { self.get_jsmanaged().opaque() } + } + + fn type_id(&self) -> Option { + if self.pseudo == PseudoElementType::Normal { + Some(self.node.type_id()) + } else { + None + } + } + + fn parent_style(&self) -> Arc { + let parent = self.node.parent_node().unwrap().as_element().unwrap(); + let parent_data = parent.borrow_data().unwrap(); + parent_data.styles.primary().clone() + } + + fn debug_id(self) -> usize { + self.node.debug_id() + } + + fn children(&self) -> style::dom::LayoutIterator { + if let Some(shadow) = self.node.as_element().and_then(|e| e.shadow_root()) { + return style::dom::LayoutIterator(ThreadSafeLayoutNodeChildrenIterator::new( + shadow.as_node().to_threadsafe(), + )); + } + style::dom::LayoutIterator(ThreadSafeLayoutNodeChildrenIterator::new(*self)) + } + + fn as_element(&self) -> Option> { + self.node + .as_element() + .map(|el| ServoThreadSafeLayoutElement { + element: el, + pseudo: self.pseudo, + }) + } + + fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { + self.node.get_style_and_opaque_layout_data() + } + + fn is_ignorable_whitespace(&self, context: &SharedStyleContext) -> bool { + unsafe { + let text: LayoutDom = match self.get_jsmanaged().downcast() { + Some(text) => text, + None => return false, + }; + + if !is_whitespace(text.upcast().data_for_layout()) { + return false; + } + + // NB: See the rules for `white-space` here: + // + // http://www.w3.org/TR/CSS21/text.html#propdef-white-space + // + // If you implement other values for this property, you will almost certainly + // want to update this check. + !self + .style(context) + .get_inherited_text() + .white_space + .preserve_newlines() + } + } + + unsafe fn unsafe_get(self) -> Self::ConcreteNode { + self.node + } + + fn node_text_content(self) -> Cow<'dom, str> { + unsafe { self.get_jsmanaged().text_content() } + } + + fn selection(&self) -> Option> { + let this = unsafe { self.get_jsmanaged() }; + + this.selection().map(|range| { + Range::new( + ByteIndex(range.start as isize), + ByteIndex(range.len() as isize), + ) + }) + } + + fn image_url(&self) -> Option { + let this = unsafe { self.get_jsmanaged() }; + this.image_url() + } + + fn image_density(&self) -> Option { + let this = unsafe { self.get_jsmanaged() }; + this.image_density() + } + + fn image_data(&self) -> Option<(Option>, Option)> { + let this = unsafe { self.get_jsmanaged() }; + this.image_data() + } + + fn canvas_data(&self) -> Option { + let this = unsafe { self.get_jsmanaged() }; + this.canvas_data() + } + + fn media_data(&self) -> Option { + let this = unsafe { self.get_jsmanaged() }; + this.media_data() + } + + fn svg_data(&self) -> Option { + let this = unsafe { self.get_jsmanaged() }; + this.svg_data() + } + + // Can return None if the iframe has no nested browsing context + fn iframe_browsing_context_id(&self) -> Option { + let this = unsafe { self.get_jsmanaged() }; + this.iframe_browsing_context_id() + } + + // Can return None if the iframe has no nested browsing context + fn iframe_pipeline_id(&self) -> Option { + let this = unsafe { self.get_jsmanaged() }; + this.iframe_pipeline_id() + } + + fn get_colspan(&self) -> u32 { + unsafe { + self.get_jsmanaged() + .downcast::() + .unwrap() + .get_colspan() + } + } + + fn get_rowspan(&self) -> u32 { + unsafe { + self.get_jsmanaged() + .downcast::() + .unwrap() + .get_rowspan() + } + } +} + +pub struct ThreadSafeLayoutNodeChildrenIterator { + current_node: Option, + parent_node: ConcreteNode, +} + +impl<'dom, ConcreteNode> ThreadSafeLayoutNodeChildrenIterator +where + ConcreteNode: DangerousThreadSafeLayoutNode<'dom>, +{ + pub fn new(parent: ConcreteNode) -> Self { + let first_child: Option = match parent.get_pseudo_element_type() { + PseudoElementType::Normal => parent + .get_before_pseudo() + .or_else(|| parent.get_details_summary_pseudo()) + .or_else(|| unsafe { parent.dangerous_first_child() }), + PseudoElementType::DetailsContent | PseudoElementType::DetailsSummary => unsafe { + parent.dangerous_first_child() + }, + _ => None, + }; + ThreadSafeLayoutNodeChildrenIterator { + current_node: first_child, + parent_node: parent, + } + } +} + +impl<'dom, ConcreteNode> Iterator for ThreadSafeLayoutNodeChildrenIterator +where + ConcreteNode: DangerousThreadSafeLayoutNode<'dom>, +{ + type Item = ConcreteNode; + fn next(&mut self) -> Option { + use selectors::Element; + match self.parent_node.get_pseudo_element_type() { + PseudoElementType::Before | PseudoElementType::After => None, + + PseudoElementType::DetailsSummary => { + let mut current_node = self.current_node.clone(); + loop { + let next_node = if let Some(ref node) = current_node { + if let Some(element) = node.as_element() { + if element.has_local_name(&local_name!("summary")) && + element.has_namespace(&ns!(html)) + { + self.current_node = None; + return Some(node.clone()); + } + } + unsafe { node.dangerous_next_sibling() } + } else { + self.current_node = None; + return None; + }; + current_node = next_node; + } + }, + + PseudoElementType::DetailsContent => { + let node = self.current_node.clone(); + let node = node.and_then(|node| { + if node.is_element() && + node.as_element() + .unwrap() + .has_local_name(&local_name!("summary")) && + node.as_element().unwrap().has_namespace(&ns!(html)) + { + unsafe { node.dangerous_next_sibling() } + } else { + Some(node) + } + }); + self.current_node = node.and_then(|node| unsafe { node.dangerous_next_sibling() }); + node + }, + + PseudoElementType::Normal => { + let node = self.current_node.clone(); + if let Some(ref node) = node { + self.current_node = match node.get_pseudo_element_type() { + PseudoElementType::Before => self + .parent_node + .get_details_summary_pseudo() + .or_else(|| unsafe { self.parent_node.dangerous_first_child() }) + .or_else(|| self.parent_node.get_after_pseudo()), + PseudoElementType::Normal => unsafe { node.dangerous_next_sibling() } + .or_else(|| self.parent_node.get_after_pseudo()), + PseudoElementType::DetailsSummary => { + self.parent_node.get_details_content_pseudo() + }, + PseudoElementType::DetailsContent => self.parent_node.get_after_pseudo(), + PseudoElementType::After => None, + }; + } + node + }, + } + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> GetStyleAndOpaqueLayoutData<'dom> + for ServoThreadSafeLayoutNode<'dom, LayoutDataType> +{ + fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData> { + self.node.get_style_and_opaque_layout_data() + } +} diff --git a/components/script/layout_dom/shadow_root.rs b/components/script/layout_dom/shadow_root.rs new file mode 100644 index 00000000000..2fd1511ccfa --- /dev/null +++ b/components/script/layout_dom/shadow_root.rs @@ -0,0 +1,84 @@ +/* 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::LayoutDom; +use crate::dom::shadowroot::{LayoutShadowRootHelpers, ShadowRoot}; +use crate::layout_dom::ServoLayoutElement; +use crate::layout_dom::ServoLayoutNode; +use script_layout_interface::wrapper_traits::LayoutDataTrait; +use selectors::matching::QuirksMode; +use std::fmt; +use std::marker::PhantomData; +use style::dom::TShadowRoot; +use style::media_queries::Device; +use style::shared_lock::SharedRwLockReadGuard as StyleSharedRwLockReadGuard; +use style::stylist::CascadeData; + +pub struct ServoShadowRoot<'dom, LayoutDataType: LayoutDataTrait> { + /// The wrapped private DOM ShadowRoot. + shadow_root: LayoutDom<'dom, ShadowRoot>, + + /// A PhantomData that is used to track the type of the stored layout data. + phantom: PhantomData, +} + +impl<'dom, LayoutDataType: LayoutDataTrait> Clone for ServoShadowRoot<'dom, LayoutDataType> { + fn clone(&self) -> Self { + *self + } +} +impl<'dom, LayoutDataType: LayoutDataTrait> Copy for ServoShadowRoot<'dom, LayoutDataType> {} + +impl<'a, LayoutDataType: LayoutDataTrait> PartialEq for ServoShadowRoot<'a, LayoutDataType> { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.shadow_root == other.shadow_root + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> fmt::Debug for ServoShadowRoot<'dom, LayoutDataType> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.as_node().fmt(f) + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> ::style::dom::TShadowRoot + for ServoShadowRoot<'dom, LayoutDataType> +{ + type ConcreteNode = ServoLayoutNode<'dom, LayoutDataType>; + + fn as_node(&self) -> Self::ConcreteNode { + ServoLayoutNode::from_layout_js(self.shadow_root.upcast()) + } + + fn host(&self) -> ServoLayoutElement<'dom, LayoutDataType> { + ServoLayoutElement::from_layout_js(self.shadow_root.get_host_for_layout()) + } + + fn style_data<'a>(&self) -> Option<&'a CascadeData> + where + Self: 'a, + { + Some(&self.shadow_root.get_style_data_for_layout()) + } +} + +impl<'dom, LayoutDataType: LayoutDataTrait> ServoShadowRoot<'dom, LayoutDataType> { + pub(super) fn from_layout_js(shadow_root: LayoutDom<'dom, ShadowRoot>) -> Self { + ServoShadowRoot { + shadow_root, + phantom: PhantomData, + } + } + + pub unsafe fn flush_stylesheets( + &self, + device: &Device, + quirks_mode: QuirksMode, + guard: &StyleSharedRwLockReadGuard, + ) { + self.shadow_root + .flush_stylesheets::>(device, quirks_mode, guard) + } +} diff --git a/components/script/lib.rs b/components/script/lib.rs index b34b1fb11ee..66c878dbbde 100644 --- a/components/script/lib.rs +++ b/components/script/lib.rs @@ -73,6 +73,8 @@ mod image_listener; mod init; #[warn(deprecated)] mod layout_image; + +pub mod layout_dom; #[warn(deprecated)] mod mem; #[warn(deprecated)] @@ -115,23 +117,3 @@ mod window_named_properties; pub use init::init; pub use script_runtime::JSEngineSetup; - -/// A module with everything layout can use from script. -/// -/// Try to keep this small! -/// -/// TODO(emilio): A few of the FooHelpers can go away, presumably... -pub mod layout_exports { - 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; - pub use crate::dom::document::{Document, LayoutDocumentHelpers}; - pub use crate::dom::element::{Element, LayoutElementHelpers}; - 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_layout_interface/wrapper_traits.rs b/components/script_layout_interface/wrapper_traits.rs index 10e6f826315..9091a394161 100644 --- a/components/script_layout_interface/wrapper_traits.rs +++ b/components/script_layout_interface/wrapper_traits.rs @@ -31,6 +31,8 @@ use style::selector_parser::{PseudoElement, PseudoElementCascadeType, SelectorIm use style::stylist::RuleInclusion; use webrender_api::ExternalScrollId; +pub trait LayoutDataTrait: Default + Send + Sync + 'static {} + #[derive(Clone, Copy, Debug, PartialEq)] pub enum PseudoElementType { Normal,