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::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::bindings::str::DOMString;
24use crate::dom::console::Console;
25use crate::dom::globalscope::GlobalScope;
26use crate::script_runtime::{CanGc, JSContext};
27
28/// List of allowed performance entry types, in alphabetical order.
29pub(crate) const VALID_ENTRY_TYPES: &[&str] = &[
30    // "frame", // TODO Frame Timing API
31    "largest-contentful-paint", // Largest Contentful Paint API
32    "mark",                     // User Timing API
33    "measure",                  // User Timing API
34    "navigation",               // Navigation Timing API
35    "paint",                    // Paint Timing API
36    "resource",                 // Resource Timing API
37                                // "server", XXX Server Timing API
38];
39
40#[derive(Clone, Copy, JSTraceable, MallocSizeOf, PartialEq)]
41enum ObserverType {
42    Undefined,
43    Single,
44    Multiple,
45}
46
47#[dom_struct]
48pub(crate) struct PerformanceObserver {
49    reflector_: Reflector,
50    #[ignore_malloc_size_of = "can't measure Rc values"]
51    callback: Rc<PerformanceObserverCallback>,
52    entries: DomRefCell<DOMPerformanceEntryList>,
53    observer_type: Cell<ObserverType>,
54}
55
56impl PerformanceObserver {
57    fn new_inherited(
58        callback: Rc<PerformanceObserverCallback>,
59        entries: DomRefCell<DOMPerformanceEntryList>,
60    ) -> PerformanceObserver {
61        PerformanceObserver {
62            reflector_: Reflector::new(),
63            callback,
64            entries,
65            observer_type: Cell::new(ObserverType::Undefined),
66        }
67    }
68
69    pub(crate) fn new(
70        global: &GlobalScope,
71        callback: Rc<PerformanceObserverCallback>,
72        entries: DOMPerformanceEntryList,
73        can_gc: CanGc,
74    ) -> DomRoot<PerformanceObserver> {
75        Self::new_with_proto(global, None, callback, entries, can_gc)
76    }
77
78    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
79    fn new_with_proto(
80        global: &GlobalScope,
81        proto: Option<HandleObject>,
82        callback: Rc<PerformanceObserverCallback>,
83        entries: DOMPerformanceEntryList,
84        can_gc: CanGc,
85    ) -> DomRoot<PerformanceObserver> {
86        let observer = PerformanceObserver::new_inherited(callback, DomRefCell::new(entries));
87        reflect_dom_object_with_proto(Box::new(observer), global, proto, can_gc)
88    }
89
90    /// Buffer a new performance entry.
91    pub(crate) fn queue_entry(&self, entry: &PerformanceEntry) {
92        self.entries.borrow_mut().push(DomRoot::from_ref(entry));
93    }
94
95    /// Trigger performance observer callback with the list of performance entries
96    /// buffered since the last callback call.
97    pub(crate) fn notify(&self, can_gc: CanGc) {
98        if self.entries.borrow().is_empty() {
99            return;
100        }
101        let entry_list = PerformanceEntryList::new(self.entries.borrow_mut().drain(..).collect());
102        let observer_entry_list =
103            PerformanceObserverEntryList::new(&self.global(), entry_list, can_gc);
104        // using self both as thisArg and as the second formal argument
105        let _ = self.callback.Call_(
106            self,
107            &observer_entry_list,
108            self,
109            ExceptionHandling::Report,
110            can_gc,
111        );
112    }
113
114    pub(crate) fn callback(&self) -> Rc<PerformanceObserverCallback> {
115        self.callback.clone()
116    }
117
118    pub(crate) fn entries(&self) -> DOMPerformanceEntryList {
119        self.entries.borrow().clone()
120    }
121
122    pub(crate) fn set_entries(&self, entries: DOMPerformanceEntryList) {
123        *self.entries.borrow_mut() = entries;
124    }
125}
126
127impl PerformanceObserverMethods<crate::DomTypeHolder> for PerformanceObserver {
128    /// <https://w3c.github.io/performance-timeline/#dom-performanceobserver-constructor>
129    fn Constructor(
130        global: &GlobalScope,
131        proto: Option<HandleObject>,
132        can_gc: CanGc,
133        callback: Rc<PerformanceObserverCallback>,
134    ) -> Fallible<DomRoot<PerformanceObserver>> {
135        Ok(PerformanceObserver::new_with_proto(
136            global,
137            proto,
138            callback,
139            Vec::new(),
140            can_gc,
141        ))
142    }
143
144    /// <https://w3c.github.io/performance-timeline/#supportedentrytypes-attribute>
145    fn SupportedEntryTypes(
146        cx: JSContext,
147        global: &GlobalScope,
148        can_gc: CanGc,
149        retval: MutableHandleValue,
150    ) {
151        // While this is exposed through a method of PerformanceObserver,
152        // it is specified as associated with the global scope.
153        global.supported_performance_entry_types(cx, retval, can_gc)
154    }
155
156    /// <https://w3c.github.io/performance-timeline/#dom-performanceobserver-observe()>
157    fn Observe(&self, options: &PerformanceObserverInit) -> Fallible<()> {
158        // Step 1 is self
159
160        // Step 2 is self.global()
161
162        // Step 3
163        if options.entryTypes.is_none() && options.type_.is_none() {
164            return Err(Error::Syntax(None));
165        }
166
167        // Step 4
168        if options.entryTypes.is_some() && (options.buffered.is_some() || options.type_.is_some()) {
169            return Err(Error::Syntax(None));
170        }
171
172        // If this point is reached, then one of options.entryTypes or options.type_
173        // is_some, but not both.
174
175        // Step 5
176        match self.observer_type.get() {
177            ObserverType::Undefined => {
178                if options.entryTypes.is_some() {
179                    self.observer_type.set(ObserverType::Multiple);
180                } else {
181                    self.observer_type.set(ObserverType::Single);
182                }
183            },
184            ObserverType::Single => {
185                if options.entryTypes.is_some() {
186                    return Err(Error::InvalidModification);
187                }
188            },
189            ObserverType::Multiple => {
190                if options.type_.is_some() {
191                    return Err(Error::InvalidModification);
192                }
193            },
194        }
195
196        // The entryTypes and type paths diverge here
197        if let Some(entry_types) = &options.entryTypes {
198            // Steps 6.1 - 6.2
199            let entry_types = entry_types
200                .iter()
201                .filter(|e| VALID_ENTRY_TYPES.contains(&&*e.str()))
202                .cloned()
203                .collect::<Vec<DOMString>>();
204
205            // Step 6.3
206            if entry_types.is_empty() {
207                Console::internal_warn(
208                    &self.global(),
209                    DOMString::from("No valid entry type provided to observe()."),
210                );
211                return Ok(());
212            }
213
214            // Steps 6.4-6.5
215            // This never pre-fills buffered entries, and
216            // any existing types are replaced.
217            self.global()
218                .performance()
219                .add_multiple_type_observer(self, entry_types);
220            Ok(())
221        } else if let Some(entry_type) = &options.type_ {
222            // Step 7.2
223            if !VALID_ENTRY_TYPES.contains(&&*entry_type.str()) {
224                Console::internal_warn(
225                    &self.global(),
226                    DOMString::from("No valid entry type provided to observe()."),
227                );
228                return Ok(());
229            }
230
231            // Steps 7.3-7.5
232            // This may pre-fill buffered entries, and
233            // existing types are appended to.
234            self.global().performance().add_single_type_observer(
235                self,
236                entry_type,
237                options.buffered.unwrap_or(false),
238            );
239            Ok(())
240        } else {
241            // Step 7.1
242            unreachable!()
243        }
244    }
245
246    /// <https://w3c.github.io/performance-timeline/#dom-performanceobserver-disconnect>
247    fn Disconnect(&self) {
248        self.global().performance().remove_observer(self);
249        self.entries.borrow_mut().clear();
250    }
251
252    /// <https://w3c.github.io/performance-timeline/#takerecords-method>
253    fn TakeRecords(&self) -> Vec<DomRoot<PerformanceEntry>> {
254        let mut entries = self.entries.borrow_mut();
255        let taken = entries
256            .iter()
257            .map(|entry| DomRoot::from_ref(&**entry))
258            .collect();
259        entries.clear();
260        taken
261    }
262}