Skip to main content

layout/
layout_impl.rs

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