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