diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index 390b4664e60..e8b7b6f5402 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -428,13 +428,14 @@ impl BoxTree { acc.union(&child_overflow) }); - FragmentTree { + FragmentTree::new( + layout_context, root_fragments, scrollable_overflow, - initial_containing_block: physical_containing_block, - canvas_background: self.canvas_background.clone(), - viewport_scroll_sensitivity: self.viewport_scroll_sensitivity, - } + physical_containing_block, + self.canvas_background.clone(), + self.viewport_scroll_sensitivity, + ) } } diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs index 30be154caf1..0e83c0d71a6 100644 --- a/components/layout/fragment_tree/box_fragment.rs +++ b/components/layout/fragment_tree/box_fragment.rs @@ -65,6 +65,10 @@ pub(crate) struct BoxFragment { /// does not include padding, border, or margin -- it only includes content. pub content_rect: PhysicalRect, + /// This [`BoxFragment`]'s containing block rectangle in coordinates relative to + /// the initial containing block, but not taking into account any transforms. + pub cumulative_containing_block_rect: PhysicalRect, + pub padding: PhysicalSides, pub border: PhysicalSides, pub margin: PhysicalSides, @@ -120,6 +124,7 @@ impl BoxFragment { style, children, content_rect, + cumulative_containing_block_rect: Default::default(), padding, border, margin, @@ -195,6 +200,8 @@ impl BoxFragment { self } + /// Get the scrollable overflow for this [`BoxFragment`] relative to its + /// containing block. pub fn scrollable_overflow(&self) -> PhysicalRect { let physical_padding_rect = self.padding_rect(); let content_origin = self.content_rect.origin.to_vector(); @@ -205,6 +212,10 @@ impl BoxFragment { ) } + pub fn offset_by_containing_block(&self, rect: &PhysicalRect) -> PhysicalRect { + rect.translate(self.cumulative_containing_block_rect.origin.to_vector()) + } + pub(crate) fn padding_rect(&self) -> PhysicalRect { self.content_rect.outer_rect(self.padding) } @@ -278,10 +289,7 @@ impl BoxFragment { overflow } - pub(crate) fn calculate_resolved_insets_if_positioned( - &self, - containing_block: &PhysicalRect, - ) -> PhysicalSides { + pub(crate) fn calculate_resolved_insets_if_positioned(&self) -> PhysicalSides { let position = self.style.get_box().position; debug_assert_ne!( position, @@ -309,7 +317,10 @@ impl BoxFragment { // used value. Otherwise the resolved value is the computed value." // https://drafts.csswg.org/cssom/#resolved-values let insets = self.style.physical_box_offsets(); - let (cb_width, cb_height) = (containing_block.width(), containing_block.height()); + let (cb_width, cb_height) = ( + self.cumulative_containing_block_rect.width(), + self.cumulative_containing_block_rect.height(), + ); if position == ComputedPosition::Relative { let get_resolved_axis = |start: &LengthPercentageOrAuto, end: &LengthPercentageOrAuto, @@ -394,4 +405,8 @@ impl BoxFragment { _ => CollapsedBlockMargins::zero(), } } + + pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect) { + self.cumulative_containing_block_rect = *containing_block; + } } diff --git a/components/layout/fragment_tree/fragment.rs b/components/layout/fragment_tree/fragment.rs index d0d1b9b1104..c08ddae55e9 100644 --- a/components/layout/fragment_tree/fragment.rs +++ b/components/layout/fragment_tree/fragment.rs @@ -112,6 +112,7 @@ impl Fragment { Fragment::Float(fragment) => fragment.borrow().base.clone(), }) } + pub(crate) fn mutate_content_rect(&mut self, callback: impl FnOnce(&mut PhysicalRect)) { match self { Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { @@ -124,6 +125,26 @@ impl Fragment { } } + pub(crate) fn set_containing_block(&self, containing_block: &PhysicalRect) { + match self { + Fragment::Box(box_fragment) => box_fragment + .borrow_mut() + .set_containing_block(containing_block), + Fragment::Float(float_fragment) => float_fragment + .borrow_mut() + .set_containing_block(containing_block), + Fragment::Positioning(_) => {}, + Fragment::AbsoluteOrFixedPositioned(hoisted_shared_fragment) => { + if let Some(ref fragment) = hoisted_shared_fragment.borrow().fragment { + fragment.set_containing_block(containing_block); + } + }, + Fragment::Text(_) => {}, + Fragment::Image(_) => {}, + Fragment::IFrame(_) => {}, + } + } + pub fn tag(&self) -> Option { self.base().and_then(|base| base.tag) } @@ -146,12 +167,12 @@ impl Fragment { } } - pub fn scrolling_area(&self, containing_block: &PhysicalRect) -> PhysicalRect { + pub fn scrolling_area(&self) -> PhysicalRect { match self { - Fragment::Box(fragment) | Fragment::Float(fragment) => fragment - .borrow() - .scrollable_overflow() - .translate(containing_block.origin.to_vector()), + Fragment::Box(fragment) | Fragment::Float(fragment) => { + let fragment = fragment.borrow(); + fragment.offset_by_containing_block(&fragment.scrollable_overflow()) + }, _ => self.scrollable_overflow(), } } diff --git a/components/layout/fragment_tree/fragment_tree.rs b/components/layout/fragment_tree/fragment_tree.rs index bb3c659466c..589ae69e8e5 100644 --- a/components/layout/fragment_tree/fragment_tree.rs +++ b/components/layout/fragment_tree/fragment_tree.rs @@ -13,6 +13,7 @@ use style::dom::OpaqueNode; use webrender_api::units; use super::{ContainingBlockManager, Fragment, Tag}; +use crate::context::LayoutContext; use crate::display_list::StackingContext; use crate::flow::CanvasBackground; use crate::geom::{PhysicalPoint, PhysicalRect}; @@ -44,6 +45,58 @@ pub struct FragmentTree { } impl FragmentTree { + pub(crate) fn new( + layout_context: &LayoutContext, + root_fragments: Vec, + scrollable_overflow: PhysicalRect, + initial_containing_block: PhysicalRect, + canvas_background: CanvasBackground, + viewport_scroll_sensitivity: AxesScrollSensitivity, + ) -> Self { + let fragment_tree = Self { + root_fragments, + scrollable_overflow, + initial_containing_block, + canvas_background, + viewport_scroll_sensitivity, + }; + + // As part of building the fragment tree, we want to stop animating elements and + // pseudo-elements that used to be animating or had animating images attached to + // them. Create a set of all elements that used to be animating. + let mut animations = layout_context.style_context.animations.sets.write(); + let mut invalid_animating_nodes: FxHashSet<_> = animations.keys().cloned().collect(); + let mut image_animations = layout_context.node_image_animation_map.write().to_owned(); + let mut invalid_image_animating_nodes: FxHashSet<_> = image_animations + .keys() + .cloned() + .map(|node| AnimationSetKey::new(node, None)) + .collect(); + + fragment_tree.find(|fragment, _level, containing_block| { + if let Some(tag) = fragment.tag() { + invalid_animating_nodes.remove(&AnimationSetKey::new(tag.node, tag.pseudo)); + invalid_image_animating_nodes.remove(&AnimationSetKey::new(tag.node, tag.pseudo)); + } + + fragment.set_containing_block(containing_block); + None::<()> + }); + + // Cancel animations for any elements and pseudo-elements that are no longer found + // in the fragment tree. + for node in &invalid_animating_nodes { + if let Some(state) = animations.get_mut(node) { + state.cancel_all_animations(); + } + } + for node in &invalid_image_animating_nodes { + image_animations.remove(&node.node); + } + + fragment_tree + } + pub(crate) fn build_display_list( &self, builder: &mut crate::display_list::DisplayListBuilder, @@ -86,14 +139,6 @@ impl FragmentTree { .find_map(|child| child.find(&info, 0, &mut process_func)) } - pub fn remove_nodes_in_fragment_tree_from_set(&self, set: &mut FxHashSet) { - self.find(|fragment, _, _| { - let tag = fragment.tag()?; - set.remove(&AnimationSetKey::new(tag.node, tag.pseudo)); - None::<()> - }); - } - /// Get the vector of rectangles that surrounds the fragments of the node with the given address. /// This function answers the `getClientRects()` query and the union of the rectangles answers /// the `getBoundingClientRect()` query. @@ -173,22 +218,8 @@ impl FragmentTree { pub fn get_scrolling_area_for_viewport(&self) -> PhysicalRect { let mut scroll_area = self.initial_containing_block; for fragment in self.root_fragments.iter() { - scroll_area = fragment - .scrolling_area(&self.initial_containing_block) - .union(&scroll_area); + scroll_area = fragment.scrolling_area().union(&scroll_area); } scroll_area } - - pub fn get_scrolling_area_for_node(&self, requested_node: OpaqueNode) -> PhysicalRect { - let tag_to_find = Tag::new(requested_node); - let scroll_area = self.find(|fragment, _, containing_block| { - if fragment.tag() == Some(tag_to_find) { - Some(fragment.scrolling_area(containing_block)) - } else { - None - } - }); - scroll_area.unwrap_or_else(PhysicalRect::::zero) - } } diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index c661f2c2c7b..361760692d2 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -23,7 +23,7 @@ use euclid::{Point2D, Scale, Size2D, Vector2D}; use fnv::FnvHashMap; use fonts::{FontContext, FontContextWebFontMethods}; use fonts_traits::StylesheetWebFontLoadFinishedCallback; -use fxhash::{FxHashMap, FxHashSet}; +use fxhash::FxHashMap; use ipc_channel::ipc::IpcSender; use log::{debug, error}; use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps}; @@ -37,15 +37,15 @@ use profile_traits::{path, time_profile}; use rayon::ThreadPool; use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use script_layout_interface::{ - ImageAnimationState, Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, - OffsetParentResponse, ReflowGoal, ReflowRequest, ReflowResult, TrustedNodeAddress, + Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, OffsetParentResponse, ReflowGoal, + ReflowRequest, ReflowResult, TrustedNodeAddress, }; use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter, ScriptThreadMessage}; use servo_arc::Arc as ServoArc; use servo_config::opts::{self, DebugOptions}; use servo_config::pref; use servo_url::ServoUrl; -use style::animation::{AnimationSetKey, DocumentAnimationSet}; +use style::animation::DocumentAnimationSet; use style::context::{ QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters, SharedStyleContext, }; @@ -330,14 +330,7 @@ impl Layout for LayoutThread { TraversalFlags::empty(), ); - let fragment_tree = self.fragment_tree.borrow().clone(); - process_resolved_style_request( - &shared_style_context, - node, - &pseudo, - &property_id, - fragment_tree, - ) + process_resolved_style_request(&shared_style_context, node, &pseudo, &property_id) } #[cfg_attr( @@ -380,7 +373,8 @@ impl Layout for LayoutThread { feature = "tracing", tracing::instrument(skip_all, fields(servo_profiling = true), level = "trace") )] - fn query_scrolling_area(&self, node: Option) -> UntypedRect { + fn query_scrolling_area(&self, node: Option) -> UntypedRect { + let node = node.map(|node| unsafe { ServoLayoutNode::new(&node) }); process_node_scroll_area_request(node, self.fragment_tree.borrow().clone()) } @@ -790,18 +784,6 @@ impl LayoutThread { run_layout() }); - Self::cancel_animations_for_nodes_not_in_fragment_tree( - &recalc_style_traversal.context().style_context.animations, - &fragment_tree, - ); - Self::cancel_image_animation_for_nodes_not_in_fragment_tree( - recalc_style_traversal - .context() - .node_image_animation_map - .clone(), - &fragment_tree, - ); - *self.fragment_tree.borrow_mut() = Some(fragment_tree); if self.debug.dump_style_tree { @@ -922,42 +904,6 @@ impl LayoutThread { }) } - /// Cancel animations for any nodes which have been removed from fragment tree. - /// TODO(mrobinson): We should look into a way of doing this during flow tree construction. - /// This also doesn't yet handles nodes that have been reparented. - fn cancel_animations_for_nodes_not_in_fragment_tree( - animations: &DocumentAnimationSet, - root: &FragmentTree, - ) { - // Assume all nodes have been removed until proven otherwise. - let mut animations = animations.sets.write(); - let mut invalid_nodes = animations.keys().cloned().collect(); - root.remove_nodes_in_fragment_tree_from_set(&mut invalid_nodes); - - // Cancel animations for any nodes that are no longer in the fragment tree. - for node in &invalid_nodes { - if let Some(state) = animations.get_mut(node) { - state.cancel_all_animations(); - } - } - } - - fn cancel_image_animation_for_nodes_not_in_fragment_tree( - image_animation_set: Arc>>, - root: &FragmentTree, - ) { - let mut image_animations = image_animation_set.write().to_owned(); - let mut invalid_nodes: FxHashSet = image_animations - .keys() - .cloned() - .map(|node| AnimationSetKey::new(node, None)) - .collect(); - root.remove_nodes_in_fragment_tree_from_set(&mut invalid_nodes); - for node in &invalid_nodes { - image_animations.remove(&node.node); - } - } - fn viewport_did_change(&mut self, viewport_details: ViewportDetails) -> bool { let new_pixel_ratio = viewport_details.hidpi_scale_factor.get(); let new_viewport_size = Size2D::new( diff --git a/components/layout/query.rs b/components/layout/query.rs index 08b264deea9..3409f7c1923 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -83,14 +83,21 @@ pub fn process_node_geometry_request( } /// -pub fn process_node_scroll_area_request( - requested_node: Option, +pub fn process_node_scroll_area_request<'dom>( + requested_node: Option + 'dom>, fragment_tree: Option>, ) -> Rect { - let rect = match (fragment_tree, requested_node) { - (Some(tree), Some(node)) => tree.get_scrolling_area_for_node(node), - (Some(tree), None) => tree.get_scrolling_area_for_viewport(), - _ => return Rect::zero(), + let Some(tree) = fragment_tree else { + return Rect::zero(); + }; + + let rect = match requested_node { + Some(node) => node + .fragments_for_pseudo(None) + .first() + .map(Fragment::scrolling_area) + .unwrap_or_default(), + None => tree.get_scrolling_area_for_viewport(), }; Rect::new( @@ -109,7 +116,6 @@ pub fn process_resolved_style_request<'dom>( node: impl LayoutNode<'dom> + 'dom, pseudo: &Option, property: &PropertyId, - fragment_tree: Option>, ) -> String { if !node.as_element().unwrap().has_data() { return process_resolved_style_request_for_unstyled_node(context, node, pseudo, property); @@ -161,8 +167,6 @@ pub fn process_resolved_style_request<'dom>( _ => style.computed_value_to_string(PropertyDeclarationId::Longhand(longhand_id)), }; - let tag_to_find = Tag::new_pseudo(node.opaque(), *pseudo); - // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle // Here we are trying to conform to the specification that says that getComputedStyle // should return the used values in certain circumstances. For size and positional @@ -191,107 +195,87 @@ pub fn process_resolved_style_request<'dom>( return computed_style(None); } - let resolve_for_fragment = - |fragment: &Fragment, containing_block: Option<&PhysicalRect>| { - let (content_rect, margins, padding, specific_layout_info) = match fragment { - Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { - let box_fragment = box_fragment.borrow(); - if style.get_box().position != Position::Static { - let resolved_insets = || { - box_fragment - .calculate_resolved_insets_if_positioned(containing_block.unwrap()) - }; - match longhand_id { - LonghandId::Top => return resolved_insets().top.to_css_string(), - LonghandId::Right => { - return resolved_insets().right.to_css_string(); - }, - LonghandId::Bottom => { - return resolved_insets().bottom.to_css_string(); - }, - LonghandId::Left => { - return resolved_insets().left.to_css_string(); - }, - _ => {}, - } - } - let content_rect = box_fragment.content_rect; - let margins = box_fragment.margin; - let padding = box_fragment.padding; - let specific_layout_info = box_fragment.specific_layout_info.clone(); - (content_rect, margins, padding, specific_layout_info) - }, - Fragment::Positioning(positioning_fragment) => { - let content_rect = positioning_fragment.borrow().rect; - ( - content_rect, - SideOffsets2D::zero(), - SideOffsets2D::zero(), - None, - ) - }, - _ => return computed_style(Some(fragment)), - }; - - // https://drafts.csswg.org/css-grid/#resolved-track-list - // > The grid-template-rows and grid-template-columns properties are - // > resolved value special case properties. - // - // > When an element generates a grid container box... - if display.inside() == DisplayInside::Grid { - if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info { - if let Some(value) = resolve_grid_template(&info, style, longhand_id) { - return value; + let resolve_for_fragment = |fragment: &Fragment| { + let (content_rect, margins, padding, specific_layout_info) = match fragment { + Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => { + let box_fragment = box_fragment.borrow(); + if style.get_box().position != Position::Static { + let resolved_insets = || box_fragment.calculate_resolved_insets_if_positioned(); + match longhand_id { + LonghandId::Top => return resolved_insets().top.to_css_string(), + LonghandId::Right => { + return resolved_insets().right.to_css_string(); + }, + LonghandId::Bottom => { + return resolved_insets().bottom.to_css_string(); + }, + LonghandId::Left => { + return resolved_insets().left.to_css_string(); + }, + _ => {}, } } - } - - // https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-height - // > If the property applies to the element or pseudo-element and the resolved value of the - // > display property is not none or contents, then the resolved value is the used value. - // > Otherwise the resolved value is the computed value. - // - // However, all browsers ignore that for margin and padding properties, and resolve to a length - // even if the property doesn't apply: https://github.com/w3c/csswg-drafts/issues/10391 - match longhand_id { - LonghandId::Width if resolved_size_should_be_used_value(fragment) => { - content_rect.size.width - }, - LonghandId::Height if resolved_size_should_be_used_value(fragment) => { - content_rect.size.height - }, - LonghandId::MarginBottom => margins.bottom, - LonghandId::MarginTop => margins.top, - LonghandId::MarginLeft => margins.left, - LonghandId::MarginRight => margins.right, - LonghandId::PaddingBottom => padding.bottom, - LonghandId::PaddingTop => padding.top, - LonghandId::PaddingLeft => padding.left, - LonghandId::PaddingRight => padding.right, - _ => return computed_style(Some(fragment)), - } - .to_css_string() + let content_rect = box_fragment.content_rect; + let margins = box_fragment.margin; + let padding = box_fragment.padding; + let specific_layout_info = box_fragment.specific_layout_info.clone(); + (content_rect, margins, padding, specific_layout_info) + }, + Fragment::Positioning(positioning_fragment) => { + let content_rect = positioning_fragment.borrow().rect; + ( + content_rect, + SideOffsets2D::zero(), + SideOffsets2D::zero(), + None, + ) + }, + _ => return computed_style(Some(fragment)), }; - if !matches!( - longhand_id, - LonghandId::Top | LonghandId::Bottom | LonghandId::Left | LonghandId::Right - ) { - if let Some(fragment) = node.fragments_for_pseudo(*pseudo).first() { - return resolve_for_fragment(fragment, None); - } - } - - fragment_tree - .and_then(|fragment_tree| { - fragment_tree.find(|fragment, _, containing_block| { - if Some(tag_to_find) == fragment.tag() { - Some(resolve_for_fragment(fragment, Some(containing_block))) - } else { - None + // https://drafts.csswg.org/css-grid/#resolved-track-list + // > The grid-template-rows and grid-template-columns properties are + // > resolved value special case properties. + // + // > When an element generates a grid container box... + if display.inside() == DisplayInside::Grid { + if let Some(SpecificLayoutInfo::Grid(info)) = specific_layout_info { + if let Some(value) = resolve_grid_template(&info, style, longhand_id) { + return value; } - }) - }) + } + } + + // https://drafts.csswg.org/cssom/#resolved-value-special-case-property-like-height + // > If the property applies to the element or pseudo-element and the resolved value of the + // > display property is not none or contents, then the resolved value is the used value. + // > Otherwise the resolved value is the computed value. + // + // However, all browsers ignore that for margin and padding properties, and resolve to a length + // even if the property doesn't apply: https://github.com/w3c/csswg-drafts/issues/10391 + match longhand_id { + LonghandId::Width if resolved_size_should_be_used_value(fragment) => { + content_rect.size.width + }, + LonghandId::Height if resolved_size_should_be_used_value(fragment) => { + content_rect.size.height + }, + LonghandId::MarginBottom => margins.bottom, + LonghandId::MarginTop => margins.top, + LonghandId::MarginLeft => margins.left, + LonghandId::MarginRight => margins.right, + LonghandId::PaddingBottom => padding.bottom, + LonghandId::PaddingTop => padding.top, + LonghandId::PaddingLeft => padding.left, + LonghandId::PaddingRight => padding.right, + _ => return computed_style(Some(fragment)), + } + .to_css_string() + }; + + node.fragments_for_pseudo(*pseudo) + .first() + .map(resolve_for_fragment) .unwrap_or_else(|| computed_style(None)) } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 96176132b6b..efe61007ef8 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -2281,11 +2281,12 @@ impl Window { node: Option<&Node>, can_gc: CanGc, ) -> UntypedRect { - let opaque = node.map(|node| node.to_opaque()); if !self.layout_reflow(QueryMsg::ScrollingAreaQuery, can_gc) { return Rect::zero(); } - self.layout.borrow().query_scrolling_area(opaque) + self.layout + .borrow() + .query_scrolling_area(node.map(Node::to_trusted_node_address)) } pub(crate) fn scroll_offset_query(&self, node: &Node) -> Vector2D { diff --git a/components/shared/script_layout/lib.rs b/components/shared/script_layout/lib.rs index 9b052642c32..69e577e139d 100644 --- a/components/shared/script_layout/lib.rs +++ b/components/shared/script_layout/lib.rs @@ -265,7 +265,7 @@ pub trait Layout { animations: DocumentAnimationSet, animation_timeline_value: f64, ) -> Option>; - fn query_scrolling_area(&self, node: Option) -> Rect; + fn query_scrolling_area(&self, node: Option) -> Rect; fn query_text_indext(&self, node: OpaqueNode, point: Point2D) -> Option; }