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