diff --git a/components/config/prefs.rs b/components/config/prefs.rs index 7bae20b721f..b48b6954914 100644 --- a/components/config/prefs.rs +++ b/components/config/prefs.rs @@ -268,6 +268,9 @@ mod gen { allowed_in_nonsecure_contexts: bool, } }, + resize_observer: { + enabled: bool, + }, script: { asynch: bool, }, 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, + /// + /// + /// Note: we are storing, but never removing, resize observers. + /// The lifetime of resize observers is specified at + /// . + /// But implementing it comes with known problems: + /// - + /// - + resize_observers: DomRefCell>>, } #[derive(JSTraceable, MallocSizeOf)] @@ -2920,6 +2930,63 @@ impl Document { pub fn name_map(&self) -> Ref>>> { self.name_map.borrow() } + + /// + pub(crate) fn add_resize_observer(&self, resize_observer: &ResizeObserver) { + self.resize_observers + .borrow_mut() + .push(Dom::from_ref(resize_observer)); + } + + /// + /// + 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 + } + + /// + 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 + } + + /// + pub(crate) fn has_skipped_resize_observations(&self) -> bool { + self.resize_observers + .borrow() + .iter() + .any(|observer| observer.has_skipped_resize_observations()) + } + + /// + pub(crate) fn deliver_resize_loop_error_notification(&self) { + let global_scope = self.window.upcast::(); + 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, 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()); } /// @@ -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()); } /// 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; + +/// +#[derive(Debug, Default, PartialEq, PartialOrd)] +pub struct ResizeObservationDepth(usize); + +impl ResizeObservationDepth { + pub fn max() -> ResizeObservationDepth { + ResizeObservationDepth(usize::MAX) + } +} + +/// +/// See `ObservationState` for active and skipped observation targets. +#[dom_struct] +pub struct ResizeObserver { + reflector_: Reflector, + /// + #[ignore_malloc_size_of = "Rc are hard"] + callback: Rc, + /// + observation_targets: DomRefCell)>>, +} + +impl ResizeObserver { + pub fn new_inherited(callback: Rc) -> ResizeObserver { + ResizeObserver { + reflector_: Reflector::new(), + callback, + observation_targets: Default::default(), + } + } + + fn new( + window: &Window, + proto: Option, + callback: Rc, + ) -> DomRoot { + let observer = Box::new(ResizeObserver::new_inherited(callback)); + reflect_dom_object_with_proto(observer, window, proto) + } + + /// + #[allow(non_snake_case)] + pub fn Constructor( + window: &Window, + proto: Option, + callback: Rc, + ) -> DomRoot { + let rooted_observer = ResizeObserver::new(window, proto, callback); + let document = window.Document(); + document.add_resize_observer(&rooted_observer); + rooted_observer + } + + /// + /// + 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; + } + } + } + } + + /// + pub fn broadcast_active_resize_observations( + &self, + shallowest_target_depth: &mut ResizeObservationDepth, + ) { + let mut entries: Vec> = 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); + } + + /// + pub fn has_skipped_resize_observations(&self) -> bool { + self.observation_targets + .borrow() + .iter() + .any(|(observation, _)| observation.state == ObservationState::Skipped) + } +} + +impl ResizeObserverMethods for ResizeObserver { + /// + 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()); + } + + /// + 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(); + } +} + +/// State machine equivalent of active and skipped observations. +#[derive(Default, MallocSizeOf, PartialEq)] +enum ObservationState { + #[default] + Done, + /// + /// 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), + /// + Skipped, +} + +/// https://drafts.csswg.org/resize-observer/#resizeobservation +#[derive(JSTraceable, MallocSizeOf)] +struct ResizeObservation { + /// + /// Note: `target` is kept out of here, to avoid having to root the `ResizeObservation`. + + /// + observed_box: ResizeObserverBoxOptions, + /// + last_reported_sizes: Vec, + /// State machine mimicking the "active" and "skipped" targets slots of the observer. + #[no_trace] + state: ObservationState, +} + +impl 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(), + } + } + + /// + /// 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> { + 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 + } + } +} + +/// +fn calculate_depth_for_node(target: &Element) -> ResizeObservationDepth { + let node = target.upcast::(); + let depth = node.ancestors().count(); + ResizeObservationDepth(depth) +} + +/// +fn calculate_box_size(target: &Element, observed_box: &ResizeObserverBoxOptions) -> Rect { + match observed_box { + ResizeObserverBoxOptions::Content_box => { + // Note: only taking first fragment, + // but the spec will expand to cover all fragments. + target + .upcast::() + .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; + +/// +#[dom_struct] +pub struct ResizeObserverEntry { + reflector_: Reflector, + /// + target: Dom, + /// + content_rect: Dom, + /// + border_box_size: Vec>, + /// + content_box_size: Vec>, + /// + device_pixel_content_box_size: Vec>, +} + +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 { + 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 { + DomRoot::from_ref(&*self.target) + } + + /// https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-contentrect + fn ContentRect(&self) -> DomRoot { + DomRoot::from_ref(&*self.content_rect) + } + + /// https://drafts.csswg.org/resize-observer/#dom-resizeobserverentry-borderboxsize + fn BorderBoxSize(&self, cx: SafeJSContext) -> JSVal { + let sizes: Vec> = 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> = 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> = 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 + } +} + +/// +#[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 { + let observer_size = Box::new(ResizeObserverSize::new_inherited(size_impl)); + reflect_dom_object_with_proto(observer_size, window, None) + } +} + +impl ResizeObserverSizeMethods for ResizeObserverSize { + /// + fn InlineSize(&self) -> f64 { + self.size_impl.inline_size() + } + + /// + 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 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*/any borderBoxSize; + readonly attribute /*FrozenArray*/any contentBoxSize; + readonly attribute /*FrozenArray*/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(); } diff --git a/resources/prefs.json b/resources/prefs.json index c26803567a9..8ebc7d450f5 100644 --- a/resources/prefs.json +++ b/resources/prefs.json @@ -20,6 +20,7 @@ "dom.offscreen_canvas.enabled": false, "dom.permissions.enabled": false, "dom.permissions.testing.allowed_in_nonsecure_contexts": false, + "dom.resize_observer.enabled": false, "dom.script.asynch": true, "dom.serviceworker.enabled": false, "dom.serviceworker.timeout_seconds": 60, diff --git a/tests/wpt/include.ini b/tests/wpt/include.ini index a44d8917b4d..a35ceddd26e 100644 --- a/tests/wpt/include.ini +++ b/tests/wpt/include.ini @@ -206,6 +206,8 @@ skip: true skip: true [srcdoc.meta] skip: true +[resize-observer] + skip: false [resource-timing] skip: false [selection] diff --git a/tests/wpt/meta/html/webappapis/update-rendering/child-document-raf-order.html.ini b/tests/wpt/meta/html/webappapis/update-rendering/child-document-raf-order.html.ini index 312c6689170..d2269e3918b 100644 --- a/tests/wpt/meta/html/webappapis/update-rendering/child-document-raf-order.html.ini +++ b/tests/wpt/meta/html/webappapis/update-rendering/child-document-raf-order.html.ini @@ -1,3 +1,4 @@ [child-document-raf-order.html] [Ordering of steps in "Update the Rendering" - child document requestAnimationFrame order] expected: FAIL + diff --git a/tests/wpt/meta/resize-observer/__dir__.ini b/tests/wpt/meta/resize-observer/__dir__.ini new file mode 100644 index 00000000000..4d4f1e646b2 --- /dev/null +++ b/tests/wpt/meta/resize-observer/__dir__.ini @@ -0,0 +1 @@ +prefs: ["dom.resize_observer.enabled:true"] diff --git a/tests/wpt/meta/resize-observer/calculate-depth-for-node.html.ini b/tests/wpt/meta/resize-observer/calculate-depth-for-node.html.ini new file mode 100644 index 00000000000..5a4a021662b --- /dev/null +++ b/tests/wpt/meta/resize-observer/calculate-depth-for-node.html.ini @@ -0,0 +1,3 @@ +[calculate-depth-for-node.html] + ["Calculate depth for node" algorithm with Shadow DOM] + expected: FAIL diff --git a/tests/wpt/meta/resize-observer/devicepixel.html.ini b/tests/wpt/meta/resize-observer/devicepixel.html.ini new file mode 100644 index 00000000000..c1cb9e98ff1 --- /dev/null +++ b/tests/wpt/meta/resize-observer/devicepixel.html.ini @@ -0,0 +1,2 @@ +[devicepixel.html] + expected: FAIL diff --git a/tests/wpt/meta/resize-observer/eventloop.html.ini b/tests/wpt/meta/resize-observer/eventloop.html.ini new file mode 100644 index 00000000000..d86a2113ddb --- /dev/null +++ b/tests/wpt/meta/resize-observer/eventloop.html.ini @@ -0,0 +1,4 @@ +[eventloop.html] + expected: TIMEOUT + [guard] + expected: NOTRUN diff --git a/tests/wpt/meta/resize-observer/fragments.html.ini b/tests/wpt/meta/resize-observer/fragments.html.ini new file mode 100644 index 00000000000..66db9d3d97b --- /dev/null +++ b/tests/wpt/meta/resize-observer/fragments.html.ini @@ -0,0 +1,9 @@ +[fragments.html] + [Adding 2nd fragment] + expected: FAIL + + [Resizing 2nd fragment] + expected: FAIL + + [Resizing all fragments] + expected: FAIL diff --git a/tests/wpt/meta/resize-observer/notify.html.ini b/tests/wpt/meta/resize-observer/notify.html.ini new file mode 100644 index 00000000000..2e7220f9d4d --- /dev/null +++ b/tests/wpt/meta/resize-observer/notify.html.ini @@ -0,0 +1,7 @@ +[notify.html] + expected: ERROR + [guard] + expected: NOTRUN + + [test2: remove/appendChild trigger notification] + expected: FAIL diff --git a/tests/wpt/meta/resize-observer/observe.html.ini b/tests/wpt/meta/resize-observer/observe.html.ini new file mode 100644 index 00000000000..dd91e2b3606 --- /dev/null +++ b/tests/wpt/meta/resize-observer/observe.html.ini @@ -0,0 +1,4 @@ +[observe.html] + expected: TIMEOUT + [guard] + expected: NOTRUN diff --git a/tests/wpt/meta/resize-observer/ordering.html.ini b/tests/wpt/meta/resize-observer/ordering.html.ini new file mode 100644 index 00000000000..4dbdb1425e4 --- /dev/null +++ b/tests/wpt/meta/resize-observer/ordering.html.ini @@ -0,0 +1,3 @@ +[ordering.html] + [ResizeObserver and IntersectionObserver ordering] + expected: FAIL diff --git a/tests/wpt/meta/resize-observer/scrollbars-2.html.ini b/tests/wpt/meta/resize-observer/scrollbars-2.html.ini new file mode 100644 index 00000000000..659e8c5cdbb --- /dev/null +++ b/tests/wpt/meta/resize-observer/scrollbars-2.html.ini @@ -0,0 +1,3 @@ +[scrollbars-2.html] + [ResizeObserver content-box size and scrollbars] + expected: FAIL diff --git a/tests/wpt/meta/resize-observer/scrollbars.html.ini b/tests/wpt/meta/resize-observer/scrollbars.html.ini new file mode 100644 index 00000000000..d50311f8767 --- /dev/null +++ b/tests/wpt/meta/resize-observer/scrollbars.html.ini @@ -0,0 +1,3 @@ +[scrollbars.html] + [ResizeObserver content-box size and scrollbars] + expected: FAIL diff --git a/tests/wpt/meta/resize-observer/svg-with-css-box-001.html.ini b/tests/wpt/meta/resize-observer/svg-with-css-box-001.html.ini new file mode 100644 index 00000000000..f83d103163b --- /dev/null +++ b/tests/wpt/meta/resize-observer/svg-with-css-box-001.html.ini @@ -0,0 +1,10 @@ +[svg-with-css-box-001.html] + expected: TIMEOUT + [test0: observe `foreignObject` SVG in HTML document] + expected: FAIL + + [test1: observe inline SVG in HTML] + expected: FAIL + + [guard] + expected: NOTRUN diff --git a/tests/wpt/meta/resize-observer/svg-with-css-box-002.svg.ini b/tests/wpt/meta/resize-observer/svg-with-css-box-002.svg.ini new file mode 100644 index 00000000000..2800c85ddad --- /dev/null +++ b/tests/wpt/meta/resize-observer/svg-with-css-box-002.svg.ini @@ -0,0 +1,2 @@ +[svg-with-css-box-002.svg] + expected: TIMEOUT diff --git a/tests/wpt/meta/resize-observer/svg.html.ini b/tests/wpt/meta/resize-observer/svg.html.ini new file mode 100644 index 00000000000..c822c78cb6e --- /dev/null +++ b/tests/wpt/meta/resize-observer/svg.html.ini @@ -0,0 +1,34 @@ +[svg.html] + expected: TIMEOUT + [guard] + expected: NOTRUN + + [test0: observe svg:circle] + expected: FAIL + + [test1: observe svg:ellipse] + expected: FAIL + + [test2: observe svg:foreignObject] + expected: FAIL + + [test3: observe svg:image] + expected: FAIL + + [test4: observe svg:line] + expected: FAIL + + [test5: observe svg:path] + expected: FAIL + + [test6: observe svg:polygon] + expected: FAIL + + [test7: observe svg:polyline] + expected: FAIL + + [test8: observe svg:rect] + expected: FAIL + + [test9: observe svg:text] + expected: TIMEOUT