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