layout/
layout_impl.rs

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