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::rc::Rc;
16use std::sync::Arc;
17use std::sync::atomic::{AtomicIsize, AtomicU64, Ordering};
18use std::thread::JoinHandle;
19use std::time::Duration;
20
21use app_units::Au;
22use atomic_refcell::AtomicRefCell;
23use background_hang_monitor_api::BackgroundHangMonitorRegister;
24use base::Epoch;
25use base::generic_channel::GenericSender;
26use base::id::{BrowsingContextId, PipelineId, WebViewId};
27use bitflags::bitflags;
28use embedder_traits::{Cursor, Theme, UntrustedNodeAddress, ViewportDetails};
29use euclid::{Point2D, Rect};
30use fonts::{FontContext, WebFontDocumentContext};
31pub use layout_damage::LayoutDamage;
32use libc::c_void;
33use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps, malloc_size_of_is_0};
34use malloc_size_of_derive::MallocSizeOf;
35use net_traits::image_cache::{ImageCache, ImageCacheFactory, PendingImageId};
36use paint_api::CrossProcessPaintApi;
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::attr::{AttrValue, parse_integer, parse_unsigned_integer};
49use style::context::QuirksMode;
50use style::data::ElementData;
51use style::dom::OpaqueNode;
52use style::invalidation::element::restyle_hints::RestyleHint;
53use style::media_queries::Device;
54use style::properties::style_structs::Font;
55use style::properties::{ComputedValues, PropertyId};
56use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
57use style::str::char_is_whitespace;
58use style::stylesheets::{DocumentStyleSheet, Stylesheet, UrlExtraData};
59use style::values::computed::Overflow;
60use style_traits::CSSPixel;
61use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
62use webrender_api::{ExternalScrollId, ImageKey};
63
64pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait {
65    fn as_any(&self) -> &dyn Any;
66}
67
68pub type GenericLayoutData = dyn GenericLayoutDataTrait + Send + Sync;
69
70#[derive(MallocSizeOf)]
71pub struct StyleData {
72    /// Data that the style system associates with a node. When the
73    /// style system is being used standalone, this is all that hangs
74    /// off the node. This must be first to permit the various
75    /// transmutations between ElementData and PersistentLayoutData.
76    pub element_data: AtomicRefCell<ElementData>,
77
78    /// Information needed during parallel traversals.
79    pub parallel: DomParallelInfo,
80}
81
82impl Default for StyleData {
83    fn default() -> Self {
84        Self {
85            element_data: AtomicRefCell::new(ElementData::default()),
86            parallel: DomParallelInfo::default(),
87        }
88    }
89}
90
91/// Information that we need stored in each DOM node.
92#[derive(Default, MallocSizeOf)]
93pub struct DomParallelInfo {
94    /// The number of children remaining to process during bottom-up traversal.
95    pub children_to_process: AtomicIsize,
96}
97
98#[derive(Clone, Copy, Debug, Eq, PartialEq)]
99pub enum LayoutNodeType {
100    Element(LayoutElementType),
101    Text,
102}
103
104#[derive(Clone, Copy, Debug, Eq, PartialEq)]
105pub enum LayoutElementType {
106    Element,
107    HTMLBodyElement,
108    HTMLBRElement,
109    HTMLCanvasElement,
110    HTMLHtmlElement,
111    HTMLIFrameElement,
112    HTMLImageElement,
113    HTMLInputElement,
114    HTMLMediaElement,
115    HTMLObjectElement,
116    HTMLOptGroupElement,
117    HTMLOptionElement,
118    HTMLParagraphElement,
119    HTMLPreElement,
120    HTMLSelectElement,
121    HTMLTableCellElement,
122    HTMLTableColElement,
123    HTMLTableElement,
124    HTMLTableRowElement,
125    HTMLTableSectionElement,
126    HTMLTextAreaElement,
127    SVGImageElement,
128    SVGSVGElement,
129}
130
131pub struct HTMLCanvasData {
132    pub image_key: Option<ImageKey>,
133    pub width: u32,
134    pub height: u32,
135}
136
137pub struct SVGElementData<'dom> {
138    /// The SVG's XML source represented as a base64 encoded `data:` url.
139    pub source: Option<Result<ServoUrl, ()>>,
140    pub width: Option<&'dom AttrValue>,
141    pub height: Option<&'dom AttrValue>,
142    pub svg_id: String,
143    pub view_box: Option<&'dom AttrValue>,
144}
145
146impl SVGElementData<'_> {
147    pub fn ratio_from_view_box(&self) -> Option<f32> {
148        let mut iter = self.view_box?.chars();
149        let _min_x = parse_integer(&mut iter).ok()?;
150        let _min_y = parse_integer(&mut iter).ok()?;
151
152        let width = parse_unsigned_integer(&mut iter).ok()?;
153        if width == 0 {
154            return None;
155        }
156
157        let height = parse_unsigned_integer(&mut iter).ok()?;
158        if height == 0 {
159            return None;
160        }
161
162        let mut iter = iter.skip_while(|c| char_is_whitespace(*c));
163        iter.next().is_none().then(|| width as f32 / height as f32)
164    }
165}
166
167/// The address of a node known to be valid. These are sent from script to layout.
168#[derive(Clone, Copy, Debug, Eq, PartialEq)]
169pub struct TrustedNodeAddress(pub *const c_void);
170
171#[expect(unsafe_code)]
172unsafe impl Send for TrustedNodeAddress {}
173
174/// Whether the pending image needs to be fetched or is waiting on an existing fetch.
175#[derive(Debug)]
176pub enum PendingImageState {
177    Unrequested(ServoUrl),
178    PendingResponse,
179}
180
181/// The destination in layout where an image is needed.
182#[derive(Debug, MallocSizeOf)]
183pub enum LayoutImageDestination {
184    BoxTreeConstruction,
185    DisplayListBuilding,
186}
187
188/// The data associated with an image that is not yet present in the image cache.
189/// Used by the script thread to hold on to DOM elements that need to be repainted
190/// when an image fetch is complete.
191#[derive(Debug)]
192pub struct PendingImage {
193    pub state: PendingImageState,
194    pub node: UntrustedNodeAddress,
195    pub id: PendingImageId,
196    pub origin: ImmutableOrigin,
197    pub destination: LayoutImageDestination,
198}
199
200/// A data structure to tarck vector image that are fully loaded (i.e has a parsed SVG
201/// tree) but not yet rasterized to the size needed by layout. The rasterization is
202/// happening in the image cache.
203#[derive(Debug)]
204pub struct PendingRasterizationImage {
205    pub node: UntrustedNodeAddress,
206    pub id: PendingImageId,
207    pub size: DeviceIntSize,
208}
209
210#[derive(Clone, Copy, Debug, MallocSizeOf)]
211pub struct MediaFrame {
212    pub image_key: webrender_api::ImageKey,
213    pub width: i32,
214    pub height: i32,
215}
216
217pub struct MediaMetadata {
218    pub width: u32,
219    pub height: u32,
220}
221
222pub struct HTMLMediaData {
223    pub current_frame: Option<MediaFrame>,
224    pub metadata: Option<MediaMetadata>,
225}
226
227pub struct LayoutConfig {
228    pub id: PipelineId,
229    pub webview_id: WebViewId,
230    pub url: ServoUrl,
231    pub is_iframe: bool,
232    pub script_chan: GenericSender<ScriptThreadMessage>,
233    pub image_cache: Arc<dyn ImageCache>,
234    pub font_context: Arc<FontContext>,
235    pub time_profiler_chan: time::ProfilerChan,
236    pub paint_api: CrossProcessPaintApi,
237    pub viewport_details: ViewportDetails,
238    pub user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
239    pub theme: Theme,
240}
241
242pub struct PropertyRegistration {
243    pub name: String,
244    pub syntax: String,
245    pub initial_value: Option<String>,
246    pub inherits: bool,
247    pub url_data: UrlExtraData,
248}
249
250#[derive(Debug)]
251pub enum RegisterPropertyError {
252    InvalidName,
253    AlreadyRegistered,
254    InvalidSyntax,
255    InvalidInitialValue,
256    InitialValueNotComputationallyIndependent,
257    NoInitialValue,
258}
259
260pub trait LayoutFactory: Send + Sync {
261    fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
262}
263
264pub trait Layout {
265    /// Get a reference to this Layout's Stylo `Device` used to handle media queries and
266    /// resolve font metrics.
267    fn device(&self) -> &Device;
268
269    /// Set the theme on this [`Layout`]'s [`Device`]. The caller should also trigger a
270    /// new layout when this happens, though it can happen later. Returns `true` if the
271    /// [`Theme`] actually changed or `false` otherwise.
272    fn set_theme(&mut self, theme: Theme) -> bool;
273
274    /// Set the [`ViewportDetails`] on this [`Layout`]'s [`Device`]. The caller should also
275    /// trigger a new layout when this happens, though it can happen later. Returns `true`
276    /// if the [`ViewportDetails`] actually changed or `false` otherwise.
277    fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
278
279    /// Load all fonts from the given stylesheet, returning the number of fonts that
280    /// need to be loaded.
281    fn load_web_fonts_from_stylesheet(
282        &self,
283        stylesheet: &ServoArc<Stylesheet>,
284        font_context: &WebFontDocumentContext,
285    );
286
287    /// Add a stylesheet to this Layout. This will add it to the Layout's `Stylist` as well as
288    /// loading all web fonts defined in the stylesheet. The second stylesheet is the insertion
289    /// point (if it exists, the sheet needs to be inserted before it).
290    fn add_stylesheet(
291        &mut self,
292        stylesheet: ServoArc<Stylesheet>,
293        before_stylsheet: Option<ServoArc<Stylesheet>>,
294        font_context: &WebFontDocumentContext,
295    );
296
297    /// Inform the layout that its ScriptThread is about to exit.
298    fn exit_now(&mut self);
299
300    /// Requests that layout measure its memory usage. The resulting reports are sent back
301    /// via the supplied channel.
302    fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
303
304    /// Sets quirks mode for the document, causing the quirks mode stylesheet to be used.
305    fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
306
307    /// Removes a stylesheet from the Layout.
308    fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
309
310    /// Removes an image from the Layout image resolver cache.
311    fn remove_cached_image(&mut self, image_url: &ServoUrl);
312
313    /// Requests a reflow.
314    fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
315
316    /// Do not request a reflow, but ensure that any previous reflow completes building a stacking
317    /// context tree so that it is ready to query the final size of any elements in script.
318    fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
319
320    /// Tells layout that script has added some paint worklet modules.
321    fn register_paint_worklet_modules(
322        &mut self,
323        name: Atom,
324        properties: Vec<Atom>,
325        painter: Box<dyn Painter>,
326    );
327
328    /// Set the scroll states of this layout after a `Paint` scroll.
329    fn set_scroll_offsets_from_renderer(
330        &mut self,
331        scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
332    );
333
334    /// Get the scroll offset of the given scroll node with id of [`ExternalScrollId`] or `None` if it does
335    /// not exist in the tree.
336    fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
337
338    /// Returns true if this layout needs to produce a new display list for rendering updates.
339    fn needs_new_display_list(&self) -> bool;
340
341    /// Marks that this layout needs to produce a new display list for rendering updates.
342    fn set_needs_new_display_list(&self);
343
344    fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
345    fn query_box_area(
346        &self,
347        node: TrustedNodeAddress,
348        area: BoxAreaType,
349        exclude_transform_and_inline: bool,
350    ) -> Option<Rect<Au, CSSPixel>>;
351    fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectIterator;
352    fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
353    fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
354    fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
355    fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
356    /// Query the scroll container for the given node. If node is `None`, the scroll container for
357    /// the viewport is returned.
358    fn query_scroll_container(
359        &self,
360        node: Option<TrustedNodeAddress>,
361        flags: ScrollContainerQueryFlags,
362    ) -> Option<ScrollContainerResponse>;
363    fn query_resolved_style(
364        &self,
365        node: TrustedNodeAddress,
366        pseudo: Option<PseudoElement>,
367        property_id: PropertyId,
368        animations: DocumentAnimationSet,
369        animation_timeline_value: f64,
370    ) -> String;
371    fn query_resolved_font_style(
372        &self,
373        node: TrustedNodeAddress,
374        value: &str,
375        animations: DocumentAnimationSet,
376        animation_timeline_value: f64,
377    ) -> Option<ServoArc<Font>>;
378    fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
379    /// Find the character offset of the point in the given node, if it has text content.
380    fn query_text_index(
381        &self,
382        node: TrustedNodeAddress,
383        point: Point2D<Au, CSSPixel>,
384    ) -> Option<usize>;
385    fn query_elements_from_point(
386        &self,
387        point: LayoutPoint,
388        flags: ElementsFromPointFlags,
389    ) -> Vec<ElementsFromPointResult>;
390    fn register_custom_property(
391        &mut self,
392        property_registration: PropertyRegistration,
393    ) -> Result<(), RegisterPropertyError>;
394}
395
396/// This trait is part of `layout_api` because it depends on both `script_traits`
397/// and also `LayoutFactory` from this crate. If it was in `script_traits` there would be a
398/// circular dependency.
399pub trait ScriptThreadFactory {
400    /// Create a `ScriptThread`.
401    fn create(
402        state: InitialScriptState,
403        layout_factory: Arc<dyn LayoutFactory>,
404        image_cache_factory: Arc<dyn ImageCacheFactory>,
405        background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
406    ) -> JoinHandle<()>;
407}
408
409/// Type of the area of CSS box for query.
410/// See <https://www.w3.org/TR/css-box-3/#box-model>.
411#[derive(Copy, Clone)]
412pub enum BoxAreaType {
413    Content,
414    Padding,
415    Border,
416}
417
418pub type CSSPixelRectIterator = Box<dyn Iterator<Item = Rect<Au, CSSPixel>>>;
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}