layout_api/
lib.rs

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