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, 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 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_containing_block_is_descendant(
360 &self,
361 root: TrustedNodeAddress,
362 possible_descendant: TrustedNodeAddress,
363 ) -> bool;
364 fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
365 fn query_box_area(
366 &self,
367 node: TrustedNodeAddress,
368 area: BoxAreaType,
369 exclude_transform_and_inline: bool,
370 ) -> Option<Rect<Au, CSSPixel>>;
371 fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectVec;
372 fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
373 fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
374 fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
375 fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
376 fn query_scroll_container(
379 &self,
380 node: Option<TrustedNodeAddress>,
381 flags: ScrollContainerQueryFlags,
382 ) -> Option<ScrollContainerResponse>;
383 fn query_resolved_style(
384 &self,
385 node: TrustedNodeAddress,
386 pseudo: Option<PseudoElement>,
387 property_id: PropertyId,
388 animations: DocumentAnimationSet,
389 animation_timeline_value: f64,
390 ) -> String;
391 fn query_resolved_font_style(
392 &self,
393 node: TrustedNodeAddress,
394 value: &str,
395 animations: DocumentAnimationSet,
396 animation_timeline_value: f64,
397 ) -> Option<ServoArc<Font>>;
398 fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
399 fn query_text_index(
401 &self,
402 node: TrustedNodeAddress,
403 point: Point2D<Au, CSSPixel>,
404 ) -> Option<usize>;
405 fn query_elements_from_point(&self, point: LayoutPoint) -> Vec<ElementsFromPointResult>;
406 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
407 fn stylist_mut(&mut self) -> &mut Stylist;
408
409 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
412
413 fn accessibility_active(&self) -> bool;
415
416 fn needs_accessibility_update(&self) -> bool;
427
428 fn set_needs_accessibility_update(&self);
430}
431
432pub trait ScriptThreadFactory {
436 fn create(
438 state: InitialScriptState,
439 layout_factory: Arc<dyn LayoutFactory>,
440 image_cache_factory: Arc<dyn ImageCacheFactory>,
441 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
442 ) -> JoinHandle<()>;
443}
444
445#[derive(Copy, Clone)]
448pub enum BoxAreaType {
449 Content,
450 Padding,
451 Border,
452}
453
454pub type CSSPixelRectVec = Vec<Rect<Au, CSSPixel>>;
455
456#[derive(Copy, Clone)]
459pub enum NodeRenderingType {
460 Rendered,
462 DelegatesRendering,
464 NotRendered,
467}
468
469#[derive(Default)]
470pub struct PhysicalSides {
471 pub left: Au,
472 pub top: Au,
473 pub right: Au,
474 pub bottom: Au,
475}
476
477#[derive(Clone, Default)]
478pub struct OffsetParentResponse {
479 pub node_address: Option<UntrustedNodeAddress>,
480 pub rect: Rect<Au, CSSPixel>,
481}
482
483bitflags! {
484 #[derive(PartialEq)]
485 pub struct ScrollContainerQueryFlags: u8 {
486 const ForScrollParent = 1 << 0;
488 const Inclusive = 1 << 1;
490 }
491}
492
493#[derive(Clone, Copy, Debug, MallocSizeOf)]
494pub struct AxesOverflow {
495 pub x: Overflow,
496 pub y: Overflow,
497}
498
499impl Default for AxesOverflow {
500 fn default() -> Self {
501 Self {
502 x: Overflow::Visible,
503 y: Overflow::Visible,
504 }
505 }
506}
507
508impl From<&ComputedValues> for AxesOverflow {
509 fn from(style: &ComputedValues) -> Self {
510 Self {
511 x: style.clone_overflow_x(),
512 y: style.clone_overflow_y(),
513 }
514 }
515}
516
517impl AxesOverflow {
518 pub fn to_scrollable(&self) -> Self {
519 Self {
520 x: self.x.to_scrollable(),
521 y: self.y.to_scrollable(),
522 }
523 }
524
525 pub fn establishes_scroll_container(&self) -> bool {
527 self.x.is_scrollable()
530 }
531}
532
533#[derive(Clone)]
534pub enum ScrollContainerResponse {
535 Viewport(AxesOverflow),
536 Element(UntrustedNodeAddress, AxesOverflow),
537}
538
539#[derive(Debug, PartialEq)]
540pub enum QueryMsg {
541 BoxArea,
542 BoxAreas,
543 ClientRectQuery,
544 CurrentCSSZoomQuery,
545 EffectiveOverflow,
546 ElementInnerOuterTextQuery,
547 ElementsFromPoint,
548 InnerWindowDimensionsQuery,
549 NodesFromPointQuery,
550 OffsetParentQuery,
551 ScrollParentQuery,
552 ResolvedFontStyleQuery,
553 ResolvedStyleQuery(PropertyId),
556 ScrollingAreaOrOffsetQuery,
557 StyleQuery,
558 TextIndexQuery,
559 PaddingQuery,
560 FlushForUpdateTheRenderingQuery,
561}
562
563#[derive(Debug, PartialEq)]
569pub enum ReflowGoal {
570 UpdateTheRendering,
573
574 LayoutQuery(QueryMsg),
577
578 UpdateScrollNode(ExternalScrollId, LayoutVector2D),
582}
583
584#[derive(Clone, Debug, MallocSizeOf)]
585pub struct IFrameSize {
586 pub browsing_context_id: BrowsingContextId,
587 pub pipeline_id: PipelineId,
588 pub viewport_details: ViewportDetails,
589}
590
591pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
592
593bitflags! {
594 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
597 pub struct RestyleReason: u16 {
598 const StylesheetsChanged = 1 << 0;
599 const DOMChanged = 1 << 1;
600 const PendingRestyles = 1 << 2;
601 const HighlightedDOMNodeChanged = 1 << 3;
602 const ThemeChanged = 1 << 4;
603 const ViewportChanged = 1 << 5;
604 const PaintWorkletLoaded = 1 << 6;
605 }
606}
607
608malloc_size_of_is_0!(RestyleReason);
609
610impl RestyleReason {
611 pub fn needs_restyle(&self) -> bool {
612 !self.is_empty()
613 }
614}
615
616#[derive(Debug, Default)]
618pub struct ReflowResult {
619 pub reflow_phases_run: ReflowPhasesRun,
621 pub reflow_statistics: ReflowStatistics,
622 pub pending_images: Vec<PendingImage>,
624 pub pending_rasterization_images: Vec<PendingRasterizationImage>,
626 pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
630 pub iframe_sizes: Option<IFrameSizes>,
636}
637
638bitflags! {
639 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
641 pub struct ReflowPhasesRun: u8 {
642 const RanLayout = 1 << 0;
643 const BuiltStackingContextTree = 1 << 2;
644 const BuiltDisplayList = 1 << 3;
645 const UpdatedScrollNodeOffset = 1 << 4;
646 const UpdatedImageData = 1 << 5;
650 const UpdatedAccessibilityTree = 1 << 6;
651 }
652}
653
654impl ReflowPhasesRun {
655 pub fn needs_frame(&self) -> bool {
656 self.intersects(
657 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
658 )
659 }
660}
661
662#[derive(Debug, Default)]
663pub struct ReflowStatistics {
664 pub rebuilt_fragment_count: u32,
666 pub restyle_fragment_count: u32,
668 pub only_descendants_changed_count: u32,
671}
672
673#[derive(Debug)]
676pub struct ReflowRequestRestyle {
677 pub reason: RestyleReason,
679 pub dirty_root: Option<TrustedNodeAddress>,
681 pub stylesheets_changed: bool,
683 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
685}
686
687#[derive(Debug)]
689pub struct ReflowRequest {
690 pub document: TrustedNodeAddress,
692 pub epoch: Epoch,
694 pub restyle: Option<ReflowRequestRestyle>,
696 pub viewport_details: ViewportDetails,
698 pub reflow_goal: ReflowGoal,
700 pub origin: ImmutableOrigin,
702 pub animation_timeline_value: f64,
704 pub animations: DocumentAnimationSet,
706 pub animating_images: Arc<RwLock<AnimatingImages>>,
708 pub highlighted_dom_node: Option<OpaqueNode>,
710 pub document_context: WebFontDocumentContext,
712 pub rooted_nodes_for_accessibility_integrity_check: Option<FxHashSet<OpaqueNode>>,
716}
717
718impl ReflowRequest {
719 pub fn stylesheets_changed(&self) -> bool {
720 self.restyle
721 .as_ref()
722 .is_some_and(|restyle| restyle.stylesheets_changed)
723 }
724}
725
726#[derive(Debug, Default, MallocSizeOf)]
728pub struct PendingRestyle {
729 pub snapshot: Option<Snapshot>,
732
733 pub hint: RestyleHint,
735
736 pub damage: RestyleDamage,
738}
739
740#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
746pub enum FragmentType {
747 FragmentBody,
749 BeforePseudoContent,
751 AfterPseudoContent,
753}
754
755impl From<Option<PseudoElement>> for FragmentType {
756 fn from(value: Option<PseudoElement>) -> Self {
757 match value {
758 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
759 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
760 _ => FragmentType::FragmentBody,
761 }
762 }
763}
764
765pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
766 debug_assert_eq!(id & (fragment_type as usize), 0);
767 (id as u64) | (fragment_type as u64)
768}
769
770pub fn node_id_from_scroll_id(id: usize) -> usize {
771 id & !3
772}
773
774#[derive(Clone, Debug, MallocSizeOf)]
775pub struct ImageAnimationState {
776 #[conditional_malloc_size_of]
777 pub image: Arc<RasterImage>,
778 pub active_frame: usize,
779 frame_start_time: f64,
780
781 pub completed_loops: Option<u32>,
786}
787
788impl ImageAnimationState {
789 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
790 let completd_loops = match &image.loop_count {
791 None => unreachable!("Loop count of an animated Image should never be None"),
792 Some(repeat) if Repeat::Infinite == *repeat => None,
793 _ => Some(0),
794 };
795
796 Self {
797 image,
798 active_frame: 0,
799 frame_start_time: last_update_time,
800 completed_loops: completd_loops,
801 }
802 }
803
804 pub fn image_key(&self) -> Option<ImageKey> {
805 self.image.id
806 }
807
808 pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
809 if self.is_finished() {
810 return None;
811 }
812 let frame_delay = self
813 .image
814 .frames
815 .get(self.active_frame)
816 .expect("Image frame should always be valid")
817 .delay
818 .unwrap_or_default();
819
820 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
821 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
822 Some(frame_delay - time_since_frame_start.min(frame_delay))
823 }
824
825 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
829 if self.image.frames.len() <= 1 || self.is_finished() {
830 return false;
831 }
832 let time_interval_since_last_update = now - self.frame_start_time;
833 let mut remain_time_interval = time_interval_since_last_update -
834 self.image
835 .frames
836 .get(self.active_frame)
837 .unwrap()
838 .delay()
839 .unwrap()
840 .as_secs_f64();
841 let mut next_active_frame_id = self.active_frame;
842
843 let frame_count = self.image.frames.len();
844 while remain_time_interval > 0.0 {
845 next_active_frame_id = (next_active_frame_id + 1) % frame_count;
846
847 if next_active_frame_id == 0 {
849 self.advance_completed_loops();
850
851 if self.is_finished() {
854 if self.active_frame == frame_count - 1 {
855 return false;
856 }
857 self.active_frame = frame_count - 1;
858 self.frame_start_time = now;
859 return true;
860 }
861 }
862
863 remain_time_interval -= self
864 .image
865 .frames
866 .get(next_active_frame_id)
867 .unwrap()
868 .delay()
869 .unwrap()
870 .as_secs_f64();
871 }
872 if self.active_frame == next_active_frame_id {
873 return false;
874 }
875 self.active_frame = next_active_frame_id;
876 self.frame_start_time = now;
877 true
878 }
879
880 fn is_finished(&self) -> bool {
882 let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
883 return false;
884 };
885 self.completed_loops
886 .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
887 }
888
889 fn advance_completed_loops(&mut self) {
891 if let Some(completed_loops) = self.completed_loops.as_mut() {
892 *completed_loops += 1;
893 }
894 }
895}
896
897#[derive(Debug)]
899pub struct ElementsFromPointResult {
900 pub node: OpaqueNode,
903 pub point_in_target: Point2D<f32, CSSPixel>,
906 pub cursor: Cursor,
909}
910
911#[derive(Debug, Default, MallocSizeOf)]
912pub struct AnimatingImages {
913 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
916 pub dirty: bool,
919}
920
921impl AnimatingImages {
922 pub fn maybe_insert_or_update(
923 &mut self,
924 node: OpaqueNode,
925 image: Arc<RasterImage>,
926 current_timeline_value: f64,
927 ) {
928 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
929 self.dirty = true;
930 ImageAnimationState::new(image.clone(), current_timeline_value)
931 });
932
933 if entry.image.id != image.id {
936 self.dirty = true;
937 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
938 }
939 }
940
941 pub fn remove(&mut self, node: OpaqueNode) {
942 if self.node_to_state_map.remove(&node).is_some() {
943 self.dirty = true;
944 }
945 }
946
947 pub fn clear_dirty(&mut self) -> bool {
949 std::mem::take(&mut self.dirty)
950 }
951
952 pub fn is_empty(&self) -> bool {
953 self.node_to_state_map.is_empty()
954 }
955}
956
957struct ThreadStateRestorer;
958
959impl ThreadStateRestorer {
960 fn new() -> Self {
961 #[cfg(debug_assertions)]
962 {
963 thread_state::exit(ThreadState::SCRIPT);
964 thread_state::enter(ThreadState::LAYOUT);
965 }
966 Self
967 }
968}
969
970impl Drop for ThreadStateRestorer {
971 fn drop(&mut self) {
972 #[cfg(debug_assertions)]
973 {
974 thread_state::exit(ThreadState::LAYOUT);
975 thread_state::enter(ThreadState::SCRIPT);
976 }
977 }
978}
979
980pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
986 let _guard = ThreadStateRestorer::new();
987 f()
988}
989
990#[cfg(test)]
991mod test {
992 use std::num::NonZeroU32;
993 use std::sync::Arc;
994 use std::time::Duration;
995
996 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
997
998 use crate::ImageAnimationState;
999
1000 #[test]
1001 fn test_animated_image_update() {
1002 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1003 delay: Some(Duration::from_millis(100)),
1004 byte_range: 0..1,
1005 width: 100,
1006 height: 100,
1007 })
1008 .take(10)
1009 .collect();
1010 let image = RasterImage {
1011 metadata: ImageMetadata {
1012 width: 100,
1013 height: 100,
1014 },
1015 format: PixelFormat::BGRA8,
1016 id: None,
1017 bytes: Arc::new(vec![1]),
1018 frames: image_frames,
1019 cors_status: CorsStatus::Unsafe,
1020 loop_count: Some(Repeat::Infinite),
1021 is_opaque: false,
1022 };
1023 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1024
1025 assert_eq!(image_animation_state.active_frame, 0);
1026 assert_eq!(image_animation_state.frame_start_time, 0.0);
1027 assert_eq!(
1028 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1029 true
1030 );
1031 assert_eq!(image_animation_state.active_frame, 1);
1032 assert_eq!(image_animation_state.frame_start_time, 0.101);
1033 assert_eq!(
1034 image_animation_state.update_frame_for_animation_timeline_value(0.116),
1035 false
1036 );
1037 assert_eq!(image_animation_state.active_frame, 1);
1038 assert_eq!(image_animation_state.frame_start_time, 0.101);
1039 }
1040
1041 #[test]
1042 fn test_finite_image_repeat() {
1043 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1044 delay: Some(Duration::from_millis(100)),
1045 byte_range: 0..1,
1046 width: 100,
1047 height: 100,
1048 })
1049 .take(2)
1050 .collect();
1051 let image = RasterImage {
1052 metadata: ImageMetadata {
1053 width: 100,
1054 height: 100,
1055 },
1056 format: PixelFormat::BGRA8,
1057 id: None,
1058 bytes: Arc::new(vec![1]),
1059 frames: image_frames,
1060 cors_status: CorsStatus::Unsafe,
1061 loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1062 is_opaque: false,
1063 };
1064 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1065
1066 assert_eq!(image_animation_state.active_frame, 0);
1067 assert_eq!(image_animation_state.frame_start_time, 0.0);
1068 assert_eq!(
1069 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1070 true
1071 );
1072 assert_eq!(image_animation_state.active_frame, 1);
1073 assert_eq!(image_animation_state.frame_start_time, 0.101);
1074 assert_eq!(
1075 image_animation_state.update_frame_for_animation_timeline_value(0.202),
1076 false
1077 );
1078 assert_eq!(
1079 image_animation_state.update_frame_for_animation_timeline_value(0.303),
1080 false
1081 );
1082
1083 assert_eq!(image_animation_state.active_frame, 1);
1084 }
1085}