Skip to main content

layout_api/
lib.rs

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