1use std::rc::Rc;
6
7use app_units::Au;
8use dom_struct::dom_struct;
9use euclid::Size2D;
10use euclid::default::Rect;
11use euclid::num::Zero;
12use html5ever::ns;
13use js::rust::HandleObject;
14use layout_api::BoxAreaType;
15
16use crate::dom::bindings::callback::ExceptionHandling;
17use crate::dom::bindings::cell::DomRefCell;
18use crate::dom::bindings::codegen::Bindings::ResizeObserverBinding::{
19 ResizeObserverBoxOptions, ResizeObserverCallback, ResizeObserverMethods, ResizeObserverOptions,
20};
21use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
22use crate::dom::bindings::inheritance::Castable;
23use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
24use crate::dom::bindings::root::{Dom, DomRoot};
25use crate::dom::document::RenderingUpdateReason;
26use crate::dom::domrectreadonly::DOMRectReadOnly;
27use crate::dom::element::Element;
28use crate::dom::node::{Node, NodeTraits};
29use crate::dom::resizeobserverentry::ResizeObserverEntry;
30use crate::dom::resizeobserversize::{ResizeObserverSize, ResizeObserverSizeImpl};
31use crate::dom::window::Window;
32use crate::script_runtime::CanGc;
33
34#[derive(Debug, Default, PartialEq, PartialOrd)]
36pub(crate) struct ResizeObservationDepth(usize);
37
38impl ResizeObservationDepth {
39 pub(crate) fn max() -> ResizeObservationDepth {
40 ResizeObservationDepth(usize::MAX)
41 }
42}
43
44#[dom_struct]
47pub(crate) struct ResizeObserver {
48 reflector_: Reflector,
49
50 #[conditional_malloc_size_of]
52 callback: Rc<ResizeObserverCallback>,
53
54 observation_targets: DomRefCell<Vec<(ResizeObservation, Dom<Element>)>>,
61}
62
63impl ResizeObserver {
64 pub(crate) fn new_inherited(callback: Rc<ResizeObserverCallback>) -> ResizeObserver {
65 ResizeObserver {
66 reflector_: Reflector::new(),
67 callback,
68 observation_targets: Default::default(),
69 }
70 }
71
72 fn new(
73 window: &Window,
74 proto: Option<HandleObject>,
75 callback: Rc<ResizeObserverCallback>,
76 can_gc: CanGc,
77 ) -> DomRoot<ResizeObserver> {
78 let observer = Box::new(ResizeObserver::new_inherited(callback));
79 reflect_dom_object_with_proto(observer, window, proto, can_gc)
80 }
81
82 pub(crate) fn gather_active_resize_observations_at_depth(
86 &self,
87 depth: &ResizeObservationDepth,
88 has_active: &mut bool,
89 ) {
90 for (observation, target) in self.observation_targets.borrow_mut().iter_mut() {
95 observation.state = Default::default();
96
97 if observation.is_active(target) {
99 let target_depth = calculate_depth_for_node(target);
101
102 if target_depth > *depth {
104 observation.state = ObservationState::Active;
105 *has_active = true;
106 }
107 else {
109 observation.state = ObservationState::Skipped;
110 }
111 }
112 }
113 }
114
115 pub(crate) fn broadcast_active_resize_observations(
117 &self,
118 shallowest_target_depth: &mut ResizeObservationDepth,
119 can_gc: CanGc,
120 ) {
121 let mut has_active_observation_targets = false;
126
127 let mut entries: Vec<DomRoot<ResizeObserverEntry>> = Default::default();
129
130 for (observation, target) in self.observation_targets.borrow_mut().iter_mut() {
132 let ObservationState::Active = observation.state else {
133 continue;
134 };
135 has_active_observation_targets = true;
136
137 let window = target.owner_window();
138 let entry =
139 create_and_populate_a_resizeobserverentry(&window, target, observation, can_gc);
140 entries.push(entry);
141 observation.state = ObservationState::Done;
142
143 let target_depth = calculate_depth_for_node(target);
144 if target_depth < *shallowest_target_depth {
145 *shallowest_target_depth = target_depth;
146 }
147 }
148
149 if !has_active_observation_targets {
150 return;
151 }
152
153 let _ = self
155 .callback
156 .Call_(self, entries, self, ExceptionHandling::Report, can_gc);
157
158 }
161
162 pub(crate) fn has_skipped_resize_observations(&self) -> bool {
164 self.observation_targets
165 .borrow()
166 .iter()
167 .any(|(observation, _)| observation.state == ObservationState::Skipped)
168 }
169}
170
171fn create_and_populate_a_resizeobserverentry(
173 window: &Window,
174 target: &Element,
175 observation: &mut ResizeObservation,
176 can_gc: CanGc,
177) -> DomRoot<ResizeObserverEntry> {
178 let border_box_size = calculate_box_size(target, &ResizeObserverBoxOptions::Border_box);
180 let content_box_size = calculate_box_size(target, &ResizeObserverBoxOptions::Content_box);
182
183 let device_pixel_content_box =
185 calculate_box_size(target, &ResizeObserverBoxOptions::Device_pixel_content_box);
186
187 let last_size = match observation.observed_box {
192 ResizeObserverBoxOptions::Content_box => content_box_size,
193 ResizeObserverBoxOptions::Border_box => border_box_size,
194 ResizeObserverBoxOptions::Device_pixel_content_box => device_pixel_content_box,
195 };
196 let last_reported_size = ResizeObserverSizeImpl::new(last_size.width(), last_size.height());
197 if observation.last_reported_sizes.is_empty() {
198 observation.last_reported_sizes.push(last_reported_size);
199 } else {
200 observation.last_reported_sizes[0] = last_reported_size;
201 }
202
203 let use_padding = *target.namespace() != ns!(svg) || target.has_css_layout_box();
205 let (padding_top, padding_left) = if use_padding {
206 let padding = target.upcast::<Node>().padding().unwrap_or_default();
209 (padding.top, padding.left)
210 } else {
211 (Au::zero(), Au::zero())
214 };
215
216 let content_rect = DOMRectReadOnly::new(
218 window.upcast(),
219 None,
220 padding_left.to_f64_px(),
221 padding_top.to_f64_px(),
222 content_box_size.width(),
223 content_box_size.height(),
224 can_gc,
225 );
226
227 let border_box_size = ResizeObserverSize::new(
228 window,
229 ResizeObserverSizeImpl::new(border_box_size.width(), border_box_size.height()),
230 can_gc,
231 );
232 let content_box_size = ResizeObserverSize::new(
233 window,
234 ResizeObserverSizeImpl::new(content_box_size.width(), content_box_size.height()),
235 can_gc,
236 );
237 let device_pixel_content_box = ResizeObserverSize::new(
238 window,
239 ResizeObserverSizeImpl::new(
240 device_pixel_content_box.width(),
241 device_pixel_content_box.height(),
242 ),
243 can_gc,
244 );
245
246 ResizeObserverEntry::new(
249 window,
250 target,
251 &content_rect,
252 &[&*border_box_size],
253 &[&*content_box_size],
254 &[&*device_pixel_content_box],
255 can_gc,
256 )
257}
258
259impl ResizeObserverMethods<crate::DomTypeHolder> for ResizeObserver {
260 fn Constructor(
262 window: &Window,
263 proto: Option<HandleObject>,
264 can_gc: CanGc,
265 callback: Rc<ResizeObserverCallback>,
266 ) -> DomRoot<ResizeObserver> {
267 let rooted_observer = ResizeObserver::new(window, proto, callback, can_gc);
268 let document = window.Document();
269 document.add_resize_observer(&rooted_observer);
270 rooted_observer
271 }
272
273 fn Observe(&self, target: &Element, options: &ResizeObserverOptions) {
275 let is_present = self
277 .observation_targets
278 .borrow()
279 .iter()
280 .any(|(_obs, other)| &**other == target);
281 if is_present {
282 self.Unobserve(target);
283 }
284
285 let resize_observation = ResizeObservation::new(options.box_);
288
289 self.observation_targets
291 .borrow_mut()
292 .push((resize_observation, Dom::from_ref(target)));
293 target
294 .owner_window()
295 .Document()
296 .add_rendering_update_reason(
297 RenderingUpdateReason::ResizeObserverStartedObservingTarget,
298 );
299 }
300
301 fn Unobserve(&self, target: &Element) {
303 self.observation_targets
304 .borrow_mut()
305 .retain_mut(|(_obs, other)| !(&**other == target));
306 }
307
308 fn Disconnect(&self) {
310 self.observation_targets.borrow_mut().clear();
311 }
312}
313
314#[derive(Default, MallocSizeOf, PartialEq)]
316enum ObservationState {
317 #[default]
318 Done,
319 Active,
321 Skipped,
323}
324
325#[derive(JSTraceable, MallocSizeOf)]
330struct ResizeObservation {
331 observed_box: ResizeObserverBoxOptions,
333 last_reported_sizes: Vec<ResizeObserverSizeImpl>,
335 #[no_trace]
337 state: ObservationState,
338}
339
340impl ResizeObservation {
341 pub(crate) fn new(observed_box: ResizeObserverBoxOptions) -> ResizeObservation {
343 ResizeObservation {
344 observed_box,
345 last_reported_sizes: vec![],
346 state: Default::default(),
347 }
348 }
349
350 fn is_active(&self, target: &Element) -> bool {
352 let Some(last_reported_size) = self.last_reported_sizes.first() else {
353 return true;
354 };
355 let box_size = calculate_box_size(target, &self.observed_box);
356 box_size.width() != last_reported_size.inline_size() ||
357 box_size.height() != last_reported_size.block_size()
358 }
359}
360
361fn calculate_depth_for_node(target: &Element) -> ResizeObservationDepth {
363 let node = target.upcast::<Node>();
364 let depth = node.inclusive_ancestors_in_flat_tree().count();
365 ResizeObservationDepth(depth)
366}
367
368fn calculate_box_size(target: &Element, observed_box: &ResizeObserverBoxOptions) -> Rect<f64> {
375 match observed_box {
376 ResizeObserverBoxOptions::Content_box => {
377 let content_box = target
380 .owner_window()
381 .box_area_query(target.upcast(), BoxAreaType::Content, true)
382 .unwrap_or_else(Rect::zero);
383
384 Rect::new(
385 content_box.origin.map(|coordinate| coordinate.to_f64_px()),
386 Size2D::new(
387 content_box.size.width.to_f64_px(),
388 content_box.size.height.to_f64_px(),
389 ),
390 )
391 },
392 ResizeObserverBoxOptions::Border_box => {
393 let border_box = target
396 .owner_window()
397 .box_area_query(target.upcast(), BoxAreaType::Border, true)
398 .unwrap_or_else(Rect::zero);
399
400 Rect::new(
401 border_box.origin.map(|coordinate| coordinate.to_f64_px()),
402 Size2D::new(
403 border_box.size.width.to_f64_px(),
404 border_box.size.height.to_f64_px(),
405 ),
406 )
407 },
408 ResizeObserverBoxOptions::Device_pixel_content_box => {
409 let device_pixel_ratio = target.owner_window().device_pixel_ratio();
410 let content_box = target
411 .owner_window()
412 .box_area_query(target.upcast(), BoxAreaType::Content, true)
413 .unwrap_or_else(Rect::zero);
414
415 Rect::new(
416 content_box
417 .origin
418 .map(|coordinate| coordinate.to_nearest_pixel(device_pixel_ratio.get()) as f64),
419 Size2D::new(
420 content_box
421 .size
422 .width
423 .to_nearest_pixel(device_pixel_ratio.get()) as f64,
424 content_box
425 .size
426 .height
427 .to_nearest_pixel(device_pixel_ratio.get()) as f64,
428 ),
429 )
430 },
431 }
432}