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