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