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::PerformanceMarkBinding::PerformanceMarkMethods;
16use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
17use script_bindings::codegen::GenericUnionTypes::StringOrPerformanceMeasureOptions;
18use script_bindings::reflector::reflect_dom_object_with_cx;
19use servo_base::cross_process_instant::CrossProcessInstant;
20use time::Duration;
21
22use super::performanceentry::{EntryType, PerformanceEntry};
23use super::performancemark::PerformanceMark;
24use super::performancemeasure::PerformanceMeasure;
25use super::performancenavigation::PerformanceNavigation;
26use super::performancenavigationtiming::PerformanceNavigationTiming;
27use super::performanceobserver::PerformanceObserver as DOMPerformanceObserver;
28use crate::dom::PERFORMANCE_TIMING_ATTRIBUTES;
29use crate::dom::bindings::codegen::Bindings::PerformanceBinding::{
30 DOMHighResTimeStamp, PerformanceMethods,
31};
32use crate::dom::bindings::codegen::UnionTypes::StringOrDouble;
33use crate::dom::bindings::error::{Error, Fallible};
34use crate::dom::bindings::inheritance::Castable;
35use crate::dom::bindings::num::Finite;
36use crate::dom::bindings::refcounted::Trusted;
37use crate::dom::bindings::reflector::DomGlobal;
38use crate::dom::bindings::root::{Dom, DomRoot};
39use crate::dom::bindings::str::DOMString;
40use crate::dom::bindings::structuredclone;
41use crate::dom::bindings::trace::RootedTraceableBox;
42use crate::dom::eventtarget::EventTarget;
43use crate::dom::globalscope::GlobalScope;
44use crate::dom::window::Window;
45
46#[derive(JSTraceable, MallocSizeOf)]
49#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
50pub(crate) struct PerformanceEntryList {
51 entries: Vec<Dom<PerformanceEntry>>,
53}
54
55impl PerformanceEntryList {
56 pub(crate) fn new(entries: Vec<DomRoot<PerformanceEntry>>) -> Self {
57 PerformanceEntryList {
58 entries: entries.into_iter().map(|entry| entry.as_traced()).collect(),
59 }
60 }
61
62 pub(crate) fn get_entries_by_name_and_type(
64 &self,
65 name: Option<DOMString>,
66 entry_type: Option<EntryType>,
67 ) -> Vec<DomRoot<PerformanceEntry>> {
68 let mut result = self
69 .entries
70 .iter()
71 .filter(|e| {
72 name.as_ref().is_none_or(|name_| *e.name() == *name_) &&
73 entry_type
74 .as_ref()
75 .is_none_or(|type_| e.entry_type() == *type_)
76 })
77 .map(|entry| entry.as_rooted())
78 .collect::<Vec<DomRoot<PerformanceEntry>>>();
79
80 result.sort_by(|a, b| {
82 a.start_time()
83 .partial_cmp(&b.start_time())
84 .unwrap_or(Ordering::Equal)
85 });
86
87 result
89 }
90
91 pub(crate) fn clear_entries_by_name_and_type(
92 &mut self,
93 name: Option<DOMString>,
94 entry_type: EntryType,
95 ) {
96 self.entries.retain(|e| {
97 e.entry_type() != entry_type || name.as_ref().is_some_and(|name_| e.name() != name_)
98 });
99 }
100
101 fn get_last_entry_start_time_with_name_and_type(
102 &self,
103 name: DOMString,
104 entry_type: EntryType,
105 ) -> Option<CrossProcessInstant> {
106 self.entries
107 .iter()
108 .rev()
109 .find(|e| e.entry_type() == entry_type && *e.name() == name)
110 .and_then(|entry| entry.start_time())
111 }
112}
113
114#[derive(JSTraceable, MallocSizeOf)]
115#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
116struct PerformanceObserver {
117 observer: Dom<DOMPerformanceObserver>,
118 entry_types: Vec<EntryType>,
119}
120
121#[dom_struct]
122pub(crate) struct Performance {
123 eventtarget: EventTarget,
124 buffer: DomRefCell<PerformanceEntryList>,
125 observers: DomRefCell<Vec<PerformanceObserver>>,
126 pending_notification_observers_task: Cell<bool>,
127 #[no_trace]
128 time_origin: CrossProcessInstant,
131 resource_timing_buffer_size_limit: Cell<usize>,
135 resource_timing_buffer_current_size: Cell<usize>,
137 resource_timing_buffer_pending_full_event: Cell<bool>,
139 resource_timing_secondary_entries: DomRefCell<VecDeque<Dom<PerformanceEntry>>>,
141}
142
143impl Performance {
144 fn new_inherited(time_origin: CrossProcessInstant) -> Performance {
145 Performance {
146 eventtarget: EventTarget::new_inherited(),
147 buffer: DomRefCell::new(PerformanceEntryList::new(Vec::new())),
148 observers: DomRefCell::new(Vec::new()),
149 pending_notification_observers_task: Cell::new(false),
150 time_origin,
151 resource_timing_buffer_size_limit: Cell::new(250),
152 resource_timing_buffer_current_size: Cell::new(0),
153 resource_timing_buffer_pending_full_event: Cell::new(false),
154 resource_timing_secondary_entries: DomRefCell::new(VecDeque::new()),
155 }
156 }
157
158 pub(crate) fn new(
159 cx: &mut JSContext,
160 global: &GlobalScope,
161 navigation_start: CrossProcessInstant,
162 ) -> DomRoot<Performance> {
163 reflect_dom_object_with_cx(
164 Box::new(Performance::new_inherited(navigation_start)),
165 global,
166 cx,
167 )
168 }
169
170 pub(crate) fn time_origin(&self) -> CrossProcessInstant {
171 self.time_origin
172 }
173
174 pub(crate) fn to_dom_high_res_time_stamp(
175 &self,
176 instant: CrossProcessInstant,
177 ) -> DOMHighResTimeStamp {
178 (instant - self.time_origin).to_dom_high_res_time_stamp()
179 }
180
181 pub(crate) fn maybe_to_dom_high_res_time_stamp(
182 &self,
183 instant: Option<CrossProcessInstant>,
184 ) -> DOMHighResTimeStamp {
185 self.to_dom_high_res_time_stamp(instant.unwrap_or(self.time_origin))
186 }
187
188 pub(crate) fn clear_and_disable_performance_entry_buffer(&self) {
192 let mut buffer = self.buffer.borrow_mut();
193 buffer.entries.clear();
194 self.resource_timing_buffer_size_limit.set(0);
195 }
196
197 pub(crate) fn add_multiple_type_observer(
201 &self,
202 observer: &DOMPerformanceObserver,
203 entry_types: Vec<EntryType>,
204 ) {
205 let mut observers = self.observers.borrow_mut();
206 match observers.iter().position(|o| *o.observer == *observer) {
207 Some(p) => observers[p].entry_types = entry_types,
210 None => observers.push(PerformanceObserver {
212 observer: Dom::from_ref(observer),
213 entry_types,
214 }),
215 };
216 }
217
218 pub(crate) fn add_single_type_observer(
219 &self,
220 observer: &DOMPerformanceObserver,
221 entry_type: EntryType,
222 buffered: bool,
223 ) {
224 if buffered {
225 let buffer = self.buffer.borrow();
226 let new_entries = buffer.get_entries_by_name_and_type(None, Some(entry_type));
227 if !new_entries.is_empty() {
228 let new_entries = new_entries.into_iter().map(|entry| entry.as_traced());
229 observer.entries_mut().extend(new_entries);
230 }
231
232 if !self.pending_notification_observers_task.get() {
233 self.pending_notification_observers_task.set(true);
234 let owner = Trusted::new(self);
235 self.global()
236 .task_manager()
237 .performance_timeline_task_source()
238 .queue(task!(notify_performance_observers: move |cx| {
239 owner.root().notify_observers(cx);
240 }));
241 }
242 }
243 let mut observers = self.observers.borrow_mut();
244 match observers.iter().position(|o| *o.observer == *observer) {
245 Some(p) => {
248 if !observers[p].entry_types.contains(&entry_type) {
250 observers[p].entry_types.push(entry_type)
251 }
252 },
253 None => observers.push(PerformanceObserver {
255 observer: Dom::from_ref(observer),
256 entry_types: vec![entry_type],
257 }),
258 };
259 }
260
261 pub(crate) fn remove_observer(&self, observer: &DOMPerformanceObserver) {
263 let mut observers = self.observers.borrow_mut();
264 let index = match observers.iter().position(|o| &(*o.observer) == observer) {
265 Some(p) => p,
266 None => return,
267 };
268
269 observers.remove(index);
270 }
271
272 pub(crate) fn queue_entry(&self, entry: &PerformanceEntry) -> Option<usize> {
281 if entry.entry_type() == EntryType::Resource && !self.should_queue_resource_entry(entry) {
283 return None;
284 }
285
286 for observer in self
291 .observers
292 .borrow()
293 .iter()
294 .filter(|o| o.entry_types.contains(&entry.entry_type()))
295 {
296 observer.observer.queue_entry(entry);
297 }
298
299 self.buffer.borrow_mut().entries.push(Dom::from_ref(entry));
302
303 let entry_last_index = self.buffer.borrow_mut().entries.len() - 1;
304
305 if self.pending_notification_observers_task.get() {
308 return None;
309 }
310
311 self.pending_notification_observers_task.set(true);
314
315 let owner = Trusted::new(self);
316 self.global()
317 .task_manager()
318 .performance_timeline_task_source()
319 .queue(task!(notify_performance_observers: move |cx| {
320 owner.root().notify_observers(cx);
321 }));
322
323 Some(entry_last_index)
324 }
325
326 fn notify_observers(&self, cx: &mut JSContext) {
331 self.pending_notification_observers_task.set(false);
333
334 let observers: Vec<DomRoot<DOMPerformanceObserver>> = self
340 .observers
341 .borrow()
342 .iter()
343 .map(|o| DomRoot::from_ref(&*o.observer))
344 .collect();
345
346 for o in observers.iter() {
348 o.notify(cx);
349 }
350 }
351
352 fn can_add_resource_timing_entry(&self) -> bool {
354 self.resource_timing_buffer_current_size.get() <
357 self.resource_timing_buffer_size_limit.get()
358 }
359
360 fn copy_secondary_resource_timing_buffer(&self) {
362 while self.can_add_resource_timing_entry() {
364 if let Some(ref entry) = self
366 .resource_timing_secondary_entries
367 .borrow_mut()
368 .pop_front()
369 {
370 self.buffer.borrow_mut().entries.push(Dom::from_ref(entry));
372 self.resource_timing_buffer_current_size
374 .set(self.resource_timing_buffer_current_size.get() + 1);
375 } else {
379 break;
380 }
381 }
382 }
383
384 fn fire_buffer_full_event(&self, cx: &mut js::context::JSContext) {
386 while !self.resource_timing_secondary_entries.borrow().is_empty() {
387 let no_of_excess_entries_before = self.resource_timing_secondary_entries.borrow().len();
388
389 if !self.can_add_resource_timing_entry() {
390 self.upcast::<EventTarget>()
391 .fire_event(cx, atom!("resourcetimingbufferfull"));
392 }
393 self.copy_secondary_resource_timing_buffer();
394 let no_of_excess_entries_after = self.resource_timing_secondary_entries.borrow().len();
395 if no_of_excess_entries_before <= no_of_excess_entries_after {
396 self.resource_timing_secondary_entries.borrow_mut().clear();
397 break;
398 }
399 }
400 self.resource_timing_buffer_pending_full_event.set(false);
401 }
402
403 fn should_queue_resource_entry(&self, entry: &PerformanceEntry) -> bool {
405 if !self.resource_timing_buffer_pending_full_event.get() {
407 if self.can_add_resource_timing_entry() {
408 self.resource_timing_buffer_current_size
412 .set(self.resource_timing_buffer_current_size.get() + 1);
413 return true;
415 }
416
417 self.resource_timing_buffer_pending_full_event.set(true);
419 let performance = Trusted::new(self);
421 self.global()
422 .task_manager()
423 .performance_timeline_task_source()
424 .queue(task!(fire_a_buffer_full_event: move |cx| {
425 performance.root().fire_buffer_full_event(cx);
426 }));
427 }
428
429 self.resource_timing_secondary_entries
431 .borrow_mut()
432 .push_back(Dom::from_ref(entry));
433
434 false
437 }
438
439 pub(crate) fn update_entry(&self, index: usize, entry: &PerformanceEntry) {
440 if let Some(e) = self.buffer.borrow_mut().entries.get_mut(index) {
441 *e = Dom::from_ref(entry);
442 }
443 }
444
445 fn convert_a_name_to_a_timestamp(&self, name: &str) -> Fallible<CrossProcessInstant> {
447 let Some(window) = DomRoot::downcast::<Window>(self.global()) else {
449 return Err(Error::Type(cformat!(
450 "Cannot use {name} from non-window global"
451 )));
452 };
453
454 if name == "navigationStart" {
456 return Ok(self.time_origin);
457 }
458
459 let end_time = window.Document().performance_timing_attribute(name)?;
466
467 let Some(end_time) = end_time else {
469 return Err(Error::InvalidAccess(Some(format!(
470 "{name} hasn't happened yet"
471 ))));
472 };
473
474 Ok(end_time)
476 }
477
478 fn convert_a_mark_to_a_timestamp(
480 &self,
481 mark: &StringOrDouble,
482 ) -> Fallible<CrossProcessInstant> {
483 match mark {
484 StringOrDouble::String(name) => {
485 if PERFORMANCE_TIMING_ATTRIBUTES.contains(&&*name.str()) {
489 self.convert_a_name_to_a_timestamp(&name.str())
490 }
491 else {
495 self.buffer
496 .borrow()
497 .get_last_entry_start_time_with_name_and_type(name.clone(), EntryType::Mark)
498 .ok_or(Error::Syntax(Some(format!(
499 "No PerformanceMark named {name} exists"
500 ))))
501 }
502 },
503 StringOrDouble::Double(timestamp) => {
505 if timestamp.is_sign_negative() {
507 return Err(Error::Type(c"Time stamps must not be negative".to_owned()));
508 }
509
510 Ok(
513 self.time_origin +
514 Duration::microseconds(timestamp.mul_add(1000.0, 0.0) as i64),
515 )
516 },
517 }
518 }
519}
520
521impl PerformanceMethods<crate::DomTypeHolder> for Performance {
522 fn Timing(&self) -> DomRoot<PerformanceNavigationTiming> {
524 let entries = self.GetEntriesByType(DOMString::from("navigation"));
525 if !entries.is_empty() {
526 return DomRoot::from_ref(
527 entries[0]
528 .downcast::<PerformanceNavigationTiming>()
529 .unwrap(),
530 );
531 }
532 unreachable!("Are we trying to expose Performance.timing in workers?");
533 }
534
535 fn Navigation(&self, cx: &mut JSContext) -> DomRoot<PerformanceNavigation> {
537 PerformanceNavigation::new(cx, &self.global())
538 }
539
540 fn Now(&self) -> DOMHighResTimeStamp {
542 self.to_dom_high_res_time_stamp(CrossProcessInstant::now())
543 }
544
545 fn TimeOrigin(&self) -> DOMHighResTimeStamp {
547 (self.time_origin - CrossProcessInstant::epoch()).to_dom_high_res_time_stamp()
548 }
549
550 fn GetEntries(&self) -> Vec<DomRoot<PerformanceEntry>> {
552 self.buffer
555 .borrow()
556 .get_entries_by_name_and_type(None, None)
557 }
558
559 fn GetEntriesByType(&self, entry_type: DOMString) -> Vec<DomRoot<PerformanceEntry>> {
561 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
562 return Vec::new();
563 };
564 self.buffer
565 .borrow()
566 .get_entries_by_name_and_type(None, Some(entry_type))
567 }
568
569 fn GetEntriesByName(
571 &self,
572 name: DOMString,
573 entry_type: Option<DOMString>,
574 ) -> Vec<DomRoot<PerformanceEntry>> {
575 let entry_type = match entry_type {
576 Some(entry_type) => {
577 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
578 return Vec::new();
579 };
580 Some(entry_type)
581 },
582 None => None,
583 };
584 self.buffer
585 .borrow()
586 .get_entries_by_name_and_type(Some(name), entry_type)
587 }
588
589 fn Mark(
591 &self,
592 cx: &mut JSContext,
593 mark_name: DOMString,
594 mark_options: RootedTraceableBox<PerformanceMarkOptions>,
595 ) -> Fallible<DomRoot<PerformanceMark>> {
596 let entry =
598 PerformanceMark::Constructor(cx, &self.global(), None, mark_name, mark_options)?;
599
600 self.queue_entry(entry.upcast::<PerformanceEntry>());
603
604 Ok(entry)
606 }
607
608 fn ClearMarks(&self, mark_name: Option<DOMString>) {
610 self.buffer
611 .borrow_mut()
612 .clear_entries_by_name_and_type(mark_name, EntryType::Mark);
613 }
614
615 fn Measure(
617 &self,
618 cx: &mut JSContext,
619 measure_name: DOMString,
620 start_or_measure_options: StringOrPerformanceMeasureOptions,
621 end_mark: Option<DOMString>,
622 ) -> Fallible<DomRoot<PerformanceMeasure>> {
623 if let StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) =
626 &start_or_measure_options &&
627 (options.start.is_some() ||
628 options.duration.is_some() ||
629 options.end.is_some() ||
630 options.detail.get().is_object_or_null())
631 {
632 if end_mark.is_some() {
634 return Err(Error::Type(
635 c"Must not provide endMark if PerformanceMeasureOptions is also provided"
636 .to_owned(),
637 ));
638 }
639
640 if options.start.is_none() && options.end.is_none() {
642 return Err(Error::Type(
643 c"Either 'start' or 'end' member of PerformanceMeasureOptions must be provided"
644 .to_owned(),
645 ));
646 }
647
648 if options.start.is_some() && options.duration.is_some() && options.end.is_some() {
650 return Err(Error::Type(c"Either 'start' or 'end' or 'duration' member of PerformanceMeasureOptions must be omitted".to_owned()));
651 }
652 }
653
654 let end_time = if let Some(end_mark) = end_mark {
658 self.convert_a_mark_to_a_timestamp(&StringOrDouble::String(end_mark))?
659 } else {
660 match &start_or_measure_options {
661 StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) => {
662 if let Some(end) = &options.end {
666 self.convert_a_mark_to_a_timestamp(end)?
667 }
668 else if let Some((start, duration)) =
671 options.start.as_ref().zip(options.duration)
672 {
673 let start = self.convert_a_mark_to_a_timestamp(start)?;
676
677 let duration = self
680 .convert_a_mark_to_a_timestamp(&StringOrDouble::Double(duration))? -
681 self.time_origin;
682
683 start + duration
685 } else {
686 CrossProcessInstant::now()
689 }
690 },
691 _ => {
692 CrossProcessInstant::now()
695 },
696 }
697 };
698
699 let start_time = match &start_or_measure_options {
701 StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) => {
702 if let Some(start) = &options.start {
706 self.convert_a_mark_to_a_timestamp(start)?
707 }
708 else if let Some((duration, end)) = options.duration.zip(options.end.as_ref()) {
711 let duration = self
714 .convert_a_mark_to_a_timestamp(&StringOrDouble::Double(duration))? -
715 self.time_origin;
716
717 let end = self.convert_a_mark_to_a_timestamp(end)?;
720
721 end - duration
723 }
724 else {
726 self.time_origin
727 }
728 },
729 StringOrPerformanceMeasureOptions::String(string) => {
730 self.convert_a_mark_to_a_timestamp(&StringOrDouble::String(string.clone()))?
733 },
734 };
735
736 let entry = PerformanceMeasure::new(
744 cx,
745 &self.global(),
746 measure_name,
747 start_time,
748 end_time - start_time,
749 );
750
751 rooted!(&in(cx) let mut detail = NullValue());
753 if let StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) =
755 &start_or_measure_options &&
756 !options.detail.get().is_null_or_undefined()
757 {
758 let record = structuredclone::write(cx, options.detail.handle(), None)?;
760
761 structuredclone::read(cx, &self.global(), record, detail.handle_mut())?;
763 }
764 entry.set_detail(detail.handle());
769
770 self.queue_entry(entry.upcast::<PerformanceEntry>());
773
774 Ok(entry)
776 }
777
778 fn ClearMeasures(&self, measure_name: Option<DOMString>) {
780 self.buffer
781 .borrow_mut()
782 .clear_entries_by_name_and_type(measure_name, EntryType::Measure);
783 }
784 fn ClearResourceTimings(&self) {
786 self.buffer
787 .borrow_mut()
788 .clear_entries_by_name_and_type(None, EntryType::Resource);
789 self.resource_timing_buffer_current_size.set(0);
790 }
791
792 fn SetResourceTimingBufferSize(&self, max_size: u32) {
794 self.resource_timing_buffer_size_limit
795 .set(max_size as usize);
796 }
797
798 event_handler!(
800 resourcetimingbufferfull,
801 GetOnresourcetimingbufferfull,
802 SetOnresourcetimingbufferfull
803 );
804}
805
806pub(crate) trait ToDOMHighResTimeStamp {
807 fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp;
808}
809
810impl ToDOMHighResTimeStamp for Duration {
811 fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp {
812 let microseconds_rounded = (self.whole_microseconds() as f64 / 10.).floor() * 10.;
818 Finite::wrap(microseconds_rounded / 1000.)
819 }
820}