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