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