1#![deny(unsafe_code)]
10
11mod layout_damage;
12mod layout_dom;
13mod layout_element;
14mod layout_node;
15mod pseudo_element_chain;
16
17use std::any::Any;
18use std::ops::Range;
19use std::rc::Rc;
20use std::sync::Arc;
21use std::sync::atomic::AtomicIsize;
22use std::thread::JoinHandle;
23use std::time::Duration;
24
25use app_units::Au;
26use atomic_refcell::AtomicRefCell;
27use background_hang_monitor_api::BackgroundHangMonitorRegister;
28use bitflags::bitflags;
29use embedder_traits::{Cursor, ScriptToEmbedderChan, Theme, UntrustedNodeAddress, ViewportDetails};
30use euclid::{Point2D, Rect};
31use fonts::{FontContext, TextByteRange, WebFontDocumentContext, WebFontSetDifference};
32pub use layout_damage::{AccessibilityDamage, LayoutDamage};
33pub use layout_dom::{
34 DangerousStyleElementOf, DangerousStyleNodeOf, LayoutDomTypeBundle, LayoutElementOf,
35 LayoutNodeOf,
36};
37pub use layout_element::{DangerousStyleElement, LayoutElement};
38pub use layout_node::{DangerousStyleNode, LayoutNode};
39use libc::c_void;
40use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps, malloc_size_of_is_0};
41use malloc_size_of_derive::MallocSizeOf;
42use net_traits::image_cache::{ImageCache, ImageCacheFactory, PendingImageId};
43use net_traits::request::InternalRequest;
44use paint_api::CrossProcessPaintApi;
45use paint_api::largest_contentful_paint_candidate::LCPCandidate;
46use parking_lot::RwLock;
47use pixels::{RasterImage, Repeat};
48use profile_traits::mem::Report;
49use profile_traits::time;
50pub use pseudo_element_chain::PseudoElementChain;
51use rustc_hash::{FxHashMap, FxHashSet};
52use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
53use serde::{Deserialize, Serialize};
54use servo_arc::Arc as ServoArc;
55use servo_base::Epoch;
56use servo_base::generic_channel::GenericSender;
57use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
58use servo_base::text::Utf32CodeUnits;
59use servo_url::{ImmutableOrigin, ServoUrl};
60use style::Atom;
61use style::animation::DocumentAnimationSet;
62use style::attr::{AttrValue, parse_integer, parse_unsigned_integer};
63use style::context::QuirksMode;
64use style::data::ElementDataWrapper;
65use style::device::Device;
66use style::dom::OpaqueNode;
67use style::invalidation::element::restyle_hints::RestyleHint;
68use style::properties::style_structs::Font;
69use style::properties::{ComputedValues, PropertyId};
70use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
71use style::str::char_is_whitespace;
72use style::stylesheets::{DocumentStyleSheet, Stylesheet};
73use style::stylist::Stylist;
74#[cfg(debug_assertions)]
75use style::thread_state::{self, ThreadState};
76use style::values::computed::Overflow;
77use style_traits::CSSPixel;
78use uuid::Uuid;
79use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
80use webrender_api::{ExternalScrollId, ImageKey};
81
82pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait + Send + Sync + 'static {
83 fn as_any(&self) -> &dyn Any;
84}
85
86pub trait LayoutDataTrait: GenericLayoutDataTrait + Default {}
87pub type GenericLayoutData = dyn GenericLayoutDataTrait;
88
89#[derive(Default, MallocSizeOf)]
90pub struct StyleData {
91 pub element_data: ElementDataWrapper,
96
97 pub parallel: DomParallelInfo,
99}
100
101#[derive(Default, MallocSizeOf)]
103pub struct DomParallelInfo {
104 pub children_to_process: AtomicIsize,
106}
107
108#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub enum LayoutNodeType {
110 Element(LayoutElementType),
111 Text,
112}
113
114#[derive(Clone, Copy, Debug, Eq, PartialEq)]
115pub enum LayoutElementType {
116 Element,
117 HTMLBodyElement,
118 HTMLButtonElement,
119 HTMLBRElement,
120 HTMLCanvasElement,
121 HTMLHtmlElement,
122 HTMLIFrameElement,
123 HTMLImageElement,
124 HTMLInputElement,
125 HTMLMediaElement,
126 HTMLObjectElement,
127 HTMLOptGroupElement,
128 HTMLOptionElement,
129 HTMLParagraphElement,
130 HTMLPreElement,
131 HTMLSelectElement,
132 HTMLTableCellElement,
133 HTMLTableColElement,
134 HTMLTableElement,
135 HTMLTableRowElement,
136 HTMLTableSectionElement,
137 HTMLTextAreaElement,
138 SVGImageElement,
139 SVGSVGElement,
140}
141
142#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
146pub struct ScriptSelection {
147 pub range: TextByteRange,
149 pub character_range: Range<usize>,
151 pub enabled: bool,
154}
155
156pub type SharedSelection = Arc<AtomicRefCell<ScriptSelection>>;
157pub struct HTMLCanvasData {
158 pub image_key: Option<ImageKey>,
159 pub width: u32,
160 pub height: u32,
161}
162
163pub struct SVGElementData<'dom> {
164 pub source: Option<Result<ServoUrl, ()>>,
166 pub width: Option<&'dom AttrValue>,
167 pub height: Option<&'dom AttrValue>,
168 pub svg_id: Uuid,
169 pub view_box: Option<&'dom AttrValue>,
170}
171
172impl SVGElementData<'_> {
173 pub fn ratio_from_view_box(&self) -> Option<f32> {
174 let mut iter = self.view_box?.chars();
175 let _min_x = parse_integer(&mut iter).ok()?;
176 let _min_y = parse_integer(&mut iter).ok()?;
177
178 let width = parse_unsigned_integer(&mut iter).ok()?;
179 if width == 0 {
180 return None;
181 }
182
183 let height = parse_unsigned_integer(&mut iter).ok()?;
184 if height == 0 {
185 return None;
186 }
187
188 let mut iter = iter.skip_while(|c| char_is_whitespace(*c));
189 iter.next().is_none().then(|| width as f32 / height as f32)
190 }
191}
192
193#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
195pub struct TrustedNodeAddress(pub *const c_void);
196
197#[expect(unsafe_code)]
198unsafe impl Send for TrustedNodeAddress {}
199
200#[derive(Debug)]
202pub enum PendingImageState {
203 Unrequested(ServoUrl),
204 PendingResponse,
205}
206
207#[derive(Debug, MallocSizeOf)]
209pub enum LayoutImageDestination {
210 BoxTreeConstruction,
211 DisplayListBuilding,
212}
213
214#[derive(Debug)]
218pub struct PendingImage {
219 pub state: PendingImageState,
220 pub node: UntrustedNodeAddress,
221 pub id: PendingImageId,
222 pub origin: ImmutableOrigin,
223 pub destination: LayoutImageDestination,
224 pub is_internal_request: InternalRequest,
225}
226
227#[derive(Debug)]
231pub struct PendingRasterizationImage {
232 pub node: UntrustedNodeAddress,
233 pub id: PendingImageId,
234 pub size: DeviceIntSize,
235}
236
237#[derive(Clone, Copy, Debug, MallocSizeOf)]
238pub struct MediaFrame {
239 pub image_key: webrender_api::ImageKey,
240 pub width: i32,
241 pub height: i32,
242}
243
244pub struct MediaMetadata {
245 pub width: u32,
246 pub height: u32,
247}
248
249pub struct HTMLMediaData {
250 pub current_frame: Option<MediaFrame>,
251 pub metadata: Option<MediaMetadata>,
252 pub poster_url: Option<ServoUrl>,
253}
254
255pub struct LayoutConfig {
256 pub id: PipelineId,
257 pub webview_id: WebViewId,
258 pub url: ServoUrl,
259 pub is_iframe: bool,
260 pub script_chan: GenericSender<ScriptThreadMessage>,
261 pub image_cache: Arc<dyn ImageCache>,
262 pub font_context: Arc<FontContext>,
263 pub time_profiler_chan: time::ProfilerChan,
264 pub paint_api: CrossProcessPaintApi,
265 pub viewport_details: ViewportDetails,
266 pub user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
267 pub theme: Theme,
268 pub embedder_chan: ScriptToEmbedderChan,
269}
270
271bitflags! {
272 pub struct HitTestFlags: u8 {
273 const IncludeDomPosition = 0b0000_0001;
275 }
276}
277
278pub trait LayoutFactory: Send + Sync {
279 fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
280}
281
282pub trait Layout {
283 fn device(&self) -> &Device;
286
287 fn set_theme(&mut self, theme: Theme) -> bool;
291
292 fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
296
297 fn add_stylesheet(
302 &mut self,
303 stylesheet: ServoArc<Stylesheet>,
304 before_stylesheet: Option<ServoArc<Stylesheet>>,
305 );
306
307 fn exit_now(&mut self);
309
310 fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
313
314 fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
316
317 fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
319
320 fn remove_cached_image(&mut self, image_url: &ServoUrl);
322
323 fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
325
326 fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
329
330 fn register_paint_worklet_modules(
332 &mut self,
333 name: Atom,
334 properties: Vec<Atom>,
335 painter: Box<dyn Painter>,
336 );
337
338 fn set_scroll_offsets_from_renderer(
340 &mut self,
341 scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
342 );
343
344 fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
347
348 fn needs_new_display_list(&self) -> bool;
350
351 fn set_needs_new_display_list(&self);
353
354 fn node_rendering_type(
357 &self,
358 node: TrustedNodeAddress,
359 pseudo: Option<PseudoElement>,
360 ) -> NodeRenderingType;
361
362 fn query_containing_block(&self, node: TrustedNodeAddress) -> Option<UntrustedNodeAddress>;
363 fn query_containing_block_is_descendant(
364 &self,
365 root: TrustedNodeAddress,
366 possible_descendant: TrustedNodeAddress,
367 ) -> bool;
368 fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
369 fn query_box_area(
370 &self,
371 node: TrustedNodeAddress,
372 area: BoxAreaType,
373 exclude_transform_and_inline: bool,
374 ) -> Option<Rect<Au, CSSPixel>>;
375 fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectVec;
376 fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
377 fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
378 fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
379 fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
380 fn query_scroll_container(
383 &self,
384 node: Option<TrustedNodeAddress>,
385 flags: ScrollContainerQueryFlags,
386 ) -> Option<ScrollContainerResponse>;
387 fn query_resolved_style(
388 &self,
389 node: TrustedNodeAddress,
390 pseudo: Option<PseudoElement>,
391 property_id: PropertyId,
392 animations: DocumentAnimationSet,
393 animation_timeline_value: f64,
394 ) -> String;
395 fn query_resolved_font_style(
396 &self,
397 node: TrustedNodeAddress,
398 value: &str,
399 animations: DocumentAnimationSet,
400 animation_timeline_value: f64,
401 ) -> Option<ServoArc<Font>>;
402 fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
403 fn hit_test(&self, flags: HitTestFlags, point: LayoutPoint) -> HitTestResult;
404 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
405 fn stylist_mut(&mut self) -> &mut Stylist;
406
407 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
410
411 fn accessibility_active(&self) -> bool;
413
414 fn needs_accessibility_update(&self) -> bool;
425
426 fn set_needs_accessibility_update(&self);
428
429 fn font_context(&self) -> &Arc<FontContext>;
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(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 pub changed_web_fonts: WebFontSetDifference,
638 pub lcp_candidate: Option<LCPCandidate>,
640 pub lcp_node_address: Option<UntrustedNodeAddress>,
642}
643
644bitflags! {
645 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
647 pub struct ReflowPhasesRun: u8 {
648 const RanLayout = 1 << 0;
649 const BuiltStackingContextTree = 1 << 2;
650 const BuiltDisplayList = 1 << 3;
651 const UpdatedScrollNodeOffset = 1 << 4;
652 const UpdatedImageData = 1 << 5;
656 const UpdatedAccessibilityTree = 1 << 6;
657 }
658}
659
660impl ReflowPhasesRun {
661 pub fn needs_frame(&self) -> bool {
662 self.intersects(
663 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
664 )
665 }
666}
667
668#[derive(Debug, Default)]
669pub struct ReflowStatistics {
670 pub rebuilt_fragment_count: u32,
672 pub restyle_fragment_count: u32,
674 pub only_descendants_changed_count: u32,
677 pub nodes_updated_from_dom: u32,
680 pub nodes_updated_from_tree: u32,
683 pub nodes_in_tree_update: u32,
685}
686
687#[derive(Debug)]
690pub struct ReflowRequestRestyle {
691 pub reason: RestyleReason,
693 pub dirty_root: Option<TrustedNodeAddress>,
695 pub stylesheets_changed: bool,
697 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
699}
700
701#[derive(Debug)]
703pub struct ReflowRequest {
704 pub document: TrustedNodeAddress,
706 pub epoch: Epoch,
708 pub restyle: Option<ReflowRequestRestyle>,
710 pub viewport_details: ViewportDetails,
712 pub reflow_goal: ReflowGoal,
714 pub origin: ImmutableOrigin,
716 pub animation_timeline_value: f64,
718 pub animations: DocumentAnimationSet,
720 pub animating_images: Arc<RwLock<AnimatingImages>>,
722 pub highlighted_dom_node: Option<OpaqueNode>,
724 pub document_context: WebFontDocumentContext,
726 pub accessibility_damage: Option<Vec<(TrustedNodeAddress, AccessibilityDamage)>>,
728 pub rooted_nodes_for_accessibility_integrity_check: Option<FxHashSet<OpaqueNode>>,
732}
733
734impl ReflowRequest {
735 pub fn stylesheets_changed(&self) -> bool {
736 self.restyle
737 .as_ref()
738 .is_some_and(|restyle| restyle.stylesheets_changed)
739 }
740}
741
742#[derive(Debug, Default, MallocSizeOf)]
744pub struct PendingRestyle {
745 pub snapshot: Option<Snapshot>,
748
749 pub hint: RestyleHint,
751
752 pub damage: RestyleDamage,
754}
755
756#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
762pub enum FragmentType {
763 FragmentBody,
765 BeforePseudoContent,
767 AfterPseudoContent,
769}
770
771impl From<Option<PseudoElement>> for FragmentType {
772 fn from(value: Option<PseudoElement>) -> Self {
773 match value {
774 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
775 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
776 _ => FragmentType::FragmentBody,
777 }
778 }
779}
780
781pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
782 debug_assert_eq!(id & (fragment_type as usize), 0);
783 (id as u64) | (fragment_type as u64)
784}
785
786pub fn node_id_from_scroll_id(id: usize) -> usize {
787 id & !3
788}
789
790#[derive(Clone, Debug, MallocSizeOf)]
791pub struct ImageAnimationState {
792 #[conditional_malloc_size_of]
793 pub image: Arc<RasterImage>,
794 pub active_frame: usize,
795 frame_start_time: f64,
796
797 pub completed_loops: Option<u32>,
802}
803
804impl ImageAnimationState {
805 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
806 let completd_loops = match &image.loop_count {
807 None => unreachable!("Loop count of an animated Image should never be None"),
808 Some(repeat) if Repeat::Infinite == *repeat => None,
809 _ => Some(0),
810 };
811
812 Self {
813 image,
814 active_frame: 0,
815 frame_start_time: last_update_time,
816 completed_loops: completd_loops,
817 }
818 }
819
820 pub fn image_key(&self) -> Option<ImageKey> {
821 self.image.id
822 }
823
824 pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
825 if self.is_finished() {
826 return None;
827 }
828 let frame_delay = self
829 .image
830 .frames
831 .get(self.active_frame)
832 .expect("Image frame should always be valid")
833 .delay
834 .unwrap_or_default();
835
836 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
837 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
838 Some(frame_delay - time_since_frame_start.min(frame_delay))
839 }
840
841 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
845 if self.image.frames.len() <= 1 || self.is_finished() {
846 return false;
847 }
848 let time_interval_since_last_update = now - self.frame_start_time;
849 let mut remain_time_interval = time_interval_since_last_update -
850 self.image
851 .frames
852 .get(self.active_frame)
853 .unwrap()
854 .delay()
855 .unwrap()
856 .as_secs_f64();
857 let mut next_active_frame_id = self.active_frame;
858
859 let frame_count = self.image.frames.len();
860 while remain_time_interval > 0.0 {
861 next_active_frame_id = (next_active_frame_id + 1) % frame_count;
862
863 if next_active_frame_id == 0 {
865 self.advance_completed_loops();
866
867 if self.is_finished() {
870 if self.active_frame == frame_count - 1 {
871 return false;
872 }
873 self.active_frame = frame_count - 1;
874 self.frame_start_time = now;
875 return true;
876 }
877 }
878
879 remain_time_interval -= self
880 .image
881 .frames
882 .get(next_active_frame_id)
883 .unwrap()
884 .delay()
885 .unwrap()
886 .as_secs_f64();
887 }
888 if self.active_frame == next_active_frame_id {
889 return false;
890 }
891 self.active_frame = next_active_frame_id;
892 self.frame_start_time = now;
893 true
894 }
895
896 fn is_finished(&self) -> bool {
898 let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
899 return false;
900 };
901 self.completed_loops
902 .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
903 }
904
905 fn advance_completed_loops(&mut self) {
907 if let Some(completed_loops) = self.completed_loops.as_mut() {
908 *completed_loops += 1;
909 }
910 }
911}
912
913#[derive(Debug, Default)]
915pub struct HitTestResult {
916 pub items: Vec<HitTestResultItem>,
917 pub dom_position_for_selection: Option<(OpaqueNode, Utf32CodeUnits)>,
918}
919
920#[derive(Debug)]
922pub struct HitTestResultItem {
923 pub node: OpaqueNode,
926 pub point_in_target: Point2D<f32, CSSPixel>,
929 pub cursor: Cursor,
932}
933
934#[derive(Debug, Default, MallocSizeOf)]
935pub struct AnimatingImages {
936 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
939 pub dirty: bool,
942}
943
944impl AnimatingImages {
945 pub fn maybe_insert_or_update(
946 &mut self,
947 node: OpaqueNode,
948 image: Arc<RasterImage>,
949 current_timeline_value: f64,
950 ) {
951 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
952 self.dirty = true;
953 ImageAnimationState::new(image.clone(), current_timeline_value)
954 });
955
956 if entry.image.id != image.id {
959 self.dirty = true;
960 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
961 }
962 }
963
964 pub fn remove(&mut self, node: OpaqueNode) {
965 if self.node_to_state_map.remove(&node).is_some() {
966 self.dirty = true;
967 }
968 }
969
970 pub fn clear_dirty(&mut self) -> bool {
972 std::mem::take(&mut self.dirty)
973 }
974
975 pub fn is_empty(&self) -> bool {
976 self.node_to_state_map.is_empty()
977 }
978}
979
980struct ThreadStateRestorer;
981
982impl ThreadStateRestorer {
983 fn new() -> Self {
984 #[cfg(debug_assertions)]
985 {
986 thread_state::exit(ThreadState::SCRIPT);
987 thread_state::enter(ThreadState::LAYOUT);
988 }
989 Self
990 }
991}
992
993impl Drop for ThreadStateRestorer {
994 fn drop(&mut self) {
995 #[cfg(debug_assertions)]
996 {
997 thread_state::exit(ThreadState::LAYOUT);
998 thread_state::enter(ThreadState::SCRIPT);
999 }
1000 }
1001}
1002
1003pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
1009 let _guard = ThreadStateRestorer::new();
1010 f()
1011}
1012
1013#[cfg(test)]
1014mod test {
1015 use std::num::NonZeroU32;
1016 use std::sync::Arc;
1017 use std::time::Duration;
1018
1019 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
1020
1021 use crate::ImageAnimationState;
1022
1023 #[test]
1024 fn test_animated_image_update() {
1025 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1026 delay: Some(Duration::from_millis(100)),
1027 byte_range: 0..1,
1028 width: 100,
1029 height: 100,
1030 })
1031 .take(10)
1032 .collect();
1033 let image = RasterImage {
1034 metadata: ImageMetadata {
1035 width: 100,
1036 height: 100,
1037 },
1038 format: PixelFormat::BGRA8,
1039 id: None,
1040 bytes: Arc::new(vec![1]),
1041 frames: image_frames,
1042 cors_status: CorsStatus::Unsafe,
1043 loop_count: Some(Repeat::Infinite),
1044 is_opaque: false,
1045 };
1046 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1047
1048 assert_eq!(image_animation_state.active_frame, 0);
1049 assert_eq!(image_animation_state.frame_start_time, 0.0);
1050 assert_eq!(
1051 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1052 true
1053 );
1054 assert_eq!(image_animation_state.active_frame, 1);
1055 assert_eq!(image_animation_state.frame_start_time, 0.101);
1056 assert_eq!(
1057 image_animation_state.update_frame_for_animation_timeline_value(0.116),
1058 false
1059 );
1060 assert_eq!(image_animation_state.active_frame, 1);
1061 assert_eq!(image_animation_state.frame_start_time, 0.101);
1062 }
1063
1064 #[test]
1065 fn test_finite_image_repeat() {
1066 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1067 delay: Some(Duration::from_millis(100)),
1068 byte_range: 0..1,
1069 width: 100,
1070 height: 100,
1071 })
1072 .take(2)
1073 .collect();
1074 let image = RasterImage {
1075 metadata: ImageMetadata {
1076 width: 100,
1077 height: 100,
1078 },
1079 format: PixelFormat::BGRA8,
1080 id: None,
1081 bytes: Arc::new(vec![1]),
1082 frames: image_frames,
1083 cors_status: CorsStatus::Unsafe,
1084 loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1085 is_opaque: false,
1086 };
1087 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1088
1089 assert_eq!(image_animation_state.active_frame, 0);
1090 assert_eq!(image_animation_state.frame_start_time, 0.0);
1091 assert_eq!(
1092 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1093 true
1094 );
1095 assert_eq!(image_animation_state.active_frame, 1);
1096 assert_eq!(image_animation_state.frame_start_time, 0.101);
1097 assert_eq!(
1098 image_animation_state.update_frame_for_animation_timeline_value(0.202),
1099 false
1100 );
1101 assert_eq!(
1102 image_animation_state.update_frame_for_animation_timeline_value(0.303),
1103 false
1104 );
1105
1106 assert_eq!(image_animation_state.active_frame, 1);
1107 }
1108}