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