diff --git a/Cargo.lock b/Cargo.lock index 914e577b4a2..e1301f6a7d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2331,7 +2331,25 @@ dependencies = [ name = "layout_2020" version = "0.0.1" dependencies = [ + "app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "euclid 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "gfx 0.0.1", + "gfx_traits 0.0.1", + "ipc-channel 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", + "malloc_size_of 0.0.1", + "msg 0.0.1", + "range 0.0.1", + "rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "script_layout_interface 0.0.1", + "script_traits 0.0.1", + "serde 1.0.88 (registry+https://github.com/rust-lang/crates.io-index)", + "servo_arc 0.1.1", + "servo_url 0.0.1", "style 0.0.1", + "style_traits 0.0.1", + "webrender_api 0.60.0 (git+https://github.com/servo/webrender)", ] [[package]] @@ -2384,20 +2402,45 @@ dependencies = [ name = "layout_thread_2020" version = "0.0.1" dependencies = [ + "app_units 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "crossbeam-channel 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", + "embedder_traits 0.0.1", "euclid 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "gfx 0.0.1", + "gfx_traits 0.0.1", + "histogram 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)", + "html5ever 0.23.0 (registry+https://github.com/rust-lang/crates.io-index)", "ipc-channel 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", "layout_2020 0.0.1", "layout_traits 0.0.1", + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.53 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "malloc_size_of 0.0.1", "metrics 0.0.1", "msg 0.0.1", "net_traits 0.0.1", + "parking_lot 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "profile_traits 0.0.1", + "range 0.0.1", + "rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "script 0.0.1", "script_layout_interface 0.0.1", "script_traits 0.0.1", + "selectors 0.21.0", + "serde_json 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "servo_allocator 0.0.1", + "servo_arc 0.1.1", + "servo_atoms 0.0.1", + "servo_config 0.0.1", "servo_geometry 0.0.1", "servo_url 0.0.1", + "style 0.0.1", + "style_traits 0.0.1", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "webrender_api 0.60.0 (git+https://github.com/servo/webrender)", ] diff --git a/components/layout_2020/Cargo.toml b/components/layout_2020/Cargo.toml index bff14135888..96ca5265f28 100644 --- a/components/layout_2020/Cargo.toml +++ b/components/layout_2020/Cargo.toml @@ -7,9 +7,28 @@ edition = "2018" publish = false [lib] +name = "layout" path = "lib.rs" test = false doctest = false [dependencies] +app_units = "0.7" +euclid = "0.20" +fnv = "1.0" +gfx = {path = "../gfx"} +gfx_traits = {path = "../gfx_traits"} +ipc-channel = "0.11" +libc = "0.2" +malloc_size_of = { path = "../malloc_size_of" } +msg = {path = "../msg"} +range = {path = "../range"} +rayon = "1" +script_layout_interface = {path = "../script_layout_interface"} +script_traits = {path = "../script_traits"} +serde = "1.0" +servo_arc = {path = "../servo_arc"} +servo_url = {path = "../url"} style = {path = "../style", features = ["servo", "servo-layout-2020"]} +style_traits = {path = "../style_traits"} +webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]} diff --git a/components/layout_2020/context.rs b/components/layout_2020/context.rs new file mode 100644 index 00000000000..72398d85a80 --- /dev/null +++ b/components/layout_2020/context.rs @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use msg::constellation_msg::PipelineId; +use style::context::SharedStyleContext; + +pub struct LayoutContext<'a> { + pub id: PipelineId, + pub style_context: SharedStyleContext<'a>, +} + +impl<'a> LayoutContext<'a> { + #[inline(always)] + pub fn shared_context(&self) -> &SharedStyleContext { + &self.style_context + } +} diff --git a/components/layout_2020/data.rs b/components/layout_2020/data.rs new file mode 100644 index 00000000000..c406a1bc4e7 --- /dev/null +++ b/components/layout_2020/data.rs @@ -0,0 +1,18 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use script_layout_interface::StyleData; + +#[repr(C)] +pub struct StyleAndLayoutData { + pub style_data: StyleData, +} + +impl StyleAndLayoutData { + pub fn new() -> Self { + Self { + style_data: StyleData::new(), + } + } +} diff --git a/components/layout_2020/display_list/builder.rs b/components/layout_2020/display_list/builder.rs new file mode 100644 index 00000000000..5cba1e7f7b1 --- /dev/null +++ b/components/layout_2020/display_list/builder.rs @@ -0,0 +1,53 @@ +/* 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/. */ + +//! Builds display lists from flows and fragments. +//! +//! Other browser engines sometimes call this "painting", but it is more accurately called display +//! list building, as the actual painting does not happen here—only deciding *what* we're going to +//! paint. + +use crate::display_list::items::OpaqueNode; +use app_units::Au; +use euclid::default::Point2D; +use fnv::FnvHashMap; +use gfx::text::glyph::ByteIndex; +use gfx::text::TextRun; +use range::Range; +use std::sync::Arc; + +pub struct IndexableTextItem { + /// The placement of the text item on the plane. + pub origin: Point2D, + /// The text run. + pub text_run: Arc, + /// The range of text within the text run. + pub range: Range, + /// The position of the start of the baseline of this text. + pub baseline_origin: Point2D, +} + +#[derive(Default)] +pub struct IndexableText { + inner: FnvHashMap>, +} + +impl IndexableText { + pub fn get(&self, node: OpaqueNode) -> Option<&[IndexableTextItem]> { + self.inner.get(&node).map(|x| x.as_slice()) + } + + // Returns the text index within a node for the point of interest. + pub fn text_index(&self, node: OpaqueNode, point_in_item: Point2D) -> Option { + let item = self.inner.get(&node)?; + // TODO(#20020): access all elements + let point = point_in_item + item[0].origin.to_vector(); + let offset = point - item[0].baseline_origin; + Some( + item[0] + .text_run + .range_index_of_advance(&item[0].range, offset.x), + ) + } +} diff --git a/components/layout_2020/display_list/items.rs b/components/layout_2020/display_list/items.rs new file mode 100644 index 00000000000..68ec3fcc188 --- /dev/null +++ b/components/layout_2020/display_list/items.rs @@ -0,0 +1,24 @@ +/* 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 euclid::Vector2D; +use gfx_traits; +use std::collections::HashMap; +use std::f32; +use webrender_api::units::LayoutPixel; +use webrender_api::ExternalScrollId; + +pub use style::dom::OpaqueNode; + +#[derive(Serialize)] +pub struct DisplayList {} + +impl gfx_traits::DisplayList for DisplayList { + fn is_contentful(&self) -> bool { + false + } +} + +/// The type of the scroll offset list. This is only populated if WebRender is in use. +pub type ScrollOffsetMap = HashMap>; diff --git a/components/layout_2020/display_list/mod.rs b/components/layout_2020/display_list/mod.rs new file mode 100644 index 00000000000..fc9e5359699 --- /dev/null +++ b/components/layout_2020/display_list/mod.rs @@ -0,0 +1,10 @@ +/* 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/. */ + +pub use self::builder::IndexableText; +pub use self::webrender_helpers::WebRenderDisplayListConverter; + +mod builder; +pub mod items; +mod webrender_helpers; diff --git a/components/layout_2020/display_list/webrender_helpers.rs b/components/layout_2020/display_list/webrender_helpers.rs new file mode 100644 index 00000000000..23ca4138e3e --- /dev/null +++ b/components/layout_2020/display_list/webrender_helpers.rs @@ -0,0 +1,26 @@ +/* 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::display_list::items::DisplayList; +use msg::constellation_msg::PipelineId; +use webrender_api::units::LayoutSize; +use webrender_api::{self, DisplayListBuilder}; + +pub trait WebRenderDisplayListConverter { + fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder; +} + +impl WebRenderDisplayListConverter for DisplayList { + fn convert_to_webrender(&mut self, pipeline_id: PipelineId) -> DisplayListBuilder { + let webrender_pipeline = pipeline_id.to_webrender(); + + let builder = DisplayListBuilder::with_capacity( + webrender_pipeline, + LayoutSize::zero(), + 1024 * 1024, // 1 MB of space + ); + + builder + } +} diff --git a/components/layout_2020/flow.rs b/components/layout_2020/flow.rs new file mode 100644 index 00000000000..4bf5e1e899f --- /dev/null +++ b/components/layout_2020/flow.rs @@ -0,0 +1,38 @@ +/* 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::fmt; +use style::selector_parser::RestyleDamage; + +#[allow(unsafe_code)] +pub unsafe trait HasBaseFlow {} + +pub trait GetBaseFlow { + fn base(&self) -> &BaseFlow; + fn mut_base(&mut self) -> &mut BaseFlow; +} + +impl GetBaseFlow for T { + #[inline(always)] + #[allow(unsafe_code)] + fn base(&self) -> &BaseFlow { + let ptr: *const Self = self; + let ptr = ptr as *const BaseFlow; + unsafe { &*ptr } + } + + #[inline(always)] + #[allow(unsafe_code)] + fn mut_base(&mut self) -> &mut BaseFlow { + let ptr: *mut Self = self; + let ptr = ptr as *mut BaseFlow; + unsafe { &mut *ptr } + } +} + +pub trait Flow: HasBaseFlow + fmt::Debug + Sync + Send + 'static {} + +pub struct BaseFlow { + pub restyle_damage: RestyleDamage, +} diff --git a/components/layout_2020/flow_ref.rs b/components/layout_2020/flow_ref.rs new file mode 100644 index 00000000000..f13a562927c --- /dev/null +++ b/components/layout_2020/flow_ref.rs @@ -0,0 +1,30 @@ +/* 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::flow::Flow; +use std::ops::Deref; +use std::sync::Arc; + +#[derive(Clone, Debug)] +pub struct FlowRef(Arc); + +impl Deref for FlowRef { + type Target = dyn Flow; + fn deref(&self) -> &dyn Flow { + self.0.deref() + } +} + +impl FlowRef { + pub fn new(mut r: Arc) -> Self { + assert!(Arc::get_mut(&mut r).is_some()); + FlowRef(r) + } + + #[allow(unsafe_code)] + pub fn deref_mut(this: &mut FlowRef) -> &mut dyn Flow { + let ptr: *const dyn Flow = &*this.0; + unsafe { &mut *(ptr as *mut dyn Flow) } + } +} diff --git a/components/layout_2020/fragment.rs b/components/layout_2020/fragment.rs new file mode 100644 index 00000000000..aa0e7a2bb0e --- /dev/null +++ b/components/layout_2020/fragment.rs @@ -0,0 +1,110 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +//! The `Fragment` type, which represents the leaves of the layout tree. + +use crate::context::LayoutContext; +use crate::display_list::items::OpaqueNode; +use crate::ServoArc; +use app_units::Au; +use euclid::default::Rect; +use script_layout_interface::wrapper_traits::{PseudoElementType, ThreadSafeLayoutNode}; +use serde::ser::{Serialize, SerializeStruct, Serializer}; +use style::logical_geometry::{LogicalMargin, LogicalRect}; +use style::properties::ComputedValues; +use style::selector_parser::RestyleDamage; +use style::servo::restyle_damage::ServoRestyleDamage; + +#[derive(Clone)] +pub struct Fragment { + pub node: OpaqueNode, + pub style: ServoArc, + pub border_box: LogicalRect, + pub border_padding: LogicalMargin, + pub margin: LogicalMargin, + pub specific: SpecificFragmentInfo, + pub restyle_damage: RestyleDamage, + pub pseudo: PseudoElementType, +} + +impl Serialize for Fragment { + fn serialize(&self, serializer: S) -> Result { + let mut serializer = serializer.serialize_struct("fragment", 3)?; + serializer.serialize_field("border_box", &self.border_box)?; + serializer.serialize_field("margin", &self.margin)?; + serializer.end() + } +} + +#[derive(Clone)] +pub enum SpecificFragmentInfo { + Generic, +} + +impl SpecificFragmentInfo { + fn restyle_damage(&self) -> RestyleDamage { + RestyleDamage::empty() + } +} + +impl Fragment { + /// Constructs a new `Fragment` instance. + pub fn new( + node: &N, + specific: SpecificFragmentInfo, + ctx: &LayoutContext, + ) -> Fragment { + let shared_context = ctx.shared_context(); + let style = node.style(shared_context); + let writing_mode = style.writing_mode; + + let mut restyle_damage = RestyleDamage::rebuild_and_reflow(); + restyle_damage.remove(ServoRestyleDamage::RECONSTRUCT_FLOW); + + Fragment { + node: node.opaque(), + style: style, + restyle_damage: restyle_damage, + border_box: LogicalRect::zero(writing_mode), + border_padding: LogicalMargin::zero(writing_mode), + margin: LogicalMargin::zero(writing_mode), + specific: specific, + pseudo: node.get_pseudo_element_type(), + } + } + + pub fn restyle_damage(&self) -> RestyleDamage { + self.restyle_damage | self.specific.restyle_damage() + } + + pub fn contains_node(&self, node_address: OpaqueNode) -> bool { + node_address == self.node + } + + /// Returns the sum of the inline-sizes of all the borders of this fragment. Note that this + /// can be expensive to compute, so if possible use the `border_padding` field instead. + #[inline] + pub fn border_width(&self) -> LogicalMargin { + self.style().logical_border_width() + } + + #[inline(always)] + pub fn style(&self) -> &ComputedValues { + &*self.style + } + + pub fn is_primary_fragment(&self) -> bool { + true + } +} + +/// A top-down fragment border box iteration handler. +pub trait FragmentBorderBoxIterator { + /// The operation to perform. + fn process(&mut self, fragment: &Fragment, level: i32, overflow: &Rect); + + /// Returns true if this fragment must be processed in-order. If this returns false, + /// we skip the operation for this fragment, but continue processing siblings. + fn should_process(&mut self, fragment: &Fragment) -> bool; +} diff --git a/components/layout_2020/lib.rs b/components/layout_2020/lib.rs index daa3e8897c2..98a2d5c618d 100644 --- a/components/layout_2020/lib.rs +++ b/components/layout_2020/lib.rs @@ -1,3 +1,26 @@ /* 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/. */ + +#![deny(unsafe_code)] + +#[macro_use] +extern crate serde; + +pub mod context; +pub mod data; +pub mod display_list; +pub mod flow; +pub mod flow_ref; +mod fragment; +pub mod opaque_node; +pub mod query; +pub mod traversal; +pub mod wrapper; + +// For unit tests: +pub use crate::fragment::Fragment; + +// We can't use servo_arc for everything in layout, because the Flow stuff uses +// weak references. +use servo_arc::Arc as ServoArc; diff --git a/components/layout_2020/opaque_node.rs b/components/layout_2020/opaque_node.rs new file mode 100644 index 00000000000..3d56ef21dae --- /dev/null +++ b/components/layout_2020/opaque_node.rs @@ -0,0 +1,17 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use crate::display_list::items::OpaqueNode; +use libc::c_void; +use script_traits::UntrustedNodeAddress; + +pub trait OpaqueNodeMethods { + fn to_untrusted_node_address(&self) -> UntrustedNodeAddress; +} + +impl OpaqueNodeMethods for OpaqueNode { + fn to_untrusted_node_address(&self) -> UntrustedNodeAddress { + UntrustedNodeAddress(self.0 as *const c_void) + } +} diff --git a/components/layout_2020/query.rs b/components/layout_2020/query.rs new file mode 100644 index 00000000000..4b7ab517ff4 --- /dev/null +++ b/components/layout_2020/query.rs @@ -0,0 +1,1001 @@ +/* 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/. */ + +//! Utilities for querying the layout, as needed by the layout thread. + +use crate::context::LayoutContext; +use crate::display_list::items::{DisplayList, OpaqueNode, ScrollOffsetMap}; +use crate::display_list::IndexableText; +use crate::fragment::{Fragment, FragmentBorderBoxIterator}; +use crate::opaque_node::OpaqueNodeMethods; +use app_units::Au; +use euclid::default::{Point2D, Rect, Size2D, Vector2D}; +use ipc_channel::ipc::IpcSender; +use msg::constellation_msg::PipelineId; +use script_layout_interface::rpc::TextIndexResponse; +use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC}; +use script_layout_interface::rpc::{NodeGeometryResponse, NodeScrollIdResponse}; +use script_layout_interface::rpc::{OffsetParentResponse, ResolvedStyleResponse, StyleResponse}; +use script_layout_interface::wrapper_traits::{ + LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode, +}; +use script_layout_interface::StyleData; +use script_layout_interface::{LayoutElementType, LayoutNodeType}; +use script_traits::LayoutMsg as ConstellationMsg; +use script_traits::UntrustedNodeAddress; +use std::cmp::{max, min}; +use std::sync::{Arc, Mutex}; +use style::computed_values::display::T as Display; +use style::computed_values::position::T as Position; +use style::computed_values::visibility::T as Visibility; +use style::context::{StyleContext, ThreadLocalStyleContext}; +use style::dom::TElement; +use style::logical_geometry::{BlockFlowDirection, InlineBaseDirection, WritingMode}; +use style::properties::{style_structs, LonghandId, PropertyDeclarationId, PropertyId}; +use style::selector_parser::PseudoElement; +use style_traits::ToCss; +use webrender_api::ExternalScrollId; + +/// Mutable data belonging to the LayoutThread. +/// +/// This needs to be protected by a mutex so we can do fast RPCs. +pub struct LayoutThreadData { + /// The channel on which messages can be sent to the constellation. + pub constellation_chan: IpcSender, + + /// The root stacking context. + pub display_list: Option, + + pub indexable_text: IndexableText, + + /// A queued response for the union of the content boxes of a node. + pub content_box_response: Option>, + + /// A queued response for the content boxes of a node. + pub content_boxes_response: Vec>, + + /// A queued response for the client {top, left, width, height} of a node in pixels. + pub client_rect_response: Rect, + + /// A queued response for the scroll id for a given node. + pub scroll_id_response: Option, + + /// A queued response for the scroll {top, left, width, height} of a node in pixels. + pub scroll_area_response: Rect, + + /// A queued response for the resolved style property of an element. + pub resolved_style_response: String, + + /// A queued response for the offset parent/rect of a node. + pub offset_parent_response: OffsetParentResponse, + + /// A queued response for the style of a node. + pub style_response: StyleResponse, + + /// Scroll offsets of scrolling regions. + pub scroll_offsets: ScrollOffsetMap, + + /// Index in a text fragment. We need this do determine the insertion point. + pub text_index_response: TextIndexResponse, + + /// A queued response for the list of nodes at a given point. + pub nodes_from_point_response: Vec, + + /// A queued response for the inner text of a given element. + pub element_inner_text_response: String, +} + +pub struct LayoutRPCImpl(pub Arc>); + +// https://drafts.csswg.org/cssom-view/#overflow-directions +fn overflow_direction(writing_mode: &WritingMode) -> OverflowDirection { + match ( + writing_mode.block_flow_direction(), + writing_mode.inline_base_direction(), + ) { + (BlockFlowDirection::TopToBottom, InlineBaseDirection::LeftToRight) | + (BlockFlowDirection::LeftToRight, InlineBaseDirection::LeftToRight) => { + OverflowDirection::RightAndDown + }, + (BlockFlowDirection::TopToBottom, InlineBaseDirection::RightToLeft) | + (BlockFlowDirection::RightToLeft, InlineBaseDirection::LeftToRight) => { + OverflowDirection::LeftAndDown + }, + (BlockFlowDirection::RightToLeft, InlineBaseDirection::RightToLeft) => { + OverflowDirection::LeftAndUp + }, + (BlockFlowDirection::LeftToRight, InlineBaseDirection::RightToLeft) => { + OverflowDirection::RightAndUp + }, + } +} + +impl LayoutRPC for LayoutRPCImpl { + // The neat thing here is that in order to answer the following two queries we only + // need to compare nodes for equality. Thus we can safely work only with `OpaqueNode`. + fn content_box(&self) -> ContentBoxResponse { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock().unwrap(); + ContentBoxResponse(rw_data.content_box_response) + } + + /// Requests the dimensions of all the content boxes, as in the `getClientRects()` call. + fn content_boxes(&self) -> ContentBoxesResponse { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock().unwrap(); + ContentBoxesResponse(rw_data.content_boxes_response.clone()) + } + + fn nodes_from_point_response(&self) -> Vec { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock().unwrap(); + rw_data.nodes_from_point_response.clone() + } + + fn node_geometry(&self) -> NodeGeometryResponse { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock().unwrap(); + NodeGeometryResponse { + client_rect: rw_data.client_rect_response, + } + } + + fn node_scroll_area(&self) -> NodeGeometryResponse { + NodeGeometryResponse { + client_rect: self.0.lock().unwrap().scroll_area_response, + } + } + + fn node_scroll_id(&self) -> NodeScrollIdResponse { + NodeScrollIdResponse( + self.0 + .lock() + .unwrap() + .scroll_id_response + .expect("scroll id is not correctly fetched"), + ) + } + + /// Retrieves the resolved value for a CSS style property. + fn resolved_style(&self) -> ResolvedStyleResponse { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock().unwrap(); + ResolvedStyleResponse(rw_data.resolved_style_response.clone()) + } + + fn offset_parent(&self) -> OffsetParentResponse { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock().unwrap(); + rw_data.offset_parent_response.clone() + } + + fn style(&self) -> StyleResponse { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock().unwrap(); + rw_data.style_response.clone() + } + + fn text_index(&self) -> TextIndexResponse { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock().unwrap(); + rw_data.text_index_response.clone() + } + + fn element_inner_text(&self) -> String { + let &LayoutRPCImpl(ref rw_data) = self; + let rw_data = rw_data.lock().unwrap(); + rw_data.element_inner_text_response.clone() + } +} + +struct UnioningFragmentBorderBoxIterator { + node_address: OpaqueNode, + rect: Option>, +} + +impl UnioningFragmentBorderBoxIterator { + fn new(node_address: OpaqueNode) -> UnioningFragmentBorderBoxIterator { + UnioningFragmentBorderBoxIterator { + node_address: node_address, + rect: None, + } + } +} + +impl FragmentBorderBoxIterator for UnioningFragmentBorderBoxIterator { + fn process(&mut self, _: &Fragment, _: i32, border_box: &Rect) { + self.rect = match self.rect { + Some(rect) => Some(rect.union(border_box)), + None => Some(*border_box), + }; + } + + fn should_process(&mut self, fragment: &Fragment) -> bool { + fragment.contains_node(self.node_address) + } +} + +struct CollectingFragmentBorderBoxIterator { + node_address: OpaqueNode, + rects: Vec>, +} + +impl CollectingFragmentBorderBoxIterator { + fn new(node_address: OpaqueNode) -> CollectingFragmentBorderBoxIterator { + CollectingFragmentBorderBoxIterator { + node_address: node_address, + rects: Vec::new(), + } + } +} + +impl FragmentBorderBoxIterator for CollectingFragmentBorderBoxIterator { + fn process(&mut self, _: &Fragment, _: i32, border_box: &Rect) { + self.rects.push(*border_box); + } + + fn should_process(&mut self, fragment: &Fragment) -> bool { + fragment.contains_node(self.node_address) + } +} + +enum Side { + Left, + Right, + Bottom, + Top, +} + +enum MarginPadding { + Margin, + Padding, +} + +enum PositionProperty { + Left, + Right, + Top, + Bottom, + Width, + Height, +} + +#[derive(Debug)] +enum OverflowDirection { + RightAndDown, + LeftAndDown, + LeftAndUp, + RightAndUp, +} + +struct PositionRetrievingFragmentBorderBoxIterator { + node_address: OpaqueNode, + result: Option, + position: Point2D, + property: PositionProperty, +} + +impl PositionRetrievingFragmentBorderBoxIterator { + fn new( + node_address: OpaqueNode, + property: PositionProperty, + position: Point2D, + ) -> PositionRetrievingFragmentBorderBoxIterator { + PositionRetrievingFragmentBorderBoxIterator { + node_address: node_address, + position: position, + property: property, + result: None, + } + } +} + +impl FragmentBorderBoxIterator for PositionRetrievingFragmentBorderBoxIterator { + fn process(&mut self, fragment: &Fragment, _: i32, border_box: &Rect) { + let border_padding = fragment + .border_padding + .to_physical(fragment.style.writing_mode); + self.result = Some(match self.property { + PositionProperty::Left => self.position.x, + PositionProperty::Top => self.position.y, + PositionProperty::Width => border_box.size.width - border_padding.horizontal(), + PositionProperty::Height => border_box.size.height - border_padding.vertical(), + // TODO: the following 2 calculations are completely wrong. + // They should return the difference between the parent's and this + // fragment's border boxes. + PositionProperty::Right => border_box.max_x() + self.position.x, + PositionProperty::Bottom => border_box.max_y() + self.position.y, + }); + } + + fn should_process(&mut self, fragment: &Fragment) -> bool { + fragment.contains_node(self.node_address) + } +} + +struct MarginRetrievingFragmentBorderBoxIterator { + node_address: OpaqueNode, + result: Option, + writing_mode: WritingMode, + margin_padding: MarginPadding, + side: Side, +} + +impl MarginRetrievingFragmentBorderBoxIterator { + fn new( + node_address: OpaqueNode, + side: Side, + margin_padding: MarginPadding, + writing_mode: WritingMode, + ) -> MarginRetrievingFragmentBorderBoxIterator { + MarginRetrievingFragmentBorderBoxIterator { + node_address: node_address, + side: side, + margin_padding: margin_padding, + result: None, + writing_mode: writing_mode, + } + } +} + +impl FragmentBorderBoxIterator for MarginRetrievingFragmentBorderBoxIterator { + fn process(&mut self, fragment: &Fragment, _: i32, _: &Rect) { + let rect = match self.margin_padding { + MarginPadding::Margin => &fragment.margin, + MarginPadding::Padding => &fragment.border_padding, + }; + self.result = Some(match self.side { + Side::Left => rect.left(self.writing_mode), + Side::Right => rect.right(self.writing_mode), + Side::Bottom => rect.bottom(self.writing_mode), + Side::Top => rect.top(self.writing_mode), + }); + } + + fn should_process(&mut self, fragment: &Fragment) -> bool { + fragment.contains_node(self.node_address) + } +} + +pub fn process_content_box_request(requested_node: OpaqueNode) -> Option> { + UnioningFragmentBorderBoxIterator::new(requested_node).rect +} + +pub fn process_content_boxes_request(requested_node: OpaqueNode) -> Vec> { + // FIXME(pcwalton): This has not been updated to handle the stacking context relative + // stuff. So the position is wrong in most cases. + CollectingFragmentBorderBoxIterator::new(requested_node).rects +} + +struct FragmentLocatingFragmentIterator { + node_address: OpaqueNode, + client_rect: Rect, +} + +impl FragmentLocatingFragmentIterator { + fn new(node_address: OpaqueNode) -> FragmentLocatingFragmentIterator { + FragmentLocatingFragmentIterator { + node_address: node_address, + client_rect: Rect::zero(), + } + } +} + +struct UnioningFragmentScrollAreaIterator { + node_address: OpaqueNode, + union_rect: Rect, + origin_rect: Rect, + level: Option, + is_child: bool, + overflow_direction: OverflowDirection, +} + +impl UnioningFragmentScrollAreaIterator { + fn new(node_address: OpaqueNode) -> UnioningFragmentScrollAreaIterator { + UnioningFragmentScrollAreaIterator { + node_address: node_address, + union_rect: Rect::zero(), + origin_rect: Rect::zero(), + level: None, + is_child: false, + // FIXME(#20867) + overflow_direction: OverflowDirection::RightAndDown, + } + } +} + +struct NodeOffsetBoxInfo { + offset: Point2D, + rectangle: Rect, +} + +struct ParentBorderBoxInfo { + node_address: OpaqueNode, + origin: Point2D, +} + +struct ParentOffsetBorderBoxIterator { + node_address: OpaqueNode, + has_processed_node: bool, + node_offset_box: Option, + parent_nodes: Vec>, +} + +impl ParentOffsetBorderBoxIterator { + fn new(node_address: OpaqueNode) -> ParentOffsetBorderBoxIterator { + ParentOffsetBorderBoxIterator { + node_address: node_address, + has_processed_node: false, + node_offset_box: None, + parent_nodes: Vec::new(), + } + } +} + +impl FragmentBorderBoxIterator for FragmentLocatingFragmentIterator { + fn process(&mut self, fragment: &Fragment, _: i32, border_box: &Rect) { + let style_structs::Border { + border_top_width: top_width, + border_right_width: right_width, + border_bottom_width: bottom_width, + border_left_width: left_width, + .. + } = *fragment.style.get_border(); + let (left_width, right_width) = (left_width.px(), right_width.px()); + let (top_width, bottom_width) = (top_width.px(), bottom_width.px()); + self.client_rect.origin.y = top_width as i32; + self.client_rect.origin.x = left_width as i32; + self.client_rect.size.width = + (border_box.size.width.to_f32_px() - left_width - right_width) as i32; + self.client_rect.size.height = + (border_box.size.height.to_f32_px() - top_width - bottom_width) as i32; + } + + fn should_process(&mut self, fragment: &Fragment) -> bool { + fragment.node == self.node_address + } +} + +// https://drafts.csswg.org/cssom-view/#scrolling-area +impl FragmentBorderBoxIterator for UnioningFragmentScrollAreaIterator { + fn process(&mut self, fragment: &Fragment, level: i32, border_box: &Rect) { + // In cases in which smaller child elements contain less padding than the parent + // the a union of the two elements padding rectangles could result in an unwanted + // increase in size. To work around this, we store the original elements padding + // rectangle as `origin_rect` and the union of all child elements padding and + // margin rectangles as `union_rect`. + let style_structs::Border { + border_top_width: top_border, + border_right_width: right_border, + border_bottom_width: bottom_border, + border_left_width: left_border, + .. + } = *fragment.style.get_border(); + let (left_border, right_border) = (left_border.px(), right_border.px()); + let (top_border, bottom_border) = (top_border.px(), bottom_border.px()); + let right_padding = (border_box.size.width.to_f32_px() - right_border - left_border) as i32; + let bottom_padding = + (border_box.size.height.to_f32_px() - bottom_border - top_border) as i32; + let top_padding = top_border as i32; + let left_padding = left_border as i32; + + match self.level { + Some(start_level) if level <= start_level => { + self.is_child = false; + }, + Some(_) => { + let padding = Rect::new( + Point2D::new(left_padding, top_padding), + Size2D::new(right_padding, bottom_padding), + ); + let top_margin = fragment.margin.top(fragment.style.writing_mode).to_px(); + let left_margin = fragment.margin.left(fragment.style.writing_mode).to_px(); + let bottom_margin = fragment.margin.bottom(fragment.style.writing_mode).to_px(); + let right_margin = fragment.margin.right(fragment.style.writing_mode).to_px(); + let margin = Rect::new( + Point2D::new(left_margin, top_margin), + Size2D::new(right_margin, bottom_margin), + ); + self.union_rect = self.union_rect.union(&margin).union(&padding); + }, + None => { + self.level = Some(level); + self.is_child = true; + self.overflow_direction = overflow_direction(&fragment.style.writing_mode); + self.origin_rect = Rect::new( + Point2D::new(left_padding, top_padding), + Size2D::new(right_padding, bottom_padding), + ); + }, + }; + } + + fn should_process(&mut self, fragment: &Fragment) -> bool { + fragment.contains_node(self.node_address) || self.is_child + } +} + +// https://drafts.csswg.org/cssom-view/#extensions-to-the-htmlelement-interface +impl FragmentBorderBoxIterator for ParentOffsetBorderBoxIterator { + fn process(&mut self, fragment: &Fragment, level: i32, border_box: &Rect) { + if self.node_offset_box.is_none() { + // We haven't found the node yet, so we're still looking + // for its parent. Remove all nodes at this level or + // higher, as they can't be parents of this node. + self.parent_nodes.truncate(level as usize); + assert_eq!( + self.parent_nodes.len(), + level as usize, + "Skipped at least one level in the flow tree!" + ); + } + + if !fragment.is_primary_fragment() { + // This fragment doesn't correspond to anything worth + // taking measurements from. + + if self.node_offset_box.is_none() { + // If this is the only fragment in the flow, we need to + // do this to avoid failing the above assertion. + self.parent_nodes.push(None); + } + + return; + } + + if fragment.node == self.node_address { + // Found the fragment in the flow tree that matches the + // DOM node being looked for. + + assert!( + self.node_offset_box.is_none(), + "Node was being treated as inline, but it has an associated fragment!" + ); + + self.has_processed_node = true; + self.node_offset_box = Some(NodeOffsetBoxInfo { + offset: border_box.origin, + rectangle: *border_box, + }); + + // offsetParent returns null if the node is fixed. + if fragment.style.get_box().position == Position::Fixed { + self.parent_nodes.clear(); + } + } else if self.node_offset_box.is_none() { + // TODO(gw): Is there a less fragile way of checking whether this + // fragment is the body element, rather than just checking that + // it's at level 1 (below the root node)? + let is_body_element = level == 1; + + let is_valid_parent = match ( + is_body_element, + fragment.style.get_box().position, + &fragment.specific, + ) { + // Spec says it's valid if any of these are true: + // 1) Is the body element + // 2) Is static position *and* is a table or table cell + // 3) Is not static position + (true, _, _) | + (false, Position::Absolute, _) | + (false, Position::Relative, _) | + (false, Position::Fixed, _) => true, + + // Otherwise, it's not a valid parent + (false, Position::Static, _) => false, + }; + + let parent_info = if is_valid_parent { + let border_width = fragment + .border_width() + .to_physical(fragment.style.writing_mode); + + Some(ParentBorderBoxInfo { + node_address: fragment.node, + origin: border_box.origin + Vector2D::new(border_width.left, border_width.top), + }) + } else { + None + }; + + self.parent_nodes.push(parent_info); + } + } + + fn should_process(&mut self, _: &Fragment) -> bool { + !self.has_processed_node + } +} + +pub fn process_node_geometry_request(requested_node: OpaqueNode) -> Rect { + FragmentLocatingFragmentIterator::new(requested_node).client_rect +} + +pub fn process_node_scroll_id_request( + id: PipelineId, + requested_node: N, +) -> ExternalScrollId { + let layout_node = requested_node.to_threadsafe(); + layout_node.generate_scroll_id(id) +} + +/// https://drafts.csswg.org/cssom-view/#scrolling-area +pub fn process_node_scroll_area_request(requested_node: OpaqueNode) -> Rect { + let iterator = UnioningFragmentScrollAreaIterator::new(requested_node); + match iterator.overflow_direction { + OverflowDirection::RightAndDown => { + let right = max( + iterator.union_rect.size.width, + iterator.origin_rect.size.width, + ); + let bottom = max( + iterator.union_rect.size.height, + iterator.origin_rect.size.height, + ); + Rect::new(iterator.origin_rect.origin, Size2D::new(right, bottom)) + }, + OverflowDirection::LeftAndDown => { + let bottom = max( + iterator.union_rect.size.height, + iterator.origin_rect.size.height, + ); + let left = min(iterator.union_rect.origin.x, iterator.origin_rect.origin.x); + Rect::new( + Point2D::new(left, iterator.origin_rect.origin.y), + Size2D::new(iterator.origin_rect.size.width, bottom), + ) + }, + OverflowDirection::LeftAndUp => { + let top = min(iterator.union_rect.origin.y, iterator.origin_rect.origin.y); + let left = min(iterator.union_rect.origin.x, iterator.origin_rect.origin.x); + Rect::new(Point2D::new(left, top), iterator.origin_rect.size) + }, + OverflowDirection::RightAndUp => { + let top = min(iterator.union_rect.origin.y, iterator.origin_rect.origin.y); + let right = max( + iterator.union_rect.size.width, + iterator.origin_rect.size.width, + ); + Rect::new( + Point2D::new(iterator.origin_rect.origin.x, top), + Size2D::new(right, iterator.origin_rect.size.height), + ) + }, + } +} + +/// Return the resolved value of property for a given (pseudo)element. +/// +pub fn process_resolved_style_request<'a, N>( + context: &LayoutContext, + node: N, + pseudo: &Option, + property: &PropertyId, +) -> String +where + N: LayoutNode, +{ + use style::stylist::RuleInclusion; + use style::traversal::resolve_style; + + let element = node.as_element().unwrap(); + + // We call process_resolved_style_request after performing a whole-document + // traversal, so in the common case, the element is styled. + if element.get_data().is_some() { + return process_resolved_style_request_internal(node, pseudo, property); + } + + // In a display: none subtree. No pseudo-element exists. + if pseudo.is_some() { + return String::new(); + } + + let mut tlc = ThreadLocalStyleContext::new(&context.style_context); + let mut context = StyleContext { + shared: &context.style_context, + thread_local: &mut tlc, + }; + + let styles = resolve_style(&mut context, element, RuleInclusion::All, pseudo.as_ref()); + let style = styles.primary(); + let longhand_id = match *property { + PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id, + // Firefox returns blank strings for the computed value of shorthands, + // so this should be web-compatible. + PropertyId::ShorthandAlias(..) | PropertyId::Shorthand(_) => return String::new(), + PropertyId::Custom(ref name) => { + return style.computed_value_to_string(PropertyDeclarationId::Custom(name)); + }, + }; + + // No need to care about used values here, since we're on a display: none + // subtree, use the resolved value. + style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)) +} + +/// The primary resolution logic, which assumes that the element is styled. +fn process_resolved_style_request_internal<'a, N>( + requested_node: N, + pseudo: &Option, + property: &PropertyId, +) -> String +where + N: LayoutNode, +{ + let layout_el = requested_node.to_threadsafe().as_element().unwrap(); + let layout_el = match *pseudo { + Some(PseudoElement::Before) => layout_el.get_before_pseudo(), + Some(PseudoElement::After) => layout_el.get_after_pseudo(), + Some(PseudoElement::DetailsSummary) | + Some(PseudoElement::DetailsContent) | + Some(PseudoElement::Selection) => None, + // FIXME(emilio): What about the other pseudos? Probably they shouldn't + // just return the element's style! + _ => Some(layout_el), + }; + + let layout_el = match layout_el { + None => { + // The pseudo doesn't exist, return nothing. Chrome seems to query + // the element itself in this case, Firefox uses the resolved value. + // https://www.w3.org/Bugs/Public/show_bug.cgi?id=29006 + return String::new(); + }, + Some(layout_el) => layout_el, + }; + + let style = &*layout_el.resolved_style(); + let longhand_id = match *property { + PropertyId::LonghandAlias(id, _) | PropertyId::Longhand(id) => id, + // Firefox returns blank strings for the computed value of shorthands, + // so this should be web-compatible. + PropertyId::ShorthandAlias(..) | PropertyId::Shorthand(_) => return String::new(), + PropertyId::Custom(ref name) => { + return style.computed_value_to_string(PropertyDeclarationId::Custom(name)); + }, + }; + + let positioned = match style.get_box().position { + Position::Relative | Position::Fixed | Position::Absolute => true, + _ => false, + }; + + //TODO: determine whether requested property applies to the element. + // eg. width does not apply to non-replaced inline elements. + // Existing browsers disagree about when left/top/right/bottom apply + // (Chrome seems to think they never apply and always returns resolved values). + // There are probably other quirks. + let applies = true; + + fn used_value_for_position_property( + _layout_el: ::ConcreteThreadSafeLayoutElement, + requested_node: N, + longhand_id: LonghandId, + ) -> String { + let position = Point2D::zero(); + let property = match longhand_id { + LonghandId::Bottom => PositionProperty::Bottom, + LonghandId::Top => PositionProperty::Top, + LonghandId::Left => PositionProperty::Left, + LonghandId::Right => PositionProperty::Right, + LonghandId::Width => PositionProperty::Width, + LonghandId::Height => PositionProperty::Height, + _ => unreachable!(), + }; + let iterator = PositionRetrievingFragmentBorderBoxIterator::new( + requested_node.opaque(), + property, + position, + ); + iterator + .result + .map(|r| r.to_css_string()) + .unwrap_or(String::new()) + } + + // TODO: we will return neither the computed nor used value for margin and padding. + match longhand_id { + LonghandId::MarginBottom | + LonghandId::MarginTop | + LonghandId::MarginLeft | + LonghandId::MarginRight | + LonghandId::PaddingBottom | + LonghandId::PaddingTop | + LonghandId::PaddingLeft | + LonghandId::PaddingRight + if applies && style.get_box().display != Display::None => + { + let (margin_padding, side) = match longhand_id { + LonghandId::MarginBottom => (MarginPadding::Margin, Side::Bottom), + LonghandId::MarginTop => (MarginPadding::Margin, Side::Top), + LonghandId::MarginLeft => (MarginPadding::Margin, Side::Left), + LonghandId::MarginRight => (MarginPadding::Margin, Side::Right), + LonghandId::PaddingBottom => (MarginPadding::Padding, Side::Bottom), + LonghandId::PaddingTop => (MarginPadding::Padding, Side::Top), + LonghandId::PaddingLeft => (MarginPadding::Padding, Side::Left), + LonghandId::PaddingRight => (MarginPadding::Padding, Side::Right), + _ => unreachable!(), + }; + let iterator = MarginRetrievingFragmentBorderBoxIterator::new( + requested_node.opaque(), + side, + margin_padding, + style.writing_mode, + ); + iterator + .result + .map(|r| r.to_css_string()) + .unwrap_or(String::new()) + } + + LonghandId::Bottom | LonghandId::Top | LonghandId::Right | LonghandId::Left + if applies && positioned && style.get_box().display != Display::None => + { + used_value_for_position_property(layout_el, requested_node, longhand_id) + }, + LonghandId::Width | LonghandId::Height + if applies && style.get_box().display != Display::None => + { + used_value_for_position_property(layout_el, requested_node, longhand_id) + }, + // FIXME: implement used value computation for line-height + _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)), + } +} + +pub fn process_offset_parent_query(requested_node: OpaqueNode) -> OffsetParentResponse { + let iterator = ParentOffsetBorderBoxIterator::new(requested_node); + + let node_offset_box = iterator.node_offset_box; + let parent_info = iterator + .parent_nodes + .into_iter() + .rev() + .filter_map(|info| info) + .next(); + match (node_offset_box, parent_info) { + (Some(node_offset_box), Some(parent_info)) => { + let origin = node_offset_box.offset - parent_info.origin.to_vector(); + let size = node_offset_box.rectangle.size; + OffsetParentResponse { + node_address: Some(parent_info.node_address.to_untrusted_node_address()), + rect: Rect::new(origin, size), + } + }, + _ => OffsetParentResponse::empty(), + } +} + +pub fn process_style_query(requested_node: N) -> StyleResponse { + let element = requested_node.as_element().unwrap(); + let data = element.borrow_data(); + + StyleResponse(data.map(|d| d.styles.primary().clone())) +} + +enum InnerTextItem { + Text(String), + RequiredLineBreakCount(u32), +} + +// https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute +pub fn process_element_inner_text_query( + node: N, + indexable_text: &IndexableText, +) -> String { + // Step 1. + let mut results = Vec::new(); + // Step 2. + inner_text_collection_steps(node, indexable_text, &mut results); + let mut max_req_line_break_count = 0; + let mut inner_text = Vec::new(); + for item in results { + match item { + InnerTextItem::Text(s) => { + if max_req_line_break_count > 0 { + // Step 5. + for _ in 0..max_req_line_break_count { + inner_text.push("\u{000A}".to_owned()); + } + max_req_line_break_count = 0; + } + // Step 3. + if !s.is_empty() { + inner_text.push(s.to_owned()); + } + }, + InnerTextItem::RequiredLineBreakCount(count) => { + // Step 4. + if inner_text.len() == 0 { + // Remove required line break count at the start. + continue; + } + // Store the count if it's the max of this run, + // but it may be ignored if no text item is found afterwards, + // which means that these are consecutive line breaks at the end. + if count > max_req_line_break_count { + max_req_line_break_count = count; + } + }, + } + } + inner_text.into_iter().collect() +} + +// https://html.spec.whatwg.org/multipage/#inner-text-collection-steps +#[allow(unsafe_code)] +fn inner_text_collection_steps( + node: N, + indexable_text: &IndexableText, + results: &mut Vec, +) { + let mut items = Vec::new(); + for child in node.traverse_preorder() { + let node = match child.type_id() { + LayoutNodeType::Text => child.parent_node().unwrap(), + _ => child, + }; + + let element_data = unsafe { + node.get_style_and_layout_data() + .map(|d| &(*(d.ptr.as_ptr() as *mut StyleData)).element_data) + }; + + if element_data.is_none() { + continue; + } + + let style = match element_data.unwrap().borrow().styles.get_primary() { + None => continue, + Some(style) => style.clone(), + }; + + // Step 2. + if style.get_inherited_box().visibility != Visibility::Visible { + continue; + } + + // Step 3. + let display = style.get_box().display; + if !child.is_connected() || display == Display::None { + continue; + } + + match child.type_id() { + LayoutNodeType::Text => { + // Step 4. + if let Some(text_content) = indexable_text.get(child.opaque()) { + for content in text_content { + items.push(InnerTextItem::Text(content.text_run.text.to_string())); + } + } + }, + LayoutNodeType::Element(LayoutElementType::HTMLBRElement) => { + // Step 5. + items.push(InnerTextItem::Text(String::from( + "\u{000A}", /* line feed */ + ))); + }, + LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement) => { + // Step 8. + items.insert(0, InnerTextItem::RequiredLineBreakCount(2)); + items.push(InnerTextItem::RequiredLineBreakCount(2)); + }, + _ => {}, + } + + match display { + Display::Block => { + // Step 9. + items.insert(0, InnerTextItem::RequiredLineBreakCount(1)); + items.push(InnerTextItem::RequiredLineBreakCount(1)); + }, + _ => {}, + } + } + + results.append(&mut items); +} diff --git a/components/layout_2020/traversal.rs b/components/layout_2020/traversal.rs new file mode 100644 index 00000000000..5aefdc6e939 --- /dev/null +++ b/components/layout_2020/traversal.rs @@ -0,0 +1,68 @@ +/* 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::context::LayoutContext; +use crate::wrapper::GetRawData; +use script_layout_interface::wrapper_traits::LayoutNode; +use style::context::{SharedStyleContext, StyleContext}; +use style::data::ElementData; +use style::dom::{NodeInfo, TElement, TNode}; +use style::traversal::PerLevelTraversalData; +use style::traversal::{recalc_style_at, DomTraversal}; + +pub struct RecalcStyleAndConstructFlows<'a> { + context: LayoutContext<'a>, +} + +impl<'a> RecalcStyleAndConstructFlows<'a> { + pub fn new(context: LayoutContext<'a>) -> Self { + RecalcStyleAndConstructFlows { context: context } + } + + pub fn destroy(self) -> LayoutContext<'a> { + self.context + } +} + +#[allow(unsafe_code)] +impl<'a, E> DomTraversal for RecalcStyleAndConstructFlows<'a> +where + E: TElement, + E::ConcreteNode: LayoutNode, + E::FontMetricsProvider: Send, +{ + fn process_preorder( + &self, + traversal_data: &PerLevelTraversalData, + context: &mut StyleContext, + node: E::ConcreteNode, + note_child: F, + ) where + F: FnMut(E::ConcreteNode), + { + unsafe { node.initialize_data() }; + + if !node.is_text_node() { + let el = node.as_element().unwrap(); + let mut data = el.mutate_data().unwrap(); + recalc_style_at(self, traversal_data, context, el, &mut data, note_child); + } + } + + fn process_postorder(&self, _style_context: &mut StyleContext, node: E::ConcreteNode) { + if let Some(el) = node.as_element() { + unsafe { + el.unset_dirty_descendants(); + } + } + } + + fn text_node_needs_traversal(node: E::ConcreteNode, parent_data: &ElementData) -> bool { + node.get_raw_data().is_none() || !parent_data.damage.is_empty() + } + + fn shared_context(&self) -> &SharedStyleContext { + &self.context.style_context + } +} diff --git a/components/layout_2020/wrapper.rs b/components/layout_2020/wrapper.rs new file mode 100644 index 00000000000..02a0f8ceeee --- /dev/null +++ b/components/layout_2020/wrapper.rs @@ -0,0 +1,21 @@ +/* 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 crate::data::StyleAndLayoutData; +use script_layout_interface::wrapper_traits::GetLayoutData; + +pub trait GetRawData { + fn get_raw_data(&self) -> Option<&StyleAndLayoutData>; +} + +impl GetRawData for T { + fn get_raw_data(&self) -> Option<&StyleAndLayoutData> { + self.get_style_and_layout_data().map(|opaque| { + let container = opaque.ptr.as_ptr() as *mut StyleAndLayoutData; + unsafe { &*container } + }) + } +} diff --git a/components/layout_thread_2020/Cargo.toml b/components/layout_thread_2020/Cargo.toml index 8711cec2638..3cc6f57136e 100644 --- a/components/layout_thread_2020/Cargo.toml +++ b/components/layout_thread_2020/Cargo.toml @@ -11,18 +11,43 @@ name = "layout_thread" path = "lib.rs" [dependencies] +app_units = "0.7" +atomic_refcell = "0.1" crossbeam-channel = "0.3" +embedder_traits = {path = "../embedder_traits"} euclid = "0.20" +fnv = "1.0" +fxhash = "0.2" gfx = {path = "../gfx"} +gfx_traits = {path = "../gfx_traits"} +histogram = "0.6.8" +html5ever = "0.23" ipc-channel = "0.11" layout = {path = "../layout_2020", package = "layout_2020"} layout_traits = {path = "../layout_traits"} +lazy_static = "1" +libc = "0.2" +log = "0.4" +time = "0.1.17" +malloc_size_of = { path = "../malloc_size_of" } metrics = {path = "../metrics"} msg = {path = "../msg"} net_traits = {path = "../net_traits"} +parking_lot = {version = "0.8", features = ["nightly"]} profile_traits = {path = "../profile_traits"} +range = {path = "../range"} +rayon = "1" +script = {path = "../script"} script_layout_interface = {path = "../script_layout_interface"} script_traits = {path = "../script_traits"} -servo_url = {path = "../url"} +selectors = { path = "../selectors" } +serde_json = "1.0" +servo_allocator = {path = "../allocator"} +servo_arc = {path = "../servo_arc"} +servo_atoms = {path = "../atoms"} +servo_config = {path = "../config"} servo_geometry = {path = "../geometry"} +servo_url = {path = "../url"} +style = {path = "../style"} +style_traits = {path = "../style_traits"} webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]} diff --git a/components/layout_thread_2020/dom_wrapper.rs b/components/layout_thread_2020/dom_wrapper.rs new file mode 100644 index 00000000000..5d815517532 --- /dev/null +++ b/components/layout_thread_2020/dom_wrapper.rs @@ -0,0 +1,1550 @@ +/* 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::data::StyleAndLayoutData; +use layout::wrapper::GetRawData; +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::PendingRestyle; +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, + RawLayoutElementHelpers, +}; +use script_layout_interface::wrapper_traits::{ + DangerousThreadSafeLayoutNode, GetLayoutData, LayoutNode, +}; +use script_layout_interface::wrapper_traits::{ + PseudoElementType, ThreadSafeLayoutElement, ThreadSafeLayoutNode, +}; +use script_layout_interface::{ + HTMLCanvasData, HTMLMediaData, LayoutNodeType, OpaqueStyleAndLayoutData, +}; +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::fmt; +use std::fmt::Debug; +use std::hash::{Hash, Hasher}; +use std::marker::PhantomData; +use std::ptr::NonNull; +use std::sync::atomic::Ordering; +use std::sync::Arc as StdArc; +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::CaseSensitivityExt; + +pub unsafe fn drop_style_and_layout_data(data: OpaqueStyleAndLayoutData) { + let ptr = data.ptr.as_ptr() as *mut StyleData; + let non_opaque: *mut StyleAndLayoutData = ptr as *mut _; + let _ = Box::from_raw(non_opaque); +} + +#[derive(Clone, Copy)] +pub struct ServoLayoutNode<'a> { + /// The wrapped node. + node: LayoutDom, + + /// Being chained to a PhantomData prevents `LayoutNode`s from escaping. + chain: PhantomData<&'a ()>, +} + +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) -> ServoLayoutNode<'ln> { + ServoLayoutNode { + node: n, + chain: PhantomData, + } + } + + pub unsafe fn new(address: &TrustedNodeAddress) -> ServoLayoutNode { + ServoLayoutNode::from_layout_js(LayoutDom::from_trusted_node_address(*address)) + } + + /// Creates a new layout node with the same lifetime as this layout node. + pub unsafe fn new_with_this_lifetime(&self, node: &LayoutDom) -> ServoLayoutNode<'ln> { + ServoLayoutNode { + node: *node, + chain: self.chain, + } + } + + fn script_type_id(&self) -> NodeTypeId { + unsafe { self.node.type_id_for_layout() } + } +} + +impl<'ln> NodeInfo for ServoLayoutNode<'ln> { + fn is_element(&self) -> bool { + unsafe { 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<'a> { + /// The wrapped shadow root. + shadow_root: LayoutDom, + + /// Being chained to a PhantomData prevents `ShadowRoot`s from escaping. + chain: PhantomData<&'a ()>, +} + +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(unsafe { self.shadow_root.get_host_for_layout() }) + } + + fn style_data<'a>(&self) -> Option<&'a CascadeData> + where + Self: 'a, + { + Some(unsafe { + &self + .shadow_root + .get_style_data_for_layout::() + .data + }) + } +} + +impl<'lr> ServoShadowRoot<'lr> { + fn from_layout_js(shadow_root: LayoutDom) -> ServoShadowRoot<'lr> { + ServoShadowRoot { + shadow_root, + chain: PhantomData, + } + } + + pub unsafe fn flush_stylesheets( + &self, + device: &Device, + quirks_mode: QuirksMode, + guard: &SharedRwLockReadGuard, + ) { + self.shadow_root + .flush_stylesheets::(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 { + unsafe { + self.node + .composed_parent_node_ref() + .map(|node| self.new_with_this_lifetime(&node)) + } + } + + fn first_child(&self) -> Option { + unsafe { + self.node + .first_child_ref() + .map(|node| self.new_with_this_lifetime(&node)) + } + } + + fn last_child(&self) -> Option { + unsafe { + self.node + .last_child_ref() + .map(|node| self.new_with_this_lifetime(&node)) + } + } + + fn prev_sibling(&self) -> Option { + unsafe { + self.node + .prev_sibling_ref() + .map(|node| self.new_with_this_lifetime(&node)) + } + } + + fn next_sibling(&self) -> Option { + unsafe { + self.node + .next_sibling_ref() + .map(|node| self.new_with_this_lifetime(&node)) + } + } + + fn owner_doc(&self) -> Self::ConcreteDocument { + ServoLayoutDocument::from_layout_js(unsafe { 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 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_raw_data().is_none() { + let ptr: *mut StyleAndLayoutData = Box::into_raw(Box::new(StyleAndLayoutData::new())); + let opaque = OpaqueStyleAndLayoutData { + ptr: NonNull::new_unchecked(ptr as *mut StyleData), + }; + self.init_style_and_layout_data(opaque); + }; + } + + unsafe fn init_style_and_layout_data(&self, data: OpaqueStyleAndLayoutData) { + self.get_jsmanaged().init_style_and_layout_data(data); + } + + unsafe fn take_style_and_layout_data(&self) -> OpaqueStyleAndLayoutData { + self.get_jsmanaged().take_style_and_layout_data() + } + + fn is_connected(&self) -> bool { + unsafe { self.node.get_flag(NodeFlags::IS_CONNECTED) } + } +} + +impl<'ln> GetLayoutData for ServoLayoutNode<'ln> { + fn get_style_and_layout_data(&self) -> Option { + unsafe { self.get_jsmanaged().get_style_and_layout_data() } + } +} + +impl<'le> GetLayoutData for ServoLayoutElement<'le> { + fn get_style_and_layout_data(&self) -> Option { + self.as_node().get_style_and_layout_data() + } +} + +impl<'ln> GetLayoutData for ServoThreadSafeLayoutNode<'ln> { + fn get_style_and_layout_data(&self) -> Option { + self.node.get_style_and_layout_data() + } +} + +impl<'le> GetLayoutData for ServoThreadSafeLayoutElement<'le> { + fn get_style_and_layout_data(&self) -> Option { + self.element.as_node().get_style_and_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 { + &self.node + } +} + +// A wrapper around documents that ensures ayout can only ever access safe properties. +#[derive(Clone, Copy)] +pub struct ServoLayoutDocument<'ld> { + document: LayoutDom, + chain: PhantomData<&'ld ()>, +} + +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 { + unsafe { self.document.quirks_mode() } + } + + fn is_html_document(&self) -> bool { + unsafe { self.document.is_html_document_for_layout() } + } +} + +impl<'ld> ServoLayoutDocument<'ld> { + pub fn root_element(&self) -> Option> { + self.as_node() + .dom_children() + .flat_map(|n| n.as_element()) + .next() + } + + pub fn drain_pending_restyles(&self) -> Vec<(ServoLayoutElement<'ld>, PendingRestyle)> { + let elements = unsafe { self.document.drain_pending_restyles() }; + elements + .into_iter() + .map(|(el, snapshot)| (ServoLayoutElement::from_layout_js(el), snapshot)) + .collect() + } + + 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 { + unsafe { 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) -> ServoLayoutDocument<'ld> { + ServoLayoutDocument { + document: doc, + chain: PhantomData, + } + } +} + +/// A wrapper around elements that ensures layout can only ever access safe properties. +#[derive(Clone, Copy)] +pub struct ServoLayoutElement<'le> { + element: LayoutDom, + chain: PhantomData<&'le ()>, +} + +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<'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 { + unsafe { 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 style_attribute(&self) -> Option>> { + unsafe { + (*self.element.style_attribute()) + .as_ref() + .map(|x| x.borrow_arc()) + } + } + + fn state(&self) -> ElementState { + self.element.get_state_for_layout() + } + + #[inline] + fn has_attr(&self, namespace: &Namespace, attr: &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(&Atom), + { + unsafe { + if let Some(ref classes) = self.element.get_classes_for_layout() { + for class in *classes { + callback(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_raw_data().is_some() { + drop_style_and_layout_data(self.as_node().take_style_and_layout_data()); + } + } + + unsafe fn ensure_data(&self) -> AtomicRefMut { + self.as_node().initialize_data(); + self.mutate_data().unwrap() + } + + fn get_data(&self) -> Option<&AtomicRefCell> { + unsafe { + self.get_style_and_layout_data() + .map(|d| &(*(d.ptr.as_ptr() as *mut StyleData)).element_data) + } + } + + 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) -> bool { + // We use this function not only for Gecko but also for Servo to know if this element has + // animations, so we maybe try to get the important rules of this element. This is used for + // off-main thread animations, but we don't support it on Servo, so return false directly. + false + } + + fn has_css_animations(&self) -> bool { + unreachable!("this should be only called on gecko"); + } + + fn has_css_transitions(&self) -> bool { + unreachable!("this should be only called on gecko"); + } + + #[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| String::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) => String::new(), + None => self.element.get_lang_for_layout(), + }; + extended_filtering(&element_lang, &*value) + } + + fn is_html_document_body_element(&self) -> bool { + // This is only used for the "tables inherit from body" quirk, which we + // don't implement. + // + // FIXME(emilio): We should be able to give the right answer though! + false + } + + fn synthesize_presentational_hints_for_legacy_attributes( + &self, + _visited_handling: VisitedHandlingMode, + hints: &mut V, + ) where + V: Push, + { + unsafe { + self.element + .synthesize_presentational_hints_for_legacy_attributes(hints); + } + } + + /// The shadow root this element is a host of. + fn shadow_root(&self) -> Option> { + unsafe { + 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> { + unsafe { + 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) -> ServoLayoutElement<'le> { + ServoLayoutElement { + element: el, + chain: PhantomData, + } + } + + #[inline] + fn get_attr_enum(&self, namespace: &Namespace, name: &LocalName) -> Option<&AttrValue> { + unsafe { (*self.element.unsafe_get()).get_attr_for_layout(namespace, name) } + } + + #[inline] + fn get_attr(&self, namespace: &Namespace, name: &LocalName) -> Option<&str> { + unsafe { (*self.element.unsafe_get()).get_attr_val_for_layout(namespace, name) } + } + + fn get_style_data(&self) -> Option<&StyleData> { + unsafe { + self.get_style_and_layout_data() + .map(|d| &*(d.ptr.as_ptr() as *mut StyleData)) + } + } + + 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); + } + + pub unsafe fn note_dirty_descendant(&self) { + use selectors::Element; + + let mut current = Some(*self); + while let Some(el) = current { + // FIXME(bholley): Ideally we'd have the invariant that any element + // with has_dirty_descendants also has the bit set on all its + // ancestors. However, there are currently some corner-cases where + // we get that wrong. I have in-flight patches to fix all this + // stuff up, so we just always propagate this bit for now. + el.set_dirty_descendants(); + current = el.parent_element(); + } + } +} + +fn as_element<'le>(node: LayoutDom) -> 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> { + unsafe { + 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<&Namespace>, + local_name: &LocalName, + operation: &AttrSelectorOperation<&String>, + ) -> 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 = + unsafe { (*self.element.unsafe_get()).get_attr_vals_for_layout(local_name) }; + values.iter().any(|value| value.eval_selector(operation)) + }, + } + } + + fn is_root(&self) -> bool { + match self.as_node().parent_node() { + None => false, + Some(node) => match node.script_type_id() { + NodeTypeId::Document(_) => true, + _ => false, + }, + } + } + + 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)) => unsafe { + 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 => unsafe { + match (*self.element.unsafe_get()) + .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::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 { + unsafe { + 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.unsafe_get()) + .get_attr_val_for_layout(&ns!(), &local_name!("href")) + .is_some(), + _ => false, + } + } + } + + #[inline] + fn has_id(&self, id: &Atom, 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: &Atom) -> bool { + false + } + + #[inline] + fn has_class(&self, name: &Atom, case_sensitivity: CaseSensitivity) -> bool { + unsafe { self.element.has_class_for_layout(name, case_sensitivity) } + } + + fn is_html_slot_element(&self) -> bool { + unsafe { self.element.is_html_element() && self.local_name() == &local_name!("slot") } + } + + fn is_html_element_in_html_document(&self) -> bool { + unsafe { + if !self.element.is_html_element() { + return false; + } + } + + 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 for ServoThreadSafeLayoutNode<'ln> { + unsafe fn dangerous_first_child(&self) -> Option { + self.get_jsmanaged() + .first_child_ref() + .map(|node| self.new_with_this_lifetime(&node)) + } + unsafe fn dangerous_next_sibling(&self) -> Option { + self.get_jsmanaged() + .next_sibling_ref() + .map(|node| self.new_with_this_lifetime(&node)) + } +} + +impl<'ln> ServoThreadSafeLayoutNode<'ln> { + /// Creates a new layout node with the same lifetime as this layout node. + pub unsafe fn new_with_this_lifetime( + &self, + node: &LayoutDom, + ) -> ServoThreadSafeLayoutNode<'ln> { + ServoThreadSafeLayoutNode { + node: self.node.new_with_this_lifetime(node), + pseudo: PseudoElementType::Normal, + } + } + + /// Creates a new `ServoThreadSafeLayoutNode` from the given `ServoLayoutNode`. + pub fn new<'a>(node: &ServoLayoutNode<'a>) -> ServoThreadSafeLayoutNode<'a> { + 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 { + 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 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.get_data().unwrap().borrow(); + 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_layout_data(&self) -> Option { + self.node.get_style_and_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) -> String { + let this = unsafe { self.get_jsmanaged() }; + return this.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 ThreadSafeLayoutNodeChildrenIterator +where + ConcreteNode: DangerousThreadSafeLayoutNode, +{ + 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 Iterator for ThreadSafeLayoutNodeChildrenIterator +where + ConcreteNode: DangerousThreadSafeLayoutNode, +{ + 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 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 { + self.element + .get_data() + .expect("Unstyled layout node?") + .borrow() + } + + fn is_shadow_host(&self) -> bool { + self.element.shadow_root().is_some() + } +} + +/// 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<&Namespace>, + local_name: &LocalName, + operation: &AttrSelectorOperation<&String>, + ) -> 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 = unsafe { + (*self.element.element.unsafe_get()).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: &Atom, _case_sensitivity: CaseSensitivity) -> bool { + debug!("ServoThreadSafeLayoutElement::has_id called"); + false + } + + #[inline] + fn is_part(&self, _name: &Atom) -> bool { + debug!("ServoThreadSafeLayoutElement::is_part called"); + false + } + + fn has_class(&self, _name: &Atom, _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 b5bbe316dcc..ad08ea4dc32 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -2,42 +2,283 @@ * 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 crossbeam_channel::{Receiver, Sender}; -use euclid::Size2D; +// Work around https://github.com/rust-lang/rust/issues/62132 +#![recursion_limit = "128"] + +//! The layout thread. Performs layout on the DOM, builds display lists and sends them to be +//! painted. + +#[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::drop_style_and_layout_data; +use crate::dom_wrapper::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; +use app_units::Au; +use crossbeam_channel::{unbounded, Receiver, Sender}; +use embedder_traits::resources::{self, Resource}; +use euclid::{default::Size2D as UntypedSize2D, Point2D, Rect, Scale, Size2D}; +use fnv::FnvHashMap; +use fxhash::FxHashMap; +use gfx::font; use gfx::font_cache_thread::FontCacheThread; -use ipc_channel::ipc::{IpcReceiver, IpcSender}; -use metrics::PaintTimeMetrics; -use msg::constellation_msg::TopLevelBrowsingContextId; -use msg::constellation_msg::{BackgroundHangMonitorRegister, PipelineId}; +use gfx::font_context; +use gfx_traits::{node_id_from_scroll_id, Epoch}; +use histogram::Histogram; +use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; +use ipc_channel::router::ROUTER; +use layout::context::LayoutContext; +use layout::display_list::items::OpaqueNode; +use layout::display_list::{IndexableText, WebRenderDisplayListConverter}; +use layout::flow::{Flow, GetBaseFlow}; +use layout::flow_ref::FlowRef; +use layout::query::{ + process_content_box_request, process_content_boxes_request, LayoutRPCImpl, LayoutThreadData, +}; +use layout::query::{process_element_inner_text_query, process_node_geometry_request}; +use layout::query::{process_node_scroll_area_request, process_node_scroll_id_request}; +use layout::query::{ + process_offset_parent_query, process_resolved_style_request, process_style_query, +}; +use layout::traversal::RecalcStyleAndConstructFlows; +use layout_traits::LayoutThreadFactory; +use libc::c_void; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use metrics::{PaintTimeMetrics, ProfilerMetadataFactory, ProgressiveWebMetric}; +use msg::constellation_msg::{ + BackgroundHangMonitor, BackgroundHangMonitorRegister, HangAnnotation, +}; +use msg::constellation_msg::{LayoutHangAnnotation, MonitoredComponentType, PipelineId}; +use msg::constellation_msg::{MonitoredComponentId, TopLevelBrowsingContextId}; use net_traits::image_cache::ImageCache; -use profile_traits::{mem, time}; -use script_traits::LayoutMsg as ConstellationMsg; -use script_traits::{ConstellationControlMsg, LayoutControlMsg}; +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_interface::message::{LayoutThreadInit, Msg, NodesFromPointQueryType}; +use script_layout_interface::message::{QueryMsg, ReflowComplete, ReflowGoal, ScriptReflow}; +use script_layout_interface::rpc::TextIndexResponse; +use script_layout_interface::rpc::{LayoutRPC, OffsetParentResponse, StyleResponse}; +use script_layout_interface::wrapper_traits::LayoutNode; +use script_traits::Painter; +use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg}; +use script_traits::{DrawAPaintImageResult, PaintWorkletError}; +use script_traits::{ScrollState, UntrustedNodeAddress}; +use selectors::Element; +use servo_arc::Arc as ServoArc; +use servo_atoms::Atom; +use servo_config::opts; +use servo_config::pref; use servo_geometry::DeviceIndependentPixel; use servo_url::ServoUrl; -use std::sync::atomic::AtomicBool; -use std::sync::Arc; +use std::cell::{Cell, RefCell}; +use std::collections::HashMap; +use std::ops::{Deref, DerefMut}; +use std::process; +use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex, MutexGuard}; +use std::thread; +use std::time::Duration; +use style::animation::Animation; +use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters}; +use style::context::{SharedStyleContext, ThreadLocalStyleContextCreationInfo}; +use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TDocument, TElement, TNode}; +use style::driver; +use style::error_reporting::RustLogReporter; +use style::global_style_data::{GLOBAL_STYLE_DATA, STYLE_THREAD_POOL}; +use style::invalidation::element::restyle_hints::RestyleHint; +use style::media_queries::{Device, MediaList, MediaType}; +use style::properties::PropertyId; +use style::selector_parser::SnapshotMap; +use style::servo::restyle_damage::ServoRestyleDamage; +use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards}; +use style::stylesheets::{ + DocumentStyleSheet, Origin, Stylesheet, StylesheetInDocument, UserAgentStylesheets, +}; +use style::stylist::Stylist; +use style::thread_state::{self, ThreadState}; +use style::timer::Timer; +use style::traversal::DomTraversal; +use style::traversal_flags::TraversalFlags; +use style_traits::CSSPixel; +use style_traits::DevicePixel; +use style_traits::SpeculativePainter; -pub struct LayoutThread; +/// Information needed by the layout thread. +pub struct LayoutThread { + /// The ID of the pipeline that we belong to. + id: PipelineId, -impl layout_traits::LayoutThreadFactory for LayoutThread { - type Message = script_layout_interface::message::Msg; + /// The ID of the top-level browsing context that we belong to. + top_level_browsing_context_id: TopLevelBrowsingContextId, - #[allow(unused)] + /// The URL of the pipeline that we belong to. + url: ServoUrl, + + /// Performs CSS selector matching and style resolution. + stylist: Stylist, + + /// Is the current reflow of an iframe, as opposed to a root window? + is_iframe: bool, + + /// The port on which we receive messages from the script thread. + port: Receiver, + + /// The port on which we receive messages from the constellation. + pipeline_port: Receiver, + + /// The port on which we receive messages from the font cache thread. + font_cache_receiver: Receiver<()>, + + /// The channel on which the font cache can send messages to us. + font_cache_sender: IpcSender<()>, + + /// A means of communication with the background hang monitor. + background_hang_monitor: Box, + + /// The channel on which messages can be sent to the script thread. + script_chan: IpcSender, + + /// The channel on which messages can be sent to the time profiler. + time_profiler_chan: profile_time::ProfilerChan, + + /// The channel on which messages can be sent to the memory profiler. + mem_profiler_chan: profile_mem::ProfilerChan, + + /// Public interface to the font cache thread. + font_cache_thread: FontCacheThread, + + /// Is this the first reflow in this LayoutThread? + first_reflow: Cell, + + /// Flag to indicate whether to use parallel operations + parallel_flag: bool, + + /// Starts at zero, and increased by one every time a layout completes. + /// This can be used to easily check for invalid stale data. + generation: Cell, + + /// A channel on which new animations that have been triggered by style recalculation can be + /// sent. + new_animations_sender: Sender, + + /// Receives newly-discovered animations. + _new_animations_receiver: Receiver, + + /// The number of Web fonts that have been requested but not yet loaded. + outstanding_web_fonts: Arc, + + /// The root of the flow tree. + root_flow: RefCell>, + + /// The document-specific shared lock used for author-origin stylesheets + document_shared_lock: Option, + + /// The list of currently-running animations. + running_animations: ServoArc>>>, + + /// The list of animations that have expired since the last style recalculation. + expired_animations: ServoArc>>>, + + /// A counter for epoch messages + epoch: Cell, + + /// The size of the viewport. This may be different from the size of the screen due to viewport + /// constraints. + viewport_size: UntypedSize2D, + + /// A mutex to allow for fast, read-only RPC of layout's internal data + /// structures, while still letting the LayoutThread modify them. + /// + /// All the other elements of this struct are read-only. + rw_data: Arc>, + + /// The executors for paint worklets. + registered_painters: RegisteredPaintersImpl, + + /// Webrender interface. + webrender_api: webrender_api::RenderApi, + + /// Webrender document. + webrender_document: webrender_api::DocumentId, + + /// The timer object to control the timing of the animations. This should + /// only be a test-mode timer during testing for animations. + timer: Timer, + + /// Paint time metrics. + paint_time_metrics: PaintTimeMetrics, + + /// The time a layout query has waited before serviced by layout thread. + layout_query_waiting_time: Histogram, + + /// Flag that indicates if LayoutThread is busy handling a request. + busy: Arc, + + /// Load web fonts synchronously to avoid non-deterministic network-driven reflows. + load_webfonts_synchronously: bool, + + /// The initial request size of the window + initial_window_size: Size2D, + + /// The ratio of device pixels per px at the default scale. + /// If unspecified, will use the platform default setting. + device_pixels_per_px: Option, + + /// Dumps the display list form after a layout. + dump_display_list: bool, + + /// Dumps the display list in JSON form after a layout. + dump_display_list_json: bool, + + /// Dumps the DOM after restyle. + dump_style_tree: bool, + + /// Dumps the flow tree after a layout. + dump_rule_tree: bool, + + /// Emits notifications when there is a relayout. + relayout_event: bool, + + /// True to turn off incremental layout. + nonincremental_layout: bool, + + /// True if each step of layout is traced to an external JSON file + /// for debugging purposes. Setting this implies sequential layout + /// and paint. + trace_layout: bool, + + /// Dumps the flow tree after a layout. + dump_flow_tree: bool, +} + +impl LayoutThreadFactory for LayoutThread { + type Message = Msg; + + /// Spawns a new layout thread. fn create( id: PipelineId, top_level_browsing_context_id: TopLevelBrowsingContextId, url: ServoUrl, is_iframe: bool, - chan: (Sender, Receiver), + chan: (Sender, Receiver), pipeline_port: IpcReceiver, - background_hang_monitor: Box, + background_hang_monitor_register: Box, constellation_chan: IpcSender, script_chan: IpcSender, - image_cache: Arc, + _image_cache: Arc, font_cache_thread: FontCacheThread, - time_profiler_chan: time::ProfilerChan, - mem_profiler_chan: mem::ProfilerChan, + time_profiler_chan: profile_time::ProfilerChan, + mem_profiler_chan: profile_mem::ProfilerChan, content_process_shutdown_chan: Option>, webrender_api_sender: webrender_api::RenderApiSender, webrender_document: webrender_api::DocumentId, @@ -55,5 +296,1423 @@ impl layout_traits::LayoutThreadFactory for LayoutThread { trace_layout: bool, dump_flow_tree: bool, ) { + thread::Builder::new() + .name(format!("LayoutThread {:?}", id)) + .spawn(move || { + thread_state::initialize(ThreadState::LAYOUT); + + // In order to get accurate crash reports, we install the top-level bc id. + TopLevelBrowsingContextId::install(top_level_browsing_context_id); + + { + // Ensures layout thread is destroyed before we send shutdown message + let sender = chan.0; + + let background_hang_monitor = background_hang_monitor_register + .register_component( + MonitoredComponentId(id, MonitoredComponentType::Layout), + Duration::from_millis(1000), + Duration::from_millis(5000), + ); + + let layout = LayoutThread::new( + id, + top_level_browsing_context_id, + url, + is_iframe, + chan.1, + pipeline_port, + background_hang_monitor, + constellation_chan, + script_chan, + font_cache_thread, + time_profiler_chan, + mem_profiler_chan.clone(), + webrender_api_sender, + webrender_document, + paint_time_metrics, + busy, + load_webfonts_synchronously, + initial_window_size, + device_pixels_per_px, + dump_display_list, + dump_display_list_json, + dump_style_tree, + dump_rule_tree, + relayout_event, + nonincremental_layout, + trace_layout, + dump_flow_tree, + ); + + let reporter_name = format!("layout-reporter-{}", id); + mem_profiler_chan.run_with_memory_reporting( + || { + layout.start(); + }, + reporter_name, + sender, + Msg::CollectReports, + ); + } + if let Some(content_process_shutdown_chan) = content_process_shutdown_chan { + let _ = content_process_shutdown_chan.send(()); + } + }) + .expect("Thread spawning failed"); + } +} + +struct ScriptReflowResult { + script_reflow: ScriptReflow, + result: RefCell>, +} + +impl Deref for ScriptReflowResult { + type Target = ScriptReflow; + fn deref(&self) -> &ScriptReflow { + &self.script_reflow + } +} + +impl DerefMut for ScriptReflowResult { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.script_reflow + } +} + +impl ScriptReflowResult { + fn new(script_reflow: ScriptReflow) -> ScriptReflowResult { + ScriptReflowResult { + script_reflow: script_reflow, + result: RefCell::new(Some(Default::default())), + } + } +} + +impl Drop for ScriptReflowResult { + fn drop(&mut self) { + self.script_reflow + .script_join_chan + .send(self.result.borrow_mut().take().unwrap()) + .unwrap(); + } +} + +/// The `LayoutThread` `rw_data` lock must remain locked until the first reflow, +/// as RPC calls don't make sense until then. Use this in combination with +/// `LayoutThread::lock_rw_data` and `LayoutThread::return_rw_data`. +pub enum RWGuard<'a> { + /// If the lock was previously held, from when the thread started. + Held(MutexGuard<'a, LayoutThreadData>), + /// If the lock was just used, and has been returned since there has been + /// a reflow already. + Used(MutexGuard<'a, LayoutThreadData>), +} + +impl<'a> Deref for RWGuard<'a> { + type Target = LayoutThreadData; + fn deref(&self) -> &LayoutThreadData { + match *self { + RWGuard::Held(ref x) => &**x, + RWGuard::Used(ref x) => &**x, + } + } +} + +impl<'a> DerefMut for RWGuard<'a> { + fn deref_mut(&mut self) -> &mut LayoutThreadData { + match *self { + RWGuard::Held(ref mut x) => &mut **x, + RWGuard::Used(ref mut x) => &mut **x, + } + } +} + +struct RwData<'a, 'b: 'a> { + rw_data: &'b Arc>, + possibly_locked_rw_data: &'a mut Option>, +} + +impl<'a, 'b: 'a> RwData<'a, 'b> { + /// If no reflow has happened yet, this will just return the lock in + /// `possibly_locked_rw_data`. Otherwise, it will acquire the `rw_data` lock. + /// + /// If you do not wish RPCs to remain blocked, just drop the `RWGuard` + /// returned from this function. If you _do_ wish for them to remain blocked, + /// use `block`. + fn lock(&mut self) -> RWGuard<'b> { + match self.possibly_locked_rw_data.take() { + None => RWGuard::Used(self.rw_data.lock().unwrap()), + Some(x) => RWGuard::Held(x), + } + } +} + +fn add_font_face_rules( + stylesheet: &Stylesheet, + guard: &SharedRwLockReadGuard, + device: &Device, + font_cache_thread: &FontCacheThread, + font_cache_sender: &IpcSender<()>, + outstanding_web_fonts_counter: &Arc, + load_webfonts_synchronously: bool, +) { + if load_webfonts_synchronously { + let (sender, receiver) = ipc::channel().unwrap(); + stylesheet.effective_font_face_rules(&device, guard, |rule| { + if let Some(font_face) = rule.font_face() { + let effective_sources = font_face.effective_sources(); + font_cache_thread.add_web_font( + font_face.family().clone(), + effective_sources, + sender.clone(), + ); + receiver.recv().unwrap(); + } + }) + } else { + stylesheet.effective_font_face_rules(&device, guard, |rule| { + if let Some(font_face) = rule.font_face() { + let effective_sources = font_face.effective_sources(); + outstanding_web_fonts_counter.fetch_add(1, Ordering::SeqCst); + font_cache_thread.add_web_font( + font_face.family().clone(), + effective_sources, + (*font_cache_sender).clone(), + ); + } + }) + } +} + +impl LayoutThread { + /// Creates a new `LayoutThread` structure. + fn new( + id: PipelineId, + top_level_browsing_context_id: TopLevelBrowsingContextId, + url: ServoUrl, + is_iframe: bool, + port: Receiver, + pipeline_port: IpcReceiver, + background_hang_monitor: Box, + constellation_chan: IpcSender, + script_chan: IpcSender, + font_cache_thread: FontCacheThread, + time_profiler_chan: profile_time::ProfilerChan, + mem_profiler_chan: profile_mem::ProfilerChan, + webrender_api_sender: webrender_api::RenderApiSender, + webrender_document: webrender_api::DocumentId, + paint_time_metrics: PaintTimeMetrics, + busy: Arc, + load_webfonts_synchronously: bool, + initial_window_size: Size2D, + device_pixels_per_px: Option, + dump_display_list: bool, + dump_display_list_json: bool, + dump_style_tree: bool, + dump_rule_tree: bool, + relayout_event: bool, + nonincremental_layout: bool, + trace_layout: bool, + dump_flow_tree: bool, + ) -> LayoutThread { + // The device pixel ratio is incorrect (it does not have the hidpi value), + // but it will be set correctly when the initial reflow takes place. + let device = Device::new( + MediaType::screen(), + initial_window_size.to_f32() * Scale::new(1.0), + Scale::new(device_pixels_per_px.unwrap_or(1.0)), + ); + + // Create the channel on which new animations can be sent. + let (new_animations_sender, new_animations_receiver) = unbounded(); + + // Proxy IPC messages from the pipeline to the layout thread. + let pipeline_receiver = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(pipeline_port); + + // Ask the router to proxy IPC messages from the font cache thread to the layout thread. + let (ipc_font_cache_sender, ipc_font_cache_receiver) = ipc::channel().unwrap(); + let font_cache_receiver = + ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(ipc_font_cache_receiver); + + LayoutThread { + id: id, + top_level_browsing_context_id: top_level_browsing_context_id, + url: url, + is_iframe: is_iframe, + port: port, + pipeline_port: pipeline_receiver, + script_chan: script_chan.clone(), + background_hang_monitor, + time_profiler_chan: time_profiler_chan, + mem_profiler_chan: mem_profiler_chan, + registered_painters: RegisteredPaintersImpl(Default::default()), + font_cache_thread: font_cache_thread, + first_reflow: Cell::new(true), + font_cache_receiver: font_cache_receiver, + font_cache_sender: ipc_font_cache_sender, + parallel_flag: true, + generation: Cell::new(0), + new_animations_sender: new_animations_sender, + _new_animations_receiver: new_animations_receiver, + outstanding_web_fonts: Arc::new(AtomicUsize::new(0)), + root_flow: RefCell::new(None), + document_shared_lock: None, + running_animations: ServoArc::new(RwLock::new(Default::default())), + expired_animations: ServoArc::new(RwLock::new(Default::default())), + epoch: Cell::new(Epoch(0)), + viewport_size: Size2D::new(Au(0), Au(0)), + webrender_api: webrender_api_sender.create_api(), + webrender_document, + stylist: Stylist::new(device, QuirksMode::NoQuirks), + rw_data: Arc::new(Mutex::new(LayoutThreadData { + constellation_chan: constellation_chan, + display_list: None, + indexable_text: IndexableText::default(), + content_box_response: None, + content_boxes_response: Vec::new(), + client_rect_response: Rect::zero(), + scroll_id_response: None, + scroll_area_response: Rect::zero(), + resolved_style_response: String::new(), + offset_parent_response: OffsetParentResponse::empty(), + style_response: StyleResponse(None), + scroll_offsets: HashMap::new(), + text_index_response: TextIndexResponse(None), + nodes_from_point_response: vec![], + element_inner_text_response: String::new(), + })), + timer: if pref!(layout.animations.test.enabled) { + Timer::test_mode() + } else { + Timer::new() + }, + paint_time_metrics: paint_time_metrics, + layout_query_waiting_time: Histogram::new(), + busy, + load_webfonts_synchronously, + initial_window_size, + device_pixels_per_px, + dump_display_list, + dump_display_list_json, + dump_style_tree, + dump_rule_tree, + relayout_event, + nonincremental_layout, + trace_layout, + dump_flow_tree, + } + } + + /// Starts listening on the port. + fn start(mut self) { + let rw_data = self.rw_data.clone(); + let mut possibly_locked_rw_data = Some(rw_data.lock().unwrap()); + let mut rw_data = RwData { + rw_data: &rw_data, + possibly_locked_rw_data: &mut possibly_locked_rw_data, + }; + while self.handle_request(&mut rw_data) { + // Loop indefinitely. + } + } + + // Create a layout context for use in building display lists, hit testing, &c. + fn build_layout_context<'a>( + &'a self, + guards: StylesheetGuards<'a>, + snapshot_map: &'a SnapshotMap, + ) -> LayoutContext<'a> { + let thread_local_style_context_creation_data = + ThreadLocalStyleContextCreationInfo::new(self.new_animations_sender.clone()); + + LayoutContext { + id: self.id, + style_context: SharedStyleContext { + stylist: &self.stylist, + options: GLOBAL_STYLE_DATA.options.clone(), + guards, + visited_styles_enabled: false, + running_animations: self.running_animations.clone(), + expired_animations: self.expired_animations.clone(), + registered_speculative_painters: &self.registered_painters, + local_context_creation_data: Mutex::new(thread_local_style_context_creation_data), + timer: self.timer.clone(), + traversal_flags: TraversalFlags::empty(), + snapshot_map: snapshot_map, + }, + } + } + + fn notify_activity_to_hang_monitor(&self, request: &Msg) { + let hang_annotation = match request { + Msg::AddStylesheet(..) => LayoutHangAnnotation::AddStylesheet, + Msg::RemoveStylesheet(..) => LayoutHangAnnotation::RemoveStylesheet, + Msg::SetQuirksMode(..) => LayoutHangAnnotation::SetQuirksMode, + Msg::Reflow(..) => LayoutHangAnnotation::Reflow, + Msg::GetRPC(..) => LayoutHangAnnotation::GetRPC, + Msg::TickAnimations => LayoutHangAnnotation::TickAnimations, + Msg::AdvanceClockMs(..) => LayoutHangAnnotation::AdvanceClockMs, + Msg::ReapStyleAndLayoutData(..) => LayoutHangAnnotation::ReapStyleAndLayoutData, + Msg::CollectReports(..) => LayoutHangAnnotation::CollectReports, + Msg::PrepareToExit(..) => LayoutHangAnnotation::PrepareToExit, + Msg::ExitNow => LayoutHangAnnotation::ExitNow, + Msg::GetCurrentEpoch(..) => LayoutHangAnnotation::GetCurrentEpoch, + Msg::GetWebFontLoadState(..) => LayoutHangAnnotation::GetWebFontLoadState, + Msg::CreateLayoutThread(..) => LayoutHangAnnotation::CreateLayoutThread, + Msg::SetFinalUrl(..) => LayoutHangAnnotation::SetFinalUrl, + Msg::SetScrollStates(..) => LayoutHangAnnotation::SetScrollStates, + Msg::UpdateScrollStateFromScript(..) => { + LayoutHangAnnotation::UpdateScrollStateFromScript + }, + Msg::RegisterPaint(..) => LayoutHangAnnotation::RegisterPaint, + Msg::SetNavigationStart(..) => LayoutHangAnnotation::SetNavigationStart, + Msg::GetRunningAnimations(..) => LayoutHangAnnotation::GetRunningAnimations, + }; + self.background_hang_monitor + .notify_activity(HangAnnotation::Layout(hang_annotation)); + } + + /// Receives and dispatches messages from the script and constellation threads + fn handle_request<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) -> bool { + enum Request { + FromPipeline(LayoutControlMsg), + FromScript(Msg), + FromFontCache, + } + + // Notify the background-hang-monitor we are waiting for an event. + self.background_hang_monitor.notify_wait(); + + let request = select! { + recv(self.pipeline_port) -> msg => Request::FromPipeline(msg.unwrap()), + recv(self.port) -> msg => Request::FromScript(msg.unwrap()), + recv(self.font_cache_receiver) -> msg => { msg.unwrap(); Request::FromFontCache } + }; + + self.busy.store(true, Ordering::Relaxed); + let result = match request { + Request::FromPipeline(LayoutControlMsg::SetScrollStates(new_scroll_states)) => self + .handle_request_helper( + Msg::SetScrollStates(new_scroll_states), + possibly_locked_rw_data, + ), + Request::FromPipeline(LayoutControlMsg::TickAnimations) => { + self.handle_request_helper(Msg::TickAnimations, possibly_locked_rw_data) + }, + Request::FromPipeline(LayoutControlMsg::GetCurrentEpoch(sender)) => { + self.handle_request_helper(Msg::GetCurrentEpoch(sender), possibly_locked_rw_data) + }, + Request::FromPipeline(LayoutControlMsg::GetWebFontLoadState(sender)) => self + .handle_request_helper(Msg::GetWebFontLoadState(sender), possibly_locked_rw_data), + Request::FromPipeline(LayoutControlMsg::ExitNow) => { + self.handle_request_helper(Msg::ExitNow, possibly_locked_rw_data) + }, + Request::FromPipeline(LayoutControlMsg::PaintMetric(epoch, paint_time)) => { + self.paint_time_metrics.maybe_set_metric(epoch, paint_time); + true + }, + Request::FromScript(msg) => self.handle_request_helper(msg, possibly_locked_rw_data), + Request::FromFontCache => { + let _rw_data = possibly_locked_rw_data.lock(); + self.outstanding_web_fonts.fetch_sub(1, Ordering::SeqCst); + font_context::invalidate_font_caches(); + self.script_chan + .send(ConstellationControlMsg::WebFontLoaded(self.id)) + .unwrap(); + true + }, + }; + self.busy.store(false, Ordering::Relaxed); + result + } + + /// Receives and dispatches messages from other threads. + fn handle_request_helper<'a, 'b>( + &mut self, + request: Msg, + possibly_locked_rw_data: &mut RwData<'a, 'b>, + ) -> bool { + self.notify_activity_to_hang_monitor(&request); + + match request { + Msg::AddStylesheet(stylesheet, before_stylesheet) => { + let guard = stylesheet.shared_lock.read(); + self.handle_add_stylesheet(&stylesheet, &guard); + + match before_stylesheet { + Some(insertion_point) => self.stylist.insert_stylesheet_before( + DocumentStyleSheet(stylesheet.clone()), + DocumentStyleSheet(insertion_point), + &guard, + ), + None => self + .stylist + .append_stylesheet(DocumentStyleSheet(stylesheet.clone()), &guard), + } + }, + Msg::RemoveStylesheet(stylesheet) => { + let guard = stylesheet.shared_lock.read(); + self.stylist + .remove_stylesheet(DocumentStyleSheet(stylesheet.clone()), &guard); + }, + Msg::SetQuirksMode(mode) => self.handle_set_quirks_mode(mode), + Msg::GetRPC(response_chan) => { + response_chan + .send(Box::new(LayoutRPCImpl(self.rw_data.clone())) as Box) + .unwrap(); + }, + Msg::Reflow(data) => { + let mut data = ScriptReflowResult::new(data); + profile( + profile_time::ProfilerCategory::LayoutPerform, + self.profiler_metadata(), + self.time_profiler_chan.clone(), + || self.handle_reflow(&mut data, possibly_locked_rw_data), + ); + }, + Msg::TickAnimations => self.tick_all_animations(possibly_locked_rw_data), + Msg::SetScrollStates(new_scroll_states) => { + self.set_scroll_states(new_scroll_states, possibly_locked_rw_data); + }, + Msg::UpdateScrollStateFromScript(state) => { + let mut rw_data = possibly_locked_rw_data.lock(); + rw_data + .scroll_offsets + .insert(state.scroll_id, state.scroll_offset); + + let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y); + let mut txn = webrender_api::Transaction::new(); + txn.scroll_node_with_id( + webrender_api::units::LayoutPoint::from_untyped(point), + state.scroll_id, + webrender_api::ScrollClamping::ToContentBounds, + ); + self.webrender_api + .send_transaction(self.webrender_document, txn); + }, + Msg::ReapStyleAndLayoutData(dead_data) => unsafe { + drop_style_and_layout_data(dead_data) + }, + Msg::CollectReports(reports_chan) => { + self.collect_reports(reports_chan, possibly_locked_rw_data); + }, + Msg::GetCurrentEpoch(sender) => { + let _rw_data = possibly_locked_rw_data.lock(); + sender.send(self.epoch.get()).unwrap(); + }, + Msg::AdvanceClockMs(how_many, do_tick) => { + self.handle_advance_clock_ms(how_many, possibly_locked_rw_data, do_tick); + }, + Msg::GetWebFontLoadState(sender) => { + let _rw_data = possibly_locked_rw_data.lock(); + let outstanding_web_fonts = self.outstanding_web_fonts.load(Ordering::SeqCst); + sender.send(outstanding_web_fonts != 0).unwrap(); + }, + Msg::CreateLayoutThread(info) => self.create_layout_thread(info), + Msg::SetFinalUrl(final_url) => { + self.url = final_url; + }, + Msg::RegisterPaint(_name, _properties, _painter) => {}, + Msg::PrepareToExit(response_chan) => { + self.prepare_to_exit(response_chan); + return false; + }, + // Receiving the Exit message at this stage only happens when layout is undergoing a "force exit". + Msg::ExitNow => { + debug!("layout: ExitNow received"); + self.exit_now(); + return false; + }, + Msg::SetNavigationStart(time) => { + self.paint_time_metrics.set_navigation_start(time); + }, + Msg::GetRunningAnimations(sender) => { + let _ = sender.send(self.running_animations.read().len()); + }, + } + + true + } + + fn collect_reports<'a, 'b>( + &self, + reports_chan: ReportsChan, + possibly_locked_rw_data: &mut RwData<'a, 'b>, + ) { + let mut reports = vec![]; + // Servo uses vanilla jemalloc, which doesn't have a + // malloc_enclosing_size_of function. + let mut ops = MallocSizeOfOps::new(servo_allocator::usable_size, None, None); + + // FIXME(njn): Just measuring the display tree for now. + let rw_data = possibly_locked_rw_data.lock(); + let display_list = rw_data.display_list.as_ref(); + let formatted_url = &format!("url({})", self.url); + reports.push(Report { + path: path![formatted_url, "layout-thread", "display-list"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: display_list.map_or(0, |sc| sc.size_of(&mut ops)), + }); + + reports.push(Report { + path: path![formatted_url, "layout-thread", "stylist"], + kind: ReportKind::ExplicitJemallocHeapSize, + size: self.stylist.size_of(&mut ops), + }); + + reports_chan.send(reports); + } + + fn create_layout_thread(&self, info: LayoutThreadInit) { + LayoutThread::create( + info.id, + self.top_level_browsing_context_id, + info.url.clone(), + info.is_parent, + info.layout_pair, + info.pipeline_port, + info.background_hang_monitor_register, + info.constellation_chan, + info.script_chan.clone(), + info.image_cache.clone(), + self.font_cache_thread.clone(), + self.time_profiler_chan.clone(), + self.mem_profiler_chan.clone(), + info.content_process_shutdown_chan, + self.webrender_api.clone_sender(), + self.webrender_document, + info.paint_time_metrics, + info.layout_is_busy, + self.load_webfonts_synchronously, + self.initial_window_size, + self.device_pixels_per_px, + self.dump_display_list, + self.dump_display_list_json, + self.dump_style_tree, + self.dump_rule_tree, + self.relayout_event, + self.nonincremental_layout, + self.trace_layout, + self.dump_flow_tree, + ); + } + + /// Enters a quiescent state in which no new messages will be processed until an `ExitNow` is + /// received. A pong is immediately sent on the given response channel. + fn prepare_to_exit(&mut self, response_chan: Sender<()>) { + response_chan.send(()).unwrap(); + loop { + match self.port.recv().unwrap() { + Msg::ReapStyleAndLayoutData(dead_data) => unsafe { + drop_style_and_layout_data(dead_data) + }, + Msg::ExitNow => { + debug!("layout thread is exiting..."); + self.exit_now(); + break; + }, + Msg::CollectReports(_) => { + // Just ignore these messages at this point. + }, + _ => panic!("layout: unexpected message received after `PrepareToExitMsg`"), + } + } + } + + /// Shuts down the layout thread now. If there are any DOM nodes left, layout will now (safely) + /// crash. + fn exit_now(&mut self) { + // Drop the root flow explicitly to avoid holding style data, such as + // rule nodes. The `Stylist` checks when it is dropped that all rule + // nodes have been GCed, so we want drop anyone who holds them first. + let waiting_time_min = self.layout_query_waiting_time.minimum().unwrap_or(0); + let waiting_time_max = self.layout_query_waiting_time.maximum().unwrap_or(0); + let waiting_time_mean = self.layout_query_waiting_time.mean().unwrap_or(0); + let waiting_time_stddev = self.layout_query_waiting_time.stddev().unwrap_or(0); + debug!( + "layout: query waiting time: min: {}, max: {}, mean: {}, standard_deviation: {}", + waiting_time_min, waiting_time_max, waiting_time_mean, waiting_time_stddev + ); + + self.root_flow.borrow_mut().take(); + self.background_hang_monitor.unregister(); + } + + fn handle_add_stylesheet(&self, stylesheet: &Stylesheet, guard: &SharedRwLockReadGuard) { + // Find all font-face rules and notify the font cache of them. + // GWTODO: Need to handle unloading web fonts. + if stylesheet.is_effective_for_device(self.stylist.device(), &guard) { + add_font_face_rules( + &*stylesheet, + &guard, + self.stylist.device(), + &self.font_cache_thread, + &self.font_cache_sender, + &self.outstanding_web_fonts, + self.load_webfonts_synchronously, + ); + } + } + + /// Advances the animation clock of the document. + fn handle_advance_clock_ms<'a, 'b>( + &mut self, + how_many_ms: i32, + possibly_locked_rw_data: &mut RwData<'a, 'b>, + tick_animations: bool, + ) { + self.timer.increment(how_many_ms as f64 / 1000.0); + if tick_animations { + self.tick_all_animations(possibly_locked_rw_data); + } + } + + /// Sets quirks mode for the document, causing the quirks mode stylesheet to be used. + fn handle_set_quirks_mode<'a, 'b>(&mut self, quirks_mode: QuirksMode) { + self.stylist.set_quirks_mode(quirks_mode); + } + + fn try_get_layout_root(&self, _node: N) -> Option { + None + } + + /// Computes the stacking-relative positions of all flows and, if the painting is dirty and the + /// reflow type need it, builds the display list. + fn compute_abs_pos_and_build_display_list( + &self, + reflow_goal: &ReflowGoal, + document: Option<&ServoLayoutDocument>, + layout_root: &mut dyn Flow, + rw_data: &mut LayoutThreadData, + ) { + let (metadata, sender) = (self.profiler_metadata(), self.time_profiler_chan.clone()); + profile( + profile_time::ProfilerCategory::LayoutDispListBuild, + metadata.clone(), + sender.clone(), + || { + if layout_root + .base() + .restyle_damage + .contains(ServoRestyleDamage::REPAINT) || + rw_data.display_list.is_none() + { + layout_root + .mut_base() + .restyle_damage + .remove(ServoRestyleDamage::REPAINT); + } + + if !reflow_goal.needs_display() { + // Defer the paint step until the next ForDisplay. + // + // We need to tell the document about this so it doesn't + // incorrectly suppress reflows. See #13131. + document + .expect("No document in a non-display reflow?") + .needs_paint_from_layout(); + return; + } + if let Some(document) = document { + document.will_paint(); + } + + let display_list = rw_data.display_list.as_mut().unwrap(); + + debug!("Layout done!"); + + // TODO: Avoid the temporary conversion and build webrender sc/dl directly! + let builder = display_list.convert_to_webrender(self.id); + + let viewport_size = Size2D::new( + self.viewport_size.width.to_f32_px(), + self.viewport_size.height.to_f32_px(), + ); + + let mut epoch = self.epoch.get(); + epoch.next(); + self.epoch.set(epoch); + + let viewport_size = webrender_api::units::LayoutSize::from_untyped(viewport_size); + + // Observe notifications about rendered frames if needed right before + // sending the display list to WebRender in order to set time related + // Progressive Web Metrics. + self.paint_time_metrics + .maybe_observe_paint_time(self, epoch, &*display_list); + + let mut txn = webrender_api::Transaction::new(); + txn.set_display_list( + webrender_api::Epoch(epoch.0), + None, + viewport_size, + builder.finalize(), + true, + ); + txn.generate_frame(); + self.webrender_api + .send_transaction(self.webrender_document, txn); + }, + ); + } + + /// The high-level routine that performs layout threads. + fn handle_reflow<'a, 'b>( + &mut self, + data: &mut ScriptReflowResult, + possibly_locked_rw_data: &mut RwData<'a, 'b>, + ) { + let document = unsafe { ServoLayoutNode::new(&data.document) }; + let document = document.as_document().unwrap(); + + // Parallelize if there's more than 750 objects based on rzambre's suggestion + // https://github.com/servo/servo/issues/10110 + self.parallel_flag = data.dom_count > 750; + debug!("layout: received layout request for: {}", self.url); + debug!("Number of objects in DOM: {}", data.dom_count); + debug!("layout: parallel? {}", self.parallel_flag); + + let mut rw_data = possibly_locked_rw_data.lock(); + + // Record the time that layout query has been waited. + let now = time::precise_time_ns(); + if let ReflowGoal::LayoutQuery(_, timestamp) = data.reflow_goal { + self.layout_query_waiting_time + .increment(now - timestamp) + .expect("layout: wrong layout query timestamp"); + }; + + let element = match document.root_element() { + None => { + // Since we cannot compute anything, give spec-required placeholders. + debug!("layout: No root node: bailing"); + match data.reflow_goal { + ReflowGoal::LayoutQuery(ref query_msg, _) => match query_msg { + &QueryMsg::ContentBoxQuery(_) => { + rw_data.content_box_response = None; + }, + &QueryMsg::ContentBoxesQuery(_) => { + rw_data.content_boxes_response = Vec::new(); + }, + &QueryMsg::NodesFromPointQuery(..) => { + rw_data.nodes_from_point_response = Vec::new(); + }, + &QueryMsg::NodeGeometryQuery(_) => { + rw_data.client_rect_response = Rect::zero(); + }, + &QueryMsg::NodeScrollGeometryQuery(_) => { + rw_data.scroll_area_response = Rect::zero(); + }, + &QueryMsg::NodeScrollIdQuery(_) => { + rw_data.scroll_id_response = None; + }, + &QueryMsg::ResolvedStyleQuery(_, _, _) => { + rw_data.resolved_style_response = String::new(); + }, + &QueryMsg::OffsetParentQuery(_) => { + rw_data.offset_parent_response = OffsetParentResponse::empty(); + }, + &QueryMsg::StyleQuery(_) => { + rw_data.style_response = StyleResponse(None); + }, + &QueryMsg::TextIndexQuery(..) => { + rw_data.text_index_response = TextIndexResponse(None); + }, + &QueryMsg::ElementInnerTextQuery(_) => { + rw_data.element_inner_text_response = String::new(); + }, + }, + ReflowGoal::Full | ReflowGoal::TickAnimations => {}, + } + return; + }, + Some(x) => x, + }; + + debug!( + "layout: processing reflow request for: {:?} ({}) (query={:?})", + element, self.url, data.reflow_goal + ); + trace!("{:?}", ShowSubtree(element.as_node())); + + let initial_viewport = data.window_size.initial_viewport; + let device_pixel_ratio = data.window_size.device_pixel_ratio; + let old_viewport_size = self.viewport_size; + let current_screen_size = Size2D::new( + Au::from_f32_px(initial_viewport.width), + Au::from_f32_px(initial_viewport.height), + ); + + // Calculate the actual viewport as per DEVICE-ADAPT § 6 + // If the entire flow tree is invalid, then it will be reflowed anyhow. + let document_shared_lock = document.style_shared_lock(); + self.document_shared_lock = Some(document_shared_lock.clone()); + let author_guard = document_shared_lock.read(); + + let ua_stylesheets = &*UA_STYLESHEETS; + let ua_or_user_guard = ua_stylesheets.shared_lock.read(); + let guards = StylesheetGuards { + author: &author_guard, + ua_or_user: &ua_or_user_guard, + }; + + let had_used_viewport_units = self.stylist.device().used_viewport_units(); + let device = Device::new(MediaType::screen(), initial_viewport, device_pixel_ratio); + let sheet_origins_affected_by_device_change = self.stylist.set_device(device, &guards); + + self.stylist + .force_stylesheet_origins_dirty(sheet_origins_affected_by_device_change); + self.viewport_size = + self.stylist + .viewport_constraints() + .map_or(current_screen_size, |constraints| { + debug!("Viewport constraints: {:?}", constraints); + + // other rules are evaluated against the actual viewport + Size2D::new( + Au::from_f32_px(constraints.size.width), + Au::from_f32_px(constraints.size.height), + ) + }); + + let viewport_size_changed = self.viewport_size != old_viewport_size; + if viewport_size_changed { + if let Some(constraints) = self.stylist.viewport_constraints() { + // let the constellation know about the viewport constraints + rw_data + .constellation_chan + .send(ConstellationMsg::ViewportConstrained( + self.id, + constraints.clone(), + )) + .unwrap(); + } + if had_used_viewport_units { + if let Some(mut data) = element.mutate_data() { + data.hint.insert(RestyleHint::recascade_subtree()); + } + } + } + + { + if self.first_reflow.get() { + debug!("First reflow, rebuilding user and UA rules"); + for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets { + self.stylist + .append_stylesheet(stylesheet.clone(), &ua_or_user_guard); + self.handle_add_stylesheet(&stylesheet.0, &ua_or_user_guard); + } + + if self.stylist.quirks_mode() != QuirksMode::NoQuirks { + self.stylist.append_stylesheet( + ua_stylesheets.quirks_mode_stylesheet.clone(), + &ua_or_user_guard, + ); + self.handle_add_stylesheet( + &ua_stylesheets.quirks_mode_stylesheet.0, + &ua_or_user_guard, + ); + } + } + + if data.stylesheets_changed { + debug!("Doc sheets changed, flushing author sheets too"); + self.stylist + .force_stylesheet_origins_dirty(Origin::Author.into()); + } + } + + if viewport_size_changed { + if let Some(mut flow) = self.try_get_layout_root(element.as_node()) { + LayoutThread::reflow_all_nodes(FlowRef::deref_mut(&mut flow)); + } + } + + debug!( + "Shadow roots in document {:?}", + document.shadow_roots().len() + ); + + // Flush shadow roots stylesheets if dirty. + document.flush_shadow_roots_stylesheets( + &self.stylist.device(), + document.quirks_mode(), + guards.author.clone(), + ); + + let restyles = document.drain_pending_restyles(); + debug!("Draining restyles: {}", restyles.len()); + + let mut map = SnapshotMap::new(); + let elements_with_snapshot: Vec<_> = restyles + .iter() + .filter(|r| r.1.snapshot.is_some()) + .map(|r| r.0) + .collect(); + + for (el, restyle) in restyles { + // Propagate the descendant bit up the ancestors. Do this before + // the restyle calculation so that we can also do it for new + // unstyled nodes, which the descendants bit helps us find. + if let Some(parent) = el.parent_element() { + unsafe { parent.note_dirty_descendant() }; + } + + // If we haven't styled this node yet, we don't need to track a + // restyle. + let style_data = match el.get_data() { + Some(d) => d, + None => { + unsafe { el.unset_snapshot_flags() }; + continue; + }, + }; + + if let Some(s) = restyle.snapshot { + unsafe { el.set_has_snapshot() }; + map.insert(el.as_node().opaque(), s); + } + + let mut style_data = style_data.borrow_mut(); + + // Stash the data on the element for processing by the style system. + style_data.hint.insert(restyle.hint.into()); + style_data.damage = restyle.damage; + debug!("Noting restyle for {:?}: {:?}", el, style_data); + } + + self.stylist.flush(&guards, Some(element), Some(&map)); + + // Create a layout context for use throughout the following passes. + let mut layout_context = self.build_layout_context(guards.clone(), &map); + + let (thread_pool, num_threads) = if self.parallel_flag { + ( + STYLE_THREAD_POOL.style_thread_pool.as_ref(), + STYLE_THREAD_POOL.num_threads, + ) + } else { + (None, 1) + }; + + let traversal = RecalcStyleAndConstructFlows::new(layout_context); + let token = { + let shared = + >::shared_context( + &traversal, + ); + RecalcStyleAndConstructFlows::pre_traverse(element, shared) + }; + + if token.should_traverse() { + // Recalculate CSS styles and rebuild flows and fragments. + profile( + profile_time::ProfilerCategory::LayoutStyleRecalc, + self.profiler_metadata(), + self.time_profiler_chan.clone(), + || { + // Perform CSS selector matching and flow construction. + driver::traverse_dom::( + &traversal, + token, + thread_pool, + ); + }, + ); + // TODO(pcwalton): Measure energy usage of text shaping, perhaps? + let text_shaping_time = + font::get_and_reset_text_shaping_performance_counter() / num_threads; + profile_time::send_profile_data( + profile_time::ProfilerCategory::LayoutTextShaping, + self.profiler_metadata(), + &self.time_profiler_chan, + 0, + text_shaping_time as u64, + 0, + 0, + ); + + // Retrieve the (possibly rebuilt) root flow. + *self.root_flow.borrow_mut() = self.try_get_layout_root(element.as_node()); + } + + for element in elements_with_snapshot { + unsafe { element.unset_snapshot_flags() } + } + + layout_context = traversal.destroy(); + + if self.dump_style_tree { + println!("{:?}", ShowSubtreeDataAndPrimaryValues(element.as_node())); + } + + if self.dump_rule_tree { + layout_context + .style_context + .stylist + .rule_tree() + .dump_stdout(&guards); + } + + // GC the rule tree if some heuristics are met. + unsafe { + layout_context.style_context.stylist.rule_tree().maybe_gc(); + } + + // Perform post-style recalculation layout passes. + if let Some(mut root_flow) = self.root_flow.borrow().clone() { + self.perform_post_style_recalc_layout_passes( + &mut root_flow, + &data.reflow_goal, + Some(&document), + &mut rw_data, + ); + } + + self.first_reflow.set(false); + self.respond_to_query_if_necessary(&data.reflow_goal, &mut *rw_data, &mut layout_context); + } + + fn respond_to_query_if_necessary( + &self, + reflow_goal: &ReflowGoal, + rw_data: &mut LayoutThreadData, + context: &mut LayoutContext, + ) { + match *reflow_goal { + ReflowGoal::LayoutQuery(ref querymsg, _) => match querymsg { + &QueryMsg::ContentBoxQuery(node) => { + rw_data.content_box_response = process_content_box_request(node); + }, + &QueryMsg::ContentBoxesQuery(node) => { + rw_data.content_boxes_response = process_content_boxes_request(node); + }, + &QueryMsg::TextIndexQuery(node, point_in_node) => { + let point_in_node = Point2D::new( + Au::from_f32_px(point_in_node.x), + Au::from_f32_px(point_in_node.y), + ); + rw_data.text_index_response = + TextIndexResponse(rw_data.indexable_text.text_index(node, point_in_node)); + }, + &QueryMsg::NodeGeometryQuery(node) => { + rw_data.client_rect_response = process_node_geometry_request(node); + }, + &QueryMsg::NodeScrollGeometryQuery(node) => { + rw_data.scroll_area_response = process_node_scroll_area_request(node); + }, + &QueryMsg::NodeScrollIdQuery(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) }; + rw_data.resolved_style_response = + process_resolved_style_request(context, node, pseudo, property); + }, + &QueryMsg::OffsetParentQuery(node) => { + rw_data.offset_parent_response = process_offset_parent_query(node); + }, + &QueryMsg::StyleQuery(node) => { + let node = unsafe { ServoLayoutNode::new(&node) }; + rw_data.style_response = process_style_query(node); + }, + &QueryMsg::NodesFromPointQuery(client_point, ref reflow_goal) => { + let mut flags = match reflow_goal { + &NodesFromPointQueryType::Topmost => webrender_api::HitTestFlags::empty(), + &NodesFromPointQueryType::All => webrender_api::HitTestFlags::FIND_ALL, + }; + + // The point we get is not relative to the entire WebRender scene, but to this + // particular pipeline, so we need to tell WebRender about that. + flags.insert(webrender_api::HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT); + + let client_point = webrender_api::units::WorldPoint::from_untyped(client_point); + let results = self.webrender_api.hit_test( + self.webrender_document, + Some(self.id.to_webrender()), + client_point, + flags, + ); + + rw_data.nodes_from_point_response = results + .items + .iter() + .map(|item| UntrustedNodeAddress(item.tag.0 as *const c_void)) + .collect() + }, + &QueryMsg::ElementInnerTextQuery(node) => { + let node = unsafe { ServoLayoutNode::new(&node) }; + rw_data.element_inner_text_response = + process_element_inner_text_query(node, &rw_data.indexable_text); + }, + }, + ReflowGoal::Full | ReflowGoal::TickAnimations => {}, + } + } + + fn set_scroll_states<'a, 'b>( + &mut self, + new_scroll_states: Vec, + possibly_locked_rw_data: &mut RwData<'a, 'b>, + ) { + let mut rw_data = possibly_locked_rw_data.lock(); + let mut script_scroll_states = vec![]; + let mut layout_scroll_states = HashMap::new(); + for new_state in &new_scroll_states { + let offset = new_state.scroll_offset; + layout_scroll_states.insert(new_state.scroll_id, offset); + + if new_state.scroll_id.is_root() { + script_scroll_states.push((UntrustedNodeAddress::from_id(0), offset)) + } else if let Some(node_id) = node_id_from_scroll_id(new_state.scroll_id.0 as usize) { + script_scroll_states.push((UntrustedNodeAddress::from_id(node_id), offset)) + } + } + let _ = self + .script_chan + .send(ConstellationControlMsg::SetScrollState( + self.id, + script_scroll_states, + )); + rw_data.scroll_offsets = layout_scroll_states + } + + fn tick_all_animations<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) { + let mut rw_data = possibly_locked_rw_data.lock(); + self.tick_animations(&mut rw_data); + } + + fn tick_animations(&mut self, rw_data: &mut LayoutThreadData) { + if self.relayout_event { + println!( + "**** pipeline={}\tForDisplay\tSpecial\tAnimationTick", + self.id + ); + } + + if let Some(mut root_flow) = self.root_flow.borrow().clone() { + // Unwrap here should not panic since self.root_flow is only ever set to Some(_) + // in handle_reflow() where self.document_shared_lock is as well. + let author_shared_lock = self.document_shared_lock.clone().unwrap(); + let author_guard = author_shared_lock.read(); + let ua_or_user_guard = UA_STYLESHEETS.shared_lock.read(); + let _guards = StylesheetGuards { + author: &author_guard, + ua_or_user: &ua_or_user_guard, + }; + + self.perform_post_style_recalc_layout_passes( + &mut root_flow, + &ReflowGoal::TickAnimations, + None, + &mut *rw_data, + ); + } + } + + fn perform_post_style_recalc_layout_passes( + &self, + root_flow: &mut FlowRef, + reflow_goal: &ReflowGoal, + document: Option<&ServoLayoutDocument>, + rw_data: &mut LayoutThreadData, + ) { + profile( + profile_time::ProfilerCategory::LayoutRestyleDamagePropagation, + self.profiler_metadata(), + self.time_profiler_chan.clone(), + || {}, + ); + + self.perform_post_main_layout_passes(root_flow, reflow_goal, document, rw_data); + } + + fn perform_post_main_layout_passes( + &self, + mut root_flow: &mut FlowRef, + reflow_goal: &ReflowGoal, + document: Option<&ServoLayoutDocument>, + rw_data: &mut LayoutThreadData, + ) { + // Build the display list if necessary, and send it to the painter. + self.compute_abs_pos_and_build_display_list( + reflow_goal, + document, + FlowRef::deref_mut(&mut root_flow), + rw_data, + ); + + self.generation.set(self.generation.get() + 1); + } + + fn reflow_all_nodes(flow: &mut dyn Flow) { + debug!("reflowing all nodes!"); + flow.mut_base().restyle_damage.insert( + ServoRestyleDamage::REPAINT | + ServoRestyleDamage::STORE_OVERFLOW | + ServoRestyleDamage::REFLOW | + ServoRestyleDamage::REPOSITION, + ); + } + + /// Returns profiling information which is passed to the time profiler. + fn profiler_metadata(&self) -> Option { + Some(TimerMetadata { + url: self.url.to_string(), + iframe: if self.is_iframe { + TimerMetadataFrameType::IFrame + } else { + TimerMetadataFrameType::RootWindow + }, + incremental: if self.first_reflow.get() { + TimerMetadataReflowType::FirstReflow + } else { + TimerMetadataReflowType::Incremental + }, + }) + } +} + +impl ProfilerMetadataFactory for LayoutThread { + fn new_metadata(&self) -> Option { + self.profiler_metadata() + } +} + +fn get_ua_stylesheets() -> Result { + fn parse_ua_stylesheet( + shared_lock: &SharedRwLock, + filename: &str, + content: &[u8], + ) -> Result { + Ok(DocumentStyleSheet(ServoArc::new(Stylesheet::from_bytes( + content, + ServoUrl::parse(&format!("chrome://resources/{:?}", filename)).unwrap(), + None, + None, + Origin::UserAgent, + MediaList::empty(), + shared_lock.clone(), + None, + None, + QuirksMode::NoQuirks, + )))) + } + + let shared_lock = &GLOBAL_STYLE_DATA.shared_lock; + + // FIXME: presentational-hints.css should be at author origin with zero specificity. + // (Does it make a difference?) + let mut user_or_user_agent_stylesheets = vec![ + parse_ua_stylesheet( + &shared_lock, + "user-agent.css", + &resources::read_bytes(Resource::UserAgentCSS), + )?, + parse_ua_stylesheet( + &shared_lock, + "servo.css", + &resources::read_bytes(Resource::ServoCSS), + )?, + parse_ua_stylesheet( + &shared_lock, + "presentational-hints.css", + &resources::read_bytes(Resource::PresentationalHintsCSS), + )?, + ]; + + for &(ref contents, ref url) in &opts::get().user_stylesheets { + user_or_user_agent_stylesheets.push(DocumentStyleSheet(ServoArc::new( + Stylesheet::from_bytes( + &contents, + url.clone(), + None, + None, + Origin::User, + MediaList::empty(), + shared_lock.clone(), + None, + Some(&RustLogReporter), + QuirksMode::NoQuirks, + ), + ))); + } + + let quirks_mode_stylesheet = parse_ua_stylesheet( + &shared_lock, + "quirks-mode.css", + &resources::read_bytes(Resource::QuirksModeCSS), + )?; + + Ok(UserAgentStylesheets { + shared_lock: shared_lock.clone(), + user_or_user_agent_stylesheets: user_or_user_agent_stylesheets, + quirks_mode_stylesheet: quirks_mode_stylesheet, + }) +} + +lazy_static! { + static ref UA_STYLESHEETS: UserAgentStylesheets = { + match get_ua_stylesheets() { + Ok(stylesheets) => stylesheets, + Err(filename) => { + error!("Failed to load UA stylesheet {}!", filename); + process::exit(1); + }, + } + }; +} + +struct RegisteredPainterImpl { + painter: Box, + name: Atom, + // FIXME: Should be a PrecomputedHashMap. + properties: FxHashMap, +} + +impl SpeculativePainter for RegisteredPainterImpl { + fn speculatively_draw_a_paint_image( + &self, + properties: Vec<(Atom, String)>, + arguments: Vec, + ) { + self.painter + .speculatively_draw_a_paint_image(properties, arguments); + } +} + +impl RegisteredSpeculativePainter for RegisteredPainterImpl { + fn properties(&self) -> &FxHashMap { + &self.properties + } + fn name(&self) -> Atom { + self.name.clone() + } +} + +impl Painter for RegisteredPainterImpl { + fn draw_a_paint_image( + &self, + size: Size2D, + device_pixel_ratio: Scale, + properties: Vec<(Atom, String)>, + arguments: Vec, + ) -> Result { + self.painter + .draw_a_paint_image(size, device_pixel_ratio, properties, arguments) + } +} + +struct RegisteredPaintersImpl(FnvHashMap); + +impl RegisteredSpeculativePainters for RegisteredPaintersImpl { + fn get(&self, name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> { + self.0 + .get(&name) + .map(|painter| painter as &dyn RegisteredSpeculativePainter) } } diff --git a/components/style/properties/longhands/inherited_text.mako.rs b/components/style/properties/longhands/inherited_text.mako.rs index 01e84cee2ec..f45369c27c7 100644 --- a/components/style/properties/longhands/inherited_text.mako.rs +++ b/components/style/properties/longhands/inherited_text.mako.rs @@ -196,7 +196,7 @@ ${helpers.predefined_type( spec="https://drafts.csswg.org/css-text/#propdef-white-space" servo_restyle_damage="rebuild_and_reflow" > - % if engine == "servo-2013": + % if engine in ["servo-2013", "servo-2020"]: impl SpecifiedValue { pub fn allow_wrap(&self) -> bool { match *self {