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