aboutsummaryrefslogtreecommitdiffstats
path: root/components/script
diff options
context:
space:
mode:
authorGregory Terzian <2792687+gterzian@users.noreply.github.com>2024-06-18 00:44:07 +0800
committerGitHub <noreply@github.com>2024-06-17 16:44:07 +0000
commit3d78d60619cb1eda22f4473c91c45cc6a7907244 (patch)
treeda2072a41d0956187c55f0d456d8046d8d794114 /components/script
parent3c1c395dfc60ca202834f9c708fffda71606bf1a (diff)
downloadservo-3d78d60619cb1eda22f4473c91c45cc6a7907244.tar.gz
servo-3d78d60619cb1eda22f4473c91c45cc6a7907244.zip
implement basic infra for ResizeObserver (#31108)
Diffstat (limited to 'components/script')
-rw-r--r--components/script/dom/bindings/error.rs1
-rw-r--r--components/script/dom/document.rs72
-rw-r--r--components/script/dom/domrectreadonly.rs2
-rw-r--r--components/script/dom/mod.rs3
-rw-r--r--components/script/dom/node.rs3
-rw-r--r--components/script/dom/resizeobserver.rs280
-rw-r--r--components/script/dom/resizeobserverentry.rs120
-rw-r--r--components/script/dom/resizeobserversize.rs67
-rw-r--r--components/script/dom/webidls/ResizeObserver.webidl23
-rw-r--r--components/script/dom/webidls/ResizeObserverEntry.webidl14
-rw-r--r--components/script/dom/webidls/ResizeObserverSize.webidl11
-rw-r--r--components/script/script_thread.rs22
12 files changed, 614 insertions, 4 deletions
diff --git a/components/script/dom/bindings/error.rs b/components/script/dom/bindings/error.rs
index 39f2da7dc1c..9474a4004b4 100644
--- a/components/script/dom/bindings/error.rs
+++ b/components/script/dom/bindings/error.rs
@@ -166,6 +166,7 @@ pub fn throw_dom_exception(cx: SafeJSContext, global: &GlobalScope, result: Erro
}
/// A struct encapsulating information about a runtime script error.
+#[derive(Default)]
pub struct ErrorInfo {
/// The error message.
pub message: String,
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index d2c97bdc5de..5c0fb4e2c7c 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -26,7 +26,7 @@ use euclid::default::{Point2D, Rect, Size2D};
use html5ever::{local_name, namespace_url, ns, LocalName, Namespace, QualName};
use hyper_serde::Serde;
use ipc_channel::ipc::{self, IpcSender};
-use js::rust::HandleObject;
+use js::rust::{HandleObject, HandleValue};
use keyboard_types::{Code, Key, KeyState};
use lazy_static::lazy_static;
use metrics::{
@@ -93,7 +93,7 @@ use crate::dom::bindings::codegen::Bindings::WindowBinding::{
FrameRequestCallback, ScrollBehavior, WindowMethods,
};
use crate::dom::bindings::codegen::UnionTypes::{NodeOrString, StringOrElementCreationOptions};
-use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
+use crate::dom::bindings::error::{Error, ErrorInfo, ErrorResult, Fallible};
use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId};
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
@@ -155,6 +155,7 @@ use crate::dom::pagetransitionevent::PageTransitionEvent;
use crate::dom::processinginstruction::ProcessingInstruction;
use crate::dom::promise::Promise;
use crate::dom::range::Range;
+use crate::dom::resizeobserver::{ResizeObservationDepth, ResizeObserver};
use crate::dom::selection::Selection;
use crate::dom::servoparser::ServoParser;
use crate::dom::shadowroot::ShadowRoot;
@@ -456,6 +457,15 @@ pub struct Document {
#[no_trace]
#[ignore_malloc_size_of = "AnimationTickType contains data from an outside crate"]
pending_animation_ticks: DomRefCell<AnimationTickType>,
+ /// <https://drafts.csswg.org/resize-observer/#dom-document-resizeobservers-slot>
+ ///
+ /// Note: we are storing, but never removing, resize observers.
+ /// The lifetime of resize observers is specified at
+ /// <https://drafts.csswg.org/resize-observer/#lifetime>.
+ /// But implementing it comes with known problems:
+ /// - <https://bugzilla.mozilla.org/show_bug.cgi?id=1596992>
+ /// - <https://github.com/w3c/csswg-drafts/issues/4518>
+ resize_observers: DomRefCell<Vec<Dom<ResizeObserver>>>,
}
#[derive(JSTraceable, MallocSizeOf)]
@@ -2920,6 +2930,63 @@ impl Document {
pub fn name_map(&self) -> Ref<HashMapTracedValues<Atom, Vec<Dom<Element>>>> {
self.name_map.borrow()
}
+
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserver-resizeobserver>
+ pub(crate) fn add_resize_observer(&self, resize_observer: &ResizeObserver) {
+ self.resize_observers
+ .borrow_mut()
+ .push(Dom::from_ref(resize_observer));
+ }
+
+ /// <https://drafts.csswg.org/resize-observer/#gather-active-observations-h>
+ /// <https://drafts.csswg.org/resize-observer/#has-active-resize-observations>
+ pub(crate) fn gather_active_resize_observations_at_depth(
+ &self,
+ depth: &ResizeObservationDepth,
+ ) -> bool {
+ let mut has_active_resize_observations = false;
+ for observer in self.resize_observers.borrow_mut().iter_mut() {
+ observer.gather_active_resize_observations_at_depth(
+ depth,
+ &mut has_active_resize_observations,
+ );
+ }
+ has_active_resize_observations
+ }
+
+ /// <https://drafts.csswg.org/resize-observer/#broadcast-active-resize-observations>
+ pub(crate) fn broadcast_active_resize_observations(&self) -> ResizeObservationDepth {
+ let mut shallowest = ResizeObservationDepth::max();
+ // Breaking potential re-borrow cycle on `resize_observers`:
+ // broadcasting resize observations calls into a JS callback,
+ // which can add new observers.
+ for observer in self
+ .resize_observers
+ .borrow()
+ .iter()
+ .map(|obs| DomRoot::from_ref(&**obs))
+ {
+ observer.broadcast_active_resize_observations(&mut shallowest);
+ }
+ shallowest
+ }
+
+ /// <https://drafts.csswg.org/resize-observer/#has-skipped-observations-h>
+ pub(crate) fn has_skipped_resize_observations(&self) -> bool {
+ self.resize_observers
+ .borrow()
+ .iter()
+ .any(|observer| observer.has_skipped_resize_observations())
+ }
+
+ /// <https://drafts.csswg.org/resize-observer/#deliver-resize-loop-error-notification>
+ pub(crate) fn deliver_resize_loop_error_notification(&self) {
+ let global_scope = self.window.upcast::<GlobalScope>();
+ let mut error_info: ErrorInfo = Default::default();
+ error_info.message =
+ "ResizeObserver loop completed with undelivered notifications.".to_string();
+ global_scope.report_an_error(error_info, HandleValue::null());
+ }
}
fn is_character_value_key(key: &Key) -> bool {
@@ -3222,6 +3289,7 @@ impl Document {
pending_animation_ticks: Default::default(),
pending_compositor_events: Default::default(),
mouse_move_event_index: Default::default(),
+ resize_observers: Default::default(),
}
}
diff --git a/components/script/dom/domrectreadonly.rs b/components/script/dom/domrectreadonly.rs
index 8deb6fd5c5c..193d02a6707 100644
--- a/components/script/dom/domrectreadonly.rs
+++ b/components/script/dom/domrectreadonly.rs
@@ -33,7 +33,7 @@ impl DOMRectReadOnly {
}
}
- fn new(
+ pub fn new(
global: &GlobalScope,
proto: Option<HandleObject>,
x: f64,
diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs
index 7f8bea43ff3..34655e4c477 100644
--- a/components/script/dom/mod.rs
+++ b/components/script/dom/mod.rs
@@ -507,6 +507,9 @@ pub mod range;
pub mod raredata;
pub mod readablestream;
pub mod request;
+pub mod resizeobserver;
+pub mod resizeobserverentry;
+pub mod resizeobserversize;
pub mod response;
pub mod rtcdatachannel;
pub mod rtcdatachannelevent;
diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs
index bbe09601d57..8632d48b6d1 100644
--- a/components/script/dom/node.rs
+++ b/components/script/dom/node.rs
@@ -2110,6 +2110,8 @@ impl Node {
MutationObserver::queue_a_mutation_record(parent, mutation);
}
node.owner_doc().remove_script_and_layout_blocker();
+
+ ScriptThread::note_rendering_opportunity(window_from_node(parent).pipeline_id());
}
/// <https://dom.spec.whatwg.org/#concept-node-replace-all>
@@ -2233,6 +2235,7 @@ impl Node {
MutationObserver::queue_a_mutation_record(parent, mutation);
}
parent.owner_doc().remove_script_and_layout_blocker();
+ ScriptThread::note_rendering_opportunity(window_from_node(parent).pipeline_id());
}
/// <https://dom.spec.whatwg.org/#concept-node-clone>
diff --git a/components/script/dom/resizeobserver.rs b/components/script/dom/resizeobserver.rs
new file mode 100644
index 00000000000..b2e0b42ad88
--- /dev/null
+++ b/components/script/dom/resizeobserver.rs
@@ -0,0 +1,280 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use std::rc::Rc;
+
+use app_units::Au;
+use dom_struct::dom_struct;
+use euclid::default::Rect;
+use js::rust::HandleObject;
+
+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::{reflect_dom_object_with_proto, Reflector};
+use crate::dom::bindings::root::{Dom, DomRoot};
+use crate::dom::domrectreadonly::DOMRectReadOnly;
+use crate::dom::element::Element;
+use crate::dom::node::{window_from_node, Node};
+use crate::dom::resizeobserverentry::ResizeObserverEntry;
+use crate::dom::resizeobserversize::{ResizeObserverSize, ResizeObserverSizeImpl};
+use crate::dom::window::Window;
+use crate::script_thread::ScriptThread;
+
+/// <https://drafts.csswg.org/resize-observer/#calculate-depth-for-node>
+#[derive(Debug, Default, PartialEq, PartialOrd)]
+pub struct ResizeObservationDepth(usize);
+
+impl ResizeObservationDepth {
+ pub fn max() -> ResizeObservationDepth {
+ ResizeObservationDepth(usize::MAX)
+ }
+}
+
+/// <https://drafts.csswg.org/resize-observer/#resize-observer-slots>
+/// See `ObservationState` for active and skipped observation targets.
+#[dom_struct]
+pub struct ResizeObserver {
+ reflector_: Reflector,
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserver-callback-slot>
+ #[ignore_malloc_size_of = "Rc are hard"]
+ callback: Rc<ResizeObserverCallback>,
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserver-observationtargets-slot>
+ observation_targets: DomRefCell<Vec<(ResizeObservation, Dom<Element>)>>,
+}
+
+impl ResizeObserver {
+ pub 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>,
+ ) -> DomRoot<ResizeObserver> {
+ let observer = Box::new(ResizeObserver::new_inherited(callback));
+ reflect_dom_object_with_proto(observer, window, proto)
+ }
+
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserver-resizeobserver>
+ #[allow(non_snake_case)]
+ pub fn Constructor(
+ window: &Window,
+ proto: Option<HandleObject>,
+ callback: Rc<ResizeObserverCallback>,
+ ) -> DomRoot<ResizeObserver> {
+ let rooted_observer = ResizeObserver::new(window, proto, callback);
+ let document = window.Document();
+ document.add_resize_observer(&rooted_observer);
+ rooted_observer
+ }
+
+ /// <https://drafts.csswg.org/resize-observer/#gather-active-observations-h>
+ /// <https://drafts.csswg.org/resize-observer/#has-active-resize-observations>
+ pub 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 let Some(size) = observation.is_active(target) {
+ let target_depth = calculate_depth_for_node(target);
+ if target_depth > *depth {
+ observation.state = ObservationState::Active(size);
+ *has_active = true;
+ } else {
+ observation.state = ObservationState::Skipped;
+ }
+ }
+ }
+ }
+
+ /// <https://drafts.csswg.org/resize-observer/#broadcast-active-resize-observations>
+ pub fn broadcast_active_resize_observations(
+ &self,
+ shallowest_target_depth: &mut ResizeObservationDepth,
+ ) {
+ let mut entries: Vec<DomRoot<ResizeObserverEntry>> = Default::default();
+ for (observation, target) in self.observation_targets.borrow_mut().iter_mut() {
+ let ObservationState::Active(box_size) = observation.state else {
+ continue;
+ };
+
+ // #create-and-populate-a-resizeobserverentry
+
+ // Note: only calculating content box size.
+ let width = box_size.width().to_f64_px();
+ let height = box_size.height().to_f64_px();
+ let size_impl = ResizeObserverSizeImpl::new(width, height);
+ let window = window_from_node(&**target);
+ let observer_size = ResizeObserverSize::new(&*window, size_impl);
+
+ // Note: content rect is built from content box size.
+ let content_rect = DOMRectReadOnly::new(
+ &*window.upcast(),
+ None,
+ box_size.origin.x.to_f64_px(),
+ box_size.origin.y.to_f64_px(),
+ width,
+ height,
+ );
+ let entry = ResizeObserverEntry::new(
+ &*window,
+ target,
+ &*content_rect,
+ &[],
+ &[&*observer_size],
+ &[],
+ );
+ entries.push(entry);
+
+ // Note: this is safe because an observation is
+ // initialized with one reported size (zero).
+ // The spec plans to store multiple reported sizes,
+ // but for now there can be only one.
+ observation.last_reported_sizes[0] = size_impl;
+ observation.state = ObservationState::Done;
+ let target_depth = calculate_depth_for_node(target);
+ if target_depth < *shallowest_target_depth {
+ *shallowest_target_depth = target_depth;
+ }
+ }
+ let _ = self
+ .callback
+ .Call_(self, entries, self, ExceptionHandling::Report);
+ }
+
+ /// <https://drafts.csswg.org/resize-observer/#has-skipped-observations-h>
+ pub fn has_skipped_resize_observations(&self) -> bool {
+ self.observation_targets
+ .borrow()
+ .iter()
+ .any(|(observation, _)| observation.state == ObservationState::Skipped)
+ }
+}
+
+impl ResizeObserverMethods for ResizeObserver {
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserver-observe>
+ 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)));
+
+ // Note: noting a rendering opportunity here is necessary
+ // to make /resize-observer/iframe-same-origin.html PASS.
+ ScriptThread::note_rendering_opportunity(window_from_node(target).pipeline_id());
+ }
+
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserver-unobserve>
+ fn Unobserve(&self, target: &Element) {
+ self.observation_targets
+ .borrow_mut()
+ .retain_mut(|(_obs, other)| !(&**other == target));
+ }
+
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserver-disconnect>
+ fn Disconnect(&self) {
+ self.observation_targets.borrow_mut().clear();
+ }
+}
+
+/// State machine equivalent of active and skipped observations.
+#[derive(Default, MallocSizeOf, PartialEq)]
+enum ObservationState {
+ #[default]
+ Done,
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserver-activetargets-slot>
+ /// With the result of the box size calculated when setting the state to active,
+ /// in order to avoid recalculating it in the subsequent broadcast.
+ Active(Rect<Au>),
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserver-skippedtargets-slot>
+ Skipped,
+}
+
+/// https://drafts.csswg.org/resize-observer/#resizeobservation
+#[derive(JSTraceable, MallocSizeOf)]
+struct ResizeObservation {
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobservation-target>
+ /// Note: `target` is kept out of here, to avoid having to root the `ResizeObservation`.
+
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobservation-observedbox>
+ observed_box: ResizeObserverBoxOptions,
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobservation-lastreportedsizes>
+ last_reported_sizes: Vec<ResizeObserverSizeImpl>,
+ /// State machine mimicking the "active" and "skipped" targets slots of the observer.
+ #[no_trace]
+ state: ObservationState,
+}
+
+impl ResizeObservation {
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobservation-resizeobservation>
+ pub fn new(observed_box: ResizeObserverBoxOptions) -> ResizeObservation {
+ let size_impl = ResizeObserverSizeImpl::new(0.0, 0.0);
+ ResizeObservation {
+ observed_box,
+ last_reported_sizes: vec![size_impl],
+ state: Default::default(),
+ }
+ }
+
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobservation-isactive>
+ /// Returning an optional calculated size, instead of a boolean,
+ /// to avoid recalculating the size in the subsequent broadcast.
+ fn is_active(&self, target: &Element) -> Option<Rect<Au>> {
+ let last_reported_size = self.last_reported_sizes[0];
+ let box_size = calculate_box_size(target, &self.observed_box);
+ let is_active = box_size.width().to_f64_px() != last_reported_size.inline_size() ||
+ box_size.height().to_f64_px() != last_reported_size.block_size();
+ if is_active {
+ Some(box_size)
+ } else {
+ None
+ }
+ }
+}
+
+/// <https://drafts.csswg.org/resize-observer/#calculate-depth-for-node>
+fn calculate_depth_for_node(target: &Element) -> ResizeObservationDepth {
+ let node = target.upcast::<Node>();
+ let depth = node.ancestors().count();
+ ResizeObservationDepth(depth)
+}
+
+/// <https://drafts.csswg.org/resize-observer/#calculate-box-size>
+fn calculate_box_size(target: &Element, observed_box: &ResizeObserverBoxOptions) -> Rect<Au> {
+ match observed_box {
+ ResizeObserverBoxOptions::Content_box => {
+ // Note: only taking first fragment,
+ // but the spec will expand to cover all fragments.
+ target
+ .upcast::<Node>()
+ .content_boxes()
+ .pop()
+ .unwrap_or_else(|| Rect::zero())
+ },
+ // TODO(#31182): add support for border box, and device pixel size, calculations.
+ _ => Rect::zero(),
+ }
+}
diff --git a/components/script/dom/resizeobserverentry.rs b/components/script/dom/resizeobserverentry.rs
new file mode 100644
index 00000000000..32670c01ef3
--- /dev/null
+++ b/components/script/dom/resizeobserverentry.rs
@@ -0,0 +1,120 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use dom_struct::dom_struct;
+use js::jsval::JSVal;
+
+use crate::dom::bindings::codegen::Bindings::ResizeObserverEntryBinding::ResizeObserverEntryMethods;
+use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
+use crate::dom::bindings::root::{Dom, DomRoot};
+use crate::dom::bindings::utils::to_frozen_array;
+use crate::dom::domrectreadonly::DOMRectReadOnly;
+use crate::dom::element::Element;
+use crate::dom::resizeobserversize::ResizeObserverSize;
+use crate::dom::window::Window;
+use crate::script_runtime::JSContext as SafeJSContext;
+
+/// <https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface>
+#[dom_struct]
+pub struct ResizeObserverEntry {
+ reflector_: Reflector,
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-target>
+ target: Dom<Element>,
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-contentrect>
+ content_rect: Dom<DOMRectReadOnly>,
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-borderboxsize>
+ border_box_size: Vec<Dom<ResizeObserverSize>>,
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-contentboxsize>
+ content_box_size: Vec<Dom<ResizeObserverSize>>,
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-devicepixelcontentboxsize>
+ device_pixel_content_box_size: Vec<Dom<ResizeObserverSize>>,
+}
+
+impl ResizeObserverEntry {
+ fn new_inherited(
+ target: &Element,
+ content_rect: &DOMRectReadOnly,
+ border_box_size: &[&ResizeObserverSize],
+ content_box_size: &[&ResizeObserverSize],
+ device_pixel_content_box_size: &[&ResizeObserverSize],
+ ) -> ResizeObserverEntry {
+ ResizeObserverEntry {
+ reflector_: Reflector::new(),
+ target: Dom::from_ref(target),
+ content_rect: Dom::from_ref(content_rect),
+ border_box_size: border_box_size
+ .iter()
+ .map(|size| Dom::from_ref(*size))
+ .collect(),
+ content_box_size: content_box_size
+ .iter()
+ .map(|size| Dom::from_ref(*size))
+ .collect(),
+ device_pixel_content_box_size: device_pixel_content_box_size
+ .iter()
+ .map(|size| Dom::from_ref(*size))
+ .collect(),
+ }
+ }
+
+ pub fn new(
+ window: &Window,
+ target: &Element,
+ content_rect: &DOMRectReadOnly,
+ border_box_size: &[&ResizeObserverSize],
+ content_box_size: &[&ResizeObserverSize],
+ device_pixel_content_box_size: &[&ResizeObserverSize],
+ ) -> DomRoot<ResizeObserverEntry> {
+ let entry = Box::new(ResizeObserverEntry::new_inherited(
+ target,
+ content_rect,
+ border_box_size,
+ content_box_size,
+ device_pixel_content_box_size,
+ ));
+ reflect_dom_object_with_proto(entry, window, None)
+ }
+}
+
+impl ResizeObserverEntryMethods for ResizeObserverEntry {
+ /// https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-target
+ fn Target(&self) -> DomRoot<Element> {
+ DomRoot::from_ref(&*self.target)
+ }
+
+ /// https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-contentrect
+ fn ContentRect(&self) -> DomRoot<DOMRectReadOnly> {
+ DomRoot::from_ref(&*self.content_rect)
+ }
+
+ /// https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-borderboxsize
+ fn BorderBoxSize(&self, cx: SafeJSContext) -> JSVal {
+ let sizes: Vec<DomRoot<ResizeObserverSize>> = self
+ .border_box_size
+ .iter()
+ .map(|size| DomRoot::from_ref(&**size))
+ .collect();
+ to_frozen_array(sizes.as_slice(), cx)
+ }
+
+ /// https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-contentboxsize
+ fn ContentBoxSize(&self, cx: SafeJSContext) -> JSVal {
+ let sizes: Vec<DomRoot<ResizeObserverSize>> = self
+ .content_box_size
+ .iter()
+ .map(|size| DomRoot::from_ref(&**size))
+ .collect();
+ to_frozen_array(sizes.as_slice(), cx)
+ }
+
+ /// https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-devicepixelcontentboxsize
+ fn DevicePixelContentBoxSize(&self, cx: SafeJSContext) -> JSVal {
+ let sizes: Vec<DomRoot<ResizeObserverSize>> = self
+ .device_pixel_content_box_size
+ .iter()
+ .map(|size| DomRoot::from_ref(&**size))
+ .collect();
+ to_frozen_array(sizes.as_slice(), cx)
+ }
+}
diff --git a/components/script/dom/resizeobserversize.rs b/components/script/dom/resizeobserversize.rs
new file mode 100644
index 00000000000..4f63a9c711c
--- /dev/null
+++ b/components/script/dom/resizeobserversize.rs
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+use dom_struct::dom_struct;
+
+use crate::dom::bindings::codegen::Bindings::ResizeObserverSizeBinding::ResizeObserverSizeMethods;
+use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector};
+use crate::dom::bindings::root::DomRoot;
+use crate::dom::window::Window;
+
+/// Non-DOM implementation backing `ResizeObserverSize`.
+#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
+pub struct ResizeObserverSizeImpl {
+ inline_size: f64,
+ block_size: f64,
+}
+
+impl ResizeObserverSizeImpl {
+ pub fn new(inline_size: f64, block_size: f64) -> ResizeObserverSizeImpl {
+ ResizeObserverSizeImpl {
+ inline_size,
+ block_size,
+ }
+ }
+
+ pub fn inline_size(&self) -> f64 {
+ self.inline_size
+ }
+
+ pub fn block_size(&self) -> f64 {
+ self.block_size
+ }
+}
+
+/// <https://drafts.csswg.org/resize-observer/#resizeobserversize>
+#[dom_struct]
+pub struct ResizeObserverSize {
+ reflector_: Reflector,
+ size_impl: ResizeObserverSizeImpl,
+}
+
+impl ResizeObserverSize {
+ fn new_inherited(size_impl: ResizeObserverSizeImpl) -> ResizeObserverSize {
+ ResizeObserverSize {
+ reflector_: Reflector::new(),
+ size_impl,
+ }
+ }
+
+ pub fn new(window: &Window, size_impl: ResizeObserverSizeImpl) -> DomRoot<ResizeObserverSize> {
+ let observer_size = Box::new(ResizeObserverSize::new_inherited(size_impl));
+ reflect_dom_object_with_proto(observer_size, window, None)
+ }
+}
+
+impl ResizeObserverSizeMethods for ResizeObserverSize {
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserversize-inlinesize>
+ fn InlineSize(&self) -> f64 {
+ self.size_impl.inline_size()
+ }
+
+ /// <https://drafts.csswg.org/resize-observer/#dom-resizeobserversize-blocksize>
+ fn BlockSize(&self) -> f64 {
+ self.size_impl.block_size()
+ }
+}
diff --git a/components/script/dom/webidls/ResizeObserver.webidl b/components/script/dom/webidls/ResizeObserver.webidl
new file mode 100644
index 00000000000..fc0a87589d5
--- /dev/null
+++ b/components/script/dom/webidls/ResizeObserver.webidl
@@ -0,0 +1,23 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// https://drafts.csswg.org/resize-observer/#resize-observer-interface
+
+[Pref="dom.resize_observer.enabled", Exposed=(Window)]
+interface ResizeObserver {
+ constructor(ResizeObserverCallback callback);
+ undefined observe(Element target, optional ResizeObserverOptions options = {});
+ undefined unobserve(Element target);
+ undefined disconnect();
+};
+
+enum ResizeObserverBoxOptions {
+ "border-box", "content-box", "device-pixel-content-box"
+};
+
+dictionary ResizeObserverOptions {
+ ResizeObserverBoxOptions box = "content-box";
+};
+
+callback ResizeObserverCallback = undefined (sequence<ResizeObserverEntry> entries, ResizeObserver observer);
diff --git a/components/script/dom/webidls/ResizeObserverEntry.webidl b/components/script/dom/webidls/ResizeObserverEntry.webidl
new file mode 100644
index 00000000000..8cb8802023a
--- /dev/null
+++ b/components/script/dom/webidls/ResizeObserverEntry.webidl
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+ // https://drafts.csswg.org/resize-observer/#resize-observer-entry-interface
+
+[Pref="dom.resize_observer.enabled", Exposed=Window]
+interface ResizeObserverEntry {
+ readonly attribute Element target;
+ readonly attribute DOMRectReadOnly contentRect;
+ readonly attribute /*FrozenArray<ResizeObserverSize>*/any borderBoxSize;
+ readonly attribute /*FrozenArray<ResizeObserverSize>*/any contentBoxSize;
+ readonly attribute /*FrozenArray<ResizeObserverSize>*/any devicePixelContentBoxSize;
+};
diff --git a/components/script/dom/webidls/ResizeObserverSize.webidl b/components/script/dom/webidls/ResizeObserverSize.webidl
new file mode 100644
index 00000000000..5cc8db2dbcd
--- /dev/null
+++ b/components/script/dom/webidls/ResizeObserverSize.webidl
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+// https://drafts.csswg.org/resize-observer/#resizeobserversize
+
+[Pref="dom.resize_observer.enabled", Exposed=Window]
+interface ResizeObserverSize {
+ readonly attribute unrestricted double inlineSize;
+ readonly attribute unrestricted double blockSize;
+};
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index 81440705e52..8a8a80b9c2a 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -859,6 +859,13 @@ impl ScriptThreadFactory for ScriptThread {
}
impl ScriptThread {
+ pub fn note_rendering_opportunity(pipeline_id: PipelineId) {
+ SCRIPT_THREAD_ROOT.with(|root| {
+ let script_thread = unsafe { &*root.get().unwrap() };
+ script_thread.rendering_opportunity(pipeline_id);
+ })
+ }
+
pub fn runtime_handle() -> ParentRuntime {
SCRIPT_THREAD_ROOT.with(|root| {
let script_thread = unsafe { &*root.get().unwrap() };
@@ -1692,7 +1699,17 @@ impl ScriptThread {
// Run the animation frame callbacks.
document.tick_all_animations();
- // TODO(#31006): Implement the resize observer steps.
+ // Run the resize observer steps.
+ let _realm = enter_realm(&*document);
+ let mut depth = Default::default();
+ while document.gather_active_resize_observations_at_depth(&depth) {
+ // Note: this will reflow the doc.
+ depth = document.broadcast_active_resize_observations();
+ }
+
+ if document.has_skipped_resize_observations() {
+ document.deliver_resize_loop_error_notification();
+ }
// TODO(#31870): Implement step 17: if the focused area of doc is not a focusable area,
// then run the focusing steps for document's viewport.
@@ -1963,6 +1980,9 @@ impl ScriptThread {
for document in docs.iter() {
let _realm = enter_realm(&**document);
document.maybe_queue_document_completion();
+
+ // Document load is a rendering opportunity.
+ ScriptThread::note_rendering_opportunity(document.window().pipeline_id());
}
docs.clear();
}