script/dom/resizeobserver/
resizeobserver.rs1use std::rc::Rc;
6
7use app_units::Au;
8use dom_struct::dom_struct;
9use euclid::num::Zero;
10use euclid::{Rect, Size2D};
11use html5ever::ns;
12use js::context::JSContext;
13use js::rust::HandleObject;
14use layout_api::BoxAreaType;
15use script_bindings::cell::DomRefCell;
16use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
17use style_traits::CSSPixel;
18
19use crate::dom::bindings::callback::ExceptionHandling;
20use crate::dom::bindings::codegen::Bindings::ResizeObserverBinding::{
21 ResizeObserverBoxOptions, ResizeObserverCallback, ResizeObserverMethods, ResizeObserverOptions,
22};
23use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
24use crate::dom::bindings::inheritance::Castable;
25use crate::dom::bindings::root::{Dom, DomRoot};
26use crate::dom::document::RenderingUpdateReason;
27use crate::dom::domrectreadonly::DOMRectReadOnly;
28use crate::dom::element::Element;
29use crate::dom::node::{Node, NodeTraits};
30use crate::dom::resizeobserverentry::ResizeObserverEntry;
31use crate::dom::resizeobserversize::{ResizeObserverSize, ResizeObserverSizeImpl};
32use crate::dom::window::Window;
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 cx: &mut JSContext,
74 window: &Window,
75 proto: Option<HandleObject>,
76 callback: Rc<ResizeObserverCallback>,
77 ) -> DomRoot<ResizeObserver> {
78 let observer = Box::new(ResizeObserver::new_inherited(callback));
79 reflect_dom_object_with_proto_and_cx(observer, window, proto, cx)
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 cx: &mut JSContext,
119 shallowest_target_depth: &mut ResizeObservationDepth,
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 = create_and_populate_a_resizeobserverentry(cx, &window, target, observation);
139 entries.push(entry);
140 observation.state = ObservationState::Done;
141
142 let target_depth = calculate_depth_for_node(target);
143 if target_depth < *shallowest_target_depth {
144 *shallowest_target_depth = target_depth;
145 }
146 }
147
148 if !has_active_observation_targets {
149 return;
150 }
151
152 let _ = self
154 .callback
155 .Call_(cx, self, entries, self, ExceptionHandling::Report);
156
157 }
160
161 pub(crate) fn has_skipped_resize_observations(&self) -> bool {
163 self.observation_targets
164 .borrow()
165 .iter()
166 .any(|(observation, _)| observation.state == ObservationState::Skipped)
167 }
168}
169
170fn create_and_populate_a_resizeobserverentry(
172 cx: &mut JSContext,
173 window: &Window,
174 target: &Element,
175 observation: &mut ResizeObservation,
176) -> DomRoot<ResizeObserverEntry> {
177 let border_box_size = calculate_box_size(target, &ResizeObserverBoxOptions::Border_box);
179 let content_box_size = calculate_box_size(target, &ResizeObserverBoxOptions::Content_box);
181
182 let device_pixel_content_box =
184 calculate_box_size(target, &ResizeObserverBoxOptions::Device_pixel_content_box);
185
186 let last_size = match observation.observed_box {
191 ResizeObserverBoxOptions::Content_box => content_box_size,
192 ResizeObserverBoxOptions::Border_box => border_box_size,
193 ResizeObserverBoxOptions::Device_pixel_content_box => device_pixel_content_box,
194 };
195 let last_reported_size = ResizeObserverSizeImpl::new(last_size.width(), last_size.height());
196 if observation.last_reported_sizes.is_empty() {
197 observation.last_reported_sizes.push(last_reported_size);
198 } else {
199 observation.last_reported_sizes[0] = last_reported_size;
200 }
201
202 let use_padding = *target.namespace() != ns!(svg) || target.has_css_layout_box();
204 let (padding_top, padding_left) = if use_padding {
205 let padding = target.upcast::<Node>().padding().unwrap_or_default();
208 (padding.top, padding.left)
209 } else {
210 (Au::zero(), Au::zero())
213 };
214
215 let content_rect = DOMRectReadOnly::new(
217 cx,
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 );
225
226 let border_box_size = ResizeObserverSize::new(
227 cx,
228 window,
229 ResizeObserverSizeImpl::new(border_box_size.width(), border_box_size.height()),
230 );
231 let content_box_size = ResizeObserverSize::new(
232 cx,
233 window,
234 ResizeObserverSizeImpl::new(content_box_size.width(), content_box_size.height()),
235 );
236 let device_pixel_content_box = ResizeObserverSize::new(
237 cx,
238 window,
239 ResizeObserverSizeImpl::new(
240 device_pixel_content_box.width(),
241 device_pixel_content_box.height(),
242 ),
243 );
244
245 ResizeObserverEntry::new(
248 cx,
249 window,
250 target,
251 &content_rect,
252 &[&*border_box_size],
253 &[&*content_box_size],
254 &[&*device_pixel_content_box],
255 )
256}
257
258impl ResizeObserverMethods<crate::DomTypeHolder> for ResizeObserver {
259 fn Constructor(
261 cx: &mut JSContext,
262 window: &Window,
263 proto: Option<HandleObject>,
264 callback: Rc<ResizeObserverCallback>,
265 ) -> DomRoot<ResizeObserver> {
266 let rooted_observer = ResizeObserver::new(cx, window, proto, callback);
267 let document = window.Document();
268 document.add_resize_observer(&rooted_observer);
269 rooted_observer
270 }
271
272 fn Observe(&self, target: &Element, options: &ResizeObserverOptions) {
274 let is_present = self
276 .observation_targets
277 .borrow()
278 .iter()
279 .any(|(_obs, other)| &**other == target);
280 if is_present {
281 self.Unobserve(target);
282 }
283
284 let resize_observation = ResizeObservation::new(options.box_);
287
288 self.observation_targets
290 .borrow_mut()
291 .push((resize_observation, Dom::from_ref(target)));
292 target
293 .owner_window()
294 .Document()
295 .add_rendering_update_reason(
296 RenderingUpdateReason::ResizeObserverStartedObservingTarget,
297 );
298 }
299
300 fn Unobserve(&self, target: &Element) {
302 self.observation_targets
303 .borrow_mut()
304 .retain_mut(|(_obs, other)| !(&**other == target));
305 }
306
307 fn Disconnect(&self) {
309 self.observation_targets.borrow_mut().clear();
310 }
311}
312
313#[derive(Default, MallocSizeOf, PartialEq)]
315enum ObservationState {
316 #[default]
317 Done,
318 Active,
320 Skipped,
322}
323
324#[derive(JSTraceable, MallocSizeOf)]
329struct ResizeObservation {
330 observed_box: ResizeObserverBoxOptions,
332 last_reported_sizes: Vec<ResizeObserverSizeImpl>,
334 #[no_trace]
336 state: ObservationState,
337}
338
339impl ResizeObservation {
340 pub(crate) fn new(observed_box: ResizeObserverBoxOptions) -> ResizeObservation {
342 ResizeObservation {
343 observed_box,
344 last_reported_sizes: vec![],
345 state: Default::default(),
346 }
347 }
348
349 fn is_active(&self, target: &Element) -> bool {
351 let Some(last_reported_size) = self.last_reported_sizes.first() else {
352 return true;
353 };
354 let box_size = calculate_box_size(target, &self.observed_box);
355 box_size.width() != last_reported_size.inline_size() ||
356 box_size.height() != last_reported_size.block_size()
357 }
358}
359
360fn calculate_depth_for_node(target: &Element) -> ResizeObservationDepth {
362 let node = target.upcast::<Node>();
363 let depth = node.inclusive_ancestors_in_flat_tree().count();
364 ResizeObservationDepth(depth)
365}
366
367fn calculate_box_size(
374 target: &Element,
375 observed_box: &ResizeObserverBoxOptions,
376) -> Rect<f64, CSSPixel> {
377 match observed_box {
378 ResizeObserverBoxOptions::Content_box => {
379 let content_box = target
382 .owner_window()
383 .box_area_query(target.upcast(), BoxAreaType::Content, true)
384 .unwrap_or_else(Rect::zero);
385
386 Rect::new(
387 content_box.origin.map(|coordinate| coordinate.to_f64_px()),
388 Size2D::new(
389 content_box.size.width.to_f64_px(),
390 content_box.size.height.to_f64_px(),
391 ),
392 )
393 },
394 ResizeObserverBoxOptions::Border_box => {
395 let border_box = target
398 .owner_window()
399 .box_area_query(target.upcast(), BoxAreaType::Border, true)
400 .unwrap_or_else(Rect::zero);
401
402 Rect::new(
403 border_box.origin.map(|coordinate| coordinate.to_f64_px()),
404 Size2D::new(
405 border_box.size.width.to_f64_px(),
406 border_box.size.height.to_f64_px(),
407 ),
408 )
409 },
410 ResizeObserverBoxOptions::Device_pixel_content_box => {
411 let device_pixel_ratio = target.owner_window().device_pixel_ratio();
412 let content_box = target
413 .owner_window()
414 .box_area_query(target.upcast(), BoxAreaType::Content, true)
415 .unwrap_or_else(Rect::zero);
416
417 Rect::new(
418 content_box
419 .origin
420 .map(|coordinate| coordinate.to_nearest_pixel(device_pixel_ratio.get()) as f64),
421 Size2D::new(
422 content_box
423 .size
424 .width
425 .to_nearest_pixel(device_pixel_ratio.get()) as f64,
426 content_box
427 .size
428 .height
429 .to_nearest_pixel(device_pixel_ratio.get()) as f64,
430 ),
431 )
432 },
433 }
434}