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