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