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_box_area(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Option<Rect<Au>>;
302    fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> Vec<Rect<Au>>;
303    fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32>;
304    fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
305    fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
306    /// Query the scroll container for the given node. If node is `None`, the scroll container for
307    /// the viewport is returned.
308    fn query_scroll_container(
309        &self,
310        node: Option<TrustedNodeAddress>,
311        flags: ScrollContainerQueryFlags,
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
369bitflags! {
370    #[derive(PartialEq)]
371    pub struct ScrollContainerQueryFlags: u8 {
372        /// Whether or not this query is for the purposes of a `scrollParent` layout query.
373        const ForScrollParent = 1 << 0;
374        /// Whether or not to consider the original element's scroll box for the return value.
375        const Inclusive = 1 << 1;
376    }
377}
378
379#[derive(Clone, Copy, Debug, MallocSizeOf)]
380pub struct AxesOverflow {
381    pub x: Overflow,
382    pub y: Overflow,
383}
384
385impl Default for AxesOverflow {
386    fn default() -> Self {
387        Self {
388            x: Overflow::Visible,
389            y: Overflow::Visible,
390        }
391    }
392}
393
394impl From<&ComputedValues> for AxesOverflow {
395    fn from(style: &ComputedValues) -> Self {
396        Self {
397            x: style.clone_overflow_x(),
398            y: style.clone_overflow_y(),
399        }
400    }
401}
402
403impl AxesOverflow {
404    pub fn to_scrollable(&self) -> Self {
405        Self {
406            x: self.x.to_scrollable(),
407            y: self.y.to_scrollable(),
408        }
409    }
410}
411
412#[derive(Clone)]
413pub enum ScrollContainerResponse {
414    Viewport(AxesOverflow),
415    Element(UntrustedNodeAddress, AxesOverflow),
416}
417
418#[derive(Debug, PartialEq)]
419pub enum QueryMsg {
420    BoxArea,
421    BoxAreas,
422    ClientRectQuery,
423    ElementInnerOuterTextQuery,
424    ElementsFromPoint,
425    InnerWindowDimensionsQuery,
426    NodesFromPointQuery,
427    OffsetParentQuery,
428    ScrollParentQuery,
429    ResolvedFontStyleQuery,
430    ResolvedStyleQuery,
431    ScrollingAreaOrOffsetQuery,
432    StyleQuery,
433    TextIndexQuery,
434}
435
436/// The goal of a reflow request.
437///
438/// Please do not add any other types of reflows. In general, all reflow should
439/// go through the *update the rendering* step of the HTML specification. Exceptions
440/// should have careful review.
441#[derive(Debug, PartialEq)]
442pub enum ReflowGoal {
443    /// A reflow has been requesting by the *update the rendering* step of the HTML
444    /// event loop. This nominally driven by the display's VSync.
445    UpdateTheRendering,
446
447    /// Script has done a layout query and this reflow ensurs that layout is up-to-date
448    /// with the latest changes to the DOM.
449    LayoutQuery(QueryMsg),
450
451    /// Tells layout about a single new scrolling offset from the script. The rest will
452    /// remain untouched. Layout will forward whether the element is scrolled through
453    /// [ReflowResult].
454    UpdateScrollNode(ExternalScrollId, LayoutVector2D),
455}
456
457#[derive(Clone, Debug, MallocSizeOf)]
458pub struct IFrameSize {
459    pub browsing_context_id: BrowsingContextId,
460    pub pipeline_id: PipelineId,
461    pub viewport_details: ViewportDetails,
462}
463
464pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
465
466bitflags! {
467    /// Conditions which cause a [`Document`] to need to be restyled during reflow, which
468    /// might cause the rest of layout to happen as well.
469    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
470    pub struct RestyleReason: u16 {
471        const StylesheetsChanged = 1 << 0;
472        const DOMChanged = 1 << 1;
473        const PendingRestyles = 1 << 2;
474        const HighlightedDOMNodeChanged = 1 << 3;
475        const ThemeChanged = 1 << 4;
476        const ViewportSizeChanged = 1 << 5;
477        const PaintWorkletLoaded = 1 << 6;
478    }
479}
480
481malloc_size_of_is_0!(RestyleReason);
482
483impl RestyleReason {
484    pub fn needs_restyle(&self) -> bool {
485        !self.is_empty()
486    }
487}
488
489/// Information derived from a layout pass that needs to be returned to the script thread.
490#[derive(Debug, Default)]
491pub struct ReflowResult {
492    /// The phases that were run during this reflow.
493    pub reflow_phases_run: ReflowPhasesRun,
494    /// The list of images that were encountered that are in progress.
495    pub pending_images: Vec<PendingImage>,
496    /// The list of vector images that were encountered that still need to be rasterized.
497    pub pending_rasterization_images: Vec<PendingRasterizationImage>,
498    /// The list of `SVGSVGElement`s encountered in the DOM that need to be serialized.
499    /// This is needed to support inline SVGs as the serialization needs to happen on
500    /// the script thread.
501    pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
502    /// The list of iframes in this layout and their sizes, used in order
503    /// to communicate them with the Constellation and also the `Window`
504    /// element of their content pages. Returning None if incremental reflow
505    /// finished before reaching this stage of the layout. I.e., no update
506    /// required.
507    pub iframe_sizes: Option<IFrameSizes>,
508}
509
510bitflags! {
511    /// The phases of reflow that were run when processing a reflow in layout.
512    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
513    pub struct ReflowPhasesRun: u8 {
514        const RanLayout = 1 << 0;
515        const CalculatedOverflow = 1 << 1;
516        const BuiltStackingContextTree = 1 << 2;
517        const BuiltDisplayList = 1 << 3;
518        const UpdatedScrollNodeOffset = 1 << 4;
519        /// Image data for a WebRender image key has been updated, without necessarily
520        /// updating style or layout. This is used when updating canvas contents and
521        /// progressing to a new animated image frame.
522        const UpdatedImageData = 1 << 5;
523    }
524}
525
526impl ReflowPhasesRun {
527    pub fn needs_frame(&self) -> bool {
528        self.intersects(
529            Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
530        )
531    }
532}
533
534/// Information needed for a script-initiated reflow that requires a restyle
535/// and reconstruction of box and fragment trees.
536#[derive(Debug)]
537pub struct ReflowRequestRestyle {
538    /// Whether or not (and for what reasons) restyle needs to happen.
539    pub reason: RestyleReason,
540    /// The dirty root from which to restyle.
541    pub dirty_root: Option<TrustedNodeAddress>,
542    /// Whether the document's stylesheets have changed since the last script reflow.
543    pub stylesheets_changed: bool,
544    /// Restyle snapshot map.
545    pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
546}
547
548/// Information needed for a script-initiated reflow.
549#[derive(Debug)]
550pub struct ReflowRequest {
551    /// The document node.
552    pub document: TrustedNodeAddress,
553    /// The current layout [`Epoch`] managed by the script thread.
554    pub epoch: Epoch,
555    /// If a restyle is necessary, all of the informatio needed to do that restyle.
556    pub restyle: Option<ReflowRequestRestyle>,
557    /// The current [`ViewportDetails`] to use for this reflow.
558    pub viewport_details: ViewportDetails,
559    /// The goal of this reflow.
560    pub reflow_goal: ReflowGoal,
561    /// The number of objects in the dom #10110
562    pub dom_count: u32,
563    /// The current window origin
564    pub origin: ImmutableOrigin,
565    /// The current animation timeline value.
566    pub animation_timeline_value: f64,
567    /// The set of animations for this document.
568    pub animations: DocumentAnimationSet,
569    /// An [`AnimatingImages`] struct used to track images that are animating.
570    pub animating_images: Arc<RwLock<AnimatingImages>>,
571    /// The theme for the window
572    pub theme: Theme,
573    /// The node highlighted by the devtools, if any
574    pub highlighted_dom_node: Option<OpaqueNode>,
575}
576
577impl ReflowRequest {
578    pub fn stylesheets_changed(&self) -> bool {
579        self.restyle
580            .as_ref()
581            .is_some_and(|restyle| restyle.stylesheets_changed)
582    }
583}
584
585/// A pending restyle.
586#[derive(Debug, Default, MallocSizeOf)]
587pub struct PendingRestyle {
588    /// If this element had a state or attribute change since the last restyle, track
589    /// the original condition of the element.
590    pub snapshot: Option<Snapshot>,
591
592    /// Any explicit restyles hints that have been accumulated for this element.
593    pub hint: RestyleHint,
594
595    /// Any explicit restyles damage that have been accumulated for this element.
596    pub damage: RestyleDamage,
597}
598
599/// The type of fragment that a scroll root is created for.
600///
601/// This can only ever grow to maximum 4 entries. That's because we cram the value of this enum
602/// into the lower 2 bits of the `ScrollRootId`, which otherwise contains a 32-bit-aligned
603/// heap address.
604#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
605pub enum FragmentType {
606    /// A StackingContext for the fragment body itself.
607    FragmentBody,
608    /// A StackingContext created to contain ::before pseudo-element content.
609    BeforePseudoContent,
610    /// A StackingContext created to contain ::after pseudo-element content.
611    AfterPseudoContent,
612}
613
614impl From<Option<PseudoElement>> for FragmentType {
615    fn from(value: Option<PseudoElement>) -> Self {
616        match value {
617            Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
618            Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
619            _ => FragmentType::FragmentBody,
620        }
621    }
622}
623
624/// The next ID that will be used for a special scroll root id.
625///
626/// A special scroll root is a scroll root that is created for generated content.
627static NEXT_SPECIAL_SCROLL_ROOT_ID: AtomicU64 = AtomicU64::new(0);
628
629/// If none of the bits outside this mask are set, the scroll root is a special scroll root.
630/// Note that we assume that the top 16 bits of the address space are unused on the platform.
631const SPECIAL_SCROLL_ROOT_ID_MASK: u64 = 0xffff;
632
633/// Returns a new scroll root ID for a scroll root.
634fn next_special_id() -> u64 {
635    // We shift this left by 2 to make room for the fragment type ID.
636    ((NEXT_SPECIAL_SCROLL_ROOT_ID.fetch_add(1, Ordering::SeqCst) + 1) << 2) &
637        SPECIAL_SCROLL_ROOT_ID_MASK
638}
639
640pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
641    debug_assert_eq!(id & (fragment_type as usize), 0);
642    if fragment_type == FragmentType::FragmentBody {
643        id as u64
644    } else {
645        next_special_id() | (fragment_type as u64)
646    }
647}
648
649pub fn node_id_from_scroll_id(id: usize) -> Option<usize> {
650    if (id as u64 & !SPECIAL_SCROLL_ROOT_ID_MASK) != 0 {
651        return Some(id & !3);
652    }
653    None
654}
655
656#[derive(Clone, Debug, MallocSizeOf)]
657pub struct ImageAnimationState {
658    #[ignore_malloc_size_of = "RasterImage"]
659    pub image: Arc<RasterImage>,
660    pub active_frame: usize,
661    frame_start_time: f64,
662}
663
664impl ImageAnimationState {
665    pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
666        Self {
667            image,
668            active_frame: 0,
669            frame_start_time: last_update_time,
670        }
671    }
672
673    pub fn image_key(&self) -> Option<ImageKey> {
674        self.image.id
675    }
676
677    pub fn duration_to_next_frame(&self, now: f64) -> Duration {
678        let frame_delay = self
679            .image
680            .frames
681            .get(self.active_frame)
682            .expect("Image frame should always be valid")
683            .delay
684            .unwrap_or_default();
685
686        let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
687        let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
688        frame_delay - time_since_frame_start.min(frame_delay)
689    }
690
691    /// check whether image active frame need to be updated given current time,
692    /// return true if there are image that need to be updated.
693    /// false otherwise.
694    pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
695        if self.image.frames.len() <= 1 {
696            return false;
697        }
698        let image = &self.image;
699        let time_interval_since_last_update = now - self.frame_start_time;
700        let mut remain_time_interval = time_interval_since_last_update -
701            image
702                .frames
703                .get(self.active_frame)
704                .unwrap()
705                .delay()
706                .unwrap()
707                .as_secs_f64();
708        let mut next_active_frame_id = self.active_frame;
709        while remain_time_interval > 0.0 {
710            next_active_frame_id = (next_active_frame_id + 1) % image.frames.len();
711            remain_time_interval -= image
712                .frames
713                .get(next_active_frame_id)
714                .unwrap()
715                .delay()
716                .unwrap()
717                .as_secs_f64();
718        }
719        if self.active_frame == next_active_frame_id {
720            return false;
721        }
722        self.active_frame = next_active_frame_id;
723        self.frame_start_time = now;
724        true
725    }
726}
727
728/// Describe an item that matched a hit-test query.
729#[derive(Debug)]
730pub struct ElementsFromPointResult {
731    /// An [`OpaqueNode`] that contains a pointer to the node hit by
732    /// this hit test result.
733    pub node: OpaqueNode,
734    /// The [`Point2D`] of the original query point relative to the
735    /// node fragment rectangle.
736    pub point_in_target: Point2D<f32, CSSPixel>,
737    /// The [`Cursor`] that's defined on the item that is hit by this
738    /// hit test result.
739    pub cursor: Cursor,
740}
741
742bitflags! {
743    pub struct ElementsFromPointFlags: u8 {
744        /// Whether or not to find all of the items for a hit test or stop at the
745        /// first hit.
746        const FindAll = 0b00000001;
747    }
748}
749
750#[derive(Debug, Default, MallocSizeOf)]
751pub struct AnimatingImages {
752    /// A map from the [`OpaqueNode`] to the state of an animating image. This is used
753    /// to update frames in script and to track newly animating nodes.
754    pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
755    /// Whether or not this map has changed during a layout. This is used by script to
756    /// trigger future animation updates.
757    pub dirty: bool,
758}
759
760impl AnimatingImages {
761    pub fn maybe_insert_or_update(
762        &mut self,
763        node: OpaqueNode,
764        image: Arc<RasterImage>,
765        current_timeline_value: f64,
766    ) {
767        let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
768            self.dirty = true;
769            ImageAnimationState::new(image.clone(), current_timeline_value)
770        });
771
772        // If the entry exists, but it is for a different image id, replace it as the image
773        // has changed during this layout.
774        if entry.image.id != image.id {
775            self.dirty = true;
776            *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
777        }
778    }
779
780    pub fn remove(&mut self, node: OpaqueNode) {
781        if self.node_to_state_map.remove(&node).is_some() {
782            self.dirty = true;
783        }
784    }
785
786    /// Clear the dirty bit on this [`AnimatingImages`] and return the previous value.
787    pub fn clear_dirty(&mut self) -> bool {
788        std::mem::take(&mut self.dirty)
789    }
790
791    pub fn is_empty(&self) -> bool {
792        self.node_to_state_map.is_empty()
793    }
794}
795
796#[cfg(test)]
797mod test {
798    use std::sync::Arc;
799    use std::time::Duration;
800
801    use ipc_channel::ipc::IpcSharedMemory;
802    use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage};
803
804    use crate::ImageAnimationState;
805
806    #[test]
807    fn test() {
808        let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
809            delay: Some(Duration::from_millis(100)),
810            byte_range: 0..1,
811            width: 100,
812            height: 100,
813        })
814        .take(10)
815        .collect();
816        let image = RasterImage {
817            metadata: ImageMetadata {
818                width: 100,
819                height: 100,
820            },
821            format: PixelFormat::BGRA8,
822            id: None,
823            bytes: IpcSharedMemory::from_byte(1, 1),
824            frames: image_frames,
825            cors_status: CorsStatus::Unsafe,
826        };
827        let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
828
829        assert_eq!(image_animation_state.active_frame, 0);
830        assert_eq!(image_animation_state.frame_start_time, 0.0);
831        assert_eq!(
832            image_animation_state.update_frame_for_animation_timeline_value(0.101),
833            true
834        );
835        assert_eq!(image_animation_state.active_frame, 1);
836        assert_eq!(image_animation_state.frame_start_time, 0.101);
837        assert_eq!(
838            image_animation_state.update_frame_for_animation_timeline_value(0.116),
839            false
840        );
841        assert_eq!(image_animation_state.active_frame, 1);
842        assert_eq!(image_animation_state.frame_start_time, 0.101);
843    }
844}