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