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