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;
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::performanceobserver::PerformanceObserver as DOMPerformanceObserver;
27use crate::dom::PERFORMANCE_TIMING_ATTRIBUTES;
28use crate::dom::bindings::codegen::Bindings::PerformanceBinding::{
29 DOMHighResTimeStamp, 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::{AsHandleValue, Dom, DomRoot};
38use crate::dom::bindings::str::DOMString;
39use crate::dom::bindings::structuredclone;
40use crate::dom::eventtarget::EventTarget;
41use crate::dom::globalscope::GlobalScope;
42use crate::dom::performance::performancetiming::PerformanceTiming;
43use crate::dom::window::Window;
44
45#[derive(JSTraceable, MallocSizeOf)]
48#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
49pub(crate) struct PerformanceEntryList {
50 entries: Vec<Dom<PerformanceEntry>>,
52}
53
54impl PerformanceEntryList {
55 pub(crate) fn new(entries: Vec<DomRoot<PerformanceEntry>>) -> Self {
56 PerformanceEntryList {
57 entries: entries.into_iter().map(|entry| entry.as_traced()).collect(),
58 }
59 }
60
61 pub(crate) fn get_entries_by_name_and_type(
63 &self,
64 name: Option<DOMString>,
65 entry_type: Option<EntryType>,
66 ) -> Vec<DomRoot<PerformanceEntry>> {
67 let mut result = self
68 .entries
69 .iter()
70 .filter(|e| {
71 name.as_ref().is_none_or(|name_| *e.name() == *name_) &&
72 entry_type
73 .as_ref()
74 .is_none_or(|type_| e.entry_type() == *type_)
75 })
76 .map(|entry| entry.as_rooted())
77 .collect::<Vec<DomRoot<PerformanceEntry>>>();
78
79 result.sort_by(|a, b| {
81 a.start_time()
82 .partial_cmp(&b.start_time())
83 .unwrap_or(Ordering::Equal)
84 });
85
86 result
88 }
89
90 pub(crate) fn clear_entries_by_name_and_type(
91 &mut self,
92 name: Option<DOMString>,
93 entry_type: EntryType,
94 ) {
95 self.entries.retain(|e| {
96 e.entry_type() != entry_type || name.as_ref().is_some_and(|name_| e.name() != name_)
97 });
98 }
99
100 fn get_last_entry_start_time_with_name_and_type(
101 &self,
102 name: DOMString,
103 entry_type: EntryType,
104 ) -> Option<CrossProcessInstant> {
105 self.entries
106 .iter()
107 .rev()
108 .find(|e| e.entry_type() == entry_type && *e.name() == name)
109 .and_then(|entry| entry.start_time())
110 }
111}
112
113#[derive(JSTraceable, MallocSizeOf)]
114#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
115struct PerformanceObserver {
116 observer: Dom<DOMPerformanceObserver>,
117 entry_types: Vec<EntryType>,
118}
119
120#[dom_struct]
121pub(crate) struct Performance {
122 eventtarget: EventTarget,
123 buffer: DomRefCell<PerformanceEntryList>,
124 observers: DomRefCell<Vec<PerformanceObserver>>,
125 pending_notification_observers_task: Cell<bool>,
126 #[no_trace]
127 time_origin: CrossProcessInstant,
130 resource_timing_buffer_size_limit: Cell<usize>,
134 resource_timing_buffer_current_size: Cell<usize>,
136 resource_timing_buffer_pending_full_event: Cell<bool>,
138 resource_timing_secondary_entries: DomRefCell<VecDeque<Dom<PerformanceEntry>>>,
140 timing: Dom<PerformanceTiming>,
141 navigation: Dom<PerformanceNavigation>,
142}
143
144impl Performance {
145 fn new_inherited(
146 time_origin: CrossProcessInstant,
147 timing: &PerformanceTiming,
148 navigation: &PerformanceNavigation,
149 ) -> 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 timing: Dom::from_ref(timing),
161 navigation: Dom::from_ref(navigation),
162 }
163 }
164
165 pub(crate) fn new(
166 cx: &mut JSContext,
167 global: &GlobalScope,
168 navigation_start: CrossProcessInstant,
169 ) -> DomRoot<Performance> {
170 let timing = PerformanceTiming::new(cx, global);
171 let navigation = PerformanceNavigation::new(cx, global);
172 reflect_dom_object(
173 cx,
174 Box::new(Performance::new_inherited(
175 navigation_start,
176 &timing,
177 &navigation,
178 )),
179 global,
180 )
181 }
182
183 pub(crate) fn time_origin(&self) -> CrossProcessInstant {
184 self.time_origin
185 }
186
187 pub(crate) fn to_dom_high_res_time_stamp(
188 &self,
189 instant: CrossProcessInstant,
190 ) -> DOMHighResTimeStamp {
191 (instant - self.time_origin).to_dom_high_res_time_stamp()
192 }
193
194 pub(crate) fn maybe_to_dom_high_res_time_stamp(
195 &self,
196 instant: Option<CrossProcessInstant>,
197 ) -> DOMHighResTimeStamp {
198 self.to_dom_high_res_time_stamp(instant.unwrap_or(self.time_origin))
199 }
200
201 pub(crate) fn clear_and_disable_performance_entry_buffer(&self) {
205 let mut buffer = self.buffer.borrow_mut();
206 buffer.entries.clear();
207 self.resource_timing_buffer_size_limit.set(0);
208 }
209
210 pub(crate) fn add_multiple_type_observer(
214 &self,
215 observer: &DOMPerformanceObserver,
216 entry_types: Vec<EntryType>,
217 ) {
218 let mut observers = self.observers.borrow_mut();
219 match observers.iter().position(|o| *o.observer == *observer) {
220 Some(p) => observers[p].entry_types = entry_types,
223 None => observers.push(PerformanceObserver {
225 observer: Dom::from_ref(observer),
226 entry_types,
227 }),
228 };
229 }
230
231 pub(crate) fn add_single_type_observer(
232 &self,
233 observer: &DOMPerformanceObserver,
234 entry_type: EntryType,
235 buffered: bool,
236 ) {
237 if buffered {
238 let buffer = self.buffer.borrow();
239 let new_entries = buffer.get_entries_by_name_and_type(None, Some(entry_type));
240 if !new_entries.is_empty() {
241 let new_entries = new_entries.into_iter().map(|entry| entry.as_traced());
242 observer.entries_mut().extend(new_entries);
243 }
244
245 if !self.pending_notification_observers_task.get() {
246 self.pending_notification_observers_task.set(true);
247 let owner = Trusted::new(self);
248 self.global()
249 .task_manager()
250 .performance_timeline_task_source()
251 .queue(task!(notify_performance_observers: move |cx| {
252 owner.root().notify_observers(cx);
253 }));
254 }
255 }
256 let mut observers = self.observers.borrow_mut();
257 match observers.iter().position(|o| *o.observer == *observer) {
258 Some(p) => {
261 if !observers[p].entry_types.contains(&entry_type) {
263 observers[p].entry_types.push(entry_type)
264 }
265 },
266 None => observers.push(PerformanceObserver {
268 observer: Dom::from_ref(observer),
269 entry_types: vec![entry_type],
270 }),
271 };
272 }
273
274 pub(crate) fn remove_observer(&self, observer: &DOMPerformanceObserver) {
276 let mut observers = self.observers.borrow_mut();
277 let index = match observers.iter().position(|o| &(*o.observer) == observer) {
278 Some(p) => p,
279 None => return,
280 };
281
282 observers.remove(index);
283 }
284
285 pub(crate) fn queue_entry(&self, entry: &PerformanceEntry) -> Option<usize> {
294 if entry.entry_type() == EntryType::Resource && !self.should_queue_resource_entry(entry) {
296 return None;
297 }
298
299 for observer in self
304 .observers
305 .borrow()
306 .iter()
307 .filter(|o| o.entry_types.contains(&entry.entry_type()))
308 {
309 observer.observer.queue_entry(entry);
310 }
311
312 self.buffer.borrow_mut().entries.push(Dom::from_ref(entry));
315
316 let entry_last_index = self.buffer.borrow_mut().entries.len() - 1;
317
318 if self.pending_notification_observers_task.get() {
321 return None;
322 }
323
324 self.pending_notification_observers_task.set(true);
327
328 let owner = Trusted::new(self);
329 self.global()
330 .task_manager()
331 .performance_timeline_task_source()
332 .queue(task!(notify_performance_observers: move |cx| {
333 owner.root().notify_observers(cx);
334 }));
335
336 Some(entry_last_index)
337 }
338
339 fn notify_observers(&self, cx: &mut JSContext) {
344 self.pending_notification_observers_task.set(false);
346
347 let observers: Vec<DomRoot<DOMPerformanceObserver>> = self
353 .observers
354 .borrow()
355 .iter()
356 .map(|o| DomRoot::from_ref(&*o.observer))
357 .collect();
358
359 for o in observers.iter() {
361 o.notify(cx);
362 }
363 }
364
365 fn can_add_resource_timing_entry(&self) -> bool {
367 self.resource_timing_buffer_current_size.get() <
370 self.resource_timing_buffer_size_limit.get()
371 }
372
373 fn copy_secondary_resource_timing_buffer(&self) {
375 while self.can_add_resource_timing_entry() {
377 if let Some(ref entry) = self
379 .resource_timing_secondary_entries
380 .borrow_mut()
381 .pop_front()
382 {
383 self.buffer.borrow_mut().entries.push(Dom::from_ref(entry));
385 self.resource_timing_buffer_current_size
387 .set(self.resource_timing_buffer_current_size.get() + 1);
388 } else {
392 break;
393 }
394 }
395 }
396
397 fn fire_buffer_full_event(&self, cx: &mut js::context::JSContext) {
399 while !self.resource_timing_secondary_entries.borrow().is_empty() {
400 let no_of_excess_entries_before = self.resource_timing_secondary_entries.borrow().len();
401
402 if !self.can_add_resource_timing_entry() {
403 self.upcast::<EventTarget>()
404 .fire_event(cx, atom!("resourcetimingbufferfull"));
405 }
406 self.copy_secondary_resource_timing_buffer();
407 let no_of_excess_entries_after = self.resource_timing_secondary_entries.borrow().len();
408 if no_of_excess_entries_before <= no_of_excess_entries_after {
409 self.resource_timing_secondary_entries.borrow_mut().clear();
410 break;
411 }
412 }
413 self.resource_timing_buffer_pending_full_event.set(false);
414 }
415
416 fn should_queue_resource_entry(&self, entry: &PerformanceEntry) -> bool {
418 if !self.resource_timing_buffer_pending_full_event.get() {
420 if self.can_add_resource_timing_entry() {
421 self.resource_timing_buffer_current_size
425 .set(self.resource_timing_buffer_current_size.get() + 1);
426 return true;
428 }
429
430 self.resource_timing_buffer_pending_full_event.set(true);
432 let performance = Trusted::new(self);
434 self.global()
435 .task_manager()
436 .performance_timeline_task_source()
437 .queue(task!(fire_a_buffer_full_event: move |cx| {
438 performance.root().fire_buffer_full_event(cx);
439 }));
440 }
441
442 self.resource_timing_secondary_entries
444 .borrow_mut()
445 .push_back(Dom::from_ref(entry));
446
447 false
450 }
451
452 pub(crate) fn update_entry(&self, index: usize, entry: &PerformanceEntry) {
453 if let Some(e) = self.buffer.borrow_mut().entries.get_mut(index) {
454 *e = Dom::from_ref(entry);
455 }
456 }
457
458 fn convert_a_name_to_a_timestamp(&self, name: &str) -> Fallible<CrossProcessInstant> {
460 let Some(window) = DomRoot::downcast::<Window>(self.global()) else {
462 return Err(Error::Type(cformat!(
463 "Cannot use {name} from non-window global"
464 )));
465 };
466
467 if name == "navigationStart" {
469 return Ok(self.time_origin);
470 }
471
472 let end_time = window.Document().performance_timing_attribute(name)?;
479
480 let Some(end_time) = end_time else {
482 return Err(Error::InvalidAccess(Some(format!(
483 "{name} hasn't happened yet"
484 ))));
485 };
486
487 Ok(end_time)
489 }
490
491 fn convert_a_mark_to_a_timestamp(
493 &self,
494 mark: &StringOrDouble,
495 ) -> Fallible<CrossProcessInstant> {
496 match mark {
497 StringOrDouble::String(name) => {
498 if PERFORMANCE_TIMING_ATTRIBUTES.contains(&&*name.str()) {
502 self.convert_a_name_to_a_timestamp(&name.str())
503 }
504 else {
508 self.buffer
509 .borrow()
510 .get_last_entry_start_time_with_name_and_type(name.clone(), EntryType::Mark)
511 .ok_or(Error::Syntax(Some(format!(
512 "No PerformanceMark named {name} exists"
513 ))))
514 }
515 },
516 StringOrDouble::Double(timestamp) => {
518 if timestamp.is_sign_negative() {
520 return Err(Error::Type(c"Time stamps must not be negative".to_owned()));
521 }
522
523 Ok(
526 self.time_origin +
527 Duration::microseconds(timestamp.mul_add(1000.0, 0.0) as i64),
528 )
529 },
530 }
531 }
532}
533
534impl PerformanceMethods<crate::DomTypeHolder> for Performance {
535 fn Timing(&self) -> DomRoot<PerformanceTiming> {
537 DomRoot::from_ref(&*self.timing)
538 }
539
540 fn Navigation(&self) -> DomRoot<PerformanceNavigation> {
542 DomRoot::from_ref(&*self.navigation)
543 }
544
545 fn Now(&self) -> DOMHighResTimeStamp {
547 self.to_dom_high_res_time_stamp(CrossProcessInstant::now())
548 }
549
550 fn TimeOrigin(&self) -> DOMHighResTimeStamp {
552 (self.time_origin - CrossProcessInstant::epoch()).to_dom_high_res_time_stamp()
553 }
554
555 fn GetEntries(&self) -> Vec<DomRoot<PerformanceEntry>> {
557 self.buffer
560 .borrow()
561 .get_entries_by_name_and_type(None, None)
562 }
563
564 fn GetEntriesByType(&self, entry_type: DOMString) -> Vec<DomRoot<PerformanceEntry>> {
566 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
567 return Vec::new();
568 };
569 self.buffer
570 .borrow()
571 .get_entries_by_name_and_type(None, Some(entry_type))
572 }
573
574 fn GetEntriesByName(
576 &self,
577 name: DOMString,
578 entry_type: Option<DOMString>,
579 ) -> Vec<DomRoot<PerformanceEntry>> {
580 let entry_type = match entry_type {
581 Some(entry_type) => {
582 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
583 return Vec::new();
584 };
585 Some(entry_type)
586 },
587 None => None,
588 };
589 self.buffer
590 .borrow()
591 .get_entries_by_name_and_type(Some(name), entry_type)
592 }
593
594 fn Mark(
596 &self,
597 cx: &mut JSContext,
598 mark_name: DOMString,
599 mark_options: &PerformanceMarkOptions,
600 ) -> Fallible<DomRoot<PerformanceMark>> {
601 let entry =
603 PerformanceMark::Constructor(cx, &self.global(), None, mark_name, mark_options)?;
604
605 self.queue_entry(entry.upcast::<PerformanceEntry>());
608
609 Ok(entry)
611 }
612
613 fn ClearMarks(&self, mark_name: Option<DOMString>) {
615 self.buffer
616 .borrow_mut()
617 .clear_entries_by_name_and_type(mark_name, EntryType::Mark);
618 }
619
620 fn Measure(
622 &self,
623 cx: &mut JSContext,
624 measure_name: DOMString,
625 start_or_measure_options: StringOrPerformanceMeasureOptions,
626 end_mark: Option<DOMString>,
627 ) -> Fallible<DomRoot<PerformanceMeasure>> {
628 if let StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) =
631 &start_or_measure_options &&
632 (options.start.is_some() ||
633 options.duration.is_some() ||
634 options.end.is_some() ||
635 options.detail.get().is_object_or_null())
636 {
637 if end_mark.is_some() {
639 return Err(Error::Type(
640 c"Must not provide endMark if PerformanceMeasureOptions is also provided"
641 .to_owned(),
642 ));
643 }
644
645 if options.start.is_none() && options.end.is_none() {
647 return Err(Error::Type(
648 c"Either 'start' or 'end' member of PerformanceMeasureOptions must be provided"
649 .to_owned(),
650 ));
651 }
652
653 if options.start.is_some() && options.duration.is_some() && options.end.is_some() {
655 return Err(Error::Type(c"Either 'start' or 'end' or 'duration' member of PerformanceMeasureOptions must be omitted".to_owned()));
656 }
657 }
658
659 let end_time = if let Some(end_mark) = end_mark {
663 self.convert_a_mark_to_a_timestamp(&StringOrDouble::String(end_mark))?
664 } else {
665 match &start_or_measure_options {
666 StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) => {
667 if let Some(end) = &options.end {
671 self.convert_a_mark_to_a_timestamp(end)?
672 }
673 else if let Some((start, duration)) =
676 options.start.as_ref().zip(options.duration)
677 {
678 let start = self.convert_a_mark_to_a_timestamp(start)?;
681
682 let duration = self
685 .convert_a_mark_to_a_timestamp(&StringOrDouble::Double(duration))? -
686 self.time_origin;
687
688 start + duration
690 } else {
691 CrossProcessInstant::now()
694 }
695 },
696 _ => {
697 CrossProcessInstant::now()
700 },
701 }
702 };
703
704 let start_time = match &start_or_measure_options {
706 StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) => {
707 if let Some(start) = &options.start {
711 self.convert_a_mark_to_a_timestamp(start)?
712 }
713 else if let Some((duration, end)) = options.duration.zip(options.end.as_ref()) {
716 let duration = self
719 .convert_a_mark_to_a_timestamp(&StringOrDouble::Double(duration))? -
720 self.time_origin;
721
722 let end = self.convert_a_mark_to_a_timestamp(end)?;
725
726 end - duration
728 }
729 else {
731 self.time_origin
732 }
733 },
734 StringOrPerformanceMeasureOptions::String(string) => {
735 self.convert_a_mark_to_a_timestamp(&StringOrDouble::String(string.clone()))?
738 },
739 };
740
741 let entry = PerformanceMeasure::new(
749 cx,
750 &self.global(),
751 measure_name,
752 start_time,
753 end_time - start_time,
754 );
755
756 rooted!(&in(cx) let mut detail = NullValue());
758 if let StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) =
760 &start_or_measure_options &&
761 !options.detail.get().is_null_or_undefined()
762 {
763 let record = structuredclone::write(cx, options.detail.as_handle_value(), None)?;
765
766 structuredclone::read(cx, &self.global(), record, detail.handle_mut())?;
768 }
769 entry.set_detail(detail.handle());
774
775 self.queue_entry(entry.upcast::<PerformanceEntry>());
778
779 Ok(entry)
781 }
782
783 fn ClearMeasures(&self, measure_name: Option<DOMString>) {
785 self.buffer
786 .borrow_mut()
787 .clear_entries_by_name_and_type(measure_name, EntryType::Measure);
788 }
789 fn ClearResourceTimings(&self) {
791 self.buffer
792 .borrow_mut()
793 .clear_entries_by_name_and_type(None, EntryType::Resource);
794 self.resource_timing_buffer_current_size.set(0);
795 }
796
797 fn SetResourceTimingBufferSize(&self, max_size: u32) {
799 self.resource_timing_buffer_size_limit
800 .set(max_size as usize);
801 }
802
803 event_handler!(
805 resourcetimingbufferfull,
806 GetOnresourcetimingbufferfull,
807 SetOnresourcetimingbufferfull
808 );
809}
810
811pub(crate) trait ToDOMHighResTimeStamp {
812 fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp;
813}
814
815impl ToDOMHighResTimeStamp for Duration {
816 fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp {
817 let microseconds_rounded = (self.whole_microseconds() as f64 / 10.).floor() * 10.;
823 Finite::wrap(microseconds_rounded / 1000.)
824 }
825}