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 query_text_index(
407 &self,
408 node: TrustedNodeAddress,
409 point_in_viewport: Point2D<Au, CSSPixel>,
410 ) -> Option<(OpaqueNode, Utf32CodeUnits)>;
411 fn hit_test(&self, flags: HitTestFlags, point: LayoutPoint) -> HitTestResult;
412 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
413 fn stylist_mut(&mut self) -> &mut Stylist;
414
415 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
418
419 fn accessibility_active(&self) -> bool;
421
422 fn needs_accessibility_update(&self) -> bool;
433
434 fn set_needs_accessibility_update(&self);
436
437 fn font_context(&self) -> &Arc<FontContext>;
438}
439
440pub trait ScriptThreadFactory {
444 fn create(
446 state: InitialScriptState,
447 layout_factory: Arc<dyn LayoutFactory>,
448 image_cache_factory: Arc<dyn ImageCacheFactory>,
449 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
450 ) -> JoinHandle<()>;
451}
452
453#[derive(Copy, Clone)]
456pub enum BoxAreaType {
457 Content,
458 Padding,
459 Border,
460}
461
462pub type CSSPixelRectVec = Vec<Rect<Au, CSSPixel>>;
463
464#[derive(Copy, Clone)]
467pub enum NodeRenderingType {
468 Rendered,
470 DelegatesRendering,
472 NotRendered,
475}
476
477#[derive(Default)]
478pub struct PhysicalSides {
479 pub left: Au,
480 pub top: Au,
481 pub right: Au,
482 pub bottom: Au,
483}
484
485#[derive(Clone, Default)]
486pub struct OffsetParentResponse {
487 pub node_address: Option<UntrustedNodeAddress>,
488 pub rect: Rect<Au, CSSPixel>,
489}
490
491bitflags! {
492 #[derive(PartialEq)]
493 pub struct ScrollContainerQueryFlags: u8 {
494 const ForScrollParent = 1 << 0;
496 const Inclusive = 1 << 1;
498 }
499}
500
501#[derive(Clone, Copy, Debug, MallocSizeOf)]
502pub struct AxesOverflow {
503 pub x: Overflow,
504 pub y: Overflow,
505}
506
507impl Default for AxesOverflow {
508 fn default() -> Self {
509 Self {
510 x: Overflow::Visible,
511 y: Overflow::Visible,
512 }
513 }
514}
515
516impl From<&ComputedValues> for AxesOverflow {
517 fn from(style: &ComputedValues) -> Self {
518 Self {
519 x: style.clone_overflow_x(),
520 y: style.clone_overflow_y(),
521 }
522 }
523}
524
525impl AxesOverflow {
526 pub fn to_scrollable(&self) -> Self {
527 Self {
528 x: self.x.to_scrollable(),
529 y: self.y.to_scrollable(),
530 }
531 }
532
533 pub fn establishes_scroll_container(&self) -> bool {
535 self.x.is_scrollable()
538 }
539}
540
541#[derive(Clone)]
542pub enum ScrollContainerResponse {
543 Viewport(AxesOverflow),
544 Element(UntrustedNodeAddress, AxesOverflow),
545}
546
547#[derive(Debug, PartialEq)]
548pub enum QueryMsg {
549 BoxArea,
550 BoxAreas,
551 ClientRectQuery,
552 CurrentCSSZoomQuery,
553 EffectiveOverflow,
554 ElementInnerOuterTextQuery,
555 ElementsFromPoint,
556 InnerWindowDimensionsQuery,
557 NodesFromPointQuery,
558 OffsetParentQuery,
559 ScrollParentQuery,
560 ResolvedFontStyleQuery,
561 ResolvedStyleQuery(PropertyId),
564 ScrollingAreaOrOffsetQuery,
565 StyleQuery,
566 TextIndexQuery,
567 PaddingQuery,
568 FlushForUpdateTheRenderingQuery,
569}
570
571#[derive(Debug, PartialEq)]
577pub enum ReflowGoal {
578 UpdateTheRendering,
581
582 LayoutQuery(QueryMsg),
585
586 UpdateScrollNode(ExternalScrollId, LayoutVector2D),
590}
591
592#[derive(Clone, Debug, MallocSizeOf)]
593pub struct IFrameSize {
594 pub browsing_context_id: BrowsingContextId,
595 pub pipeline_id: PipelineId,
596 pub viewport_details: ViewportDetails,
597}
598
599pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
600
601bitflags! {
602 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
605 pub struct RestyleReason: u16 {
606 const StylesheetsChanged = 1 << 0;
607 const DOMChanged = 1 << 1;
608 const PendingRestyles = 1 << 2;
609 const HighlightedDOMNodeChanged = 1 << 3;
610 const ThemeChanged = 1 << 4;
611 const ViewportChanged = 1 << 5;
612 const PaintWorkletLoaded = 1 << 6;
613 }
614}
615
616malloc_size_of_is_0!(RestyleReason);
617
618impl RestyleReason {
619 pub fn needs_restyle(&self) -> bool {
620 !self.is_empty()
621 }
622}
623
624#[derive(Default)]
626pub struct ReflowResult {
627 pub reflow_phases_run: ReflowPhasesRun,
629 pub reflow_statistics: ReflowStatistics,
630 pub pending_images: Vec<PendingImage>,
632 pub pending_rasterization_images: Vec<PendingRasterizationImage>,
634 pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
638 pub iframe_sizes: Option<IFrameSizes>,
644 pub changed_web_fonts: WebFontSetDifference,
646 pub lcp_candidate: Option<LCPCandidate>,
648 pub lcp_node_address: Option<UntrustedNodeAddress>,
650}
651
652bitflags! {
653 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
655 pub struct ReflowPhasesRun: u8 {
656 const RanLayout = 1 << 0;
657 const BuiltStackingContextTree = 1 << 2;
658 const BuiltDisplayList = 1 << 3;
659 const UpdatedScrollNodeOffset = 1 << 4;
660 const UpdatedImageData = 1 << 5;
664 const UpdatedAccessibilityTree = 1 << 6;
665 }
666}
667
668impl ReflowPhasesRun {
669 pub fn needs_frame(&self) -> bool {
670 self.intersects(
671 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
672 )
673 }
674}
675
676#[derive(Debug, Default)]
677pub struct ReflowStatistics {
678 pub rebuilt_fragment_count: u32,
680 pub restyle_fragment_count: u32,
682 pub only_descendants_changed_count: u32,
685 pub nodes_updated_from_dom: u32,
688 pub nodes_updated_from_tree: u32,
691 pub nodes_in_tree_update: u32,
693}
694
695#[derive(Debug)]
698pub struct ReflowRequestRestyle {
699 pub reason: RestyleReason,
701 pub dirty_root: Option<TrustedNodeAddress>,
703 pub stylesheets_changed: bool,
705 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
707}
708
709#[derive(Debug)]
711pub struct ReflowRequest {
712 pub document: TrustedNodeAddress,
714 pub epoch: Epoch,
716 pub restyle: Option<ReflowRequestRestyle>,
718 pub viewport_details: ViewportDetails,
720 pub reflow_goal: ReflowGoal,
722 pub origin: ImmutableOrigin,
724 pub animation_timeline_value: f64,
726 pub animations: DocumentAnimationSet,
728 pub animating_images: Arc<RwLock<AnimatingImages>>,
730 pub highlighted_dom_node: Option<OpaqueNode>,
732 pub document_context: WebFontDocumentContext,
734 pub accessibility_damage: Option<Vec<(TrustedNodeAddress, AccessibilityDamage)>>,
736 pub rooted_nodes_for_accessibility_integrity_check: Option<FxHashSet<OpaqueNode>>,
740}
741
742impl ReflowRequest {
743 pub fn stylesheets_changed(&self) -> bool {
744 self.restyle
745 .as_ref()
746 .is_some_and(|restyle| restyle.stylesheets_changed)
747 }
748}
749
750#[derive(Debug, Default, MallocSizeOf)]
752pub struct PendingRestyle {
753 pub snapshot: Option<Snapshot>,
756
757 pub hint: RestyleHint,
759
760 pub damage: RestyleDamage,
762}
763
764#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
770pub enum FragmentType {
771 FragmentBody,
773 BeforePseudoContent,
775 AfterPseudoContent,
777}
778
779impl From<Option<PseudoElement>> for FragmentType {
780 fn from(value: Option<PseudoElement>) -> Self {
781 match value {
782 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
783 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
784 _ => FragmentType::FragmentBody,
785 }
786 }
787}
788
789pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
790 debug_assert_eq!(id & (fragment_type as usize), 0);
791 (id as u64) | (fragment_type as u64)
792}
793
794pub fn node_id_from_scroll_id(id: usize) -> usize {
795 id & !3
796}
797
798#[derive(Clone, Debug, MallocSizeOf)]
799pub struct ImageAnimationState {
800 #[conditional_malloc_size_of]
801 pub image: Arc<RasterImage>,
802 pub active_frame: usize,
803 frame_start_time: f64,
804
805 pub completed_loops: Option<u32>,
810}
811
812impl ImageAnimationState {
813 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
814 let completd_loops = match &image.loop_count {
815 None => unreachable!("Loop count of an animated Image should never be None"),
816 Some(repeat) if Repeat::Infinite == *repeat => None,
817 _ => Some(0),
818 };
819
820 Self {
821 image,
822 active_frame: 0,
823 frame_start_time: last_update_time,
824 completed_loops: completd_loops,
825 }
826 }
827
828 pub fn image_key(&self) -> Option<ImageKey> {
829 self.image.id
830 }
831
832 pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
833 if self.is_finished() {
834 return None;
835 }
836 let frame_delay = self
837 .image
838 .frames
839 .get(self.active_frame)
840 .expect("Image frame should always be valid")
841 .delay
842 .unwrap_or_default();
843
844 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
845 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
846 Some(frame_delay - time_since_frame_start.min(frame_delay))
847 }
848
849 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
853 if self.image.frames.len() <= 1 || self.is_finished() {
854 return false;
855 }
856 let time_interval_since_last_update = now - self.frame_start_time;
857 let mut remain_time_interval = time_interval_since_last_update -
858 self.image
859 .frames
860 .get(self.active_frame)
861 .unwrap()
862 .delay()
863 .unwrap()
864 .as_secs_f64();
865 let mut next_active_frame_id = self.active_frame;
866
867 let frame_count = self.image.frames.len();
868 while remain_time_interval > 0.0 {
869 next_active_frame_id = (next_active_frame_id + 1) % frame_count;
870
871 if next_active_frame_id == 0 {
873 self.advance_completed_loops();
874
875 if self.is_finished() {
878 if self.active_frame == frame_count - 1 {
879 return false;
880 }
881 self.active_frame = frame_count - 1;
882 self.frame_start_time = now;
883 return true;
884 }
885 }
886
887 remain_time_interval -= self
888 .image
889 .frames
890 .get(next_active_frame_id)
891 .unwrap()
892 .delay()
893 .unwrap()
894 .as_secs_f64();
895 }
896 if self.active_frame == next_active_frame_id {
897 return false;
898 }
899 self.active_frame = next_active_frame_id;
900 self.frame_start_time = now;
901 true
902 }
903
904 fn is_finished(&self) -> bool {
906 let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
907 return false;
908 };
909 self.completed_loops
910 .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
911 }
912
913 fn advance_completed_loops(&mut self) {
915 if let Some(completed_loops) = self.completed_loops.as_mut() {
916 *completed_loops += 1;
917 }
918 }
919}
920
921#[derive(Debug, Default)]
923pub struct HitTestResult {
924 pub items: Vec<HitTestResultItem>,
925 pub dom_position_for_selection: Option<(OpaqueNode, Utf32CodeUnits)>,
926}
927
928#[derive(Debug)]
930pub struct HitTestResultItem {
931 pub node: OpaqueNode,
934 pub point_in_target: Point2D<f32, CSSPixel>,
937 pub cursor: Cursor,
940}
941
942#[derive(Debug, Default, MallocSizeOf)]
943pub struct AnimatingImages {
944 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
947 pub dirty: bool,
950}
951
952impl AnimatingImages {
953 pub fn maybe_insert_or_update(
954 &mut self,
955 node: OpaqueNode,
956 image: Arc<RasterImage>,
957 current_timeline_value: f64,
958 ) {
959 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
960 self.dirty = true;
961 ImageAnimationState::new(image.clone(), current_timeline_value)
962 });
963
964 if entry.image.id != image.id {
967 self.dirty = true;
968 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
969 }
970 }
971
972 pub fn remove(&mut self, node: OpaqueNode) {
973 if self.node_to_state_map.remove(&node).is_some() {
974 self.dirty = true;
975 }
976 }
977
978 pub fn clear_dirty(&mut self) -> bool {
980 std::mem::take(&mut self.dirty)
981 }
982
983 pub fn is_empty(&self) -> bool {
984 self.node_to_state_map.is_empty()
985 }
986}
987
988struct ThreadStateRestorer;
989
990impl ThreadStateRestorer {
991 fn new() -> Self {
992 #[cfg(debug_assertions)]
993 {
994 thread_state::exit(ThreadState::SCRIPT);
995 thread_state::enter(ThreadState::LAYOUT);
996 }
997 Self
998 }
999}
1000
1001impl Drop for ThreadStateRestorer {
1002 fn drop(&mut self) {
1003 #[cfg(debug_assertions)]
1004 {
1005 thread_state::exit(ThreadState::LAYOUT);
1006 thread_state::enter(ThreadState::SCRIPT);
1007 }
1008 }
1009}
1010
1011pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
1017 let _guard = ThreadStateRestorer::new();
1018 f()
1019}
1020
1021#[cfg(test)]
1022mod test {
1023 use std::num::NonZeroU32;
1024 use std::sync::Arc;
1025 use std::time::Duration;
1026
1027 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
1028
1029 use crate::ImageAnimationState;
1030
1031 #[test]
1032 fn test_animated_image_update() {
1033 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1034 delay: Some(Duration::from_millis(100)),
1035 byte_range: 0..1,
1036 width: 100,
1037 height: 100,
1038 })
1039 .take(10)
1040 .collect();
1041 let image = RasterImage {
1042 metadata: ImageMetadata {
1043 width: 100,
1044 height: 100,
1045 },
1046 format: PixelFormat::BGRA8,
1047 id: None,
1048 bytes: Arc::new(vec![1]),
1049 frames: image_frames,
1050 cors_status: CorsStatus::Unsafe,
1051 loop_count: Some(Repeat::Infinite),
1052 is_opaque: false,
1053 };
1054 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1055
1056 assert_eq!(image_animation_state.active_frame, 0);
1057 assert_eq!(image_animation_state.frame_start_time, 0.0);
1058 assert_eq!(
1059 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1060 true
1061 );
1062 assert_eq!(image_animation_state.active_frame, 1);
1063 assert_eq!(image_animation_state.frame_start_time, 0.101);
1064 assert_eq!(
1065 image_animation_state.update_frame_for_animation_timeline_value(0.116),
1066 false
1067 );
1068 assert_eq!(image_animation_state.active_frame, 1);
1069 assert_eq!(image_animation_state.frame_start_time, 0.101);
1070 }
1071
1072 #[test]
1073 fn test_finite_image_repeat() {
1074 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1075 delay: Some(Duration::from_millis(100)),
1076 byte_range: 0..1,
1077 width: 100,
1078 height: 100,
1079 })
1080 .take(2)
1081 .collect();
1082 let image = RasterImage {
1083 metadata: ImageMetadata {
1084 width: 100,
1085 height: 100,
1086 },
1087 format: PixelFormat::BGRA8,
1088 id: None,
1089 bytes: Arc::new(vec![1]),
1090 frames: image_frames,
1091 cors_status: CorsStatus::Unsafe,
1092 loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1093 is_opaque: false,
1094 };
1095 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1096
1097 assert_eq!(image_animation_state.active_frame, 0);
1098 assert_eq!(image_animation_state.frame_start_time, 0.0);
1099 assert_eq!(
1100 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1101 true
1102 );
1103 assert_eq!(image_animation_state.active_frame, 1);
1104 assert_eq!(image_animation_state.frame_start_time, 0.101);
1105 assert_eq!(
1106 image_animation_state.update_frame_for_animation_timeline_value(0.202),
1107 false
1108 );
1109 assert_eq!(
1110 image_animation_state.update_frame_for_animation_timeline_value(0.303),
1111 false
1112 );
1113
1114 assert_eq!(image_animation_state.active_frame, 1);
1115 }
1116}