1#![deny(unsafe_code)]
10
11mod container_timing_record;
12mod largest_contentful_paint_candidate;
13mod layout_damage;
14mod layout_dom;
15mod layout_element;
16mod layout_node;
17mod pseudo_element_chain;
18
19use std::any::Any;
20use std::rc::Rc;
21use std::sync::Arc;
22use std::sync::atomic::AtomicIsize;
23use std::thread::JoinHandle;
24use std::time::Duration;
25
26use accesskit::{Action, ActionData, ActionRequest};
27use app_units::Au;
28use background_hang_monitor_api::BackgroundHangMonitorRegister;
29use bitflags::bitflags;
30pub use container_timing_record::ContainerTimingRecord;
31use embedder_traits::{Cursor, ScriptToEmbedderChan, Theme, UntrustedNodeAddress, ViewportDetails};
32use euclid::{Point2D, Rect};
33use fonts::{FontContext, WebFontDocumentContext, WebFontSetDifference};
34pub use largest_contentful_paint_candidate::LCPCandidate;
35pub use layout_damage::{AccessibilityDamage, LayoutDamage};
36pub use layout_dom::{
37 DangerousStyleElementOf, DangerousStyleNodeOf, LayoutDomTypeBundle, LayoutElementOf,
38 LayoutNodeOf,
39};
40pub use layout_element::{DangerousStyleElement, LayoutElement};
41pub use layout_node::{DangerousStyleNode, LayoutNode};
42use libc::c_void;
43use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps, malloc_size_of_is_0};
44use malloc_size_of_derive::MallocSizeOf;
45use net_traits::image_cache::{ImageCache, ImageCacheFactory, PendingImageId};
46use net_traits::request::InternalRequest;
47use paint_api::CrossProcessPaintApi;
48use paint_api::display_list::PaintTimingInfo;
49use parking_lot::RwLock;
50use pixels::{RasterImage, Repeat};
51use profile_traits::mem::Report;
52use profile_traits::time;
53pub use pseudo_element_chain::PseudoElementChain;
54use rustc_hash::{FxHashMap, FxHashSet};
55use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
56use serde::{Deserialize, Serialize};
57use servo_arc::Arc as ServoArc;
58use servo_base::Epoch;
59use servo_base::generic_channel::GenericSender;
60use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
61use servo_base::text::{RangeAny, Utf32CodeUnits, Utf32CodeUnitsOrNodeOffset};
62use servo_url::{ImmutableOrigin, ServoUrl};
63use style::Atom;
64use style::animation::DocumentAnimationSet;
65use style::attr::{AttrValue, parse_double};
66use style::context::QuirksMode;
67use style::data::ElementDataWrapper;
68use style::device::Device;
69use style::dom::OpaqueNode;
70use style::invalidation::element::restyle_hints::RestyleHint;
71use style::properties::style_structs::Font;
72use style::properties::{ComputedValues, PropertyId};
73use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
74use style::str::char_is_whitespace;
75use style::stylesheets::{DocumentStyleSheet, Stylesheet};
76use style::stylist::Stylist;
77#[cfg(debug_assertions)]
78use style::thread_state::{self, ThreadState};
79use style::values::computed::Overflow;
80use style_traits::CSSPixel;
81use uuid::Uuid;
82use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
83use webrender_api::{ExternalScrollId, ImageKey};
84
85pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait + Send + Sync + 'static {
86 fn as_any(&self) -> &dyn Any;
87
88 fn set_text_run_selection(&self, new_range: Option<RangeAny<Utf32CodeUnits>>) -> bool;
90
91 fn set_element_selection(&self, selected: bool) -> bool;
94
95 fn rendered_text(&self, range: RangeAny<Utf32CodeUnits>) -> Option<String>;
98}
99
100pub trait LayoutDataTrait: GenericLayoutDataTrait + Default {}
101pub type GenericLayoutData = dyn GenericLayoutDataTrait;
102
103#[derive(Default, MallocSizeOf)]
104pub struct StyleData {
105 pub element_data: ElementDataWrapper,
110
111 pub parallel: DomParallelInfo,
113}
114
115#[derive(Default, MallocSizeOf)]
117pub struct DomParallelInfo {
118 pub children_to_process: AtomicIsize,
120}
121
122#[derive(Clone, Copy, Debug, Eq, PartialEq)]
123pub enum LayoutNodeType {
124 Element(LayoutElementType),
125 Text,
126}
127
128#[derive(Clone, Copy, Debug, Eq, PartialEq)]
129pub enum LayoutElementType {
130 Element,
131 HTMLBodyElement,
132 HTMLButtonElement,
133 HTMLBRElement,
134 HTMLCanvasElement,
135 HTMLHtmlElement,
136 HTMLIFrameElement,
137 HTMLImageElement,
138 HTMLInputElement,
139 HTMLMediaElement,
140 HTMLObjectElement,
141 HTMLOptGroupElement,
142 HTMLOptionElement,
143 HTMLParagraphElement,
144 HTMLPreElement,
145 HTMLSelectElement,
146 HTMLTableCellElement,
147 HTMLTableColElement,
148 HTMLTableElement,
149 HTMLTableRowElement,
150 HTMLTableSectionElement,
151 HTMLTextAreaElement,
152 SVGImageElement,
153 SVGSVGElement,
154}
155
156pub struct HTMLCanvasData {
157 pub image_key: Option<ImageKey>,
158 pub width: u32,
159 pub height: u32,
160}
161
162pub struct SVGElementData<'dom> {
163 pub source: Option<Result<ServoUrl, ()>>,
165 pub width: Option<&'dom AttrValue>,
166 pub height: Option<&'dom AttrValue>,
167 pub svg_id: Uuid,
168 pub view_box: Option<&'dom AttrValue>,
170}
171
172impl SVGElementData<'_> {
173 pub fn ratio_from_view_box(&self) -> Option<f32> {
175 let mut iter = self
176 .view_box?
177 .split(|c| char_is_whitespace(c) || c == ',')
178 .filter(|part| !part.is_empty());
179 let _min_x = parse_double(iter.next()?).ok()?;
180 let _min_y = parse_double(iter.next()?).ok()?;
181
182 let width = parse_double(iter.next()?).ok()?;
183 if width == 0f64 {
184 return None;
185 }
186
187 let height = parse_double(iter.next()?).ok()?;
188 if height == 0f64 {
189 return None;
190 }
191
192 iter.next().is_none().then(|| width as f32 / height as f32)
193 }
194}
195
196#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
198pub struct TrustedNodeAddress(pub *const c_void);
199
200#[expect(unsafe_code)]
201unsafe impl Send for TrustedNodeAddress {}
202
203#[derive(Debug)]
205pub enum PendingImageState {
206 Unrequested(ServoUrl),
207 PendingResponse,
208}
209
210#[derive(Debug, MallocSizeOf)]
212pub enum LayoutImageDestination {
213 BoxTreeConstruction,
214 DisplayListBuilding,
215}
216
217#[derive(Debug)]
221pub struct PendingImage {
222 pub state: PendingImageState,
223 pub node: UntrustedNodeAddress,
224 pub id: PendingImageId,
225 pub origin: ImmutableOrigin,
226 pub destination: LayoutImageDestination,
227 pub is_internal_request: InternalRequest,
228}
229
230#[derive(Debug)]
234pub struct PendingRasterizationImage {
235 pub node: UntrustedNodeAddress,
236 pub id: PendingImageId,
237 pub size: DeviceIntSize,
238}
239
240#[derive(Clone, Copy, Debug, MallocSizeOf)]
241pub struct MediaFrame {
242 pub image_key: webrender_api::ImageKey,
243 pub width: i32,
244 pub height: i32,
245}
246
247pub struct MediaMetadata {
248 pub width: u32,
249 pub height: u32,
250}
251
252pub struct HTMLMediaData {
253 pub current_frame: Option<MediaFrame>,
254 pub metadata: Option<MediaMetadata>,
255 pub poster_url: Option<ServoUrl>,
256}
257
258#[derive(Debug)]
259pub struct AccessibilityActionRequest {
260 pub action: Action,
261 pub target: OpaqueNode,
262 pub data: Option<ActionData>,
263}
264
265pub struct LayoutConfig {
266 pub id: PipelineId,
267 pub webview_id: WebViewId,
268 pub url: ServoUrl,
269 pub is_iframe: bool,
270 pub script_chan: GenericSender<ScriptThreadMessage>,
271 pub image_cache: Arc<dyn ImageCache>,
272 pub font_context: Arc<FontContext>,
273 pub time_profiler_chan: time::ProfilerChan,
274 pub paint_api: CrossProcessPaintApi,
275 pub viewport_details: ViewportDetails,
276 pub user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
277 pub theme: Theme,
278 pub embedder_chan: ScriptToEmbedderChan,
279}
280
281bitflags! {
282 #[derive(Copy, Clone)]
283 pub struct HitTestFlags: u8 {
284 const IncludeDomPosition = 0b0000_0001;
286 }
287}
288
289pub trait LayoutFactory: Send + Sync {
290 fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
291}
292
293pub trait Layout {
294 fn device(&self) -> &Device;
297
298 fn set_theme(&mut self, theme: Theme) -> bool;
302
303 fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
307
308 fn add_stylesheet(
313 &mut self,
314 stylesheet: ServoArc<Stylesheet>,
315 before_stylesheet: Option<ServoArc<Stylesheet>>,
316 );
317
318 fn exit_now(&mut self);
320
321 fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
324
325 fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
327
328 fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
330
331 fn remove_cached_image(&mut self, image_url: &ServoUrl);
333
334 fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
336
337 fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
340
341 fn register_paint_worklet_modules(
343 &mut self,
344 name: Atom,
345 properties: Vec<Atom>,
346 painter: Box<dyn Painter>,
347 );
348
349 fn set_scroll_offsets_from_renderer(
351 &mut self,
352 scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
353 );
354
355 fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
358
359 fn needs_new_display_list(&self) -> bool;
361
362 fn set_needs_new_display_list(&self);
364
365 fn node_rendering_type(
368 &self,
369 node: TrustedNodeAddress,
370 pseudo: Option<PseudoElement>,
371 ) -> NodeRenderingType;
372
373 fn query_containing_block(&self, node: TrustedNodeAddress) -> Option<UntrustedNodeAddress>;
374 fn query_containing_block_is_descendant(
375 &self,
376 root: TrustedNodeAddress,
377 possible_descendant: TrustedNodeAddress,
378 ) -> bool;
379 fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
380 fn query_box_area(
381 &self,
382 node: TrustedNodeAddress,
383 area: BoxAreaType,
384 exclude_transform_and_inline: bool,
385 ) -> Option<Rect<Au, CSSPixel>>;
386 fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectVec;
387 fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
388 fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
389 fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
390 fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
391 fn query_scroll_container(
394 &self,
395 node: Option<TrustedNodeAddress>,
396 flags: ScrollContainerQueryFlags,
397 ) -> Option<ScrollContainerResponse>;
398 fn query_resolved_style(
399 &self,
400 node: TrustedNodeAddress,
401 pseudo: Option<PseudoElement>,
402 property_id: PropertyId,
403 animations: DocumentAnimationSet,
404 animation_timeline_value: f64,
405 ) -> String;
406
407 fn query_accesskit_node(&self, node: TrustedNodeAddress) -> Option<accesskit::Node>;
408
409 fn query_resolved_font_style(
410 &self,
411 node: TrustedNodeAddress,
412 value: &str,
413 animations: DocumentAnimationSet,
414 animation_timeline_value: f64,
415 ) -> Option<ServoArc<Font>>;
416 fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
417 fn query_text_index(
421 &self,
422 node: TrustedNodeAddress,
423 point_in_viewport: Point2D<Au, CSSPixel>,
424 ) -> Option<(OpaqueNode, Utf32CodeUnits)>;
425 fn hit_test(&self, flags: HitTestFlags, point: LayoutPoint) -> HitTestResult;
426 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
427 fn stylist_mut(&mut self) -> &mut Stylist;
428
429 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
432
433 fn accessibility_active(&self) -> bool;
435
436 fn needs_accessibility_update(&self) -> bool;
447
448 fn set_force_accessibility_update_if_active(&self);
451
452 fn handle_accessibility_action(&self, action_request: ActionRequest);
454
455 fn font_context(&self) -> &Arc<FontContext>;
456}
457
458pub trait ScriptThreadFactory {
462 fn create(
464 state: InitialScriptState,
465 layout_factory: Arc<dyn LayoutFactory>,
466 image_cache_factory: Arc<dyn ImageCacheFactory>,
467 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
468 ) -> JoinHandle<()>;
469}
470
471#[derive(Copy, Clone)]
474pub enum BoxAreaType {
475 Content,
476 Padding,
477 Border,
478}
479
480pub type CSSPixelRectVec = Vec<Rect<Au, CSSPixel>>;
481
482#[derive(Copy, Clone)]
485pub enum NodeRenderingType {
486 Rendered,
488 DelegatesRendering,
490 NotRendered,
493}
494
495#[derive(Default)]
496pub struct PhysicalSides {
497 pub left: Au,
498 pub top: Au,
499 pub right: Au,
500 pub bottom: Au,
501}
502
503#[derive(Clone, Default)]
504pub struct OffsetParentResponse {
505 pub node_address: Option<UntrustedNodeAddress>,
506 pub rect: Rect<Au, CSSPixel>,
507}
508
509bitflags! {
510 #[derive(PartialEq)]
511 pub struct ScrollContainerQueryFlags: u8 {
512 const ForScrollParent = 1 << 0;
514 const Inclusive = 1 << 1;
516 }
517}
518
519#[derive(Clone, Copy, Debug, MallocSizeOf)]
520pub struct AxesOverflow {
521 pub x: Overflow,
522 pub y: Overflow,
523}
524
525impl Default for AxesOverflow {
526 fn default() -> Self {
527 Self {
528 x: Overflow::Visible,
529 y: Overflow::Visible,
530 }
531 }
532}
533
534impl From<&ComputedValues> for AxesOverflow {
535 fn from(style: &ComputedValues) -> Self {
536 Self {
537 x: style.slow_clone_overflow_x(),
538 y: style.slow_clone_overflow_y(),
539 }
540 }
541}
542
543impl AxesOverflow {
544 pub fn to_scrollable(&self) -> Self {
545 Self {
546 x: self.x.to_scrollable(),
547 y: self.y.to_scrollable(),
548 }
549 }
550
551 pub fn establishes_scroll_container(&self) -> bool {
553 self.x.is_scrollable()
556 }
557}
558
559#[derive(Clone)]
560pub enum ScrollContainerResponse {
561 Viewport(AxesOverflow),
562 Element(UntrustedNodeAddress, AxesOverflow),
563}
564
565#[derive(Debug, PartialEq)]
566pub enum QueryMsg {
567 BoxArea,
568 BoxAreas,
569 ClientRectQuery,
570 CurrentCSSZoomQuery,
571 EffectiveOverflow,
572 ElementInnerOuterTextQuery,
573 ElementsFromPoint,
574 InnerWindowDimensionsQuery,
575 NodesFromPointQuery,
576 OffsetParentQuery,
577 ScrollParentQuery,
578 ResolvedFontStyleQuery,
579 ResolvedStyleQuery(PropertyId),
582 ScrollingAreaOrOffsetQuery,
583 StyleQuery,
584 TextIndexQuery,
585 PaddingQuery,
586 FlushForUpdateTheRenderingQuery,
587 AccessKitNodeQuery,
588}
589
590#[derive(Debug, PartialEq)]
596pub enum ReflowGoal {
597 UpdateTheRendering,
600
601 LayoutQuery(QueryMsg),
604
605 UpdateScrollNode(ExternalScrollId, LayoutVector2D),
609}
610
611#[derive(Clone, Debug, MallocSizeOf)]
612pub struct IFrameSize {
613 pub browsing_context_id: BrowsingContextId,
614 pub pipeline_id: PipelineId,
615 pub viewport_details: ViewportDetails,
616}
617
618pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
619
620bitflags! {
621 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
624 pub struct RestyleReason: u16 {
625 const StylesheetsChanged = 1 << 0;
626 const DOMChanged = 1 << 1;
627 const PendingRestyles = 1 << 2;
628 const HighlightedDOMNodeChanged = 1 << 3;
629 const ThemeChanged = 1 << 4;
630 const ViewportChanged = 1 << 5;
631 const PaintWorkletLoaded = 1 << 6;
632 }
633}
634
635malloc_size_of_is_0!(RestyleReason);
636
637impl RestyleReason {
638 pub fn needs_restyle(&self) -> bool {
639 !self.is_empty()
640 }
641}
642
643#[derive(Default)]
645pub struct ReflowResult {
646 pub reflow_phases_run: ReflowPhasesRun,
648 pub reflow_statistics: ReflowStatistics,
649 pub pending_images: Vec<PendingImage>,
651 pub pending_rasterization_images: Vec<PendingRasterizationImage>,
653 pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
657 pub iframe_sizes: Option<IFrameSizes>,
663 pub changed_web_fonts: WebFontSetDifference,
665 pub lcp_candidate: Option<LCPCandidate>,
667 pub pending_accessibility_actions: Vec<AccessibilityActionRequest>,
669 pub container_timing_records: Vec<ContainerTimingRecord>,
671}
672
673bitflags! {
674 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
676 pub struct ReflowPhasesRun: u8 {
677 const RanLayout = 1 << 0;
678 const BuiltStackingContextTree = 1 << 2;
679 const BuiltDisplayList = 1 << 3;
680 const UpdatedScrollNodeOffset = 1 << 4;
681 const UpdatedImageData = 1 << 5;
685 const UpdatedAccessibilityTree = 1 << 6;
686 }
687}
688
689impl ReflowPhasesRun {
690 pub fn needs_frame(&self) -> bool {
691 self.intersects(
692 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
693 )
694 }
695}
696
697#[derive(Debug, Default)]
698pub struct ReflowStatistics {
699 pub rebuilt_fragment_count: u32,
701 pub restyle_fragment_count: u32,
703 pub only_descendants_changed_count: u32,
706 pub nodes_updated_from_dom: u32,
709 pub nodes_updated_from_tree: u32,
712 pub nodes_updated_bounds: u32,
715 pub nodes_in_tree_update: u32,
717}
718
719#[derive(Debug)]
722pub struct ReflowRequestRestyle {
723 pub reason: RestyleReason,
725 pub dirty_root: Option<TrustedNodeAddress>,
727 pub stylesheets_changed: bool,
729 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
731}
732
733#[derive(Debug)]
736pub struct ReflowRequestAccessibility {
737 pub damage: Vec<(TrustedNodeAddress, AccessibilityDamage)>,
739 pub focused_element: Option<OpaqueNode>,
741 pub rooted_nodes_for_integrity_check: Option<FxHashSet<OpaqueNode>>,
745}
746
747#[derive(Debug)]
749pub struct ReflowRequest {
750 pub document: TrustedNodeAddress,
752 pub epoch: Epoch,
754 pub restyle: Option<ReflowRequestRestyle>,
756 pub viewport_details: ViewportDetails,
758 pub reflow_goal: ReflowGoal,
760 pub origin: ImmutableOrigin,
762 pub animation_timeline_value: f64,
764 pub animations: DocumentAnimationSet,
766 pub animating_images: Arc<RwLock<AnimatingImages>>,
768 pub highlighted_dom_node: Option<OpaqueNode>,
770 pub frame_focused: bool,
773 pub halt_lcp: bool,
777 pub paint_timing_eligible: bool,
780 pub paint_timing_info: PaintTimingInfo,
783 pub document_context: WebFontDocumentContext,
785 pub accessibility: Option<ReflowRequestAccessibility>,
787}
788
789impl ReflowRequest {
790 pub fn stylesheets_changed(&self) -> bool {
791 self.restyle
792 .as_ref()
793 .is_some_and(|restyle| restyle.stylesheets_changed)
794 }
795}
796
797#[derive(Debug, Default, MallocSizeOf)]
799pub struct PendingRestyle {
800 pub snapshot: Option<Snapshot>,
803
804 pub hint: RestyleHint,
806
807 pub damage: RestyleDamage,
809}
810
811#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
817pub enum FragmentType {
818 FragmentBody,
820 BeforePseudoContent,
822 AfterPseudoContent,
824}
825
826impl From<Option<PseudoElement>> for FragmentType {
827 fn from(value: Option<PseudoElement>) -> Self {
828 match value {
829 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
830 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
831 _ => FragmentType::FragmentBody,
832 }
833 }
834}
835
836pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
837 debug_assert_eq!(id & (fragment_type as usize), 0);
838 (id as u64) | (fragment_type as u64)
839}
840
841pub fn node_id_from_scroll_id(id: usize) -> usize {
842 id & !3
843}
844
845#[derive(Clone, Debug, MallocSizeOf)]
846pub struct ImageAnimationState {
847 #[conditional_malloc_size_of]
848 pub image: Arc<RasterImage>,
849 pub active_frame: usize,
850 frame_start_time: f64,
851
852 pub completed_loops: Option<u32>,
857}
858
859impl ImageAnimationState {
860 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
861 let completd_loops = match &image.loop_count {
862 None => unreachable!("Loop count of an animated Image should never be None"),
863 Some(repeat) if Repeat::Infinite == *repeat => None,
864 _ => Some(0),
865 };
866
867 Self {
868 image,
869 active_frame: 0,
870 frame_start_time: last_update_time,
871 completed_loops: completd_loops,
872 }
873 }
874
875 pub fn image_key(&self) -> Option<ImageKey> {
876 self.image.id
877 }
878
879 pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
880 if self.is_finished() {
881 return None;
882 }
883 let frame_delay = self
884 .image
885 .frames
886 .get(self.active_frame)
887 .expect("Image frame should always be valid")
888 .delay
889 .unwrap_or_default();
890
891 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
892 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
893 Some(frame_delay - time_since_frame_start.min(frame_delay))
894 }
895
896 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
900 if self.image.frames.len() <= 1 || self.is_finished() {
901 return false;
902 }
903 let time_interval_since_last_update = now - self.frame_start_time;
904 let mut remain_time_interval = time_interval_since_last_update -
905 self.image
906 .frames
907 .get(self.active_frame)
908 .unwrap()
909 .delay()
910 .unwrap()
911 .as_secs_f64();
912 let mut next_active_frame_id = self.active_frame;
913
914 let frame_count = self.image.frames.len();
915 while remain_time_interval > 0.0 {
916 next_active_frame_id = (next_active_frame_id + 1) % frame_count;
917
918 if next_active_frame_id == 0 {
920 self.advance_completed_loops();
921
922 if self.is_finished() {
925 if self.active_frame == frame_count - 1 {
926 return false;
927 }
928 self.active_frame = frame_count - 1;
929 self.frame_start_time = now;
930 return true;
931 }
932 }
933
934 remain_time_interval -= self
935 .image
936 .frames
937 .get(next_active_frame_id)
938 .unwrap()
939 .delay()
940 .unwrap()
941 .as_secs_f64();
942 }
943 if self.active_frame == next_active_frame_id {
944 return false;
945 }
946 self.active_frame = next_active_frame_id;
947 self.frame_start_time = now;
948 true
949 }
950
951 fn is_finished(&self) -> bool {
953 let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
954 return false;
955 };
956 self.completed_loops
957 .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
958 }
959
960 fn advance_completed_loops(&mut self) {
962 if let Some(completed_loops) = self.completed_loops.as_mut() {
963 *completed_loops += 1;
964 }
965 }
966}
967
968#[derive(Debug, Default)]
970pub struct HitTestResult {
971 pub items: Vec<HitTestResultItem>,
972 pub dom_position_for_selection: Option<(OpaqueNode, Utf32CodeUnitsOrNodeOffset)>,
973}
974
975#[derive(Debug)]
977pub struct HitTestResultItem {
978 pub node: OpaqueNode,
981 pub point_in_target: Point2D<f32, CSSPixel>,
984 pub cursor: Cursor,
987}
988
989#[derive(Debug, Default, MallocSizeOf)]
990pub struct AnimatingImages {
991 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
994 pub dirty: bool,
997}
998
999impl AnimatingImages {
1000 pub fn maybe_insert_or_update(
1001 &mut self,
1002 node: OpaqueNode,
1003 image: Arc<RasterImage>,
1004 current_timeline_value: f64,
1005 ) {
1006 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
1007 self.dirty = true;
1008 ImageAnimationState::new(image.clone(), current_timeline_value)
1009 });
1010
1011 if entry.image.id != image.id {
1014 self.dirty = true;
1015 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
1016 }
1017 }
1018
1019 pub fn remove(&mut self, node: OpaqueNode) {
1020 if self.node_to_state_map.remove(&node).is_some() {
1021 self.dirty = true;
1022 }
1023 }
1024
1025 pub fn clear_dirty(&mut self) -> bool {
1027 std::mem::take(&mut self.dirty)
1028 }
1029
1030 pub fn is_empty(&self) -> bool {
1031 self.node_to_state_map.is_empty()
1032 }
1033}
1034
1035struct ThreadStateRestorer;
1036
1037impl ThreadStateRestorer {
1038 fn new() -> Self {
1039 #[cfg(debug_assertions)]
1040 {
1041 thread_state::exit(ThreadState::SCRIPT);
1042 thread_state::enter(ThreadState::LAYOUT);
1043 }
1044 Self
1045 }
1046}
1047
1048impl Drop for ThreadStateRestorer {
1049 fn drop(&mut self) {
1050 #[cfg(debug_assertions)]
1051 {
1052 thread_state::exit(ThreadState::LAYOUT);
1053 thread_state::enter(ThreadState::SCRIPT);
1054 }
1055 }
1056}
1057
1058pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
1064 let _guard = ThreadStateRestorer::new();
1065 f()
1066}
1067
1068#[cfg(test)]
1069mod test {
1070 use std::num::NonZeroU32;
1071 use std::sync::Arc;
1072 use std::time::Duration;
1073
1074 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
1075
1076 use crate::ImageAnimationState;
1077
1078 #[test]
1079 fn test_animated_image_update() {
1080 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1081 delay: Some(Duration::from_millis(100)),
1082 byte_range: 0..1,
1083 width: 100,
1084 height: 100,
1085 })
1086 .take(10)
1087 .collect();
1088 let image = RasterImage {
1089 metadata: ImageMetadata {
1090 width: 100,
1091 height: 100,
1092 },
1093 format: PixelFormat::BGRA8,
1094 id: None,
1095 bytes: Arc::new(vec![1]),
1096 frames: image_frames,
1097 cors_status: CorsStatus::Unsafe,
1098 load_time: None,
1099 loop_count: Some(Repeat::Infinite),
1100 is_opaque: false,
1101 };
1102 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1103
1104 assert_eq!(image_animation_state.active_frame, 0);
1105 assert_eq!(image_animation_state.frame_start_time, 0.0);
1106 assert_eq!(
1107 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1108 true
1109 );
1110 assert_eq!(image_animation_state.active_frame, 1);
1111 assert_eq!(image_animation_state.frame_start_time, 0.101);
1112 assert_eq!(
1113 image_animation_state.update_frame_for_animation_timeline_value(0.116),
1114 false
1115 );
1116 assert_eq!(image_animation_state.active_frame, 1);
1117 assert_eq!(image_animation_state.frame_start_time, 0.101);
1118 }
1119
1120 #[test]
1121 fn test_finite_image_repeat() {
1122 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1123 delay: Some(Duration::from_millis(100)),
1124 byte_range: 0..1,
1125 width: 100,
1126 height: 100,
1127 })
1128 .take(2)
1129 .collect();
1130 let image = RasterImage {
1131 metadata: ImageMetadata {
1132 width: 100,
1133 height: 100,
1134 },
1135 format: PixelFormat::BGRA8,
1136 id: None,
1137 bytes: Arc::new(vec![1]),
1138 frames: image_frames,
1139 cors_status: CorsStatus::Unsafe,
1140 load_time: None,
1141 loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1142 is_opaque: false,
1143 };
1144 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1145
1146 assert_eq!(image_animation_state.active_frame, 0);
1147 assert_eq!(image_animation_state.frame_start_time, 0.0);
1148 assert_eq!(
1149 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1150 true
1151 );
1152 assert_eq!(image_animation_state.active_frame, 1);
1153 assert_eq!(image_animation_state.frame_start_time, 0.101);
1154 assert_eq!(
1155 image_animation_state.update_frame_for_animation_timeline_value(0.202),
1156 false
1157 );
1158 assert_eq!(
1159 image_animation_state.update_frame_for_animation_timeline_value(0.303),
1160 false
1161 );
1162
1163 assert_eq!(image_animation_state.active_frame, 1);
1164 }
1165}