1use std::cell::Cell;
6use std::cmp::Ordering;
7use std::collections::VecDeque;
8
9use dom_struct::dom_struct;
10use js::context::JSContext;
11use js::jsval::NullValue;
12use script_bindings::cell::DomRefCell;
13use script_bindings::cformat;
14use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMarkOptions;
15use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
16use script_bindings::codegen::GenericUnionTypes::StringOrPerformanceMeasureOptions;
17use script_bindings::reflector::reflect_dom_object;
18use servo_base::cross_process_instant::CrossProcessInstant;
19use time::Duration;
20
21use super::performanceentry::{EntryType, PerformanceEntry};
22use super::performancemark::PerformanceMark;
23use super::performancemeasure::PerformanceMeasure;
24use super::performancenavigation::PerformanceNavigation;
25use super::performancenavigationtiming::PerformanceNavigationTiming;
26use super::performanceobserver::PerformanceObserver as DOMPerformanceObserver;
27use crate::dom::PERFORMANCE_TIMING_ATTRIBUTES;
28use crate::dom::bindings::codegen::Bindings::PerformanceBinding::{
29 DOMHighResTimeStamp, PerformanceEntryList as DOMPerformanceEntryList, PerformanceMethods,
30};
31use crate::dom::bindings::codegen::UnionTypes::StringOrDouble;
32use crate::dom::bindings::error::{Error, Fallible};
33use crate::dom::bindings::inheritance::Castable;
34use crate::dom::bindings::num::Finite;
35use crate::dom::bindings::refcounted::Trusted;
36use crate::dom::bindings::reflector::DomGlobal;
37use crate::dom::bindings::root::DomRoot;
38use crate::dom::bindings::str::DOMString;
39use crate::dom::bindings::structuredclone;
40use crate::dom::bindings::trace::RootedTraceableBox;
41use crate::dom::eventtarget::EventTarget;
42use crate::dom::globalscope::GlobalScope;
43use crate::dom::window::Window;
44use crate::script_runtime::CanGc;
45
46#[derive(JSTraceable, MallocSizeOf)]
49pub(crate) struct PerformanceEntryList {
50 entries: DOMPerformanceEntryList,
52}
53
54impl PerformanceEntryList {
55 pub(crate) fn new(entries: DOMPerformanceEntryList) -> Self {
56 PerformanceEntryList { entries }
57 }
58
59 pub(crate) fn get_entries_by_name_and_type(
61 &self,
62 name: Option<DOMString>,
63 entry_type: Option<EntryType>,
64 ) -> Vec<DomRoot<PerformanceEntry>> {
65 let mut result = self
66 .entries
67 .iter()
68 .filter(|e| {
69 name.as_ref().is_none_or(|name_| *e.name() == *name_) &&
70 entry_type
71 .as_ref()
72 .is_none_or(|type_| e.entry_type() == *type_)
73 })
74 .cloned()
75 .collect::<Vec<DomRoot<PerformanceEntry>>>();
76
77 result.sort_by(|a, b| {
79 a.start_time()
80 .partial_cmp(&b.start_time())
81 .unwrap_or(Ordering::Equal)
82 });
83
84 result
86 }
87
88 pub(crate) fn clear_entries_by_name_and_type(
89 &mut self,
90 name: Option<DOMString>,
91 entry_type: EntryType,
92 ) {
93 self.entries.retain(|e| {
94 e.entry_type() != entry_type || name.as_ref().is_some_and(|name_| e.name() != name_)
95 });
96 }
97
98 fn get_last_entry_start_time_with_name_and_type(
99 &self,
100 name: DOMString,
101 entry_type: EntryType,
102 ) -> Option<CrossProcessInstant> {
103 self.entries
104 .iter()
105 .rev()
106 .find(|e| e.entry_type() == entry_type && *e.name() == name)
107 .and_then(|entry| entry.start_time())
108 }
109}
110
111impl IntoIterator for PerformanceEntryList {
112 type Item = DomRoot<PerformanceEntry>;
113 type IntoIter = ::std::vec::IntoIter<DomRoot<PerformanceEntry>>;
114
115 fn into_iter(self) -> Self::IntoIter {
116 self.entries.into_iter()
117 }
118}
119
120#[derive(JSTraceable, MallocSizeOf)]
121struct PerformanceObserver {
122 observer: DomRoot<DOMPerformanceObserver>,
123 entry_types: Vec<EntryType>,
124}
125
126#[dom_struct]
127pub(crate) struct Performance {
128 eventtarget: EventTarget,
129 buffer: DomRefCell<PerformanceEntryList>,
130 observers: DomRefCell<Vec<PerformanceObserver>>,
131 pending_notification_observers_task: Cell<bool>,
132 #[no_trace]
133 time_origin: CrossProcessInstant,
136 resource_timing_buffer_size_limit: Cell<usize>,
140 resource_timing_buffer_current_size: Cell<usize>,
142 resource_timing_buffer_pending_full_event: Cell<bool>,
144 resource_timing_secondary_entries: DomRefCell<VecDeque<DomRoot<PerformanceEntry>>>,
146}
147
148impl Performance {
149 fn new_inherited(time_origin: CrossProcessInstant) -> Performance {
150 Performance {
151 eventtarget: EventTarget::new_inherited(),
152 buffer: DomRefCell::new(PerformanceEntryList::new(Vec::new())),
153 observers: DomRefCell::new(Vec::new()),
154 pending_notification_observers_task: Cell::new(false),
155 time_origin,
156 resource_timing_buffer_size_limit: Cell::new(250),
157 resource_timing_buffer_current_size: Cell::new(0),
158 resource_timing_buffer_pending_full_event: Cell::new(false),
159 resource_timing_secondary_entries: DomRefCell::new(VecDeque::new()),
160 }
161 }
162
163 pub(crate) fn new(
164 global: &GlobalScope,
165 navigation_start: CrossProcessInstant,
166 can_gc: CanGc,
167 ) -> DomRoot<Performance> {
168 reflect_dom_object(
169 Box::new(Performance::new_inherited(navigation_start)),
170 global,
171 can_gc,
172 )
173 }
174
175 pub(crate) fn time_origin(&self) -> CrossProcessInstant {
176 self.time_origin
177 }
178
179 pub(crate) fn to_dom_high_res_time_stamp(
180 &self,
181 instant: CrossProcessInstant,
182 ) -> DOMHighResTimeStamp {
183 (instant - self.time_origin).to_dom_high_res_time_stamp()
184 }
185
186 pub(crate) fn maybe_to_dom_high_res_time_stamp(
187 &self,
188 instant: Option<CrossProcessInstant>,
189 ) -> DOMHighResTimeStamp {
190 self.to_dom_high_res_time_stamp(instant.unwrap_or(self.time_origin))
191 }
192
193 pub(crate) fn clear_and_disable_performance_entry_buffer(&self) {
197 let mut buffer = self.buffer.borrow_mut();
198 buffer.entries.clear();
199 self.resource_timing_buffer_size_limit.set(0);
200 }
201
202 pub(crate) fn add_multiple_type_observer(
206 &self,
207 observer: &DOMPerformanceObserver,
208 entry_types: Vec<EntryType>,
209 ) {
210 let mut observers = self.observers.borrow_mut();
211 match observers.iter().position(|o| *o.observer == *observer) {
212 Some(p) => observers[p].entry_types = entry_types,
215 None => observers.push(PerformanceObserver {
217 observer: DomRoot::from_ref(observer),
218 entry_types,
219 }),
220 };
221 }
222
223 pub(crate) fn add_single_type_observer(
224 &self,
225 observer: &DOMPerformanceObserver,
226 entry_type: EntryType,
227 buffered: bool,
228 ) {
229 if buffered {
230 let buffer = self.buffer.borrow();
231 let mut new_entries = buffer.get_entries_by_name_and_type(None, Some(entry_type));
232 if !new_entries.is_empty() {
233 let mut obs_entries = observer.entries();
234 obs_entries.append(&mut new_entries);
235 observer.set_entries(obs_entries);
236 }
237
238 if !self.pending_notification_observers_task.get() {
239 self.pending_notification_observers_task.set(true);
240 let global = &self.global();
241 let owner = Trusted::new(&*global.performance());
242 self.global()
243 .task_manager()
244 .performance_timeline_task_source()
245 .queue(task!(notify_performance_observers: move |cx| {
246 owner.root().notify_observers(cx);
247 }));
248 }
249 }
250 let mut observers = self.observers.borrow_mut();
251 match observers.iter().position(|o| *o.observer == *observer) {
252 Some(p) => {
255 if !observers[p].entry_types.contains(&entry_type) {
257 observers[p].entry_types.push(entry_type)
258 }
259 },
260 None => observers.push(PerformanceObserver {
262 observer: DomRoot::from_ref(observer),
263 entry_types: vec![entry_type],
264 }),
265 };
266 }
267
268 pub(crate) fn remove_observer(&self, observer: &DOMPerformanceObserver) {
270 let mut observers = self.observers.borrow_mut();
271 let index = match observers.iter().position(|o| &(*o.observer) == observer) {
272 Some(p) => p,
273 None => return,
274 };
275
276 observers.remove(index);
277 }
278
279 pub(crate) fn queue_entry(&self, entry: &PerformanceEntry) -> Option<usize> {
288 if entry.entry_type() == EntryType::Resource && !self.should_queue_resource_entry(entry) {
290 return None;
291 }
292
293 for observer in self
298 .observers
299 .borrow()
300 .iter()
301 .filter(|o| o.entry_types.contains(&entry.entry_type()))
302 {
303 observer.observer.queue_entry(entry);
304 }
305
306 self.buffer
309 .borrow_mut()
310 .entries
311 .push(DomRoot::from_ref(entry));
312
313 let entry_last_index = self.buffer.borrow_mut().entries.len() - 1;
314
315 if self.pending_notification_observers_task.get() {
318 return None;
319 }
320
321 self.pending_notification_observers_task.set(true);
324
325 let global = &self.global();
326 let owner = Trusted::new(&*global.performance());
327 self.global()
328 .task_manager()
329 .performance_timeline_task_source()
330 .queue(task!(notify_performance_observers: move |cx| {
331 owner.root().notify_observers(cx);
332 }));
333
334 Some(entry_last_index)
335 }
336
337 fn notify_observers(&self, cx: &mut JSContext) {
342 self.pending_notification_observers_task.set(false);
344
345 let observers: Vec<DomRoot<DOMPerformanceObserver>> = self
351 .observers
352 .borrow()
353 .iter()
354 .map(|o| DomRoot::from_ref(&*o.observer))
355 .collect();
356
357 for o in observers.iter() {
359 o.notify(cx);
360 }
361 }
362
363 fn can_add_resource_timing_entry(&self) -> bool {
365 self.resource_timing_buffer_current_size.get() <
368 self.resource_timing_buffer_size_limit.get()
369 }
370
371 fn copy_secondary_resource_timing_buffer(&self) {
373 while self.can_add_resource_timing_entry() {
375 let entry = self
377 .resource_timing_secondary_entries
378 .borrow_mut()
379 .pop_front();
380 if let Some(ref entry) = entry {
381 self.buffer
383 .borrow_mut()
384 .entries
385 .push(DomRoot::from_ref(entry));
386 self.resource_timing_buffer_current_size
388 .set(self.resource_timing_buffer_current_size.get() + 1);
389 } else {
393 break;
394 }
395 }
396 }
397
398 fn fire_buffer_full_event(&self, cx: &mut js::context::JSContext) {
400 while !self.resource_timing_secondary_entries.borrow().is_empty() {
401 let no_of_excess_entries_before = self.resource_timing_secondary_entries.borrow().len();
402
403 if !self.can_add_resource_timing_entry() {
404 self.upcast::<EventTarget>()
405 .fire_event(cx, atom!("resourcetimingbufferfull"));
406 }
407 self.copy_secondary_resource_timing_buffer();
408 let no_of_excess_entries_after = self.resource_timing_secondary_entries.borrow().len();
409 if no_of_excess_entries_before <= no_of_excess_entries_after {
410 self.resource_timing_secondary_entries.borrow_mut().clear();
411 break;
412 }
413 }
414 self.resource_timing_buffer_pending_full_event.set(false);
415 }
416
417 fn should_queue_resource_entry(&self, entry: &PerformanceEntry) -> bool {
419 if !self.resource_timing_buffer_pending_full_event.get() {
421 if self.can_add_resource_timing_entry() {
422 self.resource_timing_buffer_current_size
426 .set(self.resource_timing_buffer_current_size.get() + 1);
427 return true;
429 }
430
431 self.resource_timing_buffer_pending_full_event.set(true);
433 let performance = Trusted::new(self);
435 self.global()
436 .task_manager()
437 .performance_timeline_task_source()
438 .queue(task!(fire_a_buffer_full_event: move |cx| {
439 performance.root().fire_buffer_full_event(cx);
440 }));
441 }
442
443 self.resource_timing_secondary_entries
445 .borrow_mut()
446 .push_back(DomRoot::from_ref(entry));
447
448 false
451 }
452
453 pub(crate) fn update_entry(&self, index: usize, entry: &PerformanceEntry) {
454 if let Some(e) = self.buffer.borrow_mut().entries.get_mut(index) {
455 *e = DomRoot::from_ref(entry);
456 }
457 }
458
459 fn convert_a_name_to_a_timestamp(&self, name: &str) -> Fallible<CrossProcessInstant> {
461 let Some(window) = DomRoot::downcast::<Window>(self.global()) else {
463 return Err(Error::Type(cformat!(
464 "Cannot use {name} from non-window global"
465 )));
466 };
467
468 if name == "navigationStart" {
470 return Ok(self.time_origin);
471 }
472
473 let end_time = window.Document().performance_timing_attribute(name)?;
480
481 let Some(end_time) = end_time else {
483 return Err(Error::InvalidAccess(Some(format!(
484 "{name} hasn't happened yet"
485 ))));
486 };
487
488 Ok(end_time)
490 }
491
492 fn convert_a_mark_to_a_timestamp(
494 &self,
495 mark: &StringOrDouble,
496 ) -> Fallible<CrossProcessInstant> {
497 match mark {
498 StringOrDouble::String(name) => {
499 if PERFORMANCE_TIMING_ATTRIBUTES.contains(&&*name.str()) {
503 self.convert_a_name_to_a_timestamp(&name.str())
504 }
505 else {
509 self.buffer
510 .borrow()
511 .get_last_entry_start_time_with_name_and_type(name.clone(), EntryType::Mark)
512 .ok_or(Error::Syntax(Some(format!(
513 "No PerformanceMark named {name} exists"
514 ))))
515 }
516 },
517 StringOrDouble::Double(timestamp) => {
519 if timestamp.is_sign_negative() {
521 return Err(Error::Type(c"Time stamps must not be negative".to_owned()));
522 }
523
524 Ok(
527 self.time_origin +
528 Duration::microseconds(timestamp.mul_add(1000.0, 0.0) as i64),
529 )
530 },
531 }
532 }
533}
534
535impl PerformanceMethods<crate::DomTypeHolder> for Performance {
536 fn Timing(&self) -> DomRoot<PerformanceNavigationTiming> {
538 let entries = self.GetEntriesByType(DOMString::from("navigation"));
539 if !entries.is_empty() {
540 return DomRoot::from_ref(
541 entries[0]
542 .downcast::<PerformanceNavigationTiming>()
543 .unwrap(),
544 );
545 }
546 unreachable!("Are we trying to expose Performance.timing in workers?");
547 }
548
549 fn Navigation(&self) -> DomRoot<PerformanceNavigation> {
551 PerformanceNavigation::new(&self.global(), CanGc::deprecated_note())
552 }
553
554 fn Now(&self) -> DOMHighResTimeStamp {
556 self.to_dom_high_res_time_stamp(CrossProcessInstant::now())
557 }
558
559 fn TimeOrigin(&self) -> DOMHighResTimeStamp {
561 (self.time_origin - CrossProcessInstant::epoch()).to_dom_high_res_time_stamp()
562 }
563
564 fn GetEntries(&self) -> Vec<DomRoot<PerformanceEntry>> {
566 self.buffer
569 .borrow()
570 .get_entries_by_name_and_type(None, None)
571 }
572
573 fn GetEntriesByType(&self, entry_type: DOMString) -> Vec<DomRoot<PerformanceEntry>> {
575 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
576 return Vec::new();
577 };
578 self.buffer
579 .borrow()
580 .get_entries_by_name_and_type(None, Some(entry_type))
581 }
582
583 fn GetEntriesByName(
585 &self,
586 name: DOMString,
587 entry_type: Option<DOMString>,
588 ) -> Vec<DomRoot<PerformanceEntry>> {
589 let entry_type = match entry_type {
590 Some(entry_type) => {
591 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
592 return Vec::new();
593 };
594 Some(entry_type)
595 },
596 None => None,
597 };
598 self.buffer
599 .borrow()
600 .get_entries_by_name_and_type(Some(name), entry_type)
601 }
602
603 fn Mark(
605 &self,
606 cx: &mut JSContext,
607 mark_name: DOMString,
608 mark_options: RootedTraceableBox<PerformanceMarkOptions>,
609 ) -> Fallible<DomRoot<PerformanceMark>> {
610 let entry =
612 PerformanceMark::new_with_proto(cx, &self.global(), None, mark_name, mark_options)?;
613
614 self.queue_entry(entry.upcast::<PerformanceEntry>());
617
618 Ok(entry)
620 }
621
622 fn ClearMarks(&self, mark_name: Option<DOMString>) {
624 self.buffer
625 .borrow_mut()
626 .clear_entries_by_name_and_type(mark_name, EntryType::Mark);
627 }
628
629 fn Measure(
631 &self,
632 cx: &mut JSContext,
633 measure_name: DOMString,
634 start_or_measure_options: StringOrPerformanceMeasureOptions,
635 end_mark: Option<DOMString>,
636 ) -> Fallible<DomRoot<PerformanceMeasure>> {
637 if let StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) =
640 &start_or_measure_options &&
641 (options.start.is_some() ||
642 options.duration.is_some() ||
643 options.end.is_some() ||
644 options.detail.get().is_object_or_null())
645 {
646 if end_mark.is_some() {
648 return Err(Error::Type(
649 c"Must not provide endMark if PerformanceMeasureOptions is also provided"
650 .to_owned(),
651 ));
652 }
653
654 if options.start.is_none() && options.end.is_none() {
656 return Err(Error::Type(
657 c"Either 'start' or 'end' member of PerformanceMeasureOptions must be provided"
658 .to_owned(),
659 ));
660 }
661
662 if options.start.is_some() && options.duration.is_some() && options.end.is_some() {
664 return Err(Error::Type(c"Either 'start' or 'end' or 'duration' member of PerformanceMeasureOptions must be omitted".to_owned()));
665 }
666 }
667
668 let end_time = if let Some(end_mark) = end_mark {
672 self.convert_a_mark_to_a_timestamp(&StringOrDouble::String(end_mark))?
673 } else {
674 match &start_or_measure_options {
675 StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) => {
676 if let Some(end) = &options.end {
680 self.convert_a_mark_to_a_timestamp(end)?
681 }
682 else if let Some((start, duration)) =
685 options.start.as_ref().zip(options.duration)
686 {
687 let start = self.convert_a_mark_to_a_timestamp(start)?;
690
691 let duration = self
694 .convert_a_mark_to_a_timestamp(&StringOrDouble::Double(duration))? -
695 self.time_origin;
696
697 start + duration
699 } else {
700 CrossProcessInstant::now()
703 }
704 },
705 _ => {
706 CrossProcessInstant::now()
709 },
710 }
711 };
712
713 let start_time = match &start_or_measure_options {
715 StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) => {
716 if let Some(start) = &options.start {
720 self.convert_a_mark_to_a_timestamp(start)?
721 }
722 else if let Some((duration, end)) = options.duration.zip(options.end.as_ref()) {
725 let duration = self
728 .convert_a_mark_to_a_timestamp(&StringOrDouble::Double(duration))? -
729 self.time_origin;
730
731 let end = self.convert_a_mark_to_a_timestamp(end)?;
734
735 end - duration
737 }
738 else {
740 self.time_origin
741 }
742 },
743 StringOrPerformanceMeasureOptions::String(string) => {
744 self.convert_a_mark_to_a_timestamp(&StringOrDouble::String(string.clone()))?
747 },
748 };
749
750 let entry = PerformanceMeasure::new(
758 &self.global(),
759 measure_name,
760 start_time,
761 end_time - start_time,
762 Default::default(),
763 );
764
765 rooted!(&in(cx) let mut detail = NullValue());
767 if let StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) =
769 &start_or_measure_options &&
770 !options.detail.get().is_null_or_undefined()
771 {
772 let record = structuredclone::write(cx, options.detail.handle(), None)?;
774
775 structuredclone::read(cx, &self.global(), record, detail.handle_mut())?;
777 }
778 entry.set_detail(detail.handle());
783
784 self.queue_entry(entry.upcast::<PerformanceEntry>());
787
788 Ok(entry)
790 }
791
792 fn ClearMeasures(&self, measure_name: Option<DOMString>) {
794 self.buffer
795 .borrow_mut()
796 .clear_entries_by_name_and_type(measure_name, EntryType::Measure);
797 }
798 fn ClearResourceTimings(&self) {
800 self.buffer
801 .borrow_mut()
802 .clear_entries_by_name_and_type(None, EntryType::Resource);
803 self.resource_timing_buffer_current_size.set(0);
804 }
805
806 fn SetResourceTimingBufferSize(&self, max_size: u32) {
808 self.resource_timing_buffer_size_limit
809 .set(max_size as usize);
810 }
811
812 event_handler!(
814 resourcetimingbufferfull,
815 GetOnresourcetimingbufferfull,
816 SetOnresourcetimingbufferfull
817 );
818}
819
820pub(crate) trait ToDOMHighResTimeStamp {
821 fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp;
822}
823
824impl ToDOMHighResTimeStamp for Duration {
825 fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp {
826 let microseconds_rounded = (self.whole_microseconds() as f64 / 10.).floor() * 10.;
832 Finite::wrap(microseconds_rounded / 1000.)
833 }
834}