Skip to main content

script/dom/performance/
performanceobserver.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::{Cell, RefMut};
6
7use dom_struct::dom_struct;
8use js::context::JSContext;
9use js::rust::{HandleObject, MutableHandleValue};
10use script_bindings::callback::{OwnerWindow, RootedCallback, TracedCallback};
11use script_bindings::cell::DomRefCell;
12use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
13
14use super::performanceentry::{EntryType, PerformanceEntry};
15use super::performanceobserverentrylist::PerformanceObserverEntryList;
16use crate::dom::bindings::callback::ExceptionHandling;
17use crate::dom::bindings::codegen::Bindings::PerformanceObserverBinding::{
18    PerformanceObserverCallback, PerformanceObserverInit, PerformanceObserverMethods,
19};
20use crate::dom::bindings::error::{Error, Fallible};
21use crate::dom::bindings::reflector::DomGlobal;
22use crate::dom::bindings::root::{Dom, DomRoot};
23use crate::dom::console::Console;
24use crate::dom::globalscope::GlobalScope;
25
26#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
27enum ObserverType {
28    Undefined,
29    Single,
30    Multiple,
31}
32
33#[dom_struct]
34pub(crate) struct PerformanceObserver {
35    reflector_: Reflector,
36    callback: TracedCallback<PerformanceObserverCallback>,
37    entries: DomRefCell<Vec<Dom<PerformanceEntry>>>,
38    observer_type: Cell<ObserverType>,
39}
40
41impl PerformanceObserver {
42    fn new_inherited(callback: RootedCallback<PerformanceObserverCallback>) -> PerformanceObserver {
43        PerformanceObserver {
44            reflector_: Reflector::new(),
45            callback: callback.to_traced(),
46            entries: Default::default(),
47            observer_type: Cell::new(ObserverType::Undefined),
48        }
49    }
50
51    fn new_with_proto(
52        cx: &mut JSContext,
53        global: &GlobalScope,
54        proto: Option<HandleObject>,
55        callback: RootedCallback<PerformanceObserverCallback>,
56    ) -> DomRoot<PerformanceObserver> {
57        reflect_dom_object_with_proto(
58            cx,
59            Box::new(PerformanceObserver::new_inherited(callback)),
60            global,
61            proto,
62        )
63    }
64
65    /// Buffer a new performance entry.
66    pub(crate) fn queue_entry(&self, entry: &PerformanceEntry) {
67        self.entries.borrow_mut().push(Dom::from_ref(entry));
68    }
69
70    /// Trigger performance observer callback with the list of performance entries
71    /// buffered since the last callback call.
72    pub(crate) fn notify(&self, cx: &mut JSContext) {
73        if self.entries.borrow().is_empty() {
74            return;
75        }
76        let entries = self
77            .entries
78            .borrow_mut()
79            .drain(..)
80            .map(|entry| entry.as_rooted())
81            .collect();
82        let observer_entry_list = PerformanceObserverEntryList::new(cx, &self.global(), entries);
83        // using self both as thisArg and as the second formal argument
84        let _ = self.callback.Call_(
85            cx,
86            self,
87            &observer_entry_list,
88            self,
89            ExceptionHandling::Report,
90        );
91    }
92
93    pub(crate) fn entries_mut(&self) -> RefMut<'_, Vec<Dom<PerformanceEntry>>> {
94        self.entries.borrow_mut()
95    }
96}
97
98impl PerformanceObserverMethods<crate::DomTypeHolder> for PerformanceObserver {
99    /// <https://w3c.github.io/performance-timeline/#dom-performanceobserver-constructor>
100    fn Constructor(
101        cx: &mut js::context::JSContext,
102        global: &GlobalScope,
103        proto: Option<HandleObject>,
104        callback: RootedCallback<PerformanceObserverCallback>,
105    ) -> Fallible<DomRoot<PerformanceObserver>> {
106        Ok(PerformanceObserver::new_with_proto(
107            cx, global, proto, callback,
108        ))
109    }
110
111    /// <https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute>
112    fn SupportedEntryTypes(cx: &mut JSContext, global: &GlobalScope, retval: MutableHandleValue) {
113        // While this is exposed through a method of PerformanceObserver,
114        // it is specified as associated with the global scope.
115        global.supported_performance_entry_types(cx, retval)
116    }
117
118    /// <https://w3c.github.io/performance-timeline/#dom-performanceobserver-observe()>
119    fn Observe(&self, cx: &mut JSContext, options: &PerformanceObserverInit) -> Fallible<()> {
120        // Step 1 is self
121
122        // Step 2 is self.global()
123
124        // Step 3
125        if options.entryTypes.is_none() && options.type_.is_none() {
126            return Err(Error::Syntax(None));
127        }
128
129        // Step 4
130        if options.entryTypes.is_some() && (options.buffered.is_some() || options.type_.is_some()) {
131            return Err(Error::Syntax(None));
132        }
133
134        // If this point is reached, then one of options.entryTypes or options.type_
135        // is_some, but not both.
136
137        // Step 5
138        match self.observer_type.get() {
139            ObserverType::Undefined => {
140                if options.entryTypes.is_some() {
141                    self.observer_type.set(ObserverType::Multiple);
142                } else {
143                    self.observer_type.set(ObserverType::Single);
144                }
145            },
146            ObserverType::Single => {
147                if options.entryTypes.is_some() {
148                    return Err(Error::InvalidModification(None));
149                }
150            },
151            ObserverType::Multiple => {
152                if options.type_.is_some() {
153                    return Err(Error::InvalidModification(None));
154                }
155            },
156        }
157
158        // The entryTypes and type paths diverge here
159        const NO_VALID_ENTRY_TYPE: &str = "No valid entry type provided to observe().";
160        if let Some(entry_types) = &options.entryTypes {
161            // Steps 6.1 - 6.2
162            let entry_types = entry_types
163                .iter()
164                .filter_map(|e| EntryType::try_from(&*e.str()).ok())
165                .collect::<Vec<EntryType>>();
166
167            // Step 6.3
168            if entry_types.is_empty() {
169                Console::internal_warn(cx, &self.global(), NO_VALID_ENTRY_TYPE.to_string());
170                return Ok(());
171            }
172
173            // Steps 6.4-6.5
174            // This never pre-fills buffered entries, and
175            // any existing types are replaced.
176            self.global()
177                .performance(cx)
178                .add_multiple_type_observer(self, entry_types);
179            Ok(())
180        } else if let Some(entry_type) = &options.type_ {
181            // Step 7.2
182            let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
183                Console::internal_warn(cx, &self.global(), NO_VALID_ENTRY_TYPE.to_string());
184                return Ok(());
185            };
186
187            // Steps 7.3-7.5
188            // This may pre-fill buffered entries, and
189            // existing types are appended to.
190            self.global().performance(cx).add_single_type_observer(
191                self,
192                entry_type,
193                options.buffered.unwrap_or(false),
194            );
195            Ok(())
196        } else {
197            // Step 7.1
198            unreachable!()
199        }
200    }
201
202    /// <https://w3c.github.io/performance-timeline/#dom-performanceobserver-disconnect>
203    fn Disconnect(&self, cx: &mut JSContext) {
204        self.global().performance(cx).remove_observer(self);
205        self.entries.borrow_mut().clear();
206    }
207
208    /// <https://w3c.github.io/performance-timeline/#takerecords-method>
209    fn TakeRecords(&self) -> Vec<DomRoot<PerformanceEntry>> {
210        let mut entries = self.entries.borrow_mut();
211        let taken = entries.iter().map(|entry| entry.as_rooted()).collect();
212        entries.clear();
213        taken
214    }
215}
216
217impl OwnerWindow<crate::DomTypeHolder> for PerformanceObserver {}