implement basic infra for ResizeObserver (#31108)

This commit is contained in:
Gregory Terzian 2024-06-18 00:44:07 +08:00 committed by GitHub
parent 3c1c395dfc
commit 3d78d60619
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
29 changed files with 706 additions and 4 deletions

View file

@ -268,6 +268,9 @@ mod gen {
allowed_in_nonsecure_contexts: bool,
}
},
resize_observer: {
enabled: bool,
},
script: {
asynch: bool,
},

View file

@ -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,

View file

@ -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(),
}
}

View file

@ -33,7 +33,7 @@ impl DOMRectReadOnly {
}
}
fn new(
pub fn new(
global: &GlobalScope,
proto: Option<HandleObject>,
x: f64,

View file

@ -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;

View file

@ -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>

View file

@ -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(),
}
}

View file

@ -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)
}
}

View file

@ -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()
}
}

View file

@ -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);

View file

@ -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;
};

View file

@ -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;
};

View file

@ -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();
}

View file

@ -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,

View file

@ -206,6 +206,8 @@ skip: true
skip: true
[srcdoc.meta]
skip: true
[resize-observer]
skip: false
[resource-timing]
skip: false
[selection]

View file

@ -1,3 +1,4 @@
[child-document-raf-order.html]
[Ordering of steps in "Update the Rendering" - child document requestAnimationFrame order]
expected: FAIL

View file

@ -0,0 +1 @@
prefs: ["dom.resize_observer.enabled:true"]

View file

@ -0,0 +1,3 @@
[calculate-depth-for-node.html]
["Calculate depth for node" algorithm with Shadow DOM]
expected: FAIL

View file

@ -0,0 +1,2 @@
[devicepixel.html]
expected: FAIL

View file

@ -0,0 +1,4 @@
[eventloop.html]
expected: TIMEOUT
[guard]
expected: NOTRUN

View file

@ -0,0 +1,9 @@
[fragments.html]
[Adding 2nd fragment]
expected: FAIL
[Resizing 2nd fragment]
expected: FAIL
[Resizing all fragments]
expected: FAIL

View file

@ -0,0 +1,7 @@
[notify.html]
expected: ERROR
[guard]
expected: NOTRUN
[test2: remove/appendChild trigger notification]
expected: FAIL

View file

@ -0,0 +1,4 @@
[observe.html]
expected: TIMEOUT
[guard]
expected: NOTRUN

View file

@ -0,0 +1,3 @@
[ordering.html]
[ResizeObserver and IntersectionObserver ordering]
expected: FAIL

View file

@ -0,0 +1,3 @@
[scrollbars-2.html]
[ResizeObserver content-box size and scrollbars]
expected: FAIL

View file

@ -0,0 +1,3 @@
[scrollbars.html]
[ResizeObserver content-box size and scrollbars]
expected: FAIL

View file

@ -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

View file

@ -0,0 +1,2 @@
[svg-with-css-box-002.svg]
expected: TIMEOUT

View file

@ -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