layout: Only create a LayoutContext if restyling (#37726)

The creation of `LayoutContext` does more work than necessary if layout
just needs to do something like make a display list and not restyle and
relayout. This change makes it so that these kind of non-restyle layouts
do not need to create a display list. In addition, the creation of
`LayoutContext` is better encapsulate

Testing: This should not change observable behavior and is thus covered
by existing WPT tests.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2025-06-27 17:01:30 +02:00 committed by GitHub
parent 5e44582277
commit 9232b0f550
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 208 additions and 196 deletions

View file

@ -4,7 +4,6 @@
use std::sync::Arc; use std::sync::Arc;
use base::id::PipelineId;
use euclid::Size2D; use euclid::Size2D;
use fnv::FnvHashMap; use fnv::FnvHashMap;
use fonts::FontContext; use fonts::FontContext;
@ -26,10 +25,8 @@ use webrender_api::units::{DeviceIntSize, DeviceSize};
pub(crate) type CachedImageOrError = Result<CachedImage, ResolveImageError>; pub(crate) type CachedImageOrError = Result<CachedImage, ResolveImageError>;
pub struct LayoutContext<'a> { pub(crate) struct LayoutContext<'a> {
pub id: PipelineId,
pub use_rayon: bool, pub use_rayon: bool,
pub origin: ImmutableOrigin,
/// Bits shared by the layout and style system. /// Bits shared by the layout and style system.
pub style_context: SharedStyleContext<'a>, pub style_context: SharedStyleContext<'a>,
@ -37,31 +34,12 @@ pub struct LayoutContext<'a> {
/// A FontContext to be used during layout. /// A FontContext to be used during layout.
pub font_context: Arc<FontContext>, pub font_context: Arc<FontContext>,
/// Reference to the script thread image cache.
pub image_cache: Arc<dyn ImageCache>,
/// A list of in-progress image loads to be shared with the script thread.
pub pending_images: Mutex<Vec<PendingImage>>,
/// A list of fully loaded vector images that need to be rasterized to a specific
/// size determined by layout. This will be shared with the script thread.
pub pending_rasterization_images: Mutex<Vec<PendingRasterizationImage>>,
/// A collection of `<iframe>` sizes to send back to script. /// A collection of `<iframe>` sizes to send back to script.
pub iframe_sizes: Mutex<IFrameSizes>, pub iframe_sizes: Mutex<IFrameSizes>,
// A cache that maps image resources used in CSS (e.g as the `url()` value /// An [`ImageResolver`] used for resolving images during box and fragment
// for `background-image` or `content` property) to the final resolved image data. /// tree construction. Later passed to display list construction.
pub resolved_images_cache: pub image_resolver: Arc<ImageResolver>,
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), CachedImageOrError>>>,
/// A shared reference to script's map of DOM nodes with animated images. This is used
/// to manage image animations in script and inform the script about newly animating
/// nodes.
pub node_to_animating_image_map: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>,
/// The DOM node that is highlighted by the devtools inspector, if any
pub highlighted_dom_node: Option<OpaqueNode>,
} }
pub enum ResolvedImage<'a> { pub enum ResolvedImage<'a> {
@ -74,15 +52,6 @@ pub enum ResolvedImage<'a> {
}, },
} }
impl Drop for LayoutContext<'_> {
fn drop(&mut self) {
if !std::thread::panicking() {
assert!(self.pending_images.lock().is_empty());
assert!(self.pending_rasterization_images.lock().is_empty());
}
}
}
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum ResolveImageError { pub enum ResolveImageError {
LoadError, LoadError,
@ -103,12 +72,44 @@ pub(crate) enum LayoutImageCacheResult {
LoadError, LoadError,
} }
impl LayoutContext<'_> { pub(crate) struct ImageResolver {
#[inline(always)] /// The origin of the `Document` that this [`ImageResolver`] resolves images for.
pub fn shared_context(&self) -> &SharedStyleContext { pub origin: ImmutableOrigin,
&self.style_context
/// Reference to the script thread image cache.
pub image_cache: Arc<dyn ImageCache>,
/// A list of in-progress image loads to be shared with the script thread.
pub pending_images: Mutex<Vec<PendingImage>>,
/// A list of fully loaded vector images that need to be rasterized to a specific
/// size determined by layout. This will be shared with the script thread.
pub pending_rasterization_images: Mutex<Vec<PendingRasterizationImage>>,
/// A shared reference to script's map of DOM nodes with animated images. This is used
/// to manage image animations in script and inform the script about newly animating
/// nodes.
pub node_to_animating_image_map: Arc<RwLock<FxHashMap<OpaqueNode, ImageAnimationState>>>,
// A cache that maps image resources used in CSS (e.g as the `url()` value
// for `background-image` or `content` property) to the final resolved image data.
pub resolved_images_cache:
Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), CachedImageOrError>>>,
/// The current animation timeline value used to properly initialize animating images.
pub animation_timeline_value: f64,
} }
impl Drop for ImageResolver {
fn drop(&mut self) {
if !std::thread::panicking() {
assert!(self.pending_images.lock().is_empty());
assert!(self.pending_rasterization_images.lock().is_empty());
}
}
}
impl ImageResolver {
pub(crate) fn get_or_request_image_or_meta( pub(crate) fn get_or_request_image_or_meta(
&self, &self,
node: OpaqueNode, node: OpaqueNode,
@ -155,18 +156,14 @@ impl LayoutContext<'_> {
} }
} }
pub fn handle_animated_image(&self, node: OpaqueNode, image: Arc<RasterImage>) { pub(crate) fn handle_animated_image(&self, node: OpaqueNode, image: Arc<RasterImage>) {
let mut map = self.node_to_animating_image_map.write(); let mut map = self.node_to_animating_image_map.write();
if !image.should_animate() { if !image.should_animate() {
map.remove(&node); map.remove(&node);
return; return;
} }
let new_image_animation_state = || { let new_image_animation_state =
ImageAnimationState::new( || ImageAnimationState::new(image.clone(), self.animation_timeline_value);
image.clone(),
self.shared_context().current_time_for_animations,
)
};
let entry = map.entry(node).or_insert_with(new_image_animation_state); let entry = map.entry(node).or_insert_with(new_image_animation_state);
@ -218,7 +215,7 @@ impl LayoutContext<'_> {
} }
} }
pub fn rasterize_vector_image( pub(crate) fn rasterize_vector_image(
&self, &self,
image_id: PendingImageId, image_id: PendingImageId,
size: DeviceIntSize, size: DeviceIntSize,
@ -237,7 +234,7 @@ impl LayoutContext<'_> {
result result
} }
pub fn resolve_image<'a>( pub(crate) fn resolve_image<'a>(
&self, &self,
node: Option<OpaqueNode>, node: Option<OpaqueNode>,
image: &'a Image, image: &'a Image,

View file

@ -11,9 +11,10 @@ use base::id::ScrollTreeNodeId;
use clip::{Clip, ClipId}; use clip::{Clip, ClipId};
use compositing_traits::display_list::{CompositorDisplayListInfo, SpatialTreeNodeInfo}; use compositing_traits::display_list::{CompositorDisplayListInfo, SpatialTreeNodeInfo};
use embedder_traits::Cursor; use embedder_traits::Cursor;
use euclid::{Point2D, SideOffsets2D, Size2D, UnknownUnit, Vector2D}; use euclid::{Point2D, Scale, SideOffsets2D, Size2D, UnknownUnit, Vector2D};
use fonts::GlyphStore; use fonts::GlyphStore;
use gradient::WebRenderGradient; use gradient::WebRenderGradient;
use layout_api::ReflowRequest;
use net_traits::image_cache::Image as CachedImage; use net_traits::image_cache::Image as CachedImage;
use range::Range as ServoRange; use range::Range as ServoRange;
use servo_arc::Arc as ServoArc; use servo_arc::Arc as ServoArc;
@ -37,7 +38,7 @@ use style::values::generics::NonNegative;
use style::values::generics::rect::Rect; use style::values::generics::rect::Rect;
use style::values::specified::text::TextDecorationLine; use style::values::specified::text::TextDecorationLine;
use style::values::specified::ui::CursorKind; use style::values::specified::ui::CursorKind;
use style_traits::CSSPixel; use style_traits::{CSSPixel as StyloCSSPixel, DevicePixel as StyloDevicePixel};
use webrender_api::units::{DeviceIntSize, DevicePixel, LayoutPixel, LayoutRect, LayoutSize}; use webrender_api::units::{DeviceIntSize, DevicePixel, LayoutPixel, LayoutRect, LayoutSize};
use webrender_api::{ use webrender_api::{
self as wr, BorderDetails, BoxShadowClipMode, BuiltDisplayList, ClipChainId, ClipMode, self as wr, BorderDetails, BoxShadowClipMode, BuiltDisplayList, ClipChainId, ClipMode,
@ -47,7 +48,7 @@ use webrender_api::{
use wr::units::LayoutVector2D; use wr::units::LayoutVector2D;
use crate::cell::ArcRefCell; use crate::cell::ArcRefCell;
use crate::context::{LayoutContext, ResolvedImage}; use crate::context::{ImageResolver, ResolvedImage};
pub use crate::display_list::conversions::ToWebRender; pub use crate::display_list::conversions::ToWebRender;
use crate::display_list::stacking_context::StackingContextSection; use crate::display_list::stacking_context::StackingContextSection;
use crate::fragment_tree::{ use crate::fragment_tree::{
@ -92,10 +93,6 @@ pub(crate) struct DisplayListBuilder<'a> {
/// list building functions. /// list building functions.
current_clip_id: ClipId, current_clip_id: ClipId,
/// A [LayoutContext] used to get information about the device pixel ratio
/// and get handles to WebRender images.
pub context: &'a LayoutContext<'a>,
/// The [`wr::DisplayListBuilder`] for this Servo [`DisplayListBuilder`]. /// The [`wr::DisplayListBuilder`] for this Servo [`DisplayListBuilder`].
pub webrender_display_list_builder: &'a mut wr::DisplayListBuilder, pub webrender_display_list_builder: &'a mut wr::DisplayListBuilder,
@ -116,6 +113,12 @@ pub(crate) struct DisplayListBuilder<'a> {
/// A mapping from [`ClipId`] To WebRender [`ClipChainId`] used when building this WebRender /// A mapping from [`ClipId`] To WebRender [`ClipChainId`] used when building this WebRender
/// display list. /// display list.
clip_map: Vec<ClipChainId>, clip_map: Vec<ClipChainId>,
/// An [`ImageResolver`] to use during display list construction.
image_resolver: Arc<ImageResolver>,
/// The device pixel ratio used for this `Document`'s display list.
device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
} }
struct InspectorHighlight { struct InspectorHighlight {
@ -132,7 +135,7 @@ struct InspectorHighlight {
struct HighlightTraversalState { struct HighlightTraversalState {
/// The smallest rectangle that fully encloses all fragments created by the highlighted /// The smallest rectangle that fully encloses all fragments created by the highlighted
/// dom node, if any. /// dom node, if any.
content_box: euclid::Rect<Au, CSSPixel>, content_box: euclid::Rect<Au, StyloCSSPixel>,
spatial_id: SpatialId, spatial_id: SpatialId,
@ -154,9 +157,11 @@ impl InspectorHighlight {
impl DisplayListBuilder<'_> { impl DisplayListBuilder<'_> {
pub(crate) fn build( pub(crate) fn build(
context: &LayoutContext, reflow_request: &ReflowRequest,
stacking_context_tree: &mut StackingContextTree, stacking_context_tree: &mut StackingContextTree,
fragment_tree: &FragmentTree, fragment_tree: &FragmentTree,
image_resolver: Arc<ImageResolver>,
device_pixel_ratio: Scale<f32, StyloCSSPixel, StyloDevicePixel>,
debug: &DebugOptions, debug: &DebugOptions,
) -> BuiltDisplayList { ) -> BuiltDisplayList {
// Build the rest of the display list which inclues all of the WebRender primitives. // Build the rest of the display list which inclues all of the WebRender primitives.
@ -182,14 +187,15 @@ impl DisplayListBuilder<'_> {
current_scroll_node_id: compositor_info.root_reference_frame_id, current_scroll_node_id: compositor_info.root_reference_frame_id,
current_reference_frame_scroll_node_id: compositor_info.root_reference_frame_id, current_reference_frame_scroll_node_id: compositor_info.root_reference_frame_id,
current_clip_id: ClipId::INVALID, current_clip_id: ClipId::INVALID,
context,
webrender_display_list_builder: &mut webrender_display_list_builder, webrender_display_list_builder: &mut webrender_display_list_builder,
compositor_info, compositor_info,
inspector_highlight: context inspector_highlight: reflow_request
.highlighted_dom_node .highlighted_dom_node
.map(InspectorHighlight::for_node), .map(InspectorHighlight::for_node),
paint_body_background: true, paint_body_background: true,
clip_map: Default::default(), clip_map: Default::default(),
image_resolver,
device_pixel_ratio,
}; };
builder.add_all_spatial_nodes(); builder.add_all_spatial_nodes();
@ -756,7 +762,7 @@ impl Fragment {
let color = parent_style.clone_color(); let color = parent_style.clone_color();
let font_metrics = &fragment.font_metrics; let font_metrics = &fragment.font_metrics;
let dppx = builder.context.style_context.device_pixel_ratio().get(); let dppx = builder.device_pixel_ratio.get();
let common = builder.common_properties(rect.to_webrender(), &parent_style); let common = builder.common_properties(rect.to_webrender(), &parent_style);
// Shadows. According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front to // Shadows. According to CSS-BACKGROUNDS, text shadows render in *reverse* order (front to
@ -1232,7 +1238,7 @@ impl<'a> BuilderForBoxFragment<'a> {
let node = self.fragment.base.tag.map(|tag| tag.node); let node = self.fragment.base.tag.map(|tag| tag.node);
// Reverse because the property is top layer first, we want to paint bottom layer first. // Reverse because the property is top layer first, we want to paint bottom layer first.
for (index, image) in b.background_image.0.iter().enumerate().rev() { for (index, image) in b.background_image.0.iter().enumerate().rev() {
match builder.context.resolve_image(node, image) { match builder.image_resolver.resolve_image(node, image) {
Err(_) => {}, Err(_) => {},
Ok(ResolvedImage::Gradient(gradient)) => { Ok(ResolvedImage::Gradient(gradient)) => {
let intrinsic = NaturalSizes::empty(); let intrinsic = NaturalSizes::empty();
@ -1279,7 +1285,7 @@ impl<'a> BuilderForBoxFragment<'a> {
let image_wr_key = match image { let image_wr_key = match image {
CachedImage::Raster(raster_image) => raster_image.id, CachedImage::Raster(raster_image) => raster_image.id,
CachedImage::Vector(vector_image) => { CachedImage::Vector(vector_image) => {
let scale = builder.context.shared_context().device_pixel_ratio().0; let scale = builder.device_pixel_ratio.get();
let default_size: DeviceIntSize = let default_size: DeviceIntSize =
Size2D::new(size.width * scale, size.height * scale).to_i32(); Size2D::new(size.width * scale, size.height * scale).to_i32();
let layer_size = layer.as_ref().map(|layer| { let layer_size = layer.as_ref().map(|layer| {
@ -1292,9 +1298,11 @@ impl<'a> BuilderForBoxFragment<'a> {
node.and_then(|node| { node.and_then(|node| {
let size = layer_size.unwrap_or(default_size); let size = layer_size.unwrap_or(default_size);
builder builder.image_resolver.rasterize_vector_image(
.context vector_image.id,
.rasterize_vector_image(vector_image.id, size, node) size,
node,
)
}) })
.and_then(|rasterized_image| rasterized_image.id) .and_then(|rasterized_image| rasterized_image.id)
}, },
@ -1476,7 +1484,7 @@ impl<'a> BuilderForBoxFragment<'a> {
let mut height = border_image_size.height; let mut height = border_image_size.height;
let node = self.fragment.base.tag.map(|tag| tag.node); let node = self.fragment.base.tag.map(|tag| tag.node);
let source = match builder let source = match builder
.context .image_resolver
.resolve_image(node, &border.border_image_source) .resolve_image(node, &border.border_image_source)
{ {
Err(_) => return false, Err(_) => return false,

View file

@ -71,7 +71,7 @@ impl<'dom> NodeAndStyleInfo<'dom> {
.to_threadsafe() .to_threadsafe()
.as_element()? .as_element()?
.with_pseudo(pseudo_element_type)? .with_pseudo(pseudo_element_type)?
.style(context.shared_context()); .style(&context.style_context);
Some(NodeAndStyleInfo { Some(NodeAndStyleInfo {
node: self.node, node: self.node,
pseudo_element_type: Some(pseudo_element_type), pseudo_element_type: Some(pseudo_element_type),
@ -200,10 +200,8 @@ fn traverse_children_of<'dom>(
traverse_eager_pseudo_element(PseudoElement::Before, parent_element, context, handler); traverse_eager_pseudo_element(PseudoElement::Before, parent_element, context, handler);
if parent_element.is_text_input() { if parent_element.is_text_input() {
let info = NodeAndStyleInfo::new( let info =
parent_element, NodeAndStyleInfo::new(parent_element, parent_element.style(&context.style_context));
parent_element.style(context.shared_context()),
);
let node_text_content = parent_element.to_threadsafe().node_text_content(); let node_text_content = parent_element.to_threadsafe().node_text_content();
if node_text_content.is_empty() { if node_text_content.is_empty() {
// The addition of zero-width space here forces the text input to have an inline formatting // The addition of zero-width space here forces the text input to have an inline formatting
@ -220,7 +218,7 @@ fn traverse_children_of<'dom>(
} else { } else {
for child in iter_child_nodes(parent_element) { for child in iter_child_nodes(parent_element) {
if child.is_text_node() { if child.is_text_node() {
let info = NodeAndStyleInfo::new(child, child.style(context.shared_context())); let info = NodeAndStyleInfo::new(child, child.style(&context.style_context));
handler.handle_text(&info, child.to_threadsafe().node_text_content()); handler.handle_text(&info, child.to_threadsafe().node_text_content());
} else if child.is_element() { } else if child.is_element() {
traverse_element(child, context, handler); traverse_element(child, context, handler);
@ -241,7 +239,7 @@ fn traverse_element<'dom>(
element.unset_pseudo_element_box(PseudoElement::Marker); element.unset_pseudo_element_box(PseudoElement::Marker);
let replaced = ReplacedContents::for_element(element, context); let replaced = ReplacedContents::for_element(element, context);
let style = element.style(context.shared_context()); let style = element.style(&context.style_context);
match Display::from(style.get_box().display) { match Display::from(style.get_box().display) {
Display::None => element.unset_all_boxes(), Display::None => element.unset_all_boxes(),
Display::Contents => { Display::Contents => {
@ -300,7 +298,7 @@ fn traverse_eager_pseudo_element<'dom>(
return; return;
}; };
let style = pseudo_element.style(context.shared_context()); let style = pseudo_element.style(&context.style_context);
if style.ineffective_content_property() { if style.ineffective_content_property() {
return; return;
} }

View file

@ -45,7 +45,7 @@ pub struct BoxTree {
} }
impl BoxTree { impl BoxTree {
pub fn construct(context: &LayoutContext, root_element: ServoLayoutNode<'_>) -> Self { pub(crate) fn construct(context: &LayoutContext, root_element: ServoLayoutNode<'_>) -> Self {
let boxes = construct_for_root_element(context, root_element); let boxes = construct_for_root_element(context, root_element);
// Zero box for `:root { display: none }`, one for the root element otherwise. // Zero box for `:root { display: none }`, one for the root element otherwise.
@ -59,7 +59,7 @@ impl BoxTree {
// > none, user agents must instead apply the overflow-* values of the first such child // > none, user agents must instead apply the overflow-* values of the first such child
// > element to the viewport. The element from which the value is propagated must then have a // > element to the viewport. The element from which the value is propagated must then have a
// > used overflow value of visible. // > used overflow value of visible.
let root_style = root_element.style(context.shared_context()); let root_style = root_element.style(&context.style_context);
let mut viewport_overflow_x = root_style.clone_overflow_x(); let mut viewport_overflow_x = root_style.clone_overflow_x();
let mut viewport_overflow_y = root_style.clone_overflow_y(); let mut viewport_overflow_y = root_style.clone_overflow_y();
@ -76,7 +76,7 @@ impl BoxTree {
continue; continue;
} }
let style = child.style(context.shared_context()); let style = child.style(&context.style_context);
if !style.get_box().display.is_none() { if !style.get_box().display.is_none() {
viewport_overflow_x = style.clone_overflow_x(); viewport_overflow_x = style.clone_overflow_x();
viewport_overflow_y = style.clone_overflow_y(); viewport_overflow_y = style.clone_overflow_y();
@ -123,7 +123,10 @@ impl BoxTree {
/// * how intrinsic content sizes are computed eagerly makes it hard /// * how intrinsic content sizes are computed eagerly makes it hard
/// to update those sizes for ancestors of the node from which we /// to update those sizes for ancestors of the node from which we
/// made an incremental update. /// made an incremental update.
pub fn update(context: &LayoutContext, dirty_root_from_script: ServoLayoutNode<'_>) -> bool { pub(crate) fn update(
context: &LayoutContext,
dirty_root_from_script: ServoLayoutNode<'_>,
) -> bool {
let Some(box_tree_update) = IncrementalBoxTreeUpdate::find(dirty_root_from_script) else { let Some(box_tree_update) = IncrementalBoxTreeUpdate::find(dirty_root_from_script) else {
return false; return false;
}; };
@ -136,7 +139,7 @@ fn construct_for_root_element(
context: &LayoutContext, context: &LayoutContext,
root_element: ServoLayoutNode<'_>, root_element: ServoLayoutNode<'_>,
) -> Vec<ArcRefCell<BlockLevelBox>> { ) -> Vec<ArcRefCell<BlockLevelBox>> {
let info = NodeAndStyleInfo::new(root_element, root_element.style(context.shared_context())); let info = NodeAndStyleInfo::new(root_element, root_element.style(&context.style_context));
let box_style = info.style.get_box(); let box_style = info.style.get_box();
let display_inside = match Display::from(box_style.display) { let display_inside = match Display::from(box_style.display) {
@ -188,7 +191,7 @@ fn construct_for_root_element(
} }
impl BoxTree { impl BoxTree {
pub fn layout( pub(crate) fn layout(
&self, &self,
layout_context: &LayoutContext, layout_context: &LayoutContext,
viewport: UntypedSize2D<Au>, viewport: UntypedSize2D<Au>,

View file

@ -111,10 +111,10 @@ impl IndependentFormattingContext {
}, },
DisplayInside::Table => { DisplayInside::Table => {
let table_grid_style = context let table_grid_style = context
.shared_context() .style_context
.stylist .stylist
.style_for_anonymous::<ServoLayoutElement>( .style_for_anonymous::<ServoLayoutElement>(
&context.shared_context().guards, &context.style_context.guards,
&PseudoElement::ServoTableGrid, &PseudoElement::ServoTableGrid,
&node_and_style_info.style, &node_and_style_info.style,
); );

View file

@ -59,6 +59,7 @@ impl FragmentTree {
let mut animations = layout_context.style_context.animations.sets.write(); let mut animations = layout_context.style_context.animations.sets.write();
let mut invalid_animating_nodes: FxHashSet<_> = animations.keys().cloned().collect(); let mut invalid_animating_nodes: FxHashSet<_> = animations.keys().cloned().collect();
let mut image_animations = layout_context let mut image_animations = layout_context
.image_resolver
.node_to_animating_image_map .node_to_animating_image_map
.write() .write()
.to_owned(); .to_owned();

View file

@ -26,8 +26,9 @@ use fonts_traits::StylesheetWebFontLoadFinishedCallback;
use fxhash::FxHashMap; use fxhash::FxHashMap;
use ipc_channel::ipc::IpcSender; use ipc_channel::ipc::IpcSender;
use layout_api::{ use layout_api::{
Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, OffsetParentResponse, QueryMsg, IFrameSizes, Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType,
ReflowGoal, ReflowRequest, ReflowRequestRestyle, ReflowResult, TrustedNodeAddress, OffsetParentResponse, QueryMsg, ReflowGoal, ReflowRequest, ReflowRequestRestyle, ReflowResult,
TrustedNodeAddress,
}; };
use log::{debug, error, warn}; use log::{debug, error, warn};
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps}; use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
@ -38,7 +39,6 @@ use profile_traits::time::{
self as profile_time, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType, self as profile_time, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType,
}; };
use profile_traits::{path, time_profile}; use profile_traits::{path, time_profile};
use rayon::ThreadPool;
use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode}; use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode};
use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter, ScriptThreadMessage}; use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter, ScriptThreadMessage};
use servo_arc::Arc as ServoArc; use servo_arc::Arc as ServoArc;
@ -78,7 +78,7 @@ use url::Url;
use webrender_api::units::{DevicePixel, DevicePoint, LayoutSize, LayoutVector2D}; use webrender_api::units::{DevicePixel, DevicePoint, LayoutSize, LayoutVector2D};
use webrender_api::{ExternalScrollId, HitTestFlags}; use webrender_api::{ExternalScrollId, HitTestFlags};
use crate::context::{CachedImageOrError, LayoutContext}; use crate::context::{CachedImageOrError, ImageResolver, LayoutContext};
use crate::display_list::{DisplayListBuilder, StackingContextTree}; use crate::display_list::{DisplayListBuilder, StackingContextTree};
use crate::query::{ use crate::query::{
get_the_text_steps, process_client_rect_request, process_content_box_request, get_the_text_steps, process_client_rect_request, process_content_box_request,
@ -655,109 +655,34 @@ impl LayoutThread {
return None; return None;
}; };
let document_shared_lock = document.style_shared_lock(); let image_resolver = Arc::new(ImageResolver {
let author_guard = document_shared_lock.read();
let ua_stylesheets = &*UA_STYLESHEETS;
let ua_or_user_guard = ua_stylesheets.shared_lock.read();
let rayon_pool = STYLE_THREAD_POOL.lock();
let rayon_pool = rayon_pool.pool();
let rayon_pool = rayon_pool.as_ref();
let guards = StylesheetGuards {
author: &author_guard,
ua_or_user: &ua_or_user_guard,
};
let mut snapshot_map = SnapshotMap::new();
let mut _snapshot_setter = None;
let mut viewport_changed = false;
if let Some(restyle) = reflow_request.restyle.as_mut() {
_snapshot_setter = Some(SnapshotSetter::new(restyle, &mut snapshot_map));
viewport_changed = self.viewport_did_change(reflow_request.viewport_details);
if self.update_device_if_necessary(&reflow_request, viewport_changed, &guards) {
if let Some(mut data) = root_element.mutate_data() {
data.hint.insert(RestyleHint::recascade_subtree());
}
}
self.prepare_stylist_for_reflow(
&reflow_request,
document,
root_element,
&guards,
ua_stylesheets,
&snapshot_map,
);
if self.previously_highlighted_dom_node.get() != reflow_request.highlighted_dom_node {
// Need to manually force layout to build a new display list regardless of whether the box tree
// changed or not.
self.need_new_display_list.set(true);
}
}
let mut layout_context = LayoutContext {
id: self.id,
origin: reflow_request.origin.clone(), origin: reflow_request.origin.clone(),
style_context: self.build_shared_style_context(
guards,
&snapshot_map,
reflow_request.animation_timeline_value,
&reflow_request.animations,
match reflow_request.stylesheets_changed() {
true => TraversalFlags::ForCSSRuleChanges,
false => TraversalFlags::empty(),
},
),
image_cache: self.image_cache.clone(), image_cache: self.image_cache.clone(),
font_context: self.font_context.clone(),
resolved_images_cache: self.resolved_images_cache.clone(), resolved_images_cache: self.resolved_images_cache.clone(),
pending_images: Mutex::default(), pending_images: Mutex::default(),
pending_rasterization_images: Mutex::default(), pending_rasterization_images: Mutex::default(),
node_to_animating_image_map: reflow_request.node_to_animating_image_map.clone(), node_to_animating_image_map: reflow_request.node_to_animating_image_map.clone(),
iframe_sizes: Mutex::default(), animation_timeline_value: reflow_request.animation_timeline_value,
use_rayon: rayon_pool.is_some(), });
highlighted_dom_node: reflow_request.highlighted_dom_node,
};
let mut damage = RestyleDamage::empty(); let (damage, iframe_sizes) = self.restyle_and_build_trees(
if let Some(restyle) = reflow_request.restyle.as_ref() { &mut reflow_request,
damage = self.restyle_and_build_trees( document,
restyle,
root_element, root_element,
rayon_pool, &image_resolver,
&mut layout_context,
viewport_changed,
); );
self.calculate_overflow(damage); self.calculate_overflow(damage);
};
self.build_stacking_context_tree(&reflow_request, damage); self.build_stacking_context_tree(&reflow_request, damage);
let built_display_list = let built_display_list = self.build_display_list(&reflow_request, damage, &image_resolver);
self.build_display_list(&reflow_request, damage, &mut layout_context);
if let ReflowGoal::UpdateScrollNode(external_scroll_id, offset) = reflow_request.reflow_goal if let ReflowGoal::UpdateScrollNode(external_scroll_id, offset) = reflow_request.reflow_goal
{ {
self.set_scroll_offset_from_script(external_scroll_id, offset); self.set_scroll_offset_from_script(external_scroll_id, offset);
} }
if self.debug.dump_scroll_tree { let pending_images = std::mem::take(&mut *image_resolver.pending_images.lock());
// Print the [ScrollTree], this is done after display list build so we have
// the information about webrender id. Whether a scroll tree is initialized
// or not depends on the reflow goal.
if let Some(tree) = self.stacking_context_tree.borrow().as_ref() {
tree.compositor_info.scroll_tree.debug_print();
} else {
println!(
"Scroll Tree -- reflow {:?}: scroll tree is not initialized yet.",
reflow_request.reflow_goal
);
}
}
let pending_images = std::mem::take(&mut *layout_context.pending_images.lock());
let pending_rasterization_images = let pending_rasterization_images =
std::mem::take(&mut *layout_context.pending_rasterization_images.lock()); std::mem::take(&mut *image_resolver.pending_rasterization_images.lock());
let iframe_sizes = std::mem::take(&mut *layout_context.iframe_sizes.lock());
Some(ReflowResult { Some(ReflowResult {
built_display_list, built_display_list,
@ -829,20 +754,80 @@ impl LayoutThread {
#[servo_tracing::instrument(skip_all)] #[servo_tracing::instrument(skip_all)]
fn restyle_and_build_trees( fn restyle_and_build_trees(
&self, &mut self,
restyle: &ReflowRequestRestyle, reflow_request: &mut ReflowRequest,
document: ServoLayoutDocument<'_>,
root_element: ServoLayoutElement<'_>, root_element: ServoLayoutElement<'_>,
rayon_pool: Option<&ThreadPool>, image_resolver: &Arc<ImageResolver>,
layout_context: &mut LayoutContext<'_>, ) -> (RestyleDamage, IFrameSizes) {
viewport_changed: bool, let mut snapshot_map = SnapshotMap::new();
) -> RestyleDamage { let _snapshot_setter = match reflow_request.restyle.as_mut() {
Some(restyle) => SnapshotSetter::new(restyle, &mut snapshot_map),
None => return (RestyleDamage::empty(), IFrameSizes::default()),
};
let document_shared_lock = document.style_shared_lock();
let author_guard = document_shared_lock.read();
let ua_stylesheets = &*UA_STYLESHEETS;
let ua_or_user_guard = ua_stylesheets.shared_lock.read();
let rayon_pool = STYLE_THREAD_POOL.lock();
let rayon_pool = rayon_pool.pool();
let rayon_pool = rayon_pool.as_ref();
let guards = StylesheetGuards {
author: &author_guard,
ua_or_user: &ua_or_user_guard,
};
let viewport_changed = self.viewport_did_change(reflow_request.viewport_details);
if self.update_device_if_necessary(reflow_request, viewport_changed, &guards) {
if let Some(mut data) = root_element.mutate_data() {
data.hint.insert(RestyleHint::recascade_subtree());
}
}
self.prepare_stylist_for_reflow(
reflow_request,
document,
root_element,
&guards,
ua_stylesheets,
&snapshot_map,
);
if self.previously_highlighted_dom_node.get() != reflow_request.highlighted_dom_node {
// Need to manually force layout to build a new display list regardless of whether the box tree
// changed or not.
self.need_new_display_list.set(true);
}
let layout_context = LayoutContext {
style_context: self.build_shared_style_context(
guards,
&snapshot_map,
reflow_request.animation_timeline_value,
&reflow_request.animations,
match reflow_request.stylesheets_changed() {
true => TraversalFlags::ForCSSRuleChanges,
false => TraversalFlags::empty(),
},
),
font_context: self.font_context.clone(),
iframe_sizes: Mutex::default(),
use_rayon: rayon_pool.is_some(),
image_resolver: image_resolver.clone(),
};
let restyle = reflow_request
.restyle
.as_ref()
.expect("Should not get here if there is not restyle.");
let dirty_root = unsafe { let dirty_root = unsafe {
ServoLayoutNode::new(&restyle.dirty_root.unwrap()) ServoLayoutNode::new(&restyle.dirty_root.unwrap())
.as_element() .as_element()
.unwrap() .unwrap()
}; };
let recalc_style_traversal = RecalcStyle::new(layout_context); let recalc_style_traversal = RecalcStyle::new(&layout_context);
let token = { let token = {
let shared = let shared =
DomTraversal::<ServoLayoutElement>::shared_context(&recalc_style_traversal); DomTraversal::<ServoLayoutElement>::shared_context(&recalc_style_traversal);
@ -851,20 +836,19 @@ impl LayoutThread {
if !token.should_traverse() { if !token.should_traverse() {
layout_context.style_context.stylist.rule_tree().maybe_gc(); layout_context.style_context.stylist.rule_tree().maybe_gc();
return RestyleDamage::empty(); return (RestyleDamage::empty(), IFrameSizes::default());
} }
let dirty_root: ServoLayoutNode = let dirty_root: ServoLayoutNode =
driver::traverse_dom(&recalc_style_traversal, token, rayon_pool).as_node(); driver::traverse_dom(&recalc_style_traversal, token, rayon_pool).as_node();
let root_node = root_element.as_node(); let root_node = root_element.as_node();
let mut damage = let mut damage = compute_damage_and_repair_style(&layout_context.style_context, root_node);
compute_damage_and_repair_style(layout_context.shared_context(), root_node);
if viewport_changed { if viewport_changed {
damage = RestyleDamage::RELAYOUT; damage = RestyleDamage::RELAYOUT;
} else if !damage.contains(RestyleDamage::RELAYOUT) { } else if !damage.contains(RestyleDamage::RELAYOUT) {
layout_context.style_context.stylist.rule_tree().maybe_gc(); layout_context.style_context.stylist.rule_tree().maybe_gc();
return damage; return (damage, IFrameSizes::default());
} }
let mut box_tree = self.box_tree.borrow_mut(); let mut box_tree = self.box_tree.borrow_mut();
@ -915,12 +899,14 @@ impl LayoutThread {
.style_context .style_context
.stylist .stylist
.rule_tree() .rule_tree()
.dump_stdout(&layout_context.shared_context().guards); .dump_stdout(&layout_context.style_context.guards);
} }
// GC the rule tree if some heuristics are met. // GC the rule tree if some heuristics are met.
layout_context.style_context.stylist.rule_tree().maybe_gc(); layout_context.style_context.stylist.rule_tree().maybe_gc();
damage
let mut iframe_sizes = layout_context.iframe_sizes.lock();
(damage, std::mem::take(&mut *iframe_sizes))
} }
fn calculate_overflow(&self, damage: RestyleDamage) { fn calculate_overflow(&self, damage: RestyleDamage) {
@ -995,6 +981,20 @@ impl LayoutThread {
// The stacking context tree is up-to-date again. // The stacking context tree is up-to-date again.
self.need_new_stacking_context_tree.set(false); self.need_new_stacking_context_tree.set(false);
if self.debug.dump_scroll_tree {
// Print the [ScrollTree], this is done after display list build so we have
// the information about webrender id. Whether a scroll tree is initialized
// or not depends on the reflow goal.
if let Some(tree) = self.stacking_context_tree.borrow().as_ref() {
tree.compositor_info.scroll_tree.debug_print();
} else {
println!(
"Scroll Tree -- reflow {:?}: scroll tree is not initialized yet.",
reflow_request.reflow_goal
);
}
}
} }
/// Build the display list for the current layout and send it to the renderer. If no display /// Build the display list for the current layout and send it to the renderer. If no display
@ -1003,7 +1003,7 @@ impl LayoutThread {
&self, &self,
reflow_request: &ReflowRequest, reflow_request: &ReflowRequest,
damage: RestyleDamage, damage: RestyleDamage,
layout_context: &mut LayoutContext<'_>, image_resolver: &Arc<ImageResolver>,
) -> bool { ) -> bool {
if !ReflowPhases::necessary(&reflow_request.reflow_goal) if !ReflowPhases::necessary(&reflow_request.reflow_goal)
.contains(ReflowPhases::DisplayListConstruction) .contains(ReflowPhases::DisplayListConstruction)
@ -1032,9 +1032,11 @@ impl LayoutThread {
stacking_context_tree.compositor_info.epoch = epoch.into(); stacking_context_tree.compositor_info.epoch = epoch.into();
let built_display_list = DisplayListBuilder::build( let built_display_list = DisplayListBuilder::build(
layout_context, reflow_request,
stacking_context_tree, stacking_context_tree,
fragment_tree, fragment_tree,
image_resolver.clone(),
self.device().device_pixel_ratio(),
&self.debug, &self.debug,
); );
self.compositor_api.send_display_list( self.compositor_api.send_display_list(

View file

@ -161,7 +161,9 @@ impl ReplacedContents {
}; };
if let ReplacedContentKind::Image(Some(Image::Raster(ref image))) = kind { if let ReplacedContentKind::Image(Some(Image::Raster(ref image))) = kind {
context.handle_animated_image(element.opaque(), image.clone()); context
.image_resolver
.handle_animated_image(element.opaque(), image.clone());
} }
let natural_size = if let Some(naturalc_size_in_dots) = natural_size_in_dots { let natural_size = if let Some(naturalc_size_in_dots) = natural_size_in_dots {
@ -190,7 +192,7 @@ impl ReplacedContents {
image_url: &ComputedUrl, image_url: &ComputedUrl,
) -> Option<Self> { ) -> Option<Self> {
if let ComputedUrl::Valid(image_url) = image_url { if let ComputedUrl::Valid(image_url) = image_url {
let (image, width, height) = match context.get_or_request_image_or_meta( let (image, width, height) = match context.image_resolver.get_or_request_image_or_meta(
element.opaque(), element.opaque(),
image_url.clone().into(), image_url.clone().into(),
UsePlaceholder::No, UsePlaceholder::No,
@ -323,12 +325,13 @@ impl ReplacedContents {
.and_then(|image| match image { .and_then(|image| match image {
Image::Raster(raster_image) => raster_image.id, Image::Raster(raster_image) => raster_image.id,
Image::Vector(vector_image) => { Image::Vector(vector_image) => {
let scale = layout_context.shared_context().device_pixel_ratio(); let scale = layout_context.style_context.device_pixel_ratio();
let width = object_fit_size.width.scale_by(scale.0).to_px(); let width = object_fit_size.width.scale_by(scale.0).to_px();
let height = object_fit_size.height.scale_by(scale.0).to_px(); let height = object_fit_size.height.scale_by(scale.0).to_px();
let size = Size2D::new(width, height); let size = Size2D::new(width, height);
let tag = self.base_fragment_info.tag?; let tag = self.base_fragment_info.tag?;
layout_context layout_context
.image_resolver
.rasterize_vector_image(vector_image.id, size, tag.node) .rasterize_vector_image(vector_image.id, size, tag.node)
.and_then(|i| i.id) .and_then(|i| i.id)
}, },
@ -355,7 +358,7 @@ impl ReplacedContents {
}, },
ReplacedContentKind::IFrame(iframe) => { ReplacedContentKind::IFrame(iframe) => {
let size = Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px()); let size = Size2D::new(rect.size.width.to_f32_px(), rect.size.height.to_f32_px());
let hidpi_scale_factor = layout_context.shared_context().device_pixel_ratio(); let hidpi_scale_factor = layout_context.style_context.device_pixel_ratio();
layout_context.iframe_sizes.lock().insert( layout_context.iframe_sizes.lock().insert(
iframe.browsing_context_id, iframe.browsing_context_id,

View file

@ -20,11 +20,11 @@ pub struct RecalcStyle<'a> {
} }
impl<'a> RecalcStyle<'a> { impl<'a> RecalcStyle<'a> {
pub fn new(context: &'a LayoutContext<'a>) -> Self { pub(crate) fn new(context: &'a LayoutContext<'a>) -> Self {
RecalcStyle { context } RecalcStyle { context }
} }
pub fn context(&self) -> &LayoutContext<'a> { pub(crate) fn context(&self) -> &LayoutContext<'a> {
self.context self.context
} }
} }