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::{Dom, 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::performance::performancetiming::PerformanceTiming;
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 timing: Dom<PerformanceTiming>,
142 navigation: Dom<PerformanceNavigation>,
143}
144
145impl Performance {
146 fn new_inherited(
147 time_origin: CrossProcessInstant,
148 timing: &PerformanceTiming,
149 navigation: &PerformanceNavigation,
150 ) -> Performance {
151 Performance {
152 eventtarget: EventTarget::new_inherited(),
153 buffer: DomRefCell::new(PerformanceEntryList::new(Vec::new())),
154 observers: DomRefCell::new(Vec::new()),
155 pending_notification_observers_task: Cell::new(false),
156 time_origin,
157 resource_timing_buffer_size_limit: Cell::new(250),
158 resource_timing_buffer_current_size: Cell::new(0),
159 resource_timing_buffer_pending_full_event: Cell::new(false),
160 resource_timing_secondary_entries: DomRefCell::new(VecDeque::new()),
161 timing: Dom::from_ref(timing),
162 navigation: Dom::from_ref(navigation),
163 }
164 }
165
166 pub(crate) fn new(
167 cx: &mut JSContext,
168 global: &GlobalScope,
169 navigation_start: CrossProcessInstant,
170 ) -> DomRoot<Performance> {
171 let timing = PerformanceTiming::new(cx, global);
172 let navigation = PerformanceNavigation::new(cx, global);
173 reflect_dom_object(
174 cx,
175 Box::new(Performance::new_inherited(
176 navigation_start,
177 &timing,
178 &navigation,
179 )),
180 global,
181 )
182 }
183
184 pub(crate) fn time_origin(&self) -> CrossProcessInstant {
185 self.time_origin
186 }
187
188 pub(crate) fn to_dom_high_res_time_stamp(
189 &self,
190 instant: CrossProcessInstant,
191 ) -> DOMHighResTimeStamp {
192 (instant - self.time_origin).to_dom_high_res_time_stamp()
193 }
194
195 pub(crate) fn maybe_to_dom_high_res_time_stamp(
196 &self,
197 instant: Option<CrossProcessInstant>,
198 ) -> DOMHighResTimeStamp {
199 self.to_dom_high_res_time_stamp(instant.unwrap_or(self.time_origin))
200 }
201
202 pub(crate) fn clear_and_disable_performance_entry_buffer(&self) {
206 let mut buffer = self.buffer.borrow_mut();
207 buffer.entries.clear();
208 self.resource_timing_buffer_size_limit.set(0);
209 }
210
211 pub(crate) fn add_multiple_type_observer(
215 &self,
216 observer: &DOMPerformanceObserver,
217 entry_types: Vec<EntryType>,
218 ) {
219 let mut observers = self.observers.borrow_mut();
220 match observers.iter().position(|o| *o.observer == *observer) {
221 Some(p) => observers[p].entry_types = entry_types,
224 None => observers.push(PerformanceObserver {
226 observer: Dom::from_ref(observer),
227 entry_types,
228 }),
229 };
230 }
231
232 pub(crate) fn add_single_type_observer(
233 &self,
234 observer: &DOMPerformanceObserver,
235 entry_type: EntryType,
236 buffered: bool,
237 ) {
238 if buffered {
239 let buffer = self.buffer.borrow();
240 let new_entries = buffer.get_entries_by_name_and_type(None, Some(entry_type));
241 if !new_entries.is_empty() {
242 let new_entries = new_entries.into_iter().map(|entry| entry.as_traced());
243 observer.entries_mut().extend(new_entries);
244 }
245
246 if !self.pending_notification_observers_task.get() {
247 self.pending_notification_observers_task.set(true);
248 let owner = Trusted::new(self);
249 self.global()
250 .task_manager()
251 .performance_timeline_task_source()
252 .queue(task!(notify_performance_observers: move |cx| {
253 owner.root().notify_observers(cx);
254 }));
255 }
256 }
257 let mut observers = self.observers.borrow_mut();
258 match observers.iter().position(|o| *o.observer == *observer) {
259 Some(p) => {
262 if !observers[p].entry_types.contains(&entry_type) {
264 observers[p].entry_types.push(entry_type)
265 }
266 },
267 None => observers.push(PerformanceObserver {
269 observer: Dom::from_ref(observer),
270 entry_types: vec![entry_type],
271 }),
272 };
273 }
274
275 pub(crate) fn remove_observer(&self, observer: &DOMPerformanceObserver) {
277 let mut observers = self.observers.borrow_mut();
278 let index = match observers.iter().position(|o| &(*o.observer) == observer) {
279 Some(p) => p,
280 None => return,
281 };
282
283 observers.remove(index);
284 }
285
286 pub(crate) fn queue_entry(&self, entry: &PerformanceEntry) -> Option<usize> {
295 if entry.entry_type() == EntryType::Resource && !self.should_queue_resource_entry(entry) {
297 return None;
298 }
299
300 for observer in self
305 .observers
306 .borrow()
307 .iter()
308 .filter(|o| o.entry_types.contains(&entry.entry_type()))
309 {
310 observer.observer.queue_entry(entry);
311 }
312
313 self.buffer.borrow_mut().entries.push(Dom::from_ref(entry));
316
317 let entry_last_index = self.buffer.borrow_mut().entries.len() - 1;
318
319 if self.pending_notification_observers_task.get() {
322 return None;
323 }
324
325 self.pending_notification_observers_task.set(true);
328
329 let owner = Trusted::new(self);
330 self.global()
331 .task_manager()
332 .performance_timeline_task_source()
333 .queue(task!(notify_performance_observers: move |cx| {
334 owner.root().notify_observers(cx);
335 }));
336
337 Some(entry_last_index)
338 }
339
340 fn notify_observers(&self, cx: &mut JSContext) {
345 self.pending_notification_observers_task.set(false);
347
348 let observers: Vec<DomRoot<DOMPerformanceObserver>> = self
354 .observers
355 .borrow()
356 .iter()
357 .map(|o| DomRoot::from_ref(&*o.observer))
358 .collect();
359
360 for o in observers.iter() {
362 o.notify(cx);
363 }
364 }
365
366 fn can_add_resource_timing_entry(&self) -> bool {
368 self.resource_timing_buffer_current_size.get() <
371 self.resource_timing_buffer_size_limit.get()
372 }
373
374 fn copy_secondary_resource_timing_buffer(&self) {
376 while self.can_add_resource_timing_entry() {
378 if let Some(ref entry) = self
380 .resource_timing_secondary_entries
381 .borrow_mut()
382 .pop_front()
383 {
384 self.buffer.borrow_mut().entries.push(Dom::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(Dom::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 = Dom::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<PerformanceTiming> {
538 DomRoot::from_ref(&*self.timing)
539 }
540
541 fn Navigation(&self) -> DomRoot<PerformanceNavigation> {
543 DomRoot::from_ref(&*self.navigation)
544 }
545
546 fn Now(&self) -> DOMHighResTimeStamp {
548 self.to_dom_high_res_time_stamp(CrossProcessInstant::now())
549 }
550
551 fn TimeOrigin(&self) -> DOMHighResTimeStamp {
553 (self.time_origin - CrossProcessInstant::epoch()).to_dom_high_res_time_stamp()
554 }
555
556 fn GetEntries(&self) -> Vec<DomRoot<PerformanceEntry>> {
558 self.buffer
561 .borrow()
562 .get_entries_by_name_and_type(None, None)
563 }
564
565 fn GetEntriesByType(&self, entry_type: DOMString) -> Vec<DomRoot<PerformanceEntry>> {
567 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
568 return Vec::new();
569 };
570 self.buffer
571 .borrow()
572 .get_entries_by_name_and_type(None, Some(entry_type))
573 }
574
575 fn GetEntriesByName(
577 &self,
578 name: DOMString,
579 entry_type: Option<DOMString>,
580 ) -> Vec<DomRoot<PerformanceEntry>> {
581 let entry_type = match entry_type {
582 Some(entry_type) => {
583 let Ok(entry_type) = EntryType::try_from(&*entry_type.str()) else {
584 return Vec::new();
585 };
586 Some(entry_type)
587 },
588 None => None,
589 };
590 self.buffer
591 .borrow()
592 .get_entries_by_name_and_type(Some(name), entry_type)
593 }
594
595 fn Mark(
597 &self,
598 cx: &mut JSContext,
599 mark_name: DOMString,
600 mark_options: RootedTraceableBox<PerformanceMarkOptions>,
601 ) -> Fallible<DomRoot<PerformanceMark>> {
602 let entry =
604 PerformanceMark::Constructor(cx, &self.global(), None, mark_name, mark_options)?;
605
606 self.queue_entry(entry.upcast::<PerformanceEntry>());
609
610 Ok(entry)
612 }
613
614 fn ClearMarks(&self, mark_name: Option<DOMString>) {
616 self.buffer
617 .borrow_mut()
618 .clear_entries_by_name_and_type(mark_name, EntryType::Mark);
619 }
620
621 fn Measure(
623 &self,
624 cx: &mut JSContext,
625 measure_name: DOMString,
626 start_or_measure_options: StringOrPerformanceMeasureOptions,
627 end_mark: Option<DOMString>,
628 ) -> Fallible<DomRoot<PerformanceMeasure>> {
629 if let StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) =
632 &start_or_measure_options &&
633 (options.start.is_some() ||
634 options.duration.is_some() ||
635 options.end.is_some() ||
636 options.detail.get().is_object_or_null())
637 {
638 if end_mark.is_some() {
640 return Err(Error::Type(
641 c"Must not provide endMark if PerformanceMeasureOptions is also provided"
642 .to_owned(),
643 ));
644 }
645
646 if options.start.is_none() && options.end.is_none() {
648 return Err(Error::Type(
649 c"Either 'start' or 'end' member of PerformanceMeasureOptions must be provided"
650 .to_owned(),
651 ));
652 }
653
654 if options.start.is_some() && options.duration.is_some() && options.end.is_some() {
656 return Err(Error::Type(c"Either 'start' or 'end' or 'duration' member of PerformanceMeasureOptions must be omitted".to_owned()));
657 }
658 }
659
660 let end_time = if let Some(end_mark) = end_mark {
664 self.convert_a_mark_to_a_timestamp(&StringOrDouble::String(end_mark))?
665 } else {
666 match &start_or_measure_options {
667 StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) => {
668 if let Some(end) = &options.end {
672 self.convert_a_mark_to_a_timestamp(end)?
673 }
674 else if let Some((start, duration)) =
677 options.start.as_ref().zip(options.duration)
678 {
679 let start = self.convert_a_mark_to_a_timestamp(start)?;
682
683 let duration = self
686 .convert_a_mark_to_a_timestamp(&StringOrDouble::Double(duration))? -
687 self.time_origin;
688
689 start + duration
691 } else {
692 CrossProcessInstant::now()
695 }
696 },
697 _ => {
698 CrossProcessInstant::now()
701 },
702 }
703 };
704
705 let start_time = match &start_or_measure_options {
707 StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) => {
708 if let Some(start) = &options.start {
712 self.convert_a_mark_to_a_timestamp(start)?
713 }
714 else if let Some((duration, end)) = options.duration.zip(options.end.as_ref()) {
717 let duration = self
720 .convert_a_mark_to_a_timestamp(&StringOrDouble::Double(duration))? -
721 self.time_origin;
722
723 let end = self.convert_a_mark_to_a_timestamp(end)?;
726
727 end - duration
729 }
730 else {
732 self.time_origin
733 }
734 },
735 StringOrPerformanceMeasureOptions::String(string) => {
736 self.convert_a_mark_to_a_timestamp(&StringOrDouble::String(string.clone()))?
739 },
740 };
741
742 let entry = PerformanceMeasure::new(
750 cx,
751 &self.global(),
752 measure_name,
753 start_time,
754 end_time - start_time,
755 );
756
757 rooted!(&in(cx) let mut detail = NullValue());
759 if let StringOrPerformanceMeasureOptions::PerformanceMeasureOptions(options) =
761 &start_or_measure_options &&
762 !options.detail.get().is_null_or_undefined()
763 {
764 let record = structuredclone::write(cx, options.detail.handle(), None)?;
766
767 structuredclone::read(cx, &self.global(), record, detail.handle_mut())?;
769 }
770 entry.set_detail(detail.handle());
775
776 self.queue_entry(entry.upcast::<PerformanceEntry>());
779
780 Ok(entry)
782 }
783
784 fn ClearMeasures(&self, measure_name: Option<DOMString>) {
786 self.buffer
787 .borrow_mut()
788 .clear_entries_by_name_and_type(measure_name, EntryType::Measure);
789 }
790 fn ClearResourceTimings(&self) {
792 self.buffer
793 .borrow_mut()
794 .clear_entries_by_name_and_type(None, EntryType::Resource);
795 self.resource_timing_buffer_current_size.set(0);
796 }
797
798 fn SetResourceTimingBufferSize(&self, max_size: u32) {
800 self.resource_timing_buffer_size_limit
801 .set(max_size as usize);
802 }
803
804 event_handler!(
806 resourcetimingbufferfull,
807 GetOnresourcetimingbufferfull,
808 SetOnresourcetimingbufferfull
809 );
810}
811
812pub(crate) trait ToDOMHighResTimeStamp {
813 fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp;
814}
815
816impl ToDOMHighResTimeStamp for Duration {
817 fn to_dom_high_res_time_stamp(&self) -> DOMHighResTimeStamp {
818 let microseconds_rounded = (self.whole_microseconds() as f64 / 10.).floor() * 10.;
824 Finite::wrap(microseconds_rounded / 1000.)
825 }
826}