mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
dom: Implement minimal IntersectionObserver workflow (#35551)
* Add very rough implemnentation of observation steps Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Fix entry reflection and propagate can_gc Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Fix BorrowError and add fragment find descendant Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Implement is descendant in containing block path correctly Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Fix unrooted error and tidy issues Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Fix comments Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Remove is descendant of other node query I suppose these changes is better separated to other PRs. Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Fix intersection and refactor registration Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Use AppUnit more and propagate GlobalScope better Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Update WPT expectations Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Revert delay changes Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Align compute intersection algo to other browser actual behavior Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Align processing documents and note several issues Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Update WPT Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Minor lint Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Fix top level browsing context Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Make Registration rootable Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Avoid reflow inside observation step algo Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Using borrow for iterating registration Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Fix document disconnect Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Update WPT Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Address comments and minor quality suggestions Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Root the observer before nofifying any of it Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Tidy docs Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Account not found element and refactor observation step Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Fix documentations Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Ignore position of document viewport Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Refactor root intersection rectangle Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Add can GC note to the callback Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Fix top-level browsing context term Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> * Fix minor comments Signed-off-by: stevennovaryo <steven.novaryo@gmail.com> --------- Signed-off-by: stevennovaryo <steven.novaryo@gmail.com>
This commit is contained in:
parent
2113e54819
commit
67a5f285ed
48 changed files with 850 additions and 175 deletions
|
@ -159,6 +159,7 @@ use crate::dom::htmlmetaelement::RefreshRedirectDue;
|
|||
use crate::dom::htmlscriptelement::{HTMLScriptElement, ScriptResult};
|
||||
use crate::dom::htmltextareaelement::HTMLTextAreaElement;
|
||||
use crate::dom::htmltitleelement::HTMLTitleElement;
|
||||
use crate::dom::intersectionobserver::IntersectionObserver;
|
||||
use crate::dom::keyboardevent::KeyboardEvent;
|
||||
use crate::dom::location::Location;
|
||||
use crate::dom::messageevent::MessageEvent;
|
||||
|
@ -520,6 +521,18 @@ pub(crate) struct Document {
|
|||
inherited_insecure_requests_policy: Cell<Option<InsecureRequestsPolicy>>,
|
||||
/// <https://w3c.github.io/IntersectionObserver/#document-intersectionobservertaskqueued>
|
||||
intersection_observer_task_queued: Cell<bool>,
|
||||
/// Active intersection observers that should be processed by this document in
|
||||
/// the update intersection observation steps.
|
||||
/// <https://w3c.github.io/IntersectionObserver/#run-the-update-intersection-observations-steps>
|
||||
/// > Let observer list be a list of all IntersectionObservers whose root is in the DOM tree of document.
|
||||
/// > For the top-level browsing context, this includes implicit root observers.
|
||||
///
|
||||
/// Details of which document that should process an observers is discussed further at
|
||||
/// <https://github.com/w3c/IntersectionObserver/issues/525>.
|
||||
///
|
||||
/// The lifetime of an intersection observer is specified at
|
||||
/// <https://github.com/w3c/IntersectionObserver/issues/525>.
|
||||
intersection_observers: DomRefCell<Vec<Dom<IntersectionObserver>>>,
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
|
@ -3421,6 +3434,104 @@ impl Document {
|
|||
// implement the Permissions Policy specification.
|
||||
true
|
||||
}
|
||||
|
||||
/// Add an [`IntersectionObserver`] to the [`Document`], to be processed in the [`Document`]'s event loop.
|
||||
/// <https://github.com/w3c/IntersectionObserver/issues/525>
|
||||
pub(crate) fn add_intersection_observer(&self, intersection_observer: &IntersectionObserver) {
|
||||
self.intersection_observers
|
||||
.borrow_mut()
|
||||
.push(Dom::from_ref(intersection_observer));
|
||||
}
|
||||
|
||||
/// Remove an [`IntersectionObserver`] from [`Document`], ommiting it from the event loop.
|
||||
/// An observer without any target, ideally should be removed to be conformant with
|
||||
/// <https://w3c.github.io/IntersectionObserver/#lifetime>.
|
||||
pub(crate) fn remove_intersection_observer(
|
||||
&self,
|
||||
intersection_observer: &IntersectionObserver,
|
||||
) {
|
||||
self.intersection_observers
|
||||
.borrow_mut()
|
||||
.retain(|observer| *observer != intersection_observer)
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo>
|
||||
pub(crate) fn update_intersection_observer_steps(
|
||||
&self,
|
||||
time: CrossProcessInstant,
|
||||
can_gc: CanGc,
|
||||
) {
|
||||
// Step 1-2
|
||||
for intersection_observer in &*self.intersection_observers.borrow() {
|
||||
self.update_single_intersection_observer_steps(intersection_observer, time, can_gc);
|
||||
}
|
||||
}
|
||||
|
||||
/// Step 2.1-2.2 of <https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo>
|
||||
fn update_single_intersection_observer_steps(
|
||||
&self,
|
||||
intersection_observer: &IntersectionObserver,
|
||||
time: CrossProcessInstant,
|
||||
can_gc: CanGc,
|
||||
) {
|
||||
// Step 1
|
||||
// > Let rootBounds be observer’s root intersection rectangle.
|
||||
let root_bounds = intersection_observer.root_intersection_rectangle(self);
|
||||
|
||||
// Step 2
|
||||
// > For each target in observer’s internal [[ObservationTargets]] slot,
|
||||
// > processed in the same order that observe() was called on each target:
|
||||
intersection_observer.update_intersection_observations_steps(
|
||||
self,
|
||||
time,
|
||||
root_bounds,
|
||||
can_gc,
|
||||
);
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/IntersectionObserver/#notify-intersection-observers-algo>
|
||||
pub(crate) fn notify_intersection_observers(&self, can_gc: CanGc) {
|
||||
// Step 1
|
||||
// > Set document’s IntersectionObserverTaskQueued flag to false.
|
||||
self.intersection_observer_task_queued.set(false);
|
||||
|
||||
// Step 2
|
||||
// > Let notify list be a list of all IntersectionObservers whose root is in the DOM tree of document.
|
||||
// We will copy the observers because callback could modify the current list.
|
||||
// It will rooted to prevent GC in the iteration.
|
||||
rooted_vec!(let notify_list <- self.intersection_observers.clone().take().into_iter());
|
||||
|
||||
// Step 3
|
||||
// > For each IntersectionObserver object observer in notify list, run these steps:
|
||||
for intersection_observer in notify_list.iter() {
|
||||
// Step 3.1-3.5
|
||||
intersection_observer.invoke_callback_if_necessary(can_gc);
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/IntersectionObserver/#queue-intersection-observer-task>
|
||||
pub(crate) fn queue_an_intersection_observer_task(&self) {
|
||||
// Step 1
|
||||
// > If document’s IntersectionObserverTaskQueued flag is set to true, return.
|
||||
if self.intersection_observer_task_queued.get() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2
|
||||
// > Set document’s IntersectionObserverTaskQueued flag to true.
|
||||
self.intersection_observer_task_queued.set(true);
|
||||
|
||||
// Step 3
|
||||
// > Queue a task on the IntersectionObserver task source associated with
|
||||
// > the document's event loop to notify intersection observers.
|
||||
let document = Trusted::new(self);
|
||||
self.owner_global()
|
||||
.task_manager()
|
||||
.intersection_observer_task_source()
|
||||
.queue(task!(notify_intersection_observers: move || {
|
||||
document.root().notify_intersection_observers(CanGc::note());
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_character_value_key(key: &Key) -> bool {
|
||||
|
@ -3720,6 +3831,7 @@ impl Document {
|
|||
allow_declarative_shadow_roots: Cell::new(allow_declarative_shadow_roots),
|
||||
inherited_insecure_requests_policy: Cell::new(inherited_insecure_requests_policy),
|
||||
intersection_observer_task_queued: Cell::new(false),
|
||||
intersection_observers: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,20 @@ impl DOMRectReadOnly {
|
|||
)
|
||||
}
|
||||
|
||||
pub(crate) fn new_from_dictionary(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
dictionary: &DOMRectInit,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<DOMRectReadOnly> {
|
||||
reflect_dom_object_with_proto(
|
||||
Box::new(create_a_domrectreadonly_from_the_dictionary(dictionary)),
|
||||
global,
|
||||
proto,
|
||||
can_gc,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn set_x(&self, value: f64) {
|
||||
self.x.set(value);
|
||||
}
|
||||
|
|
|
@ -653,15 +653,28 @@ impl Element {
|
|||
}))
|
||||
}
|
||||
|
||||
/// Add a new IntersectionObserverRegistration to the element.
|
||||
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
|
||||
pub(crate) fn add_intersection_observer_registration(
|
||||
pub(crate) fn get_intersection_observer_registration(
|
||||
&self,
|
||||
registration: IntersectionObserverRegistration,
|
||||
observer: &IntersectionObserver,
|
||||
) -> Option<Ref<IntersectionObserverRegistration>> {
|
||||
if let Some(registrations) = self.registered_intersection_observers() {
|
||||
registrations
|
||||
.iter()
|
||||
.position(|reg_obs| reg_obs.observer == observer)
|
||||
.map(|index| Ref::map(registrations, |registrations| ®istrations[index]))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a new IntersectionObserverRegistration with initial value to the element.
|
||||
pub(crate) fn add_initial_intersection_observer_registration(
|
||||
&self,
|
||||
observer: &IntersectionObserver,
|
||||
) {
|
||||
self.ensure_rare_data()
|
||||
.registered_intersection_observers
|
||||
.push(registration);
|
||||
.push(IntersectionObserverRegistration::new_initial(observer));
|
||||
}
|
||||
|
||||
/// Removes a certain IntersectionObserver.
|
||||
|
|
|
@ -4,10 +4,13 @@
|
|||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
use app_units::Au;
|
||||
use base::cross_process_instant::CrossProcessInstant;
|
||||
use cssparser::{Parser, ParserInput};
|
||||
use dom_struct::dom_struct;
|
||||
use euclid::default::{Rect, Size2D};
|
||||
use js::rust::{HandleObject, MutableHandleValue};
|
||||
use style::context::QuirksMode;
|
||||
use style::parser::{Parse, ParserContext};
|
||||
|
@ -15,21 +18,26 @@ use style::stylesheets::{CssRuleType, Origin};
|
|||
use style_traits::{ParsingMode, ToCss};
|
||||
use url::Url;
|
||||
|
||||
use super::bindings::codegen::Bindings::IntersectionObserverBinding::{
|
||||
IntersectionObserverCallback, IntersectionObserverMethods,
|
||||
};
|
||||
use super::intersectionobserverentry::IntersectionObserverEntry;
|
||||
use super::intersectionobserverrootmargin::IntersectionObserverRootMargin;
|
||||
use crate::dom::bindings::callback::ExceptionHandling;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::IntersectionObserverBinding::IntersectionObserverInit;
|
||||
use crate::dom::bindings::codegen::Bindings::IntersectionObserverBinding::{
|
||||
IntersectionObserverCallback, IntersectionObserverInit, IntersectionObserverMethods,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
|
||||
use crate::dom::bindings::codegen::UnionTypes::{DoubleOrDoubleSequence, ElementOrDocument};
|
||||
use crate::dom::bindings::error::{Error, Fallible};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::bindings::str::DOMString;
|
||||
use crate::dom::bindings::utils::to_frozen_array;
|
||||
use crate::dom::document::Document;
|
||||
use crate::dom::domrectreadonly::DOMRectReadOnly;
|
||||
use crate::dom::element::Element;
|
||||
use crate::dom::intersectionobserverentry::IntersectionObserverEntry;
|
||||
use crate::dom::intersectionobserverrootmargin::IntersectionObserverRootMargin;
|
||||
use crate::dom::node::{Node, NodeTraits};
|
||||
use crate::dom::window::Window;
|
||||
use crate::script_runtime::{CanGc, JSContext};
|
||||
|
||||
|
@ -49,12 +57,18 @@ pub type IntersectionRoot = Option<ElementOrDocument>;
|
|||
pub(crate) struct IntersectionObserver {
|
||||
reflector_: Reflector,
|
||||
|
||||
/// [`Document`] that should process this observer's observation steps.
|
||||
/// Following Chrome and Firefox, it is the current document on construction.
|
||||
/// <https://github.com/w3c/IntersectionObserver/issues/525>
|
||||
owner_doc: Dom<Document>,
|
||||
|
||||
/// > The root provided to the IntersectionObserver constructor, or null if none was provided.
|
||||
/// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-root>
|
||||
root: IntersectionRoot,
|
||||
|
||||
/// > This callback will be invoked when there are changes to a target’s intersection
|
||||
/// > with the intersection root, as per the processing model.
|
||||
///
|
||||
/// <https://w3c.github.io/IntersectionObserver/#intersection-observer-callback>
|
||||
#[ignore_malloc_size_of = "Rc are hard"]
|
||||
callback: Rc<IntersectionObserverCallback>,
|
||||
|
@ -86,7 +100,8 @@ pub(crate) struct IntersectionObserver {
|
|||
}
|
||||
|
||||
impl IntersectionObserver {
|
||||
pub(crate) fn new_inherited(
|
||||
fn new_inherited(
|
||||
window: &Window,
|
||||
callback: Rc<IntersectionObserverCallback>,
|
||||
root: IntersectionRoot,
|
||||
root_margin: IntersectionObserverRootMargin,
|
||||
|
@ -94,6 +109,7 @@ impl IntersectionObserver {
|
|||
) -> Self {
|
||||
Self {
|
||||
reflector_: Reflector::new(),
|
||||
owner_doc: window.Document().as_traced(),
|
||||
root,
|
||||
callback,
|
||||
queued_entries: Default::default(),
|
||||
|
@ -136,11 +152,10 @@ impl IntersectionObserver {
|
|||
// > 1. Let this be a new IntersectionObserver object
|
||||
// > 2. Set this’s internal [[callback]] slot to callback.
|
||||
// > 3. ... set this’s internal [[rootMargin]] slot to that.
|
||||
// > 4. ,.. set this’s internal [[scrollMargin]] slot to that.
|
||||
//
|
||||
// Owned root is also passed to the constructor.
|
||||
// > 4. ... set this’s internal [[scrollMargin]] slot to that.
|
||||
let observer = reflect_dom_object_with_proto(
|
||||
Box::new(Self::new_inherited(
|
||||
window,
|
||||
callback,
|
||||
init.root.clone(),
|
||||
root_margin,
|
||||
|
@ -194,7 +209,7 @@ impl IntersectionObserver {
|
|||
// Step 9
|
||||
// > The thresholds attribute getter will return this sorted thresholds list.
|
||||
//
|
||||
// Set this’s internal [[thresholds]] slot to the sorted thresholds list
|
||||
// Set this internal [[thresholds]] slot to the sorted thresholds list
|
||||
// and getter will return the internal [[thresholds]] slot.
|
||||
self.thresholds.replace(thresholds);
|
||||
|
||||
|
@ -206,6 +221,9 @@ impl IntersectionObserver {
|
|||
|
||||
// Step 11
|
||||
// > If options.trackVisibility is true and delay is less than 100, set delay to 100.
|
||||
//
|
||||
// In Chromium, the minimum delay required is 100 milliseconds for observation that consider trackVisibilty.
|
||||
// Currently, visibility is not implemented.
|
||||
if init.trackVisibility {
|
||||
delay = delay.max(100);
|
||||
}
|
||||
|
@ -221,6 +239,19 @@ impl IntersectionObserver {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/IntersectionObserver/#intersectionobserver-implicit-root>
|
||||
fn root_is_implicit_root(&self) -> bool {
|
||||
self.root.is_none()
|
||||
}
|
||||
|
||||
/// Return unwrapped root if it was an element, None if otherwise.
|
||||
fn maybe_element_root(&self) -> Option<&Element> {
|
||||
match &self.root {
|
||||
Some(ElementOrDocument::Element(element)) => Some(element),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/IntersectionObserver/#observe-target-element>
|
||||
fn observe_target_element(&self, target: &Element) {
|
||||
// Step 1
|
||||
|
@ -240,13 +271,11 @@ impl IntersectionObserver {
|
|||
// > a previousIsIntersecting property set to false, and a previousIsVisible property set to false.
|
||||
// Step 3
|
||||
// > Append intersectionObserverRegistration to target’s internal [[RegisteredIntersectionObservers]] slot.
|
||||
target.add_intersection_observer_registration(IntersectionObserverRegistration {
|
||||
observer: Dom::from_ref(self),
|
||||
previous_threshold_index: Cell::new(-1),
|
||||
previous_is_intersecting: Cell::new(false),
|
||||
last_update_time: Cell::new(CrossProcessInstant::epoch()),
|
||||
previous_is_visible: Cell::new(false),
|
||||
});
|
||||
target.add_initial_intersection_observer_registration(self);
|
||||
|
||||
if self.observation_targets.borrow().is_empty() {
|
||||
self.connect_to_owner_unchecked();
|
||||
}
|
||||
|
||||
// Step 4
|
||||
// > Add target to observer’s internal [[ObservationTargets]] slot.
|
||||
|
@ -269,6 +298,363 @@ impl IntersectionObserver {
|
|||
self.observation_targets
|
||||
.borrow_mut()
|
||||
.retain(|element| &**element != target);
|
||||
|
||||
// Should disconnect from owner if it is not observing anything.
|
||||
if self.observation_targets.borrow().is_empty() {
|
||||
self.disconnect_from_owner_unchecked();
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/IntersectionObserver/#queue-an-intersectionobserverentry>
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn queue_an_intersectionobserverentry(
|
||||
&self,
|
||||
document: &Document,
|
||||
time: CrossProcessInstant,
|
||||
root_bounds: Rect<Au>,
|
||||
bounding_client_rect: Rect<Au>,
|
||||
intersection_rect: Rect<Au>,
|
||||
is_intersecting: bool,
|
||||
is_visible: bool,
|
||||
intersection_ratio: f64,
|
||||
target: &Element,
|
||||
can_gc: CanGc,
|
||||
) {
|
||||
let rect_to_domrectreadonly = |rect: Rect<Au>| {
|
||||
DOMRectReadOnly::new(
|
||||
self.owner_doc.window().as_global_scope(),
|
||||
None,
|
||||
rect.origin.x.to_f64_px(),
|
||||
rect.origin.y.to_f64_px(),
|
||||
rect.size.width.to_f64_px(),
|
||||
rect.size.height.to_f64_px(),
|
||||
can_gc,
|
||||
)
|
||||
};
|
||||
|
||||
let root_bounds = rect_to_domrectreadonly(root_bounds);
|
||||
let bounding_client_rect = rect_to_domrectreadonly(bounding_client_rect);
|
||||
let intersection_rect = rect_to_domrectreadonly(intersection_rect);
|
||||
|
||||
// Step 1-2
|
||||
// > 1. Construct an IntersectionObserverEntry, passing in time, rootBounds,
|
||||
// > boundingClientRect, intersectionRect, isIntersecting, and target.
|
||||
// > 2. Append it to observer’s internal [[QueuedEntries]] slot.
|
||||
self.queued_entries.borrow_mut().push(
|
||||
IntersectionObserverEntry::new(
|
||||
self.owner_doc.window(),
|
||||
None,
|
||||
document
|
||||
.owner_global()
|
||||
.performance()
|
||||
.to_dom_high_res_time_stamp(time),
|
||||
Some(&root_bounds),
|
||||
&bounding_client_rect,
|
||||
&intersection_rect,
|
||||
is_intersecting,
|
||||
is_visible,
|
||||
Finite::wrap(intersection_ratio),
|
||||
target,
|
||||
can_gc,
|
||||
)
|
||||
.as_traced(),
|
||||
);
|
||||
// > Step 3
|
||||
// Queue an intersection observer task for document.
|
||||
document.queue_an_intersection_observer_task();
|
||||
}
|
||||
|
||||
/// Step 3.1-3.5 of <https://w3c.github.io/IntersectionObserver/#notify-intersection-observers-algo>
|
||||
pub(crate) fn invoke_callback_if_necessary(&self, can_gc: CanGc) {
|
||||
// Step 1
|
||||
// > If observer’s internal [[QueuedEntries]] slot is empty, continue.
|
||||
if self.queued_entries.borrow().is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 2-3
|
||||
// We trivially moved the entries and root them.
|
||||
let queued_entries = self
|
||||
.queued_entries
|
||||
.take()
|
||||
.iter_mut()
|
||||
.map(|entry| entry.as_rooted())
|
||||
.collect();
|
||||
|
||||
// Step 4-5
|
||||
let _ = self.callback.Call_(
|
||||
self,
|
||||
queued_entries,
|
||||
self,
|
||||
ExceptionHandling::Report,
|
||||
can_gc,
|
||||
);
|
||||
}
|
||||
|
||||
/// Connect the observer itself into owner doc if it is unconnected.
|
||||
/// It would not check whether the observer is already connected or not inside the doc.
|
||||
fn connect_to_owner_unchecked(&self) {
|
||||
self.owner_doc.add_intersection_observer(self);
|
||||
}
|
||||
|
||||
/// Disconnect the observer itself from owner doc.
|
||||
/// It would not check whether the observer is already disconnected or not inside the doc.
|
||||
fn disconnect_from_owner_unchecked(&self) {
|
||||
self.owner_doc.remove_intersection_observer(self);
|
||||
}
|
||||
|
||||
/// > The root intersection rectangle for an IntersectionObserver is
|
||||
/// > the rectangle we’ll use to check against the targets.
|
||||
///
|
||||
/// <https://w3c.github.io/IntersectionObserver/#intersectionobserver-root-intersection-rectangle>
|
||||
pub(crate) fn root_intersection_rectangle(&self, document: &Document) -> Option<Rect<Au>> {
|
||||
let intersection_rectangle = match &self.root {
|
||||
// Handle if root is an element.
|
||||
Some(ElementOrDocument::Element(element)) => {
|
||||
// TODO: recheck scrollbar approach and clip-path clipping from Chromium implementation.
|
||||
|
||||
// > Otherwise, if the intersection root has a content clip,
|
||||
// > it’s the element’s padding area.
|
||||
// TODO(stevennovaryo): check for content clip
|
||||
|
||||
// > Otherwise, it’s the result of getting the bounding box for the intersection root.
|
||||
// TODO: replace this once getBoundingBox() is implemented correctly.
|
||||
DomRoot::upcast::<Node>(element.clone()).bounding_content_box_no_reflow()
|
||||
},
|
||||
// Handle if root is a Document, which includes implicit root and explicit Document root.
|
||||
_ => {
|
||||
let document = if self.root.is_none() {
|
||||
// > If the IntersectionObserver is an implicit root observer,
|
||||
// > it’s treated as if the root were the top-level browsing context’s document,
|
||||
// > according to the following rule for document.
|
||||
//
|
||||
// There are uncertainties whether the browsing context we should consider is the browsing
|
||||
// context of the target or observer. <https://github.com/w3c/IntersectionObserver/issues/456>
|
||||
document
|
||||
.window()
|
||||
.webview_window_proxy()
|
||||
.and_then(|window_proxy| window_proxy.document())
|
||||
} else if let Some(ElementOrDocument::Document(document)) = &self.root {
|
||||
Some(document.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// > If the intersection root is a document, it’s the size of the document's viewport
|
||||
// > (note that this processing step can only be reached if the document is fully active).
|
||||
// TODO: viewport should consider native scrollbar if exist. Recheck Servo's scrollbar approach.
|
||||
document.map(|document| {
|
||||
let viewport = document.window().window_size().initial_viewport;
|
||||
Rect::from_size(Size2D::new(
|
||||
Au::from_f32_px(viewport.width),
|
||||
Au::from_f32_px(viewport.height),
|
||||
))
|
||||
})
|
||||
},
|
||||
};
|
||||
|
||||
// > When calculating the root intersection rectangle for a same-origin-domain target,
|
||||
// > the rectangle is then expanded according to the offsets in the IntersectionObserver’s
|
||||
// > [[rootMargin]] slot in a manner similar to CSS’s margin property, with the four values
|
||||
// > indicating the amount the top, right, bottom, and left edges, respectively, are offset by,
|
||||
// > with positive lengths indicating an outward offset. Percentages are resolved relative to
|
||||
// > the width of the undilated rectangle.
|
||||
// TODO(stevennovaryo): add check for same-origin-domain
|
||||
intersection_rectangle.map(|intersection_rectangle| {
|
||||
let margin = self
|
||||
.root_margin
|
||||
.borrow()
|
||||
.resolve_percentages_with_basis(intersection_rectangle);
|
||||
intersection_rectangle.outer_rect(margin)
|
||||
})
|
||||
}
|
||||
|
||||
/// Step 2.2.4-2.2.21 of <https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo>
|
||||
///
|
||||
/// If some conditions require to skips "processing further", we will skips those steps and
|
||||
/// return default values conformant to step 2.2.4. See [`IntersectionObservationOutput::default_skipped`].
|
||||
///
|
||||
/// Note that current draft specs skipped wrong steps, as it should skip computing fields that
|
||||
/// would result in different intersection entry other than the default entry per published spec.
|
||||
/// <https://www.w3.org/TR/intersection-observer/>
|
||||
fn maybe_compute_intersection_output(
|
||||
&self,
|
||||
document: &Document,
|
||||
target: &Element,
|
||||
maybe_root_bounds: Option<Rect<Au>>,
|
||||
) -> IntersectionObservationOutput {
|
||||
// Step 5
|
||||
// > If the intersection root is not the implicit root, and target is not in
|
||||
// > the same document as the intersection root, skip to step 11.
|
||||
if !self.root_is_implicit_root() && *target.owner_document() != *document {
|
||||
return IntersectionObservationOutput::default_skipped();
|
||||
}
|
||||
|
||||
// Step 6
|
||||
// > If the intersection root is an Element, and target is not a descendant of
|
||||
// > the intersection root in the containing block chain, skip to step 11.
|
||||
// TODO(stevennovaryo): implement LayoutThread query that support this.
|
||||
if let Some(_element) = self.maybe_element_root() {
|
||||
debug!("descendant of containing block chain is not implemented");
|
||||
}
|
||||
|
||||
// Step 7
|
||||
// > Set targetRect to the DOMRectReadOnly obtained by getting the bounding box for target.
|
||||
// This is what we are currently using for getBoundingBox(). However, it is not correct,
|
||||
// mainly because it is not considering transform and scroll offset.
|
||||
// TODO: replace this once getBoundingBox() is implemented correctly.
|
||||
let maybe_target_rect = target.upcast::<Node>().bounding_content_box_no_reflow();
|
||||
|
||||
// Following the implementation of Gecko, we will skip further processing if these
|
||||
// information not available. This would also handle display none element.
|
||||
if maybe_root_bounds.is_none() || maybe_target_rect.is_none() {
|
||||
return IntersectionObservationOutput::default_skipped();
|
||||
}
|
||||
let root_bounds = maybe_root_bounds.unwrap();
|
||||
let target_rect = maybe_target_rect.unwrap();
|
||||
|
||||
// TODO(stevennovaryo): we should probably also consider adding visibity check, ideally
|
||||
// it would require new query from LayoutThread.
|
||||
|
||||
// Step 8
|
||||
// > Let intersectionRect be the result of running the compute the intersection algorithm on
|
||||
// > target and observer’s intersection root.
|
||||
let intersection_rect =
|
||||
compute_the_intersection(document, target, &self.root, root_bounds, target_rect);
|
||||
|
||||
// Step 9
|
||||
// > Let targetArea be targetRect’s area.
|
||||
let target_area = target_rect.size.width.0 * target_rect.size.height.0;
|
||||
|
||||
// Step 10
|
||||
// > Let intersectionArea be intersectionRect’s area.
|
||||
let intersection_area = intersection_rect.size.width.0 * intersection_rect.size.height.0;
|
||||
|
||||
// Step 11
|
||||
// > Let isIntersecting be true if targetRect and rootBounds intersect or are edge-adjacent,
|
||||
// > even if the intersection has zero area (because rootBounds or targetRect have zero area).
|
||||
// Because we are considering edge-adjacent, instead of checking whether the rectangle is empty,
|
||||
// we are checking whether the rectangle is negative or not.
|
||||
// TODO(stevennovaryo): there is a dicussion regarding isIntersecting definition, we should update
|
||||
// it accordingly. https://github.com/w3c/IntersectionObserver/issues/432
|
||||
let is_intersecting = !target_rect
|
||||
.to_box2d()
|
||||
.intersection_unchecked(&root_bounds.to_box2d())
|
||||
.is_negative();
|
||||
|
||||
// Step 12
|
||||
// > If targetArea is non-zero, let intersectionRatio be intersectionArea divided by targetArea.
|
||||
// > Otherwise, let intersectionRatio be 1 if isIntersecting is true, or 0 if isIntersecting is false.
|
||||
let intersection_ratio = match target_area {
|
||||
0 => is_intersecting.into(),
|
||||
_ => (intersection_area as f64) / (target_area as f64),
|
||||
};
|
||||
|
||||
// Step 13
|
||||
// > Set thresholdIndex to the index of the first entry in observer.thresholds whose value is
|
||||
// > greater than intersectionRatio, or the length of observer.thresholds if intersectionRatio is
|
||||
// > greater than or equal to the last entry in observer.thresholds.
|
||||
let threshold_index = self
|
||||
.thresholds
|
||||
.borrow()
|
||||
.iter()
|
||||
.position(|threshold| **threshold > intersection_ratio)
|
||||
.unwrap_or(self.thresholds.borrow().len()) as i32;
|
||||
|
||||
// Step 14
|
||||
// > Let isVisible be the result of running the visibility algorithm on target.
|
||||
// TODO: Implement visibility algorithm
|
||||
let is_visible = false;
|
||||
|
||||
IntersectionObservationOutput::new_computed(
|
||||
threshold_index,
|
||||
is_intersecting,
|
||||
target_rect,
|
||||
intersection_rect,
|
||||
intersection_ratio,
|
||||
is_visible,
|
||||
root_bounds,
|
||||
)
|
||||
}
|
||||
|
||||
/// Step 2.2.1-2.2.21 of <https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo>
|
||||
pub(crate) fn update_intersection_observations_steps(
|
||||
&self,
|
||||
document: &Document,
|
||||
time: CrossProcessInstant,
|
||||
root_bounds: Option<Rect<Au>>,
|
||||
can_gc: CanGc,
|
||||
) {
|
||||
for target in &*self.observation_targets.borrow() {
|
||||
// Step 1
|
||||
// > Let registration be the IntersectionObserverRegistration record in target’s internal
|
||||
// > [[RegisteredIntersectionObservers]] slot whose observer property is equal to observer.
|
||||
let registration = target.get_intersection_observer_registration(self).unwrap();
|
||||
|
||||
// Step 2
|
||||
// > If (time - registration.lastUpdateTime < observer.delay), skip further processing for target.
|
||||
if time - registration.last_update_time.get() <
|
||||
Duration::from_millis(self.delay.get().max(0) as u64)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3
|
||||
// > Set registration.lastUpdateTime to time.
|
||||
registration.last_update_time.set(time);
|
||||
|
||||
// step 4-14
|
||||
let intersection_output =
|
||||
self.maybe_compute_intersection_output(document, target, root_bounds);
|
||||
|
||||
// Step 15-17
|
||||
// > 15. Let previousThresholdIndex be the registration’s previousThresholdIndex property.
|
||||
// > 16. Let previousIsIntersecting be the registration’s previousIsIntersecting property.
|
||||
// > 17. Let previousIsVisible be the registration’s previousIsVisible property.
|
||||
let previous_threshold_index = registration.previous_threshold_index.get();
|
||||
let previous_is_intersecting = registration.previous_is_intersecting.get();
|
||||
let previous_is_visible = registration.previous_is_visible.get();
|
||||
|
||||
// Step 18
|
||||
// > If thresholdIndex does not equal previousThresholdIndex, or
|
||||
// > if isIntersecting does not equal previousIsIntersecting, or
|
||||
// > if isVisible does not equal previousIsVisible,
|
||||
// > queue an IntersectionObserverEntry, passing in observer, time, rootBounds,
|
||||
// > targetRect, intersectionRect, isIntersecting, isVisible, and target.
|
||||
if intersection_output.threshold_index != previous_threshold_index ||
|
||||
intersection_output.is_intersecting != previous_is_intersecting ||
|
||||
intersection_output.is_visible != previous_is_visible
|
||||
{
|
||||
// TODO(stevennovaryo): Per IntersectionObserverEntry interface, the rootBounds
|
||||
// should be null for cross-origin-domain target.
|
||||
self.queue_an_intersectionobserverentry(
|
||||
document,
|
||||
time,
|
||||
intersection_output.root_bounds,
|
||||
intersection_output.target_rect,
|
||||
intersection_output.intersection_rect,
|
||||
intersection_output.is_intersecting,
|
||||
intersection_output.is_visible,
|
||||
intersection_output.intersection_ratio,
|
||||
target,
|
||||
can_gc,
|
||||
);
|
||||
}
|
||||
|
||||
// Step 19-21
|
||||
// > 19. Assign thresholdIndex to registration’s previousThresholdIndex property.
|
||||
// > 20. Assign isIntersecting to registration’s previousIsIntersecting property.
|
||||
// > 21. Assign isVisible to registration’s previousIsVisible property.
|
||||
registration
|
||||
.previous_threshold_index
|
||||
.set(intersection_output.threshold_index);
|
||||
registration
|
||||
.previous_is_intersecting
|
||||
.set(intersection_output.is_intersecting);
|
||||
registration
|
||||
.previous_is_visible
|
||||
.set(intersection_output.is_visible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -329,6 +715,9 @@ impl IntersectionObserverMethods<crate::DomTypeHolder> for IntersectionObserver
|
|||
/// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-observe>
|
||||
fn Observe(&self, target: &Element) {
|
||||
self.observe_target_element(target);
|
||||
|
||||
// Connect to owner doc to be accessed in the event loop.
|
||||
self.connect_to_owner_unchecked();
|
||||
}
|
||||
|
||||
/// > Run the unobserve a target Element algorithm, providing this and target.
|
||||
|
@ -348,6 +737,9 @@ impl IntersectionObserverMethods<crate::DomTypeHolder> for IntersectionObserver
|
|||
});
|
||||
// > 2. Remove target from this’s internal [[ObservationTargets]] slot.
|
||||
self.observation_targets.borrow_mut().clear();
|
||||
|
||||
// We should remove this observer from the event loop.
|
||||
self.disconnect_from_owner_unchecked();
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-takerecords>
|
||||
|
@ -373,15 +765,32 @@ impl IntersectionObserverMethods<crate::DomTypeHolder> for IntersectionObserver
|
|||
}
|
||||
|
||||
/// <https://w3c.github.io/IntersectionObserver/#intersectionobserverregistration>
|
||||
#[derive(JSTraceable, MallocSizeOf)]
|
||||
#[derive(Clone, JSTraceable, MallocSizeOf)]
|
||||
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
|
||||
pub(crate) struct IntersectionObserverRegistration {
|
||||
pub(crate) observer: Dom<IntersectionObserver>,
|
||||
previous_threshold_index: Cell<i32>,
|
||||
previous_is_intersecting: Cell<bool>,
|
||||
pub(crate) previous_threshold_index: Cell<i32>,
|
||||
pub(crate) previous_is_intersecting: Cell<bool>,
|
||||
#[no_trace]
|
||||
last_update_time: Cell<CrossProcessInstant>,
|
||||
previous_is_visible: Cell<bool>,
|
||||
pub(crate) last_update_time: Cell<CrossProcessInstant>,
|
||||
pub(crate) previous_is_visible: Cell<bool>,
|
||||
}
|
||||
|
||||
impl IntersectionObserverRegistration {
|
||||
/// Initial value of [`IntersectionObserverRegistration`] according to
|
||||
/// step 2 of <https://w3c.github.io/IntersectionObserver/#observe-target-element>.
|
||||
/// > Let intersectionObserverRegistration be an IntersectionObserverRegistration record with
|
||||
/// > an observer property set to observer, a previousThresholdIndex property set to -1,
|
||||
/// > a previousIsIntersecting property set to false, and a previousIsVisible property set to false.
|
||||
pub(crate) fn new_initial(observer: &IntersectionObserver) -> Self {
|
||||
IntersectionObserverRegistration {
|
||||
observer: Dom::from_ref(observer),
|
||||
previous_threshold_index: Cell::new(-1),
|
||||
previous_is_intersecting: Cell::new(false),
|
||||
last_update_time: Cell::new(CrossProcessInstant::epoch()),
|
||||
previous_is_visible: Cell::new(false),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/IntersectionObserver/#parse-a-margin>
|
||||
|
@ -414,3 +823,116 @@ fn parse_a_margin(value: Option<&DOMString>) -> Result<IntersectionObserverRootM
|
|||
.parse_entirely(|p| IntersectionObserverRootMargin::parse(&context, p))
|
||||
.map_err(|_| ())
|
||||
}
|
||||
|
||||
/// <https://w3c.github.io/IntersectionObserver/#compute-the-intersection>
|
||||
fn compute_the_intersection(
|
||||
_document: &Document,
|
||||
_target: &Element,
|
||||
_root: &IntersectionRoot,
|
||||
root_bounds: Rect<Au>,
|
||||
mut intersection_rect: Rect<Au>,
|
||||
) -> Rect<Au> {
|
||||
// > 1. Let intersectionRect be the result of getting the bounding box for target.
|
||||
// We had delegated the computation of this to the caller of the function.
|
||||
|
||||
// > 2. Let container be the containing block of target.
|
||||
// > 3. While container is not root:
|
||||
// > 1. If container is the document of a nested browsing context, update intersectionRect
|
||||
// > by clipping to the viewport of the document,
|
||||
// > and update container to be the browsing context container of container.
|
||||
// > 2. Map intersectionRect to the coordinate space of container.
|
||||
// > 3. If container is a scroll container, apply the IntersectionObserver’s [[scrollMargin]]
|
||||
// > to the container’s clip rect as described in apply scroll margin to a scrollport.
|
||||
// > 4. If container has a content clip or a css clip-path property, update intersectionRect
|
||||
// > by applying container’s clip.
|
||||
// > 5. If container is the root element of a browsing context, update container to be the
|
||||
// > browsing context’s document; otherwise, update container to be the containing block
|
||||
// > of container.
|
||||
// TODO: Implement rest of step 2 and 3, which will consider transform matrix, window scroll, etc.
|
||||
|
||||
// Step 4
|
||||
// > Map intersectionRect to the coordinate space of root.
|
||||
// TODO: implement this by considering the transform matrix, window scroll, etc.
|
||||
|
||||
// Step 5
|
||||
// > Update intersectionRect by intersecting it with the root intersection rectangle.
|
||||
// Note that we also consider the edge-adjacent intersection.
|
||||
let intersection_box = intersection_rect
|
||||
.to_box2d()
|
||||
.intersection_unchecked(&root_bounds.to_box2d());
|
||||
// Although not specified, the result for non-intersecting rectangle should be zero rectangle.
|
||||
// So we should give zero rectangle immediately without modifying it.
|
||||
if intersection_box.is_negative() {
|
||||
return Rect::zero();
|
||||
}
|
||||
intersection_rect = intersection_box.to_rect();
|
||||
|
||||
// Step 6
|
||||
// > Map intersectionRect to the coordinate space of the viewport of the document containing target.
|
||||
// TODO: implement this by considering the transform matrix, window scroll, etc.
|
||||
|
||||
// Step 7
|
||||
// > Return intersectionRect.
|
||||
intersection_rect
|
||||
}
|
||||
|
||||
/// The values from computing step 2.2.4-2.2.14 in
|
||||
/// <https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo>.
|
||||
/// See [`IntersectionObserver::maybe_compute_intersection_output`].
|
||||
struct IntersectionObservationOutput {
|
||||
pub(crate) threshold_index: i32,
|
||||
pub(crate) is_intersecting: bool,
|
||||
pub(crate) target_rect: Rect<Au>,
|
||||
pub(crate) intersection_rect: Rect<Au>,
|
||||
pub(crate) intersection_ratio: f64,
|
||||
pub(crate) is_visible: bool,
|
||||
|
||||
/// The root intersection rectangle [`IntersectionObserver::root_intersection_rectangle`].
|
||||
/// If the processing is skipped, computation should report the default zero value.
|
||||
pub(crate) root_bounds: Rect<Au>,
|
||||
}
|
||||
|
||||
impl IntersectionObservationOutput {
|
||||
/// Default values according to
|
||||
/// <https://w3c.github.io/IntersectionObserver/#update-intersection-observations-algo>.
|
||||
/// Step 4.
|
||||
/// > Let:
|
||||
/// > - thresholdIndex be 0.
|
||||
/// > - isIntersecting be false.
|
||||
/// > - targetRect be a DOMRectReadOnly with x, y, width, and height set to 0.
|
||||
/// > - intersectionRect be a DOMRectReadOnly with x, y, width, and height set to 0.
|
||||
///
|
||||
/// For fields that the default values is not directly mentioned, the values conformant
|
||||
/// to current browser implementation or WPT test is used instead.
|
||||
fn default_skipped() -> Self {
|
||||
Self {
|
||||
threshold_index: 0,
|
||||
is_intersecting: false,
|
||||
target_rect: Rect::zero(),
|
||||
intersection_rect: Rect::zero(),
|
||||
intersection_ratio: 0.,
|
||||
is_visible: false,
|
||||
root_bounds: Rect::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn new_computed(
|
||||
threshold_index: i32,
|
||||
is_intersecting: bool,
|
||||
target_rect: Rect<Au>,
|
||||
intersection_rect: Rect<Au>,
|
||||
intersection_ratio: f64,
|
||||
is_visible: bool,
|
||||
root_bounds: Rect<Au>,
|
||||
) -> Self {
|
||||
Self {
|
||||
threshold_index,
|
||||
is_intersecting,
|
||||
target_rect,
|
||||
intersection_rect,
|
||||
intersection_ratio,
|
||||
is_visible,
|
||||
root_bounds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,8 @@
|
|||
* 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::cell::Cell;
|
||||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::HandleObject;
|
||||
|
||||
|
@ -9,7 +11,8 @@ use super::bindings::codegen::Bindings::IntersectionObserverEntryBinding::{
|
|||
IntersectionObserverEntryInit, IntersectionObserverEntryMethods,
|
||||
};
|
||||
use super::bindings::num::Finite;
|
||||
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
|
||||
use crate::dom::bindings::codegen::Bindings::DOMRectReadOnlyBinding::DOMRectInit;
|
||||
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
|
||||
use crate::dom::bindings::root::{Dom, DomRoot};
|
||||
use crate::dom::domrectreadonly::DOMRectReadOnly;
|
||||
use crate::dom::element::Element;
|
||||
|
@ -22,24 +25,100 @@ use crate::script_runtime::CanGc;
|
|||
#[dom_struct]
|
||||
pub(crate) struct IntersectionObserverEntry {
|
||||
reflector_: Reflector,
|
||||
// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-time>
|
||||
time: Cell<Finite<f64>>,
|
||||
// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-rootbounds>
|
||||
root_bounds: Option<Dom<DOMRectReadOnly>>,
|
||||
// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-boundingclientrect>
|
||||
bounding_client_rect: Dom<DOMRectReadOnly>,
|
||||
// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-intersectionrect>
|
||||
intersection_rect: Dom<DOMRectReadOnly>,
|
||||
// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-isintersecting>
|
||||
is_intersecting: Cell<bool>,
|
||||
// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-isvisible>
|
||||
is_visible: Cell<bool>,
|
||||
// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-intersectionratio>
|
||||
intersection_ratio: Cell<Finite<f64>>,
|
||||
// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-target>
|
||||
target: Dom<Element>,
|
||||
}
|
||||
|
||||
impl IntersectionObserverEntry {
|
||||
pub(crate) fn new_inherited(init: &IntersectionObserverEntryInit) -> Self {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new_inherited(
|
||||
time: Finite<f64>,
|
||||
root_bounds: Option<&DOMRectReadOnly>,
|
||||
bounding_client_rect: &DOMRectReadOnly,
|
||||
intersection_rect: &DOMRectReadOnly,
|
||||
is_intersecting: bool,
|
||||
is_visible: bool,
|
||||
intersection_ratio: Finite<f64>,
|
||||
target: &Element,
|
||||
) -> Self {
|
||||
Self {
|
||||
reflector_: Reflector::new(),
|
||||
target: init.target.as_traced(),
|
||||
target: Dom::from_ref(target),
|
||||
time: Cell::new(time),
|
||||
root_bounds: root_bounds.map(Dom::from_ref),
|
||||
bounding_client_rect: Dom::from_ref(bounding_client_rect),
|
||||
intersection_rect: Dom::from_ref(intersection_rect),
|
||||
is_intersecting: Cell::new(is_intersecting),
|
||||
is_visible: Cell::new(is_visible),
|
||||
intersection_ratio: Cell::new(intersection_ratio),
|
||||
}
|
||||
}
|
||||
|
||||
fn new(
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn new(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
time: Finite<f64>,
|
||||
root_bounds: Option<&DOMRectReadOnly>,
|
||||
bounding_client_rect: &DOMRectReadOnly,
|
||||
intersection_rect: &DOMRectReadOnly,
|
||||
is_intersecting: bool,
|
||||
is_visible: bool,
|
||||
intersection_ratio: Finite<f64>,
|
||||
target: &Element,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<Self> {
|
||||
let observer = Box::new(Self::new_inherited(
|
||||
time,
|
||||
root_bounds,
|
||||
bounding_client_rect,
|
||||
intersection_rect,
|
||||
is_intersecting,
|
||||
is_visible,
|
||||
intersection_ratio,
|
||||
target,
|
||||
));
|
||||
reflect_dom_object_with_proto(observer, window, proto, can_gc)
|
||||
}
|
||||
|
||||
fn new_from_dictionary(
|
||||
window: &Window,
|
||||
proto: Option<HandleObject>,
|
||||
init: &IntersectionObserverEntryInit,
|
||||
can_gc: CanGc,
|
||||
) -> DomRoot<Self> {
|
||||
let observer = Box::new(Self::new_inherited(init));
|
||||
let domrectreadonly_from_dictionary = |dictionary: &DOMRectInit| {
|
||||
DOMRectReadOnly::new_from_dictionary(
|
||||
window.as_global_scope(),
|
||||
proto,
|
||||
dictionary,
|
||||
can_gc,
|
||||
)
|
||||
};
|
||||
let observer = Box::new(Self::new_inherited(
|
||||
init.time,
|
||||
Some(&*domrectreadonly_from_dictionary(&init.rootBounds)),
|
||||
&domrectreadonly_from_dictionary(&init.boundingClientRect),
|
||||
&domrectreadonly_from_dictionary(&init.intersectionRect),
|
||||
init.isIntersecting,
|
||||
init.isVisible,
|
||||
init.intersectionRatio,
|
||||
&init.target,
|
||||
));
|
||||
reflect_dom_object_with_proto(observer, window, proto, can_gc)
|
||||
}
|
||||
}
|
||||
|
@ -51,7 +130,7 @@ impl IntersectionObserverEntryMethods<crate::DomTypeHolder> for IntersectionObse
|
|||
///
|
||||
/// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-time>
|
||||
fn Time(&self) -> Finite<f64> {
|
||||
Finite::new(0.).unwrap()
|
||||
self.time.get()
|
||||
}
|
||||
|
||||
/// > For a same-origin-domain target, this will be the root intersection rectangle.
|
||||
|
@ -61,14 +140,14 @@ impl IntersectionObserverEntryMethods<crate::DomTypeHolder> for IntersectionObse
|
|||
///
|
||||
/// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-rootbounds>
|
||||
fn GetRootBounds(&self) -> Option<DomRoot<DOMRectReadOnly>> {
|
||||
None
|
||||
self.root_bounds.as_ref().map(|rect| rect.as_rooted())
|
||||
}
|
||||
|
||||
/// > A DOMRectReadOnly obtained by getting the bounding box for target.
|
||||
///
|
||||
/// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-boundingclientrect>
|
||||
fn BoundingClientRect(&self) -> DomRoot<DOMRectReadOnly> {
|
||||
DOMRectReadOnly::new(&self.global(), None, 0., 0., 0., 0., CanGc::note())
|
||||
self.bounding_client_rect.as_rooted()
|
||||
}
|
||||
|
||||
/// > boundingClientRect, intersected by each of target's ancestors' clip rects (up to
|
||||
|
@ -78,7 +157,7 @@ impl IntersectionObserverEntryMethods<crate::DomTypeHolder> for IntersectionObse
|
|||
///
|
||||
/// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-intersectionrect>
|
||||
fn IntersectionRect(&self) -> DomRoot<DOMRectReadOnly> {
|
||||
DOMRectReadOnly::new(&self.global(), None, 0., 0., 0., 0., CanGc::note())
|
||||
self.intersection_rect.as_rooted()
|
||||
}
|
||||
|
||||
/// > True if the target intersects with the root; false otherwise. This flag makes it
|
||||
|
@ -90,14 +169,14 @@ impl IntersectionObserverEntryMethods<crate::DomTypeHolder> for IntersectionObse
|
|||
///
|
||||
/// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-isintersecting>
|
||||
fn IsIntersecting(&self) -> bool {
|
||||
false
|
||||
self.is_intersecting.get()
|
||||
}
|
||||
|
||||
/// > Contains the result of running the visibility algorithm on target.
|
||||
///
|
||||
/// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-isvisible>
|
||||
fn IsVisible(&self) -> bool {
|
||||
false
|
||||
self.is_visible.get()
|
||||
}
|
||||
|
||||
/// > If the boundingClientRect has non-zero area, this will be the ratio of
|
||||
|
@ -106,7 +185,7 @@ impl IntersectionObserverEntryMethods<crate::DomTypeHolder> for IntersectionObse
|
|||
///
|
||||
/// <https://w3c.github.io/IntersectionObserver/#dom-intersectionobserverentry-intersectionratio>
|
||||
fn IntersectionRatio(&self) -> Finite<f64> {
|
||||
Finite::new(0.).unwrap()
|
||||
self.intersection_ratio.get()
|
||||
}
|
||||
|
||||
/// > The Element whose intersection with the intersection root changed.
|
||||
|
@ -123,6 +202,6 @@ impl IntersectionObserverEntryMethods<crate::DomTypeHolder> for IntersectionObse
|
|||
can_gc: CanGc,
|
||||
init: &IntersectionObserverEntryInit,
|
||||
) -> DomRoot<IntersectionObserverEntry> {
|
||||
Self::new(window, proto, init, can_gc)
|
||||
Self::new_from_dictionary(window, proto, init, can_gc)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,14 +3,16 @@
|
|||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
//! Copy of Stylo Gecko's [`style::values::specified::gecko::IntersectionObserverRootMargin`] implementation.
|
||||
//! TODO: expose the object to servo as well in Stylo
|
||||
//! TODO(#35907): make a thin wrapper and remove copied codes
|
||||
|
||||
use std::fmt;
|
||||
|
||||
use app_units::Au;
|
||||
use cssparser::{Parser, Token, match_ignore_ascii_case};
|
||||
use euclid::default::{Rect, SideOffsets2D};
|
||||
use style::parser::{Parse, ParserContext};
|
||||
use style::values::computed::{self, Length, LengthPercentage};
|
||||
use style::values::generics::rect::Rect;
|
||||
use style::values::generics::rect::Rect as StyleRect;
|
||||
use style_traits::values::SequenceWriter;
|
||||
use style_traits::{CssWriter, ParseError, StyleParseErrorKind, ToCss};
|
||||
|
||||
|
@ -44,7 +46,7 @@ fn parse_pixel_or_percent<'i>(
|
|||
///
|
||||
/// <https://w3c.github.io/IntersectionObserver/#parse-a-root-margin>
|
||||
#[repr(transparent)]
|
||||
pub struct IntersectionObserverRootMargin(pub Rect<LengthPercentage>);
|
||||
pub struct IntersectionObserverRootMargin(pub StyleRect<LengthPercentage>);
|
||||
|
||||
impl Parse for IntersectionObserverRootMargin {
|
||||
fn parse<'i>(
|
||||
|
@ -54,11 +56,11 @@ impl Parse for IntersectionObserverRootMargin {
|
|||
use style::Zero;
|
||||
if input.is_exhausted() {
|
||||
// If there are zero elements in tokens, set tokens to ["0px"].
|
||||
return Ok(IntersectionObserverRootMargin(Rect::all(
|
||||
return Ok(IntersectionObserverRootMargin(StyleRect::all(
|
||||
LengthPercentage::zero(),
|
||||
)));
|
||||
}
|
||||
let rect = Rect::parse_with(context, input, parse_pixel_or_percent)?;
|
||||
let rect = StyleRect::parse_with(context, input, parse_pixel_or_percent)?;
|
||||
Ok(IntersectionObserverRootMargin(rect))
|
||||
}
|
||||
}
|
||||
|
@ -82,3 +84,20 @@ impl ToCss for IntersectionObserverRootMargin {
|
|||
writer.item(&rect.3)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(stevennovaryo): move this to the wrapper later
|
||||
impl IntersectionObserverRootMargin {
|
||||
// Resolve to used values.
|
||||
pub(crate) fn resolve_percentages_with_basis(
|
||||
&self,
|
||||
containing_block: Rect<Au>,
|
||||
) -> SideOffsets2D<Au> {
|
||||
let inner = &self.0;
|
||||
SideOffsets2D::new(
|
||||
inner.0.to_used_value(containing_block.height()),
|
||||
inner.1.to_used_value(containing_block.width()),
|
||||
inner.2.to_used_value(containing_block.height()),
|
||||
inner.3.to_used_value(containing_block.width()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -869,6 +869,10 @@ impl Node {
|
|||
self.bounding_content_box(can_gc).unwrap_or_else(Rect::zero)
|
||||
}
|
||||
|
||||
pub(crate) fn bounding_content_box_no_reflow(&self) -> Option<Rect<Au>> {
|
||||
self.owner_window().content_box_query_unchecked(self)
|
||||
}
|
||||
|
||||
pub(crate) fn content_boxes(&self, can_gc: CanGc) -> Vec<Rect<Au>> {
|
||||
self.owner_window().content_boxes_query(self, can_gc)
|
||||
}
|
||||
|
|
|
@ -509,6 +509,13 @@ impl Window {
|
|||
})
|
||||
}
|
||||
|
||||
/// Returns the window proxy of the webview, which is the top-level ancestor browsing context.
|
||||
/// <https://html.spec.whatwg.org/multipage/#top-level-browsing-context>
|
||||
pub(crate) fn webview_window_proxy(&self) -> Option<DomRoot<WindowProxy>> {
|
||||
self.undiscarded_window_proxy()
|
||||
.and_then(|window_proxy| ScriptThread::find_window_proxy(window_proxy.webview_id().0))
|
||||
}
|
||||
|
||||
#[cfg(feature = "bluetooth")]
|
||||
pub(crate) fn bluetooth_thread(&self) -> IpcSender<BluetoothRequest> {
|
||||
self.bluetooth_thread.clone()
|
||||
|
@ -2214,11 +2221,16 @@ impl Window {
|
|||
)
|
||||
}
|
||||
|
||||
// Query content box without considering any reflow
|
||||
pub(crate) fn content_box_query_unchecked(&self, node: &Node) -> Option<UntypedRect<Au>> {
|
||||
self.layout.borrow().query_content_box(node.to_opaque())
|
||||
}
|
||||
|
||||
pub(crate) fn content_box_query(&self, node: &Node, can_gc: CanGc) -> Option<UntypedRect<Au>> {
|
||||
if !self.layout_reflow(QueryMsg::ContentBox, can_gc) {
|
||||
return None;
|
||||
}
|
||||
self.layout.borrow().query_content_box(node.to_opaque())
|
||||
self.content_box_query_unchecked(node)
|
||||
}
|
||||
|
||||
pub(crate) fn content_boxes_query(&self, node: &Node, can_gc: CanGc) -> Vec<UntypedRect<Au>> {
|
||||
|
|
|
@ -1162,6 +1162,10 @@ impl ScriptThread {
|
|||
// as well as those for which a rendering update would be unnecessary,
|
||||
// but this isn't happening here.
|
||||
|
||||
// TODO(#31242): the filtering of docs is extended to not exclude the ones that
|
||||
// has pending initial observation targets
|
||||
// https://w3c.github.io/IntersectionObserver/#pending-initial-observation
|
||||
|
||||
// If we aren't explicitly running rAFs, this update wasn't requested by the compositor,
|
||||
// and we are running animations, then wait until the compositor tells us it is time to
|
||||
// update the rendering via a TickAllAnimations message.
|
||||
|
@ -1259,8 +1263,11 @@ impl ScriptThread {
|
|||
// TODO: Perform pending transition operations from
|
||||
// https://drafts.csswg.org/css-view-transitions/#perform-pending-transition-operations.
|
||||
|
||||
// TODO(#31021): Run the update intersection observations steps from
|
||||
// https://w3c.github.io/IntersectionObserver/#run-the-update-intersection-observations-steps
|
||||
// > 19. For each doc of docs, run the update intersection observations steps for doc,
|
||||
// > passing in the relative high resolution time given now and
|
||||
// > doc's relevant global object as the timestamp. [INTERSECTIONOBSERVER]
|
||||
// TODO(stevennovaryo): The time attribute should be relative to the time origin of the global object
|
||||
document.update_intersection_observer_steps(CrossProcessInstant::now(), can_gc);
|
||||
|
||||
// TODO: Mark paint timing from https://w3c.github.io/paint-timing.
|
||||
|
||||
|
|
|
@ -146,4 +146,9 @@ impl TaskManager {
|
|||
task_source_functions!(self, timer_task_source, Timer);
|
||||
task_source_functions!(self, user_interaction_task_source, UserInteraction);
|
||||
task_source_functions!(self, websocket_task_source, WebSocket);
|
||||
task_source_functions!(
|
||||
self,
|
||||
intersection_observer_task_source,
|
||||
IntersectionObserver
|
||||
);
|
||||
}
|
||||
|
|
|
@ -40,6 +40,8 @@ pub(crate) enum TaskSourceName {
|
|||
Timer,
|
||||
/// <https://www.w3.org/TR/gamepad/#dfn-gamepad-task-source>
|
||||
Gamepad,
|
||||
/// <https://w3c.github.io/IntersectionObserver/#intersectionobserver-task-source>
|
||||
IntersectionObserver,
|
||||
}
|
||||
|
||||
impl From<TaskSourceName> for ScriptThreadEventCategory {
|
||||
|
@ -62,6 +64,7 @@ impl From<TaskSourceName> for ScriptThreadEventCategory {
|
|||
TaskSourceName::WebSocket => ScriptThreadEventCategory::WebSocketEvent,
|
||||
TaskSourceName::Timer => ScriptThreadEventCategory::TimerEvent,
|
||||
TaskSourceName::Gamepad => ScriptThreadEventCategory::InputEvent,
|
||||
TaskSourceName::IntersectionObserver => ScriptThreadEventCategory::ScriptEvent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
[overflow-clip-margin-intersection-observer.html]
|
||||
expected: TIMEOUT
|
||||
[ParentWithOverflowClipMargin]
|
||||
expected: NOTRUN
|
||||
expected: TIMEOUT
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[callback-cross-realm-report-exception.html]
|
||||
[IntersectionObserver reports the exception from its callback in the callback's global object]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[client-rect.html]
|
||||
[First rAF should generate notification.]
|
||||
expected: FAIL
|
|
@ -1,12 +1,9 @@
|
|||
[containing-block.html]
|
||||
[Not in containing block and not intersecting.]
|
||||
expected: FAIL
|
||||
|
||||
[In containing block and intersecting.]
|
||||
expected: FAIL
|
||||
|
||||
[In containing block and not intersecting.]
|
||||
expected: FAIL
|
||||
|
||||
[Not in containing block and intersecting.]
|
||||
expected: FAIL
|
||||
|
||||
[Not in containing block and not intersecting.]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[cross-document-root.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
|
@ -1,6 +1,3 @@
|
|||
[disconnect.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
||||
|
||||
[observer.disconnect()]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[display-none.html]
|
||||
[display-none]
|
||||
expected: FAIL
|
|
@ -1,4 +0,0 @@
|
|||
[empty-root-margin.html]
|
||||
expected: TIMEOUT
|
||||
[An empty rootMargin string is interpreted as a margin of size zero]
|
||||
expected: TIMEOUT
|
|
@ -1,4 +0,0 @@
|
|||
[explicit-root-different-document.html]
|
||||
expected: TIMEOUT
|
||||
[IntersectionObserver reports a (non-intersecting) entry if different-document from the doc]
|
||||
expected: NOTRUN
|
|
@ -1,6 +0,0 @@
|
|||
[fixed-position-child-scroll.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
||||
|
||||
[scrollTo(0, 1000)]
|
||||
expected: FAIL
|
|
@ -1,6 +0,0 @@
|
|||
[fixed-position-iframe-scroll.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
||||
|
||||
[scrollTo(0, 1000)]
|
||||
expected: FAIL
|
|
@ -1,6 +0,0 @@
|
|||
[fixed-position-scroll.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
||||
|
||||
[scrollTo(0, 1000)]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[grow-height-and-scrolled.html]
|
||||
[IntersectionObserver should only report intersection change after the target grows height and is scrolled.]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[inline-with-block-child-client-rect.html]
|
||||
[First rAF]
|
||||
expected: FAIL
|
|
@ -1,4 +1,3 @@
|
|||
[intersection-ratio-ib-split.html]
|
||||
expected: TIMEOUT
|
||||
[IntersectionObserver on an IB split gets the right intersection ratio]
|
||||
expected: TIMEOUT
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
[intersection-ratio-with-fractional-bounds-2.html]
|
||||
expected: TIMEOUT
|
||||
[IntersectionObserver ratio with fractional bounds]
|
||||
expected: TIMEOUT
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[intersection-ratio-with-fractional-bounds-in-iframe.html]
|
||||
expected: TIMEOUT
|
||||
[intersectionRatio in iframe should be 1 for totally visible target with fractional bounds]
|
||||
expected: NOTRUN
|
|
@ -1,4 +0,0 @@
|
|||
[intersection-ratio-with-fractional-bounds.html]
|
||||
expected: TIMEOUT
|
||||
[IntersectionObserver ratio with fractional bounds]
|
||||
expected: TIMEOUT
|
|
@ -1,3 +1,6 @@
|
|||
[isIntersecting-change-events.html]
|
||||
[Rects in initial notifications should report initial positions.]
|
||||
[Set scrollTop=100 and check for one new notification.]
|
||||
expected: FAIL
|
||||
|
||||
[Add 4th target.]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
[isIntersecting-threshold.html]
|
||||
[At initial scroll position]
|
||||
expected: FAIL
|
||||
|
||||
[Scrolled to half way through target element]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[not-in-containing-block-chain.html]
|
||||
expected: TIMEOUT
|
||||
[IntersectionObserver reports a (non-intersecting) entry even if not in the containing block chain]
|
||||
expected: NOTRUN
|
|
@ -1,4 +0,0 @@
|
|||
[observer-callback-arguments.html]
|
||||
expected: TIMEOUT
|
||||
[Callback is invoked with |this| value of IntersectionObserver and two arguments]
|
||||
expected: TIMEOUT
|
|
@ -1,4 +0,0 @@
|
|||
[padding-clip.html]
|
||||
expected: TIMEOUT
|
||||
[Scrollport is used rather than content rect to compute intersection ratio]
|
||||
expected: TIMEOUT
|
|
@ -8,8 +8,5 @@
|
|||
[root.removeChild(target).]
|
||||
expected: FAIL
|
||||
|
||||
[root.insertBefore(target, trailingSpace).]
|
||||
expected: FAIL
|
||||
|
||||
[root.scrollTop = 150 after reinserting target.]
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[root-margin-rounding.html]
|
||||
expected: TIMEOUT
|
||||
[IntersectionObserver root margin cannot end up with negative rect (and thus non-intersecting) due to rounding]
|
||||
expected: NOTRUN
|
|
@ -1,3 +0,0 @@
|
|||
[rtl-clipped-root.html]
|
||||
[First rAF]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[scroll-margin-non-scrolling-root.html]
|
||||
[Test scroll margin intersection]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[target-in-detached-document.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[target-in-different-window.html]
|
||||
[IntersectionObserver with target in a different window.]
|
||||
expected: FAIL
|
|
@ -1,4 +1,3 @@
|
|||
[target-is-root.html]
|
||||
expected: TIMEOUT
|
||||
[IntersectionObserver when root == target doesn't compute an intersection]
|
||||
expected: TIMEOUT
|
||||
expected: FAIL
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[animated-occlusion.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
|
@ -1,7 +1,4 @@
|
|||
[box-shadow.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
||||
|
||||
[occluder.style.boxShadow = "none"]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
[position-absolute-overflow-visible-and-not-visible.html]
|
||||
expected: TIMEOUT
|
||||
[ParentWithOverflowVisibleAndNotVisible]
|
||||
expected: NOTRUN
|
|
@ -1,7 +1,4 @@
|
|||
[text-shadow.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
||||
|
||||
[occluder.style.textShadow = "none"]
|
||||
expected: FAIL
|
||||
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
[zero-area-element-hidden.html]
|
||||
[First rAF.]
|
||||
expected: FAIL
|
|
@ -1,3 +0,0 @@
|
|||
[zero-area-element-visible.html]
|
||||
[First rAF should generate a notification.]
|
||||
expected: FAIL
|
|
@ -1,4 +0,0 @@
|
|||
[ordering.html]
|
||||
expected: TIMEOUT
|
||||
[ResizeObserver and IntersectionObserver ordering]
|
||||
expected: TIMEOUT
|
Loading…
Add table
Add a link
Reference in a new issue