1#![deny(unsafe_code)]
10
11mod layout_damage;
12mod layout_dom;
13mod layout_element;
14mod layout_node;
15mod pseudo_element_chain;
16
17use std::any::Any;
18use std::ops::Range;
19use std::rc::Rc;
20use std::sync::Arc;
21use std::sync::atomic::AtomicIsize;
22use std::thread::JoinHandle;
23use std::time::Duration;
24
25use app_units::Au;
26use atomic_refcell::AtomicRefCell;
27use background_hang_monitor_api::BackgroundHangMonitorRegister;
28use bitflags::bitflags;
29use embedder_traits::{Cursor, ScriptToEmbedderChan, Theme, UntrustedNodeAddress, ViewportDetails};
30use euclid::{Point2D, Rect};
31use fonts::{FontContext, TextByteRange, WebFontDocumentContext, WebFontSetDifference};
32pub use layout_damage::{AccessibilityDamage, LayoutDamage};
33pub use layout_dom::{
34 DangerousStyleElementOf, DangerousStyleNodeOf, LayoutDomTypeBundle, LayoutElementOf,
35 LayoutNodeOf,
36};
37pub use layout_element::{DangerousStyleElement, LayoutElement};
38pub use layout_node::{DangerousStyleNode, LayoutNode};
39use libc::c_void;
40use malloc_size_of::{MallocSizeOf as MallocSizeOfTrait, MallocSizeOfOps, malloc_size_of_is_0};
41use malloc_size_of_derive::MallocSizeOf;
42use net_traits::image_cache::{ImageCache, ImageCacheFactory, PendingImageId};
43use net_traits::request::InternalRequest;
44use paint_api::CrossProcessPaintApi;
45use paint_api::largest_contentful_paint_candidate::LCPCandidate;
46use parking_lot::RwLock;
47use pixels::{RasterImage, Repeat};
48use profile_traits::mem::Report;
49use profile_traits::time;
50pub use pseudo_element_chain::PseudoElementChain;
51use rustc_hash::{FxHashMap, FxHashSet};
52use script_traits::{InitialScriptState, Painter, ScriptThreadMessage};
53use serde::{Deserialize, Serialize};
54use servo_arc::Arc as ServoArc;
55use servo_base::Epoch;
56use servo_base::generic_channel::GenericSender;
57use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
58use servo_base::text::Utf32CodeUnits;
59use servo_url::{ImmutableOrigin, ServoUrl};
60use style::Atom;
61use style::animation::DocumentAnimationSet;
62use style::attr::{AttrValue, parse_integer, parse_unsigned_integer};
63use style::context::QuirksMode;
64use style::data::ElementDataWrapper;
65use style::device::Device;
66use style::dom::OpaqueNode;
67use style::invalidation::element::restyle_hints::RestyleHint;
68use style::properties::style_structs::Font;
69use style::properties::{ComputedValues, PropertyId};
70use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
71use style::str::char_is_whitespace;
72use style::stylesheets::{DocumentStyleSheet, Stylesheet};
73use style::stylist::Stylist;
74#[cfg(debug_assertions)]
75use style::thread_state::{self, ThreadState};
76use style::values::computed::Overflow;
77use style_traits::CSSPixel;
78use uuid::Uuid;
79use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
80use webrender_api::{ExternalScrollId, ImageKey};
81
82pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait + Send + Sync + 'static {
83 fn as_any(&self) -> &dyn Any;
84}
85
86pub trait LayoutDataTrait: GenericLayoutDataTrait + Default {}
87pub type GenericLayoutData = dyn GenericLayoutDataTrait;
88
89#[derive(Default, MallocSizeOf)]
90pub struct StyleData {
91 pub element_data: ElementDataWrapper,
96
97 pub parallel: DomParallelInfo,
99}
100
101#[derive(Default, MallocSizeOf)]
103pub struct DomParallelInfo {
104 pub children_to_process: AtomicIsize,
106}
107
108#[derive(Clone, Copy, Debug, Eq, PartialEq)]
109pub enum LayoutNodeType {
110 Element(LayoutElementType),
111 Text,
112}
113
114#[derive(Clone, Copy, Debug, Eq, PartialEq)]
115pub enum LayoutElementType {
116 Element,
117 HTMLBodyElement,
118 HTMLButtonElement,
119 HTMLBRElement,
120 HTMLCanvasElement,
121 HTMLHtmlElement,
122 HTMLIFrameElement,
123 HTMLImageElement,
124 HTMLInputElement,
125 HTMLMediaElement,
126 HTMLObjectElement,
127 HTMLOptGroupElement,
128 HTMLOptionElement,
129 HTMLParagraphElement,
130 HTMLPreElement,
131 HTMLSelectElement,
132 HTMLTableCellElement,
133 HTMLTableColElement,
134 HTMLTableElement,
135 HTMLTableRowElement,
136 HTMLTableSectionElement,
137 HTMLTextAreaElement,
138 SVGImageElement,
139 SVGSVGElement,
140}
141
142#[derive(Clone, Debug, Default, MallocSizeOf, PartialEq)]
146pub struct ScriptSelection {
147 pub range: TextByteRange,
149 pub character_range: Range<usize>,
151 pub enabled: bool,
154}
155
156pub type SharedSelection = Arc<AtomicRefCell<ScriptSelection>>;
157pub struct HTMLCanvasData {
158 pub image_key: Option<ImageKey>,
159 pub width: u32,
160 pub height: u32,
161}
162
163pub struct SVGElementData<'dom> {
164 pub source: Option<Result<ServoUrl, ()>>,
166 pub width: Option<&'dom AttrValue>,
167 pub height: Option<&'dom AttrValue>,
168 pub svg_id: Uuid,
169 pub view_box: Option<&'dom AttrValue>,
170}
171
172impl SVGElementData<'_> {
173 pub fn ratio_from_view_box(&self) -> Option<f32> {
174 let mut iter = self.view_box?.chars();
175 let _min_x = parse_integer(&mut iter).ok()?;
176 let _min_y = parse_integer(&mut iter).ok()?;
177
178 let width = parse_unsigned_integer(&mut iter).ok()?;
179 if width == 0 {
180 return None;
181 }
182
183 let height = parse_unsigned_integer(&mut iter).ok()?;
184 if height == 0 {
185 return None;
186 }
187
188 let mut iter = iter.skip_while(|c| char_is_whitespace(*c));
189 iter.next().is_none().then(|| width as f32 / height as f32)
190 }
191}
192
193#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
195pub struct TrustedNodeAddress(pub *const c_void);
196
197#[expect(unsafe_code)]
198unsafe impl Send for TrustedNodeAddress {}
199
200#[derive(Debug)]
202pub enum PendingImageState {
203 Unrequested(ServoUrl),
204 PendingResponse,
205}
206
207#[derive(Debug, MallocSizeOf)]
209pub enum LayoutImageDestination {
210 BoxTreeConstruction,
211 DisplayListBuilding,
212}
213
214#[derive(Debug)]
218pub struct PendingImage {
219 pub state: PendingImageState,
220 pub node: UntrustedNodeAddress,
221 pub id: PendingImageId,
222 pub origin: ImmutableOrigin,
223 pub destination: LayoutImageDestination,
224 pub is_internal_request: InternalRequest,
225}
226
227#[derive(Debug)]
231pub struct PendingRasterizationImage {
232 pub node: UntrustedNodeAddress,
233 pub id: PendingImageId,
234 pub size: DeviceIntSize,
235}
236
237#[derive(Clone, Copy, Debug, MallocSizeOf)]
238pub struct MediaFrame {
239 pub image_key: webrender_api::ImageKey,
240 pub width: i32,
241 pub height: i32,
242}
243
244pub struct MediaMetadata {
245 pub width: u32,
246 pub height: u32,
247}
248
249pub struct HTMLMediaData {
250 pub current_frame: Option<MediaFrame>,
251 pub metadata: Option<MediaMetadata>,
252 pub poster_url: Option<ServoUrl>,
253}
254
255pub struct LayoutConfig {
256 pub id: PipelineId,
257 pub webview_id: WebViewId,
258 pub url: ServoUrl,
259 pub is_iframe: bool,
260 pub script_chan: GenericSender<ScriptThreadMessage>,
261 pub image_cache: Arc<dyn ImageCache>,
262 pub font_context: Arc<FontContext>,
263 pub time_profiler_chan: time::ProfilerChan,
264 pub paint_api: CrossProcessPaintApi,
265 pub viewport_details: ViewportDetails,
266 pub user_stylesheets: Rc<Vec<DocumentStyleSheet>>,
267 pub theme: Theme,
268 pub embedder_chan: ScriptToEmbedderChan,
269}
270
271bitflags! {
272 pub struct HitTestFlags: u8 {
273 const IncludeDomPosition = 0b0000_0001;
275 }
276}
277
278pub trait LayoutFactory: Send + Sync {
279 fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
280}
281
282pub trait Layout {
283 fn device(&self) -> &Device;
286
287 fn set_theme(&mut self, theme: Theme) -> bool;
291
292 fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
296
297 fn add_stylesheet(
302 &mut self,
303 stylesheet: ServoArc<Stylesheet>,
304 before_stylesheet: Option<ServoArc<Stylesheet>>,
305 );
306
307 fn exit_now(&mut self);
309
310 fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
313
314 fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
316
317 fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
319
320 fn remove_cached_image(&mut self, image_url: &ServoUrl);
322
323 fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
325
326 fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
329
330 fn register_paint_worklet_modules(
332 &mut self,
333 name: Atom,
334 properties: Vec<Atom>,
335 painter: Box<dyn Painter>,
336 );
337
338 fn set_scroll_offsets_from_renderer(
340 &mut self,
341 scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
342 );
343
344 fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
347
348 fn needs_new_display_list(&self) -> bool;
350
351 fn set_needs_new_display_list(&self);
353
354 fn node_rendering_type(
357 &self,
358 node: TrustedNodeAddress,
359 pseudo: Option<PseudoElement>,
360 ) -> NodeRenderingType;
361
362 fn query_containing_block(&self, node: TrustedNodeAddress) -> Option<UntrustedNodeAddress>;
363 fn query_containing_block_is_descendant(
364 &self,
365 root: TrustedNodeAddress,
366 possible_descendant: TrustedNodeAddress,
367 ) -> bool;
368 fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
369 fn query_box_area(
370 &self,
371 node: TrustedNodeAddress,
372 area: BoxAreaType,
373 exclude_transform_and_inline: bool,
374 ) -> Option<Rect<Au, CSSPixel>>;
375 fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectVec;
376 fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
377 fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
378 fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
379 fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
380 fn query_scroll_container(
383 &self,
384 node: Option<TrustedNodeAddress>,
385 flags: ScrollContainerQueryFlags,
386 ) -> Option<ScrollContainerResponse>;
387 fn query_resolved_style(
388 &self,
389 node: TrustedNodeAddress,
390 pseudo: Option<PseudoElement>,
391 property_id: PropertyId,
392 animations: DocumentAnimationSet,
393 animation_timeline_value: f64,
394 ) -> String;
395 fn query_resolved_font_style(
396 &self,
397 node: TrustedNodeAddress,
398 value: &str,
399 animations: DocumentAnimationSet,
400 animation_timeline_value: f64,
401 ) -> Option<ServoArc<Font>>;
402 fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
403 fn hit_test(&self, flags: HitTestFlags, point: LayoutPoint) -> HitTestResult;
404 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
405 fn stylist_mut(&mut self) -> &mut Stylist;
406
407 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
410
411 fn accessibility_active(&self) -> bool;
413
414 fn needs_accessibility_update(&self) -> bool;
425
426 fn set_needs_accessibility_update(&self);
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 pub lcp_node_address: Option<UntrustedNodeAddress>,
640}
641
642bitflags! {
643 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
645 pub struct ReflowPhasesRun: u8 {
646 const RanLayout = 1 << 0;
647 const BuiltStackingContextTree = 1 << 2;
648 const BuiltDisplayList = 1 << 3;
649 const UpdatedScrollNodeOffset = 1 << 4;
650 const UpdatedImageData = 1 << 5;
654 const UpdatedAccessibilityTree = 1 << 6;
655 }
656}
657
658impl ReflowPhasesRun {
659 pub fn needs_frame(&self) -> bool {
660 self.intersects(
661 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
662 )
663 }
664}
665
666#[derive(Debug, Default)]
667pub struct ReflowStatistics {
668 pub rebuilt_fragment_count: u32,
670 pub restyle_fragment_count: u32,
672 pub only_descendants_changed_count: u32,
675 pub nodes_updated_from_dom: u32,
678 pub nodes_updated_from_tree: u32,
681 pub nodes_in_tree_update: u32,
683}
684
685#[derive(Debug)]
688pub struct ReflowRequestRestyle {
689 pub reason: RestyleReason,
691 pub dirty_root: Option<TrustedNodeAddress>,
693 pub stylesheets_changed: bool,
695 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
697}
698
699#[derive(Debug)]
701pub struct ReflowRequest {
702 pub document: TrustedNodeAddress,
704 pub epoch: Epoch,
706 pub restyle: Option<ReflowRequestRestyle>,
708 pub viewport_details: ViewportDetails,
710 pub reflow_goal: ReflowGoal,
712 pub origin: ImmutableOrigin,
714 pub animation_timeline_value: f64,
716 pub animations: DocumentAnimationSet,
718 pub animating_images: Arc<RwLock<AnimatingImages>>,
720 pub highlighted_dom_node: Option<OpaqueNode>,
722 pub document_context: WebFontDocumentContext,
724 pub accessibility_damage: Option<Vec<(TrustedNodeAddress, AccessibilityDamage)>>,
726 pub rooted_nodes_for_accessibility_integrity_check: Option<FxHashSet<OpaqueNode>>,
730}
731
732impl ReflowRequest {
733 pub fn stylesheets_changed(&self) -> bool {
734 self.restyle
735 .as_ref()
736 .is_some_and(|restyle| restyle.stylesheets_changed)
737 }
738}
739
740#[derive(Debug, Default, MallocSizeOf)]
742pub struct PendingRestyle {
743 pub snapshot: Option<Snapshot>,
746
747 pub hint: RestyleHint,
749
750 pub damage: RestyleDamage,
752}
753
754#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
760pub enum FragmentType {
761 FragmentBody,
763 BeforePseudoContent,
765 AfterPseudoContent,
767}
768
769impl From<Option<PseudoElement>> for FragmentType {
770 fn from(value: Option<PseudoElement>) -> Self {
771 match value {
772 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
773 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
774 _ => FragmentType::FragmentBody,
775 }
776 }
777}
778
779pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
780 debug_assert_eq!(id & (fragment_type as usize), 0);
781 (id as u64) | (fragment_type as u64)
782}
783
784pub fn node_id_from_scroll_id(id: usize) -> usize {
785 id & !3
786}
787
788#[derive(Clone, Debug, MallocSizeOf)]
789pub struct ImageAnimationState {
790 #[conditional_malloc_size_of]
791 pub image: Arc<RasterImage>,
792 pub active_frame: usize,
793 frame_start_time: f64,
794
795 pub completed_loops: Option<u32>,
800}
801
802impl ImageAnimationState {
803 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
804 let completd_loops = match &image.loop_count {
805 None => unreachable!("Loop count of an animated Image should never be None"),
806 Some(repeat) if Repeat::Infinite == *repeat => None,
807 _ => Some(0),
808 };
809
810 Self {
811 image,
812 active_frame: 0,
813 frame_start_time: last_update_time,
814 completed_loops: completd_loops,
815 }
816 }
817
818 pub fn image_key(&self) -> Option<ImageKey> {
819 self.image.id
820 }
821
822 pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
823 if self.is_finished() {
824 return None;
825 }
826 let frame_delay = self
827 .image
828 .frames
829 .get(self.active_frame)
830 .expect("Image frame should always be valid")
831 .delay
832 .unwrap_or_default();
833
834 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
835 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
836 Some(frame_delay - time_since_frame_start.min(frame_delay))
837 }
838
839 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
843 if self.image.frames.len() <= 1 || self.is_finished() {
844 return false;
845 }
846 let time_interval_since_last_update = now - self.frame_start_time;
847 let mut remain_time_interval = time_interval_since_last_update -
848 self.image
849 .frames
850 .get(self.active_frame)
851 .unwrap()
852 .delay()
853 .unwrap()
854 .as_secs_f64();
855 let mut next_active_frame_id = self.active_frame;
856
857 let frame_count = self.image.frames.len();
858 while remain_time_interval > 0.0 {
859 next_active_frame_id = (next_active_frame_id + 1) % frame_count;
860
861 if next_active_frame_id == 0 {
863 self.advance_completed_loops();
864
865 if self.is_finished() {
868 if self.active_frame == frame_count - 1 {
869 return false;
870 }
871 self.active_frame = frame_count - 1;
872 self.frame_start_time = now;
873 return true;
874 }
875 }
876
877 remain_time_interval -= self
878 .image
879 .frames
880 .get(next_active_frame_id)
881 .unwrap()
882 .delay()
883 .unwrap()
884 .as_secs_f64();
885 }
886 if self.active_frame == next_active_frame_id {
887 return false;
888 }
889 self.active_frame = next_active_frame_id;
890 self.frame_start_time = now;
891 true
892 }
893
894 fn is_finished(&self) -> bool {
896 let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
897 return false;
898 };
899 self.completed_loops
900 .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
901 }
902
903 fn advance_completed_loops(&mut self) {
905 if let Some(completed_loops) = self.completed_loops.as_mut() {
906 *completed_loops += 1;
907 }
908 }
909}
910
911#[derive(Debug, Default)]
913pub struct HitTestResult {
914 pub items: Vec<HitTestResultItem>,
915 pub dom_position_for_selection: Option<(OpaqueNode, Utf32CodeUnits)>,
916}
917
918#[derive(Debug)]
920pub struct HitTestResultItem {
921 pub node: OpaqueNode,
924 pub point_in_target: Point2D<f32, CSSPixel>,
927 pub cursor: Cursor,
930}
931
932#[derive(Debug, Default, MallocSizeOf)]
933pub struct AnimatingImages {
934 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
937 pub dirty: bool,
940}
941
942impl AnimatingImages {
943 pub fn maybe_insert_or_update(
944 &mut self,
945 node: OpaqueNode,
946 image: Arc<RasterImage>,
947 current_timeline_value: f64,
948 ) {
949 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
950 self.dirty = true;
951 ImageAnimationState::new(image.clone(), current_timeline_value)
952 });
953
954 if entry.image.id != image.id {
957 self.dirty = true;
958 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
959 }
960 }
961
962 pub fn remove(&mut self, node: OpaqueNode) {
963 if self.node_to_state_map.remove(&node).is_some() {
964 self.dirty = true;
965 }
966 }
967
968 pub fn clear_dirty(&mut self) -> bool {
970 std::mem::take(&mut self.dirty)
971 }
972
973 pub fn is_empty(&self) -> bool {
974 self.node_to_state_map.is_empty()
975 }
976}
977
978struct ThreadStateRestorer;
979
980impl ThreadStateRestorer {
981 fn new() -> Self {
982 #[cfg(debug_assertions)]
983 {
984 thread_state::exit(ThreadState::SCRIPT);
985 thread_state::enter(ThreadState::LAYOUT);
986 }
987 Self
988 }
989}
990
991impl Drop for ThreadStateRestorer {
992 fn drop(&mut self) {
993 #[cfg(debug_assertions)]
994 {
995 thread_state::exit(ThreadState::LAYOUT);
996 thread_state::enter(ThreadState::SCRIPT);
997 }
998 }
999}
1000
1001pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
1007 let _guard = ThreadStateRestorer::new();
1008 f()
1009}
1010
1011#[cfg(test)]
1012mod test {
1013 use std::num::NonZeroU32;
1014 use std::sync::Arc;
1015 use std::time::Duration;
1016
1017 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
1018
1019 use crate::ImageAnimationState;
1020
1021 #[test]
1022 fn test_animated_image_update() {
1023 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1024 delay: Some(Duration::from_millis(100)),
1025 byte_range: 0..1,
1026 width: 100,
1027 height: 100,
1028 })
1029 .take(10)
1030 .collect();
1031 let image = RasterImage {
1032 metadata: ImageMetadata {
1033 width: 100,
1034 height: 100,
1035 },
1036 format: PixelFormat::BGRA8,
1037 id: None,
1038 bytes: Arc::new(vec![1]),
1039 frames: image_frames,
1040 cors_status: CorsStatus::Unsafe,
1041 loop_count: Some(Repeat::Infinite),
1042 is_opaque: false,
1043 };
1044 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1045
1046 assert_eq!(image_animation_state.active_frame, 0);
1047 assert_eq!(image_animation_state.frame_start_time, 0.0);
1048 assert_eq!(
1049 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1050 true
1051 );
1052 assert_eq!(image_animation_state.active_frame, 1);
1053 assert_eq!(image_animation_state.frame_start_time, 0.101);
1054 assert_eq!(
1055 image_animation_state.update_frame_for_animation_timeline_value(0.116),
1056 false
1057 );
1058 assert_eq!(image_animation_state.active_frame, 1);
1059 assert_eq!(image_animation_state.frame_start_time, 0.101);
1060 }
1061
1062 #[test]
1063 fn test_finite_image_repeat() {
1064 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1065 delay: Some(Duration::from_millis(100)),
1066 byte_range: 0..1,
1067 width: 100,
1068 height: 100,
1069 })
1070 .take(2)
1071 .collect();
1072 let image = RasterImage {
1073 metadata: ImageMetadata {
1074 width: 100,
1075 height: 100,
1076 },
1077 format: PixelFormat::BGRA8,
1078 id: None,
1079 bytes: Arc::new(vec![1]),
1080 frames: image_frames,
1081 cors_status: CorsStatus::Unsafe,
1082 loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1083 is_opaque: false,
1084 };
1085 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1086
1087 assert_eq!(image_animation_state.active_frame, 0);
1088 assert_eq!(image_animation_state.frame_start_time, 0.0);
1089 assert_eq!(
1090 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1091 true
1092 );
1093 assert_eq!(image_animation_state.active_frame, 1);
1094 assert_eq!(image_animation_state.frame_start_time, 0.101);
1095 assert_eq!(
1096 image_animation_state.update_frame_for_animation_timeline_value(0.202),
1097 false
1098 );
1099 assert_eq!(
1100 image_animation_state.update_frame_for_animation_timeline_value(0.303),
1101 false
1102 );
1103
1104 assert_eq!(image_animation_state.active_frame, 1);
1105 }
1106}