mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
implement basic infra for ResizeObserver (#31108)
This commit is contained in:
parent
3c1c395dfc
commit
3d78d60619
29 changed files with 706 additions and 4 deletions
|
@ -268,6 +268,9 @@ mod gen {
|
|||
allowed_in_nonsecure_contexts: bool,
|
||||
}
|
||||
},
|
||||
resize_observer: {
|
||||
enabled: bool,
|
||||
},
|
||||
script: {
|
||||
asynch: bool,
|
||||
},
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -33,7 +33,7 @@ impl DOMRectReadOnly {
|
|||
}
|
||||
}
|
||||
|
||||
fn new(
|
||||
pub fn new(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
x: f64,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
280
components/script/dom/resizeobserver.rs
Normal file
280
components/script/dom/resizeobserver.rs
Normal 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(),
|
||||
}
|
||||
}
|
120
components/script/dom/resizeobserverentry.rs
Normal file
120
components/script/dom/resizeobserverentry.rs
Normal 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)
|
||||
}
|
||||
}
|
67
components/script/dom/resizeobserversize.rs
Normal file
67
components/script/dom/resizeobserversize.rs
Normal 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()
|
||||
}
|
||||
}
|
23
components/script/dom/webidls/ResizeObserver.webidl
Normal file
23
components/script/dom/webidls/ResizeObserver.webidl
Normal 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);
|
14
components/script/dom/webidls/ResizeObserverEntry.webidl
Normal file
14
components/script/dom/webidls/ResizeObserverEntry.webidl
Normal 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;
|
||||
};
|
11
components/script/dom/webidls/ResizeObserverSize.webidl
Normal file
11
components/script/dom/webidls/ResizeObserverSize.webidl
Normal 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;
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -206,6 +206,8 @@ skip: true
|
|||
skip: true
|
||||
[srcdoc.meta]
|
||||
skip: true
|
||||
[resize-observer]
|
||||
skip: false
|
||||
[resource-timing]
|
||||
skip: false
|
||||
[selection]
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
[child-document-raf-order.html]
|
||||
[Ordering of steps in "Update the Rendering" - child document requestAnimationFrame order]
|
||||
expected: FAIL
|
||||
|
||||
|
|
1
tests/wpt/meta/resize-observer/__dir__.ini
Normal file
1
tests/wpt/meta/resize-observer/__dir__.ini
Normal file
|
@ -0,0 +1 @@
|
|||
prefs: ["dom.resize_observer.enabled:true"]
|
|
@ -0,0 +1,3 @@
|
|||
[calculate-depth-for-node.html]
|
||||
["Calculate depth for node" algorithm with Shadow DOM]
|
||||
expected: FAIL
|
2
tests/wpt/meta/resize-observer/devicepixel.html.ini
Normal file
2
tests/wpt/meta/resize-observer/devicepixel.html.ini
Normal file
|
@ -0,0 +1,2 @@
|
|||
[devicepixel.html]
|
||||
expected: FAIL
|
4
tests/wpt/meta/resize-observer/eventloop.html.ini
Normal file
4
tests/wpt/meta/resize-observer/eventloop.html.ini
Normal file
|
@ -0,0 +1,4 @@
|
|||
[eventloop.html]
|
||||
expected: TIMEOUT
|
||||
[guard]
|
||||
expected: NOTRUN
|
9
tests/wpt/meta/resize-observer/fragments.html.ini
Normal file
9
tests/wpt/meta/resize-observer/fragments.html.ini
Normal file
|
@ -0,0 +1,9 @@
|
|||
[fragments.html]
|
||||
[Adding 2nd fragment]
|
||||
expected: FAIL
|
||||
|
||||
[Resizing 2nd fragment]
|
||||
expected: FAIL
|
||||
|
||||
[Resizing all fragments]
|
||||
expected: FAIL
|
7
tests/wpt/meta/resize-observer/notify.html.ini
Normal file
7
tests/wpt/meta/resize-observer/notify.html.ini
Normal file
|
@ -0,0 +1,7 @@
|
|||
[notify.html]
|
||||
expected: ERROR
|
||||
[guard]
|
||||
expected: NOTRUN
|
||||
|
||||
[test2: remove/appendChild trigger notification]
|
||||
expected: FAIL
|
4
tests/wpt/meta/resize-observer/observe.html.ini
Normal file
4
tests/wpt/meta/resize-observer/observe.html.ini
Normal file
|
@ -0,0 +1,4 @@
|
|||
[observe.html]
|
||||
expected: TIMEOUT
|
||||
[guard]
|
||||
expected: NOTRUN
|
3
tests/wpt/meta/resize-observer/ordering.html.ini
Normal file
3
tests/wpt/meta/resize-observer/ordering.html.ini
Normal file
|
@ -0,0 +1,3 @@
|
|||
[ordering.html]
|
||||
[ResizeObserver and IntersectionObserver ordering]
|
||||
expected: FAIL
|
3
tests/wpt/meta/resize-observer/scrollbars-2.html.ini
Normal file
3
tests/wpt/meta/resize-observer/scrollbars-2.html.ini
Normal file
|
@ -0,0 +1,3 @@
|
|||
[scrollbars-2.html]
|
||||
[ResizeObserver content-box size and scrollbars]
|
||||
expected: FAIL
|
3
tests/wpt/meta/resize-observer/scrollbars.html.ini
Normal file
3
tests/wpt/meta/resize-observer/scrollbars.html.ini
Normal file
|
@ -0,0 +1,3 @@
|
|||
[scrollbars.html]
|
||||
[ResizeObserver content-box size and scrollbars]
|
||||
expected: FAIL
|
10
tests/wpt/meta/resize-observer/svg-with-css-box-001.html.ini
Normal file
10
tests/wpt/meta/resize-observer/svg-with-css-box-001.html.ini
Normal 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
|
|
@ -0,0 +1,2 @@
|
|||
[svg-with-css-box-002.svg]
|
||||
expected: TIMEOUT
|
34
tests/wpt/meta/resize-observer/svg.html.ini
Normal file
34
tests/wpt/meta/resize-observer/svg.html.ini
Normal 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
|
Loading…
Add table
Add a link
Reference in a new issue