diff options
author | Gregory Terzian <2792687+gterzian@users.noreply.github.com> | 2024-06-18 00:44:07 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-06-17 16:44:07 +0000 |
commit | 3d78d60619cb1eda22f4473c91c45cc6a7907244 (patch) | |
tree | da2072a41d0956187c55f0d456d8046d8d794114 /components/script | |
parent | 3c1c395dfc60ca202834f9c708fffda71606bf1a (diff) | |
download | servo-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.rs | 1 | ||||
-rw-r--r-- | components/script/dom/document.rs | 72 | ||||
-rw-r--r-- | components/script/dom/domrectreadonly.rs | 2 | ||||
-rw-r--r-- | components/script/dom/mod.rs | 3 | ||||
-rw-r--r-- | components/script/dom/node.rs | 3 | ||||
-rw-r--r-- | components/script/dom/resizeobserver.rs | 280 | ||||
-rw-r--r-- | components/script/dom/resizeobserverentry.rs | 120 | ||||
-rw-r--r-- | components/script/dom/resizeobserversize.rs | 67 | ||||
-rw-r--r-- | components/script/dom/webidls/ResizeObserver.webidl | 23 | ||||
-rw-r--r-- | components/script/dom/webidls/ResizeObserverEntry.webidl | 14 | ||||
-rw-r--r-- | components/script/dom/webidls/ResizeObserverSize.webidl | 11 | ||||
-rw-r--r-- | components/script/script_thread.rs | 22 |
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(); } |