script/dom/performance/
performanceobserver.rs1use std::cell::Cell;
6use std::rc::Rc;
7
8use dom_struct::dom_struct;
9use js::context::JSContext;
10use js::rust::{HandleObject, MutableHandleValue};
11use script_bindings::cell::DomRefCell;
12use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto_and_cx};
13
14use super::performance::PerformanceEntryList;
15use super::performanceentry::{EntryType, PerformanceEntry};
16use super::performanceobserverentrylist::PerformanceObserverEntryList;
17use crate::dom::bindings::callback::ExceptionHandling;
18use crate::dom::bindings::codegen::Bindings::PerformanceBinding::PerformanceEntryList as DOMPerformanceEntryList;
19use crate::dom::bindings::codegen::Bindings::PerformanceObserverBinding::{
20 PerformanceObserverCallback, PerformanceObserverInit, PerformanceObserverMethods,
21};
22use crate::dom::bindings::error::{Error, Fallible};
23use crate::dom::bindings::reflector::DomGlobal;
24use crate::dom::bindings::root::DomRoot;
25use crate::dom::console::Console;
26use crate::dom::globalscope::GlobalScope;
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 cx: &mut JSContext,
59 global: &GlobalScope,
60 callback: Rc<PerformanceObserverCallback>,
61 entries: DOMPerformanceEntryList,
62 ) -> DomRoot<PerformanceObserver> {
63 Self::new_with_proto(cx, global, None, callback, entries)
64 }
65
66 #[cfg_attr(crown, expect(crown::unrooted_must_root))]
67 fn new_with_proto(
68 cx: &mut JSContext,
69 global: &GlobalScope,
70 proto: Option<HandleObject>,
71 callback: Rc<PerformanceObserverCallback>,
72 entries: DOMPerformanceEntryList,
73 ) -> DomRoot<PerformanceObserver> {
74 let observer = PerformanceObserver::new_inherited(callback, DomRefCell::new(entries));
75 reflect_dom_object_with_proto_and_cx(Box::new(observer), global, proto, cx)
76 }
77
78 pub(crate) fn queue_entry(&self, entry: &PerformanceEntry) {
80 self.entries.borrow_mut().push(DomRoot::from_ref(entry));
81 }
82
83 pub(crate) fn notify(&self, cx: &mut 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 = PerformanceObserverEntryList::new(cx, &self.global(), entry_list);
91 let _ = self.callback.Call_(
93 cx,
94 self,
95 &observer_entry_list,
96 self,
97 ExceptionHandling::Report,
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 fn Constructor(
117 cx: &mut js::context::JSContext,
118 global: &GlobalScope,
119 proto: Option<HandleObject>,
120 callback: Rc<PerformanceObserverCallback>,
121 ) -> Fallible<DomRoot<PerformanceObserver>> {
122 Ok(PerformanceObserver::new_with_proto(
123 cx,
124 global,
125 proto,
126 callback,
127 Vec::new(),
128 ))
129 }
130
131 fn SupportedEntryTypes(cx: &mut JSContext, global: &GlobalScope, retval: MutableHandleValue) {
133 global.supported_performance_entry_types(cx, retval)
136 }
137
138 fn Observe(&self, cx: &mut JSContext, options: &PerformanceObserverInit) -> Fallible<()> {
140 if options.entryTypes.is_none() && options.type_.is_none() {
146 return Err(Error::Syntax(None));
147 }
148
149 if options.entryTypes.is_some() && (options.buffered.is_some() || options.type_.is_some()) {
151 return Err(Error::Syntax(None));
152 }
153
154 match self.observer_type.get() {
159 ObserverType::Undefined => {
160 if options.entryTypes.is_some() {
161 self.observer_type.set(ObserverType::Multiple);
162 } else {
163 self.observer_type.set(ObserverType::Single);
164 }
165 },
166 ObserverType::Single => {
167 if options.entryTypes.is_some() {
168 return Err(Error::InvalidModification(None));
169 }
170 },
171 ObserverType::Multiple => {
172 if options.type_.is_some() {
173 return Err(Error::InvalidModification(None));
174 }
175 },
176 }
177
178 const NO_VALID_ENTRY_TYPE: &str = "No valid entry type provided to observe().";
180 if let Some(entry_types) = &options.entryTypes {
181 let entry_types = entry_types
183 .iter()
184 .filter_map(|e| EntryType::try_from(&*e.str()).ok())
185 .collect::<Vec<EntryType>>();
186
187 if entry_types.is_empty() {
189 Console::internal_warn(cx, &self.global(), NO_VALID_ENTRY_TYPE.to_string());
190 return Ok(());
191 }
192
193 self.global()
197 .performance()
198 .add_multiple_type_observer(self, entry_types);
199 Ok(())
200 } else if let Some(entry_type) = &options.type_ {
201 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
203 Console::internal_warn(cx, &self.global(), NO_VALID_ENTRY_TYPE.to_string());
204 return Ok(());
205 };
206
207 self.global().performance().add_single_type_observer(
211 self,
212 entry_type,
213 options.buffered.unwrap_or(false),
214 );
215 Ok(())
216 } else {
217 unreachable!()
219 }
220 }
221
222 fn Disconnect(&self) {
224 self.global().performance().remove_observer(self);
225 self.entries.borrow_mut().clear();
226 }
227
228 fn TakeRecords(&self) -> Vec<DomRoot<PerformanceEntry>> {
230 let mut entries = self.entries.borrow_mut();
231 let taken = entries
232 .iter()
233 .map(|entry| DomRoot::from_ref(&**entry))
234 .collect();
235 entries.clear();
236 taken
237 }
238}