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};
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 net_traits::request::InternalRequest;
44use paint_api::CrossProcessPaintApi;
45use parking_lot::RwLock;
46use pixels::RasterImage;
47use profile_traits::mem::Report;
48use profile_traits::time;
49pub use pseudo_element_chain::PseudoElementChain;
50use rustc_hash::FxHashMap;
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 webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
77use webrender_api::{ExternalScrollId, ImageKey};
78
79pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait + Send + Sync + 'static {
80 fn as_any(&self) -> &dyn Any;
81}
82
83pub trait LayoutDataTrait: GenericLayoutDataTrait + Default {}
84pub type GenericLayoutData = dyn GenericLayoutDataTrait;
85
86#[derive(Default, MallocSizeOf)]
87pub struct StyleData {
88 pub element_data: ElementDataWrapper,
93
94 pub parallel: DomParallelInfo,
96}
97
98#[derive(Default, MallocSizeOf)]
100pub struct DomParallelInfo {
101 pub children_to_process: AtomicIsize,
103}
104
105#[derive(Clone, Copy, Debug, Eq, PartialEq)]
106pub enum LayoutNodeType {
107 Element(LayoutElementType),
108 Text,
109}
110
111#[derive(Clone, Copy, Debug, Eq, PartialEq)]
112pub enum LayoutElementType {
113 Element,
114 HTMLBodyElement,
115 HTMLBRElement,
116 HTMLCanvasElement,
117 HTMLHtmlElement,
118 HTMLIFrameElement,
119 HTMLImageElement,
120 HTMLInputElement,
121 HTMLMediaElement,
122 HTMLObjectElement,
123 HTMLOptGroupElement,
124 HTMLOptionElement,
125 HTMLParagraphElement,
126 HTMLPreElement,
127 HTMLSelectElement,
128 HTMLTableCellElement,
129 HTMLTableColElement,
130 HTMLTableElement,
131 HTMLTableRowElement,
132 HTMLTableSectionElement,
133 HTMLTextAreaElement,
134 SVGImageElement,
135 SVGSVGElement,
136}
137
138#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
142pub struct ScriptSelection {
143 pub range: TextByteRange,
145 pub character_range: Range<usize>,
147 pub enabled: bool,
150}
151
152pub type SharedSelection = Arc<AtomicRefCell<ScriptSelection>>;
153pub struct HTMLCanvasData {
154 pub image_key: Option<ImageKey>,
155 pub width: u32,
156 pub height: u32,
157}
158
159pub struct SVGElementData<'dom> {
160 pub source: Option<Result<ServoUrl, ()>>,
162 pub width: Option<&'dom AttrValue>,
163 pub height: Option<&'dom AttrValue>,
164 pub svg_id: String,
165 pub view_box: Option<&'dom AttrValue>,
166}
167
168impl SVGElementData<'_> {
169 pub fn ratio_from_view_box(&self) -> Option<f32> {
170 let mut iter = self.view_box?.chars();
171 let _min_x = parse_integer(&mut iter).ok()?;
172 let _min_y = parse_integer(&mut iter).ok()?;
173
174 let width = parse_unsigned_integer(&mut iter).ok()?;
175 if width == 0 {
176 return None;
177 }
178
179 let height = parse_unsigned_integer(&mut iter).ok()?;
180 if height == 0 {
181 return None;
182 }
183
184 let mut iter = iter.skip_while(|c| char_is_whitespace(*c));
185 iter.next().is_none().then(|| width as f32 / height as f32)
186 }
187}
188
189#[derive(Clone, Copy, Debug, Eq, PartialEq)]
191pub struct TrustedNodeAddress(pub *const c_void);
192
193#[expect(unsafe_code)]
194unsafe impl Send for TrustedNodeAddress {}
195
196#[derive(Debug)]
198pub enum PendingImageState {
199 Unrequested(ServoUrl),
200 PendingResponse,
201}
202
203#[derive(Debug, MallocSizeOf)]
205pub enum LayoutImageDestination {
206 BoxTreeConstruction,
207 DisplayListBuilding,
208}
209
210#[derive(Debug)]
214pub struct PendingImage {
215 pub state: PendingImageState,
216 pub node: UntrustedNodeAddress,
217 pub id: PendingImageId,
218 pub origin: ImmutableOrigin,
219 pub destination: LayoutImageDestination,
220 pub is_internal_request: InternalRequest,
221}
222
223#[derive(Debug)]
227pub struct PendingRasterizationImage {
228 pub node: UntrustedNodeAddress,
229 pub id: PendingImageId,
230 pub size: DeviceIntSize,
231}
232
233#[derive(Clone, Copy, Debug, MallocSizeOf)]
234pub struct MediaFrame {
235 pub image_key: webrender_api::ImageKey,
236 pub width: i32,
237 pub height: i32,
238}
239
240pub struct MediaMetadata {
241 pub width: u32,
242 pub height: u32,
243}
244
245pub struct HTMLMediaData {
246 pub current_frame: Option<MediaFrame>,
247 pub metadata: Option<MediaMetadata>,
248}
249
250pub struct LayoutConfig {
251 pub id: PipelineId,
252 pub webview_id: WebViewId,
253 pub url: ServoUrl,
254 pub is_iframe: bool,
255 pub script_chan: GenericSender<ScriptThreadMessage>,
256 pub image_cache: Arc<dyn ImageCache>,
257 pub font_context: Arc<FontContext>,
258 pub time_profiler_chan: time::ProfilerChan,
259 pub paint_api: CrossProcessPaintApi,
260 pub viewport_details: ViewportDetails,
261 pub user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
262 pub theme: Theme,
263 pub embedder_chan: ScriptToEmbedderChan,
264}
265
266pub trait LayoutFactory: Send + Sync {
267 fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
268}
269
270pub trait Layout {
271 fn device(&self) -> &Device;
274
275 fn set_theme(&mut self, theme: Theme) -> bool;
279
280 fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
284
285 fn load_web_fonts_from_stylesheet(
288 &self,
289 stylesheet: &ServoArc<Stylesheet>,
290 font_context: &WebFontDocumentContext,
291 );
292
293 fn add_stylesheet(
297 &mut self,
298 stylesheet: ServoArc<Stylesheet>,
299 before_stylsheet: Option<ServoArc<Stylesheet>>,
300 font_context: &WebFontDocumentContext,
301 );
302
303 fn exit_now(&mut self);
305
306 fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
309
310 fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
312
313 fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
315
316 fn remove_cached_image(&mut self, image_url: &ServoUrl);
318
319 fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
321
322 fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
325
326 fn register_paint_worklet_modules(
328 &mut self,
329 name: Atom,
330 properties: Vec<Atom>,
331 painter: Box<dyn Painter>,
332 );
333
334 fn set_scroll_offsets_from_renderer(
336 &mut self,
337 scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
338 );
339
340 fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
343
344 fn needs_new_display_list(&self) -> bool;
346
347 fn set_needs_new_display_list(&self);
349
350 fn node_rendering_type(
353 &self,
354 node: TrustedNodeAddress,
355 pseudo: Option<PseudoElement>,
356 ) -> NodeRenderingType;
357
358 fn query_containing_block(&self, node: TrustedNodeAddress) -> Option<UntrustedNodeAddress>;
359 fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
360 fn query_box_area(
361 &self,
362 node: TrustedNodeAddress,
363 area: BoxAreaType,
364 exclude_transform_and_inline: bool,
365 ) -> Option<Rect<Au, CSSPixel>>;
366 fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectVec;
367 fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
368 fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
369 fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
370 fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
371 fn query_scroll_container(
374 &self,
375 node: Option<TrustedNodeAddress>,
376 flags: ScrollContainerQueryFlags,
377 ) -> Option<ScrollContainerResponse>;
378 fn query_resolved_style(
379 &self,
380 node: TrustedNodeAddress,
381 pseudo: Option<PseudoElement>,
382 property_id: PropertyId,
383 animations: DocumentAnimationSet,
384 animation_timeline_value: f64,
385 ) -> String;
386 fn query_resolved_font_style(
387 &self,
388 node: TrustedNodeAddress,
389 value: &str,
390 animations: DocumentAnimationSet,
391 animation_timeline_value: f64,
392 ) -> Option<ServoArc<Font>>;
393 fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
394 fn query_text_index(
396 &self,
397 node: TrustedNodeAddress,
398 point: Point2D<Au, CSSPixel>,
399 ) -> Option<usize>;
400 fn query_elements_from_point(
401 &self,
402 point: LayoutPoint,
403 flags: ElementsFromPointFlags,
404 ) -> Vec<ElementsFromPointResult>;
405 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
406 fn stylist_mut(&mut self) -> &mut Stylist;
407
408 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
411
412 fn needs_accessibility_update(&self) -> bool;
423
424 fn set_needs_accessibility_update(&self);
426}
427
428pub trait ScriptThreadFactory {
432 fn create(
434 state: InitialScriptState,
435 layout_factory: Arc<dyn LayoutFactory>,
436 image_cache_factory: Arc<dyn ImageCacheFactory>,
437 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
438 ) -> JoinHandle<()>;
439}
440
441#[derive(Copy, Clone)]
444pub enum BoxAreaType {
445 Content,
446 Padding,
447 Border,
448}
449
450pub type CSSPixelRectVec = Vec<Rect<Au, CSSPixel>>;
451
452#[derive(Copy, Clone)]
455pub enum NodeRenderingType {
456 Rendered,
458 DelegatesRendering,
460 NotRendered,
463}
464
465#[derive(Default)]
466pub struct PhysicalSides {
467 pub left: Au,
468 pub top: Au,
469 pub right: Au,
470 pub bottom: Au,
471}
472
473#[derive(Clone, Default)]
474pub struct OffsetParentResponse {
475 pub node_address: Option<UntrustedNodeAddress>,
476 pub rect: Rect<Au, CSSPixel>,
477}
478
479bitflags! {
480 #[derive(PartialEq)]
481 pub struct ScrollContainerQueryFlags: u8 {
482 const ForScrollParent = 1 << 0;
484 const Inclusive = 1 << 1;
486 }
487}
488
489#[derive(Clone, Copy, Debug, MallocSizeOf)]
490pub struct AxesOverflow {
491 pub x: Overflow,
492 pub y: Overflow,
493}
494
495impl Default for AxesOverflow {
496 fn default() -> Self {
497 Self {
498 x: Overflow::Visible,
499 y: Overflow::Visible,
500 }
501 }
502}
503
504impl From<&ComputedValues> for AxesOverflow {
505 fn from(style: &ComputedValues) -> Self {
506 Self {
507 x: style.clone_overflow_x(),
508 y: style.clone_overflow_y(),
509 }
510 }
511}
512
513impl AxesOverflow {
514 pub fn to_scrollable(&self) -> Self {
515 Self {
516 x: self.x.to_scrollable(),
517 y: self.y.to_scrollable(),
518 }
519 }
520
521 pub fn establishes_scroll_container(&self) -> bool {
523 self.x.is_scrollable()
526 }
527}
528
529#[derive(Clone)]
530pub enum ScrollContainerResponse {
531 Viewport(AxesOverflow),
532 Element(UntrustedNodeAddress, AxesOverflow),
533}
534
535#[derive(Debug, PartialEq)]
536pub enum QueryMsg {
537 BoxArea,
538 BoxAreas,
539 ClientRectQuery,
540 CurrentCSSZoomQuery,
541 EffectiveOverflow,
542 ElementInnerOuterTextQuery,
543 ElementsFromPoint,
544 InnerWindowDimensionsQuery,
545 NodesFromPointQuery,
546 OffsetParentQuery,
547 ScrollParentQuery,
548 ResolvedFontStyleQuery,
549 ResolvedStyleQuery,
550 ScrollingAreaOrOffsetQuery,
551 StyleQuery,
552 TextIndexQuery,
553 PaddingQuery,
554}
555
556#[derive(Debug, PartialEq)]
562pub enum ReflowGoal {
563 UpdateTheRendering,
566
567 LayoutQuery(QueryMsg),
570
571 UpdateScrollNode(ExternalScrollId, LayoutVector2D),
575}
576
577#[derive(Clone, Debug, MallocSizeOf)]
578pub struct IFrameSize {
579 pub browsing_context_id: BrowsingContextId,
580 pub pipeline_id: PipelineId,
581 pub viewport_details: ViewportDetails,
582}
583
584pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
585
586bitflags! {
587 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
590 pub struct RestyleReason: u16 {
591 const StylesheetsChanged = 1 << 0;
592 const DOMChanged = 1 << 1;
593 const PendingRestyles = 1 << 2;
594 const HighlightedDOMNodeChanged = 1 << 3;
595 const ThemeChanged = 1 << 4;
596 const ViewportChanged = 1 << 5;
597 const PaintWorkletLoaded = 1 << 6;
598 }
599}
600
601malloc_size_of_is_0!(RestyleReason);
602
603impl RestyleReason {
604 pub fn needs_restyle(&self) -> bool {
605 !self.is_empty()
606 }
607}
608
609#[derive(Debug, Default)]
611pub struct ReflowResult {
612 pub reflow_phases_run: ReflowPhasesRun,
614 pub reflow_statistics: ReflowStatistics,
615 pub pending_images: Vec<PendingImage>,
617 pub pending_rasterization_images: Vec<PendingRasterizationImage>,
619 pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
623 pub iframe_sizes: Option<IFrameSizes>,
629}
630
631bitflags! {
632 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
634 pub struct ReflowPhasesRun: u8 {
635 const RanLayout = 1 << 0;
636 const BuiltStackingContextTree = 1 << 2;
637 const BuiltDisplayList = 1 << 3;
638 const UpdatedScrollNodeOffset = 1 << 4;
639 const UpdatedImageData = 1 << 5;
643 const UpdatedAccessibilityTree = 1 << 6;
644 }
645}
646
647impl ReflowPhasesRun {
648 pub fn needs_frame(&self) -> bool {
649 self.intersects(
650 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
651 )
652 }
653}
654
655#[derive(Debug, Default)]
656pub struct ReflowStatistics {
657 pub rebuilt_fragment_count: u32,
659 pub restyle_fragment_count: u32,
661 pub only_descendants_changed_count: u32,
664}
665
666#[derive(Debug)]
669pub struct ReflowRequestRestyle {
670 pub reason: RestyleReason,
672 pub dirty_root: Option<TrustedNodeAddress>,
674 pub stylesheets_changed: bool,
676 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
678}
679
680#[derive(Debug)]
682pub struct ReflowRequest {
683 pub document: TrustedNodeAddress,
685 pub epoch: Epoch,
687 pub restyle: Option<ReflowRequestRestyle>,
689 pub viewport_details: ViewportDetails,
691 pub reflow_goal: ReflowGoal,
693 pub origin: ImmutableOrigin,
695 pub animation_timeline_value: f64,
697 pub animations: DocumentAnimationSet,
699 pub animating_images: Arc<RwLock<AnimatingImages>>,
701 pub highlighted_dom_node: Option<OpaqueNode>,
703 pub document_context: WebFontDocumentContext,
705}
706
707impl ReflowRequest {
708 pub fn stylesheets_changed(&self) -> bool {
709 self.restyle
710 .as_ref()
711 .is_some_and(|restyle| restyle.stylesheets_changed)
712 }
713}
714
715#[derive(Debug, Default, MallocSizeOf)]
717pub struct PendingRestyle {
718 pub snapshot: Option<Snapshot>,
721
722 pub hint: RestyleHint,
724
725 pub damage: RestyleDamage,
727}
728
729#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
735pub enum FragmentType {
736 FragmentBody,
738 BeforePseudoContent,
740 AfterPseudoContent,
742}
743
744impl From<Option<PseudoElement>> for FragmentType {
745 fn from(value: Option<PseudoElement>) -> Self {
746 match value {
747 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
748 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
749 _ => FragmentType::FragmentBody,
750 }
751 }
752}
753
754pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
755 debug_assert_eq!(id & (fragment_type as usize), 0);
756 (id as u64) | (fragment_type as u64)
757}
758
759pub fn node_id_from_scroll_id(id: usize) -> usize {
760 id & !3
761}
762
763#[derive(Clone, Debug, MallocSizeOf)]
764pub struct ImageAnimationState {
765 #[conditional_malloc_size_of]
766 pub image: Arc<RasterImage>,
767 pub active_frame: usize,
768 frame_start_time: f64,
769}
770
771impl ImageAnimationState {
772 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
773 Self {
774 image,
775 active_frame: 0,
776 frame_start_time: last_update_time,
777 }
778 }
779
780 pub fn image_key(&self) -> Option<ImageKey> {
781 self.image.id
782 }
783
784 pub fn duration_to_next_frame(&self, now: f64) -> Duration {
785 let frame_delay = self
786 .image
787 .frames
788 .get(self.active_frame)
789 .expect("Image frame should always be valid")
790 .delay
791 .unwrap_or_default();
792
793 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
794 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
795 frame_delay - time_since_frame_start.min(frame_delay)
796 }
797
798 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
802 if self.image.frames.len() <= 1 {
803 return false;
804 }
805 let image = &self.image;
806 let time_interval_since_last_update = now - self.frame_start_time;
807 let mut remain_time_interval = time_interval_since_last_update -
808 image
809 .frames
810 .get(self.active_frame)
811 .unwrap()
812 .delay()
813 .unwrap()
814 .as_secs_f64();
815 let mut next_active_frame_id = self.active_frame;
816 while remain_time_interval > 0.0 {
817 next_active_frame_id = (next_active_frame_id + 1) % image.frames.len();
818 remain_time_interval -= image
819 .frames
820 .get(next_active_frame_id)
821 .unwrap()
822 .delay()
823 .unwrap()
824 .as_secs_f64();
825 }
826 if self.active_frame == next_active_frame_id {
827 return false;
828 }
829 self.active_frame = next_active_frame_id;
830 self.frame_start_time = now;
831 true
832 }
833}
834
835#[derive(Debug)]
837pub struct ElementsFromPointResult {
838 pub node: OpaqueNode,
841 pub point_in_target: Point2D<f32, CSSPixel>,
844 pub cursor: Cursor,
847}
848
849bitflags! {
850 pub struct ElementsFromPointFlags: u8 {
851 const FindAll = 0b00000001;
854 }
855}
856
857#[derive(Debug, Default, MallocSizeOf)]
858pub struct AnimatingImages {
859 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
862 pub dirty: bool,
865}
866
867impl AnimatingImages {
868 pub fn maybe_insert_or_update(
869 &mut self,
870 node: OpaqueNode,
871 image: Arc<RasterImage>,
872 current_timeline_value: f64,
873 ) {
874 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
875 self.dirty = true;
876 ImageAnimationState::new(image.clone(), current_timeline_value)
877 });
878
879 if entry.image.id != image.id {
882 self.dirty = true;
883 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
884 }
885 }
886
887 pub fn remove(&mut self, node: OpaqueNode) {
888 if self.node_to_state_map.remove(&node).is_some() {
889 self.dirty = true;
890 }
891 }
892
893 pub fn clear_dirty(&mut self) -> bool {
895 std::mem::take(&mut self.dirty)
896 }
897
898 pub fn is_empty(&self) -> bool {
899 self.node_to_state_map.is_empty()
900 }
901}
902
903struct ThreadStateRestorer;
904
905impl ThreadStateRestorer {
906 fn new() -> Self {
907 #[cfg(debug_assertions)]
908 {
909 thread_state::exit(ThreadState::SCRIPT);
910 thread_state::enter(ThreadState::LAYOUT);
911 }
912 Self
913 }
914}
915
916impl Drop for ThreadStateRestorer {
917 fn drop(&mut self) {
918 #[cfg(debug_assertions)]
919 {
920 thread_state::exit(ThreadState::LAYOUT);
921 thread_state::enter(ThreadState::SCRIPT);
922 }
923 }
924}
925
926pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
932 let _guard = ThreadStateRestorer::new();
933 f()
934}
935
936#[cfg(test)]
937mod test {
938 use std::sync::Arc;
939 use std::time::Duration;
940
941 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage};
942
943 use crate::ImageAnimationState;
944
945 #[test]
946 fn test() {
947 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
948 delay: Some(Duration::from_millis(100)),
949 byte_range: 0..1,
950 width: 100,
951 height: 100,
952 })
953 .take(10)
954 .collect();
955 let image = RasterImage {
956 metadata: ImageMetadata {
957 width: 100,
958 height: 100,
959 },
960 format: PixelFormat::BGRA8,
961 id: None,
962 bytes: Arc::new(vec![1]),
963 frames: image_frames,
964 cors_status: CorsStatus::Unsafe,
965 is_opaque: false,
966 };
967 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
968
969 assert_eq!(image_animation_state.active_frame, 0);
970 assert_eq!(image_animation_state.frame_start_time, 0.0);
971 assert_eq!(
972 image_animation_state.update_frame_for_animation_timeline_value(0.101),
973 true
974 );
975 assert_eq!(image_animation_state.active_frame, 1);
976 assert_eq!(image_animation_state.frame_start_time, 0.101);
977 assert_eq!(
978 image_animation_state.update_frame_for_animation_timeline_value(0.116),
979 false
980 );
981 assert_eq!(image_animation_state.active_frame, 1);
982 assert_eq!(image_animation_state.frame_start_time, 0.101);
983 }
984}