Skip to main content

layout_api/
lib.rs

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