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