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 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 pub element_data: ElementDataWrapper,
91
92 pub parallel: DomParallelInfo,
94}
95
96#[derive(Default, MallocSizeOf)]
98pub struct DomParallelInfo {
99 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#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
140pub struct ScriptSelection {
141 pub range: TextByteRange,
143 pub character_range: Range<usize>,
145 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 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#[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#[derive(Debug)]
196pub enum PendingImageState {
197 Unrequested(ServoUrl),
198 PendingResponse,
199}
200
201#[derive(Debug, MallocSizeOf)]
203pub enum LayoutImageDestination {
204 BoxTreeConstruction,
205 DisplayListBuilding,
206}
207
208#[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#[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 fn device(&self) -> &Device;
271
272 fn set_theme(&mut self, theme: Theme) -> bool;
276
277 fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
281
282 fn load_web_fonts_from_stylesheet(
285 &self,
286 stylesheet: &ServoArc<Stylesheet>,
287 font_context: &WebFontDocumentContext,
288 );
289
290 fn add_stylesheet(
294 &mut self,
295 stylesheet: ServoArc<Stylesheet>,
296 before_stylsheet: Option<ServoArc<Stylesheet>>,
297 font_context: &WebFontDocumentContext,
298 );
299
300 fn exit_now(&mut self);
302
303 fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
306
307 fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
309
310 fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
312
313 fn remove_cached_image(&mut self, image_url: &ServoUrl);
315
316 fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
318
319 fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
322
323 fn register_paint_worklet_modules(
325 &mut self,
326 name: Atom,
327 properties: Vec<Atom>,
328 painter: Box<dyn Painter>,
329 );
330
331 fn set_scroll_offsets_from_renderer(
333 &mut self,
334 scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
335 );
336
337 fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
340
341 fn needs_new_display_list(&self) -> bool;
343
344 fn set_needs_new_display_list(&self);
346
347 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 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 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 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
408
409 fn needs_accessibility_update(&self) -> bool;
420
421 fn set_needs_accessibility_update(&self);
423}
424
425pub trait ScriptThreadFactory {
429 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#[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#[derive(Copy, Clone)]
452pub enum NodeRenderingType {
453 Rendered,
455 DelegatesRendering,
457 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 const ForScrollParent = 1 << 0;
481 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 pub fn establishes_scroll_container(&self) -> bool {
520 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#[derive(Debug, PartialEq)]
559pub enum ReflowGoal {
560 UpdateTheRendering,
563
564 LayoutQuery(QueryMsg),
567
568 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 #[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#[derive(Debug, Default)]
608pub struct ReflowResult {
609 pub reflow_phases_run: ReflowPhasesRun,
611 pub reflow_statistics: ReflowStatistics,
612 pub pending_images: Vec<PendingImage>,
614 pub pending_rasterization_images: Vec<PendingRasterizationImage>,
616 pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
620 pub iframe_sizes: Option<IFrameSizes>,
626}
627
628bitflags! {
629 #[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 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 pub rebuilt_fragment_count: u32,
657 pub restyle_fragment_count: u32,
659 pub possibly_moved_fragment_count: u32,
662}
663
664#[derive(Debug)]
667pub struct ReflowRequestRestyle {
668 pub reason: RestyleReason,
670 pub dirty_root: Option<TrustedNodeAddress>,
672 pub stylesheets_changed: bool,
674 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
676}
677
678#[derive(Debug)]
680pub struct ReflowRequest {
681 pub document: TrustedNodeAddress,
683 pub epoch: Epoch,
685 pub restyle: Option<ReflowRequestRestyle>,
687 pub viewport_details: ViewportDetails,
689 pub reflow_goal: ReflowGoal,
691 pub origin: ImmutableOrigin,
693 pub animation_timeline_value: f64,
695 pub animations: DocumentAnimationSet,
697 pub animating_images: Arc<RwLock<AnimatingImages>>,
699 pub highlighted_dom_node: Option<OpaqueNode>,
701 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#[derive(Debug, Default, MallocSizeOf)]
715pub struct PendingRestyle {
716 pub snapshot: Option<Snapshot>,
719
720 pub hint: RestyleHint,
722
723 pub damage: RestyleDamage,
725}
726
727#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
733pub enum FragmentType {
734 FragmentBody,
736 BeforePseudoContent,
738 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 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#[derive(Debug)]
835pub struct ElementsFromPointResult {
836 pub node: OpaqueNode,
839 pub point_in_target: Point2D<f32, CSSPixel>,
842 pub cursor: Cursor,
845}
846
847bitflags! {
848 pub struct ElementsFromPointFlags: u8 {
849 const FindAll = 0b00000001;
852 }
853}
854
855#[derive(Debug, Default, MallocSizeOf)]
856pub struct AnimatingImages {
857 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
860 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 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 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
924pub 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}