Skip to main content

layout/
layout_impl.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![expect(unsafe_code)]
6
7use std::cell::{Cell, RefCell};
8use std::collections::HashMap;
9use std::fmt::Debug;
10use std::process;
11use std::rc::Rc;
12use std::sync::{Arc, LazyLock};
13
14use app_units::Au;
15use bitflags::bitflags;
16use embedder_traits::{Theme, ViewportDetails};
17use euclid::{Point2D, Rect, Scale, Size2D};
18use fonts::{FontContext, FontContextWebFontMethods, WebFontDocumentContext};
19use fonts_traits::StylesheetWebFontLoadFinishedCallback;
20use layout_api::wrapper_traits::LayoutNode;
21use layout_api::{
22    AxesOverflow, BoxAreaType, CSSPixelRectIterator, IFrameSizes, Layout, LayoutConfig,
23    LayoutFactory, OffsetParentResponse, PhysicalSides, QueryMsg, ReflowGoal, ReflowPhasesRun,
24    ReflowRequest, ReflowRequestRestyle, ReflowResult, ReflowStatistics, ScrollContainerQueryFlags,
25    ScrollContainerResponse, TrustedNodeAddress, with_layout_state,
26};
27use log::{debug, error, warn};
28use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
29use net_traits::image_cache::ImageCache;
30use paint_api::CrossProcessPaintApi;
31use paint_api::display_list::ScrollType;
32use parking_lot::{Mutex, RwLock};
33use profile_traits::mem::{Report, ReportKind};
34use profile_traits::time::{
35    self as profile_time, TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType,
36};
37use profile_traits::{path, time_profile};
38use rustc_hash::FxHashMap;
39use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode};
40use script_traits::{DrawAPaintImageResult, PaintWorkletError, Painter, ScriptThreadMessage};
41use servo_arc::Arc as ServoArc;
42use servo_base::generic_channel::GenericSender;
43use servo_base::id::{PipelineId, WebViewId};
44use servo_config::opts::{self, DiagnosticsLogging};
45use servo_config::pref;
46use servo_url::ServoUrl;
47use style::animation::DocumentAnimationSet;
48use style::context::{
49    QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters, SharedStyleContext,
50};
51use style::device::Device;
52use style::device::servo::FontMetricsProvider;
53use style::dom::{OpaqueNode, ShowSubtreeDataAndPrimaryValues, TElement, TNode};
54use style::font_metrics::FontMetrics;
55use style::global_style_data::GLOBAL_STYLE_DATA;
56use style::invalidation::element::restyle_hints::RestyleHint;
57use style::invalidation::stylesheets::StylesheetInvalidationSet;
58use style::media_queries::{MediaList, MediaType};
59use style::properties::style_structs::Font;
60use style::properties::{ComputedValues, PropertyId};
61use style::queries::values::PrefersColorScheme;
62use style::selector_parser::{PseudoElement, RestyleDamage, SnapshotMap};
63use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards};
64use style::stylesheets::{
65    CustomMediaMap, DocumentStyleSheet, Origin, Stylesheet, StylesheetInDocument,
66};
67use style::stylist::Stylist;
68use style::traversal::DomTraversal;
69use style::traversal_flags::TraversalFlags;
70use style::values::computed::font::GenericFontFamily;
71use style::values::computed::{CSSPixelLength, FontSize, Length, NonNegativeLength, XLang};
72use style::values::specified::font::{KeywordInfo, QueryFontMetricsFlags};
73use style::{Zero, driver};
74use style_traits::{CSSPixel, SpeculativePainter};
75use stylo_atoms::Atom;
76use url::Url;
77use webrender_api::ExternalScrollId;
78use webrender_api::units::{DevicePixel, LayoutVector2D};
79
80use crate::context::{CachedImageOrError, ImageResolver, LayoutContext};
81use crate::display_list::{DisplayListBuilder, HitTest, PaintTimingHandler, StackingContextTree};
82use crate::query::{
83    find_character_offset_in_fragment_descendants, get_the_text_steps, process_box_area_request,
84    process_box_areas_request, process_client_rect_request, process_current_css_zoom_query,
85    process_effective_overflow_query, process_node_scroll_area_request,
86    process_offset_parent_query, process_padding_request, process_resolved_font_style_query,
87    process_resolved_style_request, process_scroll_container_query,
88};
89use crate::traversal::{RecalcStyle, compute_damage_and_rebuild_box_tree};
90use crate::{BoxTree, FragmentTree};
91
92// This mutex is necessary due to syncronisation issues between two different types of thread-local storage
93// which manifest themselves when the layout thread tries to layout iframes in parallel with the main page
94//
95// See: https://github.com/servo/servo/pull/29792
96// And: https://gist.github.com/mukilan/ed57eb61b83237a05fbf6360ec5e33b0
97static STYLE_THREAD_POOL: Mutex<&LazyLock<style::global_style_data::StyleThreadPool>> =
98    Mutex::new(&style::global_style_data::STYLE_THREAD_POOL);
99
100/// A CSS file to style the user agent stylesheet.
101static USER_AGENT_CSS: &[u8] = include_bytes!("./stylesheets/user-agent.css");
102
103/// A CSS file to style the Servo browser.
104static SERVO_CSS: &[u8] = include_bytes!("./stylesheets/servo.css");
105
106/// A CSS file to style the presentational hints.
107static PRESENTATIONAL_HINTS_CSS: &[u8] = include_bytes!("./stylesheets/presentational-hints.css");
108
109/// A CSS file to style the quirks mode.
110static QUIRKS_MODE_CSS: &[u8] = include_bytes!("./stylesheets/quirks-mode.css");
111
112/// Information needed by layout.
113pub struct LayoutThread {
114    /// The ID of the pipeline that we belong to.
115    id: PipelineId,
116
117    /// The webview that contains the pipeline we belong to.
118    webview_id: WebViewId,
119
120    /// The URL of the pipeline that we belong to.
121    url: ServoUrl,
122
123    /// Performs CSS selector matching and style resolution.
124    stylist: Stylist,
125
126    /// Is the current reflow of an iframe, as opposed to a root window?
127    is_iframe: bool,
128
129    /// The channel on which messages can be sent to the script thread.
130    script_chan: GenericSender<ScriptThreadMessage>,
131
132    /// The channel on which messages can be sent to the time profiler.
133    time_profiler_chan: profile_time::ProfilerChan,
134
135    /// Reference to the script thread image cache.
136    image_cache: Arc<dyn ImageCache>,
137
138    /// A FontContext to be used during layout.
139    font_context: Arc<FontContext>,
140
141    /// Whether or not user agent stylesheets have been added to the Stylist or not.
142    have_added_user_agent_stylesheets: bool,
143
144    // A vector of parsed `DocumentStyleSheet`s representing the corresponding `UserStyleSheet`s
145    // associated with the `WebView` to which this `Layout` belongs. The `DocumentStylesheet`s might
146    // be shared with `Layout`s in the same `ScriptThread`.
147    user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
148
149    /// Whether or not this [`LayoutImpl`]'s [`Device`] has changed since the last restyle.
150    /// If it has, a restyle is pending.
151    device_has_changed: bool,
152
153    /// Is this the first reflow in this LayoutThread?
154    have_ever_generated_display_list: Cell<bool>,
155
156    /// Whether a new overflow calculation needs to happen due to changes to the fragment
157    /// tree. This is set to true every time a restyle requests overflow calculation.
158    need_overflow_calculation: Cell<bool>,
159
160    /// Whether a new display list is necessary due to changes to layout or stacking
161    /// contexts. This is set to true every time layout changes, even when a display list
162    /// isn't requested for this layout, such as for layout queries. The next time a
163    /// layout requests a display list, it is produced unconditionally, even when the
164    /// layout trees remain the same.
165    need_new_display_list: Cell<bool>,
166
167    /// Whether or not the existing stacking context tree is dirty and needs to be
168    /// rebuilt. This happens after a relayout or overflow update. The reason that we
169    /// don't simply clear the stacking context tree when it becomes dirty is that we need
170    /// to preserve scroll offsets from the old tree to the new one.
171    need_new_stacking_context_tree: Cell<bool>,
172
173    /// The box tree.
174    box_tree: RefCell<Option<Arc<BoxTree>>>,
175
176    /// The fragment tree.
177    fragment_tree: RefCell<Option<Rc<FragmentTree>>>,
178
179    /// The [`StackingContextTree`] cached from previous layouts.
180    stacking_context_tree: RefCell<Option<StackingContextTree>>,
181
182    // A cache that maps image resources specified in CSS (e.g as the `url()` value
183    // for `background-image` or `content` properties) to either the final resolved
184    // image data, or an error if the image cache failed to load/decode the image.
185    resolved_images_cache: Arc<RwLock<HashMap<ServoUrl, CachedImageOrError>>>,
186
187    /// The executors for paint worklets.
188    registered_painters: RegisteredPaintersImpl,
189
190    /// Cross-process access to the `Paint` API.
191    paint_api: CrossProcessPaintApi,
192
193    /// Debug options, copied from configuration to this `LayoutThread` in order
194    /// to avoid having to constantly access the thread-safe global options.
195    debug: DiagnosticsLogging,
196
197    /// Tracks the node that was highlighted by the devtools during the last reflow.
198    ///
199    /// If this changed, then we need to create a new display list.
200    previously_highlighted_dom_node: Cell<Option<OpaqueNode>>,
201
202    /// Handler for all Paint Timings
203    paint_timing_handler: RefCell<Option<PaintTimingHandler>>,
204
205    /// Whether accessibility is active in this layout.
206    /// (Note: this is a temporary field which will be replaced with an optional accessibility tree member.)
207    accessibility_active: Cell<bool>,
208}
209
210pub struct LayoutFactoryImpl();
211
212impl LayoutFactory for LayoutFactoryImpl {
213    fn create(&self, config: LayoutConfig) -> Box<dyn Layout> {
214        Box::new(LayoutThread::new(config))
215    }
216}
217
218impl Drop for LayoutThread {
219    fn drop(&mut self) {
220        let (keys, instance_keys) = self
221            .font_context
222            .collect_unused_webrender_resources(true /* all */);
223        self.paint_api
224            .remove_unused_font_resources(self.webview_id.into(), keys, instance_keys)
225    }
226}
227
228impl Layout for LayoutThread {
229    fn device(&self) -> &Device {
230        self.stylist.device()
231    }
232
233    fn set_theme(&mut self, theme: Theme) -> bool {
234        let theme: PrefersColorScheme = theme.into();
235        let device = self.stylist.device_mut();
236        if theme == device.color_scheme() {
237            return false;
238        }
239
240        device.set_color_scheme(theme);
241        self.device_has_changed = true;
242        true
243    }
244
245    fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool {
246        let device = self.stylist.device_mut();
247        let device_pixel_ratio = Scale::new(viewport_details.hidpi_scale_factor.get());
248        if device.viewport_size() == viewport_details.size &&
249            device.device_pixel_ratio() == device_pixel_ratio
250        {
251            return false;
252        }
253
254        device.set_viewport_size(viewport_details.size);
255        device.set_device_pixel_ratio(device_pixel_ratio);
256        self.device_has_changed = true;
257        true
258    }
259
260    fn load_web_fonts_from_stylesheet(
261        &self,
262        stylesheet: &ServoArc<Stylesheet>,
263        document_context: &WebFontDocumentContext,
264    ) {
265        let guard = stylesheet.shared_lock.read();
266        self.load_all_web_fonts_from_stylesheet_with_guard(
267            &DocumentStyleSheet(stylesheet.clone()),
268            &guard,
269            document_context,
270        );
271    }
272
273    #[servo_tracing::instrument(skip_all)]
274    fn add_stylesheet(
275        &mut self,
276        stylesheet: ServoArc<Stylesheet>,
277        before_stylesheet: Option<ServoArc<Stylesheet>>,
278        document_context: &WebFontDocumentContext,
279    ) {
280        let guard = stylesheet.shared_lock.read();
281        let stylesheet = DocumentStyleSheet(stylesheet.clone());
282        self.load_all_web_fonts_from_stylesheet_with_guard(&stylesheet, &guard, document_context);
283
284        match before_stylesheet {
285            Some(insertion_point) => self.stylist.insert_stylesheet_before(
286                stylesheet,
287                DocumentStyleSheet(insertion_point),
288                &guard,
289            ),
290            None => self.stylist.append_stylesheet(stylesheet, &guard),
291        }
292    }
293
294    #[servo_tracing::instrument(skip_all)]
295    fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>) {
296        let guard = stylesheet.shared_lock.read();
297        let stylesheet = DocumentStyleSheet(stylesheet.clone());
298        self.stylist.remove_stylesheet(stylesheet.clone(), &guard);
299        self.font_context
300            .remove_all_web_fonts_from_stylesheet(&stylesheet);
301    }
302
303    #[servo_tracing::instrument(skip_all)]
304    fn remove_cached_image(&mut self, url: &ServoUrl) {
305        let mut resolved_images_cache = self.resolved_images_cache.write();
306        resolved_images_cache.remove(url);
307    }
308
309    /// Return the resolved values of this node's padding rect.
310    #[servo_tracing::instrument(skip_all)]
311    fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides> {
312        with_layout_state(|| {
313            // If we have not built a fragment tree yet, there is no way we have layout information for
314            // this query, which can be run without forcing a layout (for IntersectionObserver).
315            if self.fragment_tree.borrow().is_none() {
316                return None;
317            }
318
319            let node = unsafe { ServoLayoutNode::new(&node) };
320            process_padding_request(node.to_threadsafe())
321        })
322    }
323
324    /// Return the union of this node's areas in the coordinate space of the Document. This is used
325    /// to implement `getBoundingClientRect()` and support many other API where the such query is
326    /// required.
327    ///
328    /// Part of <https://drafts.csswg.org/cssom-view-1/#element-get-the-bounding-box>.
329    #[servo_tracing::instrument(skip_all)]
330    fn query_box_area(
331        &self,
332        node: TrustedNodeAddress,
333        area: BoxAreaType,
334        exclude_transform_and_inline: bool,
335    ) -> Option<Rect<Au, CSSPixel>> {
336        with_layout_state(|| {
337            // If we have not built a fragment tree yet, there is no way we have layout information for
338            // this query, which can be run without forcing a layout (for IntersectionObserver).
339            if self.fragment_tree.borrow().is_none() {
340                return None;
341            }
342
343            let node = unsafe { ServoLayoutNode::new(&node) };
344            let stacking_context_tree = self.stacking_context_tree.borrow();
345            let stacking_context_tree = stacking_context_tree
346                .as_ref()
347                .expect("Should always have a StackingContextTree for box area queries");
348            process_box_area_request(
349                stacking_context_tree,
350                node.to_threadsafe(),
351                area,
352                exclude_transform_and_inline,
353            )
354        })
355    }
356
357    /// Get a `Vec` of bounding boxes of this node's `Fragment`s specific area in the coordinate space of
358    /// the Document. This is used to implement `getClientRects()`.
359    ///
360    /// See <https://drafts.csswg.org/cssom-view/#dom-element-getclientrects>.
361    #[servo_tracing::instrument(skip_all)]
362    fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectIterator {
363        with_layout_state(|| {
364            // If we have not built a fragment tree yet, there is no way we have layout information for
365            // this query, which can be run without forcing a layout (for IntersectionObserver).
366            if self.fragment_tree.borrow().is_none() {
367                return Box::new(std::iter::empty()) as CSSPixelRectIterator;
368            }
369
370            let node = unsafe { ServoLayoutNode::new(&node) };
371            let stacking_context_tree = self.stacking_context_tree.borrow();
372            let stacking_context_tree = stacking_context_tree
373                .as_ref()
374                .expect("Should always have a StackingContextTree for box area queries");
375            process_box_areas_request(stacking_context_tree, node.to_threadsafe(), area)
376        })
377    }
378
379    #[servo_tracing::instrument(skip_all)]
380    fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel> {
381        with_layout_state(|| {
382            let node = unsafe { ServoLayoutNode::new(&node) };
383            process_client_rect_request(node.to_threadsafe())
384        })
385    }
386
387    #[servo_tracing::instrument(skip_all)]
388    fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32 {
389        with_layout_state(|| {
390            let node = unsafe { ServoLayoutNode::new(&node) };
391            process_current_css_zoom_query(node)
392        })
393    }
394
395    #[servo_tracing::instrument(skip_all)]
396    fn query_element_inner_outer_text(&self, node: layout_api::TrustedNodeAddress) -> String {
397        with_layout_state(|| {
398            let node = unsafe { ServoLayoutNode::new(&node) };
399            get_the_text_steps(node)
400        })
401    }
402    #[servo_tracing::instrument(skip_all)]
403    fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse {
404        with_layout_state(|| {
405            let node = unsafe { ServoLayoutNode::new(&node) };
406            let stacking_context_tree = self.stacking_context_tree.borrow();
407            let stacking_context_tree = stacking_context_tree
408                .as_ref()
409                .expect("Should always have a StackingContextTree for offset parent queries");
410            process_offset_parent_query(&stacking_context_tree.paint_info.scroll_tree, node)
411                .unwrap_or_default()
412        })
413    }
414
415    #[servo_tracing::instrument(skip_all)]
416    fn query_scroll_container(
417        &self,
418        node: Option<TrustedNodeAddress>,
419        flags: ScrollContainerQueryFlags,
420    ) -> Option<ScrollContainerResponse> {
421        with_layout_state(|| {
422            let node = unsafe { node.as_ref().map(|node| ServoLayoutNode::new(node)) };
423            let viewport_overflow = self
424                .box_tree
425                .borrow()
426                .as_ref()
427                .expect("Should have a BoxTree for all scroll container queries.")
428                .viewport_overflow;
429            process_scroll_container_query(node, flags, viewport_overflow)
430        })
431    }
432
433    #[servo_tracing::instrument(skip_all)]
434    fn query_resolved_style(
435        &self,
436        node: TrustedNodeAddress,
437        pseudo: Option<PseudoElement>,
438        property_id: PropertyId,
439        animations: DocumentAnimationSet,
440        animation_timeline_value: f64,
441    ) -> String {
442        with_layout_state(|| {
443            let node = unsafe { ServoLayoutNode::new(&node) };
444            let document = node.owner_doc();
445            let document_shared_lock = document.style_shared_lock();
446            let guards = StylesheetGuards {
447                author: &document_shared_lock.read(),
448                ua_or_user: &GLOBAL_STYLE_DATA.shared_lock.read(),
449            };
450            let snapshot_map = SnapshotMap::new();
451
452            let shared_style_context = self.build_shared_style_context(
453                guards,
454                &snapshot_map,
455                animation_timeline_value,
456                &animations,
457                TraversalFlags::empty(),
458            );
459
460            process_resolved_style_request(&shared_style_context, node, &pseudo, &property_id)
461        })
462    }
463
464    #[servo_tracing::instrument(skip_all)]
465    fn query_resolved_font_style(
466        &self,
467        node: TrustedNodeAddress,
468        value: &str,
469        animations: DocumentAnimationSet,
470        animation_timeline_value: f64,
471    ) -> Option<ServoArc<Font>> {
472        with_layout_state(|| {
473            let node = unsafe { ServoLayoutNode::new(&node) };
474            let document = node.owner_doc();
475            let document_shared_lock = document.style_shared_lock();
476            let guards = StylesheetGuards {
477                author: &document_shared_lock.read(),
478                ua_or_user: &GLOBAL_STYLE_DATA.shared_lock.read(),
479            };
480            let snapshot_map = SnapshotMap::new();
481            let shared_style_context = self.build_shared_style_context(
482                guards,
483                &snapshot_map,
484                animation_timeline_value,
485                &animations,
486                TraversalFlags::empty(),
487            );
488
489            process_resolved_font_style_query(
490                &shared_style_context,
491                node,
492                value,
493                self.url.clone(),
494                document_shared_lock,
495            )
496        })
497    }
498
499    #[servo_tracing::instrument(skip_all)]
500    fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel> {
501        with_layout_state(|| {
502            let node = node.map(|node| unsafe { ServoLayoutNode::new(&node).to_threadsafe() });
503            process_node_scroll_area_request(node, self.fragment_tree.borrow().clone())
504        })
505    }
506
507    #[servo_tracing::instrument(skip_all)]
508    fn query_text_index(
509        &self,
510        node: TrustedNodeAddress,
511        point_in_node: Point2D<Au, CSSPixel>,
512    ) -> Option<usize> {
513        with_layout_state(|| {
514            let node = unsafe { ServoLayoutNode::new(&node).to_threadsafe() };
515            let stacking_context_tree = self.stacking_context_tree.borrow_mut();
516            let stacking_context_tree = stacking_context_tree.as_ref()?;
517            find_character_offset_in_fragment_descendants(
518                &node,
519                stacking_context_tree,
520                point_in_node,
521            )
522        })
523    }
524
525    #[servo_tracing::instrument(skip_all)]
526    fn query_elements_from_point(
527        &self,
528        point: webrender_api::units::LayoutPoint,
529        flags: layout_api::ElementsFromPointFlags,
530    ) -> Vec<layout_api::ElementsFromPointResult> {
531        with_layout_state(|| {
532            self.stacking_context_tree
533                .borrow_mut()
534                .as_mut()
535                .map(|tree| HitTest::run(tree, point, flags))
536                .unwrap_or_default()
537        })
538    }
539
540    #[servo_tracing::instrument(skip_all)]
541    fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow> {
542        with_layout_state(|| {
543            let node = unsafe { ServoLayoutNode::new(&node).to_threadsafe() };
544            process_effective_overflow_query(node)
545        })
546    }
547
548    fn exit_now(&mut self) {}
549
550    fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps) {
551        // TODO: Measure more than just display list, stylist, and font context.
552        let formatted_url = &format!("url({})", self.url);
553        reports.push(Report {
554            path: path![formatted_url, "layout-thread", "display-list"],
555            kind: ReportKind::ExplicitJemallocHeapSize,
556            size: 0,
557        });
558
559        reports.push(Report {
560            path: path![formatted_url, "layout-thread", "stylist"],
561            kind: ReportKind::ExplicitJemallocHeapSize,
562            size: self.stylist.size_of(ops),
563        });
564
565        reports.push(Report {
566            path: path![formatted_url, "layout-thread", "font-context"],
567            kind: ReportKind::ExplicitJemallocHeapSize,
568            size: self.font_context.conditional_size_of(ops),
569        });
570
571        reports.push(Report {
572            path: path![formatted_url, "layout-thread", "box-tree"],
573            kind: ReportKind::ExplicitJemallocHeapSize,
574            size: self
575                .box_tree
576                .borrow()
577                .as_ref()
578                .map_or(0, |tree| tree.conditional_size_of(ops)),
579        });
580
581        reports.push(Report {
582            path: path![formatted_url, "layout-thread", "fragment-tree"],
583            kind: ReportKind::ExplicitJemallocHeapSize,
584            size: self
585                .fragment_tree
586                .borrow()
587                .as_ref()
588                .map(|tree| tree.conditional_size_of(ops))
589                .unwrap_or_default(),
590        });
591
592        reports.push(Report {
593            path: path![formatted_url, "layout-thread", "stacking-context-tree"],
594            kind: ReportKind::ExplicitJemallocHeapSize,
595            size: self.stacking_context_tree.size_of(ops),
596        });
597
598        reports.extend(self.image_cache.memory_reports(formatted_url, ops));
599    }
600
601    fn set_quirks_mode(&mut self, quirks_mode: QuirksMode) {
602        self.stylist.set_quirks_mode(quirks_mode);
603    }
604
605    fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult> {
606        time_profile!(
607            profile_time::ProfilerCategory::Layout,
608            self.profiler_metadata(),
609            self.time_profiler_chan.clone(),
610            || with_layout_state(|| self.handle_reflow(reflow_request)),
611        )
612    }
613
614    fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails) {
615        with_layout_state(|| {
616            if self.stacking_context_tree.borrow().is_some() &&
617                !self.need_new_stacking_context_tree.get()
618            {
619                return;
620            }
621            self.build_stacking_context_tree(viewport_details);
622        })
623    }
624
625    fn register_paint_worklet_modules(
626        &mut self,
627        _name: Atom,
628        _properties: Vec<Atom>,
629        _painter: Box<dyn Painter>,
630    ) {
631    }
632
633    fn set_scroll_offsets_from_renderer(
634        &mut self,
635        scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
636    ) {
637        let mut stacking_context_tree = self.stacking_context_tree.borrow_mut();
638        let Some(stacking_context_tree) = stacking_context_tree.as_mut() else {
639            warn!("Received scroll offsets before finishing layout.");
640            return;
641        };
642
643        stacking_context_tree
644            .paint_info
645            .scroll_tree
646            .set_all_scroll_offsets(scroll_states);
647    }
648
649    fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D> {
650        self.stacking_context_tree
651            .borrow_mut()
652            .as_mut()
653            .and_then(|tree| tree.paint_info.scroll_tree.scroll_offset(id))
654    }
655
656    fn needs_new_display_list(&self) -> bool {
657        self.need_new_display_list.get()
658    }
659
660    fn set_needs_new_display_list(&self) {
661        self.need_new_display_list.set(true);
662    }
663
664    /// <https://drafts.css-houdini.org/css-properties-values-api-1/#the-registerproperty-function>
665    fn stylist_mut(&mut self) -> &mut Stylist {
666        &mut self.stylist
667    }
668
669    fn set_accessibility_active(&self, active: bool) {
670        if !(pref!(accessibility_enabled)) {
671            return;
672        }
673
674        self.accessibility_active.replace(active);
675    }
676}
677
678impl LayoutThread {
679    fn new(config: LayoutConfig) -> LayoutThread {
680        // Let webrender know about this pipeline by sending an empty display list.
681        config
682            .paint_api
683            .send_initial_transaction(config.webview_id, config.id.into());
684
685        let mut font = Font::initial_values();
686        let default_font_size = pref!(fonts_default_size);
687        font.font_size = FontSize {
688            computed_size: NonNegativeLength::new(default_font_size as f32),
689            used_size: NonNegativeLength::new(default_font_size as f32),
690            keyword_info: KeywordInfo::medium(),
691        };
692
693        // The device pixel ratio is incorrect (it does not have the hidpi value),
694        // but it will be set correctly when the initial reflow takes place.
695        let device = Device::new(
696            MediaType::screen(),
697            QuirksMode::NoQuirks,
698            config.viewport_details.size,
699            Scale::new(config.viewport_details.hidpi_scale_factor.get()),
700            Box::new(LayoutFontMetricsProvider(config.font_context.clone())),
701            ComputedValues::initial_values_with_font_override(font),
702            config.theme.into(),
703        );
704
705        LayoutThread {
706            id: config.id,
707            webview_id: config.webview_id,
708            url: config.url,
709            is_iframe: config.is_iframe,
710            script_chan: config.script_chan.clone(),
711            time_profiler_chan: config.time_profiler_chan,
712            registered_painters: RegisteredPaintersImpl(Default::default()),
713            image_cache: config.image_cache,
714            font_context: config.font_context,
715            have_added_user_agent_stylesheets: false,
716            have_ever_generated_display_list: Cell::new(false),
717            device_has_changed: false,
718            need_overflow_calculation: Cell::new(false),
719            need_new_display_list: Cell::new(false),
720            need_new_stacking_context_tree: Cell::new(false),
721            box_tree: Default::default(),
722            fragment_tree: Default::default(),
723            stacking_context_tree: Default::default(),
724            paint_api: config.paint_api,
725            stylist: Stylist::new(device, QuirksMode::NoQuirks),
726            resolved_images_cache: Default::default(),
727            debug: opts::get().debug.clone(),
728            previously_highlighted_dom_node: Cell::new(None),
729            paint_timing_handler: Default::default(),
730            user_stylesheets: config.user_stylesheets,
731            accessibility_active: Cell::new(false),
732        }
733    }
734
735    fn build_shared_style_context<'a>(
736        &'a self,
737        guards: StylesheetGuards<'a>,
738        snapshot_map: &'a SnapshotMap,
739        animation_timeline_value: f64,
740        animations: &DocumentAnimationSet,
741        traversal_flags: TraversalFlags,
742    ) -> SharedStyleContext<'a> {
743        SharedStyleContext {
744            stylist: &self.stylist,
745            options: GLOBAL_STYLE_DATA.options.clone(),
746            guards,
747            visited_styles_enabled: false,
748            animations: animations.clone(),
749            registered_speculative_painters: &self.registered_painters,
750            current_time_for_animations: animation_timeline_value,
751            traversal_flags,
752            snapshot_map,
753        }
754    }
755
756    fn load_all_web_fonts_from_stylesheet_with_guard(
757        &self,
758        stylesheet: &DocumentStyleSheet,
759        guard: &SharedRwLockReadGuard,
760        document_context: &WebFontDocumentContext,
761    ) {
762        let custom_media = &CustomMediaMap::default();
763        if !stylesheet.is_effective_for_device(self.stylist.device(), custom_media, guard) {
764            return;
765        }
766
767        let locked_script_channel = Mutex::new(self.script_chan.clone());
768        let pipeline_id = self.id;
769        let web_font_finished_loading_callback = move |succeeded: bool| {
770            if succeeded {
771                let _ = locked_script_channel
772                    .lock()
773                    .send(ScriptThreadMessage::WebFontLoaded(pipeline_id));
774            }
775        };
776
777        self.font_context.add_all_web_fonts_from_stylesheet(
778            self.webview_id,
779            stylesheet,
780            guard,
781            self.stylist.device(),
782            Arc::new(web_font_finished_loading_callback) as StylesheetWebFontLoadFinishedCallback,
783            document_context,
784        );
785    }
786
787    /// In some cases, if a restyle isn't necessary we can skip doing any work for layout
788    /// entirely. This check allows us to return early from layout without doing any work
789    /// at all.
790    fn can_skip_reflow_request_entirely(&self, reflow_request: &ReflowRequest) -> bool {
791        // If a restyle is necessary, restyle and reflow is a necessity.
792        if reflow_request.restyle.is_some() {
793            return false;
794        }
795        // We always need to at least build a fragment tree.
796        if self.fragment_tree.borrow().is_none() {
797            return false;
798        }
799
800        // If we have a fragment tree and it's up-to-date and this reflow
801        // doesn't need more reflow results, we can skip the rest of layout.
802        let necessary_phases = ReflowPhases::necessary(&reflow_request.reflow_goal);
803        if necessary_phases.is_empty() {
804            return true;
805        }
806
807        // If only the stacking context tree is required, and it's up-to-date,
808        // layout is unnecessary, otherwise a layout is necessary.
809        if necessary_phases == ReflowPhases::StackingContextTreeConstruction {
810            return self.stacking_context_tree.borrow().is_some() &&
811                !self.need_new_stacking_context_tree.get();
812        }
813
814        // Otherwise, the only interesting thing is whether the current display
815        // list is up-to-date.
816        assert_eq!(
817            necessary_phases,
818            ReflowPhases::StackingContextTreeConstruction | ReflowPhases::DisplayListConstruction
819        );
820        !self.need_new_display_list.get()
821    }
822
823    fn maybe_print_reflow_event(&self, reflow_request: &ReflowRequest) {
824        if !self.debug.relayout_event {
825            return;
826        }
827
828        println!(
829            "**** Reflow({}) => {:?}, {:?}",
830            self.id,
831            reflow_request.reflow_goal,
832            reflow_request
833                .restyle
834                .as_ref()
835                .map(|restyle| restyle.reason)
836                .unwrap_or_default()
837        );
838    }
839
840    /// Checks whether we need to update the scroll node, and report whether the
841    /// node is scrolled. We need to update the scroll node whenever it is requested.
842    fn handle_update_scroll_node_request(&self, reflow_request: &ReflowRequest) -> bool {
843        if let ReflowGoal::UpdateScrollNode(external_scroll_id, offset) = reflow_request.reflow_goal
844        {
845            self.set_scroll_offset_from_script(external_scroll_id, offset)
846        } else {
847            false
848        }
849    }
850
851    /// The high-level routine that performs layout.
852    #[servo_tracing::instrument(skip_all)]
853    fn handle_reflow(&mut self, mut reflow_request: ReflowRequest) -> Option<ReflowResult> {
854        self.maybe_print_reflow_event(&reflow_request);
855
856        if self.can_skip_reflow_request_entirely(&reflow_request) {
857            // We can skip layout, but we might need to update a scroll node.
858            return self
859                .handle_update_scroll_node_request(&reflow_request)
860                .then(|| ReflowResult {
861                    reflow_phases_run: ReflowPhasesRun::UpdatedScrollNodeOffset,
862                    ..Default::default()
863                });
864        }
865
866        let document = unsafe { ServoLayoutNode::new(&reflow_request.document) };
867        let document = document.as_document().unwrap();
868        let Some(root_element) = document.root_element() else {
869            debug!("layout: No root node: bailing");
870            return None;
871        };
872
873        let image_resolver = Arc::new(ImageResolver {
874            origin: reflow_request.origin.clone(),
875            image_cache: self.image_cache.clone(),
876            resolved_images_cache: self.resolved_images_cache.clone(),
877            pending_images: Mutex::default(),
878            pending_rasterization_images: Mutex::default(),
879            pending_svg_elements_for_serialization: Mutex::default(),
880            animating_images: reflow_request.animating_images.clone(),
881            animation_timeline_value: reflow_request.animation_timeline_value,
882        });
883        let mut reflow_statistics = Default::default();
884
885        let (mut reflow_phases_run, iframe_sizes) = self.restyle_and_build_trees(
886            &mut reflow_request,
887            document,
888            root_element,
889            &image_resolver,
890        );
891        if self.calculate_overflow() {
892            reflow_phases_run.insert(ReflowPhasesRun::CalculatedOverflow);
893        }
894        if self.build_stacking_context_tree_for_reflow(&reflow_request) {
895            reflow_phases_run.insert(ReflowPhasesRun::BuiltStackingContextTree);
896        }
897        if self.build_display_list(&reflow_request, &image_resolver, &mut reflow_statistics) {
898            reflow_phases_run.insert(ReflowPhasesRun::BuiltDisplayList);
899        }
900        if self.handle_update_scroll_node_request(&reflow_request) {
901            reflow_phases_run.insert(ReflowPhasesRun::UpdatedScrollNodeOffset);
902        }
903
904        let pending_images = std::mem::take(&mut *image_resolver.pending_images.lock());
905        let pending_rasterization_images =
906            std::mem::take(&mut *image_resolver.pending_rasterization_images.lock());
907        let pending_svg_elements_for_serialization =
908            std::mem::take(&mut *image_resolver.pending_svg_elements_for_serialization.lock());
909
910        Some(ReflowResult {
911            reflow_phases_run,
912            pending_images,
913            pending_rasterization_images,
914            pending_svg_elements_for_serialization,
915            iframe_sizes: Some(iframe_sizes),
916            reflow_statistics,
917        })
918    }
919
920    #[servo_tracing::instrument(skip_all)]
921    fn prepare_stylist_for_reflow<'dom>(
922        &mut self,
923        reflow_request: &ReflowRequest,
924        document: ServoLayoutDocument<'dom>,
925        guards: &StylesheetGuards,
926        ua_stylesheets: &UserAgentStylesheets,
927    ) -> StylesheetInvalidationSet {
928        if !self.have_added_user_agent_stylesheets {
929            for stylesheet in &ua_stylesheets.user_agent_stylesheets {
930                self.stylist
931                    .append_stylesheet(stylesheet.clone(), guards.ua_or_user);
932                self.load_all_web_fonts_from_stylesheet_with_guard(
933                    stylesheet,
934                    guards.ua_or_user,
935                    &reflow_request.document_context,
936                );
937            }
938
939            for user_stylesheet in self.user_stylesheets.iter() {
940                self.stylist
941                    .append_stylesheet(user_stylesheet.clone(), guards.ua_or_user);
942                self.load_all_web_fonts_from_stylesheet_with_guard(
943                    user_stylesheet,
944                    guards.ua_or_user,
945                    &reflow_request.document_context,
946                );
947            }
948
949            if self.stylist.quirks_mode() == QuirksMode::Quirks {
950                self.stylist.append_stylesheet(
951                    ua_stylesheets.quirks_mode_stylesheet.clone(),
952                    guards.ua_or_user,
953                );
954                self.load_all_web_fonts_from_stylesheet_with_guard(
955                    &ua_stylesheets.quirks_mode_stylesheet,
956                    guards.ua_or_user,
957                    &reflow_request.document_context,
958                );
959            }
960            self.have_added_user_agent_stylesheets = true;
961        }
962
963        if reflow_request.stylesheets_changed() {
964            self.stylist
965                .force_stylesheet_origins_dirty(Origin::Author.into());
966        }
967
968        // Flush shadow roots stylesheets if dirty.
969        document.flush_shadow_roots_stylesheets(&mut self.stylist, guards.author);
970
971        self.stylist.flush(guards)
972    }
973
974    #[servo_tracing::instrument(skip_all)]
975    fn restyle_and_build_trees(
976        &mut self,
977        reflow_request: &mut ReflowRequest,
978        document: ServoLayoutDocument<'_>,
979        root_element: ServoLayoutElement<'_>,
980        image_resolver: &Arc<ImageResolver>,
981    ) -> (ReflowPhasesRun, IFrameSizes) {
982        let mut snapshot_map = SnapshotMap::new();
983        let _snapshot_setter = match reflow_request.restyle.as_mut() {
984            Some(restyle) => SnapshotSetter::new(restyle, &mut snapshot_map),
985            None => return Default::default(),
986        };
987
988        let document_shared_lock = document.style_shared_lock();
989        let author_guard = document_shared_lock.read();
990        let ua_stylesheets = &*UA_STYLESHEETS;
991        let guards = StylesheetGuards {
992            author: &author_guard,
993            ua_or_user: &GLOBAL_STYLE_DATA.shared_lock.read(),
994        };
995
996        let rayon_pool = STYLE_THREAD_POOL.lock();
997        let rayon_pool = rayon_pool.pool();
998        let rayon_pool = rayon_pool.as_ref();
999
1000        let device_has_changed = std::mem::replace(&mut self.device_has_changed, false);
1001        if device_has_changed {
1002            let sheet_origins_affected_by_device_change = self
1003                .stylist
1004                .media_features_change_changed_style(&guards, self.device());
1005            self.stylist
1006                .force_stylesheet_origins_dirty(sheet_origins_affected_by_device_change);
1007
1008            if let Some(mut data) = root_element.mutate_data() {
1009                data.hint.insert(RestyleHint::recascade_subtree());
1010            }
1011        }
1012
1013        self.prepare_stylist_for_reflow(reflow_request, document, &guards, ua_stylesheets)
1014            .process_style(root_element, Some(&snapshot_map));
1015
1016        if self.previously_highlighted_dom_node.get() != reflow_request.highlighted_dom_node {
1017            // Need to manually force layout to build a new display list regardless of whether the box tree
1018            // changed or not.
1019            self.need_new_display_list.set(true);
1020        }
1021
1022        let layout_context = LayoutContext {
1023            style_context: self.build_shared_style_context(
1024                guards,
1025                &snapshot_map,
1026                reflow_request.animation_timeline_value,
1027                &reflow_request.animations,
1028                match reflow_request.stylesheets_changed() {
1029                    true => TraversalFlags::ForCSSRuleChanges,
1030                    false => TraversalFlags::empty(),
1031                },
1032            ),
1033            font_context: self.font_context.clone(),
1034            iframe_sizes: Mutex::default(),
1035            use_rayon: rayon_pool.is_some(),
1036            image_resolver: image_resolver.clone(),
1037            painter_id: self.webview_id.into(),
1038        };
1039
1040        let restyle = reflow_request
1041            .restyle
1042            .as_ref()
1043            .expect("Should not get here if there is not restyle.");
1044
1045        let recalc_style_traversal;
1046        let dirty_root;
1047        {
1048            let _span = profile_traits::trace_span!("Styling").entered();
1049
1050            let original_dirty_root = unsafe {
1051                ServoLayoutNode::new(&restyle.dirty_root.unwrap())
1052                    .as_element()
1053                    .unwrap()
1054            };
1055
1056            recalc_style_traversal = RecalcStyle::new(&layout_context);
1057            let token = {
1058                let shared =
1059                    DomTraversal::<ServoLayoutElement>::shared_context(&recalc_style_traversal);
1060                RecalcStyle::pre_traverse(original_dirty_root, shared)
1061            };
1062
1063            if !token.should_traverse() {
1064                layout_context.style_context.stylist.rule_tree().maybe_gc();
1065                return Default::default();
1066            }
1067
1068            dirty_root = driver::traverse_dom(&recalc_style_traversal, token, rayon_pool).as_node();
1069        }
1070
1071        let root_node = root_element.as_node();
1072        let damage_from_environment = if device_has_changed {
1073            RestyleDamage::RELAYOUT
1074        } else {
1075            Default::default()
1076        };
1077
1078        let mut box_tree = self.box_tree.borrow_mut();
1079        let damage = {
1080            let box_tree = &mut *box_tree;
1081            let mut compute_damage_and_build_box_tree = || {
1082                compute_damage_and_rebuild_box_tree(
1083                    box_tree,
1084                    &layout_context,
1085                    dirty_root,
1086                    root_node,
1087                    damage_from_environment,
1088                )
1089            };
1090
1091            if let Some(pool) = rayon_pool {
1092                pool.install(compute_damage_and_build_box_tree)
1093            } else {
1094                compute_damage_and_build_box_tree()
1095            }
1096        };
1097
1098        if damage.contains(RestyleDamage::RECALCULATE_OVERFLOW) {
1099            self.need_overflow_calculation.set(true);
1100        }
1101        if damage.contains(RestyleDamage::REBUILD_STACKING_CONTEXT) {
1102            self.need_new_stacking_context_tree.set(true);
1103        }
1104        if damage.contains(RestyleDamage::REPAINT) {
1105            self.need_new_display_list.set(true);
1106        }
1107        if !damage.contains(RestyleDamage::RELAYOUT) {
1108            layout_context.style_context.stylist.rule_tree().maybe_gc();
1109            return (ReflowPhasesRun::empty(), IFrameSizes::default());
1110        }
1111
1112        let box_tree = &*box_tree;
1113        let viewport_size = self.stylist.device().au_viewport_size();
1114        let run_layout = || {
1115            box_tree
1116                .as_ref()
1117                .unwrap()
1118                .layout(recalc_style_traversal.context(), viewport_size)
1119        };
1120        let fragment_tree = Rc::new(if let Some(pool) = rayon_pool {
1121            pool.install(run_layout)
1122        } else {
1123            run_layout()
1124        });
1125
1126        *self.fragment_tree.borrow_mut() = Some(fragment_tree);
1127
1128        if self.debug.style_tree {
1129            println!(
1130                "{:?}",
1131                ShowSubtreeDataAndPrimaryValues(root_element.as_node())
1132            );
1133        }
1134        if self.debug.rule_tree {
1135            recalc_style_traversal
1136                .context()
1137                .style_context
1138                .stylist
1139                .rule_tree()
1140                .dump_stdout(&layout_context.style_context.guards);
1141        }
1142
1143        // GC the rule tree if some heuristics are met.
1144        layout_context.style_context.stylist.rule_tree().maybe_gc();
1145
1146        let mut iframe_sizes = layout_context.iframe_sizes.lock();
1147        (
1148            ReflowPhasesRun::RanLayout,
1149            std::mem::take(&mut *iframe_sizes),
1150        )
1151    }
1152
1153    #[servo_tracing::instrument(name = "Overflow Calculation", skip_all)]
1154    fn calculate_overflow(&self) -> bool {
1155        if !self.need_overflow_calculation.get() {
1156            return false;
1157        }
1158
1159        if let Some(fragment_tree) = &*self.fragment_tree.borrow() {
1160            fragment_tree.calculate_scrollable_overflow();
1161            if self.debug.flow_tree {
1162                fragment_tree.print();
1163            }
1164        }
1165
1166        self.need_overflow_calculation.set(false);
1167        assert!(self.need_new_display_list.get());
1168        assert!(self.need_new_stacking_context_tree.get());
1169
1170        true
1171    }
1172
1173    fn build_stacking_context_tree_for_reflow(&self, reflow_request: &ReflowRequest) -> bool {
1174        if !ReflowPhases::necessary(&reflow_request.reflow_goal)
1175            .contains(ReflowPhases::StackingContextTreeConstruction)
1176        {
1177            return false;
1178        }
1179        if !self.need_new_stacking_context_tree.get() {
1180            return false;
1181        }
1182
1183        self.build_stacking_context_tree(reflow_request.viewport_details)
1184    }
1185
1186    #[servo_tracing::instrument(name = "Stacking Context Tree Construction", skip_all)]
1187    fn build_stacking_context_tree(&self, viewport_details: ViewportDetails) -> bool {
1188        let Some(fragment_tree) = &*self.fragment_tree.borrow() else {
1189            return false;
1190        };
1191
1192        let mut stacking_context_tree = self.stacking_context_tree.borrow_mut();
1193        let old_scroll_offsets = stacking_context_tree
1194            .as_ref()
1195            .map(|tree| tree.paint_info.scroll_tree.scroll_offsets());
1196
1197        // Build the StackingContextTree. This turns the `FragmentTree` into a
1198        // tree of fragments in CSS painting order and also creates all
1199        // applicable spatial and clip nodes.
1200        let mut new_stacking_context_tree = StackingContextTree::new(
1201            fragment_tree,
1202            viewport_details,
1203            self.id.into(),
1204            !self.have_ever_generated_display_list.get(),
1205            &self.debug,
1206        );
1207
1208        // When a new StackingContextTree is built, it contains a freshly built
1209        // ScrollTree. We want to preserve any existing scroll offsets in that tree,
1210        // adjusted by any new scroll constraints.
1211        if let Some(old_scroll_offsets) = old_scroll_offsets {
1212            new_stacking_context_tree
1213                .paint_info
1214                .scroll_tree
1215                .set_all_scroll_offsets(&old_scroll_offsets);
1216        }
1217
1218        if self.debug.scroll_tree {
1219            new_stacking_context_tree
1220                .paint_info
1221                .scroll_tree
1222                .debug_print();
1223        }
1224
1225        *stacking_context_tree = Some(new_stacking_context_tree);
1226
1227        // The stacking context tree is up-to-date again.
1228        self.need_new_stacking_context_tree.set(false);
1229        assert!(self.need_new_display_list.get());
1230
1231        true
1232    }
1233
1234    /// Build the display list for the current layout and send it to the renderer. If no display
1235    /// list is built, returns false.
1236    #[servo_tracing::instrument(name = "Display List Construction", skip_all)]
1237    fn build_display_list(
1238        &self,
1239        reflow_request: &ReflowRequest,
1240        image_resolver: &Arc<ImageResolver>,
1241        reflow_statistics: &mut ReflowStatistics,
1242    ) -> bool {
1243        if !ReflowPhases::necessary(&reflow_request.reflow_goal)
1244            .contains(ReflowPhases::DisplayListConstruction)
1245        {
1246            return false;
1247        }
1248        let Some(fragment_tree) = &*self.fragment_tree.borrow() else {
1249            return false;
1250        };
1251        let mut stacking_context_tree = self.stacking_context_tree.borrow_mut();
1252        let Some(stacking_context_tree) = stacking_context_tree.as_mut() else {
1253            return false;
1254        };
1255
1256        // If a non-display-list-generating reflow updated layout in a previous refow, we
1257        // cannot skip display list generation here the next time a display list is
1258        // requested.
1259        if !self.need_new_display_list.get() {
1260            return false;
1261        }
1262
1263        // TODO: Eventually this should be set when `paint_info` is created, but that requires
1264        // ensuring that the Epoch is passed to any method that can creates `StackingContextTree`.
1265        stacking_context_tree.paint_info.epoch = reflow_request.epoch;
1266
1267        let mut paint_timing_handler = self.paint_timing_handler.borrow_mut();
1268        // This ensures that we only create the PaintTimingHandler once per layout thread.
1269        let paint_timing_handler = match paint_timing_handler.as_mut() {
1270            Some(paint_timing_handler) => paint_timing_handler,
1271            None => {
1272                *paint_timing_handler = Some(PaintTimingHandler::new(
1273                    stacking_context_tree
1274                        .paint_info
1275                        .viewport_details
1276                        .layout_size(),
1277                ));
1278                paint_timing_handler.as_mut().unwrap()
1279            },
1280        };
1281
1282        let built_display_list = DisplayListBuilder::build(
1283            stacking_context_tree,
1284            fragment_tree,
1285            image_resolver.clone(),
1286            self.device().device_pixel_ratio(),
1287            reflow_request.highlighted_dom_node,
1288            &self.debug,
1289            paint_timing_handler,
1290            reflow_statistics,
1291        );
1292        self.paint_api.send_display_list(
1293            self.webview_id,
1294            &stacking_context_tree.paint_info,
1295            built_display_list,
1296        );
1297
1298        if paint_timing_handler.did_lcp_candidate_update() {
1299            if let Some(lcp_candidate) = paint_timing_handler.largest_contentful_paint_candidate() {
1300                self.paint_api.send_lcp_candidate(
1301                    lcp_candidate,
1302                    self.webview_id,
1303                    self.id,
1304                    stacking_context_tree.paint_info.epoch,
1305                );
1306                paint_timing_handler.unset_lcp_candidate_updated();
1307            }
1308        }
1309
1310        let (keys, instance_keys) = self
1311            .font_context
1312            .collect_unused_webrender_resources(false /* all */);
1313        self.paint_api
1314            .remove_unused_font_resources(self.webview_id.into(), keys, instance_keys);
1315
1316        self.have_ever_generated_display_list.set(true);
1317        self.need_new_display_list.set(false);
1318        self.previously_highlighted_dom_node
1319            .set(reflow_request.highlighted_dom_node);
1320        true
1321    }
1322
1323    fn set_scroll_offset_from_script(
1324        &self,
1325        external_scroll_id: ExternalScrollId,
1326        offset: LayoutVector2D,
1327    ) -> bool {
1328        let mut stacking_context_tree = self.stacking_context_tree.borrow_mut();
1329        let Some(stacking_context_tree) = stacking_context_tree.as_mut() else {
1330            return false;
1331        };
1332
1333        if let Some(offset) = stacking_context_tree
1334            .paint_info
1335            .scroll_tree
1336            .set_scroll_offset_for_node_with_external_scroll_id(
1337                external_scroll_id,
1338                offset,
1339                ScrollType::Script,
1340            )
1341        {
1342            self.paint_api.scroll_node_by_delta(
1343                self.webview_id,
1344                self.id.into(),
1345                offset,
1346                external_scroll_id,
1347            );
1348            true
1349        } else {
1350            false
1351        }
1352    }
1353
1354    /// Returns profiling information which is passed to the time profiler.
1355    fn profiler_metadata(&self) -> Option<TimerMetadata> {
1356        Some(TimerMetadata {
1357            url: self.url.to_string(),
1358            iframe: if self.is_iframe {
1359                TimerMetadataFrameType::IFrame
1360            } else {
1361                TimerMetadataFrameType::RootWindow
1362            },
1363            incremental: if self.have_ever_generated_display_list.get() {
1364                TimerMetadataReflowType::Incremental
1365            } else {
1366                TimerMetadataReflowType::FirstReflow
1367            },
1368        })
1369    }
1370}
1371
1372fn get_ua_stylesheets() -> Result<UserAgentStylesheets, &'static str> {
1373    fn parse_ua_stylesheet(
1374        shared_lock: &SharedRwLock,
1375        filename: &str,
1376        content: &[u8],
1377    ) -> Result<DocumentStyleSheet, &'static str> {
1378        let url = Url::parse(&format!("chrome://resources/{:?}", filename))
1379            .ok()
1380            .unwrap();
1381        Ok(DocumentStyleSheet(ServoArc::new(Stylesheet::from_bytes(
1382            content,
1383            url.into(),
1384            None,
1385            None,
1386            Origin::UserAgent,
1387            ServoArc::new(shared_lock.wrap(MediaList::empty())),
1388            shared_lock.clone(),
1389            None,
1390            None,
1391            QuirksMode::NoQuirks,
1392        ))))
1393    }
1394
1395    let shared_lock = &GLOBAL_STYLE_DATA.shared_lock;
1396
1397    // FIXME: presentational-hints.css should be at author origin with zero specificity.
1398    //        (Does it make a difference?)
1399    let user_agent_stylesheets = vec![
1400        parse_ua_stylesheet(shared_lock, "user-agent.css", USER_AGENT_CSS)?,
1401        parse_ua_stylesheet(shared_lock, "servo.css", SERVO_CSS)?,
1402        parse_ua_stylesheet(
1403            shared_lock,
1404            "presentational-hints.css",
1405            PRESENTATIONAL_HINTS_CSS,
1406        )?,
1407    ];
1408
1409    let quirks_mode_stylesheet =
1410        parse_ua_stylesheet(shared_lock, "quirks-mode.css", QUIRKS_MODE_CSS)?;
1411
1412    Ok(UserAgentStylesheets {
1413        user_agent_stylesheets,
1414        quirks_mode_stylesheet,
1415    })
1416}
1417
1418/// This structure holds the user-agent stylesheets.
1419pub struct UserAgentStylesheets {
1420    /// The user agent stylesheets.
1421    pub user_agent_stylesheets: Vec<DocumentStyleSheet>,
1422    /// The quirks mode stylesheet.
1423    pub quirks_mode_stylesheet: DocumentStyleSheet,
1424}
1425
1426static UA_STYLESHEETS: LazyLock<UserAgentStylesheets> =
1427    LazyLock::new(|| match get_ua_stylesheets() {
1428        Ok(stylesheets) => stylesheets,
1429        Err(filename) => {
1430            error!("Failed to load UA stylesheet {}!", filename);
1431            process::exit(1);
1432        },
1433    });
1434
1435struct RegisteredPainterImpl {
1436    painter: Box<dyn Painter>,
1437    name: Atom,
1438    // FIXME: Should be a PrecomputedHashMap.
1439    properties: FxHashMap<Atom, PropertyId>,
1440}
1441
1442impl SpeculativePainter for RegisteredPainterImpl {
1443    fn speculatively_draw_a_paint_image(
1444        &self,
1445        properties: Vec<(Atom, String)>,
1446        arguments: Vec<String>,
1447    ) {
1448        self.painter
1449            .speculatively_draw_a_paint_image(properties, arguments);
1450    }
1451}
1452
1453impl RegisteredSpeculativePainter for RegisteredPainterImpl {
1454    fn properties(&self) -> &FxHashMap<Atom, PropertyId> {
1455        &self.properties
1456    }
1457    fn name(&self) -> Atom {
1458        self.name.clone()
1459    }
1460}
1461
1462impl Painter for RegisteredPainterImpl {
1463    fn draw_a_paint_image(
1464        &self,
1465        size: Size2D<f32, CSSPixel>,
1466        device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
1467        properties: Vec<(Atom, String)>,
1468        arguments: Vec<String>,
1469    ) -> Result<DrawAPaintImageResult, PaintWorkletError> {
1470        self.painter
1471            .draw_a_paint_image(size, device_pixel_ratio, properties, arguments)
1472    }
1473}
1474
1475struct RegisteredPaintersImpl(HashMap<Atom, RegisteredPainterImpl>);
1476
1477impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
1478    fn get(&self, name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> {
1479        self.0
1480            .get(name)
1481            .map(|painter| painter as &dyn RegisteredSpeculativePainter)
1482    }
1483}
1484
1485struct LayoutFontMetricsProvider(Arc<FontContext>);
1486
1487impl FontMetricsProvider for LayoutFontMetricsProvider {
1488    fn query_font_metrics(
1489        &self,
1490        _vertical: bool,
1491        font: &Font,
1492        base_size: CSSPixelLength,
1493        _flags: QueryFontMetricsFlags,
1494    ) -> FontMetrics {
1495        let font_context = &self.0;
1496        let font_group = self
1497            .0
1498            .font_group_with_size(ServoArc::new(font.clone()), base_size.into());
1499
1500        let Some(first_font_metrics) = font_group
1501            .first(font_context)
1502            .map(|font| font.metrics.clone())
1503        else {
1504            return Default::default();
1505        };
1506
1507        // Only use the x-height of this font if it is non-zero. Some fonts return
1508        // inaccurate metrics, which shouldn't be used.
1509        let x_height = Some(first_font_metrics.x_height)
1510            .filter(|x_height| !x_height.is_zero())
1511            .map(CSSPixelLength::from);
1512
1513        let zero_advance_measure = first_font_metrics
1514            .zero_horizontal_advance
1515            .or_else(|| {
1516                font_group
1517                    .find_by_codepoint(font_context, '0', None, XLang::get_initial_value())?
1518                    .metrics
1519                    .zero_horizontal_advance
1520            })
1521            .map(CSSPixelLength::from);
1522
1523        let ic_width = first_font_metrics
1524            .ic_horizontal_advance
1525            .or_else(|| {
1526                font_group
1527                    .find_by_codepoint(font_context, '\u{6C34}', None, XLang::get_initial_value())?
1528                    .metrics
1529                    .ic_horizontal_advance
1530            })
1531            .map(CSSPixelLength::from);
1532
1533        FontMetrics {
1534            x_height,
1535            zero_advance_measure,
1536            cap_height: None,
1537            ic_width,
1538            ascent: first_font_metrics.ascent.into(),
1539            script_percent_scale_down: None,
1540            script_script_percent_scale_down: None,
1541        }
1542    }
1543
1544    fn base_size_for_generic(&self, generic: GenericFontFamily) -> Length {
1545        Length::new(match generic {
1546            GenericFontFamily::Monospace => pref!(fonts_default_monospace_size),
1547            _ => pref!(fonts_default_size),
1548        } as f32)
1549        .max(Length::new(0.0))
1550    }
1551}
1552
1553impl Debug for LayoutFontMetricsProvider {
1554    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1555        f.debug_tuple("LayoutFontMetricsProvider").finish()
1556    }
1557}
1558
1559struct SnapshotSetter<'dom> {
1560    elements_with_snapshot: Vec<ServoLayoutElement<'dom>>,
1561}
1562
1563impl SnapshotSetter<'_> {
1564    fn new(restyle: &mut ReflowRequestRestyle, snapshot_map: &mut SnapshotMap) -> Self {
1565        debug!("Draining restyles: {}", restyle.pending_restyles.len());
1566        let restyles = std::mem::take(&mut restyle.pending_restyles);
1567
1568        let elements_with_snapshot: Vec<_> = restyles
1569            .iter()
1570            .filter(|r| r.1.snapshot.is_some())
1571            .map(|r| unsafe { ServoLayoutNode::new(&r.0).as_element().unwrap() })
1572            .collect();
1573
1574        for (element, restyle) in restyles {
1575            let element = unsafe { ServoLayoutNode::new(&element).as_element().unwrap() };
1576
1577            // If we haven't styled this node yet, we don't need to track a
1578            // restyle.
1579            let Some(mut style_data) = element.mutate_data() else {
1580                unsafe { element.unset_snapshot_flags() };
1581                continue;
1582            };
1583
1584            debug!("Noting restyle for {:?}: {:?}", element, style_data);
1585            if let Some(s) = restyle.snapshot {
1586                unsafe { element.set_has_snapshot() };
1587                snapshot_map.insert(element.as_node().opaque(), s);
1588            }
1589
1590            // Stash the data on the element for processing by the style system.
1591            style_data.hint.insert(restyle.hint);
1592            style_data.damage = restyle.damage;
1593        }
1594        Self {
1595            elements_with_snapshot,
1596        }
1597    }
1598}
1599
1600impl Drop for SnapshotSetter<'_> {
1601    fn drop(&mut self) {
1602        for element in &self.elements_with_snapshot {
1603            unsafe { element.unset_snapshot_flags() }
1604        }
1605    }
1606}
1607
1608bitflags! {
1609    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
1610    pub struct ReflowPhases: u8 {
1611        const StackingContextTreeConstruction = 1 << 0;
1612        const DisplayListConstruction = 1 << 1;
1613    }
1614}
1615
1616impl ReflowPhases {
1617    /// Return the necessary phases of layout for the given [`ReflowGoal`]. Note that all
1618    /// [`ReflowGoals`] need the basic restyle + box tree layout + fragment tree layout,
1619    /// so [`ReflowPhases::empty()`] implies that.
1620    fn necessary(reflow_goal: &ReflowGoal) -> Self {
1621        match reflow_goal {
1622            ReflowGoal::LayoutQuery(query) => match query {
1623                QueryMsg::NodesFromPointQuery => {
1624                    Self::StackingContextTreeConstruction | Self::DisplayListConstruction
1625                },
1626                QueryMsg::BoxArea |
1627                QueryMsg::BoxAreas |
1628                QueryMsg::ElementsFromPoint |
1629                QueryMsg::OffsetParentQuery |
1630                QueryMsg::ResolvedStyleQuery |
1631                QueryMsg::ScrollingAreaOrOffsetQuery |
1632                QueryMsg::TextIndexQuery => Self::StackingContextTreeConstruction,
1633                QueryMsg::ClientRectQuery |
1634                QueryMsg::CurrentCSSZoomQuery |
1635                QueryMsg::EffectiveOverflow |
1636                QueryMsg::ElementInnerOuterTextQuery |
1637                QueryMsg::InnerWindowDimensionsQuery |
1638                QueryMsg::PaddingQuery |
1639                QueryMsg::ResolvedFontStyleQuery |
1640                QueryMsg::ScrollParentQuery |
1641                QueryMsg::StyleQuery => Self::empty(),
1642            },
1643            ReflowGoal::UpdateScrollNode(..) | ReflowGoal::UpdateTheRendering => {
1644                Self::StackingContextTreeConstruction | Self::DisplayListConstruction
1645            },
1646        }
1647    }
1648}