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;
12pub mod wrapper_traits;
13
14use std::any::Any;
15use std::sync::Arc;
16use std::sync::atomic::{AtomicIsize, AtomicU64, Ordering};
17use std::thread::JoinHandle;
18use std::time::Duration;
19
20use app_units::Au;
21use atomic_refcell::AtomicRefCell;
22use background_hang_monitor_api::BackgroundHangMonitorRegister;
23use base::Epoch;
24use base::generic_channel::GenericSender;
25use base::id::{BrowsingContextId, PipelineId, WebViewId};
26use bitflags::bitflags;
27use compositing_traits::CrossProcessCompositorApi;
28use embedder_traits::{Cursor, Theme, UntrustedNodeAddress, ViewportDetails};
29use euclid::Point2D;
30use euclid::default::{Point2D as UntypedPoint2D, Rect};
31use fonts::{FontContext, WebFontDocumentContext};
32pub use layout_damage::LayoutDamage;
33use libc::c_void;
34use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps, malloc_size_of_is_0};
35use malloc_size_of_derive::MallocSizeOf;
36use net_traits::image_cache::{ImageCache, ImageCacheFactory, PendingImageId};
37use parking_lot::RwLock;
38use pixels::RasterImage;
39use profile_traits::mem::Report;
40use profile_traits::time;
41use rustc_hash::FxHashMap;
42use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
43use serde::{Deserialize, Serialize};
44use servo_arc::Arc as ServoArc;
45use servo_url::{ImmutableOrigin, ServoUrl};
46use style::Atom;
47use style::animation::DocumentAnimationSet;
48use style::context::QuirksMode;
49use style::data::ElementData;
50use style::dom::OpaqueNode;
51use style::invalidation::element::restyle_hints::RestyleHint;
52use style::media_queries::Device;
53use style::properties::style_structs::Font;
54use style::properties::{ComputedValues, PropertyId};
55use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
56use style::stylesheets::{Stylesheet, UrlExtraData};
57use style::values::computed::Overflow;
58use style_traits::CSSPixel;
59use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
60use webrender_api::{ExternalScrollId, ImageKey};
61
62pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait {
63    fn as_any(&self) -> &dyn Any;
64}
65
66pub type GenericLayoutData = dyn GenericLayoutDataTrait + Send + Sync;
67
68#[derive(MallocSizeOf)]
69pub struct StyleData {
70    /// Data that the style system associates with a node. When the
71    /// style system is being used standalone, this is all that hangs
72    /// off the node. This must be first to permit the various
73    /// transmutations between ElementData and PersistentLayoutData.
74    pub element_data: AtomicRefCell<ElementData>,
75
76    /// Information needed during parallel traversals.
77    pub parallel: DomParallelInfo,
78}
79
80impl Default for StyleData {
81    fn default() -> Self {
82        Self {
83            element_data: AtomicRefCell::new(ElementData::default()),
84            parallel: DomParallelInfo::default(),
85        }
86    }
87}
88
89/// Information that we need stored in each DOM node.
90#[derive(Default, MallocSizeOf)]
91pub struct DomParallelInfo {
92    /// The number of children remaining to process during bottom-up traversal.
93    pub children_to_process: AtomicIsize,
94}
95
96#[derive(Clone, Copy, Debug, Eq, PartialEq)]
97pub enum LayoutNodeType {
98    Element(LayoutElementType),
99    Text,
100}
101
102#[derive(Clone, Copy, Debug, Eq, PartialEq)]
103pub enum LayoutElementType {
104    Element,
105    HTMLBodyElement,
106    HTMLBRElement,
107    HTMLCanvasElement,
108    HTMLHtmlElement,
109    HTMLIFrameElement,
110    HTMLImageElement,
111    HTMLInputElement,
112    HTMLMediaElement,
113    HTMLObjectElement,
114    HTMLOptGroupElement,
115    HTMLOptionElement,
116    HTMLParagraphElement,
117    HTMLPreElement,
118    HTMLSelectElement,
119    HTMLTableCellElement,
120    HTMLTableColElement,
121    HTMLTableElement,
122    HTMLTableRowElement,
123    HTMLTableSectionElement,
124    HTMLTextAreaElement,
125    SVGImageElement,
126    SVGSVGElement,
127}
128
129pub struct HTMLCanvasData {
130    pub image_key: Option<ImageKey>,
131    pub width: u32,
132    pub height: u32,
133}
134
135pub struct SVGElementData {
136    /// The SVG's XML source represented as a base64 encoded `data:` url.
137    pub source: Option<Result<ServoUrl, ()>>,
138    pub width: Option<i32>,
139    pub height: Option<i32>,
140    pub ratio: Option<f32>,
141}
142
143/// The address of a node known to be valid. These are sent from script to layout.
144#[derive(Clone, Copy, Debug, Eq, PartialEq)]
145pub struct TrustedNodeAddress(pub *const c_void);
146
147#[expect(unsafe_code)]
148unsafe impl Send for TrustedNodeAddress {}
149
150/// Whether the pending image needs to be fetched or is waiting on an existing fetch.
151#[derive(Debug)]
152pub enum PendingImageState {
153    Unrequested(ServoUrl),
154    PendingResponse,
155}
156
157/// The destination in layout where an image is needed.
158#[derive(Debug, MallocSizeOf)]
159pub enum LayoutImageDestination {
160    BoxTreeConstruction,
161    DisplayListBuilding,
162}
163
164/// The data associated with an image that is not yet present in the image cache.
165/// Used by the script thread to hold on to DOM elements that need to be repainted
166/// when an image fetch is complete.
167#[derive(Debug)]
168pub struct PendingImage {
169    pub state: PendingImageState,
170    pub node: UntrustedNodeAddress,
171    pub id: PendingImageId,
172    pub origin: ImmutableOrigin,
173    pub destination: LayoutImageDestination,
174}
175
176/// A data structure to tarck vector image that are fully loaded (i.e has a parsed SVG
177/// tree) but not yet rasterized to the size needed by layout. The rasterization is
178/// happening in the image cache.
179#[derive(Debug)]
180pub struct PendingRasterizationImage {
181    pub node: UntrustedNodeAddress,
182    pub id: PendingImageId,
183    pub size: DeviceIntSize,
184}
185
186#[derive(Clone, Copy, Debug, MallocSizeOf)]
187pub struct MediaFrame {
188    pub image_key: webrender_api::ImageKey,
189    pub width: i32,
190    pub height: i32,
191}
192
193pub struct MediaMetadata {
194    pub width: u32,
195    pub height: u32,
196}
197
198pub struct HTMLMediaData {
199    pub current_frame: Option<MediaFrame>,
200    pub metadata: Option<MediaMetadata>,
201}
202
203pub struct LayoutConfig {
204    pub id: PipelineId,
205    pub webview_id: WebViewId,
206    pub url: ServoUrl,
207    pub is_iframe: bool,
208    pub script_chan: GenericSender<ScriptThreadMessage>,
209    pub image_cache: Arc<dyn ImageCache>,
210    pub font_context: Arc<FontContext>,
211    pub time_profiler_chan: time::ProfilerChan,
212    pub compositor_api: CrossProcessCompositorApi,
213    pub viewport_details: ViewportDetails,
214    pub theme: Theme,
215}
216
217pub struct PropertyRegistration {
218    pub name: String,
219    pub syntax: String,
220    pub initial_value: Option<String>,
221    pub inherits: bool,
222    pub url_data: UrlExtraData,
223}
224
225#[derive(Debug)]
226pub enum RegisterPropertyError {
227    InvalidName,
228    AlreadyRegistered,
229    InvalidSyntax,
230    InvalidInitialValue,
231    InitialValueNotComputationallyIndependent,
232    NoInitialValue,
233}
234
235pub trait LayoutFactory: Send + Sync {
236    fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
237}
238
239pub trait Layout {
240    /// Get a reference to this Layout's Stylo `Device` used to handle media queries and
241    /// resolve font metrics.
242    fn device(&self) -> &Device;
243
244    /// Set the theme on this [`Layout`]'s [`Device`]. The caller should also trigger a
245    /// new layout when this happens, though it can happen later. Returns `true` if the
246    /// [`Theme`] actually changed or `false` otherwise.
247    fn set_theme(&mut self, theme: Theme) -> bool;
248
249    /// Set the [`ViewportDetails`] on this [`Layout`]'s [`Device`]. The caller should also
250    /// trigger a new layout when this happens, though it can happen later. Returns `true`
251    /// if the [`ViewportDetails`] actually changed or `false` otherwise.
252    fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
253
254    /// Load all fonts from the given stylesheet, returning the number of fonts that
255    /// need to be loaded.
256    fn load_web_fonts_from_stylesheet(
257        &self,
258        stylesheet: &ServoArc<Stylesheet>,
259        font_context: &WebFontDocumentContext,
260    );
261
262    /// Add a stylesheet to this Layout. This will add it to the Layout's `Stylist` as well as
263    /// loading all web fonts defined in the stylesheet. The second stylesheet is the insertion
264    /// point (if it exists, the sheet needs to be inserted before it).
265    fn add_stylesheet(
266        &mut self,
267        stylesheet: ServoArc<Stylesheet>,
268        before_stylsheet: Option<ServoArc<Stylesheet>>,
269        font_context: &WebFontDocumentContext,
270    );
271
272    /// Inform the layout that its ScriptThread is about to exit.
273    fn exit_now(&mut self);
274
275    /// Requests that layout measure its memory usage. The resulting reports are sent back
276    /// via the supplied channel.
277    fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
278
279    /// Sets quirks mode for the document, causing the quirks mode stylesheet to be used.
280    fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
281
282    /// Removes a stylesheet from the Layout.
283    fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
284
285    /// Requests a reflow.
286    fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
287
288    /// Do not request a reflow, but ensure that any previous reflow completes building a stacking
289    /// context tree so that it is ready to query the final size of any elements in script.
290    fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
291
292    /// Tells layout that script has added some paint worklet modules.
293    fn register_paint_worklet_modules(
294        &mut self,
295        name: Atom,
296        properties: Vec<Atom>,
297        painter: Box<dyn Painter>,
298    );
299
300    /// Set the scroll states of this layout after a compositor scroll.
301    fn set_scroll_offsets_from_renderer(
302        &mut self,
303        scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
304    );
305
306    /// Get the scroll offset of the given scroll node with id of [`ExternalScrollId`] or `None` if it does
307    /// not exist in the tree.
308    fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
309
310    /// Returns true if this layout needs to produce a new display list for rendering updates.
311    fn needs_new_display_list(&self) -> bool;
312
313    /// Marks that this layout needs to produce a new display list for rendering updates.
314    fn set_needs_new_display_list(&self);
315
316    fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
317    fn query_box_area(
318        &self,
319        node: TrustedNodeAddress,
320        area: BoxAreaType,
321        exclude_transform_and_inline: bool,
322    ) -> Option<Rect<Au>>;
323    fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Vec<Rect<Au>>;
324    fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32>;
325    fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
326    fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
327    fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
328    /// Query the scroll container for the given node. If node is `None`, the scroll container for
329    /// the viewport is returned.
330    fn query_scroll_container(
331        &self,
332        node: Option<TrustedNodeAddress>,
333        flags: ScrollContainerQueryFlags,
334    ) -> Option<ScrollContainerResponse>;
335    fn query_resolved_style(
336        &self,
337        node: TrustedNodeAddress,
338        pseudo: Option<PseudoElement>,
339        property_id: PropertyId,
340        animations: DocumentAnimationSet,
341        animation_timeline_value: f64,
342    ) -> String;
343    fn query_resolved_font_style(
344        &self,
345        node: TrustedNodeAddress,
346        value: &str,
347        animations: DocumentAnimationSet,
348        animation_timeline_value: f64,
349    ) -> Option<ServoArc<Font>>;
350    fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32>;
351    fn query_text_indext(&self, node: OpaqueNode, point: UntypedPoint2D<f32>) -> Option<usize>;
352    fn query_elements_from_point(
353        &self,
354        point: LayoutPoint,
355        flags: ElementsFromPointFlags,
356    ) -> Vec<ElementsFromPointResult>;
357    fn register_custom_property(
358        &mut self,
359        property_registration: PropertyRegistration,
360    ) -> Result<(), RegisterPropertyError>;
361}
362
363/// This trait is part of `layout_api` because it depends on both `script_traits`
364/// and also `LayoutFactory` from this crate. If it was in `script_traits` there would be a
365/// circular dependency.
366pub trait ScriptThreadFactory {
367    /// Create a `ScriptThread`.
368    fn create(
369        state: InitialScriptState,
370        layout_factory: Arc<dyn LayoutFactory>,
371        image_cache_factory: Arc<dyn ImageCacheFactory>,
372        background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
373    ) -> JoinHandle<()>;
374}
375
376/// Type of the area of CSS box for query.
377/// See <https://www.w3.org/TR/css-box-3/#box-model>.
378#[derive(Copy, Clone)]
379pub enum BoxAreaType {
380    Content,
381    Padding,
382    Border,
383}
384
385#[derive(Default)]
386pub struct PhysicalSides {
387    pub left: Au,
388    pub top: Au,
389    pub right: Au,
390    pub bottom: Au,
391}
392
393#[derive(Clone, Default)]
394pub struct OffsetParentResponse {
395    pub node_address: Option<UntrustedNodeAddress>,
396    pub rect: Rect<Au>,
397}
398
399bitflags! {
400    #[derive(PartialEq)]
401    pub struct ScrollContainerQueryFlags: u8 {
402        /// Whether or not this query is for the purposes of a `scrollParent` layout query.
403        const ForScrollParent = 1 << 0;
404        /// Whether or not to consider the original element's scroll box for the return value.
405        const Inclusive = 1 << 1;
406    }
407}
408
409#[derive(Clone, Copy, Debug, MallocSizeOf)]
410pub struct AxesOverflow {
411    pub x: Overflow,
412    pub y: Overflow,
413}
414
415impl Default for AxesOverflow {
416    fn default() -> Self {
417        Self {
418            x: Overflow::Visible,
419            y: Overflow::Visible,
420        }
421    }
422}
423
424impl From<&ComputedValues> for AxesOverflow {
425    fn from(style: &ComputedValues) -> Self {
426        Self {
427            x: style.clone_overflow_x(),
428            y: style.clone_overflow_y(),
429        }
430    }
431}
432
433impl AxesOverflow {
434    pub fn to_scrollable(&self) -> Self {
435        Self {
436            x: self.x.to_scrollable(),
437            y: self.y.to_scrollable(),
438        }
439    }
440}
441
442#[derive(Clone)]
443pub enum ScrollContainerResponse {
444    Viewport(AxesOverflow),
445    Element(UntrustedNodeAddress, AxesOverflow),
446}
447
448#[derive(Debug, PartialEq)]
449pub enum QueryMsg {
450    BoxArea,
451    BoxAreas,
452    ClientRectQuery,
453    CurrentCSSZoomQuery,
454    ElementInnerOuterTextQuery,
455    ElementsFromPoint,
456    InnerWindowDimensionsQuery,
457    NodesFromPointQuery,
458    OffsetParentQuery,
459    ScrollParentQuery,
460    ResolvedFontStyleQuery,
461    ResolvedStyleQuery,
462    ScrollingAreaOrOffsetQuery,
463    StyleQuery,
464    TextIndexQuery,
465    PaddingQuery,
466}
467
468/// The goal of a reflow request.
469///
470/// Please do not add any other types of reflows. In general, all reflow should
471/// go through the *update the rendering* step of the HTML specification. Exceptions
472/// should have careful review.
473#[derive(Debug, PartialEq)]
474pub enum ReflowGoal {
475    /// A reflow has been requesting by the *update the rendering* step of the HTML
476    /// event loop. This nominally driven by the display's VSync.
477    UpdateTheRendering,
478
479    /// Script has done a layout query and this reflow ensurs that layout is up-to-date
480    /// with the latest changes to the DOM.
481    LayoutQuery(QueryMsg),
482
483    /// Tells layout about a single new scrolling offset from the script. The rest will
484    /// remain untouched. Layout will forward whether the element is scrolled through
485    /// [ReflowResult].
486    UpdateScrollNode(ExternalScrollId, LayoutVector2D),
487}
488
489#[derive(Clone, Debug, MallocSizeOf)]
490pub struct IFrameSize {
491    pub browsing_context_id: BrowsingContextId,
492    pub pipeline_id: PipelineId,
493    pub viewport_details: ViewportDetails,
494}
495
496pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
497
498bitflags! {
499    /// Conditions which cause a [`Document`] to need to be restyled during reflow, which
500    /// might cause the rest of layout to happen as well.
501    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
502    pub struct RestyleReason: u16 {
503        const StylesheetsChanged = 1 << 0;
504        const DOMChanged = 1 << 1;
505        const PendingRestyles = 1 << 2;
506        const HighlightedDOMNodeChanged = 1 << 3;
507        const ThemeChanged = 1 << 4;
508        const ViewportChanged = 1 << 5;
509        const PaintWorkletLoaded = 1 << 6;
510    }
511}
512
513malloc_size_of_is_0!(RestyleReason);
514
515impl RestyleReason {
516    pub fn needs_restyle(&self) -> bool {
517        !self.is_empty()
518    }
519}
520
521/// Information derived from a layout pass that needs to be returned to the script thread.
522#[derive(Debug, Default)]
523pub struct ReflowResult {
524    /// The phases that were run during this reflow.
525    pub reflow_phases_run: ReflowPhasesRun,
526    /// The list of images that were encountered that are in progress.
527    pub pending_images: Vec<PendingImage>,
528    /// The list of vector images that were encountered that still need to be rasterized.
529    pub pending_rasterization_images: Vec<PendingRasterizationImage>,
530    /// The list of `SVGSVGElement`s encountered in the DOM that need to be serialized.
531    /// This is needed to support inline SVGs as the serialization needs to happen on
532    /// the script thread.
533    pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
534    /// The list of iframes in this layout and their sizes, used in order
535    /// to communicate them with the Constellation and also the `Window`
536    /// element of their content pages. Returning None if incremental reflow
537    /// finished before reaching this stage of the layout. I.e., no update
538    /// required.
539    pub iframe_sizes: Option<IFrameSizes>,
540}
541
542bitflags! {
543    /// The phases of reflow that were run when processing a reflow in layout.
544    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
545    pub struct ReflowPhasesRun: u8 {
546        const RanLayout = 1 << 0;
547        const CalculatedOverflow = 1 << 1;
548        const BuiltStackingContextTree = 1 << 2;
549        const BuiltDisplayList = 1 << 3;
550        const UpdatedScrollNodeOffset = 1 << 4;
551        /// Image data for a WebRender image key has been updated, without necessarily
552        /// updating style or layout. This is used when updating canvas contents and
553        /// progressing to a new animated image frame.
554        const UpdatedImageData = 1 << 5;
555    }
556}
557
558impl ReflowPhasesRun {
559    pub fn needs_frame(&self) -> bool {
560        self.intersects(
561            Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
562        )
563    }
564}
565
566/// Information needed for a script-initiated reflow that requires a restyle
567/// and reconstruction of box and fragment trees.
568#[derive(Debug)]
569pub struct ReflowRequestRestyle {
570    /// Whether or not (and for what reasons) restyle needs to happen.
571    pub reason: RestyleReason,
572    /// The dirty root from which to restyle.
573    pub dirty_root: Option<TrustedNodeAddress>,
574    /// Whether the document's stylesheets have changed since the last script reflow.
575    pub stylesheets_changed: bool,
576    /// Restyle snapshot map.
577    pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
578}
579
580/// Information needed for a script-initiated reflow.
581#[derive(Debug)]
582pub struct ReflowRequest {
583    /// The document node.
584    pub document: TrustedNodeAddress,
585    /// The current layout [`Epoch`] managed by the script thread.
586    pub epoch: Epoch,
587    /// If a restyle is necessary, all of the informatio needed to do that restyle.
588    pub restyle: Option<ReflowRequestRestyle>,
589    /// The current [`ViewportDetails`] to use for this reflow.
590    pub viewport_details: ViewportDetails,
591    /// The goal of this reflow.
592    pub reflow_goal: ReflowGoal,
593    /// The number of objects in the dom #10110
594    pub dom_count: u32,
595    /// The current window origin
596    pub origin: ImmutableOrigin,
597    /// The current animation timeline value.
598    pub animation_timeline_value: f64,
599    /// The set of animations for this document.
600    pub animations: DocumentAnimationSet,
601    /// An [`AnimatingImages`] struct used to track images that are animating.
602    pub animating_images: Arc<RwLock<AnimatingImages>>,
603    /// The node highlighted by the devtools, if any
604    pub highlighted_dom_node: Option<OpaqueNode>,
605    /// The current font context.
606    pub document_context: WebFontDocumentContext,
607}
608
609impl ReflowRequest {
610    pub fn stylesheets_changed(&self) -> bool {
611        self.restyle
612            .as_ref()
613            .is_some_and(|restyle| restyle.stylesheets_changed)
614    }
615}
616
617/// A pending restyle.
618#[derive(Debug, Default, MallocSizeOf)]
619pub struct PendingRestyle {
620    /// If this element had a state or attribute change since the last restyle, track
621    /// the original condition of the element.
622    pub snapshot: Option<Snapshot>,
623
624    /// Any explicit restyles hints that have been accumulated for this element.
625    pub hint: RestyleHint,
626
627    /// Any explicit restyles damage that have been accumulated for this element.
628    pub damage: RestyleDamage,
629}
630
631/// The type of fragment that a scroll root is created for.
632///
633/// This can only ever grow to maximum 4 entries. That's because we cram the value of this enum
634/// into the lower 2 bits of the `ScrollRootId`, which otherwise contains a 32-bit-aligned
635/// heap address.
636#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
637pub enum FragmentType {
638    /// A StackingContext for the fragment body itself.
639    FragmentBody,
640    /// A StackingContext created to contain ::before pseudo-element content.
641    BeforePseudoContent,
642    /// A StackingContext created to contain ::after pseudo-element content.
643    AfterPseudoContent,
644}
645
646impl From<Option<PseudoElement>> for FragmentType {
647    fn from(value: Option<PseudoElement>) -> Self {
648        match value {
649            Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
650            Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
651            _ => FragmentType::FragmentBody,
652        }
653    }
654}
655
656/// The next ID that will be used for a special scroll root id.
657///
658/// A special scroll root is a scroll root that is created for generated content.
659static NEXT_SPECIAL_SCROLL_ROOT_ID: AtomicU64 = AtomicU64::new(0);
660
661/// If none of the bits outside this mask are set, the scroll root is a special scroll root.
662/// Note that we assume that the top 16 bits of the address space are unused on the platform.
663const SPECIAL_SCROLL_ROOT_ID_MASK: u64 = 0xffff;
664
665/// Returns a new scroll root ID for a scroll root.
666fn next_special_id() -> u64 {
667    // We shift this left by 2 to make room for the fragment type ID.
668    ((NEXT_SPECIAL_SCROLL_ROOT_ID.fetch_add(1, Ordering::SeqCst) + 1) << 2) &
669        SPECIAL_SCROLL_ROOT_ID_MASK
670}
671
672pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
673    debug_assert_eq!(id & (fragment_type as usize), 0);
674    if fragment_type == FragmentType::FragmentBody {
675        id as u64
676    } else {
677        next_special_id() | (fragment_type as u64)
678    }
679}
680
681pub fn node_id_from_scroll_id(id: usize) -> Option<usize> {
682    if (id as u64 & !SPECIAL_SCROLL_ROOT_ID_MASK) != 0 {
683        return Some(id & !3);
684    }
685    None
686}
687
688#[derive(Clone, Debug, MallocSizeOf)]
689pub struct ImageAnimationState {
690    #[ignore_malloc_size_of = "RasterImage"]
691    pub image: Arc<RasterImage>,
692    pub active_frame: usize,
693    frame_start_time: f64,
694}
695
696impl ImageAnimationState {
697    pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
698        Self {
699            image,
700            active_frame: 0,
701            frame_start_time: last_update_time,
702        }
703    }
704
705    pub fn image_key(&self) -> Option<ImageKey> {
706        self.image.id
707    }
708
709    pub fn duration_to_next_frame(&self, now: f64) -> Duration {
710        let frame_delay = self
711            .image
712            .frames
713            .get(self.active_frame)
714            .expect("Image frame should always be valid")
715            .delay
716            .unwrap_or_default();
717
718        let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
719        let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
720        frame_delay - time_since_frame_start.min(frame_delay)
721    }
722
723    /// check whether image active frame need to be updated given current time,
724    /// return true if there are image that need to be updated.
725    /// false otherwise.
726    pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
727        if self.image.frames.len() <= 1 {
728            return false;
729        }
730        let image = &self.image;
731        let time_interval_since_last_update = now - self.frame_start_time;
732        let mut remain_time_interval = time_interval_since_last_update -
733            image
734                .frames
735                .get(self.active_frame)
736                .unwrap()
737                .delay()
738                .unwrap()
739                .as_secs_f64();
740        let mut next_active_frame_id = self.active_frame;
741        while remain_time_interval > 0.0 {
742            next_active_frame_id = (next_active_frame_id + 1) % image.frames.len();
743            remain_time_interval -= image
744                .frames
745                .get(next_active_frame_id)
746                .unwrap()
747                .delay()
748                .unwrap()
749                .as_secs_f64();
750        }
751        if self.active_frame == next_active_frame_id {
752            return false;
753        }
754        self.active_frame = next_active_frame_id;
755        self.frame_start_time = now;
756        true
757    }
758}
759
760/// Describe an item that matched a hit-test query.
761#[derive(Debug)]
762pub struct ElementsFromPointResult {
763    /// An [`OpaqueNode`] that contains a pointer to the node hit by
764    /// this hit test result.
765    pub node: OpaqueNode,
766    /// The [`Point2D`] of the original query point relative to the
767    /// node fragment rectangle.
768    pub point_in_target: Point2D<f32, CSSPixel>,
769    /// The [`Cursor`] that's defined on the item that is hit by this
770    /// hit test result.
771    pub cursor: Cursor,
772}
773
774bitflags! {
775    pub struct ElementsFromPointFlags: u8 {
776        /// Whether or not to find all of the items for a hit test or stop at the
777        /// first hit.
778        const FindAll = 0b00000001;
779    }
780}
781
782#[derive(Debug, Default, MallocSizeOf)]
783pub struct AnimatingImages {
784    /// A map from the [`OpaqueNode`] to the state of an animating image. This is used
785    /// to update frames in script and to track newly animating nodes.
786    pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
787    /// Whether or not this map has changed during a layout. This is used by script to
788    /// trigger future animation updates.
789    pub dirty: bool,
790}
791
792impl AnimatingImages {
793    pub fn maybe_insert_or_update(
794        &mut self,
795        node: OpaqueNode,
796        image: Arc<RasterImage>,
797        current_timeline_value: f64,
798    ) {
799        let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
800            self.dirty = true;
801            ImageAnimationState::new(image.clone(), current_timeline_value)
802        });
803
804        // If the entry exists, but it is for a different image id, replace it as the image
805        // has changed during this layout.
806        if entry.image.id != image.id {
807            self.dirty = true;
808            *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
809        }
810    }
811
812    pub fn remove(&mut self, node: OpaqueNode) {
813        if self.node_to_state_map.remove(&node).is_some() {
814            self.dirty = true;
815        }
816    }
817
818    /// Clear the dirty bit on this [`AnimatingImages`] and return the previous value.
819    pub fn clear_dirty(&mut self) -> bool {
820        std::mem::take(&mut self.dirty)
821    }
822
823    pub fn is_empty(&self) -> bool {
824        self.node_to_state_map.is_empty()
825    }
826}
827
828#[cfg(test)]
829mod test {
830    use std::sync::Arc;
831    use std::time::Duration;
832
833    use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage};
834
835    use crate::ImageAnimationState;
836
837    #[test]
838    fn test() {
839        let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
840            delay: Some(Duration::from_millis(100)),
841            byte_range: 0..1,
842            width: 100,
843            height: 100,
844        })
845        .take(10)
846        .collect();
847        let image = RasterImage {
848            metadata: ImageMetadata {
849                width: 100,
850                height: 100,
851            },
852            format: PixelFormat::BGRA8,
853            id: None,
854            bytes: Arc::new(vec![1]),
855            frames: image_frames,
856            cors_status: CorsStatus::Unsafe,
857            is_opaque: false,
858        };
859        let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
860
861        assert_eq!(image_animation_state.active_frame, 0);
862        assert_eq!(image_animation_state.frame_start_time, 0.0);
863        assert_eq!(
864            image_animation_state.update_frame_for_animation_timeline_value(0.101),
865            true
866        );
867        assert_eq!(image_animation_state.active_frame, 1);
868        assert_eq!(image_animation_state.frame_start_time, 0.101);
869        assert_eq!(
870            image_animation_state.update_frame_for_animation_timeline_value(0.116),
871            false
872        );
873        assert_eq!(image_animation_state.active_frame, 1);
874        assert_eq!(image_animation_state.frame_start_time, 0.101);
875    }
876}