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