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