diff options
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/document.rs | 112 | ||||
-rw-r--r-- | components/script/dom/domrectreadonly.rs | 14 | ||||
-rw-r--r-- | components/script/dom/element.rs | 23 | ||||
-rw-r--r-- | components/script/dom/intersectionobserver.rs | 568 | ||||
-rw-r--r-- | components/script/dom/intersectionobserverentry.rs | 105 | ||||
-rw-r--r-- | components/script/dom/intersectionobserverrootmargin.rs | 29 | ||||
-rw-r--r-- | components/script/dom/node.rs | 4 | ||||
-rw-r--r-- | components/script/dom/window.rs | 14 | ||||
-rw-r--r-- | components/script/script_thread.rs | 11 | ||||
-rw-r--r-- | components/script/task_manager.rs | 5 | ||||
-rw-r--r-- | components/script/task_source.rs | 3 |
11 files changed, 839 insertions, 49 deletions
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index c089dfbe951..cf82cd7676d 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -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(), } } diff --git a/components/script/dom/domrectreadonly.rs b/components/script/dom/domrectreadonly.rs index d50045b8bfd..1034c049df7 100644 --- a/components/script/dom/domrectreadonly.rs +++ b/components/script/dom/domrectreadonly.rs @@ -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); } diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index b85db3bd255..e410e73bb56 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -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, + 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, - registration: IntersectionObserverRegistration, + observer: &IntersectionObserver, ) { self.ensure_rare_data() .registered_intersection_observers - .push(registration); + .push(IntersectionObserverRegistration::new_initial(observer)); } /// Removes a certain IntersectionObserver. diff --git a/components/script/dom/intersectionobserver.rs b/components/script/dom/intersectionobserver.rs index c8a761a627c..95ede7780f9 100644 --- a/components/script/dom/intersectionobserver.rs +++ b/components/script/dom/intersectionobserver.rs @@ -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, + } + } +} diff --git a/components/script/dom/intersectionobserverentry.rs b/components/script/dom/intersectionobserverentry.rs index 759b950cb50..7bd46e4cac2 100644 --- a/components/script/dom/intersectionobserverentry.rs +++ b/components/script/dom/intersectionobserverentry.rs @@ -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) } } diff --git a/components/script/dom/intersectionobserverrootmargin.rs b/components/script/dom/intersectionobserverrootmargin.rs index 4f5decdcfd6..267edd40bad 100644 --- a/components/script/dom/intersectionobserverrootmargin.rs +++ b/components/script/dom/intersectionobserverrootmargin.rs @@ -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()), + ) + } +} diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index ca1a1b21123..e9bd8df07fa 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -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) } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index 6689cd87d1a..aac4c7d3a73 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -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>> { diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 234183d1e08..ef9be18b691 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -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. diff --git a/components/script/task_manager.rs b/components/script/task_manager.rs index 75a9bb8330e..2d4463e0c38 100644 --- a/components/script/task_manager.rs +++ b/components/script/task_manager.rs @@ -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 + ); } diff --git a/components/script/task_source.rs b/components/script/task_source.rs index 4f18292693e..4bb6a5c110c 100644 --- a/components/script/task_source.rs +++ b/components/script/task_source.rs @@ -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, } } } |