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, Utf32CodeUnitsOrNodeOffset};
59use servo_url::{ImmutableOrigin, ServoUrl};
60use style::Atom;
61use style::animation::DocumentAnimationSet;
62use style::attr::{AttrValue, parse_integer, parse_unsigned_integer};
63use style::context::QuirksMode;
64use style::data::ElementDataWrapper;
65use style::device::Device;
66use style::dom::OpaqueNode;
67use style::invalidation::element::restyle_hints::RestyleHint;
68use style::properties::style_structs::Font;
69use style::properties::{ComputedValues, PropertyId};
70use style::selector_parser::{PseudoElement, RestyleDamage, Snapshot};
71use style::str::char_is_whitespace;
72use style::stylesheets::{DocumentStyleSheet, Stylesheet};
73use style::stylist::Stylist;
74#[cfg(debug_assertions)]
75use style::thread_state::{self, ThreadState};
76use style::values::computed::Overflow;
77use style_traits::CSSPixel;
78use uuid::Uuid;
79use webrender_api::units::{DeviceIntSize, LayoutPoint, LayoutVector2D};
80use webrender_api::{ExternalScrollId, ImageKey};
81
82pub trait GenericLayoutDataTrait: Any + MallocSizeOfTrait + Send + Sync + 'static {
83 fn as_any(&self) -> &dyn Any;
84}
85
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 #[derive(Copy, Clone)]
273 pub struct HitTestFlags: u8 {
274 const IncludeDomPosition = 0b0000_0001;
276 }
277}
278
279pub trait LayoutFactory: Send + Sync {
280 fn create(&self, config: LayoutConfig) -> Box<dyn Layout>;
281}
282
283pub trait Layout {
284 fn device(&self) -> &Device;
287
288 fn set_theme(&mut self, theme: Theme) -> bool;
292
293 fn set_viewport_details(&mut self, viewport_details: ViewportDetails) -> bool;
297
298 fn add_stylesheet(
303 &mut self,
304 stylesheet: ServoArc<Stylesheet>,
305 before_stylesheet: Option<ServoArc<Stylesheet>>,
306 );
307
308 fn exit_now(&mut self);
310
311 fn collect_reports(&self, reports: &mut Vec<Report>, ops: &mut MallocSizeOfOps);
314
315 fn set_quirks_mode(&mut self, quirks_mode: QuirksMode);
317
318 fn remove_stylesheet(&mut self, stylesheet: ServoArc<Stylesheet>);
320
321 fn remove_cached_image(&mut self, image_url: &ServoUrl);
323
324 fn reflow(&mut self, reflow_request: ReflowRequest) -> Option<ReflowResult>;
326
327 fn ensure_stacking_context_tree(&self, viewport_details: ViewportDetails);
330
331 fn register_paint_worklet_modules(
333 &mut self,
334 name: Atom,
335 properties: Vec<Atom>,
336 painter: Box<dyn Painter>,
337 );
338
339 fn set_scroll_offsets_from_renderer(
341 &mut self,
342 scroll_states: &FxHashMap<ExternalScrollId, LayoutVector2D>,
343 );
344
345 fn scroll_offset(&self, id: ExternalScrollId) -> Option<LayoutVector2D>;
348
349 fn needs_new_display_list(&self) -> bool;
351
352 fn set_needs_new_display_list(&self);
354
355 fn node_rendering_type(
358 &self,
359 node: TrustedNodeAddress,
360 pseudo: Option<PseudoElement>,
361 ) -> NodeRenderingType;
362
363 fn query_containing_block(&self, node: TrustedNodeAddress) -> Option<UntrustedNodeAddress>;
364 fn query_containing_block_is_descendant(
365 &self,
366 root: TrustedNodeAddress,
367 possible_descendant: TrustedNodeAddress,
368 ) -> bool;
369 fn query_padding(&self, node: TrustedNodeAddress) -> Option<PhysicalSides>;
370 fn query_box_area(
371 &self,
372 node: TrustedNodeAddress,
373 area: BoxAreaType,
374 exclude_transform_and_inline: bool,
375 ) -> Option<Rect<Au, CSSPixel>>;
376 fn query_box_areas(&self, node: TrustedNodeAddress, area: BoxAreaType) -> CSSPixelRectVec;
377 fn query_client_rect(&self, node: TrustedNodeAddress) -> Rect<i32, CSSPixel>;
378 fn query_current_css_zoom(&self, node: TrustedNodeAddress) -> f32;
379 fn query_element_inner_outer_text(&self, node: TrustedNodeAddress) -> String;
380 fn query_offset_parent(&self, node: TrustedNodeAddress) -> OffsetParentResponse;
381 fn query_scroll_container(
384 &self,
385 node: Option<TrustedNodeAddress>,
386 flags: ScrollContainerQueryFlags,
387 ) -> Option<ScrollContainerResponse>;
388 fn query_resolved_style(
389 &self,
390 node: TrustedNodeAddress,
391 pseudo: Option<PseudoElement>,
392 property_id: PropertyId,
393 animations: DocumentAnimationSet,
394 animation_timeline_value: f64,
395 ) -> String;
396 fn query_resolved_font_style(
397 &self,
398 node: TrustedNodeAddress,
399 value: &str,
400 animations: DocumentAnimationSet,
401 animation_timeline_value: f64,
402 ) -> Option<ServoArc<Font>>;
403 fn query_scrolling_area(&self, node: Option<TrustedNodeAddress>) -> Rect<i32, CSSPixel>;
404 fn query_text_index(
408 &self,
409 node: TrustedNodeAddress,
410 point_in_viewport: Point2D<Au, CSSPixel>,
411 ) -> Option<(OpaqueNode, Utf32CodeUnits)>;
412 fn hit_test(&self, flags: HitTestFlags, point: LayoutPoint) -> HitTestResult;
413 fn query_effective_overflow(&self, node: TrustedNodeAddress) -> Option<AxesOverflow>;
414 fn stylist_mut(&mut self) -> &mut Stylist;
415
416 fn set_accessibility_active(&self, enabled: bool, epoch: Epoch);
419
420 fn accessibility_active(&self) -> bool;
422
423 fn force_accessibility_update(&self) -> bool;
431
432 fn set_force_accessibility_update(&self);
434
435 fn font_context(&self) -> &Arc<FontContext>;
436}
437
438pub trait ScriptThreadFactory {
442 fn create(
444 state: InitialScriptState,
445 layout_factory: Arc<dyn LayoutFactory>,
446 image_cache_factory: Arc<dyn ImageCacheFactory>,
447 background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
448 ) -> JoinHandle<()>;
449}
450
451#[derive(Copy, Clone)]
454pub enum BoxAreaType {
455 Content,
456 Padding,
457 Border,
458}
459
460pub type CSSPixelRectVec = Vec<Rect<Au, CSSPixel>>;
461
462#[derive(Copy, Clone)]
465pub enum NodeRenderingType {
466 Rendered,
468 DelegatesRendering,
470 NotRendered,
473}
474
475#[derive(Default)]
476pub struct PhysicalSides {
477 pub left: Au,
478 pub top: Au,
479 pub right: Au,
480 pub bottom: Au,
481}
482
483#[derive(Clone, Default)]
484pub struct OffsetParentResponse {
485 pub node_address: Option<UntrustedNodeAddress>,
486 pub rect: Rect<Au, CSSPixel>,
487}
488
489bitflags! {
490 #[derive(PartialEq)]
491 pub struct ScrollContainerQueryFlags: u8 {
492 const ForScrollParent = 1 << 0;
494 const Inclusive = 1 << 1;
496 }
497}
498
499#[derive(Clone, Copy, Debug, MallocSizeOf)]
500pub struct AxesOverflow {
501 pub x: Overflow,
502 pub y: Overflow,
503}
504
505impl Default for AxesOverflow {
506 fn default() -> Self {
507 Self {
508 x: Overflow::Visible,
509 y: Overflow::Visible,
510 }
511 }
512}
513
514impl From<&ComputedValues> for AxesOverflow {
515 fn from(style: &ComputedValues) -> Self {
516 Self {
517 x: style.clone_overflow_x(),
518 y: style.clone_overflow_y(),
519 }
520 }
521}
522
523impl AxesOverflow {
524 pub fn to_scrollable(&self) -> Self {
525 Self {
526 x: self.x.to_scrollable(),
527 y: self.y.to_scrollable(),
528 }
529 }
530
531 pub fn establishes_scroll_container(&self) -> bool {
533 self.x.is_scrollable()
536 }
537}
538
539#[derive(Clone)]
540pub enum ScrollContainerResponse {
541 Viewport(AxesOverflow),
542 Element(UntrustedNodeAddress, AxesOverflow),
543}
544
545#[derive(Debug, PartialEq)]
546pub enum QueryMsg {
547 BoxArea,
548 BoxAreas,
549 ClientRectQuery,
550 CurrentCSSZoomQuery,
551 EffectiveOverflow,
552 ElementInnerOuterTextQuery,
553 ElementsFromPoint,
554 InnerWindowDimensionsQuery,
555 NodesFromPointQuery,
556 OffsetParentQuery,
557 ScrollParentQuery,
558 ResolvedFontStyleQuery,
559 ResolvedStyleQuery(PropertyId),
562 ScrollingAreaOrOffsetQuery,
563 StyleQuery,
564 TextIndexQuery,
565 PaddingQuery,
566 FlushForUpdateTheRenderingQuery,
567}
568
569#[derive(Debug, PartialEq)]
575pub enum ReflowGoal {
576 UpdateTheRendering,
579
580 LayoutQuery(QueryMsg),
583
584 UpdateScrollNode(ExternalScrollId, LayoutVector2D),
588}
589
590#[derive(Clone, Debug, MallocSizeOf)]
591pub struct IFrameSize {
592 pub browsing_context_id: BrowsingContextId,
593 pub pipeline_id: PipelineId,
594 pub viewport_details: ViewportDetails,
595}
596
597pub type IFrameSizes = FxHashMap<BrowsingContextId, IFrameSize>;
598
599bitflags! {
600 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
603 pub struct RestyleReason: u16 {
604 const StylesheetsChanged = 1 << 0;
605 const DOMChanged = 1 << 1;
606 const PendingRestyles = 1 << 2;
607 const HighlightedDOMNodeChanged = 1 << 3;
608 const ThemeChanged = 1 << 4;
609 const ViewportChanged = 1 << 5;
610 const PaintWorkletLoaded = 1 << 6;
611 }
612}
613
614malloc_size_of_is_0!(RestyleReason);
615
616impl RestyleReason {
617 pub fn needs_restyle(&self) -> bool {
618 !self.is_empty()
619 }
620}
621
622#[derive(Default)]
624pub struct ReflowResult {
625 pub reflow_phases_run: ReflowPhasesRun,
627 pub reflow_statistics: ReflowStatistics,
628 pub pending_images: Vec<PendingImage>,
630 pub pending_rasterization_images: Vec<PendingRasterizationImage>,
632 pub pending_svg_elements_for_serialization: Vec<UntrustedNodeAddress>,
636 pub iframe_sizes: Option<IFrameSizes>,
642 pub changed_web_fonts: WebFontSetDifference,
644 pub lcp_candidate: Option<LCPCandidate>,
646 pub lcp_node_address: Option<UntrustedNodeAddress>,
648}
649
650bitflags! {
651 #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
653 pub struct ReflowPhasesRun: u8 {
654 const RanLayout = 1 << 0;
655 const BuiltStackingContextTree = 1 << 2;
656 const BuiltDisplayList = 1 << 3;
657 const UpdatedScrollNodeOffset = 1 << 4;
658 const UpdatedImageData = 1 << 5;
662 const UpdatedAccessibilityTree = 1 << 6;
663 }
664}
665
666impl ReflowPhasesRun {
667 pub fn needs_frame(&self) -> bool {
668 self.intersects(
669 Self::BuiltDisplayList | Self::UpdatedScrollNodeOffset | Self::UpdatedImageData,
670 )
671 }
672}
673
674#[derive(Debug, Default)]
675pub struct ReflowStatistics {
676 pub rebuilt_fragment_count: u32,
678 pub restyle_fragment_count: u32,
680 pub only_descendants_changed_count: u32,
683 pub nodes_updated_from_dom: u32,
686 pub nodes_updated_from_tree: u32,
689 pub nodes_updated_bounds: u32,
692 pub nodes_in_tree_update: u32,
694}
695
696#[derive(Debug)]
699pub struct ReflowRequestRestyle {
700 pub reason: RestyleReason,
702 pub dirty_root: Option<TrustedNodeAddress>,
704 pub stylesheets_changed: bool,
706 pub pending_restyles: Vec<(TrustedNodeAddress, PendingRestyle)>,
708}
709
710#[derive(Debug)]
712pub struct ReflowRequest {
713 pub document: TrustedNodeAddress,
715 pub epoch: Epoch,
717 pub restyle: Option<ReflowRequestRestyle>,
719 pub viewport_details: ViewportDetails,
721 pub reflow_goal: ReflowGoal,
723 pub origin: ImmutableOrigin,
725 pub animation_timeline_value: f64,
727 pub animations: DocumentAnimationSet,
729 pub animating_images: Arc<RwLock<AnimatingImages>>,
731 pub highlighted_dom_node: Option<OpaqueNode>,
733 pub halt_lcp: bool,
737 pub document_context: WebFontDocumentContext,
739 pub accessibility_damage: Option<Vec<(TrustedNodeAddress, AccessibilityDamage)>>,
741 pub rooted_nodes_for_accessibility_integrity_check: Option<FxHashSet<OpaqueNode>>,
745}
746
747impl ReflowRequest {
748 pub fn stylesheets_changed(&self) -> bool {
749 self.restyle
750 .as_ref()
751 .is_some_and(|restyle| restyle.stylesheets_changed)
752 }
753}
754
755#[derive(Debug, Default, MallocSizeOf)]
757pub struct PendingRestyle {
758 pub snapshot: Option<Snapshot>,
761
762 pub hint: RestyleHint,
764
765 pub damage: RestyleDamage,
767}
768
769#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
775pub enum FragmentType {
776 FragmentBody,
778 BeforePseudoContent,
780 AfterPseudoContent,
782}
783
784impl From<Option<PseudoElement>> for FragmentType {
785 fn from(value: Option<PseudoElement>) -> Self {
786 match value {
787 Some(PseudoElement::After) => FragmentType::AfterPseudoContent,
788 Some(PseudoElement::Before) => FragmentType::BeforePseudoContent,
789 _ => FragmentType::FragmentBody,
790 }
791 }
792}
793
794pub fn combine_id_with_fragment_type(id: usize, fragment_type: FragmentType) -> u64 {
795 debug_assert_eq!(id & (fragment_type as usize), 0);
796 (id as u64) | (fragment_type as u64)
797}
798
799pub fn node_id_from_scroll_id(id: usize) -> usize {
800 id & !3
801}
802
803#[derive(Clone, Debug, MallocSizeOf)]
804pub struct ImageAnimationState {
805 #[conditional_malloc_size_of]
806 pub image: Arc<RasterImage>,
807 pub active_frame: usize,
808 frame_start_time: f64,
809
810 pub completed_loops: Option<u32>,
815}
816
817impl ImageAnimationState {
818 pub fn new(image: Arc<RasterImage>, last_update_time: f64) -> Self {
819 let completd_loops = match &image.loop_count {
820 None => unreachable!("Loop count of an animated Image should never be None"),
821 Some(repeat) if Repeat::Infinite == *repeat => None,
822 _ => Some(0),
823 };
824
825 Self {
826 image,
827 active_frame: 0,
828 frame_start_time: last_update_time,
829 completed_loops: completd_loops,
830 }
831 }
832
833 pub fn image_key(&self) -> Option<ImageKey> {
834 self.image.id
835 }
836
837 pub fn duration_to_next_frame(&self, now: f64) -> Option<Duration> {
838 if self.is_finished() {
839 return None;
840 }
841 let frame_delay = self
842 .image
843 .frames
844 .get(self.active_frame)
845 .expect("Image frame should always be valid")
846 .delay
847 .unwrap_or_default();
848
849 let time_since_frame_start = (now - self.frame_start_time).max(0.0) * 1000.0;
850 let time_since_frame_start = Duration::from_secs_f64(time_since_frame_start);
851 Some(frame_delay - time_since_frame_start.min(frame_delay))
852 }
853
854 pub fn update_frame_for_animation_timeline_value(&mut self, now: f64) -> bool {
858 if self.image.frames.len() <= 1 || self.is_finished() {
859 return false;
860 }
861 let time_interval_since_last_update = now - self.frame_start_time;
862 let mut remain_time_interval = time_interval_since_last_update -
863 self.image
864 .frames
865 .get(self.active_frame)
866 .unwrap()
867 .delay()
868 .unwrap()
869 .as_secs_f64();
870 let mut next_active_frame_id = self.active_frame;
871
872 let frame_count = self.image.frames.len();
873 while remain_time_interval > 0.0 {
874 next_active_frame_id = (next_active_frame_id + 1) % frame_count;
875
876 if next_active_frame_id == 0 {
878 self.advance_completed_loops();
879
880 if self.is_finished() {
883 if self.active_frame == frame_count - 1 {
884 return false;
885 }
886 self.active_frame = frame_count - 1;
887 self.frame_start_time = now;
888 return true;
889 }
890 }
891
892 remain_time_interval -= self
893 .image
894 .frames
895 .get(next_active_frame_id)
896 .unwrap()
897 .delay()
898 .unwrap()
899 .as_secs_f64();
900 }
901 if self.active_frame == next_active_frame_id {
902 return false;
903 }
904 self.active_frame = next_active_frame_id;
905 self.frame_start_time = now;
906 true
907 }
908
909 fn is_finished(&self) -> bool {
911 let Some(Repeat::Finite(maximum_loops)) = self.image.loop_count.as_ref() else {
912 return false;
913 };
914 self.completed_loops
915 .is_some_and(|completed_loops| completed_loops >= maximum_loops.get())
916 }
917
918 fn advance_completed_loops(&mut self) {
920 if let Some(completed_loops) = self.completed_loops.as_mut() {
921 *completed_loops += 1;
922 }
923 }
924}
925
926#[derive(Debug, Default)]
928pub struct HitTestResult {
929 pub items: Vec<HitTestResultItem>,
930 pub dom_position_for_selection: Option<(OpaqueNode, Utf32CodeUnitsOrNodeOffset)>,
931}
932
933#[derive(Debug)]
935pub struct HitTestResultItem {
936 pub node: OpaqueNode,
939 pub point_in_target: Point2D<f32, CSSPixel>,
942 pub cursor: Cursor,
945}
946
947#[derive(Debug, Default, MallocSizeOf)]
948pub struct AnimatingImages {
949 pub node_to_state_map: FxHashMap<OpaqueNode, ImageAnimationState>,
952 pub dirty: bool,
955}
956
957impl AnimatingImages {
958 pub fn maybe_insert_or_update(
959 &mut self,
960 node: OpaqueNode,
961 image: Arc<RasterImage>,
962 current_timeline_value: f64,
963 ) {
964 let entry = self.node_to_state_map.entry(node).or_insert_with(|| {
965 self.dirty = true;
966 ImageAnimationState::new(image.clone(), current_timeline_value)
967 });
968
969 if entry.image.id != image.id {
972 self.dirty = true;
973 *entry = ImageAnimationState::new(image.clone(), current_timeline_value);
974 }
975 }
976
977 pub fn remove(&mut self, node: OpaqueNode) {
978 if self.node_to_state_map.remove(&node).is_some() {
979 self.dirty = true;
980 }
981 }
982
983 pub fn clear_dirty(&mut self) -> bool {
985 std::mem::take(&mut self.dirty)
986 }
987
988 pub fn is_empty(&self) -> bool {
989 self.node_to_state_map.is_empty()
990 }
991}
992
993struct ThreadStateRestorer;
994
995impl ThreadStateRestorer {
996 fn new() -> Self {
997 #[cfg(debug_assertions)]
998 {
999 thread_state::exit(ThreadState::SCRIPT);
1000 thread_state::enter(ThreadState::LAYOUT);
1001 }
1002 Self
1003 }
1004}
1005
1006impl Drop for ThreadStateRestorer {
1007 fn drop(&mut self) {
1008 #[cfg(debug_assertions)]
1009 {
1010 thread_state::exit(ThreadState::LAYOUT);
1011 thread_state::enter(ThreadState::SCRIPT);
1012 }
1013 }
1014}
1015
1016pub fn with_layout_state<R>(f: impl FnOnce() -> R) -> R {
1022 let _guard = ThreadStateRestorer::new();
1023 f()
1024}
1025
1026#[cfg(test)]
1027mod test {
1028 use std::num::NonZeroU32;
1029 use std::sync::Arc;
1030 use std::time::Duration;
1031
1032 use pixels::{CorsStatus, ImageFrame, ImageMetadata, PixelFormat, RasterImage, Repeat};
1033
1034 use crate::ImageAnimationState;
1035
1036 #[test]
1037 fn test_animated_image_update() {
1038 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1039 delay: Some(Duration::from_millis(100)),
1040 byte_range: 0..1,
1041 width: 100,
1042 height: 100,
1043 })
1044 .take(10)
1045 .collect();
1046 let image = RasterImage {
1047 metadata: ImageMetadata {
1048 width: 100,
1049 height: 100,
1050 },
1051 format: PixelFormat::BGRA8,
1052 id: None,
1053 bytes: Arc::new(vec![1]),
1054 frames: image_frames,
1055 cors_status: CorsStatus::Unsafe,
1056 loop_count: Some(Repeat::Infinite),
1057 is_opaque: false,
1058 };
1059 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1060
1061 assert_eq!(image_animation_state.active_frame, 0);
1062 assert_eq!(image_animation_state.frame_start_time, 0.0);
1063 assert_eq!(
1064 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1065 true
1066 );
1067 assert_eq!(image_animation_state.active_frame, 1);
1068 assert_eq!(image_animation_state.frame_start_time, 0.101);
1069 assert_eq!(
1070 image_animation_state.update_frame_for_animation_timeline_value(0.116),
1071 false
1072 );
1073 assert_eq!(image_animation_state.active_frame, 1);
1074 assert_eq!(image_animation_state.frame_start_time, 0.101);
1075 }
1076
1077 #[test]
1078 fn test_finite_image_repeat() {
1079 let image_frames: Vec<ImageFrame> = std::iter::repeat_with(|| ImageFrame {
1080 delay: Some(Duration::from_millis(100)),
1081 byte_range: 0..1,
1082 width: 100,
1083 height: 100,
1084 })
1085 .take(2)
1086 .collect();
1087 let image = RasterImage {
1088 metadata: ImageMetadata {
1089 width: 100,
1090 height: 100,
1091 },
1092 format: PixelFormat::BGRA8,
1093 id: None,
1094 bytes: Arc::new(vec![1]),
1095 frames: image_frames,
1096 cors_status: CorsStatus::Unsafe,
1097 loop_count: Some(Repeat::Finite(NonZeroU32::new(1).unwrap())),
1098 is_opaque: false,
1099 };
1100 let mut image_animation_state = ImageAnimationState::new(Arc::new(image), 0.0);
1101
1102 assert_eq!(image_animation_state.active_frame, 0);
1103 assert_eq!(image_animation_state.frame_start_time, 0.0);
1104 assert_eq!(
1105 image_animation_state.update_frame_for_animation_timeline_value(0.101),
1106 true
1107 );
1108 assert_eq!(image_animation_state.active_frame, 1);
1109 assert_eq!(image_animation_state.frame_start_time, 0.101);
1110 assert_eq!(
1111 image_animation_state.update_frame_for_animation_timeline_value(0.202),
1112 false
1113 );
1114 assert_eq!(
1115 image_animation_state.update_frame_for_animation_timeline_value(0.303),
1116 false
1117 );
1118
1119 assert_eq!(image_animation_state.active_frame, 1);
1120 }
1121}