1#![deny(unsafe_code)]
10
11mod accessibility;
12mod container_timing_record;
13mod 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::rc::Rc;
22use std::sync::Arc;
23use std::sync::atomic::AtomicIsize;
24use std::thread::JoinHandle;
25use std::time::Duration;
26
27pub use accessibility::{
28 ACCESSKIT_ROLE_TO_ARIA_ROLE, ARIA_ROLE_TO_ACCESSKIT_ROLE, AccessibilityDamage,
29 SUPPORTED_ARIA_ROLES,
30};
31use accesskit::{Action, ActionData, ActionRequest};
32use app_units::Au;
33use background_hang_monitor_api::BackgroundHangMonitorRegister;
34use bitflags::bitflags;
35pub use container_timing_record::ContainerTimingRecord;
36use embedder_traits::{Cursor, ScriptToEmbedderChan, Theme, UntrustedNodeAddress, ViewportDetails};
37use euclid::{Point2D, Rect};
38use fonts::{FontContext, WebFontDocumentContext, WebFontSetDifference};
39pub use largest_contentful_paint_candidate::LCPCandidate;
40pub use layout_damage::LayoutDamage;
41pub use layout_dom::{
42 DangerousStyleElementOf, DangerousStyleNodeOf, LayoutDomTypeBundle, LayoutElementOf,
43 LayoutNodeOf,
44};
45pub use layout_element::{DangerousStyleElement, LayoutElement};
46pub use layout_node::{DangerousStyleNode, LayoutNode};
47use libc::c_void;
48use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps, malloc_size_of_is_0};
49use malloc_size_of_derive::MallocSizeOf;
50use net_traits::image_cache::{ImageCache, ImageCacheFactory, PendingImageId};
51use net_traits::request::InternalRequest;
52use paint_api::CrossProcessPaintApi;
53use paint_api::display_list::PaintTimingInfo;
54use parking_lot::RwLock;
55use pixels::{RasterImage, Repeat};
56use profile_traits::mem::Report;
57use profile_traits::time;
58pub use pseudo_element_chain::PseudoElementChain;
59use rustc_hash::{FxHashMap, FxHashSet};
60use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
61use serde::{Deserialize, Serialize};
62use servo_arc::Arc as ServoArc;
63use servo_base::Epoch;
64use servo_base::generic_channel::GenericSender;
65use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
66use servo_base::text::{RangeAny, Utf32CodeUnits, Utf32CodeUnitsOrNodeOffset};
67use servo_url::{ImmutableOrigin, ServoUrl};
68use style::Atom;
69use style::animation::DocumentAnimationSet;
70use style::attr::{AttrValue, parse_double};
71use style::context::QuirksMode;
72use style::data::ElementDataWrapper;
73use style::device::Device;
74use style::dom::OpaqueNode;
75use style::invalidation::element::restyle_hints::RestyleHint;
76use style::properties::style_structs::Font;
77use style::properties::{ComputedValues, PropertyId};
78use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
79use style::str::char_is_whitespace;
80use style::stylesheets::{DocumentStyleSheet, Stylesheet};
81use style::stylist::Stylist;
82#[cfg(debug_assertions)]
83use style::thread_state::{self, ThreadState};
84use style::values::computed::Overflow;
85use style_traits::CSSPixel;
86use uuid::Uuid;
87use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
88use webrender_api::{ExternalScrollId, ImageKey};
89
90pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait + Send + Sync + 'static {
91 fn as_any(&self) -> &dyn Any;
92
93 fn set_text_run_selection(&self, new_range: Option<RangeAny<Utf32CodeUnits>>) -> bool;
95
96 fn set_element_selection(&self, selected: bool) -> bool;
99
100 fn rendered_text(&self, range: RangeAny<Utf32CodeUnits>) -> Option<String>;
103}
104
105pub trait LayoutDataTrait: GenericLayoutDataTrait + Default {}
106pub type GenericLayoutData = dyn GenericLayoutDataTrait;
107
108#[derive(Default, MallocSizeOf)]
109pub struct StyleData {
110 pub element_data: ElementDataWrapper,
115
116 pub parallel: DomParallelInfo,
118}
119
120#[derive(Default, MallocSizeOf)]
122pub struct DomParallelInfo {
123 pub children_to_process: AtomicIsize,
125}
126
127#[derive(Clone, Copy, Debug, Eq, PartialEq)]
128pub enum LayoutNodeType {
129 Element(LayoutElementType),
130 Text,
131}
132
133#[derive(Clone, Copy, Debug, Eq, PartialEq)]
134pub enum LayoutElementType {
135 Element,
136 HTMLBodyElement,
137 HTMLButtonElement,
138 HTMLBRElement,
139 HTMLCanvasElement,
140 HTMLHtmlElement,
141 HTMLIFrameElement,
142 HTMLImageElement,
143 HTMLInputElement,
144 HTMLMediaElement,
145 HTMLMeterElement,
146 HTMLObjectElement,
147 HTMLOptGroupElement,
148 HTMLOptionElement,
149 HTMLParagraphElement,
150 HTMLPreElement,
151 HTMLProgressElement,
152 HTMLSelectElement,
153 HTMLTableCellElement,
154 HTMLTableColElement,
155 HTMLTableElement,
156 HTMLTableRowElement,
157 HTMLTableSectionElement,
158 HTMLTextAreaElement,
159 SVGImageElement,
160 SVGSVGElement,
161}
162
163pub struct HTMLCanvasData {
164 pub image_key: Option<ImageKey>,
165 pub width: u32,
166 pub height: u32,
167}
168
169pub struct SVGElementData<'dom> {
170 pub source: Option<Result<ServoUrl, ()>>,
172 pub width: Option<&'dom AttrValue>,
173 pub height: Option<&'dom AttrValue>,
174 pub svg_id: Uuid,
175 pub view_box: Option<&'dom AttrValue>,
177}
178
179impl SVGElementData<'_> {
180 pub fn ratio_from_view_box(&self) -> Option<f32> {
182 let mut iter = self
183 .view_box?
184 .split(|c| char_is_whitespace(c) || c == ',')
185 .filter(|part| !part.is_empty());
186 let _min_x = parse_double(iter.next()?).ok()?;
187 let _min_y = parse_double(iter.next()?).ok()?;
188
189 let width = parse_double(iter.next()?).ok()?;
190 if width == 0f64 {
191 return None;
192 }
193
194 let height = parse_double(iter.next()?).ok()?;
195 if height == 0f64 {
196 return None;
197 }
198
199 iter.next().is_none().then(|| width as f32 / height as f32)
200 }
201}
202
203#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
205pub struct TrustedNodeAddress(pub *const c_void);
206
207#[expect(unsafe_code)]
208unsafe impl Send for TrustedNodeAddress {}
209
210#[derive(Debug)]
212pub enum PendingImageState {
213 Unrequested(ServoUrl),
214 PendingResponse,
215}
216
217#[derive(Debug, MallocSizeOf)]
219pub enum LayoutImageDestination {
220 BoxTreeConstruction,
221 DisplayListBuilding,
222}
223
224#[derive(Debug)]
228pub struct PendingImage {
229 pub state: PendingImageState,
230 pub node: UntrustedNodeAddress,
231 pub id: PendingImageId,
232 pub origin: ImmutableOrigin,
233 pub destination: LayoutImageDestination,
234 pub is_internal_request: InternalRequest,
235}
236
237#[derive(Debug)]
241pub struct PendingRasterizationImage {
242 pub node: UntrustedNodeAddress,
243 pub id: PendingImageId,
244 pub size: DeviceIntSize,
245}
246
247#[derive(Clone, Copy, Debug, MallocSizeOf)]
248pub struct MediaFrame {
249 pub image_key: webrender_api::ImageKey,
250 pub width: i32,
251 pub height: i32,
252}
253
254pub struct MediaMetadata {
255 pub width: u32,
256 pub height: u32,
257}
258
259pub struct HTMLMediaData {
260 pub current_frame: Option<MediaFrame>,
261 pub metadata: Option<MediaMetadata>,
262 pub poster_or_src_url: Option<ServoUrl>,
263}
264
265#[derive(Debug)]
266pub struct AccessibilityActionRequest {
267 pub action: Action,
268 pub target: OpaqueNode,
269 pub data: Option<ActionData>,
270}
271
272pub struct LayoutConfig {
273 pub id: PipelineId,
274 pub webview_id: WebViewId,
275 pub url: ServoUrl,
276 pub is_iframe: bool,
277 pub script_chan: GenericSender<ScriptThreadMessage>,
278 pub image_cache: Arc<dyn ImageCache>,
279 pub font_context: Arc<FontContext>,
280 pub time_profiler_chan: time::ProfilerChan,
281 pub paint_api: CrossProcessPaintApi,
282 pub viewport_details: ViewportDetails,
283 pub user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
284 pub theme: Theme,
285 pub embedder_chan: ScriptToEmbedderChan,
286}
287
288bitflags! {
289 #[derive(Copy, Clone)]
290 pub struct HitTestFlags: u8 {
291 const IncludeDomPosition = 0b0000_0001;
293 }
294}
295
296pub trait LayoutFactory: Send + Sync {
297 fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
298}
299
300pub trait Layout {
301 fn device(&self) -> &Device;
304
305 fn set_theme(&mut self, theme: Theme) -> bool;
309
310 fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
314
315 fn add_stylesheet(
320 &mut self,
321 stylesheet: ServoArc<Stylesheet>,
322 before_stylesheet: Option<ServoArc<Stylesheet>>,
323 );
324
325 fn exit_now(&mut self);
327
328 fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
331
332 fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
334
335 fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
337
338 fn remove_cached_image(&mut self, image_url: &ServoUrl);
340
341 fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
343
344 fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
347
348 fn register_paint_worklet_modules(
350 &mut self,
351 name: Atom,
352 properties: Vec<Atom>,
353 painter: Box<dyn Painter>,
354 );
355
356 fn set_scroll_offsets_from_renderer(
358 &mut self,
359 scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
360 );
361
362 fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
365
366 fn needs_new_display_list(&self) -> bool;
368
369 fn set_needs_new_display_list(&self);
371
372 fn node_rendering_type(
375 &self,
376 node: TrustedNodeAddress,
377 pseudo: Option<PseudoElement>,
378 ) -> NodeRenderingType;
379
380 fn query_containing_block(&self, node: TrustedNodeAddress) -> Option<UntrustedNodeAddress>;
381 fn query_containing_block_is_descendant(
382 &self,
383 root: TrustedNodeAddress,
384 possible_descendant: TrustedNodeAddress,
385 ) -> bool;
386 fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides<Au>>;
387 fn query_box_area(
388 &self,
389 node: TrustedNodeAddress,
390 area: BoxAreaType,
391 exclude_transform_and_inline: bool,
392 ) -> Option<Rect<Au, CSSPixel>>;
393 fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectVec;
394 fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
395 fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
396 fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
397 fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
398 fn query_scroll_container(
401 &self,
402 node: Option<TrustedNodeAddress>,
403 flags: ScrollContainerQueryFlags,
404 ) -> Option<ScrollContainerResponse>;
405 fn query_resolved_style(
406 &self,
407 node: TrustedNodeAddress,
408 pseudo: Option<PseudoElement>,
409 property_id: PropertyId,
410 animations: DocumentAnimationSet,
411 animation_timeline_value: f64,
412 ) -> String;
413
414 fn query_accesskit_node(&self, node: TrustedNodeAddress) -> Option<accesskit::Node>;
415
416 fn query_resolved_font_style(
417 &self,
418 node: TrustedNodeAddress,
419 value: &str,
420 animations: DocumentAnimationSet,
421 animation_timeline_value: f64,
422 ) -> Option<ServoArc<Font>>;
423 fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
424 fn query_text_index(
428 &self,
429 node: TrustedNodeAddress,
430 point_in_viewport: Point2D<Au, CSSPixel>,
431 ) -> Option<(OpaqueNode, Utf32CodeUnits)>;
432 fn query_text_segment(
435 &self,
436 node: TrustedNodeAddress,
437 offset: Utf32CodeUnitsOrNodeOffset,
438 granularity: SegmentGranularity,
439 ) -> Option<(
440 (OpaqueNode, Utf32CodeUnitsOrNodeOffset),
441 (OpaqueNode, Utf32CodeUnitsOrNodeOffset),
442 )>;
443 fn hit_test(&self, flags: HitTestFlags, point: LayoutPoint) -> HitTestResult;
444 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
445 fn stylist_mut(&mut self) -> &mut Stylist;
446
447 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
450
451 fn accessibility_active(&self) -> bool;
453
454 fn needs_accessibility_update(&self) -> bool;
465
466 fn set_force_accessibility_update_if_active(&self);
469
470 fn handle_accessibility_action(&self, action_request: ActionRequest);
472
473 fn font_context(&self) -> &Arc<FontContext>;
474}
475
476#[derive(Clone, Copy, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
478pub enum SegmentGranularity {
479 Word,
481 HardLineBreak,
484}
485
486pub trait ScriptThreadFactory {
490 fn create(
492 state: InitialScriptState,
493 layout_factory: Arc<dyn LayoutFactory>,
494 image_cache_factory: Arc<dyn ImageCacheFactory>,
495 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
496 ) -> JoinHandle<()>;
497}
498
499#[derive(Copy, Clone)]
502pub enum BoxAreaType {
503 Content,
504 Padding,
505 Border,
506}
507
508pub type CSSPixelRectVec = Vec<Rect<Au, CSSPixel>>;
509
510#[derive(Copy, Clone)]
513pub enum NodeRenderingType {
514 Rendered,
516 DelegatesRendering,
518 NotRendered,
521}
522
523pub type PhysicalSides<U> = euclid::SideOffsets2D<U, CSSPixel>;
524
525#[derive(Clone, Default)]
526pub struct OffsetParentResponse {
527 pub node_address: Option<UntrustedNodeAddress>,
528 pub rect: Rect<Au, CSSPixel>,
529}
530
531bitflags! {
532 #[derive(PartialEq)]
533 pub struct ScrollContainerQueryFlags: u8 {
534 const ForScrollParent = 1 << 0;
536 const Inclusive = 1 << 1;
538 }
539}
540
541#[derive(Clone, Copy, Debug, MallocSizeOf)]
542pub struct AxesOverflow {
543 pub x: Overflow,
544 pub y: Overflow,
545}
546
547impl Default for AxesOverflow {
548 fn default() -> Self {
549 Self {
550 x: Overflow::Visible,
551 y: Overflow::Visible,
552 }
553 }
554}
555
556impl From<&ComputedValues> for AxesOverflow {
557 fn from(style: &ComputedValues) -> Self {
558 Self {
559 x: style.slow_clone_overflow_x(),
560 y: style.slow_clone_overflow_y(),
561 }
562 }
563}
564
565impl AxesOverflow {
566 pub fn to_scrollable(&self) -> Self {
567 Self {
568 x: self.x.to_scrollable(),
569 y: self.y.to_scrollable(),
570 }
571 }
572
573 pub fn establishes_scroll_container(&self) -> bool {
575 self.x.is_scrollable()
578 }
579}
580
581#[derive(Clone)]
582pub enum ScrollContainerResponse {
583 Viewport(AxesOverflow),
584 Element(UntrustedNodeAddress, AxesOverflow),
585}
586
587#[derive(Debug, PartialEq)]
588pub enum QueryMsg {
589 BoxArea,
590 BoxAreas,
591 ClientRectQuery,
592 CurrentCSSZoomQuery,
593 EffectiveOverflow,
594 ElementInnerOuterTextQuery,
595 ElementsFromPoint,
596 InnerWindowDimensionsQuery,
597 NodesFromPointQuery,
598 OffsetParentQuery,
599 ScrollParentQuery,
600 ResolvedFontStyleQuery,
601 ResolvedStyleQuery(PropertyId),
604 ScrollingAreaOrOffsetQuery,
605 StyleQuery,
606 TextIndexOrSegmentQuery,
607 PaddingQuery,
608 FlushForUpdateTheRenderingQuery,
609 AccessKitNodeQuery,
610}
611
612#[derive(Debug, PartialEq)]
618pub enum ReflowGoal {
619 UpdateTheRendering,
622
623 LayoutQuery(QueryMsg),
626
627 UpdateScrollNode(ExternalScrollId, LayoutVector2D),
631}
632
633#[derive(Clone, Debug, MallocSizeOf)]
634pub struct IFrameSize {
635 pub browsing_context_id: BrowsingContextId,
636 pub pipeline_id: PipelineId,
637 pub viewport_details: ViewportDetails,
638}
639
640pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
641
642bitflags! {
643 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
646 pub struct RestyleReason: u16 {
647 const StylesheetsChanged = 1 << 0;
648 const DOMChanged = 1 << 1;
649 const PendingRestyles = 1 << 2;
650 const HighlightedDOMNodeChanged = 1 << 3;
651 const ThemeChanged = 1 << 4;
652 const ViewportChanged = 1 << 5;
653 const PaintWorkletLoaded = 1 << 6;
654 }
655}
656
657malloc_size_of_is_0!(RestyleReason);
658
659impl RestyleReason {
660 pub fn needs_restyle(&self) -> bool {
661 !self.is_empty()
662 }
663}
664
665#[derive(Default)]
667pub struct ReflowResult {
668 pub reflow_phases_run: ReflowPhasesRun,
670 pub reflow_statistics: ReflowStatistics,
671 pub pending_images: Vec<PendingImage>,
673 pub pending_rasterization_images: Vec<PendingRasterizationImage>,
675 pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
679 pub iframe_sizes: Option<IFrameSizes>,
685 pub changed_web_fonts: WebFontSetDifference,
687 pub lcp_candidate: Option<LCPCandidate>,
689 pub pending_accessibility_actions: Vec<AccessibilityActionRequest>,
691 pub container_timing_records: Vec<ContainerTimingRecord>,
693}
694
695bitflags! {
696 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
698 pub struct ReflowPhasesRun: u8 {
699 const RanLayout = 1 << 0;
700 const BuiltStackingContextTree = 1 << 2;
701 const BuiltDisplayList = 1 << 3;
702 const UpdatedScrollNodeOffset = 1 << 4;
703 const UpdatedImageData = 1 << 5;
707 const UpdatedAccessibilityTree = 1 << 6;
708 }
709}
710
711impl ReflowPhasesRun {
712 pub fn needs_frame(&self) -> bool {
713 self.intersects(
714 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
715 )
716 }
717}
718
719#[derive(Debug, Default)]
720pub struct ReflowStatistics {
721 pub rebuilt_fragment_count: u32,
723 pub restyle_fragment_count: u32,
725 pub only_descendants_changed_count: u32,
728 pub nodes_updated_from_dom: u32,
731 pub nodes_updated_from_tree: u32,
734 pub nodes_updated_bounds: u32,
737 pub nodes_in_tree_update: u32,
739}
740
741#[derive(Debug)]
744pub struct ReflowRequestRestyle {
745 pub reason: RestyleReason,
747 pub dirty_root: Option<TrustedNodeAddress>,
749 pub stylesheets_changed: bool,
751 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
753}
754
755#[derive(Debug)]
758pub struct ReflowRequestAccessibility {
759 pub damage: Vec<(TrustedNodeAddress, AccessibilityDamage)>,
761 pub focused_element: Option<OpaqueNode>,
763 pub rooted_nodes_for_integrity_check: Option<FxHashSet<OpaqueNode>>,
767}
768
769#[derive(Debug)]
771pub struct ReflowRequest {
772 pub document: TrustedNodeAddress,
774 pub epoch: Epoch,
776 pub restyle: Option<ReflowRequestRestyle>,
778 pub viewport_details: ViewportDetails,
780 pub reflow_goal: ReflowGoal,
782 pub origin: ImmutableOrigin,
784 pub animation_timeline_value: f64,
786 pub animations: DocumentAnimationSet,
788 pub animating_images: Arc<RwLock<AnimatingImages>>,
790 pub highlighted_dom_node: Option<OpaqueNode>,
792 pub frame_focused: bool,
795 pub halt_lcp: bool,
799 pub paint_timing_eligible: bool,
802 pub paint_timing_info: PaintTimingInfo,
805 pub document_context: WebFontDocumentContext,
807 pub accessibility: Option<ReflowRequestAccessibility>,
809}
810
811impl ReflowRequest {
812 pub fn stylesheets_changed(&self) -> bool {
813 self.restyle
814 .as_ref()
815 .is_some_and(|restyle| restyle.stylesheets_changed)
816 }
817}
818
819#[derive(Debug, Default, MallocSizeOf)]
821pub struct PendingRestyle {
822 pub snapshot: Option<Snapshot>,
825
826 pub hint: RestyleHint,
828
829 pub damage: RestyleDamage,
831}
832
833#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
839pub enum FragmentType {
840 FragmentBody,
842 BeforePseudoContent,
844 AfterPseudoContent,
846}
847
848impl From<Option<PseudoElement>> for FragmentType {
849 fn from(value: Option<PseudoElement>) -> Self {
850 match value {
851 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
852 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
853 _ => FragmentType::FragmentBody,
854 }
855 }
856}
857
858pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
859 debug_assert_eq!(id & (fragment_type as usize), 0);
860 (id as u64) | (fragment_type as u64)
861}
862
863pub fn node_id_from_scroll_id(id: usize) -> usize {
864 id & !3
865}
866
867#[derive(Clone, Debug, MallocSizeOf)]
868pub struct ImageAnimationState {
869 #[conditional_malloc_size_of]
870 pub image: Arc<RasterImage>,
871 pub active_frame: usize,
872 frame_start_time: f64,
873
874 pub completed_loops: Option<u32>,
879}
880
881impl ImageAnimationState {
882 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
883 let completd_loops = match &image.loop_count {
884 None => unreachable!("Loop count of an animated Image should never be None"),
885 Some(repeat) if Repeat::Infinite == *repeat => None,
886 _ => Some(0),
887 };
888
889 Self {
890 image,
891 active_frame: 0,
892 frame_start_time: last_update_time,
893 completed_loops: completd_loops,
894 }
895 }
896
897 pub fn image_key(&self) -> Option<ImageKey> {
898 self.image.id
899 }
900
901 pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
902 if self.is_finished() {
903 return None;
904 }
905 let frame_delay = self
906 .image
907 .frames
908 .get(self.active_frame)
909 .expect("Image frame should always be valid")
910 .delay
911 .unwrap_or_default();
912
913 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
914 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
915 Some(frame_delay - time_since_frame_start.min(frame_delay))
916 }
917
918 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
922 if self.image.frames.len() <= 1 || self.is_finished() {
923 return false;
924 }
925 let time_interval_since_last_update = now - self.frame_start_time;
926 let mut remain_time_interval = time_interval_since_last_update -
927 self.image
928 .frames
929 .get(self.active_frame)
930 .unwrap()
931 .delay()
932 .unwrap()
933 .as_secs_f64();
934 let mut next_active_frame_id = self.active_frame;
935
936 let frame_count = self.image.frames.len();
937 while remain_time_interval > 0.0 {
938 next_active_frame_id = (next_active_frame_id + 1) % frame_count;
939
940 if next_active_frame_id == 0 {
942 self.advance_completed_loops();
943
944 if self.is_finished() {
947 if self.active_frame == frame_count - 1 {
948 return false;
949 }
950 self.active_frame = frame_count - 1;
951 self.frame_start_time = now;
952 return true;
953 }
954 }
955
956 remain_time_interval -= self
957 .image
958 .frames
959 .get(next_active_frame_id)
960 .unwrap()
961 .delay()
962 .unwrap()
963 .as_secs_f64();
964 }
965 if self.active_frame == next_active_frame_id {
966 return false;
967 }
968 self.active_frame = next_active_frame_id;
969 self.frame_start_time = now;
970 true
971 }
972
973 fn is_finished(&self) -> bool {
975 let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
976 return false;
977 };
978 self.completed_loops
979 .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
980 }
981
982 fn advance_completed_loops(&mut self) {
984 if let Some(completed_loops) = self.completed_loops.as_mut() {
985 *completed_loops += 1;
986 }
987 }
988}
989
990#[derive(Debug, Default)]
992pub struct HitTestResult {
993 pub items: Vec<HitTestResultItem>,
994 pub dom_position_for_selection: Option<(OpaqueNode, Utf32CodeUnitsOrNodeOffset)>,
995}
996
997#[derive(Debug)]
999pub struct HitTestResultItem {
1000 pub node: OpaqueNode,
1003 pub point_in_target: Point2D<f32, CSSPixel>,
1006 pub cursor: Cursor,
1009}
1010
1011#[derive(Debug, Default, MallocSizeOf)]
1012pub struct AnimatingImages {
1013 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
1016 pub dirty: bool,
1019}
1020
1021impl AnimatingImages {
1022 pub fn maybe_insert_or_update(
1023 &mut self,
1024 node: OpaqueNode,
1025 image: Arc<RasterImage>,
1026 current_timeline_value: f64,
1027 ) {
1028 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
1029 self.dirty = true;
1030 ImageAnimationState::new(image.clone(), current_timeline_value)
1031 });
1032
1033 if entry.image.id != image.id {
1036 self.dirty = true;
1037 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
1038 }
1039 }
1040
1041 pub fn remove(&mut self, node: OpaqueNode) {
1042 if self.node_to_state_map.remove(&node).is_some() {
1043 self.dirty = true;
1044 }
1045 }
1046
1047 pub fn clear_dirty(&mut self) -> bool {
1049 std::mem::take(&mut self.dirty)
1050 }
1051
1052 pub fn is_empty(&self) -> bool {
1053 self.node_to_state_map.is_empty()
1054 }
1055}
1056
1057struct ThreadStateRestorer;
1058
1059impl ThreadStateRestorer {
1060 fn new() -> Self {
1061 #[cfg(debug_assertions)]
1062 {
1063 thread_state::exit(ThreadState::SCRIPT);
1064 thread_state::enter(ThreadState::LAYOUT);
1065 }
1066 Self
1067 }
1068}
1069
1070impl Drop for ThreadStateRestorer {
1071 fn drop(&mut self) {
1072 #[cfg(debug_assertions)]
1073 {
1074 thread_state::exit(ThreadState::LAYOUT);
1075 thread_state::enter(ThreadState::SCRIPT);
1076 }
1077 }
1078}
1079
1080pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
1086 let _guard = ThreadStateRestorer::new();
1087 f()
1088}
1089
1090#[cfg(test)]
1091mod test {
1092 use std::num::NonZeroU32;
1093 use std::sync::Arc;
1094 use std::time::Duration;
1095
1096 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
1097
1098 use crate::ImageAnimationState;
1099
1100 #[test]
1101 fn test_animated_image_update() {
1102 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1103 delay: Some(Duration::from_millis(100)),
1104 byte_range: 0..1,
1105 width: 100,
1106 height: 100,
1107 })
1108 .take(10)
1109 .collect();
1110 let image = RasterImage {
1111 metadata: ImageMetadata {
1112 width: 100,
1113 height: 100,
1114 },
1115 format: PixelFormat::BGRA8,
1116 id: None,
1117 bytes: Arc::new(vec![1]),
1118 frames: image_frames,
1119 cors_status: CorsStatus::Unsafe,
1120 load_time: None,
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 load_time: None,
1163 loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1164 is_opaque: false,
1165 };
1166 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1167
1168 assert_eq!(image_animation_state.active_frame, 0);
1169 assert_eq!(image_animation_state.frame_start_time, 0.0);
1170 assert_eq!(
1171 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1172 true
1173 );
1174 assert_eq!(image_animation_state.active_frame, 1);
1175 assert_eq!(image_animation_state.frame_start_time, 0.101);
1176 assert_eq!(
1177 image_animation_state.update_frame_for_animation_timeline_value(0.202),
1178 false
1179 );
1180 assert_eq!(
1181 image_animation_state.update_frame_for_animation_timeline_value(0.303),
1182 false
1183 );
1184
1185 assert_eq!(image_animation_state.active_frame, 1);
1186 }
1187}