1#![deny(unsafe_code)]
10
11mod largest_contentful_paint_candidate;
14mod layout_damage;
15mod layout_dom;
16mod layout_element;
17mod layout_node;
18mod pseudo_element_chain;
19
20use std::any::Any;
21use std::ops::Range;
22use std::rc::Rc;
23use std::sync::Arc;
24use std::sync::atomic::AtomicIsize;
25use std::thread::JoinHandle;
26use std::time::Duration;
27
28use app_units::Au;
29use atomic_refcell::AtomicRefCell;
30use background_hang_monitor_api::BackgroundHangMonitorRegister;
31use bitflags::bitflags;
32use embedder_traits::{Cursor, ScriptToEmbedderChan, Theme, UntrustedNodeAddress, ViewportDetails};
33use euclid::{Point2D, Rect};
34use fonts::{FontContext, TextByteRange, WebFontDocumentContext, WebFontSetDifference};
35pub use largest_contentful_paint_candidate::LCPCandidate;
36pub use layout_damage::{AccessibilityDamage, LayoutDamage};
37pub use layout_dom::{
38 DangerousStyleElementOf, DangerousStyleNodeOf, LayoutDomTypeBundle, LayoutElementOf,
39 LayoutNodeOf,
40};
41pub use layout_element::{DangerousStyleElement, LayoutElement};
42pub use layout_node::{DangerousStyleNode, LayoutNode};
43use libc::c_void;
44use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps, malloc_size_of_is_0};
45use malloc_size_of_derive::MallocSizeOf;
46use net_traits::image_cache::{ImageCache, ImageCacheFactory, PendingImageId};
47use net_traits::request::InternalRequest;
48use paint_api::display_list::PaintTimingInfo;
49use paint_api::CrossProcessPaintApi;
50use parking_lot::RwLock;
51use pixels::{RasterImage, Repeat};
52use profile_traits::mem::Report;
53use profile_traits::time;
54pub use pseudo_element_chain::PseudoElementChain;
55use rustc_hash::{FxHashMap, FxHashSet};
56use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
57use serde::{Deserialize, Serialize};
58use servo_arc::Arc as ServoArc;
59use servo_base::Epoch;
60use servo_base::generic_channel::GenericSender;
61use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
62use servo_base::text::{Utf32CodeUnits, Utf32CodeUnitsOrNodeOffset};
63use servo_url::{ImmutableOrigin, ServoUrl};
64use style::Atom;
65use style::animation::DocumentAnimationSet;
66use style::attr::{AttrValue, parse_double};
67use style::context::QuirksMode;
68use style::data::ElementDataWrapper;
69use style::device::Device;
70use style::dom::OpaqueNode;
71use style::invalidation::element::restyle_hints::RestyleHint;
72use style::properties::style_structs::Font;
73use style::properties::{ComputedValues, PropertyId};
74use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
75use style::str::char_is_whitespace;
76use style::stylesheets::{DocumentStyleSheet, Stylesheet};
77use style::stylist::Stylist;
78#[cfg(debug_assertions)]
79use style::thread_state::{self, ThreadState};
80use style::values::computed::Overflow;
81use style_traits::CSSPixel;
82use uuid::Uuid;
83use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
84use webrender_api::{ExternalScrollId, ImageKey};
85
86pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait + Send + Sync + 'static {
87 fn as_any(&self) -> &dyn Any;
88}
89
90pub trait LayoutDataTrait: GenericLayoutDataTrait + Default {}
91pub type GenericLayoutData = dyn GenericLayoutDataTrait;
92
93#[derive(Default, MallocSizeOf)]
94pub struct StyleData {
95 pub element_data: ElementDataWrapper,
100
101 pub parallel: DomParallelInfo,
103}
104
105#[derive(Default, MallocSizeOf)]
107pub struct DomParallelInfo {
108 pub children_to_process: AtomicIsize,
110}
111
112#[derive(Clone, Copy, Debug, Eq, PartialEq)]
113pub enum LayoutNodeType {
114 Element(LayoutElementType),
115 Text,
116}
117
118#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub enum LayoutElementType {
120 Element,
121 HTMLBodyElement,
122 HTMLButtonElement,
123 HTMLBRElement,
124 HTMLCanvasElement,
125 HTMLHtmlElement,
126 HTMLIFrameElement,
127 HTMLImageElement,
128 HTMLInputElement,
129 HTMLMediaElement,
130 HTMLObjectElement,
131 HTMLOptGroupElement,
132 HTMLOptionElement,
133 HTMLParagraphElement,
134 HTMLPreElement,
135 HTMLSelectElement,
136 HTMLTableCellElement,
137 HTMLTableColElement,
138 HTMLTableElement,
139 HTMLTableRowElement,
140 HTMLTableSectionElement,
141 HTMLTextAreaElement,
142 SVGImageElement,
143 SVGSVGElement,
144}
145
146#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
150pub struct ScriptSelection {
151 pub range: TextByteRange,
153 pub character_range: Range<usize>,
155 pub enabled: bool,
158}
159
160pub type SharedSelection = Arc<AtomicRefCell<ScriptSelection>>;
161pub struct HTMLCanvasData {
162 pub image_key: Option<ImageKey>,
163 pub width: u32,
164 pub height: u32,
165}
166
167pub struct SVGElementData<'dom> {
168 pub source: Option<Result<ServoUrl, ()>>,
170 pub width: Option<&'dom AttrValue>,
171 pub height: Option<&'dom AttrValue>,
172 pub svg_id: Uuid,
173 pub view_box: Option<&'dom AttrValue>,
175}
176
177impl SVGElementData<'_> {
178 pub fn ratio_from_view_box(&self) -> Option<f32> {
180 let mut iter = self
181 .view_box?
182 .split(|c| char_is_whitespace(c) || c == ',')
183 .filter(|part| !part.is_empty());
184 let _min_x = parse_double(iter.next()?).ok()?;
185 let _min_y = parse_double(iter.next()?).ok()?;
186
187 let width = parse_double(iter.next()?).ok()?;
188 if width == 0f64 {
189 return None;
190 }
191
192 let height = parse_double(iter.next()?).ok()?;
193 if height == 0f64 {
194 return None;
195 }
196
197 iter.next().is_none().then(|| width as f32 / height as f32)
198 }
199}
200
201#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
203pub struct TrustedNodeAddress(pub *const c_void);
204
205#[expect(unsafe_code)]
206unsafe impl Send for TrustedNodeAddress {}
207
208#[derive(Debug)]
210pub enum PendingImageState {
211 Unrequested(ServoUrl),
212 PendingResponse,
213}
214
215#[derive(Debug, MallocSizeOf)]
217pub enum LayoutImageDestination {
218 BoxTreeConstruction,
219 DisplayListBuilding,
220}
221
222#[derive(Debug)]
226pub struct PendingImage {
227 pub state: PendingImageState,
228 pub node: UntrustedNodeAddress,
229 pub id: PendingImageId,
230 pub origin: ImmutableOrigin,
231 pub destination: LayoutImageDestination,
232 pub is_internal_request: InternalRequest,
233}
234
235#[derive(Debug)]
239pub struct PendingRasterizationImage {
240 pub node: UntrustedNodeAddress,
241 pub id: PendingImageId,
242 pub size: DeviceIntSize,
243}
244
245#[derive(Clone, Copy, Debug, MallocSizeOf)]
246pub struct MediaFrame {
247 pub image_key: webrender_api::ImageKey,
248 pub width: i32,
249 pub height: i32,
250}
251
252pub struct MediaMetadata {
253 pub width: u32,
254 pub height: u32,
255}
256
257#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)]
266pub enum WebVttPositionAlign {
267 LineLeft,
268 Center,
269 LineRight,
270 #[default]
271 Auto,
272}
273
274#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)]
276pub enum WebVttTextAlign {
277 Start,
278 #[default]
279 Center,
280 End,
281 Left,
282 Right,
283}
284
285#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
288pub struct WebVttCueBoxData {
289 pub text_lines: Vec<String>,
292 pub line: Option<f64>,
294 pub snap_to_lines: bool,
296 pub position: Option<f64>,
298 pub position_align: WebVttPositionAlign,
300 pub align: WebVttTextAlign,
302 pub size: f64,
304 pub order: usize,
307}
308
309pub struct HTMLMediaData {
310 pub current_frame: Option<MediaFrame>,
311 pub metadata: Option<MediaMetadata>,
312 pub poster_url: Option<ServoUrl>,
313 pub cue_overlays: Vec<WebVttCueBoxData>,
316}
317
318pub struct LayoutConfig {
319 pub id: PipelineId,
320 pub webview_id: WebViewId,
321 pub url: ServoUrl,
322 pub is_iframe: bool,
323 pub script_chan: GenericSender<ScriptThreadMessage>,
324 pub image_cache: Arc<dyn ImageCache>,
325 pub font_context: Arc<FontContext>,
326 pub time_profiler_chan: time::ProfilerChan,
327 pub paint_api: CrossProcessPaintApi,
328 pub viewport_details: ViewportDetails,
329 pub user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
330 pub theme: Theme,
331 pub embedder_chan: ScriptToEmbedderChan,
332}
333
334bitflags! {
335 #[derive(Copy, Clone)]
336 pub struct HitTestFlags: u8 {
337 const IncludeDomPosition = 0b0000_0001;
339 }
340}
341
342pub trait LayoutFactory: Send + Sync {
343 fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
344}
345
346pub trait Layout {
347 fn device(&self) -> &Device;
350
351 fn set_theme(&mut self, theme: Theme) -> bool;
355
356 fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
360
361 fn add_stylesheet(
366 &mut self,
367 stylesheet: ServoArc<Stylesheet>,
368 before_stylesheet: Option<ServoArc<Stylesheet>>,
369 );
370
371 fn exit_now(&mut self);
373
374 fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
377
378 fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
380
381 fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
383
384 fn remove_cached_image(&mut self, image_url: &ServoUrl);
386
387 fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
389
390 fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
393
394 fn register_paint_worklet_modules(
396 &mut self,
397 name: Atom,
398 properties: Vec<Atom>,
399 painter: Box<dyn Painter>,
400 );
401
402 fn set_scroll_offsets_from_renderer(
404 &mut self,
405 scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
406 );
407
408 fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
411
412 fn needs_new_display_list(&self) -> bool;
414
415 fn set_needs_new_display_list(&self);
417
418 fn node_rendering_type(
421 &self,
422 node: TrustedNodeAddress,
423 pseudo: Option<PseudoElement>,
424 ) -> NodeRenderingType;
425
426 fn query_containing_block(&self, node: TrustedNodeAddress) -> Option<UntrustedNodeAddress>;
427 fn query_containing_block_is_descendant(
428 &self,
429 root: TrustedNodeAddress,
430 possible_descendant: TrustedNodeAddress,
431 ) -> bool;
432 fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
433 fn query_box_area(
434 &self,
435 node: TrustedNodeAddress,
436 area: BoxAreaType,
437 exclude_transform_and_inline: bool,
438 ) -> Option<Rect<Au, CSSPixel>>;
439 fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectVec;
440 fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
441 fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
442 fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
443 fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
444 fn query_scroll_container(
447 &self,
448 node: Option<TrustedNodeAddress>,
449 flags: ScrollContainerQueryFlags,
450 ) -> Option<ScrollContainerResponse>;
451 fn query_resolved_style(
452 &self,
453 node: TrustedNodeAddress,
454 pseudo: Option<PseudoElement>,
455 property_id: PropertyId,
456 animations: DocumentAnimationSet,
457 animation_timeline_value: f64,
458 ) -> String;
459 fn query_resolved_font_style(
460 &self,
461 node: TrustedNodeAddress,
462 value: &str,
463 animations: DocumentAnimationSet,
464 animation_timeline_value: f64,
465 ) -> Option<ServoArc<Font>>;
466 fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
467 fn query_text_index(
471 &self,
472 node: TrustedNodeAddress,
473 point_in_viewport: Point2D<Au, CSSPixel>,
474 ) -> Option<(OpaqueNode, Utf32CodeUnits)>;
475 fn hit_test(&self, flags: HitTestFlags, point: LayoutPoint) -> HitTestResult;
476 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
477 fn stylist_mut(&mut self) -> &mut Stylist;
478
479 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
482
483 fn accessibility_active(&self) -> bool;
485
486 fn force_accessibility_update(&self) -> bool;
494
495 fn set_force_accessibility_update(&self);
497
498 fn font_context(&self) -> &Arc<FontContext>;
499}
500
501pub trait ScriptThreadFactory {
505 fn create(
507 state: InitialScriptState,
508 layout_factory: Arc<dyn LayoutFactory>,
509 image_cache_factory: Arc<dyn ImageCacheFactory>,
510 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
511 ) -> JoinHandle<()>;
512}
513
514#[derive(Copy, Clone)]
517pub enum BoxAreaType {
518 Content,
519 Padding,
520 Border,
521}
522
523pub type CSSPixelRectVec = Vec<Rect<Au, CSSPixel>>;
524
525#[derive(Copy, Clone)]
528pub enum NodeRenderingType {
529 Rendered,
531 DelegatesRendering,
533 NotRendered,
536}
537
538#[derive(Default)]
539pub struct PhysicalSides {
540 pub left: Au,
541 pub top: Au,
542 pub right: Au,
543 pub bottom: Au,
544}
545
546#[derive(Clone, Default)]
547pub struct OffsetParentResponse {
548 pub node_address: Option<UntrustedNodeAddress>,
549 pub rect: Rect<Au, CSSPixel>,
550}
551
552bitflags! {
553 #[derive(PartialEq)]
554 pub struct ScrollContainerQueryFlags: u8 {
555 const ForScrollParent = 1 << 0;
557 const Inclusive = 1 << 1;
559 }
560}
561
562#[derive(Clone, Copy, Debug, MallocSizeOf)]
563pub struct AxesOverflow {
564 pub x: Overflow,
565 pub y: Overflow,
566}
567
568impl Default for AxesOverflow {
569 fn default() -> Self {
570 Self {
571 x: Overflow::Visible,
572 y: Overflow::Visible,
573 }
574 }
575}
576
577impl From<&ComputedValues> for AxesOverflow {
578 fn from(style: &ComputedValues) -> Self {
579 Self {
580 x: style.clone_overflow_x(),
581 y: style.clone_overflow_y(),
582 }
583 }
584}
585
586impl AxesOverflow {
587 pub fn to_scrollable(&self) -> Self {
588 Self {
589 x: self.x.to_scrollable(),
590 y: self.y.to_scrollable(),
591 }
592 }
593
594 pub fn establishes_scroll_container(&self) -> bool {
596 self.x.is_scrollable()
599 }
600}
601
602#[derive(Clone)]
603pub enum ScrollContainerResponse {
604 Viewport(AxesOverflow),
605 Element(UntrustedNodeAddress, AxesOverflow),
606}
607
608#[derive(Debug, PartialEq)]
609pub enum QueryMsg {
610 BoxArea,
611 BoxAreas,
612 ClientRectQuery,
613 CurrentCSSZoomQuery,
614 EffectiveOverflow,
615 ElementInnerOuterTextQuery,
616 ElementsFromPoint,
617 InnerWindowDimensionsQuery,
618 NodesFromPointQuery,
619 OffsetParentQuery,
620 ScrollParentQuery,
621 ResolvedFontStyleQuery,
622 ResolvedStyleQuery(PropertyId),
625 ScrollingAreaOrOffsetQuery,
626 StyleQuery,
627 TextIndexQuery,
628 PaddingQuery,
629 FlushForUpdateTheRenderingQuery,
630}
631
632#[derive(Debug, PartialEq)]
638pub enum ReflowGoal {
639 UpdateTheRendering,
642
643 LayoutQuery(QueryMsg),
646
647 UpdateScrollNode(ExternalScrollId, LayoutVector2D),
651}
652
653#[derive(Clone, Debug, MallocSizeOf)]
654pub struct IFrameSize {
655 pub browsing_context_id: BrowsingContextId,
656 pub pipeline_id: PipelineId,
657 pub viewport_details: ViewportDetails,
658}
659
660pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
661
662bitflags! {
663 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
666 pub struct RestyleReason: u16 {
667 const StylesheetsChanged = 1 << 0;
668 const DOMChanged = 1 << 1;
669 const PendingRestyles = 1 << 2;
670 const HighlightedDOMNodeChanged = 1 << 3;
671 const ThemeChanged = 1 << 4;
672 const ViewportChanged = 1 << 5;
673 const PaintWorkletLoaded = 1 << 6;
674 }
675}
676
677malloc_size_of_is_0!(RestyleReason);
678
679impl RestyleReason {
680 pub fn needs_restyle(&self) -> bool {
681 !self.is_empty()
682 }
683}
684
685#[derive(Default)]
687pub struct ReflowResult {
688 pub reflow_phases_run: ReflowPhasesRun,
690 pub reflow_statistics: ReflowStatistics,
691 pub pending_images: Vec<PendingImage>,
693 pub pending_rasterization_images: Vec<PendingRasterizationImage>,
695 pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
699 pub iframe_sizes: Option<IFrameSizes>,
705 pub changed_web_fonts: WebFontSetDifference,
707 pub lcp_candidate: Option<LCPCandidate>,
709}
710
711bitflags! {
712 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
714 pub struct ReflowPhasesRun: u8 {
715 const RanLayout = 1 << 0;
716 const BuiltStackingContextTree = 1 << 2;
717 const BuiltDisplayList = 1 << 3;
718 const UpdatedScrollNodeOffset = 1 << 4;
719 const UpdatedImageData = 1 << 5;
723 const UpdatedAccessibilityTree = 1 << 6;
724 }
725}
726
727impl ReflowPhasesRun {
728 pub fn needs_frame(&self) -> bool {
729 self.intersects(
730 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
731 )
732 }
733}
734
735#[derive(Debug, Default)]
736pub struct ReflowStatistics {
737 pub rebuilt_fragment_count: u32,
739 pub restyle_fragment_count: u32,
741 pub only_descendants_changed_count: u32,
744 pub nodes_updated_from_dom: u32,
747 pub nodes_updated_from_tree: u32,
750 pub nodes_updated_bounds: u32,
753 pub nodes_in_tree_update: u32,
755}
756
757#[derive(Debug)]
760pub struct ReflowRequestRestyle {
761 pub reason: RestyleReason,
763 pub dirty_root: Option<TrustedNodeAddress>,
765 pub stylesheets_changed: bool,
767 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
769}
770
771#[derive(Debug)]
773pub struct ReflowRequest {
774 pub document: TrustedNodeAddress,
776 pub epoch: Epoch,
778 pub restyle: Option<ReflowRequestRestyle>,
780 pub viewport_details: ViewportDetails,
782 pub reflow_goal: ReflowGoal,
784 pub origin: ImmutableOrigin,
786 pub animation_timeline_value: f64,
788 pub animations: DocumentAnimationSet,
790 pub animating_images: Arc<RwLock<AnimatingImages>>,
792 pub highlighted_dom_node: Option<OpaqueNode>,
794 pub halt_lcp: bool,
798 pub paint_timing_eligible: bool,
803 pub paint_timing_info: PaintTimingInfo,
806 pub document_context: WebFontDocumentContext,
808 pub accessibility_damage: Option<Vec<(TrustedNodeAddress, AccessibilityDamage)>>,
810 pub rooted_nodes_for_accessibility_integrity_check: Option<FxHashSet<OpaqueNode>>,
814}
815
816impl ReflowRequest {
817 pub fn stylesheets_changed(&self) -> bool {
818 self.restyle
819 .as_ref()
820 .is_some_and(|restyle| restyle.stylesheets_changed)
821 }
822}
823
824#[derive(Debug, Default, MallocSizeOf)]
826pub struct PendingRestyle {
827 pub snapshot: Option<Snapshot>,
830
831 pub hint: RestyleHint,
833
834 pub damage: RestyleDamage,
836}
837
838#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
844pub enum FragmentType {
845 FragmentBody,
847 BeforePseudoContent,
849 AfterPseudoContent,
851}
852
853impl From<Option<PseudoElement>> for FragmentType {
854 fn from(value: Option<PseudoElement>) -> Self {
855 match value {
856 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
857 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
858 _ => FragmentType::FragmentBody,
859 }
860 }
861}
862
863pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
864 debug_assert_eq!(id & (fragment_type as usize), 0);
865 (id as u64) | (fragment_type as u64)
866}
867
868pub fn node_id_from_scroll_id(id: usize) -> usize {
869 id & !3
870}
871
872#[derive(Clone, Debug, MallocSizeOf)]
873pub struct ImageAnimationState {
874 #[conditional_malloc_size_of]
875 pub image: Arc<RasterImage>,
876 pub active_frame: usize,
877 frame_start_time: f64,
878
879 pub completed_loops: Option<u32>,
884}
885
886impl ImageAnimationState {
887 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
888 let completd_loops = match &image.loop_count {
889 None => unreachable!("Loop count of an animated Image should never be None"),
890 Some(repeat) if Repeat::Infinite == *repeat => None,
891 _ => Some(0),
892 };
893
894 Self {
895 image,
896 active_frame: 0,
897 frame_start_time: last_update_time,
898 completed_loops: completd_loops,
899 }
900 }
901
902 pub fn image_key(&self) -> Option<ImageKey> {
903 self.image.id
904 }
905
906 pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
907 if self.is_finished() {
908 return None;
909 }
910 let frame_delay = self
911 .image
912 .frames
913 .get(self.active_frame)
914 .expect("Image frame should always be valid")
915 .delay
916 .unwrap_or_default();
917
918 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
919 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
920 Some(frame_delay - time_since_frame_start.min(frame_delay))
921 }
922
923 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
927 if self.image.frames.len() <= 1 || self.is_finished() {
928 return false;
929 }
930 let time_interval_since_last_update = now - self.frame_start_time;
931 let mut remain_time_interval = time_interval_since_last_update -
932 self.image
933 .frames
934 .get(self.active_frame)
935 .unwrap()
936 .delay()
937 .unwrap()
938 .as_secs_f64();
939 let mut next_active_frame_id = self.active_frame;
940
941 let frame_count = self.image.frames.len();
942 while remain_time_interval > 0.0 {
943 next_active_frame_id = (next_active_frame_id + 1) % frame_count;
944
945 if next_active_frame_id == 0 {
947 self.advance_completed_loops();
948
949 if self.is_finished() {
952 if self.active_frame == frame_count - 1 {
953 return false;
954 }
955 self.active_frame = frame_count - 1;
956 self.frame_start_time = now;
957 return true;
958 }
959 }
960
961 remain_time_interval -= self
962 .image
963 .frames
964 .get(next_active_frame_id)
965 .unwrap()
966 .delay()
967 .unwrap()
968 .as_secs_f64();
969 }
970 if self.active_frame == next_active_frame_id {
971 return false;
972 }
973 self.active_frame = next_active_frame_id;
974 self.frame_start_time = now;
975 true
976 }
977
978 fn is_finished(&self) -> bool {
980 let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
981 return false;
982 };
983 self.completed_loops
984 .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
985 }
986
987 fn advance_completed_loops(&mut self) {
989 if let Some(completed_loops) = self.completed_loops.as_mut() {
990 *completed_loops += 1;
991 }
992 }
993}
994
995#[derive(Debug, Default)]
997pub struct HitTestResult {
998 pub items: Vec<HitTestResultItem>,
999 pub dom_position_for_selection: Option<(OpaqueNode, Utf32CodeUnitsOrNodeOffset)>,
1000}
1001
1002#[derive(Debug)]
1004pub struct HitTestResultItem {
1005 pub node: OpaqueNode,
1008 pub point_in_target: Point2D<f32, CSSPixel>,
1011 pub cursor: Cursor,
1014}
1015
1016#[derive(Debug, Default, MallocSizeOf)]
1017pub struct AnimatingImages {
1018 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
1021 pub dirty: bool,
1024}
1025
1026impl AnimatingImages {
1027 pub fn maybe_insert_or_update(
1028 &mut self,
1029 node: OpaqueNode,
1030 image: Arc<RasterImage>,
1031 current_timeline_value: f64,
1032 ) {
1033 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
1034 self.dirty = true;
1035 ImageAnimationState::new(image.clone(), current_timeline_value)
1036 });
1037
1038 if entry.image.id != image.id {
1041 self.dirty = true;
1042 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
1043 }
1044 }
1045
1046 pub fn remove(&mut self, node: OpaqueNode) {
1047 if self.node_to_state_map.remove(&node).is_some() {
1048 self.dirty = true;
1049 }
1050 }
1051
1052 pub fn clear_dirty(&mut self) -> bool {
1054 std::mem::take(&mut self.dirty)
1055 }
1056
1057 pub fn is_empty(&self) -> bool {
1058 self.node_to_state_map.is_empty()
1059 }
1060}
1061
1062struct ThreadStateRestorer;
1063
1064impl ThreadStateRestorer {
1065 fn new() -> Self {
1066 #[cfg(debug_assertions)]
1067 {
1068 thread_state::exit(ThreadState::SCRIPT);
1069 thread_state::enter(ThreadState::LAYOUT);
1070 }
1071 Self
1072 }
1073}
1074
1075impl Drop for ThreadStateRestorer {
1076 fn drop(&mut self) {
1077 #[cfg(debug_assertions)]
1078 {
1079 thread_state::exit(ThreadState::LAYOUT);
1080 thread_state::enter(ThreadState::SCRIPT);
1081 }
1082 }
1083}
1084
1085pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
1091 let _guard = ThreadStateRestorer::new();
1092 f()
1093}
1094
1095#[cfg(test)]
1096mod test {
1097 use std::num::NonZeroU32;
1098 use std::sync::Arc;
1099 use std::time::Duration;
1100
1101 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
1102
1103 use crate::ImageAnimationState;
1104
1105 #[test]
1106 fn test_animated_image_update() {
1107 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1108 delay: Some(Duration::from_millis(100)),
1109 byte_range: 0..1,
1110 width: 100,
1111 height: 100,
1112 })
1113 .take(10)
1114 .collect();
1115 let image = RasterImage {
1116 metadata: ImageMetadata {
1117 width: 100,
1118 height: 100,
1119 },
1120 format: PixelFormat::BGRA8,
1121 id: None,
1122 bytes: Arc::new(vec![1]),
1123 frames: image_frames,
1124 cors_status: CorsStatus::Unsafe,
1125 loop_count: Some(Repeat::Infinite),
1126 is_opaque: false,
1127 };
1128 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1129
1130 assert_eq!(image_animation_state.active_frame, 0);
1131 assert_eq!(image_animation_state.frame_start_time, 0.0);
1132 assert_eq!(
1133 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1134 true
1135 );
1136 assert_eq!(image_animation_state.active_frame, 1);
1137 assert_eq!(image_animation_state.frame_start_time, 0.101);
1138 assert_eq!(
1139 image_animation_state.update_frame_for_animation_timeline_value(0.116),
1140 false
1141 );
1142 assert_eq!(image_animation_state.active_frame, 1);
1143 assert_eq!(image_animation_state.frame_start_time, 0.101);
1144 }
1145
1146 #[test]
1147 fn test_finite_image_repeat() {
1148 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1149 delay: Some(Duration::from_millis(100)),
1150 byte_range: 0..1,
1151 width: 100,
1152 height: 100,
1153 })
1154 .take(2)
1155 .collect();
1156 let image = RasterImage {
1157 metadata: ImageMetadata {
1158 width: 100,
1159 height: 100,
1160 },
1161 format: PixelFormat::BGRA8,
1162 id: None,
1163 bytes: Arc::new(vec![1]),
1164 frames: image_frames,
1165 cors_status: CorsStatus::Unsafe,
1166 loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1167 is_opaque: false,
1168 };
1169 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1170
1171 assert_eq!(image_animation_state.active_frame, 0);
1172 assert_eq!(image_animation_state.frame_start_time, 0.0);
1173 assert_eq!(
1174 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1175 true
1176 );
1177 assert_eq!(image_animation_state.active_frame, 1);
1178 assert_eq!(image_animation_state.frame_start_time, 0.101);
1179 assert_eq!(
1180 image_animation_state.update_frame_for_animation_timeline_value(0.202),
1181 false
1182 );
1183 assert_eq!(
1184 image_animation_state.update_frame_for_animation_timeline_value(0.303),
1185 false
1186 );
1187
1188 assert_eq!(image_animation_state.active_frame, 1);
1189 }
1190}