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 base::Epoch;
23use base::generic_channel::GenericSender;
24use base::id::{BrowsingContextId, PipelineId, WebViewId};
25use bitflags::bitflags;
26use compositing_traits::CrossProcessCompositorApi;
27use constellation_traits::LoadData;
28use embedder_traits::{Cursor, Theme, UntrustedNodeAddress, ViewportDetails};
29use euclid::Point2D;
30use euclid::default::{Point2D as UntypedPoint2D, Rect};
31use fonts::{FontContext, SystemFontServiceProxy};
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, 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 source: 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#[allow(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    /// Load all fonts from the given stylesheet, returning the number of fonts that
245    /// need to be loaded.
246    fn load_web_fonts_from_stylesheet(&self, stylesheet: &ServoArc<Stylesheet>);
247
248    /// Add a stylesheet to this Layout. This will add it to the Layout's `Stylist` as well as
249    /// loading all web fonts defined in the stylesheet. The second stylesheet is the insertion
250    /// point (if it exists, the sheet needs to be inserted before it).
251    fn add_stylesheet(
252        &mut self,
253        stylesheet: ServoArc<Stylesheet>,
254        before_stylsheet: Option<ServoArc<Stylesheet>>,
255    );
256
257    /// Inform the layout that its ScriptThread is about to exit.
258    fn exit_now(&mut self);
259
260    /// Requests that layout measure its memory usage. The resulting reports are sent back
261    /// via the supplied channel.
262    fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
263
264    /// Sets quirks mode for the document, causing the quirks mode stylesheet to be used.
265    fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
266
267    /// Removes a stylesheet from the Layout.
268    fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
269
270    /// Requests a reflow.
271    fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
272
273    /// Do not request a reflow, but ensure that any previous reflow completes building a stacking
274    /// context tree so that it is ready to query the final size of any elements in script.
275    fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
276
277    /// Tells layout that script has added some paint worklet modules.
278    fn register_paint_worklet_modules(
279        &mut self,
280        name: Atom,
281        properties: Vec<Atom>,
282        painter: Box<dyn Painter>,
283    );
284
285    /// Set the scroll states of this layout after a compositor scroll.
286    fn set_scroll_offsets_from_renderer(
287        &mut self,
288        scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
289    );
290
291    /// Get the scroll offset of the given scroll node with id of [`ExternalScrollId`] or `None` if it does
292    /// not exist in the tree.
293    fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
294
295    /// Returns true if this layout needs to produce a new display list for rendering updates.
296    fn needs_new_display_list(&self) -> bool;
297
298    /// Marks that this layout needs to produce a new display list for rendering updates.
299    fn set_needs_new_display_list(&self);
300
301    fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
302    fn query_box_area(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Option<Rect<Au>>;
303    fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Vec<Rect<Au>>;
304    fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32>;
305    fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
306    fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
307    /// Query the scroll container for the given node. If node is `None`, the scroll container for
308    /// the viewport is returned.
309    fn query_scroll_container(
310        &self,
311        node: Option<TrustedNodeAddress>,
312        flags: ScrollContainerQueryFlags,
313    ) -> Option<ScrollContainerResponse>;
314    fn query_resolved_style(
315        &self,
316        node: TrustedNodeAddress,
317        pseudo: Option<PseudoElement>,
318        property_id: PropertyId,
319        animations: DocumentAnimationSet,
320        animation_timeline_value: f64,
321    ) -> String;
322    fn query_resolved_font_style(
323        &self,
324        node: TrustedNodeAddress,
325        value: &str,
326        animations: DocumentAnimationSet,
327        animation_timeline_value: f64,
328    ) -> Option<ServoArc<Font>>;
329    fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32>;
330    fn query_text_indext(&self, node: OpaqueNode, point: UntypedPoint2D<f32>) -> Option<usize>;
331    fn query_elements_from_point(
332        &self,
333        point: LayoutPoint,
334        flags: ElementsFromPointFlags,
335    ) -> Vec<ElementsFromPointResult>;
336    fn register_custom_property(
337        &mut self,
338        property_registration: PropertyRegistration,
339    ) -> Result<(), RegisterPropertyError>;
340}
341
342/// This trait is part of `layout_api` because it depends on both `script_traits`
343/// and also `LayoutFactory` from this crate. If it was in `script_traits` there would be a
344/// circular dependency.
345pub trait ScriptThreadFactory {
346    /// Create a `ScriptThread`.
347    fn create(
348        state: InitialScriptState,
349        layout_factory: Arc<dyn LayoutFactory>,
350        system_font_service: Arc<SystemFontServiceProxy>,
351        load_data: LoadData,
352    ) -> JoinHandle<()>;
353}
354
355/// Type of the area of CSS box for query.
356/// See <https://www.w3.org/TR/css-box-3/#box-model>.
357#[derive(Copy, Clone)]
358pub enum BoxAreaType {
359    Content,
360    Padding,
361    Border,
362}
363
364#[derive(Default)]
365pub struct PhysicalSides {
366    pub left: Au,
367    pub top: Au,
368    pub right: Au,
369    pub bottom: Au,
370}
371
372#[derive(Clone, Default)]
373pub struct OffsetParentResponse {
374    pub node_address: Option<UntrustedNodeAddress>,
375    pub rect: Rect<Au>,
376}
377
378bitflags! {
379    #[derive(PartialEq)]
380    pub struct ScrollContainerQueryFlags: u8 {
381        /// Whether or not this query is for the purposes of a `scrollParent` layout query.
382        const ForScrollParent = 1 << 0;
383        /// Whether or not to consider the original element's scroll box for the return value.
384        const Inclusive = 1 << 1;
385    }
386}
387
388#[derive(Clone, Copy, Debug, MallocSizeOf)]
389pub struct AxesOverflow {
390    pub x: Overflow,
391    pub y: Overflow,
392}
393
394impl Default for AxesOverflow {
395    fn default() -> Self {
396        Self {
397            x: Overflow::Visible,
398            y: Overflow::Visible,
399        }
400    }
401}
402
403impl From<&ComputedValues> for AxesOverflow {
404    fn from(style: &ComputedValues) -> Self {
405        Self {
406            x: style.clone_overflow_x(),
407            y: style.clone_overflow_y(),
408        }
409    }
410}
411
412impl AxesOverflow {
413    pub fn to_scrollable(&self) -> Self {
414        Self {
415            x: self.x.to_scrollable(),
416            y: self.y.to_scrollable(),
417        }
418    }
419}
420
421#[derive(Clone)]
422pub enum ScrollContainerResponse {
423    Viewport(AxesOverflow),
424    Element(UntrustedNodeAddress, AxesOverflow),
425}
426
427#[derive(Debug, PartialEq)]
428pub enum QueryMsg {
429    BoxArea,
430    BoxAreas,
431    ClientRectQuery,
432    ElementInnerOuterTextQuery,
433    ElementsFromPoint,
434    InnerWindowDimensionsQuery,
435    NodesFromPointQuery,
436    OffsetParentQuery,
437    ScrollParentQuery,
438    ResolvedFontStyleQuery,
439    ResolvedStyleQuery,
440    ScrollingAreaOrOffsetQuery,
441    StyleQuery,
442    TextIndexQuery,
443    PaddingQuery,
444}
445
446/// The goal of a reflow request.
447///
448/// Please do not add any other types of reflows. In general, all reflow should
449/// go through the *update the rendering* step of the HTML specification. Exceptions
450/// should have careful review.
451#[derive(Debug, PartialEq)]
452pub enum ReflowGoal {
453    /// A reflow has been requesting by the *update the rendering* step of the HTML
454    /// event loop. This nominally driven by the display's VSync.
455    UpdateTheRendering,
456
457    /// Script has done a layout query and this reflow ensurs that layout is up-to-date
458    /// with the latest changes to the DOM.
459    LayoutQuery(QueryMsg),
460
461    /// Tells layout about a single new scrolling offset from the script. The rest will
462    /// remain untouched. Layout will forward whether the element is scrolled through
463    /// [ReflowResult].
464    UpdateScrollNode(ExternalScrollId, LayoutVector2D),
465}
466
467#[derive(Clone, Debug, MallocSizeOf)]
468pub struct IFrameSize {
469    pub browsing_context_id: BrowsingContextId,
470    pub pipeline_id: PipelineId,
471    pub viewport_details: ViewportDetails,
472}
473
474pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
475
476bitflags! {
477    /// Conditions which cause a [`Document`] to need to be restyled during reflow, which
478    /// might cause the rest of layout to happen as well.
479    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
480    pub struct RestyleReason: u16 {
481        const StylesheetsChanged = 1 << 0;
482        const DOMChanged = 1 << 1;
483        const PendingRestyles = 1 << 2;
484        const HighlightedDOMNodeChanged = 1 << 3;
485        const ThemeChanged = 1 << 4;
486        const ViewportSizeChanged = 1 << 5;
487        const PaintWorkletLoaded = 1 << 6;
488    }
489}
490
491malloc_size_of_is_0!(RestyleReason);
492
493impl RestyleReason {
494    pub fn needs_restyle(&self) -> bool {
495        !self.is_empty()
496    }
497}
498
499/// Information derived from a layout pass that needs to be returned to the script thread.
500#[derive(Debug, Default)]
501pub struct ReflowResult {
502    /// The phases that were run during this reflow.
503    pub reflow_phases_run: ReflowPhasesRun,
504    /// The list of images that were encountered that are in progress.
505    pub pending_images: Vec<PendingImage>,
506    /// The list of vector images that were encountered that still need to be rasterized.
507    pub pending_rasterization_images: Vec<PendingRasterizationImage>,
508    /// The list of `SVGSVGElement`s encountered in the DOM that need to be serialized.
509    /// This is needed to support inline SVGs as the serialization needs to happen on
510    /// the script thread.
511    pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
512    /// The list of iframes in this layout and their sizes, used in order
513    /// to communicate them with the Constellation and also the `Window`
514    /// element of their content pages. Returning None if incremental reflow
515    /// finished before reaching this stage of the layout. I.e., no update
516    /// required.
517    pub iframe_sizes: Option<IFrameSizes>,
518}
519
520bitflags! {
521    /// The phases of reflow that were run when processing a reflow in layout.
522    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
523    pub struct ReflowPhasesRun: u8 {
524        const RanLayout = 1 << 0;
525        const CalculatedOverflow = 1 << 1;
526        const BuiltStackingContextTree = 1 << 2;
527        const BuiltDisplayList = 1 << 3;
528        const UpdatedScrollNodeOffset = 1 << 4;
529        /// Image data for a WebRender image key has been updated, without necessarily
530        /// updating style or layout. This is used when updating canvas contents and
531        /// progressing to a new animated image frame.
532        const UpdatedImageData = 1 << 5;
533    }
534}
535
536impl ReflowPhasesRun {
537    pub fn needs_frame(&self) -> bool {
538        self.intersects(
539            Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
540        )
541    }
542}
543
544/// Information needed for a script-initiated reflow that requires a restyle
545/// and reconstruction of box and fragment trees.
546#[derive(Debug)]
547pub struct ReflowRequestRestyle {
548    /// Whether or not (and for what reasons) restyle needs to happen.
549    pub reason: RestyleReason,
550    /// The dirty root from which to restyle.
551    pub dirty_root: Option<TrustedNodeAddress>,
552    /// Whether the document's stylesheets have changed since the last script reflow.
553    pub stylesheets_changed: bool,
554    /// Restyle snapshot map.
555    pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
556}
557
558/// Information needed for a script-initiated reflow.
559#[derive(Debug)]
560pub struct ReflowRequest {
561    /// The document node.
562    pub document: TrustedNodeAddress,
563    /// The current layout [`Epoch`] managed by the script thread.
564    pub epoch: Epoch,
565    /// If a restyle is necessary, all of the informatio needed to do that restyle.
566    pub restyle: Option<ReflowRequestRestyle>,
567    /// The current [`ViewportDetails`] to use for this reflow.
568    pub viewport_details: ViewportDetails,
569    /// The goal of this reflow.
570    pub reflow_goal: ReflowGoal,
571    /// The number of objects in the dom #10110
572    pub dom_count: u32,
573    /// The current window origin
574    pub origin: ImmutableOrigin,
575    /// The current animation timeline value.
576    pub animation_timeline_value: f64,
577    /// The set of animations for this document.
578    pub animations: DocumentAnimationSet,
579    /// An [`AnimatingImages`] struct used to track images that are animating.
580    pub animating_images: Arc<RwLock<AnimatingImages>>,
581    /// The theme for the window
582    pub theme: Theme,
583    /// The node highlighted by the devtools, if any
584    pub highlighted_dom_node: Option<OpaqueNode>,
585}
586
587impl ReflowRequest {
588    pub fn stylesheets_changed(&self) -> bool {
589        self.restyle
590            .as_ref()
591            .is_some_and(|restyle| restyle.stylesheets_changed)
592    }
593}
594
595/// A pending restyle.
596#[derive(Debug, Default, MallocSizeOf)]
597pub struct PendingRestyle {
598    /// If this element had a state or attribute change since the last restyle, track
599    /// the original condition of the element.
600    pub snapshot: Option<Snapshot>,
601
602    /// Any explicit restyles hints that have been accumulated for this element.
603    pub hint: RestyleHint,
604
605    /// Any explicit restyles damage that have been accumulated for this element.
606    pub damage: RestyleDamage,
607}
608
609/// The type of fragment that a scroll root is created for.
610///
611/// This can only ever grow to maximum 4 entries. That's because we cram the value of this enum
612/// into the lower 2 bits of the `ScrollRootId`, which otherwise contains a 32-bit-aligned
613/// heap address.
614#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
615pub enum FragmentType {
616    /// A StackingContext for the fragment body itself.
617    FragmentBody,
618    /// A StackingContext created to contain ::before pseudo-element content.
619    BeforePseudoContent,
620    /// A StackingContext created to contain ::after pseudo-element content.
621    AfterPseudoContent,
622}
623
624impl From<Option<PseudoElement>> for FragmentType {
625    fn from(value: Option<PseudoElement>) -> Self {
626        match value {
627            Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
628            Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
629            _ => FragmentType::FragmentBody,
630        }
631    }
632}
633
634/// The next ID that will be used for a special scroll root id.
635///
636/// A special scroll root is a scroll root that is created for generated content.
637static NEXT_SPECIAL_SCROLL_ROOT_ID: AtomicU64 = AtomicU64::new(0);
638
639/// If none of the bits outside this mask are set, the scroll root is a special scroll root.
640/// Note that we assume that the top 16 bits of the address space are unused on the platform.
641const SPECIAL_SCROLL_ROOT_ID_MASK: u64 = 0xffff;
642
643/// Returns a new scroll root ID for a scroll root.
644fn next_special_id() -> u64 {
645    // We shift this left by 2 to make room for the fragment type ID.
646    ((NEXT_SPECIAL_SCROLL_ROOT_ID.fetch_add(1, Ordering::SeqCst) + 1) << 2) &
647        SPECIAL_SCROLL_ROOT_ID_MASK
648}
649
650pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
651    debug_assert_eq!(id & (fragment_type as usize), 0);
652    if fragment_type == FragmentType::FragmentBody {
653        id as u64
654    } else {
655        next_special_id() | (fragment_type as u64)
656    }
657}
658
659pub fn node_id_from_scroll_id(id: usize) -> Option<usize> {
660    if (id as u64 & !SPECIAL_SCROLL_ROOT_ID_MASK) != 0 {
661        return Some(id & !3);
662    }
663    None
664}
665
666#[derive(Clone, Debug, MallocSizeOf)]
667pub struct ImageAnimationState {
668    #[ignore_malloc_size_of = "RasterImage"]
669    pub image: Arc<RasterImage>,
670    pub active_frame: usize,
671    frame_start_time: f64,
672}
673
674impl ImageAnimationState {
675    pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
676        Self {
677            image,
678            active_frame: 0,
679            frame_start_time: last_update_time,
680        }
681    }
682
683    pub fn image_key(&self) -> Option<ImageKey> {
684        self.image.id
685    }
686
687    pub fn duration_to_next_frame(&self, now: f64) -> Duration {
688        let frame_delay = self
689            .image
690            .frames
691            .get(self.active_frame)
692            .expect("Image frame should always be valid")
693            .delay
694            .unwrap_or_default();
695
696        let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
697        let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
698        frame_delay - time_since_frame_start.min(frame_delay)
699    }
700
701    /// check whether image active frame need to be updated given current time,
702    /// return true if there are image that need to be updated.
703    /// false otherwise.
704    pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
705        if self.image.frames.len() <= 1 {
706            return false;
707        }
708        let image = &self.image;
709        let time_interval_since_last_update = now - self.frame_start_time;
710        let mut remain_time_interval = time_interval_since_last_update -
711            image
712                .frames
713                .get(self.active_frame)
714                .unwrap()
715                .delay()
716                .unwrap()
717                .as_secs_f64();
718        let mut next_active_frame_id = self.active_frame;
719        while remain_time_interval > 0.0 {
720            next_active_frame_id = (next_active_frame_id + 1) % image.frames.len();
721            remain_time_interval -= image
722                .frames
723                .get(next_active_frame_id)
724                .unwrap()
725                .delay()
726                .unwrap()
727                .as_secs_f64();
728        }
729        if self.active_frame == next_active_frame_id {
730            return false;
731        }
732        self.active_frame = next_active_frame_id;
733        self.frame_start_time = now;
734        true
735    }
736}
737
738/// Describe an item that matched a hit-test query.
739#[derive(Debug)]
740pub struct ElementsFromPointResult {
741    /// An [`OpaqueNode`] that contains a pointer to the node hit by
742    /// this hit test result.
743    pub node: OpaqueNode,
744    /// The [`Point2D`] of the original query point relative to the
745    /// node fragment rectangle.
746    pub point_in_target: Point2D<f32, CSSPixel>,
747    /// The [`Cursor`] that's defined on the item that is hit by this
748    /// hit test result.
749    pub cursor: Cursor,
750}
751
752bitflags! {
753    pub struct ElementsFromPointFlags: u8 {
754        /// Whether or not to find all of the items for a hit test or stop at the
755        /// first hit.
756        const FindAll = 0b00000001;
757    }
758}
759
760#[derive(Debug, Default, MallocSizeOf)]
761pub struct AnimatingImages {
762    /// A map from the [`OpaqueNode`] to the state of an animating image. This is used
763    /// to update frames in script and to track newly animating nodes.
764    pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
765    /// Whether or not this map has changed during a layout. This is used by script to
766    /// trigger future animation updates.
767    pub dirty: bool,
768}
769
770impl AnimatingImages {
771    pub fn maybe_insert_or_update(
772        &mut self,
773        node: OpaqueNode,
774        image: Arc<RasterImage>,
775        current_timeline_value: f64,
776    ) {
777        let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
778            self.dirty = true;
779            ImageAnimationState::new(image.clone(), current_timeline_value)
780        });
781
782        // If the entry exists, but it is for a different image id, replace it as the image
783        // has changed during this layout.
784        if entry.image.id != image.id {
785            self.dirty = true;
786            *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
787        }
788    }
789
790    pub fn remove(&mut self, node: OpaqueNode) {
791        if self.node_to_state_map.remove(&node).is_some() {
792            self.dirty = true;
793        }
794    }
795
796    /// Clear the dirty bit on this [`AnimatingImages`] and return the previous value.
797    pub fn clear_dirty(&mut self) -> bool {
798        std::mem::take(&mut self.dirty)
799    }
800
801    pub fn is_empty(&self) -> bool {
802        self.node_to_state_map.is_empty()
803    }
804}
805
806#[cfg(test)]
807mod test {
808    use std::sync::Arc;
809    use std::time::Duration;
810
811    use ipc_channel::ipc::IpcSharedMemory;
812    use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage};
813
814    use crate::ImageAnimationState;
815
816    #[test]
817    fn test() {
818        let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
819            delay: Some(Duration::from_millis(100)),
820            byte_range: 0..1,
821            width: 100,
822            height: 100,
823        })
824        .take(10)
825        .collect();
826        let image = RasterImage {
827            metadata: ImageMetadata {
828                width: 100,
829                height: 100,
830            },
831            format: PixelFormat::BGRA8,
832            id: None,
833            bytes: Arc::new(IpcSharedMemory::from_byte(1, 1)),
834            frames: image_frames,
835            cors_status: CorsStatus::Unsafe,
836            is_opaque: false,
837        };
838        let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
839
840        assert_eq!(image_animation_state.active_frame, 0);
841        assert_eq!(image_animation_state.frame_start_time, 0.0);
842        assert_eq!(
843            image_animation_state.update_frame_for_animation_timeline_value(0.101),
844            true
845        );
846        assert_eq!(image_animation_state.active_frame, 1);
847        assert_eq!(image_animation_state.frame_start_time, 0.101);
848        assert_eq!(
849            image_animation_state.update_frame_for_animation_timeline_value(0.116),
850            false
851        );
852        assert_eq!(image_animation_state.active_frame, 1);
853        assert_eq!(image_animation_state.frame_start_time, 0.101);
854    }
855}