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