Skip to main content

layout_api/
lib.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//! This module contains traits in script used generically in the rest of Servo.
6//! The traits are here instead of in script so that these modules won't have
7//! to depend on script.
8
9#![deny(unsafe_code)]
10
11mod largest_contentful_paint_candidate;
12mod layout_damage;
13mod layout_dom;
14mod layout_element;
15mod layout_node;
16mod pseudo_element_chain;
17
18use std::any::Any;
19use std::rc::Rc;
20use std::sync::Arc;
21use std::sync::atomic::AtomicIsize;
22use std::thread::JoinHandle;
23use std::time::Duration;
24
25use app_units::Au;
26use background_hang_monitor_api::BackgroundHangMonitorRegister;
27use bitflags::bitflags;
28use embedder_traits::{Cursor, ScriptToEmbedderChan, Theme, UntrustedNodeAddress, ViewportDetails};
29use euclid::{Point2D, Rect};
30use fonts::{FontContext, WebFontDocumentContext, WebFontSetDifference};
31pub use largest_contentful_paint_candidate::LCPCandidate;
32pub use layout_damage::{AccessibilityDamage, LayoutDamage};
33pub use layout_dom::{
34    DangerousStyleElementOf, DangerousStyleNodeOf, LayoutDomTypeBundle, LayoutElementOf,
35    LayoutNodeOf,
36};
37pub use layout_element::{DangerousStyleElement, LayoutElement};
38pub use layout_node::{DangerousStyleNode, LayoutNode};
39use libc::c_void;
40use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps, malloc_size_of_is_0};
41use malloc_size_of_derive::MallocSizeOf;
42use net_traits::image_cache::{ImageCache, ImageCacheFactory, PendingImageId};
43use net_traits::request::InternalRequest;
44use paint_api::CrossProcessPaintApi;
45use paint_api::display_list::PaintTimingInfo;
46use parking_lot::RwLock;
47use pixels::{RasterImage, Repeat};
48use profile_traits::mem::Report;
49use profile_traits::time;
50pub use pseudo_element_chain::PseudoElementChain;
51use rustc_hash::{FxHashMap, FxHashSet};
52use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
53use serde::{Deserialize, Serialize};
54use servo_arc::Arc as ServoArc;
55use servo_base::Epoch;
56use servo_base::generic_channel::GenericSender;
57use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
58use servo_base::text::{RangeAny, Utf32CodeUnits, Utf32CodeUnitsOrNodeOffset};
59use servo_url::{ImmutableOrigin, ServoUrl};
60use style::Atom;
61use style::animation::DocumentAnimationSet;
62use style::attr::{AttrValue, parse_integer, parse_unsigned_integer};
63use style::context::QuirksMode;
64use style::data::ElementDataWrapper;
65use style::device::Device;
66use style::dom::OpaqueNode;
67use style::invalidation::element::restyle_hints::RestyleHint;
68use style::properties::style_structs::Font;
69use style::properties::{ComputedValues, PropertyId};
70use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
71use style::str::char_is_whitespace;
72use style::stylesheets::{DocumentStyleSheet, Stylesheet};
73use style::stylist::Stylist;
74#[cfg(debug_assertions)]
75use style::thread_state::{self, ThreadState};
76use style::values::computed::Overflow;
77use style_traits::CSSPixel;
78use uuid::Uuid;
79use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
80use webrender_api::{ExternalScrollId, ImageKey};
81
82pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait + Send + Sync + 'static {
83    fn as_any(&self) -> &dyn Any;
84
85    /// Returns whether `new_range` was successfully set on an existing text run
86    fn set_text_run_selection(&self, new_range: Option<RangeAny<Utf32CodeUnits>>) -> bool;
87
88    /// Set whether or not this node is selected when it is an element. Returns `true`
89    /// if anything changed that requires a new display list.
90    fn set_element_selection(&self, selected: bool) -> bool;
91}
92
93pub trait LayoutDataTrait: GenericLayoutDataTrait + Default {}
94pub type GenericLayoutData = dyn GenericLayoutDataTrait;
95
96#[derive(Default, MallocSizeOf)]
97pub struct StyleData {
98    /// Data that the style system associates with a node. When the
99    /// style system is being used standalone, this is all that hangs
100    /// off the node. This must be first to permit the various
101    /// transmutations between ElementData and PersistentLayoutData.
102    pub element_data: ElementDataWrapper,
103
104    /// Information needed during parallel traversals.
105    pub parallel: DomParallelInfo,
106}
107
108/// Information that we need stored in each DOM node.
109#[derive(Default, MallocSizeOf)]
110pub struct DomParallelInfo {
111    /// The number of children remaining to process during bottom-up traversal.
112    pub children_to_process: AtomicIsize,
113}
114
115#[derive(Clone, Copy, Debug, Eq, PartialEq)]
116pub enum LayoutNodeType {
117    Element(LayoutElementType),
118    Text,
119}
120
121#[derive(Clone, Copy, Debug, Eq, PartialEq)]
122pub enum LayoutElementType {
123    Element,
124    HTMLBodyElement,
125    HTMLButtonElement,
126    HTMLBRElement,
127    HTMLCanvasElement,
128    HTMLHtmlElement,
129    HTMLIFrameElement,
130    HTMLImageElement,
131    HTMLInputElement,
132    HTMLMediaElement,
133    HTMLObjectElement,
134    HTMLOptGroupElement,
135    HTMLOptionElement,
136    HTMLParagraphElement,
137    HTMLPreElement,
138    HTMLSelectElement,
139    HTMLTableCellElement,
140    HTMLTableColElement,
141    HTMLTableElement,
142    HTMLTableRowElement,
143    HTMLTableSectionElement,
144    HTMLTextAreaElement,
145    SVGImageElement,
146    SVGSVGElement,
147}
148
149pub struct HTMLCanvasData {
150    pub image_key: Option<ImageKey>,
151    pub width: u32,
152    pub height: u32,
153}
154
155pub struct SVGElementData<'dom> {
156    /// The SVG's XML source represented as a base64 encoded `data:` url.
157    pub source: Option<Result<ServoUrl, ()>>,
158    pub width: Option<&'dom AttrValue>,
159    pub height: Option<&'dom AttrValue>,
160    pub svg_id: Uuid,
161    pub view_box: Option<&'dom AttrValue>,
162}
163
164impl SVGElementData<'_> {
165    pub fn ratio_from_view_box(&self) -> Option<f32> {
166        let mut iter = self.view_box?.chars();
167        let _min_x = parse_integer(&mut iter).ok()?;
168        let _min_y = parse_integer(&mut iter).ok()?;
169
170        let width = parse_unsigned_integer(&mut iter).ok()?;
171        if width == 0 {
172            return None;
173        }
174
175        let height = parse_unsigned_integer(&mut iter).ok()?;
176        if height == 0 {
177            return None;
178        }
179
180        let mut iter = iter.skip_while(|c| char_is_whitespace(*c));
181        iter.next().is_none().then(|| width as f32 / height as f32)
182    }
183}
184
185/// The address of a node known to be valid. These are sent from script to layout.
186#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
187pub struct TrustedNodeAddress(pub *const c_void);
188
189#[expect(unsafe_code)]
190unsafe impl Send for TrustedNodeAddress {}
191
192/// Whether the pending image needs to be fetched or is waiting on an existing fetch.
193#[derive(Debug)]
194pub enum PendingImageState {
195    Unrequested(ServoUrl),
196    PendingResponse,
197}
198
199/// The destination in layout where an image is needed.
200#[derive(Debug, MallocSizeOf)]
201pub enum LayoutImageDestination {
202    BoxTreeConstruction,
203    DisplayListBuilding,
204}
205
206/// The data associated with an image that is not yet present in the image cache.
207/// Used by the script thread to hold on to DOM elements that need to be repainted
208/// when an image fetch is complete.
209#[derive(Debug)]
210pub struct PendingImage {
211    pub state: PendingImageState,
212    pub node: UntrustedNodeAddress,
213    pub id: PendingImageId,
214    pub origin: ImmutableOrigin,
215    pub destination: LayoutImageDestination,
216    pub is_internal_request: InternalRequest,
217}
218
219/// A data structure to track vector image that are fully loaded (i.e has a parsed SVG
220/// tree) but not yet rasterized to the size needed by layout. The rasterization is
221/// happening in the image cache.
222#[derive(Debug)]
223pub struct PendingRasterizationImage {
224    pub node: UntrustedNodeAddress,
225    pub id: PendingImageId,
226    pub size: DeviceIntSize,
227}
228
229#[derive(Clone, Copy, Debug, MallocSizeOf)]
230pub struct MediaFrame {
231    pub image_key: webrender_api::ImageKey,
232    pub width: i32,
233    pub height: i32,
234}
235
236pub struct MediaMetadata {
237    pub width: u32,
238    pub height: u32,
239}
240
241pub struct HTMLMediaData {
242    pub current_frame: Option<MediaFrame>,
243    pub metadata: Option<MediaMetadata>,
244    pub poster_url: Option<ServoUrl>,
245}
246
247pub struct LayoutConfig {
248    pub id: PipelineId,
249    pub webview_id: WebViewId,
250    pub url: ServoUrl,
251    pub is_iframe: bool,
252    pub script_chan: GenericSender<ScriptThreadMessage>,
253    pub image_cache: Arc<dyn ImageCache>,
254    pub font_context: Arc<FontContext>,
255    pub time_profiler_chan: time::ProfilerChan,
256    pub paint_api: CrossProcessPaintApi,
257    pub viewport_details: ViewportDetails,
258    pub user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
259    pub theme: Theme,
260    pub embedder_chan: ScriptToEmbedderChan,
261}
262
263bitflags! {
264    #[derive(Copy, Clone)]
265    pub struct HitTestFlags: u8 {
266        /// Whether to populate [`HitTestResult::dom_position_for_selection`]
267        const IncludeDomPosition = 0b0000_0001;
268    }
269}
270
271pub trait LayoutFactory: Send + Sync {
272    fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
273}
274
275pub trait Layout {
276    /// Get a reference to this Layout's Stylo `Device` used to handle media queries and
277    /// resolve font metrics.
278    fn device(&self) -> &Device;
279
280    /// Set the theme on this [`Layout`]'s [`Device`]. The caller should also trigger a
281    /// new layout when this happens, though it can happen later. Returns `true` if the
282    /// [`Theme`] actually changed or `false` otherwise.
283    fn set_theme(&mut self, theme: Theme) -> bool;
284
285    /// Set the [`ViewportDetails`] on this [`Layout`]'s [`Device`]. The caller should also
286    /// trigger a new layout when this happens, though it can happen later. Returns `true`
287    /// if the [`ViewportDetails`] actually changed or `false` otherwise.
288    fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
289
290    /// Add a stylesheet to this Layout's `Stylist`.
291    ///
292    /// The second stylesheet is the insertion point (if it exists, the sheet needs to be
293    /// inserted before it).
294    fn add_stylesheet(
295        &mut self,
296        stylesheet: ServoArc<Stylesheet>,
297        before_stylesheet: Option<ServoArc<Stylesheet>>,
298    );
299
300    /// Inform the layout that its ScriptThread is about to exit.
301    fn exit_now(&mut self);
302
303    /// Requests that layout measure its memory usage. The resulting reports are sent back
304    /// via the supplied channel.
305    fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
306
307    /// Sets quirks mode for the document, causing the quirks mode stylesheet to be used.
308    fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
309
310    /// Removes a stylesheet from the Layout.
311    fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
312
313    /// Removes an image from the Layout image resolver cache.
314    fn remove_cached_image(&mut self, image_url: &ServoUrl);
315
316    /// Requests a reflow.
317    fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
318
319    /// Do not request a reflow, but ensure that any previous reflow completes building a stacking
320    /// context tree so that it is ready to query the final size of any elements in script.
321    fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
322
323    /// Tells layout that script has added some paint worklet modules.
324    fn register_paint_worklet_modules(
325        &mut self,
326        name: Atom,
327        properties: Vec<Atom>,
328        painter: Box<dyn Painter>,
329    );
330
331    /// Set the scroll states of this layout after a `Paint` scroll.
332    fn set_scroll_offsets_from_renderer(
333        &mut self,
334        scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
335    );
336
337    /// Get the scroll offset of the given scroll node with id of [`ExternalScrollId`] or `None` if it does
338    /// not exist in the tree.
339    fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
340
341    /// Returns true if this layout needs to produce a new display list for rendering updates.
342    fn needs_new_display_list(&self) -> bool;
343
344    /// Marks that this layout needs to produce a new display list for rendering updates.
345    fn set_needs_new_display_list(&self);
346
347    /// Returns the [`NodeRenderingType`] for this node and pseudo. This is used to determine
348    /// if a node is being rendered, delegating its rendering, or not being rendered at all.
349    fn node_rendering_type(
350        &self,
351        node: TrustedNodeAddress,
352        pseudo: Option<PseudoElement>,
353    ) -> NodeRenderingType;
354
355    fn query_containing_block(&self, node: TrustedNodeAddress) -> Option<UntrustedNodeAddress>;
356    fn query_containing_block_is_descendant(
357        &self,
358        root: TrustedNodeAddress,
359        possible_descendant: TrustedNodeAddress,
360    ) -> bool;
361    fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
362    fn query_box_area(
363        &self,
364        node: TrustedNodeAddress,
365        area: BoxAreaType,
366        exclude_transform_and_inline: bool,
367    ) -> Option<Rect<Au, CSSPixel>>;
368    fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectVec;
369    fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
370    fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
371    fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
372    fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
373    /// Query the scroll container for the given node. If node is `None`, the scroll container for
374    /// the viewport is returned.
375    fn query_scroll_container(
376        &self,
377        node: Option<TrustedNodeAddress>,
378        flags: ScrollContainerQueryFlags,
379    ) -> Option<ScrollContainerResponse>;
380    fn query_resolved_style(
381        &self,
382        node: TrustedNodeAddress,
383        pseudo: Option<PseudoElement>,
384        property_id: PropertyId,
385        animations: DocumentAnimationSet,
386        animation_timeline_value: f64,
387    ) -> String;
388    fn query_resolved_font_style(
389        &self,
390        node: TrustedNodeAddress,
391        value: &str,
392        animations: DocumentAnimationSet,
393        animation_timeline_value: f64,
394    ) -> Option<ServoArc<Font>>;
395    fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
396    /// Find the closest character offset of the point within descendants of the given
397    /// node, if it has text content. This works even if the point is outside of all of
398    /// the layout boxes of the node.
399    fn query_text_index(
400        &self,
401        node: TrustedNodeAddress,
402        point_in_viewport: Point2D<Au, CSSPixel>,
403    ) -> Option<(OpaqueNode, Utf32CodeUnits)>;
404    fn hit_test(&self, flags: HitTestFlags, point: LayoutPoint) -> HitTestResult;
405    fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
406    fn stylist_mut(&mut self) -> &mut Stylist;
407
408    /// Set whether the accessibility tree should be constructed for this Layout.
409    /// This should be called by the embedder when accessibility is requested by the user.
410    fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
411
412    /// Returns whether accessibility is active for this Layout.
413    fn accessibility_active(&self) -> bool;
414
415    /// Whether the accessibility tree must be updated. This is set to true when
416    /// - accessibility is activated; or
417    /// - a page is loaded after accesibility is activated.
418    ///
419    /// Checked in can_skip_reflow_request_entirely(), as a dirty accessibility tree
420    /// should force a reflow, and handle_accessibility_tree_update() to determine whether to
421    /// update the accessibility tree during reflow.
422    fn force_accessibility_update(&self) -> bool;
423
424    /// See [Self::force_accessibility_update()].
425    fn set_force_accessibility_update(&self);
426
427    fn font_context(&self) -> &Arc<FontContext>;
428}
429
430/// This trait is part of `layout_api` because it depends on both `script_traits`
431/// and also `LayoutFactory` from this crate. If it was in `script_traits` there would be a
432/// circular dependency.
433pub trait ScriptThreadFactory {
434    /// Create a `ScriptThread`.
435    fn create(
436        state: InitialScriptState,
437        layout_factory: Arc<dyn LayoutFactory>,
438        image_cache_factory: Arc<dyn ImageCacheFactory>,
439        background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
440    ) -> JoinHandle<()>;
441}
442
443/// Type of the area of CSS box for query.
444/// See <https://www.w3.org/TR/css-box-3/#box-model>.
445#[derive(Copy, Clone)]
446pub enum BoxAreaType {
447    Content,
448    Padding,
449    Border,
450}
451
452pub type CSSPixelRectVec = Vec<Rect<Au, CSSPixel>>;
453
454/// Whether or not this node is being rendered or delegates rendering according
455/// to the HTML standard.
456#[derive(Copy, Clone)]
457pub enum NodeRenderingType {
458    /// <https://html.spec.whatwg.org/multipage/#being-rendered>
459    Rendered,
460    /// <https://html.spec.whatwg.org/multipage/#delegating-its-rendering-to-its-children>
461    DelegatesRendering,
462    /// If neither of the other two cases are true, this is. The node is effectively not
463    /// taking part in the final layout of the page.
464    NotRendered,
465}
466
467#[derive(Default)]
468pub struct PhysicalSides {
469    pub left: Au,
470    pub top: Au,
471    pub right: Au,
472    pub bottom: Au,
473}
474
475#[derive(Clone, Default)]
476pub struct OffsetParentResponse {
477    pub node_address: Option<UntrustedNodeAddress>,
478    pub rect: Rect<Au, CSSPixel>,
479}
480
481bitflags! {
482    #[derive(PartialEq)]
483    pub struct ScrollContainerQueryFlags: u8 {
484        /// Whether or not this query is for the purposes of a `scrollParent` layout query.
485        const ForScrollParent = 1 << 0;
486        /// Whether or not to consider the original element's scroll box for the return value.
487        const Inclusive = 1 << 1;
488    }
489}
490
491#[derive(Clone, Copy, Debug, MallocSizeOf)]
492pub struct AxesOverflow {
493    pub x: Overflow,
494    pub y: Overflow,
495}
496
497impl Default for AxesOverflow {
498    fn default() -> Self {
499        Self {
500            x: Overflow::Visible,
501            y: Overflow::Visible,
502        }
503    }
504}
505
506impl From<&ComputedValues> for AxesOverflow {
507    fn from(style: &ComputedValues) -> Self {
508        Self {
509            x: style.clone_overflow_x(),
510            y: style.clone_overflow_y(),
511        }
512    }
513}
514
515impl AxesOverflow {
516    pub fn to_scrollable(&self) -> Self {
517        Self {
518            x: self.x.to_scrollable(),
519            y: self.y.to_scrollable(),
520        }
521    }
522
523    /// Whether or not the `overflow` value establishes a scroll container.
524    pub fn establishes_scroll_container(&self) -> bool {
525        // Checking one axis suffices, because the computed value ensures that
526        // either both axes are scrollable, or none is scrollable.
527        self.x.is_scrollable()
528    }
529}
530
531#[derive(Clone)]
532pub enum ScrollContainerResponse {
533    Viewport(AxesOverflow),
534    Element(UntrustedNodeAddress, AxesOverflow),
535}
536
537#[derive(Debug, PartialEq)]
538pub enum QueryMsg {
539    BoxArea,
540    BoxAreas,
541    ClientRectQuery,
542    CurrentCSSZoomQuery,
543    EffectiveOverflow,
544    ElementInnerOuterTextQuery,
545    ElementsFromPoint,
546    InnerWindowDimensionsQuery,
547    NodesFromPointQuery,
548    OffsetParentQuery,
549    ScrollParentQuery,
550    ResolvedFontStyleQuery,
551    /// A style query, with an optional [`PropertyId`], used to limit the phases
552    /// of layout run before the query.
553    ResolvedStyleQuery(PropertyId),
554    ScrollingAreaOrOffsetQuery,
555    StyleQuery,
556    TextIndexQuery,
557    PaddingQuery,
558    FlushForUpdateTheRenderingQuery,
559}
560
561/// The goal of a reflow request.
562///
563/// Please do not add any other types of reflows. In general, all reflow should
564/// go through the *update the rendering* step of the HTML specification. Exceptions
565/// should have careful review.
566#[derive(Debug, PartialEq)]
567pub enum ReflowGoal {
568    /// A reflow has been requesting by the *update the rendering* step of the HTML
569    /// event loop. This nominally driven by the display's VSync.
570    UpdateTheRendering,
571
572    /// Script has done a layout query and this reflow ensurs that layout is up-to-date
573    /// with the latest changes to the DOM.
574    LayoutQuery(QueryMsg),
575
576    /// Tells layout about a single new scrolling offset from the script. The rest will
577    /// remain untouched. Layout will forward whether the element is scrolled through
578    /// [ReflowResult].
579    UpdateScrollNode(ExternalScrollId, LayoutVector2D),
580}
581
582#[derive(Clone, Debug, MallocSizeOf)]
583pub struct IFrameSize {
584    pub browsing_context_id: BrowsingContextId,
585    pub pipeline_id: PipelineId,
586    pub viewport_details: ViewportDetails,
587}
588
589pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
590
591bitflags! {
592    /// Conditions which cause a [`Document`] to need to be restyled during reflow, which
593    /// might cause the rest of layout to happen as well.
594    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
595    pub struct RestyleReason: u16 {
596        const StylesheetsChanged = 1 << 0;
597        const DOMChanged = 1 << 1;
598        const PendingRestyles = 1 << 2;
599        const HighlightedDOMNodeChanged = 1 << 3;
600        const ThemeChanged = 1 << 4;
601        const ViewportChanged = 1 << 5;
602        const PaintWorkletLoaded = 1 << 6;
603    }
604}
605
606malloc_size_of_is_0!(RestyleReason);
607
608impl RestyleReason {
609    pub fn needs_restyle(&self) -> bool {
610        !self.is_empty()
611    }
612}
613
614/// Information derived from a layout pass that needs to be returned to the script thread.
615#[derive(Default)]
616pub struct ReflowResult {
617    /// The phases that were run during this reflow.
618    pub reflow_phases_run: ReflowPhasesRun,
619    pub reflow_statistics: ReflowStatistics,
620    /// The list of images that were encountered that are in progress.
621    pub pending_images: Vec<PendingImage>,
622    /// The list of vector images that were encountered that still need to be rasterized.
623    pub pending_rasterization_images: Vec<PendingRasterizationImage>,
624    /// The list of `SVGSVGElement`s encountered in the DOM that need to be serialized.
625    /// This is needed to support inline SVGs as the serialization needs to happen on
626    /// the script thread.
627    pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
628    /// The list of iframes in this layout and their sizes, used in order
629    /// to communicate them with the Constellation and also the `Window`
630    /// element of their content pages. Returning None if incremental reflow
631    /// finished before reaching this stage of the layout. I.e., no update
632    /// required.
633    pub iframe_sizes: Option<IFrameSizes>,
634    /// Enumerates web fonts that were added or removed as part of restyling.
635    pub changed_web_fonts: WebFontSetDifference,
636    /// The LCP candidate during this layout pass, if any.
637    pub lcp_candidate: Option<LCPCandidate>,
638}
639
640bitflags! {
641    /// The phases of reflow that were run when processing a reflow in layout.
642    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
643    pub struct ReflowPhasesRun: u8 {
644        const RanLayout = 1 << 0;
645        const BuiltStackingContextTree = 1 << 2;
646        const BuiltDisplayList = 1 << 3;
647        const UpdatedScrollNodeOffset = 1 << 4;
648        /// Image data for a WebRender image key has been updated, without necessarily
649        /// updating style or layout. This is used when updating canvas contents and
650        /// progressing to a new animated image frame.
651        const UpdatedImageData = 1 << 5;
652        const UpdatedAccessibilityTree = 1 << 6;
653    }
654}
655
656impl ReflowPhasesRun {
657    pub fn needs_frame(&self) -> bool {
658        self.intersects(
659            Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
660        )
661    }
662}
663
664#[derive(Debug, Default)]
665pub struct ReflowStatistics {
666    /// A count of the number of fragments that have been completely rebuilt.
667    pub rebuilt_fragment_count: u32,
668    /// A count of the number of fragments that are reused, but have had their style change.
669    pub restyle_fragment_count: u32,
670    /// A count of the number of fragments that are reused, but may have had some descendant
671    /// fragment change.
672    pub only_descendants_changed_count: u32,
673    /// A count of the number of accessibility nodes which were checked for changes based on their
674    /// corresponding DOM nodes (whether the check resulted in changes or not).
675    pub nodes_updated_from_dom: u32,
676    /// A count of the number of accessibility nodes which were checked for changes based on data
677    /// already in the accessibility tree (whether the check resulted in changes or not).
678    pub nodes_updated_from_tree: u32,
679    /// A count of the number of accessibility nodes which had their bounds recomputed from layout
680    /// geometry (whether the recomputation resulted in changes or not).
681    pub nodes_updated_bounds: u32,
682    /// A count of the number of accessibility nodes actually serialized to the TreeUpdate.
683    pub nodes_in_tree_update: u32,
684}
685
686/// Information needed for a script-initiated reflow that requires a restyle
687/// and reconstruction of box and fragment trees.
688#[derive(Debug)]
689pub struct ReflowRequestRestyle {
690    /// Whether or not (and for what reasons) restyle needs to happen.
691    pub reason: RestyleReason,
692    /// The dirty root from which to restyle.
693    pub dirty_root: Option<TrustedNodeAddress>,
694    /// Whether the document's stylesheets have changed since the last script reflow.
695    pub stylesheets_changed: bool,
696    /// Restyle snapshot map.
697    pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
698}
699
700/// Information needed for a script-initiated reflow.
701#[derive(Debug)]
702pub struct ReflowRequest {
703    /// The document node.
704    pub document: TrustedNodeAddress,
705    /// The current layout [`Epoch`] managed by the script thread.
706    pub epoch: Epoch,
707    /// If a restyle is necessary, all of the informatio needed to do that restyle.
708    pub restyle: Option<ReflowRequestRestyle>,
709    /// The current [`ViewportDetails`] to use for this reflow.
710    pub viewport_details: ViewportDetails,
711    /// The goal of this reflow.
712    pub reflow_goal: ReflowGoal,
713    /// The current window origin
714    pub origin: ImmutableOrigin,
715    /// The current animation timeline value.
716    pub animation_timeline_value: f64,
717    /// The set of animations for this document.
718    pub animations: DocumentAnimationSet,
719    /// An [`AnimatingImages`] struct used to track images that are animating.
720    pub animating_images: Arc<RwLock<AnimatingImages>>,
721    /// The node highlighted by the devtools, if any
722    pub highlighted_dom_node: Option<OpaqueNode>,
723    /// Whether LCP computation should be halted for this reflow.
724    /// From <https://www.w3.org/TR/largest-contentful-paint/#limitations>:
725    /// > The LargestContentfulPaint ... algorithm halts ... inputs.
726    pub halt_lcp: bool,
727    /// The [`PaintTimingInfo`] for this reflow.
728    /// <https://www.w3.org/TR/paint-timing/#paint-timing-info>
729    pub paint_timing_info: PaintTimingInfo,
730    /// The current font context.
731    pub document_context: WebFontDocumentContext,
732    /// Damage to the accessibility tree from DOM mutations.
733    pub accessibility_damage: Option<Vec<(TrustedNodeAddress, AccessibilityDamage)>>,
734    /// Nodes which were removed from the DOM tree since the last reflow, which were rooted in
735    /// [`AccessibilityData`]. Only set if [`pref::expensive_accessibility_test_assertions_enabled`]
736    /// is set.
737    pub rooted_nodes_for_accessibility_integrity_check: Option<FxHashSet<OpaqueNode>>,
738}
739
740impl ReflowRequest {
741    pub fn stylesheets_changed(&self) -> bool {
742        self.restyle
743            .as_ref()
744            .is_some_and(|restyle| restyle.stylesheets_changed)
745    }
746}
747
748/// A pending restyle.
749#[derive(Debug, Default, MallocSizeOf)]
750pub struct PendingRestyle {
751    /// If this element had a state or attribute change since the last restyle, track
752    /// the original condition of the element.
753    pub snapshot: Option<Snapshot>,
754
755    /// Any explicit restyles hints that have been accumulated for this element.
756    pub hint: RestyleHint,
757
758    /// Any explicit restyles damage that have been accumulated for this element.
759    pub damage: RestyleDamage,
760}
761
762/// The type of fragment that a scroll root is created for.
763///
764/// This can only ever grow to maximum 4 entries. That's because we cram the value of this enum
765/// into the lower 2 bits of the `OpaqueNodeId`, which otherwise contains a 32-bit-aligned
766/// or 64-bit-aligned heap address depending on the machine.
767#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
768pub enum FragmentType {
769    /// A StackingContext for the fragment body itself.
770    FragmentBody,
771    /// A StackingContext created to contain ::before pseudo-element content.
772    BeforePseudoContent,
773    /// A StackingContext created to contain ::after pseudo-element content.
774    AfterPseudoContent,
775}
776
777impl From<Option<PseudoElement>> for FragmentType {
778    fn from(value: Option<PseudoElement>) -> Self {
779        match value {
780            Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
781            Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
782            _ => FragmentType::FragmentBody,
783        }
784    }
785}
786
787pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
788    debug_assert_eq!(id & (fragment_type as usize), 0);
789    (id as u64) | (fragment_type as u64)
790}
791
792pub fn node_id_from_scroll_id(id: usize) -> usize {
793    id & !3
794}
795
796#[derive(Clone, Debug, MallocSizeOf)]
797pub struct ImageAnimationState {
798    #[conditional_malloc_size_of]
799    pub image: Arc<RasterImage>,
800    pub active_frame: usize,
801    frame_start_time: f64,
802
803    /// The number of loops that have fully completed in this [`ImageAnimationState`].
804    /// If this is greater than or equal to the maximum number of loops in the
805    /// [`RasterImage`], then the animation has ended. If it is `None`, then the image
806    /// will loop infinitely.
807    pub completed_loops: Option<u32>,
808}
809
810impl ImageAnimationState {
811    pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
812        let completd_loops = match &image.loop_count {
813            None => unreachable!("Loop count of an animated Image should never be None"),
814            Some(repeat) if Repeat::Infinite == *repeat => None,
815            _ => Some(0),
816        };
817
818        Self {
819            image,
820            active_frame: 0,
821            frame_start_time: last_update_time,
822            completed_loops: completd_loops,
823        }
824    }
825
826    pub fn image_key(&self) -> Option<ImageKey> {
827        self.image.id
828    }
829
830    pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
831        if self.is_finished() {
832            return None;
833        }
834        let frame_delay = self
835            .image
836            .frames
837            .get(self.active_frame)
838            .expect("Image frame should always be valid")
839            .delay
840            .unwrap_or_default();
841
842        let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
843        let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
844        Some(frame_delay - time_since_frame_start.min(frame_delay))
845    }
846
847    /// check whether image active frame need to be updated given current time,
848    /// return true if there are image that need to be updated.
849    /// false otherwise.
850    pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
851        if self.image.frames.len() <= 1 || self.is_finished() {
852            return false;
853        }
854        let time_interval_since_last_update = now - self.frame_start_time;
855        let mut remain_time_interval = time_interval_since_last_update -
856            self.image
857                .frames
858                .get(self.active_frame)
859                .unwrap()
860                .delay()
861                .unwrap()
862                .as_secs_f64();
863        let mut next_active_frame_id = self.active_frame;
864
865        let frame_count = self.image.frames.len();
866        while remain_time_interval > 0.0 {
867            next_active_frame_id = (next_active_frame_id + 1) % frame_count;
868
869            // If the next active frame is 0, this means the animation is about to loop.
870            if next_active_frame_id == 0 {
871                self.advance_completed_loops();
872
873                // If we have just finished the animation, advance to the final frame if
874                // necessary and stop walking through frames.
875                if self.is_finished() {
876                    if self.active_frame == frame_count - 1 {
877                        return false;
878                    }
879                    self.active_frame = frame_count - 1;
880                    self.frame_start_time = now;
881                    return true;
882                }
883            }
884
885            remain_time_interval -= self
886                .image
887                .frames
888                .get(next_active_frame_id)
889                .unwrap()
890                .delay()
891                .unwrap()
892                .as_secs_f64();
893        }
894        if self.active_frame == next_active_frame_id {
895            return false;
896        }
897        self.active_frame = next_active_frame_id;
898        self.frame_start_time = now;
899        true
900    }
901
902    /// Whether or not this animation has finished looping and has reached its final frame.
903    fn is_finished(&self) -> bool {
904        let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
905            return false;
906        };
907        self.completed_loops
908            .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
909    }
910
911    /// If this animation has a finite number of loops, advance the count of completed loops.
912    fn advance_completed_loops(&mut self) {
913        if let Some(completed_loops) = self.completed_loops.as_mut() {
914            *completed_loops += 1;
915        }
916    }
917}
918
919/// The result of a hit test query.
920#[derive(Debug, Default)]
921pub struct HitTestResult {
922    pub items: Vec<HitTestResultItem>,
923    pub dom_position_for_selection: Option<(OpaqueNode, Utf32CodeUnitsOrNodeOffset)>,
924}
925
926/// Describe an item that matched a hit-test query.
927#[derive(Debug)]
928pub struct HitTestResultItem {
929    /// An [`OpaqueNode`] that contains a pointer to the node hit by
930    /// this hit test result.
931    pub node: OpaqueNode,
932    /// The [`Point2D`] of the original query point relative to the
933    /// node fragment rectangle.
934    pub point_in_target: Point2D<f32, CSSPixel>,
935    /// The [`Cursor`] that's defined on the item that is hit by this
936    /// hit test result.
937    pub cursor: Cursor,
938}
939
940#[derive(Debug, Default, MallocSizeOf)]
941pub struct AnimatingImages {
942    /// A map from the [`OpaqueNode`] to the state of an animating image. This is used
943    /// to update frames in script and to track newly animating nodes.
944    pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
945    /// Whether or not this map has changed during a layout. This is used by script to
946    /// trigger future animation updates.
947    pub dirty: bool,
948}
949
950impl AnimatingImages {
951    pub fn maybe_insert_or_update(
952        &mut self,
953        node: OpaqueNode,
954        image: Arc<RasterImage>,
955        current_timeline_value: f64,
956    ) {
957        let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
958            self.dirty = true;
959            ImageAnimationState::new(image.clone(), current_timeline_value)
960        });
961
962        // If the entry exists, but it is for a different image id, replace it as the image
963        // has changed during this layout.
964        if entry.image.id != image.id {
965            self.dirty = true;
966            *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
967        }
968    }
969
970    pub fn remove(&mut self, node: OpaqueNode) {
971        if self.node_to_state_map.remove(&node).is_some() {
972            self.dirty = true;
973        }
974    }
975
976    /// Clear the dirty bit on this [`AnimatingImages`] and return the previous value.
977    pub fn clear_dirty(&mut self) -> bool {
978        std::mem::take(&mut self.dirty)
979    }
980
981    pub fn is_empty(&self) -> bool {
982        self.node_to_state_map.is_empty()
983    }
984}
985
986struct ThreadStateRestorer;
987
988impl ThreadStateRestorer {
989    fn new() -> Self {
990        #[cfg(debug_assertions)]
991        {
992            thread_state::exit(ThreadState::SCRIPT);
993            thread_state::enter(ThreadState::LAYOUT);
994        }
995        Self
996    }
997}
998
999impl Drop for ThreadStateRestorer {
1000    fn drop(&mut self) {
1001        #[cfg(debug_assertions)]
1002        {
1003            thread_state::exit(ThreadState::LAYOUT);
1004            thread_state::enter(ThreadState::SCRIPT);
1005        }
1006    }
1007}
1008
1009/// Set up the thread-local state to reflect that layout code is about to run,
1010/// then call the provided function.
1011/// This must be used when running code that will interact with the DOM tree
1012/// through types like `ServoLayoutNode`, `ServoLayoutElement`, and `LayoutDom`,
1013/// which have rules about how they must be used from layout worker threads.
1014pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
1015    let _guard = ThreadStateRestorer::new();
1016    f()
1017}
1018
1019#[cfg(test)]
1020mod test {
1021    use std::num::NonZeroU32;
1022    use std::sync::Arc;
1023    use std::time::Duration;
1024
1025    use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
1026
1027    use crate::ImageAnimationState;
1028
1029    #[test]
1030    fn test_animated_image_update() {
1031        let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1032            delay: Some(Duration::from_millis(100)),
1033            byte_range: 0..1,
1034            width: 100,
1035            height: 100,
1036        })
1037        .take(10)
1038        .collect();
1039        let image = RasterImage {
1040            metadata: ImageMetadata {
1041                width: 100,
1042                height: 100,
1043            },
1044            format: PixelFormat::BGRA8,
1045            id: None,
1046            bytes: Arc::new(vec![1]),
1047            frames: image_frames,
1048            cors_status: CorsStatus::Unsafe,
1049            loop_count: Some(Repeat::Infinite),
1050            is_opaque: false,
1051        };
1052        let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1053
1054        assert_eq!(image_animation_state.active_frame, 0);
1055        assert_eq!(image_animation_state.frame_start_time, 0.0);
1056        assert_eq!(
1057            image_animation_state.update_frame_for_animation_timeline_value(0.101),
1058            true
1059        );
1060        assert_eq!(image_animation_state.active_frame, 1);
1061        assert_eq!(image_animation_state.frame_start_time, 0.101);
1062        assert_eq!(
1063            image_animation_state.update_frame_for_animation_timeline_value(0.116),
1064            false
1065        );
1066        assert_eq!(image_animation_state.active_frame, 1);
1067        assert_eq!(image_animation_state.frame_start_time, 0.101);
1068    }
1069
1070    #[test]
1071    fn test_finite_image_repeat() {
1072        let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1073            delay: Some(Duration::from_millis(100)),
1074            byte_range: 0..1,
1075            width: 100,
1076            height: 100,
1077        })
1078        .take(2)
1079        .collect();
1080        let image = RasterImage {
1081            metadata: ImageMetadata {
1082                width: 100,
1083                height: 100,
1084            },
1085            format: PixelFormat::BGRA8,
1086            id: None,
1087            bytes: Arc::new(vec![1]),
1088            frames: image_frames,
1089            cors_status: CorsStatus::Unsafe,
1090            loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1091            is_opaque: false,
1092        };
1093        let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1094
1095        assert_eq!(image_animation_state.active_frame, 0);
1096        assert_eq!(image_animation_state.frame_start_time, 0.0);
1097        assert_eq!(
1098            image_animation_state.update_frame_for_animation_timeline_value(0.101),
1099            true
1100        );
1101        assert_eq!(image_animation_state.active_frame, 1);
1102        assert_eq!(image_animation_state.frame_start_time, 0.101);
1103        assert_eq!(
1104            image_animation_state.update_frame_for_animation_timeline_value(0.202),
1105            false
1106        );
1107        assert_eq!(
1108            image_animation_state.update_frame_for_animation_timeline_value(0.303),
1109            false
1110        );
1111
1112        assert_eq!(image_animation_state.active_frame, 1);
1113    }
1114}