diff options
Diffstat (limited to 'components/shared/script_layout')
-rw-r--r-- | components/shared/script_layout/Cargo.toml | 39 | ||||
-rw-r--r-- | components/shared/script_layout/lib.rs | 164 | ||||
-rw-r--r-- | components/shared/script_layout/message.rs | 260 | ||||
-rw-r--r-- | components/shared/script_layout/rpc.rs | 75 | ||||
-rw-r--r-- | components/shared/script_layout/wrapper_traits.rs | 497 |
5 files changed, 1035 insertions, 0 deletions
diff --git a/components/shared/script_layout/Cargo.toml b/components/shared/script_layout/Cargo.toml new file mode 100644 index 00000000000..553a773afac --- /dev/null +++ b/components/shared/script_layout/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "script_layout_interface" +version = "0.0.1" +authors = ["The Servo Project Developers"] +license = "MPL-2.0" +edition = "2018" +publish = false + +[lib] +name = "script_layout_interface" +path = "lib.rs" + +[dependencies] +app_units = { workspace = true } +atomic_refcell = { workspace = true } +canvas_traits = { workspace = true } +crossbeam-channel = { workspace = true } +euclid = { workspace = true } +fxhash = { workspace = true } +gfx_traits = { workspace = true } +html5ever = { workspace = true } +ipc-channel = { workspace = true } +libc = { workspace = true } +malloc_size_of = { path = "../../malloc_size_of" } +malloc_size_of_derive = { workspace = true } +metrics = { path = "../../metrics" } +msg = { workspace = true } +net_traits = { workspace = true } +parking_lot = { workspace = true } +profile_traits = { workspace = true } +range = { path = "../../range" } +script_traits = { workspace = true } +selectors = { path = "../../selectors", features = ["shmem"] } +servo_arc = { path = "../../servo_arc" } +servo_atoms = { path = "../../atoms" } +servo_url = { path = "../../url" } +style = { path = "../../style", features = ["servo"] } +style_traits = { workspace = true } +webrender_api = { git = "https://github.com/servo/webrender" } diff --git a/components/shared/script_layout/lib.rs b/components/shared/script_layout/lib.rs new file mode 100644 index 00000000000..0db42229678 --- /dev/null +++ b/components/shared/script_layout/lib.rs @@ -0,0 +1,164 @@ +/* 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/. */ + +//! This module contains traits in script used generically in the rest of Servo. +//! The traits are here instead of in script so that these modules won't have +//! to depend on script. + +#![deny(unsafe_code)] + +pub mod message; +pub mod rpc; +pub mod wrapper_traits; + +use std::any::Any; +use std::sync::atomic::AtomicIsize; + +use atomic_refcell::AtomicRefCell; +use canvas_traits::canvas::{CanvasId, CanvasMsg}; +use ipc_channel::ipc::IpcSender; +use libc::c_void; +use malloc_size_of_derive::MallocSizeOf; +use net_traits::image_cache::PendingImageId; +use script_traits::UntrustedNodeAddress; +use servo_url::{ImmutableOrigin, ServoUrl}; +use style::data::ElementData; +use webrender_api::ImageKey; + +#[derive(MallocSizeOf)] +pub struct StyleData { + /// Data that the style system associates with a node. When the + /// style system is being used standalone, this is all that hangs + /// off the node. This must be first to permit the various + /// transmutations between ElementData and PersistentLayoutData. + #[ignore_malloc_size_of = "This probably should not be ignored"] + pub element_data: AtomicRefCell<ElementData>, + + /// Information needed during parallel traversals. + pub parallel: DomParallelInfo, +} + +impl StyleData { + pub fn new() -> Self { + Self { + element_data: AtomicRefCell::new(ElementData::default()), + parallel: DomParallelInfo::new(), + } + } +} + +pub type StyleAndOpaqueLayoutData = StyleAndGenericData<dyn Any + Send + Sync>; + +#[derive(MallocSizeOf)] +pub struct StyleAndGenericData<T> +where + T: ?Sized, +{ + /// The style data. + pub style_data: StyleData, + /// The opaque layout data. + #[ignore_malloc_size_of = "Trait objects are hard"] + pub generic_data: T, +} + +impl StyleAndOpaqueLayoutData { + #[inline] + pub fn new<T>(style_data: StyleData, layout_data: T) -> Box<Self> + where + T: Any + Send + Sync, + { + Box::new(StyleAndGenericData { + style_data, + generic_data: layout_data, + }) + } +} + +/// Information that we need stored in each DOM node. +#[derive(MallocSizeOf)] +pub struct DomParallelInfo { + /// The number of children remaining to process during bottom-up traversal. + pub children_to_process: AtomicIsize, +} + +impl DomParallelInfo { + pub fn new() -> DomParallelInfo { + DomParallelInfo { + children_to_process: AtomicIsize::new(0), + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum LayoutNodeType { + Element(LayoutElementType), + Text, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum LayoutElementType { + Element, + HTMLBodyElement, + HTMLBRElement, + HTMLCanvasElement, + HTMLHtmlElement, + HTMLIFrameElement, + HTMLImageElement, + HTMLInputElement, + HTMLMediaElement, + HTMLObjectElement, + HTMLParagraphElement, + HTMLTableCellElement, + HTMLTableColElement, + HTMLTableElement, + HTMLTableRowElement, + HTMLTableSectionElement, + HTMLTextAreaElement, + SVGSVGElement, +} + +pub enum HTMLCanvasDataSource { + WebGL(ImageKey), + Image(Option<IpcSender<CanvasMsg>>), + WebGPU(ImageKey), +} + +pub struct HTMLCanvasData { + pub source: HTMLCanvasDataSource, + pub width: u32, + pub height: u32, + pub canvas_id: CanvasId, +} + +pub struct SVGSVGData { + pub width: u32, + pub height: u32, +} + +/// The address of a node known to be valid. These are sent from script to layout. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct TrustedNodeAddress(pub *const c_void); + +#[allow(unsafe_code)] +unsafe impl Send for TrustedNodeAddress {} + +/// Whether the pending image needs to be fetched or is waiting on an existing fetch. +pub enum PendingImageState { + Unrequested(ServoUrl), + PendingResponse, +} + +/// The data associated with an image that is not yet present in the image cache. +/// Used by the script thread to hold on to DOM elements that need to be repainted +/// when an image fetch is complete. +pub struct PendingImage { + pub state: PendingImageState, + pub node: UntrustedNodeAddress, + pub id: PendingImageId, + pub origin: ImmutableOrigin, +} + +pub struct HTMLMediaData { + pub current_frame: Option<(ImageKey, i32, i32)>, +} diff --git a/components/shared/script_layout/message.rs b/components/shared/script_layout/message.rs new file mode 100644 index 00000000000..01c5d58e659 --- /dev/null +++ b/components/shared/script_layout/message.rs @@ -0,0 +1,260 @@ +/* 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 std::sync::atomic::AtomicBool; +use std::sync::Arc; + +use app_units::Au; +use crossbeam_channel::{Receiver, Sender}; +use euclid::default::{Point2D, Rect}; +use gfx_traits::Epoch; +use ipc_channel::ipc::{IpcReceiver, IpcSender}; +use malloc_size_of_derive::MallocSizeOf; +use metrics::PaintTimeMetrics; +use msg::constellation_msg::{BackgroundHangMonitorRegister, BrowsingContextId, PipelineId}; +use net_traits::image_cache::ImageCache; +use profile_traits::mem::ReportsChan; +use script_traits::{ + ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg, Painter, ScrollState, + WindowSizeData, +}; +use servo_arc::Arc as ServoArc; +use servo_atoms::Atom; +use servo_url::{ImmutableOrigin, ServoUrl}; +use style::animation::DocumentAnimationSet; +use style::context::QuirksMode; +use style::dom::OpaqueNode; +use style::invalidation::element::restyle_hints::RestyleHint; +use style::properties::PropertyId; +use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot}; +use style::stylesheets::Stylesheet; + +use crate::rpc::LayoutRPC; +use crate::{PendingImage, TrustedNodeAddress}; + +/// Asynchronous messages that script can send to layout. +pub enum Msg { + /// Adds the given stylesheet to the document. The second stylesheet is the + /// insertion point (if it exists, the sheet needs to be inserted before + /// it). + AddStylesheet(ServoArc<Stylesheet>, Option<ServoArc<Stylesheet>>), + + /// Removes a stylesheet from the document. + RemoveStylesheet(ServoArc<Stylesheet>), + + /// Change the quirks mode. + SetQuirksMode(QuirksMode), + + /// Requests a reflow. + Reflow(ScriptReflow), + + /// Get an RPC interface. + GetRPC(Sender<Box<dyn LayoutRPC + Send>>), + + /// Requests that the layout thread measure its memory usage. The resulting reports are sent back + /// via the supplied channel. + CollectReports(ReportsChan), + + /// Requests that the layout thread enter a quiescent state in which no more messages are + /// accepted except `ExitMsg`. A response message will be sent on the supplied channel when + /// this happens. + PrepareToExit(Sender<()>), + + /// Requests that the layout thread immediately shut down. There must be no more nodes left after + /// this, or layout will crash. + ExitNow, + + /// Get the last epoch counter for this layout thread. + GetCurrentEpoch(IpcSender<Epoch>), + + /// Asks the layout thread whether any Web fonts have yet to load (if true, loads are pending; + /// false otherwise). + GetWebFontLoadState(IpcSender<bool>), + + /// Creates a new layout thread. + /// + /// This basically exists to keep the script-layout dependency one-way. + CreateLayoutThread(LayoutThreadInit), + + /// Set the final Url. + SetFinalUrl(ServoUrl), + + /// Tells layout about the new scrolling offsets of each scrollable stacking context. + SetScrollStates(Vec<ScrollState>), + + /// Tells layout that script has added some paint worklet modules. + RegisterPaint(Atom, Vec<Atom>, Box<dyn Painter>), + + /// Send to layout the precise time when the navigation started. + SetNavigationStart(u64), +} + +#[derive(Debug, PartialEq)] +pub enum NodesFromPointQueryType { + All, + Topmost, +} + +#[derive(Debug, PartialEq)] +pub enum QueryMsg { + ContentBoxQuery(OpaqueNode), + ContentBoxesQuery(OpaqueNode), + ClientRectQuery(OpaqueNode), + ScrollingAreaQuery(Option<OpaqueNode>), + OffsetParentQuery(OpaqueNode), + TextIndexQuery(OpaqueNode, Point2D<f32>), + NodesFromPointQuery(Point2D<f32>, NodesFromPointQueryType), + + // FIXME(nox): The following queries use the TrustedNodeAddress to + // access actual DOM nodes, but those values can be constructed from + // garbage values such as `0xdeadbeef as *const _`, this is unsound. + NodeScrollIdQuery(TrustedNodeAddress), + ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, PropertyId), + StyleQuery, + ElementInnerTextQuery(TrustedNodeAddress), + ResolvedFontStyleQuery(TrustedNodeAddress, PropertyId, String), + InnerWindowDimensionsQuery(BrowsingContextId), +} + +/// Any query to perform with this reflow. +#[derive(Debug, PartialEq)] +pub enum ReflowGoal { + Full, + TickAnimations, + LayoutQuery(QueryMsg, u64), + + /// Tells layout about a single new scrolling offset from the script. The rest will + /// remain untouched and layout won't forward this back to script. + UpdateScrollNode(ScrollState), +} + +impl ReflowGoal { + /// Returns true if the given ReflowQuery needs a full, up-to-date display list to + /// be present or false if it only needs stacking-relative positions. + pub fn needs_display_list(&self) -> bool { + match *self { + ReflowGoal::Full | ReflowGoal::TickAnimations | ReflowGoal::UpdateScrollNode(_) => true, + ReflowGoal::LayoutQuery(ref querymsg, _) => match *querymsg { + QueryMsg::NodesFromPointQuery(..) | + QueryMsg::TextIndexQuery(..) | + QueryMsg::InnerWindowDimensionsQuery(_) | + QueryMsg::ElementInnerTextQuery(_) => true, + QueryMsg::ContentBoxQuery(_) | + QueryMsg::ContentBoxesQuery(_) | + QueryMsg::ClientRectQuery(_) | + QueryMsg::ScrollingAreaQuery(_) | + QueryMsg::NodeScrollIdQuery(_) | + QueryMsg::ResolvedStyleQuery(..) | + QueryMsg::ResolvedFontStyleQuery(..) | + QueryMsg::OffsetParentQuery(_) | + QueryMsg::StyleQuery => false, + }, + } + } + + /// Returns true if the given ReflowQuery needs its display list send to WebRender or + /// false if a layout_thread display list is sufficient. + pub fn needs_display(&self) -> bool { + match *self { + ReflowGoal::Full | ReflowGoal::TickAnimations | ReflowGoal::UpdateScrollNode(_) => true, + ReflowGoal::LayoutQuery(ref querymsg, _) => match *querymsg { + QueryMsg::NodesFromPointQuery(..) | + QueryMsg::TextIndexQuery(..) | + QueryMsg::ElementInnerTextQuery(_) => true, + QueryMsg::ContentBoxQuery(_) | + QueryMsg::ContentBoxesQuery(_) | + QueryMsg::ClientRectQuery(_) | + QueryMsg::ScrollingAreaQuery(_) | + QueryMsg::NodeScrollIdQuery(_) | + QueryMsg::ResolvedStyleQuery(..) | + QueryMsg::ResolvedFontStyleQuery(..) | + QueryMsg::OffsetParentQuery(_) | + QueryMsg::InnerWindowDimensionsQuery(_) | + QueryMsg::StyleQuery => false, + }, + } + } +} + +/// Information needed for a reflow. +pub struct Reflow { + /// A clipping rectangle for the page, an enlarged rectangle containing the viewport. + pub page_clip_rect: Rect<Au>, +} + +/// Information derived from a layout pass that needs to be returned to the script thread. +#[derive(Default)] +pub struct ReflowComplete { + /// The list of images that were encountered that are in progress. + pub pending_images: Vec<PendingImage>, +} + +/// Information needed for a script-initiated reflow. +pub struct ScriptReflow { + /// General reflow data. + pub reflow_info: Reflow, + /// The document node. + pub document: TrustedNodeAddress, + /// The dirty root from which to restyle. + pub dirty_root: Option<TrustedNodeAddress>, + /// Whether the document's stylesheets have changed since the last script reflow. + pub stylesheets_changed: bool, + /// The current window size. + pub window_size: WindowSizeData, + /// The channel that we send a notification to. + pub script_join_chan: Sender<ReflowComplete>, + /// The goal of this reflow. + pub reflow_goal: ReflowGoal, + /// The number of objects in the dom #10110 + pub dom_count: u32, + /// The current window origin + pub origin: ImmutableOrigin, + /// Restyle snapshot map. + pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>, + /// The current animation timeline value. + pub animation_timeline_value: f64, + /// The set of animations for this document. + pub animations: DocumentAnimationSet, +} + +pub struct LayoutThreadInit { + pub id: PipelineId, + pub url: ServoUrl, + pub is_parent: bool, + pub layout_pair: (Sender<Msg>, Receiver<Msg>), + pub pipeline_port: IpcReceiver<LayoutControlMsg>, + pub background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>, + pub constellation_chan: IpcSender<ConstellationMsg>, + pub script_chan: IpcSender<ConstellationControlMsg>, + pub image_cache: Arc<dyn ImageCache>, + pub paint_time_metrics: PaintTimeMetrics, + pub layout_is_busy: Arc<AtomicBool>, + pub window_size: WindowSizeData, +} + +/// A pending restyle. +#[derive(Debug, MallocSizeOf)] +pub struct PendingRestyle { + /// If this element had a state or attribute change since the last restyle, track + /// the original condition of the element. + pub snapshot: Option<Snapshot>, + + /// Any explicit restyles hints that have been accumulated for this element. + pub hint: RestyleHint, + + /// Any explicit restyles damage that have been accumulated for this element. + pub damage: RestyleDamage, +} + +impl PendingRestyle { + /// Creates a new empty pending restyle. + #[inline] + pub fn new() -> Self { + PendingRestyle { + snapshot: None, + hint: RestyleHint::empty(), + damage: RestyleDamage::empty(), + } + } +} diff --git a/components/shared/script_layout/rpc.rs b/components/shared/script_layout/rpc.rs new file mode 100644 index 00000000000..3778d8f975a --- /dev/null +++ b/components/shared/script_layout/rpc.rs @@ -0,0 +1,75 @@ +/* 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 app_units::Au; +use euclid::default::Rect; +use euclid::Size2D; +use script_traits::UntrustedNodeAddress; +use servo_arc::Arc; +use style::properties::style_structs::Font; +use style_traits::CSSPixel; +use webrender_api::ExternalScrollId; + +/// Synchronous messages that script can send to layout. +/// +/// In general, you should use messages to talk to Layout. Use the RPC interface +/// if and only if the work is +/// +/// 1) read-only with respect to LayoutThreadData, +/// 2) small, +/// 3) and really needs to be fast. +pub trait LayoutRPC { + /// Requests the dimensions of the content box, as in the `getBoundingClientRect()` call. + fn content_box(&self) -> ContentBoxResponse; + /// Requests the dimensions of all the content boxes, as in the `getClientRects()` call. + fn content_boxes(&self) -> ContentBoxesResponse; + /// Requests the geometry of this node. Used by APIs such as `clientTop`. + fn node_geometry(&self) -> NodeGeometryResponse; + /// Requests the scroll geometry of this node. Used by APIs such as `scrollTop`. + fn scrolling_area(&self) -> NodeGeometryResponse; + /// Requests the scroll id of this node. Used by APIs such as `scrollTop` + fn node_scroll_id(&self) -> NodeScrollIdResponse; + /// Query layout for the resolved value of a given CSS property + fn resolved_style(&self) -> ResolvedStyleResponse; + /// Query layout to get the resolved font style for canvas. + fn resolved_font_style(&self) -> Option<Arc<Font>>; + fn offset_parent(&self) -> OffsetParentResponse; + fn text_index(&self) -> TextIndexResponse; + /// Requests the list of nodes from the given point. + fn nodes_from_point_response(&self) -> Vec<UntrustedNodeAddress>; + /// Query layout to get the inner text for a given element. + fn element_inner_text(&self) -> String; + /// Get the dimensions of an iframe's inner window. + fn inner_window_dimensions(&self) -> Option<Size2D<f32, CSSPixel>>; +} + +pub struct ContentBoxResponse(pub Option<Rect<Au>>); + +pub struct ContentBoxesResponse(pub Vec<Rect<Au>>); + +pub struct NodeGeometryResponse { + pub client_rect: Rect<i32>, +} + +pub struct NodeScrollIdResponse(pub ExternalScrollId); + +pub struct ResolvedStyleResponse(pub String); + +#[derive(Clone)] +pub struct OffsetParentResponse { + pub node_address: Option<UntrustedNodeAddress>, + pub rect: Rect<Au>, +} + +impl OffsetParentResponse { + pub fn empty() -> OffsetParentResponse { + OffsetParentResponse { + node_address: None, + rect: Rect::zero(), + } + } +} + +#[derive(Clone)] +pub struct TextIndexResponse(pub Option<usize>); diff --git a/components/shared/script_layout/wrapper_traits.rs b/components/shared/script_layout/wrapper_traits.rs new file mode 100644 index 00000000000..1cc20c5f6ab --- /dev/null +++ b/components/shared/script_layout/wrapper_traits.rs @@ -0,0 +1,497 @@ +/* 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 std::borrow::Cow; +use std::fmt::Debug; +use std::sync::Arc as StdArc; + +use atomic_refcell::AtomicRef; +use gfx_traits::{combine_id_with_fragment_type, ByteIndex, FragmentType}; +use html5ever::{local_name, namespace_url, ns, LocalName, Namespace}; +use msg::constellation_msg::{BrowsingContextId, PipelineId}; +use net_traits::image::base::{Image, ImageMetadata}; +use range::Range; +use servo_arc::Arc; +use servo_url::ServoUrl; +use style::attr::AttrValue; +use style::context::SharedStyleContext; +use style::data::ElementData; +use style::dom::{LayoutIterator, NodeInfo, OpaqueNode, TElement, TNode}; +use style::properties::ComputedValues; +use style::selector_parser::{PseudoElement, PseudoElementCascadeType, SelectorImpl}; +use style::stylist::RuleInclusion; +use webrender_api::ExternalScrollId; + +use crate::{HTMLCanvasData, HTMLMediaData, LayoutNodeType, SVGSVGData, StyleAndOpaqueLayoutData}; + +pub trait LayoutDataTrait: Default + Send + Sync + 'static {} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum PseudoElementType { + Normal, + Before, + After, + DetailsSummary, + DetailsContent, +} + +impl PseudoElementType { + pub fn fragment_type(&self) -> FragmentType { + match *self { + PseudoElementType::Normal => FragmentType::FragmentBody, + PseudoElementType::Before => FragmentType::BeforePseudoContent, + PseudoElementType::After => FragmentType::AfterPseudoContent, + PseudoElementType::DetailsSummary => FragmentType::FragmentBody, + PseudoElementType::DetailsContent => FragmentType::FragmentBody, + } + } + + pub fn is_before(&self) -> bool { + match *self { + PseudoElementType::Before => true, + _ => false, + } + } + + pub fn is_replaced_content(&self) -> bool { + match *self { + PseudoElementType::Before | PseudoElementType::After => true, + _ => false, + } + } + + pub fn style_pseudo_element(&self) -> PseudoElement { + match *self { + PseudoElementType::Normal => { + unreachable!("style_pseudo_element called with PseudoElementType::Normal") + }, + PseudoElementType::Before => PseudoElement::Before, + PseudoElementType::After => PseudoElement::After, + PseudoElementType::DetailsSummary => PseudoElement::DetailsSummary, + PseudoElementType::DetailsContent => PseudoElement::DetailsContent, + } + } +} + +/// Trait to abstract access to layout data across various data structures. +pub trait GetStyleAndOpaqueLayoutData<'dom> { + fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData>; +} + +/// A wrapper so that layout can access only the methods that it should have access to. Layout must +/// only ever see these and must never see instances of `LayoutDom`. +/// FIXME(mrobinson): `Send + Sync` is required here for Layout 2020, but eventually it +/// should stop sending LayoutNodes to other threads and rely on ThreadSafeLayoutNode +/// or some other mechanism to ensure thread safety. +pub trait LayoutNode<'dom>: + Copy + Debug + GetStyleAndOpaqueLayoutData<'dom> + TNode + Send + Sync +{ + type ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode<'dom>; + fn to_threadsafe(&self) -> Self::ConcreteThreadSafeLayoutNode; + + /// Returns the type ID of this node. + fn type_id(&self) -> LayoutNodeType; + + unsafe fn initialize_data(&self); + unsafe fn init_style_and_opaque_layout_data(&self, data: Box<StyleAndOpaqueLayoutData>); + unsafe fn take_style_and_opaque_layout_data(&self) -> Box<StyleAndOpaqueLayoutData>; + + fn rev_children(self) -> LayoutIterator<ReverseChildrenIterator<Self>> { + LayoutIterator(ReverseChildrenIterator { + current: self.last_child(), + }) + } + + fn traverse_preorder(self) -> TreeIterator<Self> { + TreeIterator::new(self) + } + + /// Returns whether the node is connected. + fn is_connected(&self) -> bool; +} + +pub struct ReverseChildrenIterator<ConcreteNode> { + current: Option<ConcreteNode>, +} + +impl<'dom, ConcreteNode> Iterator for ReverseChildrenIterator<ConcreteNode> +where + ConcreteNode: LayoutNode<'dom>, +{ + type Item = ConcreteNode; + fn next(&mut self) -> Option<ConcreteNode> { + let node = self.current; + self.current = node.and_then(|node| node.prev_sibling()); + node + } +} + +pub struct TreeIterator<ConcreteNode> { + stack: Vec<ConcreteNode>, +} + +impl<'dom, ConcreteNode> TreeIterator<ConcreteNode> +where + ConcreteNode: LayoutNode<'dom>, +{ + fn new(root: ConcreteNode) -> TreeIterator<ConcreteNode> { + let mut stack = vec![]; + stack.push(root); + TreeIterator { stack: stack } + } + + pub fn next_skipping_children(&mut self) -> Option<ConcreteNode> { + self.stack.pop() + } +} + +impl<'dom, ConcreteNode> Iterator for TreeIterator<ConcreteNode> +where + ConcreteNode: LayoutNode<'dom>, +{ + type Item = ConcreteNode; + fn next(&mut self) -> Option<ConcreteNode> { + let ret = self.stack.pop(); + ret.map(|node| self.stack.extend(node.rev_children())); + ret + } +} + +/// A thread-safe version of `LayoutNode`, used during flow construction. This type of layout +/// node does not allow any parents or siblings of nodes to be accessed, to avoid races. +pub trait ThreadSafeLayoutNode<'dom>: + Clone + Copy + Debug + GetStyleAndOpaqueLayoutData<'dom> + NodeInfo + PartialEq + Sized +{ + type ConcreteNode: LayoutNode<'dom, ConcreteThreadSafeLayoutNode = Self>; + type ConcreteElement: TElement; + + type ConcreteThreadSafeLayoutElement: ThreadSafeLayoutElement<'dom, ConcreteThreadSafeLayoutNode = Self> + + ::selectors::Element<Impl = SelectorImpl>; + type ChildrenIterator: Iterator<Item = Self> + Sized; + + /// Converts self into an `OpaqueNode`. + fn opaque(&self) -> OpaqueNode; + + /// Returns the type ID of this node. + /// Returns `None` if this is a pseudo-element; otherwise, returns `Some`. + fn type_id(&self) -> Option<LayoutNodeType>; + + /// Returns the style for a text node. This is computed on the fly from the + /// parent style to avoid traversing text nodes in the style system. + /// + /// Note that this does require accessing the parent, which this interface + /// technically forbids. But accessing the parent is only unsafe insofar as + /// it can be used to reach siblings and cousins. A simple immutable borrow + /// of the parent data is fine, since the bottom-up traversal will not process + /// the parent until all the children have been processed. + fn parent_style(&self) -> Arc<ComputedValues>; + + fn get_before_pseudo(&self) -> Option<Self> { + self.as_element() + .and_then(|el| el.get_before_pseudo()) + .map(|el| el.as_node()) + } + + fn get_after_pseudo(&self) -> Option<Self> { + self.as_element() + .and_then(|el| el.get_after_pseudo()) + .map(|el| el.as_node()) + } + + fn get_details_summary_pseudo(&self) -> Option<Self> { + self.as_element() + .and_then(|el| el.get_details_summary_pseudo()) + .map(|el| el.as_node()) + } + + fn get_details_content_pseudo(&self) -> Option<Self> { + self.as_element() + .and_then(|el| el.get_details_content_pseudo()) + .map(|el| el.as_node()) + } + + fn debug_id(self) -> usize; + + /// Returns an iterator over this node's children. + fn children(&self) -> LayoutIterator<Self::ChildrenIterator>; + + /// Returns a ThreadSafeLayoutElement if this is an element, None otherwise. + fn as_element(&self) -> Option<Self::ConcreteThreadSafeLayoutElement>; + + #[inline] + fn get_pseudo_element_type(&self) -> PseudoElementType { + self.as_element() + .map_or(PseudoElementType::Normal, |el| el.get_pseudo_element_type()) + } + + fn get_style_and_opaque_layout_data(self) -> Option<&'dom StyleAndOpaqueLayoutData>; + + fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> { + if let Some(el) = self.as_element() { + el.style(context) + } else { + // Text nodes are not styled during traversal,instead we simply + // return parent style here and do cascading during layout. + debug_assert!(self.is_text_node()); + self.parent_style() + } + } + + fn selected_style(&self) -> Arc<ComputedValues> { + if let Some(el) = self.as_element() { + el.selected_style() + } else { + debug_assert!(self.is_text_node()); + // TODO(stshine): What should the selected style be for text? + self.parent_style() + } + } + + fn is_ignorable_whitespace(&self, context: &SharedStyleContext) -> bool; + + /// Returns true if this node contributes content. This is used in the implementation of + /// `empty_cells` per CSS 2.1 § 17.6.1.1. + fn is_content(&self) -> bool { + self.type_id().is_some() + } + + /// Returns access to the underlying LayoutNode. This is breaks the abstraction + /// barrier of ThreadSafeLayout wrapper layer, and can lead to races if not used + /// carefully. + /// + /// We need this because the implementation of some methods need to access the layout + /// data flags, and we have this annoying trait separation between script and layout :-( + unsafe fn unsafe_get(self) -> Self::ConcreteNode; + + fn node_text_content(self) -> Cow<'dom, str>; + + /// If the insertion point is within this node, returns it. Otherwise, returns `None`. + fn selection(&self) -> Option<Range<ByteIndex>>; + + /// If this is an image element, returns its URL. If this is not an image element, fails. + fn image_url(&self) -> Option<ServoUrl>; + + /// If this is an image element, returns its current-pixel-density. If this is not an image element, fails. + fn image_density(&self) -> Option<f64>; + + /// If this is an image element, returns its image data. Otherwise, returns `None`. + fn image_data(&self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)>; + + fn canvas_data(&self) -> Option<HTMLCanvasData>; + + fn svg_data(&self) -> Option<SVGSVGData>; + + fn media_data(&self) -> Option<HTMLMediaData>; + + /// If this node is an iframe element, returns its browsing context ID. If this node is + /// not an iframe element, fails. Returns None if there is no nested browsing context. + fn iframe_browsing_context_id(&self) -> Option<BrowsingContextId>; + + /// If this node is an iframe element, returns its pipeline ID. If this node is + /// not an iframe element, fails. Returns None if there is no nested browsing context. + fn iframe_pipeline_id(&self) -> Option<PipelineId>; + + fn get_colspan(&self) -> u32; + + fn get_rowspan(&self) -> u32; + + fn fragment_type(&self) -> FragmentType { + self.get_pseudo_element_type().fragment_type() + } + + fn generate_scroll_id(&self, pipeline_id: PipelineId) -> ExternalScrollId { + let id = combine_id_with_fragment_type(self.opaque().id(), self.fragment_type()); + ExternalScrollId(id as u64, pipeline_id.to_webrender()) + } +} + +pub trait ThreadSafeLayoutElement<'dom>: + Clone + + Copy + + Sized + + Debug + + ::selectors::Element<Impl = SelectorImpl> + + GetStyleAndOpaqueLayoutData<'dom> +{ + type ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode< + 'dom, + ConcreteThreadSafeLayoutElement = Self, + >; + + /// This type alias is just a work-around to avoid writing + /// + /// <Self::ConcreteThreadSafeLayoutNode as ThreadSafeLayoutNode>::ConcreteElement + /// + type ConcreteElement: TElement; + + fn as_node(&self) -> Self::ConcreteThreadSafeLayoutNode; + + /// Creates a new `ThreadSafeLayoutElement` for the same `LayoutElement` + /// with a different pseudo-element type. + fn with_pseudo(&self, pseudo: PseudoElementType) -> Self; + + /// Returns the type ID of this node. + /// Returns `None` if this is a pseudo-element; otherwise, returns `Some`. + fn type_id(&self) -> Option<LayoutNodeType>; + + /// Returns access to the underlying TElement. This is breaks the abstraction + /// barrier of ThreadSafeLayout wrapper layer, and can lead to races if not used + /// carefully. + /// + /// We need this so that the functions defined on this trait can call + /// lazily_compute_pseudo_element_style, which operates on TElement. + unsafe fn unsafe_get(self) -> Self::ConcreteElement; + + fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str>; + + fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue>; + + fn style_data(&self) -> AtomicRef<ElementData>; + + fn get_pseudo_element_type(&self) -> PseudoElementType; + + #[inline] + fn get_before_pseudo(&self) -> Option<Self> { + if self + .style_data() + .styles + .pseudos + .get(&PseudoElement::Before) + .is_some() + { + Some(self.with_pseudo(PseudoElementType::Before)) + } else { + None + } + } + + #[inline] + fn get_after_pseudo(&self) -> Option<Self> { + if self + .style_data() + .styles + .pseudos + .get(&PseudoElement::After) + .is_some() + { + Some(self.with_pseudo(PseudoElementType::After)) + } else { + None + } + } + + #[inline] + fn get_details_summary_pseudo(&self) -> Option<Self> { + if self.has_local_name(&local_name!("details")) && self.has_namespace(&ns!(html)) { + Some(self.with_pseudo(PseudoElementType::DetailsSummary)) + } else { + None + } + } + + #[inline] + fn get_details_content_pseudo(&self) -> Option<Self> { + if self.has_local_name(&local_name!("details")) && + self.has_namespace(&ns!(html)) && + self.get_attr(&ns!(), &local_name!("open")).is_some() + { + Some(self.with_pseudo(PseudoElementType::DetailsContent)) + } else { + None + } + } + + /// Returns the style results for the given node. If CSS selector matching + /// has not yet been performed, fails. + /// + /// Unlike the version on TNode, this handles pseudo-elements. + #[inline] + fn style(&self, context: &SharedStyleContext) -> Arc<ComputedValues> { + let data = self.style_data(); + match self.get_pseudo_element_type() { + PseudoElementType::Normal => data.styles.primary().clone(), + other => { + // Precompute non-eagerly-cascaded pseudo-element styles if not + // cached before. + let style_pseudo = other.style_pseudo_element(); + match style_pseudo.cascade_type() { + // Already computed during the cascade. + PseudoElementCascadeType::Eager => self + .style_data() + .styles + .pseudos + .get(&style_pseudo) + .unwrap() + .clone(), + PseudoElementCascadeType::Precomputed => context + .stylist + .precomputed_values_for_pseudo::<Self::ConcreteElement>( + &context.guards, + &style_pseudo, + Some(data.styles.primary()), + ), + PseudoElementCascadeType::Lazy => { + context + .stylist + .lazily_compute_pseudo_element_style( + &context.guards, + unsafe { self.unsafe_get() }, + &style_pseudo, + RuleInclusion::All, + data.styles.primary(), + /* is_probe = */ false, + /* matching_func = */ None, + ) + .unwrap() + }, + } + }, + } + } + + #[inline] + fn selected_style(&self) -> Arc<ComputedValues> { + let data = self.style_data(); + data.styles + .pseudos + .get(&PseudoElement::Selection) + .unwrap_or(data.styles.primary()) + .clone() + } + + /// Returns the already resolved style of the node. + /// + /// This differs from `style(ctx)` in that if the pseudo-element has not yet + /// been computed it would panic. + /// + /// This should be used just for querying layout, or when we know the + /// element style is precomputed, not from general layout itself. + #[inline] + fn resolved_style(&self) -> Arc<ComputedValues> { + let data = self.style_data(); + match self.get_pseudo_element_type() { + PseudoElementType::Normal => data.styles.primary().clone(), + other => data + .styles + .pseudos + .get(&other.style_pseudo_element()) + .unwrap() + .clone(), + } + } + + fn is_shadow_host(&self) -> bool; + + /// Returns whether this node is a body element of an html element root + /// in an HTML element document. + /// + /// Note that this does require accessing the parent, which this interface + /// technically forbids. But accessing the parent is only unsafe insofar as + /// it can be used to reach siblings and cousins. A simple immutable borrow + /// of the parent data is fine, since the bottom-up traversal will not process + /// the parent until all the children have been processed. + fn is_body_element_of_html_element_root(&self) -> bool; +} |