use std::rc::Rc;
use app_units::Au;
use dom_struct::dom_struct;
use euclid::num::Zero;
use euclid::{Rect, Size2D};
use html5ever::ns;
use js::context::JSContext;
use js::rust::HandleObject;
use layout_api::BoxAreaType;
use style_traits::CSSPixel;
use crate::dom::bindings::callback::ExceptionHandling;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::ResizeObserverBinding::{
ResizeObserverBoxOptions, ResizeObserverCallback, ResizeObserverMethods, ResizeObserverOptions,
};
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::{Dom, DomRoot};
use crate::dom::document::RenderingUpdateReason;
use crate::dom::domrectreadonly::DOMRectReadOnly;
use crate::dom::element::Element;
use crate::dom::node::{Node, NodeTraits};
use crate::dom::resizeobserverentry::ResizeObserverEntry;
use crate::dom::resizeobserversize::{ResizeObserverSize, ResizeObserverSizeImpl};
use crate::dom::window::Window;
use crate::script_runtime::CanGc;
#[derive(Debug, Default, PartialEq, PartialOrd)]
pub(crate) struct ResizeObservationDepth(usize);
impl ResizeObservationDepth {
pub(crate) fn max() -> ResizeObservationDepth {
ResizeObservationDepth(usize::MAX)
}
}
#[dom_struct]
pub(crate) struct ResizeObserver {
reflector_: Reflector,
#[conditional_malloc_size_of]
callback: Rc<ResizeObserverCallback>,
observation_targets: DomRefCell<Vec<(ResizeObservation, Dom<Element>)>>,
}
impl ResizeObserver {
pub(crate) fn new_inherited(callback: Rc<ResizeObserverCallback>) -> ResizeObserver {
ResizeObserver {
reflector_: Reflector::new(),
callback,
observation_targets: Default::default(),
}
}
fn new(
window: &Window,
proto: Option<HandleObject>,
callback: Rc<ResizeObserverCallback>,
can_gc: CanGc,
) -> DomRoot<ResizeObserver> {
let observer = Box::new(ResizeObserver::new_inherited(callback));
reflect_dom_object_with_proto(observer, window, proto, can_gc)
}
pub(crate) fn gather_active_resize_observations_at_depth(
&self,
depth: &ResizeObservationDepth,
has_active: &mut bool,
) {
for (observation, target) in self.observation_targets.borrow_mut().iter_mut() {
observation.state = Default::default();
if observation.is_active(target) {
let target_depth = calculate_depth_for_node(target);
if target_depth > *depth {
observation.state = ObservationState::Active;
*has_active = true;
}
else {
observation.state = ObservationState::Skipped;
}
}
}
}
pub(crate) fn broadcast_active_resize_observations(
&self,
cx: &mut JSContext,
shallowest_target_depth: &mut ResizeObservationDepth,
) {
let mut has_active_observation_targets = false;
let mut entries: Vec<DomRoot<ResizeObserverEntry>> = Default::default();
for (observation, target) in self.observation_targets.borrow_mut().iter_mut() {
let ObservationState::Active = observation.state else {
continue;
};
has_active_observation_targets = true;
let window = target.owner_window();
let entry = create_and_populate_a_resizeobserverentry(
&window,
target,
observation,
CanGc::from_cx(cx),
);
entries.push(entry);
observation.state = ObservationState::Done;
let target_depth = calculate_depth_for_node(target);
if target_depth < *shallowest_target_depth {
*shallowest_target_depth = target_depth;
}
}
if !has_active_observation_targets {
return;
}
let _ = self
.callback
.Call_(cx, self, entries, self, ExceptionHandling::Report);
}
pub(crate) fn has_skipped_resize_observations(&self) -> bool {
self.observation_targets
.borrow()
.iter()
.any(|(observation, _)| observation.state == ObservationState::Skipped)
}
}
fn create_and_populate_a_resizeobserverentry(
window: &Window,
target: &Element,
observation: &mut ResizeObservation,
can_gc: CanGc,
) -> DomRoot<ResizeObserverEntry> {
let border_box_size = calculate_box_size(target, &ResizeObserverBoxOptions::Border_box);
let content_box_size = calculate_box_size(target, &ResizeObserverBoxOptions::Content_box);
let device_pixel_content_box =
calculate_box_size(target, &ResizeObserverBoxOptions::Device_pixel_content_box);
let last_size = match observation.observed_box {
ResizeObserverBoxOptions::Content_box => content_box_size,
ResizeObserverBoxOptions::Border_box => border_box_size,
ResizeObserverBoxOptions::Device_pixel_content_box => device_pixel_content_box,
};
let last_reported_size = ResizeObserverSizeImpl::new(last_size.width(), last_size.height());
if observation.last_reported_sizes.is_empty() {
observation.last_reported_sizes.push(last_reported_size);
} else {
observation.last_reported_sizes[0] = last_reported_size;
}
let use_padding = *target.namespace() != ns!(svg) || target.has_css_layout_box();
let (padding_top, padding_left) = if use_padding {
let padding = target.upcast::<Node>().padding().unwrap_or_default();
(padding.top, padding.left)
} else {
(Au::zero(), Au::zero())
};
let content_rect = DOMRectReadOnly::new(
window.upcast(),
None,
padding_left.to_f64_px(),
padding_top.to_f64_px(),
content_box_size.width(),
content_box_size.height(),
can_gc,
);
let border_box_size = ResizeObserverSize::new(
window,
ResizeObserverSizeImpl::new(border_box_size.width(), border_box_size.height()),
can_gc,
);
let content_box_size = ResizeObserverSize::new(
window,
ResizeObserverSizeImpl::new(content_box_size.width(), content_box_size.height()),
can_gc,
);
let device_pixel_content_box = ResizeObserverSize::new(
window,
ResizeObserverSizeImpl::new(
device_pixel_content_box.width(),
device_pixel_content_box.height(),
),
can_gc,
);
ResizeObserverEntry::new(
window,
target,
&content_rect,
&[&*border_box_size],
&[&*content_box_size],
&[&*device_pixel_content_box],
can_gc,
)
}
impl ResizeObserverMethods<crate::DomTypeHolder> for ResizeObserver {
fn Constructor(
window: &Window,
proto: Option<HandleObject>,
can_gc: CanGc,
callback: Rc<ResizeObserverCallback>,
) -> DomRoot<ResizeObserver> {
let rooted_observer = ResizeObserver::new(window, proto, callback, can_gc);
let document = window.Document();
document.add_resize_observer(&rooted_observer);
rooted_observer
}
fn Observe(&self, target: &Element, options: &ResizeObserverOptions) {
let is_present = self
.observation_targets
.borrow()
.iter()
.any(|(_obs, other)| &**other == target);
if is_present {
self.Unobserve(target);
}
let resize_observation = ResizeObservation::new(options.box_);
self.observation_targets
.borrow_mut()
.push((resize_observation, Dom::from_ref(target)));
target
.owner_window()
.Document()
.add_rendering_update_reason(
RenderingUpdateReason::ResizeObserverStartedObservingTarget,
);
}
fn Unobserve(&self, target: &Element) {
self.observation_targets
.borrow_mut()
.retain_mut(|(_obs, other)| !(&**other == target));
}
fn Disconnect(&self) {
self.observation_targets.borrow_mut().clear();
}
}
#[derive(Default, MallocSizeOf, PartialEq)]
enum ObservationState {
#[default]
Done,
Active,
Skipped,
}
#[derive(JSTraceable, MallocSizeOf)]
struct ResizeObservation {
observed_box: ResizeObserverBoxOptions,
last_reported_sizes: Vec<ResizeObserverSizeImpl>,
#[no_trace]
state: ObservationState,
}
impl ResizeObservation {
pub(crate) fn new(observed_box: ResizeObserverBoxOptions) -> ResizeObservation {
ResizeObservation {
observed_box,
last_reported_sizes: vec![],
state: Default::default(),
}
}
fn is_active(&self, target: &Element) -> bool {
let Some(last_reported_size) = self.last_reported_sizes.first() else {
return true;
};
let box_size = calculate_box_size(target, &self.observed_box);
box_size.width() != last_reported_size.inline_size() ||
box_size.height() != last_reported_size.block_size()
}
}
fn calculate_depth_for_node(target: &Element) -> ResizeObservationDepth {
let node = target.upcast::<Node>();
let depth = node.inclusive_ancestors_in_flat_tree().count();
ResizeObservationDepth(depth)
}
fn calculate_box_size(
target: &Element,
observed_box: &ResizeObserverBoxOptions,
) -> Rect<f64, CSSPixel> {
match observed_box {
ResizeObserverBoxOptions::Content_box => {
let content_box = target
.owner_window()
.box_area_query(target.upcast(), BoxAreaType::Content, true)
.unwrap_or_else(Rect::zero);
Rect::new(
content_box.origin.map(|coordinate| coordinate.to_f64_px()),
Size2D::new(
content_box.size.width.to_f64_px(),
content_box.size.height.to_f64_px(),
),
)
},
ResizeObserverBoxOptions::Border_box => {
let border_box = target
.owner_window()
.box_area_query(target.upcast(), BoxAreaType::Border, true)
.unwrap_or_else(Rect::zero);
Rect::new(
border_box.origin.map(|coordinate| coordinate.to_f64_px()),
Size2D::new(
border_box.size.width.to_f64_px(),
border_box.size.height.to_f64_px(),
),
)
},
ResizeObserverBoxOptions::Device_pixel_content_box => {
let device_pixel_ratio = target.owner_window().device_pixel_ratio();
let content_box = target
.owner_window()
.box_area_query(target.upcast(), BoxAreaType::Content, true)
.unwrap_or_else(Rect::zero);
Rect::new(
content_box
.origin
.map(|coordinate| coordinate.to_nearest_pixel(device_pixel_ratio.get()) as f64),
Size2D::new(
content_box
.size
.width
.to_nearest_pixel(device_pixel_ratio.get()) as f64,
content_box
.size
.height
.to_nearest_pixel(device_pixel_ratio.get()) as f64,
),
)
},
}
}