1use std::cell::Cell;
6use std::cmp::Ordering;
7use std::collections::VecDeque;
8
9use dom_struct::dom_struct;
10use script_bindings::codegen::GenericUnionTypes::StringOrPerformanceMeasureOptions;
11use servo_base::cross_process_instant::CrossProcessInstant;
12use time::Duration;
13
14use super::performanceentry::{EntryType, PerformanceEntry};
15use super::performancemark::PerformanceMark;
16use super::performancemeasure::PerformanceMeasure;
17use super::performancenavigation::PerformanceNavigation;
18use super::performancenavigationtiming::PerformanceNavigationTiming;
19use super::performanceobserver::PerformanceObserver as DOMPerformanceObserver;
20use crate::dom::bindings::cell::DomRefCell;
21use crate::dom::bindings::codegen::Bindings::PerformanceBinding::{
22 DOMHighResTimeStamp, PerformanceEntryList as DOMPerformanceEntryList, PerformanceMethods,
23};
24use crate::dom::bindings::codegen::UnionTypes::StringOrDouble;
25use crate::dom::bindings::error::{Error, Fallible};
26use crate::dom::bindings::inheritance::Castable;
27use crate::dom::bindings::num::Finite;
28use crate::dom::bindings::refcounted::Trusted;
29use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object};
30use crate::dom::bindings::root::DomRoot;
31use crate::dom::bindings::str::DOMString;
32use crate::dom::eventtarget::EventTarget;
33use crate::dom::globalscope::GlobalScope;
34use crate::dom::window::Window;
35use crate::script_runtime::CanGc;
36
37const INVALID_ENTRY_NAMES: &[&str] = &[
38 "navigationStart",
39 "unloadEventStart",
40 "unloadEventEnd",
41 "redirectStart",
42 "redirectEnd",
43 "fetchStart",
44 "domainLookupStart",
45 "domainLookupEnd",
46 "connectStart",
47 "connectEnd",
48 "secureConnectionStart",
49 "requestStart",
50 "responseStart",
51 "responseEnd",
52 "domLoading",
53 "domInteractive",
54 "domContentLoadedEventStart",
55 "domContentLoadedEventEnd",
56 "domComplete",
57 "loadEventStart",
58 "loadEventEnd",
59];
60
61#[derive(JSTraceable, MallocSizeOf)]
64pub(crate) struct PerformanceEntryList {
65 entries: DOMPerformanceEntryList,
67}
68
69impl PerformanceEntryList {
70 pub(crate) fn new(entries: DOMPerformanceEntryList) -> Self {
71 PerformanceEntryList { entries }
72 }
73
74 pub(crate) fn get_entries_by_name_and_type(
76 &self,
77 name: Option<DOMString>,
78 entry_type: Option<EntryType>,
79 ) -> Vec<DomRoot<PerformanceEntry>> {
80 let mut result = self
81 .entries
82 .iter()
83 .filter(|e| {
84 name.as_ref().is_none_or(|name_| *e.name() == *name_) &&
85 entry_type
86 .as_ref()
87 .is_none_or(|type_| e.entry_type() == *type_)
88 })
89 .cloned()
90 .collect::<Vec<DomRoot<PerformanceEntry>>>();
91
92 result.sort_by(|a, b| {
94 a.start_time()
95 .partial_cmp(&b.start_time())
96 .unwrap_or(Ordering::Equal)
97 });
98
99 result
101 }
102
103 pub(crate) fn clear_entries_by_name_and_type(
104 &mut self,
105 name: Option<DOMString>,
106 entry_type: EntryType,
107 ) {
108 self.entries.retain(|e| {
109 e.entry_type() != entry_type || name.as_ref().is_some_and(|name_| e.name() != name_)
110 });
111 }
112
113 fn get_last_entry_start_time_with_name_and_type(
114 &self,
115 name: DOMString,
116 entry_type: EntryType,
117 ) -> Option<CrossProcessInstant> {
118 self.entries
119 .iter()
120 .rev()
121 .find(|e| e.entry_type() == entry_type && *e.name() == name)
122 .and_then(|entry| entry.start_time())
123 }
124}
125
126impl IntoIterator for PerformanceEntryList {
127 type Item = DomRoot<PerformanceEntry>;
128 type IntoIter = ::std::vec::IntoIter<DomRoot<PerformanceEntry>>;
129
130 fn into_iter(self) -> Self::IntoIter {
131 self.entries.into_iter()
132 }
133}
134
135#[derive(JSTraceable, MallocSizeOf)]
136struct PerformanceObserver {
137 observer: DomRoot<DOMPerformanceObserver>,
138 entry_types: Vec<EntryType>,
139}
140
141#[dom_struct]
142pub(crate) struct Performance {
143 eventtarget: EventTarget,
144 buffer: DomRefCell<PerformanceEntryList>,
145 observers: DomRefCell<Vec<PerformanceObserver>>,
146 pending_notification_observers_task: Cell<bool>,
147 #[no_trace]
148 time_origin: CrossProcessInstant,
151 resource_timing_buffer_size_limit: Cell<usize>,
155 resource_timing_buffer_current_size: Cell<usize>,
157 resource_timing_buffer_pending_full_event: Cell<bool>,
159 resource_timing_secondary_entries: DomRefCell<VecDeque<DomRoot<PerformanceEntry>>>,
161}
162
163impl Performance {
164 fn new_inherited(time_origin: CrossProcessInstant) -> Performance {
165 Performance {
166 eventtarget: EventTarget::new_inherited(),
167 buffer: DomRefCell::new(PerformanceEntryList::new(Vec::new())),
168 observers: DomRefCell::new(Vec::new()),
169 pending_notification_observers_task: Cell::new(false),
170 time_origin,
171 resource_timing_buffer_size_limit: Cell::new(250),
172 resource_timing_buffer_current_size: Cell::new(0),
173 resource_timing_buffer_pending_full_event: Cell::new(false),
174 resource_timing_secondary_entries: DomRefCell::new(VecDeque::new()),
175 }
176 }
177
178 pub(crate) fn new(
179 global: &GlobalScope,
180 navigation_start: CrossProcessInstant,
181 can_gc: CanGc,
182 ) -> DomRoot<Performance> {
183 reflect_dom_object(
184 Box::new(Performance::new_inherited(navigation_start)),
185 global,
186 can_gc,
187 )
188 }
189
190 pub(crate) fn to_dom_high_res_time_stamp(
191 &self,
192 instant: CrossProcessInstant,
193 ) -> DOMHighResTimeStamp {
194 (instant - self.time_origin).to_dom_high_res_time_stamp()
195 }
196
197 pub(crate) fn maybe_to_dom_high_res_time_stamp(
198 &self,
199 instant: Option<CrossProcessInstant>,
200 ) -> DOMHighResTimeStamp {
201 self.to_dom_high_res_time_stamp(instant.unwrap_or(self.time_origin))
202 }
203
204 pub(crate) fn clear_and_disable_performance_entry_buffer(&self) {
208 let mut buffer = self.buffer.borrow_mut();
209 buffer.entries.clear();
210 self.resource_timing_buffer_size_limit.set(0);
211 }
212
213 pub(crate) fn add_multiple_type_observer(
217 &self,
218 observer: &DOMPerformanceObserver,
219 entry_types: Vec<EntryType>,
220 ) {
221 let mut observers = self.observers.borrow_mut();
222 match observers.iter().position(|o| *o.observer == *observer) {
223 Some(p) => observers[p].entry_types = entry_types,
226 None => observers.push(PerformanceObserver {
228 observer: DomRoot::from_ref(observer),
229 entry_types,
230 }),
231 };
232 }
233
234 pub(crate) fn add_single_type_observer(
235 &self,
236 observer: &DOMPerformanceObserver,
237 entry_type: EntryType,
238 buffered: bool,
239 ) {
240 if buffered {
241 let buffer = self.buffer.borrow();
242 let mut new_entries = buffer.get_entries_by_name_and_type(None, Some(entry_type));
243 if !new_entries.is_empty() {
244 let mut obs_entries = observer.entries();
245 obs_entries.append(&mut new_entries);
246 observer.set_entries(obs_entries);
247 }
248
249 if !self.pending_notification_observers_task.get() {
250 self.pending_notification_observers_task.set(true);
251 let global = &self.global();
252 let owner = Trusted::new(&*global.performance());
253 self.global()
254 .task_manager()
255 .performance_timeline_task_source()
256 .queue(task!(notify_performance_observers: move || {
257 owner.root().notify_observers();
258 }));
259 }
260 }
261 let mut observers = self.observers.borrow_mut();
262 match observers.iter().position(|o| *o.observer == *observer) {
263 Some(p) => {
266 if !observers[p].entry_types.contains(&entry_type) {
268 observers[p].entry_types.push(entry_type)
269 }
270 },
271 None => observers.push(PerformanceObserver {
273 observer: DomRoot::from_ref(observer),
274 entry_types: vec![entry_type],
275 }),
276 };
277 }
278
279 pub(crate) fn remove_observer(&self, observer: &DOMPerformanceObserver) {
281 let mut observers = self.observers.borrow_mut();
282 let index = match observers.iter().position(|o| &(*o.observer) == observer) {
283 Some(p) => p,
284 None => return,
285 };
286
287 observers.remove(index);
288 }
289
290 pub(crate) fn queue_entry(&self, entry: &PerformanceEntry) -> Option<usize> {
299 if entry.entry_type() == EntryType::Resource && !self.should_queue_resource_entry(entry) {
301 return None;
302 }
303
304 for observer in self
309 .observers
310 .borrow()
311 .iter()
312 .filter(|o| o.entry_types.contains(&entry.entry_type()))
313 {
314 observer.observer.queue_entry(entry);
315 }
316
317 self.buffer
320 .borrow_mut()
321 .entries
322 .push(DomRoot::from_ref(entry));
323
324 let entry_last_index = self.buffer.borrow_mut().entries.len() - 1;
325
326 if self.pending_notification_observers_task.get() {
329 return None;
330 }
331
332 self.pending_notification_observers_task.set(true);
335
336 let global = &self.global();
337 let owner = Trusted::new(&*global.performance());
338 self.global()
339 .task_manager()
340 .performance_timeline_task_source()
341 .queue(task!(notify_performance_observers: move || {
342 owner.root().notify_observers();
343 }));
344
345 Some(entry_last_index)
346 }
347
348 pub(crate) fn notify_observers(&self) {
353 self.pending_notification_observers_task.set(false);
355
356 let observers: Vec<DomRoot<DOMPerformanceObserver>> = self
362 .observers
363 .borrow()
364 .iter()
365 .map(|o| DomRoot::from_ref(&*o.observer))
366 .collect();
367
368 for o in observers.iter() {
370 o.notify(CanGc::note());
371 }
372 }
373
374 fn can_add_resource_timing_entry(&self) -> bool {
376 self.resource_timing_buffer_current_size.get() <=
380 self.resource_timing_buffer_size_limit.get()
381 }
382
383 fn copy_secondary_resource_timing_buffer(&self) {
385 while self.can_add_resource_timing_entry() {
387 let entry = self
389 .resource_timing_secondary_entries
390 .borrow_mut()
391 .pop_front();
392 if let Some(ref entry) = entry {
393 self.buffer
395 .borrow_mut()
396 .entries
397 .push(DomRoot::from_ref(entry));
398 self.resource_timing_buffer_current_size
400 .set(self.resource_timing_buffer_current_size.get() + 1);
401 } else {
405 break;
406 }
407 }
408 }
409 fn fire_buffer_full_event(&self, can_gc: CanGc) {
412 while !self.resource_timing_secondary_entries.borrow().is_empty() {
413 let no_of_excess_entries_before = self.resource_timing_secondary_entries.borrow().len();
414
415 if !self.can_add_resource_timing_entry() {
416 self.upcast::<EventTarget>()
417 .fire_event(atom!("resourcetimingbufferfull"), can_gc);
418 }
419 self.copy_secondary_resource_timing_buffer();
420 let no_of_excess_entries_after = self.resource_timing_secondary_entries.borrow().len();
421 if no_of_excess_entries_before <= no_of_excess_entries_after {
422 self.resource_timing_secondary_entries.borrow_mut().clear();
423 break;
424 }
425 }
426 self.resource_timing_buffer_pending_full_event.set(false);
427 }
428
429 fn should_queue_resource_entry(&self, entry: &PerformanceEntry) -> bool {
431 if !self.resource_timing_buffer_pending_full_event.get() {
433 if self.can_add_resource_timing_entry() {
434 self.resource_timing_buffer_current_size
438 .set(self.resource_timing_buffer_current_size.get() + 1);
439 return true;
441 }
442
443 self.resource_timing_buffer_pending_full_event.set(true);
445 let performance = Trusted::new(self);
447 self.global()
448 .task_manager()
449 .performance_timeline_task_source()
450 .queue(task!(fire_a_buffer_full_event: move || {
451 performance.root().fire_buffer_full_event(CanGc::note());
452 }));
453 }
454
455 self.resource_timing_secondary_entries
457 .borrow_mut()
458 .push_back(DomRoot::from_ref(entry));
459
460 false
463 }
464
465 pub(crate) fn update_entry(&self, index: usize, entry: &PerformanceEntry) {
466 if let Some(e) = self.buffer.borrow_mut().entries.get_mut(index) {
467 *e = DomRoot::from_ref(entry);
468 }
469 }
470
471 fn convert_a_mark_to_a_timestamp(
473 &self,
474 mark: &StringOrDouble,
475 ) -> Fallible<CrossProcessInstant> {
476 match mark {
477 StringOrDouble::String(name) => {
478 self.buffer
486 .borrow()
487 .get_last_entry_start_time_with_name_and_type(name.clone(), EntryType::Mark)
488 .ok_or(Error::Syntax(Some(format!(
489 "No PerformanceMark named {name} exists"
490 ))))
491 },
492 StringOrDouble::Double(timestamp) => {
494 if timestamp.is_sign_negative() {
496 return Err(Error::Type(c"Time stamps must not be negative".to_owned()));
497 }
498
499 Ok(self.time_origin + Duration::milliseconds(timestamp.round() as i64))
502 },
503 }
504 }
505}
506
507impl PerformanceMethods<crate::DomTypeHolder> for Performance {
508 fn Timing(&self) -> DomRoot<PerformanceNavigationTiming> {
510 let entries = self.GetEntriesByType(DOMString::from("navigation"));
511 if !entries.is_empty() {
512 return DomRoot::from_ref(
513 entries[0]
514 .downcast::<PerformanceNavigationTiming>()
515 .unwrap(),
516 );
517 }
518 unreachable!("Are we trying to expose Performance.timing in workers?");
519 }
520
521 fn Navigation(&self) -> DomRoot<PerformanceNavigation> {
523 PerformanceNavigation::new(&self.global(), CanGc::note())
524 }
525
526 fn Now(&self) -> DOMHighResTimeStamp {
528 self.to_dom_high_res_time_stamp(CrossProcessInstant::now())
529 }
530
531 fn TimeOrigin(&self) -> DOMHighResTimeStamp {
533 (self.time_origin - CrossProcessInstant::epoch()).to_dom_high_res_time_stamp()
534 }
535
536 fn GetEntries(&self) -> Vec<DomRoot<PerformanceEntry>> {
538 self.buffer
541 .borrow()
542 .get_entries_by_name_and_type(None, None)
543 }
544
545 fn GetEntriesByType(&self, entry_type: DOMString) -> Vec<DomRoot<PerformanceEntry>> {
547 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
548 return Vec::new();
549 };
550 self.buffer
551 .borrow()
552 .get_entries_by_name_and_type(None, Some(entry_type))
553 }
554
555 fn GetEntriesByName(
557 &self,
558 name: DOMString,
559 entry_type: Option<DOMString>,
560 ) -> Vec<DomRoot<PerformanceEntry>> {
561 let entry_type = match entry_type {
562 Some(entry_type) => {
563 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
564 return Vec::new();
565 };
566 Some(entry_type)
567 },
568 None => None,
569 };
570 self.buffer
571 .borrow()
572 .get_entries_by_name_and_type(Some(name), entry_type)
573 }
574
575 fn Mark(&self, mark_name: DOMString) -> Fallible<()> {
577 let global = self.global();
578 if global.is::<Window>() && INVALID_ENTRY_NAMES.contains(&&*mark_name.str()) {
580 return Err(Error::Syntax(None));
581 }
582
583 let entry = PerformanceMark::new(
585 &global,
586 mark_name,
587 CrossProcessInstant::now(),
588 Duration::ZERO,
589 );
590
591 self.queue_entry(entry.upcast::<PerformanceEntry>());
593
594 Ok(())
598 }
599
600 fn ClearMarks(&self, mark_name: Option<DOMString>) {
602 self.buffer
603 .borrow_mut()
604 .clear_entries_by_name_and_type(mark_name, EntryType::Mark);
605 }
606
607 fn Measure(
609 &self,
610 measure_name: DOMString,
611 start_or_measure_options: StringOrPerformanceMeasureOptions,
612 end_mark: Option<DOMString>,
613 ) -> Fallible<DomRoot<PerformanceMeasure>> {
614 if let StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) =
617 &start_or_measure_options
618 {
619 if options.start.is_some() || options.duration.is_some() || options.end.is_some() {
620 if end_mark.is_some() {
622 return Err(Error::Type(
623 c"Must not provide endMark if PerformanceMeasureOptions is also provided"
624 .to_owned(),
625 ));
626 }
627
628 if options.start.is_none() && options.end.is_none() {
630 return Err(Error::Type(c"Either 'start' or 'end' member of PerformanceMeasureOptions must be provided".to_owned()));
631 }
632
633 if options.start.is_some() && options.duration.is_some() && options.end.is_some() {
635 return Err(Error::Type(c"Either 'start' or 'end' or 'duration' member of PerformanceMeasureOptions must be omitted".to_owned()));
636 }
637 }
638 }
639
640 let end_time = if let Some(end_mark) = end_mark {
644 self.convert_a_mark_to_a_timestamp(&StringOrDouble::String(end_mark))?
645 } else {
646 match &start_or_measure_options {
647 StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) => {
648 if let Some(end) = &options.end {
652 self.convert_a_mark_to_a_timestamp(end)?
653 }
654 else if let Some((start, duration)) =
657 options.start.as_ref().zip(options.duration)
658 {
659 let start = self.convert_a_mark_to_a_timestamp(start)?;
662
663 let duration = self
666 .convert_a_mark_to_a_timestamp(&StringOrDouble::Double(duration))? -
667 self.time_origin;
668
669 start + duration
671 } else {
672 CrossProcessInstant::now()
675 }
676 },
677 _ => {
678 CrossProcessInstant::now()
681 },
682 }
683 };
684
685 let start_time = match start_or_measure_options {
687 StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) => {
688 if let Some(start) = &options.start {
692 self.convert_a_mark_to_a_timestamp(start)?
693 }
694 else if let Some((duration, end)) = options.duration.zip(options.end.as_ref()) {
697 let duration = self
700 .convert_a_mark_to_a_timestamp(&StringOrDouble::Double(duration))? -
701 self.time_origin;
702
703 let end = self.convert_a_mark_to_a_timestamp(end)?;
706
707 end - duration
709 }
710 else {
712 self.time_origin
713 }
714 },
715 StringOrPerformanceMeasureOptions::String(string) => {
716 self.convert_a_mark_to_a_timestamp(&StringOrDouble::String(string))?
719 },
720 };
721
722 let entry = PerformanceMeasure::new(
730 &self.global(),
731 measure_name,
732 start_time,
733 end_time - start_time,
734 );
735
736 self.queue_entry(entry.upcast::<PerformanceEntry>());
739
740 Ok(entry)
742 }
743
744 fn ClearMeasures(&self, measure_name: Option<DOMString>) {
746 self.buffer
747 .borrow_mut()
748 .clear_entries_by_name_and_type(measure_name, EntryType::Measure);
749 }
750 fn ClearResourceTimings(&self) {
752 self.buffer
753 .borrow_mut()
754 .clear_entries_by_name_and_type(None, EntryType::Resource);
755 self.resource_timing_buffer_current_size.set(0);
756 }
757
758 fn SetResourceTimingBufferSize(&self, max_size: u32) {
760 self.resource_timing_buffer_size_limit
761 .set(max_size as usize);
762 }
763
764 event_handler!(
766 resourcetimingbufferfull,
767 GetOnresourcetimingbufferfull,
768 SetOnresourcetimingbufferfull
769 );
770}
771
772pub(crate) trait ToDOMHighResTimeStamp {
773 fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp;
774}
775
776impl ToDOMHighResTimeStamp for Duration {
777 fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp {
778 let microseconds_rounded = (self.whole_microseconds() as f64 / 10.).floor() * 10.;
784 Finite::wrap(microseconds_rounded / 1000.)
785 }
786}