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