1#![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, WebFontSetDifference};
32pub use layout_damage::{AccessibilityDamage, 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 net_traits::request::InternalRequest;
44use paint_api::CrossProcessPaintApi;
45use parking_lot::RwLock;
46use pixels::{RasterImage, Repeat};
47use profile_traits::mem::Report;
48use profile_traits::time;
49pub use pseudo_element_chain::PseudoElementChain;
50use rustc_hash::{FxHashMap, FxHashSet};
51use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
52use serde::{Deserialize, Serialize};
53use servo_arc::Arc as ServoArc;
54use servo_base::Epoch;
55use servo_base::generic_channel::GenericSender;
56use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
57use servo_url::{ImmutableOrigin, ServoUrl};
58use style::Atom;
59use style::animation::DocumentAnimationSet;
60use style::attr::{AttrValue, parse_integer, parse_unsigned_integer};
61use style::context::QuirksMode;
62use style::data::ElementDataWrapper;
63use style::device::Device;
64use style::dom::OpaqueNode;
65use style::invalidation::element::restyle_hints::RestyleHint;
66use style::properties::style_structs::Font;
67use style::properties::{ComputedValues, PropertyId};
68use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
69use style::str::char_is_whitespace;
70use style::stylesheets::{DocumentStyleSheet, Stylesheet};
71use style::stylist::Stylist;
72#[cfg(debug_assertions)]
73use style::thread_state::{self, ThreadState};
74use style::values::computed::Overflow;
75use style_traits::CSSPixel;
76use uuid::Uuid;
77use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
78use webrender_api::{ExternalScrollId, ImageKey};
79
80pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait + Send + Sync + 'static {
81 fn as_any(&self) -> &dyn Any;
82}
83
84pub trait LayoutDataTrait: GenericLayoutDataTrait + Default {}
85pub type GenericLayoutData = dyn GenericLayoutDataTrait;
86
87#[derive(Default, MallocSizeOf)]
88pub struct StyleData {
89 pub element_data: ElementDataWrapper,
94
95 pub parallel: DomParallelInfo,
97}
98
99#[derive(Default, MallocSizeOf)]
101pub struct DomParallelInfo {
102 pub children_to_process: AtomicIsize,
104}
105
106#[derive(Clone, Copy, Debug, Eq, PartialEq)]
107pub enum LayoutNodeType {
108 Element(LayoutElementType),
109 Text,
110}
111
112#[derive(Clone, Copy, Debug, Eq, PartialEq)]
113pub enum LayoutElementType {
114 Element,
115 HTMLBodyElement,
116 HTMLButtonElement,
117 HTMLBRElement,
118 HTMLCanvasElement,
119 HTMLHtmlElement,
120 HTMLIFrameElement,
121 HTMLImageElement,
122 HTMLInputElement,
123 HTMLMediaElement,
124 HTMLObjectElement,
125 HTMLOptGroupElement,
126 HTMLOptionElement,
127 HTMLParagraphElement,
128 HTMLPreElement,
129 HTMLSelectElement,
130 HTMLTableCellElement,
131 HTMLTableColElement,
132 HTMLTableElement,
133 HTMLTableRowElement,
134 HTMLTableSectionElement,
135 HTMLTextAreaElement,
136 SVGImageElement,
137 SVGSVGElement,
138}
139
140#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
144pub struct ScriptSelection {
145 pub range: TextByteRange,
147 pub character_range: Range<usize>,
149 pub enabled: bool,
152}
153
154pub type SharedSelection = Arc<AtomicRefCell<ScriptSelection>>;
155pub struct HTMLCanvasData {
156 pub image_key: Option<ImageKey>,
157 pub width: u32,
158 pub height: u32,
159}
160
161pub struct SVGElementData<'dom> {
162 pub source: Option<Result<ServoUrl, ()>>,
164 pub width: Option<&'dom AttrValue>,
165 pub height: Option<&'dom AttrValue>,
166 pub svg_id: Uuid,
167 pub view_box: Option<&'dom AttrValue>,
168}
169
170impl SVGElementData<'_> {
171 pub fn ratio_from_view_box(&self) -> Option<f32> {
172 let mut iter = self.view_box?.chars();
173 let _min_x = parse_integer(&mut iter).ok()?;
174 let _min_y = parse_integer(&mut iter).ok()?;
175
176 let width = parse_unsigned_integer(&mut iter).ok()?;
177 if width == 0 {
178 return None;
179 }
180
181 let height = parse_unsigned_integer(&mut iter).ok()?;
182 if height == 0 {
183 return None;
184 }
185
186 let mut iter = iter.skip_while(|c| char_is_whitespace(*c));
187 iter.next().is_none().then(|| width as f32 / height as f32)
188 }
189}
190
191#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
193pub struct TrustedNodeAddress(pub *const c_void);
194
195#[expect(unsafe_code)]
196unsafe impl Send for TrustedNodeAddress {}
197
198#[derive(Debug)]
200pub enum PendingImageState {
201 Unrequested(ServoUrl),
202 PendingResponse,
203}
204
205#[derive(Debug, MallocSizeOf)]
207pub enum LayoutImageDestination {
208 BoxTreeConstruction,
209 DisplayListBuilding,
210}
211
212#[derive(Debug)]
216pub struct PendingImage {
217 pub state: PendingImageState,
218 pub node: UntrustedNodeAddress,
219 pub id: PendingImageId,
220 pub origin: ImmutableOrigin,
221 pub destination: LayoutImageDestination,
222 pub is_internal_request: InternalRequest,
223}
224
225#[derive(Debug)]
229pub struct PendingRasterizationImage {
230 pub node: UntrustedNodeAddress,
231 pub id: PendingImageId,
232 pub size: DeviceIntSize,
233}
234
235#[derive(Clone, Copy, Debug, MallocSizeOf)]
236pub struct MediaFrame {
237 pub image_key: webrender_api::ImageKey,
238 pub width: i32,
239 pub height: i32,
240}
241
242pub struct MediaMetadata {
243 pub width: u32,
244 pub height: u32,
245}
246
247pub struct HTMLMediaData {
248 pub current_frame: Option<MediaFrame>,
249 pub metadata: Option<MediaMetadata>,
250}
251
252pub struct LayoutConfig {
253 pub id: PipelineId,
254 pub webview_id: WebViewId,
255 pub url: ServoUrl,
256 pub is_iframe: bool,
257 pub script_chan: GenericSender<ScriptThreadMessage>,
258 pub image_cache: Arc<dyn ImageCache>,
259 pub font_context: Arc<FontContext>,
260 pub time_profiler_chan: time::ProfilerChan,
261 pub paint_api: CrossProcessPaintApi,
262 pub viewport_details: ViewportDetails,
263 pub user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
264 pub theme: Theme,
265 pub embedder_chan: ScriptToEmbedderChan,
266}
267
268pub trait LayoutFactory: Send + Sync {
269 fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
270}
271
272pub trait Layout {
273 fn device(&self) -> &Device;
276
277 fn set_theme(&mut self, theme: Theme) -> bool;
281
282 fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
286
287 fn add_stylesheet(
292 &mut self,
293 stylesheet: ServoArc<Stylesheet>,
294 before_stylesheet: Option<ServoArc<Stylesheet>>,
295 );
296
297 fn exit_now(&mut self);
299
300 fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
303
304 fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
306
307 fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
309
310 fn remove_cached_image(&mut self, image_url: &ServoUrl);
312
313 fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
315
316 fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
319
320 fn register_paint_worklet_modules(
322 &mut self,
323 name: Atom,
324 properties: Vec<Atom>,
325 painter: Box<dyn Painter>,
326 );
327
328 fn set_scroll_offsets_from_renderer(
330 &mut self,
331 scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
332 );
333
334 fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
337
338 fn needs_new_display_list(&self) -> bool;
340
341 fn set_needs_new_display_list(&self);
343
344 fn node_rendering_type(
347 &self,
348 node: TrustedNodeAddress,
349 pseudo: Option<PseudoElement>,
350 ) -> NodeRenderingType;
351
352 fn query_containing_block(&self, node: TrustedNodeAddress) -> Option<UntrustedNodeAddress>;
353 fn query_containing_block_is_descendant(
354 &self,
355 root: TrustedNodeAddress,
356 possible_descendant: TrustedNodeAddress,
357 ) -> bool;
358 fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
359 fn query_box_area(
360 &self,
361 node: TrustedNodeAddress,
362 area: BoxAreaType,
363 exclude_transform_and_inline: bool,
364 ) -> Option<Rect<Au, CSSPixel>>;
365 fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectVec;
366 fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
367 fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
368 fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
369 fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
370 fn query_scroll_container(
373 &self,
374 node: Option<TrustedNodeAddress>,
375 flags: ScrollContainerQueryFlags,
376 ) -> Option<ScrollContainerResponse>;
377 fn query_resolved_style(
378 &self,
379 node: TrustedNodeAddress,
380 pseudo: Option<PseudoElement>,
381 property_id: PropertyId,
382 animations: DocumentAnimationSet,
383 animation_timeline_value: f64,
384 ) -> String;
385 fn query_resolved_font_style(
386 &self,
387 node: TrustedNodeAddress,
388 value: &str,
389 animations: DocumentAnimationSet,
390 animation_timeline_value: f64,
391 ) -> Option<ServoArc<Font>>;
392 fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
393 fn query_text_index(
395 &self,
396 node: TrustedNodeAddress,
397 point: Point2D<Au, CSSPixel>,
398 ) -> Option<usize>;
399 fn query_elements_from_point(&self, point: LayoutPoint) -> Vec<ElementsFromPointResult>;
400 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
401 fn stylist_mut(&mut self) -> &mut Stylist;
402
403 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
406
407 fn accessibility_active(&self) -> bool;
409
410 fn needs_accessibility_update(&self) -> bool;
421
422 fn set_needs_accessibility_update(&self);
424}
425
426pub trait ScriptThreadFactory {
430 fn create(
432 state: InitialScriptState,
433 layout_factory: Arc<dyn LayoutFactory>,
434 image_cache_factory: Arc<dyn ImageCacheFactory>,
435 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
436 ) -> JoinHandle<()>;
437}
438
439#[derive(Copy, Clone)]
442pub enum BoxAreaType {
443 Content,
444 Padding,
445 Border,
446}
447
448pub type CSSPixelRectVec = Vec<Rect<Au, CSSPixel>>;
449
450#[derive(Copy, Clone)]
453pub enum NodeRenderingType {
454 Rendered,
456 DelegatesRendering,
458 NotRendered,
461}
462
463#[derive(Default)]
464pub struct PhysicalSides {
465 pub left: Au,
466 pub top: Au,
467 pub right: Au,
468 pub bottom: Au,
469}
470
471#[derive(Clone, Default)]
472pub struct OffsetParentResponse {
473 pub node_address: Option<UntrustedNodeAddress>,
474 pub rect: Rect<Au, CSSPixel>,
475}
476
477bitflags! {
478 #[derive(PartialEq)]
479 pub struct ScrollContainerQueryFlags: u8 {
480 const ForScrollParent = 1 << 0;
482 const Inclusive = 1 << 1;
484 }
485}
486
487#[derive(Clone, Copy, Debug, MallocSizeOf)]
488pub struct AxesOverflow {
489 pub x: Overflow,
490 pub y: Overflow,
491}
492
493impl Default for AxesOverflow {
494 fn default() -> Self {
495 Self {
496 x: Overflow::Visible,
497 y: Overflow::Visible,
498 }
499 }
500}
501
502impl From<&ComputedValues> for AxesOverflow {
503 fn from(style: &ComputedValues) -> Self {
504 Self {
505 x: style.clone_overflow_x(),
506 y: style.clone_overflow_y(),
507 }
508 }
509}
510
511impl AxesOverflow {
512 pub fn to_scrollable(&self) -> Self {
513 Self {
514 x: self.x.to_scrollable(),
515 y: self.y.to_scrollable(),
516 }
517 }
518
519 pub fn establishes_scroll_container(&self) -> bool {
521 self.x.is_scrollable()
524 }
525}
526
527#[derive(Clone)]
528pub enum ScrollContainerResponse {
529 Viewport(AxesOverflow),
530 Element(UntrustedNodeAddress, AxesOverflow),
531}
532
533#[derive(Debug, PartialEq)]
534pub enum QueryMsg {
535 BoxArea,
536 BoxAreas,
537 ClientRectQuery,
538 CurrentCSSZoomQuery,
539 EffectiveOverflow,
540 ElementInnerOuterTextQuery,
541 ElementsFromPoint,
542 InnerWindowDimensionsQuery,
543 NodesFromPointQuery,
544 OffsetParentQuery,
545 ScrollParentQuery,
546 ResolvedFontStyleQuery,
547 ResolvedStyleQuery(PropertyId),
550 ScrollingAreaOrOffsetQuery,
551 StyleQuery,
552 TextIndexQuery,
553 PaddingQuery,
554 FlushForUpdateTheRenderingQuery,
555}
556
557#[derive(Debug, PartialEq)]
563pub enum ReflowGoal {
564 UpdateTheRendering,
567
568 LayoutQuery(QueryMsg),
571
572 UpdateScrollNode(ExternalScrollId, LayoutVector2D),
576}
577
578#[derive(Clone, Debug, MallocSizeOf)]
579pub struct IFrameSize {
580 pub browsing_context_id: BrowsingContextId,
581 pub pipeline_id: PipelineId,
582 pub viewport_details: ViewportDetails,
583}
584
585pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
586
587bitflags! {
588 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
591 pub struct RestyleReason: u16 {
592 const StylesheetsChanged = 1 << 0;
593 const DOMChanged = 1 << 1;
594 const PendingRestyles = 1 << 2;
595 const HighlightedDOMNodeChanged = 1 << 3;
596 const ThemeChanged = 1 << 4;
597 const ViewportChanged = 1 << 5;
598 const PaintWorkletLoaded = 1 << 6;
599 }
600}
601
602malloc_size_of_is_0!(RestyleReason);
603
604impl RestyleReason {
605 pub fn needs_restyle(&self) -> bool {
606 !self.is_empty()
607 }
608}
609
610#[derive(Default)]
612pub struct ReflowResult {
613 pub reflow_phases_run: ReflowPhasesRun,
615 pub reflow_statistics: ReflowStatistics,
616 pub pending_images: Vec<PendingImage>,
618 pub pending_rasterization_images: Vec<PendingRasterizationImage>,
620 pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
624 pub iframe_sizes: Option<IFrameSizes>,
630 pub changed_web_fonts: WebFontSetDifference,
632}
633
634bitflags! {
635 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
637 pub struct ReflowPhasesRun: u8 {
638 const RanLayout = 1 << 0;
639 const BuiltStackingContextTree = 1 << 2;
640 const BuiltDisplayList = 1 << 3;
641 const UpdatedScrollNodeOffset = 1 << 4;
642 const UpdatedImageData = 1 << 5;
646 const UpdatedAccessibilityTree = 1 << 6;
647 }
648}
649
650impl ReflowPhasesRun {
651 pub fn needs_frame(&self) -> bool {
652 self.intersects(
653 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
654 )
655 }
656}
657
658#[derive(Debug, Default)]
659pub struct ReflowStatistics {
660 pub rebuilt_fragment_count: u32,
662 pub restyle_fragment_count: u32,
664 pub only_descendants_changed_count: u32,
667 pub nodes_updated_from_dom: u32,
670 pub nodes_updated_from_tree: u32,
673 pub nodes_in_tree_update: u32,
675}
676
677#[derive(Debug)]
680pub struct ReflowRequestRestyle {
681 pub reason: RestyleReason,
683 pub dirty_root: Option<TrustedNodeAddress>,
685 pub stylesheets_changed: bool,
687 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
689}
690
691#[derive(Debug)]
693pub struct ReflowRequest {
694 pub document: TrustedNodeAddress,
696 pub epoch: Epoch,
698 pub restyle: Option<ReflowRequestRestyle>,
700 pub viewport_details: ViewportDetails,
702 pub reflow_goal: ReflowGoal,
704 pub origin: ImmutableOrigin,
706 pub animation_timeline_value: f64,
708 pub animations: DocumentAnimationSet,
710 pub animating_images: Arc<RwLock<AnimatingImages>>,
712 pub highlighted_dom_node: Option<OpaqueNode>,
714 pub document_context: WebFontDocumentContext,
716 pub accessibility_damage: Option<Vec<(TrustedNodeAddress, AccessibilityDamage)>>,
718 pub rooted_nodes_for_accessibility_integrity_check: Option<FxHashSet<OpaqueNode>>,
722}
723
724impl ReflowRequest {
725 pub fn stylesheets_changed(&self) -> bool {
726 self.restyle
727 .as_ref()
728 .is_some_and(|restyle| restyle.stylesheets_changed)
729 }
730}
731
732#[derive(Debug, Default, MallocSizeOf)]
734pub struct PendingRestyle {
735 pub snapshot: Option<Snapshot>,
738
739 pub hint: RestyleHint,
741
742 pub damage: RestyleDamage,
744}
745
746#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
752pub enum FragmentType {
753 FragmentBody,
755 BeforePseudoContent,
757 AfterPseudoContent,
759}
760
761impl From<Option<PseudoElement>> for FragmentType {
762 fn from(value: Option<PseudoElement>) -> Self {
763 match value {
764 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
765 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
766 _ => FragmentType::FragmentBody,
767 }
768 }
769}
770
771pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
772 debug_assert_eq!(id & (fragment_type as usize), 0);
773 (id as u64) | (fragment_type as u64)
774}
775
776pub fn node_id_from_scroll_id(id: usize) -> usize {
777 id & !3
778}
779
780#[derive(Clone, Debug, MallocSizeOf)]
781pub struct ImageAnimationState {
782 #[conditional_malloc_size_of]
783 pub image: Arc<RasterImage>,
784 pub active_frame: usize,
785 frame_start_time: f64,
786
787 pub completed_loops: Option<u32>,
792}
793
794impl ImageAnimationState {
795 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
796 let completd_loops = match &image.loop_count {
797 None => unreachable!("Loop count of an animated Image should never be None"),
798 Some(repeat) if Repeat::Infinite == *repeat => None,
799 _ => Some(0),
800 };
801
802 Self {
803 image,
804 active_frame: 0,
805 frame_start_time: last_update_time,
806 completed_loops: completd_loops,
807 }
808 }
809
810 pub fn image_key(&self) -> Option<ImageKey> {
811 self.image.id
812 }
813
814 pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
815 if self.is_finished() {
816 return None;
817 }
818 let frame_delay = self
819 .image
820 .frames
821 .get(self.active_frame)
822 .expect("Image frame should always be valid")
823 .delay
824 .unwrap_or_default();
825
826 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
827 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
828 Some(frame_delay - time_since_frame_start.min(frame_delay))
829 }
830
831 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
835 if self.image.frames.len() <= 1 || self.is_finished() {
836 return false;
837 }
838 let time_interval_since_last_update = now - self.frame_start_time;
839 let mut remain_time_interval = time_interval_since_last_update -
840 self.image
841 .frames
842 .get(self.active_frame)
843 .unwrap()
844 .delay()
845 .unwrap()
846 .as_secs_f64();
847 let mut next_active_frame_id = self.active_frame;
848
849 let frame_count = self.image.frames.len();
850 while remain_time_interval > 0.0 {
851 next_active_frame_id = (next_active_frame_id + 1) % frame_count;
852
853 if next_active_frame_id == 0 {
855 self.advance_completed_loops();
856
857 if self.is_finished() {
860 if self.active_frame == frame_count - 1 {
861 return false;
862 }
863 self.active_frame = frame_count - 1;
864 self.frame_start_time = now;
865 return true;
866 }
867 }
868
869 remain_time_interval -= self
870 .image
871 .frames
872 .get(next_active_frame_id)
873 .unwrap()
874 .delay()
875 .unwrap()
876 .as_secs_f64();
877 }
878 if self.active_frame == next_active_frame_id {
879 return false;
880 }
881 self.active_frame = next_active_frame_id;
882 self.frame_start_time = now;
883 true
884 }
885
886 fn is_finished(&self) -> bool {
888 let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
889 return false;
890 };
891 self.completed_loops
892 .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
893 }
894
895 fn advance_completed_loops(&mut self) {
897 if let Some(completed_loops) = self.completed_loops.as_mut() {
898 *completed_loops += 1;
899 }
900 }
901}
902
903#[derive(Debug)]
905pub struct ElementsFromPointResult {
906 pub node: OpaqueNode,
909 pub point_in_target: Point2D<f32, CSSPixel>,
912 pub cursor: Cursor,
915}
916
917#[derive(Debug, Default, MallocSizeOf)]
918pub struct AnimatingImages {
919 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
922 pub dirty: bool,
925}
926
927impl AnimatingImages {
928 pub fn maybe_insert_or_update(
929 &mut self,
930 node: OpaqueNode,
931 image: Arc<RasterImage>,
932 current_timeline_value: f64,
933 ) {
934 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
935 self.dirty = true;
936 ImageAnimationState::new(image.clone(), current_timeline_value)
937 });
938
939 if entry.image.id != image.id {
942 self.dirty = true;
943 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
944 }
945 }
946
947 pub fn remove(&mut self, node: OpaqueNode) {
948 if self.node_to_state_map.remove(&node).is_some() {
949 self.dirty = true;
950 }
951 }
952
953 pub fn clear_dirty(&mut self) -> bool {
955 std::mem::take(&mut self.dirty)
956 }
957
958 pub fn is_empty(&self) -> bool {
959 self.node_to_state_map.is_empty()
960 }
961}
962
963struct ThreadStateRestorer;
964
965impl ThreadStateRestorer {
966 fn new() -> Self {
967 #[cfg(debug_assertions)]
968 {
969 thread_state::exit(ThreadState::SCRIPT);
970 thread_state::enter(ThreadState::LAYOUT);
971 }
972 Self
973 }
974}
975
976impl Drop for ThreadStateRestorer {
977 fn drop(&mut self) {
978 #[cfg(debug_assertions)]
979 {
980 thread_state::exit(ThreadState::LAYOUT);
981 thread_state::enter(ThreadState::SCRIPT);
982 }
983 }
984}
985
986pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
992 let _guard = ThreadStateRestorer::new();
993 f()
994}
995
996#[cfg(test)]
997mod test {
998 use std::num::NonZeroU32;
999 use std::sync::Arc;
1000 use std::time::Duration;
1001
1002 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
1003
1004 use crate::ImageAnimationState;
1005
1006 #[test]
1007 fn test_animated_image_update() {
1008 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1009 delay: Some(Duration::from_millis(100)),
1010 byte_range: 0..1,
1011 width: 100,
1012 height: 100,
1013 })
1014 .take(10)
1015 .collect();
1016 let image = RasterImage {
1017 metadata: ImageMetadata {
1018 width: 100,
1019 height: 100,
1020 },
1021 format: PixelFormat::BGRA8,
1022 id: None,
1023 bytes: Arc::new(vec![1]),
1024 frames: image_frames,
1025 cors_status: CorsStatus::Unsafe,
1026 loop_count: Some(Repeat::Infinite),
1027 is_opaque: false,
1028 };
1029 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1030
1031 assert_eq!(image_animation_state.active_frame, 0);
1032 assert_eq!(image_animation_state.frame_start_time, 0.0);
1033 assert_eq!(
1034 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1035 true
1036 );
1037 assert_eq!(image_animation_state.active_frame, 1);
1038 assert_eq!(image_animation_state.frame_start_time, 0.101);
1039 assert_eq!(
1040 image_animation_state.update_frame_for_animation_timeline_value(0.116),
1041 false
1042 );
1043 assert_eq!(image_animation_state.active_frame, 1);
1044 assert_eq!(image_animation_state.frame_start_time, 0.101);
1045 }
1046
1047 #[test]
1048 fn test_finite_image_repeat() {
1049 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1050 delay: Some(Duration::from_millis(100)),
1051 byte_range: 0..1,
1052 width: 100,
1053 height: 100,
1054 })
1055 .take(2)
1056 .collect();
1057 let image = RasterImage {
1058 metadata: ImageMetadata {
1059 width: 100,
1060 height: 100,
1061 },
1062 format: PixelFormat::BGRA8,
1063 id: None,
1064 bytes: Arc::new(vec![1]),
1065 frames: image_frames,
1066 cors_status: CorsStatus::Unsafe,
1067 loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1068 is_opaque: false,
1069 };
1070 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1071
1072 assert_eq!(image_animation_state.active_frame, 0);
1073 assert_eq!(image_animation_state.frame_start_time, 0.0);
1074 assert_eq!(
1075 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1076 true
1077 );
1078 assert_eq!(image_animation_state.active_frame, 1);
1079 assert_eq!(image_animation_state.frame_start_time, 0.101);
1080 assert_eq!(
1081 image_animation_state.update_frame_for_animation_timeline_value(0.202),
1082 false
1083 );
1084 assert_eq!(
1085 image_animation_state.update_frame_for_animation_timeline_value(0.303),
1086 false
1087 );
1088
1089 assert_eq!(image_animation_state.active_frame, 1);
1090 }
1091}