#![recursion_limit = "128"]
use std::borrow::ToOwned;
use std::cell::{Cell, RefCell};
use std::ops::{Deref, DerefMut};
use std::process;
use std::sync::{Arc, LazyLock, Mutex};
use app_units::Au;
use base::cross_process_instant::CrossProcessInstant;
use base::id::{BrowsingContextId, PipelineId};
use base::Epoch;
use embedder_traits::resources::{self, Resource};
use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect, Size2D as UntypedSize2D};
use euclid::{Point2D, Rect, Scale, Size2D};
use fnv::FnvHashMap;
use fonts::{
get_and_reset_text_shaping_performance_counter, FontCacheThread, FontContext,
FontContextWebFontMethods,
};
use fonts_traits::WebFontLoadFinishedCallback;
use fxhash::{FxHashMap, FxHashSet};
use ipc_channel::ipc::IpcSender;
use layout::construct::ConstructionResult;
use layout::context::{LayoutContext, RegisteredPainter, RegisteredPainters};
use layout::display_list::items::{DisplayList, ScrollOffsetMap, WebRenderImageInfo};
use layout::display_list::{IndexableText, ToLayout};
use layout::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils, MutableOwnedFlowUtils};
use layout::flow_ref::FlowRef;
use layout::incremental::{RelayoutMode, SpecialRestyleDamage};
use layout::query::{
process_client_rect_query, process_content_box_request, process_content_boxes_request,
process_element_inner_text_query, process_offset_parent_query,
process_resolved_font_style_request, process_resolved_style_request,
process_scrolling_area_request,
};
use layout::traversal::{
construct_flows_at_ancestors, ComputeStackingRelativePositions, PreorderFlowTraversal,
RecalcStyleAndConstructFlows,
};
use layout::wrapper::ThreadSafeLayoutNodeHelpers;
use layout::{layout_debug, layout_debug_scope, parallel, sequential};
use log::{debug, error, trace, warn};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use metrics::{PaintTimeMetrics, ProfilerMetadataFactory};
use net_traits::image_cache::{ImageCache, UsePlaceholder};
use net_traits::ResourceThreads;
use parking_lot::RwLock;
use profile_traits::mem::{Report, ReportKind};
use profile_traits::path;
use profile_traits::time::{
self as profile_time, profile, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType,
};
use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode};
use script_layout_interface::wrapper_traits::LayoutNode;
use script_layout_interface::{
Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, OffsetParentResponse, Reflow,
ReflowComplete, ReflowGoal, ScriptReflow, TrustedNodeAddress,
};
use script_traits::{
ConstellationControlMsg, DrawAPaintImageResult, IFrameSizeMsg, LayoutMsg as ConstellationMsg,
PaintWorkletError, Painter, ScrollState, UntrustedNodeAddress, WindowSizeData, WindowSizeType,
};
use servo_arc::Arc as ServoArc;
use servo_atoms::Atom;
use servo_config::opts::{self, DebugOptions};
use servo_config::pref;
use servo_url::{ImmutableOrigin, ServoUrl};
use style::animation::{AnimationSetKey, DocumentAnimationSet, ElementAnimationSet};
use style::context::{
QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters, SharedStyleContext,
};
use style::dom::{OpaqueNode, ShowSubtree, ShowSubtreeDataAndPrimaryValues, 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::logical_geometry::LogicalPoint;
use style::media_queries::{Device, MediaList, MediaType};
use style::properties::style_structs::Font;
use style::properties::{ComputedValues, PropertyId};
use style::selector_parser::{PseudoElement, SnapshotMap};
use style::servo::media_queries::FontMetricsProvider;
use style::servo::restyle_damage::ServoRestyleDamage;
use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards};
use style::stylesheets::{
DocumentStyleSheet, Origin, Stylesheet, StylesheetInDocument, UrlExtraData,
UserAgentStylesheets,
};
use style::stylist::Stylist;
use style::traversal::DomTraversal;
use style::traversal_flags::TraversalFlags;
use style::values::computed::font::GenericFontFamily;
use style::values::computed::{FontSize, Length, NonNegativeLength};
use style::values::specified::font::KeywordInfo;
use style_traits::{CSSPixel, DevicePixel, SpeculativePainter};
use time_03::Duration;
use url::Url;
use webrender_api::{units, ColorF, HitTestFlags};
use webrender_traits::WebRenderScriptApi;
pub struct LayoutThread {
id: PipelineId,
url: ServoUrl,
stylist: Stylist,
is_iframe: bool,
constellation_chan: IpcSender<ConstellationMsg>,
script_chan: IpcSender<ConstellationControlMsg>,
time_profiler_chan: profile_time::ProfilerChan,
image_cache: Arc<dyn ImageCache>,
font_context: Arc<FontContext<FontCacheThread>>,
first_reflow: Cell<bool>,
parallel_flag: bool,
generation: Cell<u32>,
root_flow: RefCell<Option<FlowRef>>,
epoch: Cell<Epoch>,
viewport_size: UntypedSize2D<Au>,
display_list: RefCell<Option<DisplayList>>,
indexable_text: RefCell<IndexableText>,
scroll_offsets: RefCell<ScrollOffsetMap>,
webrender_image_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
registered_painters: RegisteredPaintersImpl,
webrender_api: WebRenderScriptApi,
paint_time_metrics: PaintTimeMetrics,
last_iframe_sizes: RefCell<FnvHashMap<BrowsingContextId, Size2D<f32, CSSPixel>>>,
debug: DebugOptions,
nonincremental_layout: bool,
}
pub struct LayoutFactoryImpl();
impl LayoutFactory for LayoutFactoryImpl {
fn create(&self, config: LayoutConfig) -> Box<dyn Layout> {
Box::new(LayoutThread::new(
config.id,
config.url,
config.is_iframe,
config.constellation_chan,
config.script_chan,
config.image_cache,
config.resource_threads,
config.font_cache_thread,
config.time_profiler_chan,
config.webrender_api_sender,
config.paint_time_metrics,
config.window_size,
))
}
}
struct ScriptReflowResult {
script_reflow: ScriptReflow,
result: RefCell<Option<ReflowComplete>>,
}
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,
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();
}
}
impl Drop for LayoutThread {
fn drop(&mut self) {
let (keys, instance_keys) = self
.font_context
.collect_unused_webrender_resources(true );
self.webrender_api
.remove_unused_font_resources(keys, instance_keys)
}
}
impl Layout for LayoutThread {
fn device(&self) -> &Device {
self.stylist.device()
}
fn waiting_for_web_fonts_to_load(&self) -> bool {
self.font_context.web_fonts_still_loading() != 0
}
fn current_epoch(&self) -> Epoch {
self.epoch.get()
}
fn load_web_fonts_from_stylesheet(&self, stylesheet: ServoArc<Stylesheet>) {
let guard = stylesheet.shared_lock.read();
self.load_all_web_fonts_from_stylesheet_with_guard(
&DocumentStyleSheet(stylesheet.clone()),
&guard,
);
}
fn add_stylesheet(
&mut self,
stylesheet: ServoArc<Stylesheet>,
before_stylesheet: Option<ServoArc<Stylesheet>>,
) {
let guard = stylesheet.shared_lock.read();
let stylesheet = DocumentStyleSheet(stylesheet.clone());
self.load_all_web_fonts_from_stylesheet_with_guard(&stylesheet, &guard);
match before_stylesheet {
Some(insertion_point) => self.stylist.insert_stylesheet_before(
stylesheet,
DocumentStyleSheet(insertion_point),
&guard,
),
None => self.stylist.append_stylesheet(stylesheet, &guard),
}
}
fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>) {
let guard = stylesheet.shared_lock.read();
let stylesheet = DocumentStyleSheet(stylesheet.clone());
self.stylist.remove_stylesheet(stylesheet.clone(), &guard);
self.font_context
.remove_all_web_fonts_from_stylesheet(&stylesheet);
}
fn query_content_box(&self, node: OpaqueNode) -> Option<UntypedRect<Au>> {
let mut root_flow = self.root_flow_for_query()?;
let root_flow_ref = FlowRef::deref_mut(&mut root_flow);
process_content_box_request(node, root_flow_ref)
}
fn query_content_boxes(&self, node: OpaqueNode) -> Vec<UntypedRect<Au>> {
let Some(mut root_flow) = self.root_flow_for_query() else {
return vec![];
};
let root_flow_ref = FlowRef::deref_mut(&mut root_flow);
process_content_boxes_request(node, root_flow_ref)
}
fn query_client_rect(&self, node: OpaqueNode) -> UntypedRect<i32> {
let Some(mut root_flow) = self.root_flow_for_query() else {
return UntypedRect::zero();
};
let root_flow_ref = FlowRef::deref_mut(&mut root_flow);
process_client_rect_query(node, root_flow_ref)
}
fn query_element_inner_text(
&self,
node: script_layout_interface::TrustedNodeAddress,
) -> String {
let node = unsafe { ServoLayoutNode::new(&node) };
process_element_inner_text_query(node, &self.indexable_text.borrow())
}
fn query_inner_window_dimension(
&self,
browsing_context_id: BrowsingContextId,
) -> Option<Size2D<f32, CSSPixel>> {
self.last_iframe_sizes
.borrow()
.get(&browsing_context_id)
.cloned()
}
fn query_nodes_from_point(
&self,
point: UntypedPoint2D<f32>,
query_type: NodesFromPointQueryType,
) -> Vec<UntrustedNodeAddress> {
let mut flags = match query_type {
NodesFromPointQueryType::Topmost => HitTestFlags::empty(),
NodesFromPointQueryType::All => HitTestFlags::FIND_ALL,
};
flags.insert(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT);
let client_point = units::DevicePoint::from_untyped(point);
let results = self
.webrender_api
.hit_test(Some(self.id.into()), client_point, flags);
results.iter().map(|result| result.node.into()).collect()
}
fn query_offset_parent(&self, node: OpaqueNode) -> OffsetParentResponse {
let Some(mut root_flow) = self.root_flow_for_query() else {
return OffsetParentResponse::default();
};
let root_flow_ref = FlowRef::deref_mut(&mut root_flow);
process_offset_parent_query(node, root_flow_ref)
}
fn query_resolved_style(
&self,
node: TrustedNodeAddress,
pseudo: Option<PseudoElement>,
property_id: PropertyId,
animations: DocumentAnimationSet,
animation_timeline_value: f64,
) -> String {
let node = unsafe { ServoLayoutNode::new(&node) };
let document = node.owner_doc();
let document_shared_lock = document.style_shared_lock();
let guards = StylesheetGuards {
author: &document_shared_lock.read(),
ua_or_user: &UA_STYLESHEETS.shared_lock.read(),
};
let snapshot_map = SnapshotMap::new();
let shared_style_context = self.build_shared_style_context(
guards,
&snapshot_map,
animation_timeline_value,
&animations,
TraversalFlags::empty(),
);
let Some(mut root_flow) = self.root_flow_for_query() else {
return String::new();
};
let root_flow_ref = FlowRef::deref_mut(&mut root_flow);
process_resolved_style_request(
&shared_style_context,
node,
&pseudo,
&property_id,
root_flow_ref,
)
}
fn query_resolved_font_style(
&self,
node: TrustedNodeAddress,
value: &str,
animations: DocumentAnimationSet,
animation_timeline_value: f64,
) -> Option<ServoArc<Font>> {
let node = unsafe { ServoLayoutNode::new(&node) };
let document = node.owner_doc();
let document_shared_lock = document.style_shared_lock();
let guards = StylesheetGuards {
author: &document_shared_lock.read(),
ua_or_user: &UA_STYLESHEETS.shared_lock.read(),
};
let snapshot_map = SnapshotMap::new();
let shared_style_context = self.build_shared_style_context(
guards,
&snapshot_map,
animation_timeline_value,
&animations,
TraversalFlags::empty(),
);
process_resolved_font_style_request(
&shared_style_context,
node,
value,
self.url.clone(),
document_shared_lock,
)
}
fn query_scrolling_area(&self, node: Option<OpaqueNode>) -> UntypedRect<i32> {
let Some(mut root_flow) = self.root_flow_for_query() else {
return UntypedRect::zero();
};
let root_flow_ref = FlowRef::deref_mut(&mut root_flow);
process_scrolling_area_request(node, root_flow_ref)
}
fn query_text_indext(
&self,
node: OpaqueNode,
point_in_node: UntypedPoint2D<f32>,
) -> Option<usize> {
let point_in_node = Point2D::new(
Au::from_f32_px(point_in_node.x),
Au::from_f32_px(point_in_node.y),
);
self.indexable_text.borrow().text_index(node, point_in_node)
}
fn exit_now(&mut self) {
self.root_flow.borrow_mut().take();
}
fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) {
self.stylist.set_quirks_mode(quirks_mode);
}
fn register_paint_worklet_modules(
&mut self,
name: Atom,
mut properties: Vec<Atom>,
painter: Box<dyn Painter>,
) {
debug!("Registering the painter");
let properties = properties
.drain(..)
.filter_map(|name| {
let id = PropertyId::parse_enabled_for_all_content(&name).ok()?;
Some((name.clone(), id))
})
.filter(|(_, id)| !id.is_shorthand())
.collect();
let registered_painter = RegisteredPainterImpl {
name: name.clone(),
properties,
painter,
};
self.registered_painters.0.insert(name, registered_painter);
}
fn collect_reports(&self, reports: &mut Vec<Report>) {
let mut ops = MallocSizeOfOps::new(servo_allocator::usable_size, None, None);
let display_list = self.display_list.borrow();
let display_list_ref = 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_ref.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.push(Report {
path: path![formatted_url, "layout-thread", "font-context"],
kind: ReportKind::ExplicitJemallocHeapSize,
size: self.font_context.size_of(&mut ops),
});
}
fn reflow(&mut self, script_reflow: script_layout_interface::ScriptReflow) {
let mut result = ScriptReflowResult::new(script_reflow);
profile(
profile_time::ProfilerCategory::LayoutPerform,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| self.handle_reflow(&mut result),
);
}
fn set_scroll_states(&mut self, scroll_states: &[ScrollState]) {
*self.scroll_offsets.borrow_mut() = scroll_states
.iter()
.map(|scroll_state| (scroll_state.scroll_id, scroll_state.scroll_offset))
.collect();
}
fn set_epoch_paint_time(&mut self, epoch: Epoch, paint_time: CrossProcessInstant) {
self.paint_time_metrics.maybe_set_metric(epoch, paint_time);
}
}
impl LayoutThread {
fn root_flow_for_query(&self) -> Option<FlowRef> {
self.root_flow.borrow().clone()
}
fn new(
id: PipelineId,
url: ServoUrl,
is_iframe: bool,
constellation_chan: IpcSender<ConstellationMsg>,
script_chan: IpcSender<ConstellationControlMsg>,
image_cache: Arc<dyn ImageCache>,
resource_threads: ResourceThreads,
font_cache_thread: FontCacheThread,
time_profiler_chan: profile_time::ProfilerChan,
webrender_api: WebRenderScriptApi,
paint_time_metrics: PaintTimeMetrics,
window_size: WindowSizeData,
) -> LayoutThread {
webrender_api.send_initial_transaction(id.into());
let mut font = Font::initial_values();
let default_font_size = pref!(fonts.default_size);
font.font_size = FontSize {
computed_size: NonNegativeLength::new(default_font_size as f32),
used_size: NonNegativeLength::new(default_font_size as f32),
keyword_info: KeywordInfo::medium(),
};
let font_context = Arc::new(FontContext::new(font_cache_thread, resource_threads));
let device = Device::new(
MediaType::screen(),
QuirksMode::NoQuirks,
window_size.initial_viewport,
window_size.device_pixel_ratio,
Box::new(LayoutFontMetricsProvider),
ComputedValues::initial_values_with_font_override(font),
);
LayoutThread {
id,
url,
is_iframe,
script_chan,
constellation_chan,
time_profiler_chan,
registered_painters: RegisteredPaintersImpl(Default::default()),
image_cache,
font_context,
first_reflow: Cell::new(true),
parallel_flag: true,
generation: Cell::new(0),
root_flow: RefCell::new(None),
epoch: Cell::new(Epoch(1)),
viewport_size: Size2D::new(
Au::from_f32_px(window_size.initial_viewport.width),
Au::from_f32_px(window_size.initial_viewport.height),
),
webrender_api,
stylist: Stylist::new(device, QuirksMode::NoQuirks),
display_list: Default::default(),
indexable_text: Default::default(),
scroll_offsets: Default::default(),
webrender_image_cache: Arc::new(RwLock::new(FnvHashMap::default())),
paint_time_metrics,
last_iframe_sizes: Default::default(),
debug: opts::get().debug.clone(),
nonincremental_layout: opts::get().nonincremental_layout,
}
}
fn build_shared_style_context<'a>(
&'a self,
guards: StylesheetGuards<'a>,
snapshot_map: &'a SnapshotMap,
animation_timeline_value: f64,
animations: &DocumentAnimationSet,
traversal_flags: TraversalFlags,
) -> SharedStyleContext<'a> {
SharedStyleContext {
stylist: &self.stylist,
options: GLOBAL_STYLE_DATA.options.clone(),
guards,
visited_styles_enabled: false,
animations: animations.clone(),
registered_speculative_painters: &self.registered_painters,
current_time_for_animations: animation_timeline_value,
traversal_flags,
snapshot_map,
}
}
fn build_layout_context<'a>(
&'a self,
guards: StylesheetGuards<'a>,
snapshot_map: &'a SnapshotMap,
origin: ImmutableOrigin,
animation_timeline_value: f64,
animations: &DocumentAnimationSet,
stylesheets_changed: bool,
) -> LayoutContext<'a> {
let traversal_flags = match stylesheets_changed {
true => TraversalFlags::ForCSSRuleChanges,
false => TraversalFlags::empty(),
};
LayoutContext {
id: self.id,
origin,
style_context: self.build_shared_style_context(
guards,
snapshot_map,
animation_timeline_value,
animations,
traversal_flags,
),
image_cache: self.image_cache.clone(),
font_context: self.font_context.clone(),
webrender_image_cache: self.webrender_image_cache.clone(),
pending_images: Mutex::new(vec![]),
registered_painters: &self.registered_painters,
}
}
fn load_all_web_fonts_from_stylesheet_with_guard(
&self,
stylesheet: &DocumentStyleSheet,
guard: &SharedRwLockReadGuard,
) {
if !stylesheet.is_effective_for_device(self.stylist.device(), guard) {
return;
}
let locked_script_channel = Mutex::new(self.script_chan.clone());
let pipeline_id = self.id;
let web_font_finished_loading_callback =
move |succeeded: bool| {
let _ = locked_script_channel.lock().unwrap().send(
ConstellationControlMsg::WebFontLoaded(pipeline_id, succeeded),
);
};
let newly_loading_font_count = self.font_context.add_all_web_fonts_from_stylesheet(
stylesheet,
guard,
self.stylist.device(),
Arc::new(web_font_finished_loading_callback) as WebFontLoadFinishedCallback,
self.debug.load_webfonts_synchronously,
);
if self.debug.load_webfonts_synchronously && newly_loading_font_count > 0 {
let _ = self
.script_chan
.send(ConstellationControlMsg::WebFontLoaded(self.id, true));
}
}
fn try_get_layout_root<'dom>(&self, node: impl LayoutNode<'dom>) -> Option<FlowRef> {
let result = node
.to_threadsafe()
.mutate_layout_data()?
.flow_construction_result
.get();
let mut flow = match result {
ConstructionResult::Flow(mut flow, abs_descendants) => {
if flow
.base()
.flags
.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
{
flow.set_absolute_descendants(abs_descendants);
} else {
flow.push_absolute_descendants(abs_descendants);
}
flow
},
_ => return None,
};
FlowRef::deref_mut(&mut flow).mark_as_root();
Some(flow)
}
#[inline(never)]
fn solve_constraints(layout_root: &mut dyn Flow, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!("solve_constraints");
sequential::reflow(layout_root, layout_context, RelayoutMode::Incremental);
}
#[inline(never)]
fn solve_constraints_parallel(
traversal: &rayon::ThreadPool,
layout_root: &mut dyn Flow,
profiler_metadata: Option<TimerMetadata>,
time_profiler_chan: profile_time::ProfilerChan,
layout_context: &LayoutContext,
) {
let _scope = layout_debug_scope!("solve_constraints_parallel");
parallel::reflow(
layout_root,
profiler_metadata,
time_profiler_chan,
layout_context,
traversal,
);
}
fn update_iframe_sizes(
&self,
new_iframe_sizes: FnvHashMap<BrowsingContextId, Size2D<f32, CSSPixel>>,
) {
let old_iframe_sizes =
std::mem::replace(&mut *self.last_iframe_sizes.borrow_mut(), new_iframe_sizes);
if self.last_iframe_sizes.borrow().is_empty() {
return;
}
let size_messages: Vec<_> = self
.last_iframe_sizes
.borrow()
.iter()
.filter_map(|(browsing_context_id, size)| {
match old_iframe_sizes.get(browsing_context_id) {
Some(old_size) if old_size != size => Some(IFrameSizeMsg {
browsing_context_id: *browsing_context_id,
size: *size,
type_: WindowSizeType::Resize,
}),
None => Some(IFrameSizeMsg {
browsing_context_id: *browsing_context_id,
size: *size,
type_: WindowSizeType::Initial,
}),
_ => None,
}
})
.collect();
if !size_messages.is_empty() {
let msg = ConstellationMsg::IFrameSizes(size_messages);
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Layout resize to constellation failed ({}).", e);
}
}
}
fn compute_abs_pos_and_build_display_list(
&self,
data: &Reflow,
reflow_goal: &ReflowGoal,
document: Option<&ServoLayoutDocument>,
layout_root: &mut dyn Flow,
layout_context: &mut LayoutContext,
) {
let writing_mode = layout_root.base().writing_mode;
let (metadata, sender) = (self.profiler_metadata(), self.time_profiler_chan.clone());
profile(
profile_time::ProfilerCategory::LayoutDispListBuild,
metadata.clone(),
sender.clone(),
|| {
layout_root.mut_base().stacking_relative_position =
LogicalPoint::zero(writing_mode)
.to_physical(writing_mode, self.viewport_size)
.to_vector();
layout_root.mut_base().clip = data.page_clip_rect;
let traversal = ComputeStackingRelativePositions { layout_context };
traversal.traverse(layout_root);
if (layout_root
.base()
.restyle_damage
.contains(ServoRestyleDamage::REPAINT) ||
self.display_list.borrow().is_none()) &&
reflow_goal.needs_display_list()
{
let background_color = get_root_flow_background_color(layout_root);
let mut build_state = sequential::build_display_list_for_subtree(
layout_root,
layout_context,
background_color,
data.page_clip_rect.size,
);
debug!("Done building display list.");
let root_size = {
let root_flow = layout_root.base();
root_flow.overflow.scroll.size
};
let origin = Rect::new(Point2D::new(Au(0), Au(0)), root_size).to_layout();
build_state.root_stacking_context.bounds = origin;
build_state.root_stacking_context.overflow = origin;
let iframe_sizes = std::mem::take(&mut build_state.iframe_sizes);
self.update_iframe_sizes(iframe_sizes);
*self.indexable_text.borrow_mut() =
std::mem::take(&mut build_state.indexable_text);
*self.display_list.borrow_mut() = Some(build_state.to_display_list());
}
if !reflow_goal.needs_display() {
document
.expect("No document in a non-display reflow?")
.needs_paint_from_layout();
return;
}
if let Some(document) = document {
document.will_paint();
}
let mut display_list = self.display_list.borrow_mut();
let display_list = display_list.as_mut().unwrap();
if self.debug.dump_display_list {
display_list.print();
}
if self.debug.dump_display_list_json {
println!("{}", serde_json::to_string_pretty(&display_list).unwrap());
}
debug!("Layout done!");
let viewport_size = units::LayoutSize::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 (mut builder, compositor_info, is_contentful) = display_list
.convert_to_webrender(
self.id,
viewport_size,
epoch.into(),
self.debug.dump_display_list,
);
self.paint_time_metrics
.maybe_observe_paint_time(self, epoch, is_contentful.0);
self.webrender_api
.send_display_list(compositor_info, builder.end().1);
let (keys, instance_keys) = self
.font_context
.collect_unused_webrender_resources(false );
self.webrender_api
.remove_unused_font_resources(keys, instance_keys)
},
);
}
fn handle_reflow(&mut self, data: &mut ScriptReflowResult) {
let document = unsafe { ServoLayoutNode::new(&data.document) };
let document = document.as_document().unwrap();
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 Some(root_element) = document.root_element() else {
debug!("layout: No root node: bailing");
return;
};
debug!(
"layout: processing reflow request for: {:?} ({}) (query={:?})",
root_element, self.url, data.reflow_goal
);
trace!("{:?}", ShowSubtree(root_element.as_node()));
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 guards = StylesheetGuards {
author: &author_guard,
ua_or_user: &ua_or_user_guard,
};
let had_used_viewport_units = self.stylist.device().used_viewport_units();
let viewport_size_changed = self.handle_viewport_change(data.window_size, &guards);
if viewport_size_changed && had_used_viewport_units {
if let Some(mut data) = root_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.load_all_web_fonts_from_stylesheet_with_guard(stylesheet, &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.load_all_web_fonts_from_stylesheet_with_guard(
&ua_stylesheets.quirks_mode_stylesheet,
&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(root_element.as_node()) {
LayoutThread::reflow_all_nodes(FlowRef::deref_mut(&mut flow));
}
}
debug!(
"Shadow roots in document {:?}",
document.shadow_roots().len()
);
document.flush_shadow_roots_stylesheets(&mut self.stylist, guards.author);
let restyles = std::mem::take(&mut data.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| unsafe { ServoLayoutNode::new(&r.0).as_element().unwrap() })
.collect();
for (el, restyle) in restyles {
let el = unsafe { ServoLayoutNode::new(&el).as_element().unwrap() };
let mut style_data = match el.mutate_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);
}
style_data.hint.insert(restyle.hint);
style_data.damage = restyle.damage;
debug!("Noting restyle for {:?}: {:?}", el, style_data);
}
self.stylist.flush(&guards, Some(root_element), Some(&map));
let mut layout_context = self.build_layout_context(
guards.clone(),
&map,
data.origin.clone(),
data.animation_timeline_value,
&data.animations,
data.stylesheets_changed,
);
let pool = STYLE_THREAD_POOL.lock().unwrap();
let thread_pool = pool.pool();
let (thread_pool, num_threads) = if self.parallel_flag {
(thread_pool.as_ref(), pool.num_threads.unwrap_or(1))
} else {
(None, 1)
};
let dirty_root = unsafe {
ServoLayoutNode::new(&data.dirty_root.unwrap())
.as_element()
.unwrap()
};
let traversal = RecalcStyleAndConstructFlows::new(layout_context);
let token = {
let shared =
<RecalcStyleAndConstructFlows as DomTraversal<ServoLayoutElement>>::shared_context(
&traversal,
);
RecalcStyleAndConstructFlows::pre_traverse(dirty_root, shared)
};
if token.should_traverse() {
profile(
profile_time::ProfilerCategory::LayoutStyleRecalc,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
let root = driver::traverse_dom::<
ServoLayoutElement,
RecalcStyleAndConstructFlows,
>(&traversal, token, thread_pool);
unsafe {
construct_flows_at_ancestors(traversal.context(), root.as_node());
}
},
);
let text_shaping_time = Duration::nanoseconds(
(get_and_reset_text_shaping_performance_counter() / num_threads) as i64,
);
profile_time::send_profile_data(
profile_time::ProfilerCategory::LayoutTextShaping,
self.profiler_metadata(),
&self.time_profiler_chan,
CrossProcessInstant::epoch(),
CrossProcessInstant::epoch() + text_shaping_time,
);
*self.root_flow.borrow_mut() = self.try_get_layout_root(root_element.as_node());
}
for element in elements_with_snapshot {
unsafe { element.unset_snapshot_flags() }
}
layout_context = traversal.destroy();
if self.debug.dump_style_tree {
println!(
"{:?}",
ShowSubtreeDataAndPrimaryValues(root_element.as_node())
);
}
if self.debug.dump_rule_tree {
layout_context
.style_context
.stylist
.rule_tree()
.dump_stdout(&guards);
}
layout_context.style_context.stylist.rule_tree().maybe_gc();
if let Some(mut root_flow) = self.root_flow.borrow().clone() {
self.perform_post_style_recalc_layout_passes(
&mut root_flow,
&data.reflow_info,
&data.reflow_goal,
Some(&document),
&mut layout_context,
thread_pool,
);
}
self.first_reflow.set(false);
data.result.borrow_mut().as_mut().unwrap().pending_images =
std::mem::take(&mut *layout_context.pending_images.lock().unwrap());
if let ReflowGoal::UpdateScrollNode(scroll_state) = data.reflow_goal {
self.update_scroll_node_state(&scroll_state);
}
}
fn update_scroll_node_state(&self, state: &ScrollState) {
self.scroll_offsets
.borrow_mut()
.insert(state.scroll_id, state.scroll_offset);
let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y);
self.webrender_api.send_scroll_node(
self.id.into(),
units::LayoutPoint::from_untyped(point),
state.scroll_id,
);
}
fn cancel_animations_for_nodes_not_in_flow_tree(
animations: &mut FxHashMap<AnimationSetKey, ElementAnimationSet>,
root_flow: &mut dyn Flow,
) {
let mut invalid_nodes = animations.keys().cloned().collect();
fn traverse_flow(flow: &mut dyn Flow, invalid_nodes: &mut FxHashSet<AnimationSetKey>) {
flow.mutate_fragments(&mut |fragment| {
invalid_nodes.remove(&AnimationSetKey::new_for_non_pseudo(fragment.node));
invalid_nodes.remove(&AnimationSetKey::new_for_pseudo(
fragment.node,
PseudoElement::Before,
));
invalid_nodes.remove(&AnimationSetKey::new_for_pseudo(
fragment.node,
PseudoElement::After,
));
});
for kid in flow.mut_base().children.iter_mut() {
traverse_flow(kid, invalid_nodes)
}
}
traverse_flow(root_flow, &mut invalid_nodes);
for node in &invalid_nodes {
if let Some(state) = animations.get_mut(node) {
state.cancel_all_animations();
}
}
}
fn perform_post_style_recalc_layout_passes(
&self,
root_flow: &mut FlowRef,
data: &Reflow,
reflow_goal: &ReflowGoal,
document: Option<&ServoLayoutDocument>,
context: &mut LayoutContext,
thread_pool: Option<&rayon::ThreadPool>,
) {
Self::cancel_animations_for_nodes_not_in_flow_tree(
&mut (context.style_context.animations.sets.write()),
FlowRef::deref_mut(root_flow),
);
profile(
profile_time::ProfilerCategory::LayoutRestyleDamagePropagation,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
let damage = FlowRef::deref_mut(root_flow).compute_layout_damage();
if self.nonincremental_layout ||
damage.contains(SpecialRestyleDamage::REFLOW_ENTIRE_DOCUMENT)
{
FlowRef::deref_mut(root_flow).reflow_entire_document()
}
},
);
if self.debug.trace_layout {
layout_debug::begin_trace(root_flow.clone());
}
profile(
profile_time::ProfilerCategory::LayoutGeneratedContent,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| sequential::resolve_generated_content(FlowRef::deref_mut(root_flow), context),
);
profile(
profile_time::ProfilerCategory::LayoutFloatPlacementSpeculation,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| sequential::guess_float_placement(FlowRef::deref_mut(root_flow)),
);
if root_flow
.base()
.restyle_damage
.intersects(ServoRestyleDamage::REFLOW | ServoRestyleDamage::REFLOW_OUT_OF_FLOW)
{
profile(
profile_time::ProfilerCategory::LayoutMain,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
let profiler_metadata = self.profiler_metadata();
if let Some(pool) = thread_pool {
LayoutThread::solve_constraints_parallel(
pool,
FlowRef::deref_mut(root_flow),
profiler_metadata,
self.time_profiler_chan.clone(),
&*context,
);
} else {
LayoutThread::solve_constraints(FlowRef::deref_mut(root_flow), context)
}
},
);
}
profile(
profile_time::ProfilerCategory::LayoutStoreOverflow,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
sequential::store_overflow(context, FlowRef::deref_mut(root_flow) as &mut dyn Flow);
},
);
self.perform_post_main_layout_passes(data, root_flow, reflow_goal, document, context);
}
fn perform_post_main_layout_passes(
&self,
data: &Reflow,
root_flow: &mut FlowRef,
reflow_goal: &ReflowGoal,
document: Option<&ServoLayoutDocument>,
layout_context: &mut LayoutContext,
) {
self.compute_abs_pos_and_build_display_list(
data,
reflow_goal,
document,
FlowRef::deref_mut(root_flow),
&mut *layout_context,
);
if self.debug.trace_layout {
layout_debug::end_trace(self.generation.get());
}
if self.debug.dump_flow_tree {
root_flow.print("Post layout flow tree".to_owned());
}
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,
);
for child in flow.mut_base().child_iter_mut() {
LayoutThread::reflow_all_nodes(child);
}
}
fn profiler_metadata(&self) -> Option<TimerMetadata> {
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
},
})
}
fn handle_viewport_change(
&mut self,
window_size_data: WindowSizeData,
guards: &StylesheetGuards,
) -> bool {
let au_viewport_size = Size2D::new(
Au::from_f32_px(window_size_data.initial_viewport.width),
Au::from_f32_px(window_size_data.initial_viewport.height),
);
if self.stylist.device().au_viewport_size() == au_viewport_size &&
self.stylist.device().device_pixel_ratio() == window_size_data.device_pixel_ratio
{
return false;
}
let device = Device::new(
MediaType::screen(),
self.stylist.quirks_mode(),
window_size_data.initial_viewport,
window_size_data.device_pixel_ratio,
Box::new(LayoutFontMetricsProvider),
self.stylist.device().default_computed_values().to_arc(),
);
device.set_root_font_size(self.stylist.device().root_font_size().px());
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 = au_viewport_size;
true
}
}
impl ProfilerMetadataFactory for LayoutThread {
fn new_metadata(&self) -> Option<TimerMetadata> {
self.profiler_metadata()
}
}
fn get_root_flow_background_color(flow: &mut dyn Flow) -> ColorF {
let transparent = ColorF {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
};
if !flow.is_block_like() {
return transparent;
}
let block_flow = flow.as_mut_block();
let kid = match block_flow.base.children.iter_mut().next() {
None => return transparent,
Some(kid) => kid,
};
if !kid.is_block_like() {
return transparent;
}
let kid_block_flow = kid.as_block();
let color = kid_block_flow.fragment.style.resolve_color(
kid_block_flow
.fragment
.style
.get_background()
.background_color
.clone(),
);
color.to_layout()
}
fn get_ua_stylesheets() -> Result<UserAgentStylesheets, &'static str> {
fn parse_ua_stylesheet(
shared_lock: &SharedRwLock,
filename: &str,
content: &[u8],
) -> Result<DocumentStyleSheet, &'static str> {
let url = Url::parse(&format!("chrome://resources/{:?}", filename))
.ok()
.unwrap();
Ok(DocumentStyleSheet(ServoArc::new(Stylesheet::from_bytes(
content,
url.into(),
None,
None,
Origin::UserAgent,
MediaList::empty(),
shared_lock.clone(),
None,
None,
QuirksMode::NoQuirks,
))))
}
let shared_lock = &GLOBAL_STYLE_DATA.shared_lock;
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 (contents, url) in &opts::get().user_stylesheets {
user_or_user_agent_stylesheets.push(DocumentStyleSheet(ServoArc::new(
Stylesheet::from_bytes(
contents,
UrlExtraData(url.get_arc()),
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,
quirks_mode_stylesheet,
})
}
static UA_STYLESHEETS: LazyLock<UserAgentStylesheets> =
LazyLock::new(|| match get_ua_stylesheets() {
Ok(stylesheets) => stylesheets,
Err(filename) => {
error!("Failed to load UA stylesheet {}!", filename);
process::exit(1);
},
});
struct RegisteredPainterImpl {
painter: Box<dyn Painter>,
name: Atom,
properties: FxHashMap<Atom, PropertyId>,
}
impl SpeculativePainter for RegisteredPainterImpl {
fn speculatively_draw_a_paint_image(
&self,
properties: Vec<(Atom, String)>,
arguments: Vec<String>,
) {
self.painter
.speculatively_draw_a_paint_image(properties, arguments);
}
}
impl RegisteredSpeculativePainter for RegisteredPainterImpl {
fn properties(&self) -> &FxHashMap<Atom, PropertyId> {
&self.properties
}
fn name(&self) -> Atom {
self.name.clone()
}
}
impl Painter for RegisteredPainterImpl {
fn draw_a_paint_image(
&self,
size: Size2D<f32, CSSPixel>,
device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
properties: Vec<(Atom, String)>,
arguments: Vec<String>,
) -> Result<DrawAPaintImageResult, PaintWorkletError> {
self.painter
.draw_a_paint_image(size, device_pixel_ratio, properties, arguments)
}
}
impl RegisteredPainter for RegisteredPainterImpl {}
struct RegisteredPaintersImpl(FnvHashMap<Atom, RegisteredPainterImpl>);
impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
fn get(&self, name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> {
self.0
.get(name)
.map(|painter| painter as &dyn RegisteredSpeculativePainter)
}
}
impl RegisteredPainters for RegisteredPaintersImpl {
fn get(&self, name: &Atom) -> Option<&dyn RegisteredPainter> {
self.0
.get(name)
.map(|painter| painter as &dyn RegisteredPainter)
}
}
#[derive(Debug)]
struct LayoutFontMetricsProvider;
impl FontMetricsProvider for LayoutFontMetricsProvider {
fn query_font_metrics(
&self,
_vertical: bool,
_font: &Font,
_base_size: style::values::computed::CSSPixelLength,
_in_media_query: bool,
_retrieve_math_scales: bool,
) -> style::font_metrics::FontMetrics {
Default::default()
}
fn base_size_for_generic(&self, generic: GenericFontFamily) -> Length {
Length::new(match generic {
GenericFontFamily::Monospace => pref!(fonts.default_monospace_size),
_ => pref!(fonts.default_size),
} as f32)
.max(Length::new(0.0))
}
}