1use std::cell::Cell;
6use std::cmp::Ordering;
7use std::collections::VecDeque;
8
9use dom_struct::dom_struct;
10use script_bindings::cformat;
11use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
12use script_bindings::codegen::GenericUnionTypes::StringOrPerformanceMeasureOptions;
13use servo_base::cross_process_instant::CrossProcessInstant;
14use time::Duration;
15
16use super::performanceentry::{EntryType, PerformanceEntry};
17use super::performancemark::PerformanceMark;
18use super::performancemeasure::PerformanceMeasure;
19use super::performancenavigation::PerformanceNavigation;
20use super::performancenavigationtiming::PerformanceNavigationTiming;
21use super::performanceobserver::PerformanceObserver as DOMPerformanceObserver;
22use crate::dom::bindings::cell::DomRefCell;
23use crate::dom::bindings::codegen::Bindings::PerformanceBinding::{
24 DOMHighResTimeStamp, PerformanceEntryList as DOMPerformanceEntryList, PerformanceMethods,
25};
26use crate::dom::bindings::codegen::UnionTypes::StringOrDouble;
27use crate::dom::bindings::error::{Error, Fallible};
28use crate::dom::bindings::inheritance::Castable;
29use crate::dom::bindings::num::Finite;
30use crate::dom::bindings::refcounted::Trusted;
31use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
32use crate::dom::bindings::root::DomRoot;
33use crate::dom::bindings::str::DOMString;
34use crate::dom::eventtarget::EventTarget;
35use crate::dom::globalscope::GlobalScope;
36use crate::dom::window::Window;
37use crate::script_runtime::CanGc;
38
39const INVALID_ENTRY_NAMES: &[&str] = &[
40 "navigationStart",
41 "unloadEventStart",
42 "unloadEventEnd",
43 "redirectStart",
44 "redirectEnd",
45 "fetchStart",
46 "domainLookupStart",
47 "domainLookupEnd",
48 "connectStart",
49 "connectEnd",
50 "secureConnectionStart",
51 "requestStart",
52 "responseStart",
53 "responseEnd",
54 "domLoading",
55 "domInteractive",
56 "domContentLoadedEventStart",
57 "domContentLoadedEventEnd",
58 "domComplete",
59 "loadEventStart",
60 "loadEventEnd",
61];
62
63#[derive(JSTraceable, MallocSizeOf)]
66pub(crate) struct PerformanceEntryList {
67 entries: DOMPerformanceEntryList,
69}
70
71impl PerformanceEntryList {
72 pub(crate) fn new(entries: DOMPerformanceEntryList) -> Self {
73 PerformanceEntryList { entries }
74 }
75
76 pub(crate) fn get_entries_by_name_and_type(
78 &self,
79 name: Option<DOMString>,
80 entry_type: Option<EntryType>,
81 ) -> Vec<DomRoot<PerformanceEntry>> {
82 let mut result = self
83 .entries
84 .iter()
85 .filter(|e| {
86 name.as_ref().is_none_or(|name_| *e.name() == *name_) &&
87 entry_type
88 .as_ref()
89 .is_none_or(|type_| e.entry_type() == *type_)
90 })
91 .cloned()
92 .collect::<Vec<DomRoot<PerformanceEntry>>>();
93
94 result.sort_by(|a, b| {
96 a.start_time()
97 .partial_cmp(&b.start_time())
98 .unwrap_or(Ordering::Equal)
99 });
100
101 result
103 }
104
105 pub(crate) fn clear_entries_by_name_and_type(
106 &mut self,
107 name: Option<DOMString>,
108 entry_type: EntryType,
109 ) {
110 self.entries.retain(|e| {
111 e.entry_type() != entry_type || name.as_ref().is_some_and(|name_| e.name() != name_)
112 });
113 }
114
115 fn get_last_entry_start_time_with_name_and_type(
116 &self,
117 name: DOMString,
118 entry_type: EntryType,
119 ) -> Option<CrossProcessInstant> {
120 self.entries
121 .iter()
122 .rev()
123 .find(|e| e.entry_type() == entry_type && *e.name() == name)
124 .and_then(|entry| entry.start_time())
125 }
126}
127
128impl IntoIterator for PerformanceEntryList {
129 type Item = DomRoot<PerformanceEntry>;
130 type IntoIter = ::std::vec::IntoIter<DomRoot<PerformanceEntry>>;
131
132 fn into_iter(self) -> Self::IntoIter {
133 self.entries.into_iter()
134 }
135}
136
137#[derive(JSTraceable, MallocSizeOf)]
138struct PerformanceObserver {
139 observer: DomRoot<DOMPerformanceObserver>,
140 entry_types: Vec<EntryType>,
141}
142
143#[dom_struct]
144pub(crate) struct Performance {
145 eventtarget: EventTarget,
146 buffer: DomRefCell<PerformanceEntryList>,
147 observers: DomRefCell<Vec<PerformanceObserver>>,
148 pending_notification_observers_task: Cell<bool>,
149 #[no_trace]
150 time_origin: CrossProcessInstant,
153 resource_timing_buffer_size_limit: Cell<usize>,
157 resource_timing_buffer_current_size: Cell<usize>,
159 resource_timing_buffer_pending_full_event: Cell<bool>,
161 resource_timing_secondary_entries: DomRefCell<VecDeque<DomRoot<PerformanceEntry>>>,
163}
164
165impl Performance {
166 fn new_inherited(time_origin: CrossProcessInstant) -> Performance {
167 Performance {
168 eventtarget: EventTarget::new_inherited(),
169 buffer: DomRefCell::new(PerformanceEntryList::new(Vec::new())),
170 observers: DomRefCell::new(Vec::new()),
171 pending_notification_observers_task: Cell::new(false),
172 time_origin,
173 resource_timing_buffer_size_limit: Cell::new(250),
174 resource_timing_buffer_current_size: Cell::new(0),
175 resource_timing_buffer_pending_full_event: Cell::new(false),
176 resource_timing_secondary_entries: DomRefCell::new(VecDeque::new()),
177 }
178 }
179
180 pub(crate) fn new(
181 global: &GlobalScope,
182 navigation_start: CrossProcessInstant,
183 can_gc: CanGc,
184 ) -> DomRoot<Performance> {
185 reflect_dom_object(
186 Box::new(Performance::new_inherited(navigation_start)),
187 global,
188 can_gc,
189 )
190 }
191
192 pub(crate) fn to_dom_high_res_time_stamp(
193 &self,
194 instant: CrossProcessInstant,
195 ) -> DOMHighResTimeStamp {
196 (instant - self.time_origin).to_dom_high_res_time_stamp()
197 }
198
199 pub(crate) fn maybe_to_dom_high_res_time_stamp(
200 &self,
201 instant: Option<CrossProcessInstant>,
202 ) -> DOMHighResTimeStamp {
203 self.to_dom_high_res_time_stamp(instant.unwrap_or(self.time_origin))
204 }
205
206 pub(crate) fn clear_and_disable_performance_entry_buffer(&self) {
210 let mut buffer = self.buffer.borrow_mut();
211 buffer.entries.clear();
212 self.resource_timing_buffer_size_limit.set(0);
213 }
214
215 pub(crate) fn add_multiple_type_observer(
219 &self,
220 observer: &DOMPerformanceObserver,
221 entry_types: Vec<EntryType>,
222 ) {
223 let mut observers = self.observers.borrow_mut();
224 match observers.iter().position(|o| *o.observer == *observer) {
225 Some(p) => observers[p].entry_types = entry_types,
228 None => observers.push(PerformanceObserver {
230 observer: DomRoot::from_ref(observer),
231 entry_types,
232 }),
233 };
234 }
235
236 pub(crate) fn add_single_type_observer(
237 &self,
238 observer: &DOMPerformanceObserver,
239 entry_type: EntryType,
240 buffered: bool,
241 ) {
242 if buffered {
243 let buffer = self.buffer.borrow();
244 let mut new_entries = buffer.get_entries_by_name_and_type(None, Some(entry_type));
245 if !new_entries.is_empty() {
246 let mut obs_entries = observer.entries();
247 obs_entries.append(&mut new_entries);
248 observer.set_entries(obs_entries);
249 }
250
251 if !self.pending_notification_observers_task.get() {
252 self.pending_notification_observers_task.set(true);
253 let global = &self.global();
254 let owner = Trusted::new(&*global.performance());
255 self.global()
256 .task_manager()
257 .performance_timeline_task_source()
258 .queue(task!(notify_performance_observers: move || {
259 owner.root().notify_observers();
260 }));
261 }
262 }
263 let mut observers = self.observers.borrow_mut();
264 match observers.iter().position(|o| *o.observer == *observer) {
265 Some(p) => {
268 if !observers[p].entry_types.contains(&entry_type) {
270 observers[p].entry_types.push(entry_type)
271 }
272 },
273 None => observers.push(PerformanceObserver {
275 observer: DomRoot::from_ref(observer),
276 entry_types: vec![entry_type],
277 }),
278 };
279 }
280
281 pub(crate) fn remove_observer(&self, observer: &DOMPerformanceObserver) {
283 let mut observers = self.observers.borrow_mut();
284 let index = match observers.iter().position(|o| &(*o.observer) == observer) {
285 Some(p) => p,
286 None => return,
287 };
288
289 observers.remove(index);
290 }
291
292 pub(crate) fn queue_entry(&self, entry: &PerformanceEntry) -> Option<usize> {
301 if entry.entry_type() == EntryType::Resource && !self.should_queue_resource_entry(entry) {
303 return None;
304 }
305
306 for observer in self
311 .observers
312 .borrow()
313 .iter()
314 .filter(|o| o.entry_types.contains(&entry.entry_type()))
315 {
316 observer.observer.queue_entry(entry);
317 }
318
319 self.buffer
322 .borrow_mut()
323 .entries
324 .push(DomRoot::from_ref(entry));
325
326 let entry_last_index = self.buffer.borrow_mut().entries.len() - 1;
327
328 if self.pending_notification_observers_task.get() {
331 return None;
332 }
333
334 self.pending_notification_observers_task.set(true);
337
338 let global = &self.global();
339 let owner = Trusted::new(&*global.performance());
340 self.global()
341 .task_manager()
342 .performance_timeline_task_source()
343 .queue(task!(notify_performance_observers: move || {
344 owner.root().notify_observers();
345 }));
346
347 Some(entry_last_index)
348 }
349
350 pub(crate) fn notify_observers(&self) {
355 self.pending_notification_observers_task.set(false);
357
358 let observers: Vec<DomRoot<DOMPerformanceObserver>> = self
364 .observers
365 .borrow()
366 .iter()
367 .map(|o| DomRoot::from_ref(&*o.observer))
368 .collect();
369
370 for o in observers.iter() {
372 o.notify(CanGc::deprecated_note());
373 }
374 }
375
376 fn can_add_resource_timing_entry(&self) -> bool {
378 self.resource_timing_buffer_current_size.get() <=
382 self.resource_timing_buffer_size_limit.get()
383 }
384
385 fn copy_secondary_resource_timing_buffer(&self) {
387 while self.can_add_resource_timing_entry() {
389 let entry = self
391 .resource_timing_secondary_entries
392 .borrow_mut()
393 .pop_front();
394 if let Some(ref entry) = entry {
395 self.buffer
397 .borrow_mut()
398 .entries
399 .push(DomRoot::from_ref(entry));
400 self.resource_timing_buffer_current_size
402 .set(self.resource_timing_buffer_current_size.get() + 1);
403 } else {
407 break;
408 }
409 }
410 }
411 fn fire_buffer_full_event(&self, can_gc: CanGc) {
414 while !self.resource_timing_secondary_entries.borrow().is_empty() {
415 let no_of_excess_entries_before = self.resource_timing_secondary_entries.borrow().len();
416
417 if !self.can_add_resource_timing_entry() {
418 self.upcast::<EventTarget>()
419 .fire_event(atom!("resourcetimingbufferfull"), can_gc);
420 }
421 self.copy_secondary_resource_timing_buffer();
422 let no_of_excess_entries_after = self.resource_timing_secondary_entries.borrow().len();
423 if no_of_excess_entries_before <= no_of_excess_entries_after {
424 self.resource_timing_secondary_entries.borrow_mut().clear();
425 break;
426 }
427 }
428 self.resource_timing_buffer_pending_full_event.set(false);
429 }
430
431 fn should_queue_resource_entry(&self, entry: &PerformanceEntry) -> bool {
433 if !self.resource_timing_buffer_pending_full_event.get() {
435 if self.can_add_resource_timing_entry() {
436 self.resource_timing_buffer_current_size
440 .set(self.resource_timing_buffer_current_size.get() + 1);
441 return true;
443 }
444
445 self.resource_timing_buffer_pending_full_event.set(true);
447 let performance = Trusted::new(self);
449 self.global()
450 .task_manager()
451 .performance_timeline_task_source()
452 .queue(task!(fire_a_buffer_full_event: move || {
453 performance.root().fire_buffer_full_event(CanGc::deprecated_note());
454 }));
455 }
456
457 self.resource_timing_secondary_entries
459 .borrow_mut()
460 .push_back(DomRoot::from_ref(entry));
461
462 false
465 }
466
467 pub(crate) fn update_entry(&self, index: usize, entry: &PerformanceEntry) {
468 if let Some(e) = self.buffer.borrow_mut().entries.get_mut(index) {
469 *e = DomRoot::from_ref(entry);
470 }
471 }
472
473 fn convert_a_name_to_a_timestamp(&self, name: &str) -> Fallible<CrossProcessInstant> {
475 let Some(window) = DomRoot::downcast::<Window>(self.global()) else {
477 return Err(Error::Type(cformat!(
478 "Cannot use {name} from non-window global"
479 )));
480 };
481
482 if name == "navigationStart" {
484 return Ok(self.time_origin);
485 }
486
487 let document = window.Document();
493 let end_time = match name {
494 "unloadEventStart" => document.get_unload_event_start(),
495 "unloadEventEnd" => document.get_unload_event_end(),
496 "domInteractive" => document.get_dom_interactive(),
497 "domContentLoadedEventStart" => document.get_dom_content_loaded_event_start(),
498 "domContentLoadedEventEnd" => document.get_dom_content_loaded_event_end(),
499 "domComplete" => document.get_dom_complete(),
500 "loadEventStart" => document.get_load_event_start(),
501 "loadEventEnd" => document.get_load_event_end(),
502 other => {
503 if cfg!(debug_assertions) {
504 unreachable!("{other:?} is not the name of a timestamp");
505 }
506 return Err(Error::Operation(None));
507 },
508 };
509 let Some(end_time) = end_time else {
511 return Err(Error::InvalidAccess(Some(format!(
512 "{name} hasn't happened yet"
513 ))));
514 };
515
516 Ok(end_time)
518 }
519
520 fn convert_a_mark_to_a_timestamp(
522 &self,
523 mark: &StringOrDouble,
524 ) -> Fallible<CrossProcessInstant> {
525 match mark {
526 StringOrDouble::String(name) => {
527 if matches!(
532 &*name.str(),
533 "navigationStart" |
534 "unloadEventStart" |
535 "unloadEventEnd" |
536 "domInteractive" |
537 "domContentLoadedEventStart" |
538 "domContentLoadedEventEnd" |
539 "domComplete" |
540 "loadEventStart" |
541 "loadEventEnd"
542 ) {
543 self.convert_a_name_to_a_timestamp(&name.str())
544 }
545 else {
549 self.buffer
550 .borrow()
551 .get_last_entry_start_time_with_name_and_type(name.clone(), EntryType::Mark)
552 .ok_or(Error::Syntax(Some(format!(
553 "No PerformanceMark named {name} exists"
554 ))))
555 }
556 },
557 StringOrDouble::Double(timestamp) => {
559 if timestamp.is_sign_negative() {
561 return Err(Error::Type(c"Time stamps must not be negative".to_owned()));
562 }
563
564 Ok(self.time_origin + Duration::milliseconds(timestamp.round() as i64))
567 },
568 }
569 }
570}
571
572impl PerformanceMethods<crate::DomTypeHolder> for Performance {
573 fn Timing(&self) -> DomRoot<PerformanceNavigationTiming> {
575 let entries = self.GetEntriesByType(DOMString::from("navigation"));
576 if !entries.is_empty() {
577 return DomRoot::from_ref(
578 entries[0]
579 .downcast::<PerformanceNavigationTiming>()
580 .unwrap(),
581 );
582 }
583 unreachable!("Are we trying to expose Performance.timing in workers?");
584 }
585
586 fn Navigation(&self) -> DomRoot<PerformanceNavigation> {
588 PerformanceNavigation::new(&self.global(), CanGc::deprecated_note())
589 }
590
591 fn Now(&self) -> DOMHighResTimeStamp {
593 self.to_dom_high_res_time_stamp(CrossProcessInstant::now())
594 }
595
596 fn TimeOrigin(&self) -> DOMHighResTimeStamp {
598 (self.time_origin - CrossProcessInstant::epoch()).to_dom_high_res_time_stamp()
599 }
600
601 fn GetEntries(&self) -> Vec<DomRoot<PerformanceEntry>> {
603 self.buffer
606 .borrow()
607 .get_entries_by_name_and_type(None, None)
608 }
609
610 fn GetEntriesByType(&self, entry_type: DOMString) -> Vec<DomRoot<PerformanceEntry>> {
612 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
613 return Vec::new();
614 };
615 self.buffer
616 .borrow()
617 .get_entries_by_name_and_type(None, Some(entry_type))
618 }
619
620 fn GetEntriesByName(
622 &self,
623 name: DOMString,
624 entry_type: Option<DOMString>,
625 ) -> Vec<DomRoot<PerformanceEntry>> {
626 let entry_type = match entry_type {
627 Some(entry_type) => {
628 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
629 return Vec::new();
630 };
631 Some(entry_type)
632 },
633 None => None,
634 };
635 self.buffer
636 .borrow()
637 .get_entries_by_name_and_type(Some(name), entry_type)
638 }
639
640 fn Mark(&self, mark_name: DOMString) -> Fallible<DomRoot<PerformanceMark>> {
642 let global = self.global();
643 if global.is::<Window>() && INVALID_ENTRY_NAMES.contains(&&*mark_name.str()) {
645 return Err(Error::Syntax(None));
646 }
647
648 let entry = PerformanceMark::new(
650 &global,
651 mark_name,
652 CrossProcessInstant::now(),
653 Duration::ZERO,
654 );
655
656 self.queue_entry(entry.upcast::<PerformanceEntry>());
658
659 Ok(entry)
663 }
664
665 fn ClearMarks(&self, mark_name: Option<DOMString>) {
667 self.buffer
668 .borrow_mut()
669 .clear_entries_by_name_and_type(mark_name, EntryType::Mark);
670 }
671
672 fn Measure(
674 &self,
675 measure_name: DOMString,
676 start_or_measure_options: StringOrPerformanceMeasureOptions,
677 end_mark: Option<DOMString>,
678 ) -> Fallible<DomRoot<PerformanceMeasure>> {
679 if let StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) =
682 &start_or_measure_options
683 {
684 if options.start.is_some() || options.duration.is_some() || options.end.is_some() {
685 if end_mark.is_some() {
687 return Err(Error::Type(
688 c"Must not provide endMark if PerformanceMeasureOptions is also provided"
689 .to_owned(),
690 ));
691 }
692
693 if options.start.is_none() && options.end.is_none() {
695 return Err(Error::Type(c"Either 'start' or 'end' member of PerformanceMeasureOptions must be provided".to_owned()));
696 }
697
698 if options.start.is_some() && options.duration.is_some() && options.end.is_some() {
700 return Err(Error::Type(c"Either 'start' or 'end' or 'duration' member of PerformanceMeasureOptions must be omitted".to_owned()));
701 }
702 }
703 }
704
705 let end_time = if let Some(end_mark) = end_mark {
709 self.convert_a_mark_to_a_timestamp(&StringOrDouble::String(end_mark))?
710 } else {
711 match &start_or_measure_options {
712 StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) => {
713 if let Some(end) = &options.end {
717 self.convert_a_mark_to_a_timestamp(end)?
718 }
719 else if let Some((start, duration)) =
722 options.start.as_ref().zip(options.duration)
723 {
724 let start = self.convert_a_mark_to_a_timestamp(start)?;
727
728 let duration = self
731 .convert_a_mark_to_a_timestamp(&StringOrDouble::Double(duration))? -
732 self.time_origin;
733
734 start + duration
736 } else {
737 CrossProcessInstant::now()
740 }
741 },
742 _ => {
743 CrossProcessInstant::now()
746 },
747 }
748 };
749
750 let start_time = match start_or_measure_options {
752 StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) => {
753 if let Some(start) = &options.start {
757 self.convert_a_mark_to_a_timestamp(start)?
758 }
759 else if let Some((duration, end)) = options.duration.zip(options.end.as_ref()) {
762 let duration = self
765 .convert_a_mark_to_a_timestamp(&StringOrDouble::Double(duration))? -
766 self.time_origin;
767
768 let end = self.convert_a_mark_to_a_timestamp(end)?;
771
772 end - duration
774 }
775 else {
777 self.time_origin
778 }
779 },
780 StringOrPerformanceMeasureOptions::String(string) => {
781 self.convert_a_mark_to_a_timestamp(&StringOrDouble::String(string))?
784 },
785 };
786
787 let entry = PerformanceMeasure::new(
795 &self.global(),
796 measure_name,
797 start_time,
798 end_time - start_time,
799 );
800
801 self.queue_entry(entry.upcast::<PerformanceEntry>());
804
805 Ok(entry)
807 }
808
809 fn ClearMeasures(&self, measure_name: Option<DOMString>) {
811 self.buffer
812 .borrow_mut()
813 .clear_entries_by_name_and_type(measure_name, EntryType::Measure);
814 }
815 fn ClearResourceTimings(&self) {
817 self.buffer
818 .borrow_mut()
819 .clear_entries_by_name_and_type(None, EntryType::Resource);
820 self.resource_timing_buffer_current_size.set(0);
821 }
822
823 fn SetResourceTimingBufferSize(&self, max_size: u32) {
825 self.resource_timing_buffer_size_limit
826 .set(max_size as usize);
827 }
828
829 event_handler!(
831 resourcetimingbufferfull,
832 GetOnresourcetimingbufferfull,
833 SetOnresourcetimingbufferfull
834 );
835}
836
837pub(crate) trait ToDOMHighResTimeStamp {
838 fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp;
839}
840
841impl ToDOMHighResTimeStamp for Duration {
842 fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp {
843 let microseconds_rounded = (self.whole_microseconds() as f64 / 10.).floor() * 10.;
849 Finite::wrap(microseconds_rounded / 1000.)
850 }
851}