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, Repeat};
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(&self, point: LayoutPoint) -> Vec<ElementsFromPointResult>;
401 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
402 fn stylist_mut(&mut self) -> &mut Stylist;
403
404 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
407
408 fn accessibility_active(&self) -> bool;
410
411 fn needs_accessibility_update(&self) -> bool;
422
423 fn set_needs_accessibility_update(&self);
425}
426
427pub trait ScriptThreadFactory {
431 fn create(
433 state: InitialScriptState,
434 layout_factory: Arc<dyn LayoutFactory>,
435 image_cache_factory: Arc<dyn ImageCacheFactory>,
436 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
437 ) -> JoinHandle<()>;
438}
439
440#[derive(Copy, Clone)]
443pub enum BoxAreaType {
444 Content,
445 Padding,
446 Border,
447}
448
449pub type CSSPixelRectVec = Vec<Rect<Au, CSSPixel>>;
450
451#[derive(Copy, Clone)]
454pub enum NodeRenderingType {
455 Rendered,
457 DelegatesRendering,
459 NotRendered,
462}
463
464#[derive(Default)]
465pub struct PhysicalSides {
466 pub left: Au,
467 pub top: Au,
468 pub right: Au,
469 pub bottom: Au,
470}
471
472#[derive(Clone, Default)]
473pub struct OffsetParentResponse {
474 pub node_address: Option<UntrustedNodeAddress>,
475 pub rect: Rect<Au, CSSPixel>,
476}
477
478bitflags! {
479 #[derive(PartialEq)]
480 pub struct ScrollContainerQueryFlags: u8 {
481 const ForScrollParent = 1 << 0;
483 const Inclusive = 1 << 1;
485 }
486}
487
488#[derive(Clone, Copy, Debug, MallocSizeOf)]
489pub struct AxesOverflow {
490 pub x: Overflow,
491 pub y: Overflow,
492}
493
494impl Default for AxesOverflow {
495 fn default() -> Self {
496 Self {
497 x: Overflow::Visible,
498 y: Overflow::Visible,
499 }
500 }
501}
502
503impl From<&ComputedValues> for AxesOverflow {
504 fn from(style: &ComputedValues) -> Self {
505 Self {
506 x: style.clone_overflow_x(),
507 y: style.clone_overflow_y(),
508 }
509 }
510}
511
512impl AxesOverflow {
513 pub fn to_scrollable(&self) -> Self {
514 Self {
515 x: self.x.to_scrollable(),
516 y: self.y.to_scrollable(),
517 }
518 }
519
520 pub fn establishes_scroll_container(&self) -> bool {
522 self.x.is_scrollable()
525 }
526}
527
528#[derive(Clone)]
529pub enum ScrollContainerResponse {
530 Viewport(AxesOverflow),
531 Element(UntrustedNodeAddress, AxesOverflow),
532}
533
534#[derive(Debug, PartialEq)]
535pub enum QueryMsg {
536 BoxArea,
537 BoxAreas,
538 ClientRectQuery,
539 CurrentCSSZoomQuery,
540 EffectiveOverflow,
541 ElementInnerOuterTextQuery,
542 ElementsFromPoint,
543 InnerWindowDimensionsQuery,
544 NodesFromPointQuery,
545 OffsetParentQuery,
546 ScrollParentQuery,
547 ResolvedFontStyleQuery,
548 ResolvedStyleQuery,
549 ScrollingAreaOrOffsetQuery,
550 StyleQuery,
551 TextIndexQuery,
552 PaddingQuery,
553}
554
555#[derive(Debug, PartialEq)]
561pub enum ReflowGoal {
562 UpdateTheRendering,
565
566 LayoutQuery(QueryMsg),
569
570 UpdateScrollNode(ExternalScrollId, LayoutVector2D),
574}
575
576#[derive(Clone, Debug, MallocSizeOf)]
577pub struct IFrameSize {
578 pub browsing_context_id: BrowsingContextId,
579 pub pipeline_id: PipelineId,
580 pub viewport_details: ViewportDetails,
581}
582
583pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
584
585bitflags! {
586 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
589 pub struct RestyleReason: u16 {
590 const StylesheetsChanged = 1 << 0;
591 const DOMChanged = 1 << 1;
592 const PendingRestyles = 1 << 2;
593 const HighlightedDOMNodeChanged = 1 << 3;
594 const ThemeChanged = 1 << 4;
595 const ViewportChanged = 1 << 5;
596 const PaintWorkletLoaded = 1 << 6;
597 }
598}
599
600malloc_size_of_is_0!(RestyleReason);
601
602impl RestyleReason {
603 pub fn needs_restyle(&self) -> bool {
604 !self.is_empty()
605 }
606}
607
608#[derive(Debug, Default)]
610pub struct ReflowResult {
611 pub reflow_phases_run: ReflowPhasesRun,
613 pub reflow_statistics: ReflowStatistics,
614 pub pending_images: Vec<PendingImage>,
616 pub pending_rasterization_images: Vec<PendingRasterizationImage>,
618 pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
622 pub iframe_sizes: Option<IFrameSizes>,
628}
629
630bitflags! {
631 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
633 pub struct ReflowPhasesRun: u8 {
634 const RanLayout = 1 << 0;
635 const BuiltStackingContextTree = 1 << 2;
636 const BuiltDisplayList = 1 << 3;
637 const UpdatedScrollNodeOffset = 1 << 4;
638 const UpdatedImageData = 1 << 5;
642 const UpdatedAccessibilityTree = 1 << 6;
643 }
644}
645
646impl ReflowPhasesRun {
647 pub fn needs_frame(&self) -> bool {
648 self.intersects(
649 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
650 )
651 }
652}
653
654#[derive(Debug, Default)]
655pub struct ReflowStatistics {
656 pub rebuilt_fragment_count: u32,
658 pub restyle_fragment_count: u32,
660 pub only_descendants_changed_count: u32,
663}
664
665#[derive(Debug)]
668pub struct ReflowRequestRestyle {
669 pub reason: RestyleReason,
671 pub dirty_root: Option<TrustedNodeAddress>,
673 pub stylesheets_changed: bool,
675 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
677}
678
679#[derive(Debug)]
681pub struct ReflowRequest {
682 pub document: TrustedNodeAddress,
684 pub epoch: Epoch,
686 pub restyle: Option<ReflowRequestRestyle>,
688 pub viewport_details: ViewportDetails,
690 pub reflow_goal: ReflowGoal,
692 pub origin: ImmutableOrigin,
694 pub animation_timeline_value: f64,
696 pub animations: DocumentAnimationSet,
698 pub animating_images: Arc<RwLock<AnimatingImages>>,
700 pub highlighted_dom_node: Option<OpaqueNode>,
702 pub document_context: WebFontDocumentContext,
704}
705
706impl ReflowRequest {
707 pub fn stylesheets_changed(&self) -> bool {
708 self.restyle
709 .as_ref()
710 .is_some_and(|restyle| restyle.stylesheets_changed)
711 }
712}
713
714#[derive(Debug, Default, MallocSizeOf)]
716pub struct PendingRestyle {
717 pub snapshot: Option<Snapshot>,
720
721 pub hint: RestyleHint,
723
724 pub damage: RestyleDamage,
726}
727
728#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
734pub enum FragmentType {
735 FragmentBody,
737 BeforePseudoContent,
739 AfterPseudoContent,
741}
742
743impl From<Option<PseudoElement>> for FragmentType {
744 fn from(value: Option<PseudoElement>) -> Self {
745 match value {
746 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
747 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
748 _ => FragmentType::FragmentBody,
749 }
750 }
751}
752
753pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
754 debug_assert_eq!(id & (fragment_type as usize), 0);
755 (id as u64) | (fragment_type as u64)
756}
757
758pub fn node_id_from_scroll_id(id: usize) -> usize {
759 id & !3
760}
761
762#[derive(Clone, Debug, MallocSizeOf)]
763pub struct ImageAnimationState {
764 #[conditional_malloc_size_of]
765 pub image: Arc<RasterImage>,
766 pub active_frame: usize,
767 frame_start_time: f64,
768
769 pub completed_loops: Option<u32>,
774}
775
776impl ImageAnimationState {
777 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
778 let completd_loops = match &image.loop_count {
779 None => unreachable!("Loop count of an animated Image should never be None"),
780 Some(repeat) if Repeat::Infinite == *repeat => None,
781 _ => Some(0),
782 };
783
784 Self {
785 image,
786 active_frame: 0,
787 frame_start_time: last_update_time,
788 completed_loops: completd_loops,
789 }
790 }
791
792 pub fn image_key(&self) -> Option<ImageKey> {
793 self.image.id
794 }
795
796 pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
797 if self.is_finished() {
798 return None;
799 }
800 let frame_delay = self
801 .image
802 .frames
803 .get(self.active_frame)
804 .expect("Image frame should always be valid")
805 .delay
806 .unwrap_or_default();
807
808 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
809 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
810 Some(frame_delay - time_since_frame_start.min(frame_delay))
811 }
812
813 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
817 if self.image.frames.len() <= 1 || self.is_finished() {
818 return false;
819 }
820 let time_interval_since_last_update = now - self.frame_start_time;
821 let mut remain_time_interval = time_interval_since_last_update -
822 self.image
823 .frames
824 .get(self.active_frame)
825 .unwrap()
826 .delay()
827 .unwrap()
828 .as_secs_f64();
829 let mut next_active_frame_id = self.active_frame;
830
831 let frame_count = self.image.frames.len();
832 while remain_time_interval > 0.0 {
833 next_active_frame_id = (next_active_frame_id + 1) % frame_count;
834
835 if next_active_frame_id == 0 {
837 self.advance_completed_loops();
838
839 if self.is_finished() {
842 if self.active_frame == frame_count - 1 {
843 return false;
844 }
845 self.active_frame = frame_count - 1;
846 self.frame_start_time = now;
847 return true;
848 }
849 }
850
851 remain_time_interval -= self
852 .image
853 .frames
854 .get(next_active_frame_id)
855 .unwrap()
856 .delay()
857 .unwrap()
858 .as_secs_f64();
859 }
860 if self.active_frame == next_active_frame_id {
861 return false;
862 }
863 self.active_frame = next_active_frame_id;
864 self.frame_start_time = now;
865 true
866 }
867
868 fn is_finished(&self) -> bool {
870 let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
871 return false;
872 };
873 self.completed_loops
874 .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
875 }
876
877 fn advance_completed_loops(&mut self) {
879 if let Some(completed_loops) = self.completed_loops.as_mut() {
880 *completed_loops += 1;
881 }
882 }
883}
884
885#[derive(Debug)]
887pub struct ElementsFromPointResult {
888 pub node: OpaqueNode,
891 pub point_in_target: Point2D<f32, CSSPixel>,
894 pub cursor: Cursor,
897}
898
899#[derive(Debug, Default, MallocSizeOf)]
900pub struct AnimatingImages {
901 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
904 pub dirty: bool,
907}
908
909impl AnimatingImages {
910 pub fn maybe_insert_or_update(
911 &mut self,
912 node: OpaqueNode,
913 image: Arc<RasterImage>,
914 current_timeline_value: f64,
915 ) {
916 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
917 self.dirty = true;
918 ImageAnimationState::new(image.clone(), current_timeline_value)
919 });
920
921 if entry.image.id != image.id {
924 self.dirty = true;
925 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
926 }
927 }
928
929 pub fn remove(&mut self, node: OpaqueNode) {
930 if self.node_to_state_map.remove(&node).is_some() {
931 self.dirty = true;
932 }
933 }
934
935 pub fn clear_dirty(&mut self) -> bool {
937 std::mem::take(&mut self.dirty)
938 }
939
940 pub fn is_empty(&self) -> bool {
941 self.node_to_state_map.is_empty()
942 }
943}
944
945struct ThreadStateRestorer;
946
947impl ThreadStateRestorer {
948 fn new() -> Self {
949 #[cfg(debug_assertions)]
950 {
951 thread_state::exit(ThreadState::SCRIPT);
952 thread_state::enter(ThreadState::LAYOUT);
953 }
954 Self
955 }
956}
957
958impl Drop for ThreadStateRestorer {
959 fn drop(&mut self) {
960 #[cfg(debug_assertions)]
961 {
962 thread_state::exit(ThreadState::LAYOUT);
963 thread_state::enter(ThreadState::SCRIPT);
964 }
965 }
966}
967
968pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
974 let _guard = ThreadStateRestorer::new();
975 f()
976}
977
978#[cfg(test)]
979mod test {
980 use std::num::NonZeroU32;
981 use std::sync::Arc;
982 use std::time::Duration;
983
984 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
985
986 use crate::ImageAnimationState;
987
988 #[test]
989 fn test_animated_image_update() {
990 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
991 delay: Some(Duration::from_millis(100)),
992 byte_range: 0..1,
993 width: 100,
994 height: 100,
995 })
996 .take(10)
997 .collect();
998 let image = RasterImage {
999 metadata: ImageMetadata {
1000 width: 100,
1001 height: 100,
1002 },
1003 format: PixelFormat::BGRA8,
1004 id: None,
1005 bytes: Arc::new(vec![1]),
1006 frames: image_frames,
1007 cors_status: CorsStatus::Unsafe,
1008 loop_count: Some(Repeat::Infinite),
1009 is_opaque: false,
1010 };
1011 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1012
1013 assert_eq!(image_animation_state.active_frame, 0);
1014 assert_eq!(image_animation_state.frame_start_time, 0.0);
1015 assert_eq!(
1016 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1017 true
1018 );
1019 assert_eq!(image_animation_state.active_frame, 1);
1020 assert_eq!(image_animation_state.frame_start_time, 0.101);
1021 assert_eq!(
1022 image_animation_state.update_frame_for_animation_timeline_value(0.116),
1023 false
1024 );
1025 assert_eq!(image_animation_state.active_frame, 1);
1026 assert_eq!(image_animation_state.frame_start_time, 0.101);
1027 }
1028
1029 #[test]
1030 fn test_finite_image_repeat() {
1031 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1032 delay: Some(Duration::from_millis(100)),
1033 byte_range: 0..1,
1034 width: 100,
1035 height: 100,
1036 })
1037 .take(2)
1038 .collect();
1039 let image = RasterImage {
1040 metadata: ImageMetadata {
1041 width: 100,
1042 height: 100,
1043 },
1044 format: PixelFormat::BGRA8,
1045 id: None,
1046 bytes: Arc::new(vec![1]),
1047 frames: image_frames,
1048 cors_status: CorsStatus::Unsafe,
1049 loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1050 is_opaque: false,
1051 };
1052 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1053
1054 assert_eq!(image_animation_state.active_frame, 0);
1055 assert_eq!(image_animation_state.frame_start_time, 0.0);
1056 assert_eq!(
1057 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1058 true
1059 );
1060 assert_eq!(image_animation_state.active_frame, 1);
1061 assert_eq!(image_animation_state.frame_start_time, 0.101);
1062 assert_eq!(
1063 image_animation_state.update_frame_for_animation_timeline_value(0.202),
1064 false
1065 );
1066 assert_eq!(
1067 image_animation_state.update_frame_for_animation_timeline_value(0.303),
1068 false
1069 );
1070
1071 assert_eq!(image_animation_state.active_frame, 1);
1072 }
1073}