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