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