1#![deny(unsafe_code)]
10
11mod largest_contentful_paint_candidate;
14mod layout_damage;
15mod layout_dom;
16mod layout_element;
17mod layout_node;
18mod pseudo_element_chain;
19
20use std::any::Any;
21use std::ops::Range;
22use std::rc::Rc;
23use std::sync::Arc;
24use std::sync::atomic::AtomicIsize;
25use std::thread::JoinHandle;
26use std::time::Duration;
27
28use app_units::Au;
29use atomic_refcell::AtomicRefCell;
30use background_hang_monitor_api::BackgroundHangMonitorRegister;
31use bitflags::bitflags;
32use embedder_traits::{Cursor, ScriptToEmbedderChan, Theme, UntrustedNodeAddress, ViewportDetails};
33use euclid::{Point2D, Rect};
34use fonts::{FontContext, TextByteRange, WebFontDocumentContext, WebFontSetDifference};
35pub use largest_contentful_paint_candidate::LCPCandidate;
36pub use layout_damage::{AccessibilityDamage, LayoutDamage};
37pub use layout_dom::{
38 DangerousStyleElementOf, DangerousStyleNodeOf, LayoutDomTypeBundle, LayoutElementOf,
39 LayoutNodeOf,
40};
41pub use layout_element::{DangerousStyleElement, LayoutElement};
42pub use layout_node::{DangerousStyleNode, LayoutNode};
43use libc::c_void;
44use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps, malloc_size_of_is_0};
45use malloc_size_of_derive::MallocSizeOf;
46use net_traits::image_cache::{ImageCache, ImageCacheFactory, PendingImageId};
47use net_traits::request::InternalRequest;
48use paint_api::display_list::PaintTimingInfo;
49use paint_api::CrossProcessPaintApi;
50use parking_lot::RwLock;
51use pixels::{RasterImage, Repeat};
52use profile_traits::mem::Report;
53use profile_traits::time;
54pub use pseudo_element_chain::PseudoElementChain;
55use rustc_hash::{FxHashMap, FxHashSet};
56use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
57use serde::{Deserialize, Serialize};
58use servo_arc::Arc as ServoArc;
59use servo_base::Epoch;
60use servo_base::generic_channel::GenericSender;
61use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
62use servo_base::text::{Utf32CodeUnits, Utf32CodeUnitsOrNodeOffset};
63use servo_url::{ImmutableOrigin, ServoUrl};
64use style::Atom;
65use style::animation::DocumentAnimationSet;
66use style::attr::{AttrValue, parse_integer, parse_unsigned_integer};
67use style::context::QuirksMode;
68use style::data::ElementDataWrapper;
69use style::device::Device;
70use style::dom::OpaqueNode;
71use style::invalidation::element::restyle_hints::RestyleHint;
72use style::properties::style_structs::Font;
73use style::properties::{ComputedValues, PropertyId};
74use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
75use style::str::char_is_whitespace;
76use style::stylesheets::{DocumentStyleSheet, Stylesheet};
77use style::stylist::Stylist;
78#[cfg(debug_assertions)]
79use style::thread_state::{self, ThreadState};
80use style::values::computed::Overflow;
81use style_traits::CSSPixel;
82use uuid::Uuid;
83use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
84use webrender_api::{ExternalScrollId, ImageKey};
85
86pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait + Send + Sync + 'static {
87 fn as_any(&self) -> &dyn Any;
88}
89
90pub trait LayoutDataTrait: GenericLayoutDataTrait + Default {}
91pub type GenericLayoutData = dyn GenericLayoutDataTrait;
92
93#[derive(Default, MallocSizeOf)]
94pub struct StyleData {
95 pub element_data: ElementDataWrapper,
100
101 pub parallel: DomParallelInfo,
103}
104
105#[derive(Default, MallocSizeOf)]
107pub struct DomParallelInfo {
108 pub children_to_process: AtomicIsize,
110}
111
112#[derive(Clone, Copy, Debug, Eq, PartialEq)]
113pub enum LayoutNodeType {
114 Element(LayoutElementType),
115 Text,
116}
117
118#[derive(Clone, Copy, Debug, Eq, PartialEq)]
119pub enum LayoutElementType {
120 Element,
121 HTMLBodyElement,
122 HTMLButtonElement,
123 HTMLBRElement,
124 HTMLCanvasElement,
125 HTMLHtmlElement,
126 HTMLIFrameElement,
127 HTMLImageElement,
128 HTMLInputElement,
129 HTMLMediaElement,
130 HTMLObjectElement,
131 HTMLOptGroupElement,
132 HTMLOptionElement,
133 HTMLParagraphElement,
134 HTMLPreElement,
135 HTMLSelectElement,
136 HTMLTableCellElement,
137 HTMLTableColElement,
138 HTMLTableElement,
139 HTMLTableRowElement,
140 HTMLTableSectionElement,
141 HTMLTextAreaElement,
142 SVGImageElement,
143 SVGSVGElement,
144}
145
146#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
150pub struct ScriptSelection {
151 pub range: TextByteRange,
153 pub character_range: Range<usize>,
155 pub enabled: bool,
158}
159
160pub type SharedSelection = Arc<AtomicRefCell<ScriptSelection>>;
161pub struct HTMLCanvasData {
162 pub image_key: Option<ImageKey>,
163 pub width: u32,
164 pub height: u32,
165}
166
167pub struct SVGElementData<'dom> {
168 pub source: Option<Result<ServoUrl, ()>>,
170 pub width: Option<&'dom AttrValue>,
171 pub height: Option<&'dom AttrValue>,
172 pub svg_id: Uuid,
173 pub view_box: Option<&'dom AttrValue>,
174}
175
176impl SVGElementData<'_> {
177 pub fn ratio_from_view_box(&self) -> Option<f32> {
178 let mut iter = self.view_box?.chars();
179 let _min_x = parse_integer(&mut iter).ok()?;
180 let _min_y = parse_integer(&mut iter).ok()?;
181
182 let width = parse_unsigned_integer(&mut iter).ok()?;
183 if width == 0 {
184 return None;
185 }
186
187 let height = parse_unsigned_integer(&mut iter).ok()?;
188 if height == 0 {
189 return None;
190 }
191
192 let mut iter = iter.skip_while(|c| char_is_whitespace(*c));
193 iter.next().is_none().then(|| width as f32 / height as f32)
194 }
195}
196
197#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
199pub struct TrustedNodeAddress(pub *const c_void);
200
201#[expect(unsafe_code)]
202unsafe impl Send for TrustedNodeAddress {}
203
204#[derive(Debug)]
206pub enum PendingImageState {
207 Unrequested(ServoUrl),
208 PendingResponse,
209}
210
211#[derive(Debug, MallocSizeOf)]
213pub enum LayoutImageDestination {
214 BoxTreeConstruction,
215 DisplayListBuilding,
216}
217
218#[derive(Debug)]
222pub struct PendingImage {
223 pub state: PendingImageState,
224 pub node: UntrustedNodeAddress,
225 pub id: PendingImageId,
226 pub origin: ImmutableOrigin,
227 pub destination: LayoutImageDestination,
228 pub is_internal_request: InternalRequest,
229}
230
231#[derive(Debug)]
235pub struct PendingRasterizationImage {
236 pub node: UntrustedNodeAddress,
237 pub id: PendingImageId,
238 pub size: DeviceIntSize,
239}
240
241#[derive(Clone, Copy, Debug, MallocSizeOf)]
242pub struct MediaFrame {
243 pub image_key: webrender_api::ImageKey,
244 pub width: i32,
245 pub height: i32,
246}
247
248pub struct MediaMetadata {
249 pub width: u32,
250 pub height: u32,
251}
252
253#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)]
262pub enum WebVttPositionAlign {
263 LineLeft,
264 Center,
265 LineRight,
266 #[default]
267 Auto,
268}
269
270#[derive(Clone, Copy, Debug, Default, MallocSizeOf, PartialEq)]
272pub enum WebVttTextAlign {
273 Start,
274 #[default]
275 Center,
276 End,
277 Left,
278 Right,
279}
280
281#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
284pub struct WebVttCueBoxData {
285 pub text_lines: Vec<String>,
288 pub line: Option<f64>,
290 pub snap_to_lines: bool,
292 pub position: Option<f64>,
294 pub position_align: WebVttPositionAlign,
296 pub align: WebVttTextAlign,
298 pub size: f64,
300 pub order: usize,
303}
304
305pub struct HTMLMediaData {
306 pub current_frame: Option<MediaFrame>,
307 pub metadata: Option<MediaMetadata>,
308 pub poster_url: Option<ServoUrl>,
309 pub cue_overlays: Vec<WebVttCueBoxData>,
312}
313
314pub struct LayoutConfig {
315 pub id: PipelineId,
316 pub webview_id: WebViewId,
317 pub url: ServoUrl,
318 pub is_iframe: bool,
319 pub script_chan: GenericSender<ScriptThreadMessage>,
320 pub image_cache: Arc<dyn ImageCache>,
321 pub font_context: Arc<FontContext>,
322 pub time_profiler_chan: time::ProfilerChan,
323 pub paint_api: CrossProcessPaintApi,
324 pub viewport_details: ViewportDetails,
325 pub user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
326 pub theme: Theme,
327 pub embedder_chan: ScriptToEmbedderChan,
328}
329
330bitflags! {
331 #[derive(Copy, Clone)]
332 pub struct HitTestFlags: u8 {
333 const IncludeDomPosition = 0b0000_0001;
335 }
336}
337
338pub trait LayoutFactory: Send + Sync {
339 fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
340}
341
342pub trait Layout {
343 fn device(&self) -> &Device;
346
347 fn set_theme(&mut self, theme: Theme) -> bool;
351
352 fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
356
357 fn add_stylesheet(
362 &mut self,
363 stylesheet: ServoArc<Stylesheet>,
364 before_stylesheet: Option<ServoArc<Stylesheet>>,
365 );
366
367 fn exit_now(&mut self);
369
370 fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
373
374 fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
376
377 fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
379
380 fn remove_cached_image(&mut self, image_url: &ServoUrl);
382
383 fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
385
386 fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
389
390 fn register_paint_worklet_modules(
392 &mut self,
393 name: Atom,
394 properties: Vec<Atom>,
395 painter: Box<dyn Painter>,
396 );
397
398 fn set_scroll_offsets_from_renderer(
400 &mut self,
401 scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
402 );
403
404 fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
407
408 fn needs_new_display_list(&self) -> bool;
410
411 fn set_needs_new_display_list(&self);
413
414 fn node_rendering_type(
417 &self,
418 node: TrustedNodeAddress,
419 pseudo: Option<PseudoElement>,
420 ) -> NodeRenderingType;
421
422 fn query_containing_block(&self, node: TrustedNodeAddress) -> Option<UntrustedNodeAddress>;
423 fn query_containing_block_is_descendant(
424 &self,
425 root: TrustedNodeAddress,
426 possible_descendant: TrustedNodeAddress,
427 ) -> bool;
428 fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
429 fn query_box_area(
430 &self,
431 node: TrustedNodeAddress,
432 area: BoxAreaType,
433 exclude_transform_and_inline: bool,
434 ) -> Option<Rect<Au, CSSPixel>>;
435 fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectVec;
436 fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
437 fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
438 fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
439 fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
440 fn query_scroll_container(
443 &self,
444 node: Option<TrustedNodeAddress>,
445 flags: ScrollContainerQueryFlags,
446 ) -> Option<ScrollContainerResponse>;
447 fn query_resolved_style(
448 &self,
449 node: TrustedNodeAddress,
450 pseudo: Option<PseudoElement>,
451 property_id: PropertyId,
452 animations: DocumentAnimationSet,
453 animation_timeline_value: f64,
454 ) -> String;
455 fn query_resolved_font_style(
456 &self,
457 node: TrustedNodeAddress,
458 value: &str,
459 animations: DocumentAnimationSet,
460 animation_timeline_value: f64,
461 ) -> Option<ServoArc<Font>>;
462 fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
463 fn query_text_index(
467 &self,
468 node: TrustedNodeAddress,
469 point_in_viewport: Point2D<Au, CSSPixel>,
470 ) -> Option<(OpaqueNode, Utf32CodeUnits)>;
471 fn hit_test(&self, flags: HitTestFlags, point: LayoutPoint) -> HitTestResult;
472 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
473 fn stylist_mut(&mut self) -> &mut Stylist;
474
475 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
478
479 fn accessibility_active(&self) -> bool;
481
482 fn force_accessibility_update(&self) -> bool;
490
491 fn set_force_accessibility_update(&self);
493
494 fn font_context(&self) -> &Arc<FontContext>;
495}
496
497pub trait ScriptThreadFactory {
501 fn create(
503 state: InitialScriptState,
504 layout_factory: Arc<dyn LayoutFactory>,
505 image_cache_factory: Arc<dyn ImageCacheFactory>,
506 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
507 ) -> JoinHandle<()>;
508}
509
510#[derive(Copy, Clone)]
513pub enum BoxAreaType {
514 Content,
515 Padding,
516 Border,
517}
518
519pub type CSSPixelRectVec = Vec<Rect<Au, CSSPixel>>;
520
521#[derive(Copy, Clone)]
524pub enum NodeRenderingType {
525 Rendered,
527 DelegatesRendering,
529 NotRendered,
532}
533
534#[derive(Default)]
535pub struct PhysicalSides {
536 pub left: Au,
537 pub top: Au,
538 pub right: Au,
539 pub bottom: Au,
540}
541
542#[derive(Clone, Default)]
543pub struct OffsetParentResponse {
544 pub node_address: Option<UntrustedNodeAddress>,
545 pub rect: Rect<Au, CSSPixel>,
546}
547
548bitflags! {
549 #[derive(PartialEq)]
550 pub struct ScrollContainerQueryFlags: u8 {
551 const ForScrollParent = 1 << 0;
553 const Inclusive = 1 << 1;
555 }
556}
557
558#[derive(Clone, Copy, Debug, MallocSizeOf)]
559pub struct AxesOverflow {
560 pub x: Overflow,
561 pub y: Overflow,
562}
563
564impl Default for AxesOverflow {
565 fn default() -> Self {
566 Self {
567 x: Overflow::Visible,
568 y: Overflow::Visible,
569 }
570 }
571}
572
573impl From<&ComputedValues> for AxesOverflow {
574 fn from(style: &ComputedValues) -> Self {
575 Self {
576 x: style.clone_overflow_x(),
577 y: style.clone_overflow_y(),
578 }
579 }
580}
581
582impl AxesOverflow {
583 pub fn to_scrollable(&self) -> Self {
584 Self {
585 x: self.x.to_scrollable(),
586 y: self.y.to_scrollable(),
587 }
588 }
589
590 pub fn establishes_scroll_container(&self) -> bool {
592 self.x.is_scrollable()
595 }
596}
597
598#[derive(Clone)]
599pub enum ScrollContainerResponse {
600 Viewport(AxesOverflow),
601 Element(UntrustedNodeAddress, AxesOverflow),
602}
603
604#[derive(Debug, PartialEq)]
605pub enum QueryMsg {
606 BoxArea,
607 BoxAreas,
608 ClientRectQuery,
609 CurrentCSSZoomQuery,
610 EffectiveOverflow,
611 ElementInnerOuterTextQuery,
612 ElementsFromPoint,
613 InnerWindowDimensionsQuery,
614 NodesFromPointQuery,
615 OffsetParentQuery,
616 ScrollParentQuery,
617 ResolvedFontStyleQuery,
618 ResolvedStyleQuery(PropertyId),
621 ScrollingAreaOrOffsetQuery,
622 StyleQuery,
623 TextIndexQuery,
624 PaddingQuery,
625 FlushForUpdateTheRenderingQuery,
626}
627
628#[derive(Debug, PartialEq)]
634pub enum ReflowGoal {
635 UpdateTheRendering,
638
639 LayoutQuery(QueryMsg),
642
643 UpdateScrollNode(ExternalScrollId, LayoutVector2D),
647}
648
649#[derive(Clone, Debug, MallocSizeOf)]
650pub struct IFrameSize {
651 pub browsing_context_id: BrowsingContextId,
652 pub pipeline_id: PipelineId,
653 pub viewport_details: ViewportDetails,
654}
655
656pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
657
658bitflags! {
659 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
662 pub struct RestyleReason: u16 {
663 const StylesheetsChanged = 1 << 0;
664 const DOMChanged = 1 << 1;
665 const PendingRestyles = 1 << 2;
666 const HighlightedDOMNodeChanged = 1 << 3;
667 const ThemeChanged = 1 << 4;
668 const ViewportChanged = 1 << 5;
669 const PaintWorkletLoaded = 1 << 6;
670 }
671}
672
673malloc_size_of_is_0!(RestyleReason);
674
675impl RestyleReason {
676 pub fn needs_restyle(&self) -> bool {
677 !self.is_empty()
678 }
679}
680
681#[derive(Default)]
683pub struct ReflowResult {
684 pub reflow_phases_run: ReflowPhasesRun,
686 pub reflow_statistics: ReflowStatistics,
687 pub pending_images: Vec<PendingImage>,
689 pub pending_rasterization_images: Vec<PendingRasterizationImage>,
691 pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
695 pub iframe_sizes: Option<IFrameSizes>,
701 pub changed_web_fonts: WebFontSetDifference,
703 pub lcp_candidate: Option<LCPCandidate>,
705}
706
707bitflags! {
708 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
710 pub struct ReflowPhasesRun: u8 {
711 const RanLayout = 1 << 0;
712 const BuiltStackingContextTree = 1 << 2;
713 const BuiltDisplayList = 1 << 3;
714 const UpdatedScrollNodeOffset = 1 << 4;
715 const UpdatedImageData = 1 << 5;
719 const UpdatedAccessibilityTree = 1 << 6;
720 }
721}
722
723impl ReflowPhasesRun {
724 pub fn needs_frame(&self) -> bool {
725 self.intersects(
726 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
727 )
728 }
729}
730
731#[derive(Debug, Default)]
732pub struct ReflowStatistics {
733 pub rebuilt_fragment_count: u32,
735 pub restyle_fragment_count: u32,
737 pub only_descendants_changed_count: u32,
740 pub nodes_updated_from_dom: u32,
743 pub nodes_updated_from_tree: u32,
746 pub nodes_updated_bounds: u32,
749 pub nodes_in_tree_update: u32,
751}
752
753#[derive(Debug)]
756pub struct ReflowRequestRestyle {
757 pub reason: RestyleReason,
759 pub dirty_root: Option<TrustedNodeAddress>,
761 pub stylesheets_changed: bool,
763 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
765}
766
767#[derive(Debug)]
769pub struct ReflowRequest {
770 pub document: TrustedNodeAddress,
772 pub epoch: Epoch,
774 pub restyle: Option<ReflowRequestRestyle>,
776 pub viewport_details: ViewportDetails,
778 pub reflow_goal: ReflowGoal,
780 pub origin: ImmutableOrigin,
782 pub animation_timeline_value: f64,
784 pub animations: DocumentAnimationSet,
786 pub animating_images: Arc<RwLock<AnimatingImages>>,
788 pub highlighted_dom_node: Option<OpaqueNode>,
790 pub halt_lcp: bool,
794 pub paint_timing_eligible: bool,
799 pub paint_timing_info: PaintTimingInfo,
802 pub document_context: WebFontDocumentContext,
804 pub accessibility_damage: Option<Vec<(TrustedNodeAddress, AccessibilityDamage)>>,
806 pub rooted_nodes_for_accessibility_integrity_check: Option<FxHashSet<OpaqueNode>>,
810}
811
812impl ReflowRequest {
813 pub fn stylesheets_changed(&self) -> bool {
814 self.restyle
815 .as_ref()
816 .is_some_and(|restyle| restyle.stylesheets_changed)
817 }
818}
819
820#[derive(Debug, Default, MallocSizeOf)]
822pub struct PendingRestyle {
823 pub snapshot: Option<Snapshot>,
826
827 pub hint: RestyleHint,
829
830 pub damage: RestyleDamage,
832}
833
834#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
840pub enum FragmentType {
841 FragmentBody,
843 BeforePseudoContent,
845 AfterPseudoContent,
847}
848
849impl From<Option<PseudoElement>> for FragmentType {
850 fn from(value: Option<PseudoElement>) -> Self {
851 match value {
852 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
853 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
854 _ => FragmentType::FragmentBody,
855 }
856 }
857}
858
859pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
860 debug_assert_eq!(id & (fragment_type as usize), 0);
861 (id as u64) | (fragment_type as u64)
862}
863
864pub fn node_id_from_scroll_id(id: usize) -> usize {
865 id & !3
866}
867
868#[derive(Clone, Debug, MallocSizeOf)]
869pub struct ImageAnimationState {
870 #[conditional_malloc_size_of]
871 pub image: Arc<RasterImage>,
872 pub active_frame: usize,
873 frame_start_time: f64,
874
875 pub completed_loops: Option<u32>,
880}
881
882impl ImageAnimationState {
883 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
884 let completd_loops = match &image.loop_count {
885 None => unreachable!("Loop count of an animated Image should never be None"),
886 Some(repeat) if Repeat::Infinite == *repeat => None,
887 _ => Some(0),
888 };
889
890 Self {
891 image,
892 active_frame: 0,
893 frame_start_time: last_update_time,
894 completed_loops: completd_loops,
895 }
896 }
897
898 pub fn image_key(&self) -> Option<ImageKey> {
899 self.image.id
900 }
901
902 pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
903 if self.is_finished() {
904 return None;
905 }
906 let frame_delay = self
907 .image
908 .frames
909 .get(self.active_frame)
910 .expect("Image frame should always be valid")
911 .delay
912 .unwrap_or_default();
913
914 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
915 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
916 Some(frame_delay - time_since_frame_start.min(frame_delay))
917 }
918
919 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
923 if self.image.frames.len() <= 1 || self.is_finished() {
924 return false;
925 }
926 let time_interval_since_last_update = now - self.frame_start_time;
927 let mut remain_time_interval = time_interval_since_last_update -
928 self.image
929 .frames
930 .get(self.active_frame)
931 .unwrap()
932 .delay()
933 .unwrap()
934 .as_secs_f64();
935 let mut next_active_frame_id = self.active_frame;
936
937 let frame_count = self.image.frames.len();
938 while remain_time_interval > 0.0 {
939 next_active_frame_id = (next_active_frame_id + 1) % frame_count;
940
941 if next_active_frame_id == 0 {
943 self.advance_completed_loops();
944
945 if self.is_finished() {
948 if self.active_frame == frame_count - 1 {
949 return false;
950 }
951 self.active_frame = frame_count - 1;
952 self.frame_start_time = now;
953 return true;
954 }
955 }
956
957 remain_time_interval -= self
958 .image
959 .frames
960 .get(next_active_frame_id)
961 .unwrap()
962 .delay()
963 .unwrap()
964 .as_secs_f64();
965 }
966 if self.active_frame == next_active_frame_id {
967 return false;
968 }
969 self.active_frame = next_active_frame_id;
970 self.frame_start_time = now;
971 true
972 }
973
974 fn is_finished(&self) -> bool {
976 let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
977 return false;
978 };
979 self.completed_loops
980 .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
981 }
982
983 fn advance_completed_loops(&mut self) {
985 if let Some(completed_loops) = self.completed_loops.as_mut() {
986 *completed_loops += 1;
987 }
988 }
989}
990
991#[derive(Debug, Default)]
993pub struct HitTestResult {
994 pub items: Vec<HitTestResultItem>,
995 pub dom_position_for_selection: Option<(OpaqueNode, Utf32CodeUnitsOrNodeOffset)>,
996}
997
998#[derive(Debug)]
1000pub struct HitTestResultItem {
1001 pub node: OpaqueNode,
1004 pub point_in_target: Point2D<f32, CSSPixel>,
1007 pub cursor: Cursor,
1010}
1011
1012#[derive(Debug, Default, MallocSizeOf)]
1013pub struct AnimatingImages {
1014 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
1017 pub dirty: bool,
1020}
1021
1022impl AnimatingImages {
1023 pub fn maybe_insert_or_update(
1024 &mut self,
1025 node: OpaqueNode,
1026 image: Arc<RasterImage>,
1027 current_timeline_value: f64,
1028 ) {
1029 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
1030 self.dirty = true;
1031 ImageAnimationState::new(image.clone(), current_timeline_value)
1032 });
1033
1034 if entry.image.id != image.id {
1037 self.dirty = true;
1038 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
1039 }
1040 }
1041
1042 pub fn remove(&mut self, node: OpaqueNode) {
1043 if self.node_to_state_map.remove(&node).is_some() {
1044 self.dirty = true;
1045 }
1046 }
1047
1048 pub fn clear_dirty(&mut self) -> bool {
1050 std::mem::take(&mut self.dirty)
1051 }
1052
1053 pub fn is_empty(&self) -> bool {
1054 self.node_to_state_map.is_empty()
1055 }
1056}
1057
1058struct ThreadStateRestorer;
1059
1060impl ThreadStateRestorer {
1061 fn new() -> Self {
1062 #[cfg(debug_assertions)]
1063 {
1064 thread_state::exit(ThreadState::SCRIPT);
1065 thread_state::enter(ThreadState::LAYOUT);
1066 }
1067 Self
1068 }
1069}
1070
1071impl Drop for ThreadStateRestorer {
1072 fn drop(&mut self) {
1073 #[cfg(debug_assertions)]
1074 {
1075 thread_state::exit(ThreadState::LAYOUT);
1076 thread_state::enter(ThreadState::SCRIPT);
1077 }
1078 }
1079}
1080
1081pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
1087 let _guard = ThreadStateRestorer::new();
1088 f()
1089}
1090
1091#[cfg(test)]
1092mod test {
1093 use std::num::NonZeroU32;
1094 use std::sync::Arc;
1095 use std::time::Duration;
1096
1097 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
1098
1099 use crate::ImageAnimationState;
1100
1101 #[test]
1102 fn test_animated_image_update() {
1103 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1104 delay: Some(Duration::from_millis(100)),
1105 byte_range: 0..1,
1106 width: 100,
1107 height: 100,
1108 })
1109 .take(10)
1110 .collect();
1111 let image = RasterImage {
1112 metadata: ImageMetadata {
1113 width: 100,
1114 height: 100,
1115 },
1116 format: PixelFormat::BGRA8,
1117 id: None,
1118 bytes: Arc::new(vec![1]),
1119 frames: image_frames,
1120 cors_status: CorsStatus::Unsafe,
1121 loop_count: Some(Repeat::Infinite),
1122 is_opaque: false,
1123 };
1124 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1125
1126 assert_eq!(image_animation_state.active_frame, 0);
1127 assert_eq!(image_animation_state.frame_start_time, 0.0);
1128 assert_eq!(
1129 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1130 true
1131 );
1132 assert_eq!(image_animation_state.active_frame, 1);
1133 assert_eq!(image_animation_state.frame_start_time, 0.101);
1134 assert_eq!(
1135 image_animation_state.update_frame_for_animation_timeline_value(0.116),
1136 false
1137 );
1138 assert_eq!(image_animation_state.active_frame, 1);
1139 assert_eq!(image_animation_state.frame_start_time, 0.101);
1140 }
1141
1142 #[test]
1143 fn test_finite_image_repeat() {
1144 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1145 delay: Some(Duration::from_millis(100)),
1146 byte_range: 0..1,
1147 width: 100,
1148 height: 100,
1149 })
1150 .take(2)
1151 .collect();
1152 let image = RasterImage {
1153 metadata: ImageMetadata {
1154 width: 100,
1155 height: 100,
1156 },
1157 format: PixelFormat::BGRA8,
1158 id: None,
1159 bytes: Arc::new(vec![1]),
1160 frames: image_frames,
1161 cors_status: CorsStatus::Unsafe,
1162 loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1163 is_opaque: false,
1164 };
1165 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1166
1167 assert_eq!(image_animation_state.active_frame, 0);
1168 assert_eq!(image_animation_state.frame_start_time, 0.0);
1169 assert_eq!(
1170 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1171 true
1172 );
1173 assert_eq!(image_animation_state.active_frame, 1);
1174 assert_eq!(image_animation_state.frame_start_time, 0.101);
1175 assert_eq!(
1176 image_animation_state.update_frame_for_animation_timeline_value(0.202),
1177 false
1178 );
1179 assert_eq!(
1180 image_animation_state.update_frame_for_animation_timeline_value(0.303),
1181 false
1182 );
1183
1184 assert_eq!(image_animation_state.active_frame, 1);
1185 }
1186}