1#![deny(unsafe_code)]
10
11mod largest_contentful_paint_candidate;
12mod layout_damage;
13mod layout_dom;
14mod layout_element;
15mod layout_node;
16mod pseudo_element_chain;
17
18use std::any::Any;
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 background_hang_monitor_api::BackgroundHangMonitorRegister;
27use bitflags::bitflags;
28use embedder_traits::{Cursor, ScriptToEmbedderChan, Theme, UntrustedNodeAddress, ViewportDetails};
29use euclid::{Point2D, Rect};
30use fonts::{FontContext, WebFontDocumentContext, WebFontSetDifference};
31pub use largest_contentful_paint_candidate::LCPCandidate;
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::display_list::PaintTimingInfo;
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::{RangeAny, Utf32CodeUnits, Utf32CodeUnitsOrNodeOffset};
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 fn set_text_run_selection(&self, new_range: Option<RangeAny<Utf32CodeUnits>>) -> bool;
87
88 fn set_element_selection(&self, selected: bool) -> bool;
91}
92
93pub trait LayoutDataTrait: GenericLayoutDataTrait + Default {}
94pub type GenericLayoutData = dyn GenericLayoutDataTrait;
95
96#[derive(Default, MallocSizeOf)]
97pub struct StyleData {
98 pub element_data: ElementDataWrapper,
103
104 pub parallel: DomParallelInfo,
106}
107
108#[derive(Default, MallocSizeOf)]
110pub struct DomParallelInfo {
111 pub children_to_process: AtomicIsize,
113}
114
115#[derive(Clone, Copy, Debug, Eq, PartialEq)]
116pub enum LayoutNodeType {
117 Element(LayoutElementType),
118 Text,
119}
120
121#[derive(Clone, Copy, Debug, Eq, PartialEq)]
122pub enum LayoutElementType {
123 Element,
124 HTMLBodyElement,
125 HTMLButtonElement,
126 HTMLBRElement,
127 HTMLCanvasElement,
128 HTMLHtmlElement,
129 HTMLIFrameElement,
130 HTMLImageElement,
131 HTMLInputElement,
132 HTMLMediaElement,
133 HTMLObjectElement,
134 HTMLOptGroupElement,
135 HTMLOptionElement,
136 HTMLParagraphElement,
137 HTMLPreElement,
138 HTMLSelectElement,
139 HTMLTableCellElement,
140 HTMLTableColElement,
141 HTMLTableElement,
142 HTMLTableRowElement,
143 HTMLTableSectionElement,
144 HTMLTextAreaElement,
145 SVGImageElement,
146 SVGSVGElement,
147}
148
149pub struct HTMLCanvasData {
150 pub image_key: Option<ImageKey>,
151 pub width: u32,
152 pub height: u32,
153}
154
155pub struct SVGElementData<'dom> {
156 pub source: Option<Result<ServoUrl, ()>>,
158 pub width: Option<&'dom AttrValue>,
159 pub height: Option<&'dom AttrValue>,
160 pub svg_id: Uuid,
161 pub view_box: Option<&'dom AttrValue>,
162}
163
164impl SVGElementData<'_> {
165 pub fn ratio_from_view_box(&self) -> Option<f32> {
166 let mut iter = self.view_box?.chars();
167 let _min_x = parse_integer(&mut iter).ok()?;
168 let _min_y = parse_integer(&mut iter).ok()?;
169
170 let width = parse_unsigned_integer(&mut iter).ok()?;
171 if width == 0 {
172 return None;
173 }
174
175 let height = parse_unsigned_integer(&mut iter).ok()?;
176 if height == 0 {
177 return None;
178 }
179
180 let mut iter = iter.skip_while(|c| char_is_whitespace(*c));
181 iter.next().is_none().then(|| width as f32 / height as f32)
182 }
183}
184
185#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
187pub struct TrustedNodeAddress(pub *const c_void);
188
189#[expect(unsafe_code)]
190unsafe impl Send for TrustedNodeAddress {}
191
192#[derive(Debug)]
194pub enum PendingImageState {
195 Unrequested(ServoUrl),
196 PendingResponse,
197}
198
199#[derive(Debug, MallocSizeOf)]
201pub enum LayoutImageDestination {
202 BoxTreeConstruction,
203 DisplayListBuilding,
204}
205
206#[derive(Debug)]
210pub struct PendingImage {
211 pub state: PendingImageState,
212 pub node: UntrustedNodeAddress,
213 pub id: PendingImageId,
214 pub origin: ImmutableOrigin,
215 pub destination: LayoutImageDestination,
216 pub is_internal_request: InternalRequest,
217}
218
219#[derive(Debug)]
223pub struct PendingRasterizationImage {
224 pub node: UntrustedNodeAddress,
225 pub id: PendingImageId,
226 pub size: DeviceIntSize,
227}
228
229#[derive(Clone, Copy, Debug, MallocSizeOf)]
230pub struct MediaFrame {
231 pub image_key: webrender_api::ImageKey,
232 pub width: i32,
233 pub height: i32,
234}
235
236pub struct MediaMetadata {
237 pub width: u32,
238 pub height: u32,
239}
240
241pub struct HTMLMediaData {
242 pub current_frame: Option<MediaFrame>,
243 pub metadata: Option<MediaMetadata>,
244 pub poster_url: Option<ServoUrl>,
245}
246
247pub struct LayoutConfig {
248 pub id: PipelineId,
249 pub webview_id: WebViewId,
250 pub url: ServoUrl,
251 pub is_iframe: bool,
252 pub script_chan: GenericSender<ScriptThreadMessage>,
253 pub image_cache: Arc<dyn ImageCache>,
254 pub font_context: Arc<FontContext>,
255 pub time_profiler_chan: time::ProfilerChan,
256 pub paint_api: CrossProcessPaintApi,
257 pub viewport_details: ViewportDetails,
258 pub user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
259 pub theme: Theme,
260 pub embedder_chan: ScriptToEmbedderChan,
261}
262
263bitflags! {
264 #[derive(Copy, Clone)]
265 pub struct HitTestFlags: u8 {
266 const IncludeDomPosition = 0b0000_0001;
268 }
269}
270
271pub trait LayoutFactory: Send + Sync {
272 fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
273}
274
275pub trait Layout {
276 fn device(&self) -> &Device;
279
280 fn set_theme(&mut self, theme: Theme) -> bool;
284
285 fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
289
290 fn add_stylesheet(
295 &mut self,
296 stylesheet: ServoArc<Stylesheet>,
297 before_stylesheet: Option<ServoArc<Stylesheet>>,
298 );
299
300 fn exit_now(&mut self);
302
303 fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
306
307 fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
309
310 fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
312
313 fn remove_cached_image(&mut self, image_url: &ServoUrl);
315
316 fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
318
319 fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
322
323 fn register_paint_worklet_modules(
325 &mut self,
326 name: Atom,
327 properties: Vec<Atom>,
328 painter: Box<dyn Painter>,
329 );
330
331 fn set_scroll_offsets_from_renderer(
333 &mut self,
334 scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
335 );
336
337 fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
340
341 fn needs_new_display_list(&self) -> bool;
343
344 fn set_needs_new_display_list(&self);
346
347 fn node_rendering_type(
350 &self,
351 node: TrustedNodeAddress,
352 pseudo: Option<PseudoElement>,
353 ) -> NodeRenderingType;
354
355 fn query_containing_block(&self, node: TrustedNodeAddress) -> Option<UntrustedNodeAddress>;
356 fn query_containing_block_is_descendant(
357 &self,
358 root: TrustedNodeAddress,
359 possible_descendant: TrustedNodeAddress,
360 ) -> bool;
361 fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
362 fn query_box_area(
363 &self,
364 node: TrustedNodeAddress,
365 area: BoxAreaType,
366 exclude_transform_and_inline: bool,
367 ) -> Option<Rect<Au, CSSPixel>>;
368 fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectVec;
369 fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
370 fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
371 fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
372 fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
373 fn query_scroll_container(
376 &self,
377 node: Option<TrustedNodeAddress>,
378 flags: ScrollContainerQueryFlags,
379 ) -> Option<ScrollContainerResponse>;
380 fn query_resolved_style(
381 &self,
382 node: TrustedNodeAddress,
383 pseudo: Option<PseudoElement>,
384 property_id: PropertyId,
385 animations: DocumentAnimationSet,
386 animation_timeline_value: f64,
387 ) -> String;
388 fn query_resolved_font_style(
389 &self,
390 node: TrustedNodeAddress,
391 value: &str,
392 animations: DocumentAnimationSet,
393 animation_timeline_value: f64,
394 ) -> Option<ServoArc<Font>>;
395 fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
396 fn query_text_index(
400 &self,
401 node: TrustedNodeAddress,
402 point_in_viewport: Point2D<Au, CSSPixel>,
403 ) -> Option<(OpaqueNode, Utf32CodeUnits)>;
404 fn hit_test(&self, flags: HitTestFlags, point: LayoutPoint) -> HitTestResult;
405 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
406 fn stylist_mut(&mut self) -> &mut Stylist;
407
408 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
411
412 fn accessibility_active(&self) -> bool;
414
415 fn force_accessibility_update(&self) -> bool;
423
424 fn set_force_accessibility_update(&self);
426
427 fn font_context(&self) -> &Arc<FontContext>;
428}
429
430pub trait ScriptThreadFactory {
434 fn create(
436 state: InitialScriptState,
437 layout_factory: Arc<dyn LayoutFactory>,
438 image_cache_factory: Arc<dyn ImageCacheFactory>,
439 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
440 ) -> JoinHandle<()>;
441}
442
443#[derive(Copy, Clone)]
446pub enum BoxAreaType {
447 Content,
448 Padding,
449 Border,
450}
451
452pub type CSSPixelRectVec = Vec<Rect<Au, CSSPixel>>;
453
454#[derive(Copy, Clone)]
457pub enum NodeRenderingType {
458 Rendered,
460 DelegatesRendering,
462 NotRendered,
465}
466
467#[derive(Default)]
468pub struct PhysicalSides {
469 pub left: Au,
470 pub top: Au,
471 pub right: Au,
472 pub bottom: Au,
473}
474
475#[derive(Clone, Default)]
476pub struct OffsetParentResponse {
477 pub node_address: Option<UntrustedNodeAddress>,
478 pub rect: Rect<Au, CSSPixel>,
479}
480
481bitflags! {
482 #[derive(PartialEq)]
483 pub struct ScrollContainerQueryFlags: u8 {
484 const ForScrollParent = 1 << 0;
486 const Inclusive = 1 << 1;
488 }
489}
490
491#[derive(Clone, Copy, Debug, MallocSizeOf)]
492pub struct AxesOverflow {
493 pub x: Overflow,
494 pub y: Overflow,
495}
496
497impl Default for AxesOverflow {
498 fn default() -> Self {
499 Self {
500 x: Overflow::Visible,
501 y: Overflow::Visible,
502 }
503 }
504}
505
506impl From<&ComputedValues> for AxesOverflow {
507 fn from(style: &ComputedValues) -> Self {
508 Self {
509 x: style.clone_overflow_x(),
510 y: style.clone_overflow_y(),
511 }
512 }
513}
514
515impl AxesOverflow {
516 pub fn to_scrollable(&self) -> Self {
517 Self {
518 x: self.x.to_scrollable(),
519 y: self.y.to_scrollable(),
520 }
521 }
522
523 pub fn establishes_scroll_container(&self) -> bool {
525 self.x.is_scrollable()
528 }
529}
530
531#[derive(Clone)]
532pub enum ScrollContainerResponse {
533 Viewport(AxesOverflow),
534 Element(UntrustedNodeAddress, AxesOverflow),
535}
536
537#[derive(Debug, PartialEq)]
538pub enum QueryMsg {
539 BoxArea,
540 BoxAreas,
541 ClientRectQuery,
542 CurrentCSSZoomQuery,
543 EffectiveOverflow,
544 ElementInnerOuterTextQuery,
545 ElementsFromPoint,
546 InnerWindowDimensionsQuery,
547 NodesFromPointQuery,
548 OffsetParentQuery,
549 ScrollParentQuery,
550 ResolvedFontStyleQuery,
551 ResolvedStyleQuery(PropertyId),
554 ScrollingAreaOrOffsetQuery,
555 StyleQuery,
556 TextIndexQuery,
557 PaddingQuery,
558 FlushForUpdateTheRenderingQuery,
559}
560
561#[derive(Debug, PartialEq)]
567pub enum ReflowGoal {
568 UpdateTheRendering,
571
572 LayoutQuery(QueryMsg),
575
576 UpdateScrollNode(ExternalScrollId, LayoutVector2D),
580}
581
582#[derive(Clone, Debug, MallocSizeOf)]
583pub struct IFrameSize {
584 pub browsing_context_id: BrowsingContextId,
585 pub pipeline_id: PipelineId,
586 pub viewport_details: ViewportDetails,
587}
588
589pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
590
591bitflags! {
592 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
595 pub struct RestyleReason: u16 {
596 const StylesheetsChanged = 1 << 0;
597 const DOMChanged = 1 << 1;
598 const PendingRestyles = 1 << 2;
599 const HighlightedDOMNodeChanged = 1 << 3;
600 const ThemeChanged = 1 << 4;
601 const ViewportChanged = 1 << 5;
602 const PaintWorkletLoaded = 1 << 6;
603 }
604}
605
606malloc_size_of_is_0!(RestyleReason);
607
608impl RestyleReason {
609 pub fn needs_restyle(&self) -> bool {
610 !self.is_empty()
611 }
612}
613
614#[derive(Default)]
616pub struct ReflowResult {
617 pub reflow_phases_run: ReflowPhasesRun,
619 pub reflow_statistics: ReflowStatistics,
620 pub pending_images: Vec<PendingImage>,
622 pub pending_rasterization_images: Vec<PendingRasterizationImage>,
624 pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
628 pub iframe_sizes: Option<IFrameSizes>,
634 pub changed_web_fonts: WebFontSetDifference,
636 pub lcp_candidate: Option<LCPCandidate>,
638}
639
640bitflags! {
641 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
643 pub struct ReflowPhasesRun: u8 {
644 const RanLayout = 1 << 0;
645 const BuiltStackingContextTree = 1 << 2;
646 const BuiltDisplayList = 1 << 3;
647 const UpdatedScrollNodeOffset = 1 << 4;
648 const UpdatedImageData = 1 << 5;
652 const UpdatedAccessibilityTree = 1 << 6;
653 }
654}
655
656impl ReflowPhasesRun {
657 pub fn needs_frame(&self) -> bool {
658 self.intersects(
659 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
660 )
661 }
662}
663
664#[derive(Debug, Default)]
665pub struct ReflowStatistics {
666 pub rebuilt_fragment_count: u32,
668 pub restyle_fragment_count: u32,
670 pub only_descendants_changed_count: u32,
673 pub nodes_updated_from_dom: u32,
676 pub nodes_updated_from_tree: u32,
679 pub nodes_updated_bounds: u32,
682 pub nodes_in_tree_update: u32,
684}
685
686#[derive(Debug)]
689pub struct ReflowRequestRestyle {
690 pub reason: RestyleReason,
692 pub dirty_root: Option<TrustedNodeAddress>,
694 pub stylesheets_changed: bool,
696 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
698}
699
700#[derive(Debug)]
702pub struct ReflowRequest {
703 pub document: TrustedNodeAddress,
705 pub epoch: Epoch,
707 pub restyle: Option<ReflowRequestRestyle>,
709 pub viewport_details: ViewportDetails,
711 pub reflow_goal: ReflowGoal,
713 pub origin: ImmutableOrigin,
715 pub animation_timeline_value: f64,
717 pub animations: DocumentAnimationSet,
719 pub animating_images: Arc<RwLock<AnimatingImages>>,
721 pub highlighted_dom_node: Option<OpaqueNode>,
723 pub halt_lcp: bool,
727 pub paint_timing_info: PaintTimingInfo,
730 pub document_context: WebFontDocumentContext,
732 pub accessibility_damage: Option<Vec<(TrustedNodeAddress, AccessibilityDamage)>>,
734 pub rooted_nodes_for_accessibility_integrity_check: Option<FxHashSet<OpaqueNode>>,
738}
739
740impl ReflowRequest {
741 pub fn stylesheets_changed(&self) -> bool {
742 self.restyle
743 .as_ref()
744 .is_some_and(|restyle| restyle.stylesheets_changed)
745 }
746}
747
748#[derive(Debug, Default, MallocSizeOf)]
750pub struct PendingRestyle {
751 pub snapshot: Option<Snapshot>,
754
755 pub hint: RestyleHint,
757
758 pub damage: RestyleDamage,
760}
761
762#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
768pub enum FragmentType {
769 FragmentBody,
771 BeforePseudoContent,
773 AfterPseudoContent,
775}
776
777impl From<Option<PseudoElement>> for FragmentType {
778 fn from(value: Option<PseudoElement>) -> Self {
779 match value {
780 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
781 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
782 _ => FragmentType::FragmentBody,
783 }
784 }
785}
786
787pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
788 debug_assert_eq!(id & (fragment_type as usize), 0);
789 (id as u64) | (fragment_type as u64)
790}
791
792pub fn node_id_from_scroll_id(id: usize) -> usize {
793 id & !3
794}
795
796#[derive(Clone, Debug, MallocSizeOf)]
797pub struct ImageAnimationState {
798 #[conditional_malloc_size_of]
799 pub image: Arc<RasterImage>,
800 pub active_frame: usize,
801 frame_start_time: f64,
802
803 pub completed_loops: Option<u32>,
808}
809
810impl ImageAnimationState {
811 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
812 let completd_loops = match &image.loop_count {
813 None => unreachable!("Loop count of an animated Image should never be None"),
814 Some(repeat) if Repeat::Infinite == *repeat => None,
815 _ => Some(0),
816 };
817
818 Self {
819 image,
820 active_frame: 0,
821 frame_start_time: last_update_time,
822 completed_loops: completd_loops,
823 }
824 }
825
826 pub fn image_key(&self) -> Option<ImageKey> {
827 self.image.id
828 }
829
830 pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
831 if self.is_finished() {
832 return None;
833 }
834 let frame_delay = self
835 .image
836 .frames
837 .get(self.active_frame)
838 .expect("Image frame should always be valid")
839 .delay
840 .unwrap_or_default();
841
842 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
843 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
844 Some(frame_delay - time_since_frame_start.min(frame_delay))
845 }
846
847 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
851 if self.image.frames.len() <= 1 || self.is_finished() {
852 return false;
853 }
854 let time_interval_since_last_update = now - self.frame_start_time;
855 let mut remain_time_interval = time_interval_since_last_update -
856 self.image
857 .frames
858 .get(self.active_frame)
859 .unwrap()
860 .delay()
861 .unwrap()
862 .as_secs_f64();
863 let mut next_active_frame_id = self.active_frame;
864
865 let frame_count = self.image.frames.len();
866 while remain_time_interval > 0.0 {
867 next_active_frame_id = (next_active_frame_id + 1) % frame_count;
868
869 if next_active_frame_id == 0 {
871 self.advance_completed_loops();
872
873 if self.is_finished() {
876 if self.active_frame == frame_count - 1 {
877 return false;
878 }
879 self.active_frame = frame_count - 1;
880 self.frame_start_time = now;
881 return true;
882 }
883 }
884
885 remain_time_interval -= self
886 .image
887 .frames
888 .get(next_active_frame_id)
889 .unwrap()
890 .delay()
891 .unwrap()
892 .as_secs_f64();
893 }
894 if self.active_frame == next_active_frame_id {
895 return false;
896 }
897 self.active_frame = next_active_frame_id;
898 self.frame_start_time = now;
899 true
900 }
901
902 fn is_finished(&self) -> bool {
904 let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
905 return false;
906 };
907 self.completed_loops
908 .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
909 }
910
911 fn advance_completed_loops(&mut self) {
913 if let Some(completed_loops) = self.completed_loops.as_mut() {
914 *completed_loops += 1;
915 }
916 }
917}
918
919#[derive(Debug, Default)]
921pub struct HitTestResult {
922 pub items: Vec<HitTestResultItem>,
923 pub dom_position_for_selection: Option<(OpaqueNode, Utf32CodeUnitsOrNodeOffset)>,
924}
925
926#[derive(Debug)]
928pub struct HitTestResultItem {
929 pub node: OpaqueNode,
932 pub point_in_target: Point2D<f32, CSSPixel>,
935 pub cursor: Cursor,
938}
939
940#[derive(Debug, Default, MallocSizeOf)]
941pub struct AnimatingImages {
942 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
945 pub dirty: bool,
948}
949
950impl AnimatingImages {
951 pub fn maybe_insert_or_update(
952 &mut self,
953 node: OpaqueNode,
954 image: Arc<RasterImage>,
955 current_timeline_value: f64,
956 ) {
957 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
958 self.dirty = true;
959 ImageAnimationState::new(image.clone(), current_timeline_value)
960 });
961
962 if entry.image.id != image.id {
965 self.dirty = true;
966 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
967 }
968 }
969
970 pub fn remove(&mut self, node: OpaqueNode) {
971 if self.node_to_state_map.remove(&node).is_some() {
972 self.dirty = true;
973 }
974 }
975
976 pub fn clear_dirty(&mut self) -> bool {
978 std::mem::take(&mut self.dirty)
979 }
980
981 pub fn is_empty(&self) -> bool {
982 self.node_to_state_map.is_empty()
983 }
984}
985
986struct ThreadStateRestorer;
987
988impl ThreadStateRestorer {
989 fn new() -> Self {
990 #[cfg(debug_assertions)]
991 {
992 thread_state::exit(ThreadState::SCRIPT);
993 thread_state::enter(ThreadState::LAYOUT);
994 }
995 Self
996 }
997}
998
999impl Drop for ThreadStateRestorer {
1000 fn drop(&mut self) {
1001 #[cfg(debug_assertions)]
1002 {
1003 thread_state::exit(ThreadState::LAYOUT);
1004 thread_state::enter(ThreadState::SCRIPT);
1005 }
1006 }
1007}
1008
1009pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
1015 let _guard = ThreadStateRestorer::new();
1016 f()
1017}
1018
1019#[cfg(test)]
1020mod test {
1021 use std::num::NonZeroU32;
1022 use std::sync::Arc;
1023 use std::time::Duration;
1024
1025 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
1026
1027 use crate::ImageAnimationState;
1028
1029 #[test]
1030 fn test_animated_image_update() {
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(10)
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::Infinite),
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.116),
1064 false
1065 );
1066 assert_eq!(image_animation_state.active_frame, 1);
1067 assert_eq!(image_animation_state.frame_start_time, 0.101);
1068 }
1069
1070 #[test]
1071 fn test_finite_image_repeat() {
1072 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1073 delay: Some(Duration::from_millis(100)),
1074 byte_range: 0..1,
1075 width: 100,
1076 height: 100,
1077 })
1078 .take(2)
1079 .collect();
1080 let image = RasterImage {
1081 metadata: ImageMetadata {
1082 width: 100,
1083 height: 100,
1084 },
1085 format: PixelFormat::BGRA8,
1086 id: None,
1087 bytes: Arc::new(vec![1]),
1088 frames: image_frames,
1089 cors_status: CorsStatus::Unsafe,
1090 loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1091 is_opaque: false,
1092 };
1093 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1094
1095 assert_eq!(image_animation_state.active_frame, 0);
1096 assert_eq!(image_animation_state.frame_start_time, 0.0);
1097 assert_eq!(
1098 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1099 true
1100 );
1101 assert_eq!(image_animation_state.active_frame, 1);
1102 assert_eq!(image_animation_state.frame_start_time, 0.101);
1103 assert_eq!(
1104 image_animation_state.update_frame_for_animation_timeline_value(0.202),
1105 false
1106 );
1107 assert_eq!(
1108 image_animation_state.update_frame_for_animation_timeline_value(0.303),
1109 false
1110 );
1111
1112 assert_eq!(image_animation_state.active_frame, 1);
1113 }
1114}