1use malloc_size_of_derive::MallocSizeOf;
6use parking_lot::{Mutex, MutexGuard};
7use serde::{Deserialize, Serialize};
8use servo_arc::Arc;
9use servo_base::cross_process_instant::CrossProcessInstant;
10
11#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
12pub struct ResourceFetchTiming {
13 pub domain_lookup_start: Option<CrossProcessInstant>,
14 pub timing_check_passed: bool,
15 pub timing_type: ResourceTimingType,
16 pub redirect_count: u16,
18 pub request_start: Option<CrossProcessInstant>,
20 pub secure_connection_start: Option<CrossProcessInstant>,
21 pub response_start: Option<CrossProcessInstant>,
22 pub fetch_start: Option<CrossProcessInstant>,
23 pub response_end: Option<CrossProcessInstant>,
25 pub redirect_start: Option<CrossProcessInstant>,
27 pub redirect_end: Option<CrossProcessInstant>,
29 pub connect_start: Option<CrossProcessInstant>,
30 pub connect_end: Option<CrossProcessInstant>,
31 pub start_time: Option<CrossProcessInstant>,
32 pub final_service_worker_start_time: Option<CrossProcessInstant>,
34 pub preloaded: bool,
35}
36
37#[derive(Clone)]
38pub enum RedirectStartValue {
39 Zero,
40 FetchStart,
41}
42
43#[derive(Clone)]
44pub enum RedirectEndValue {
45 Zero,
46 ResponseEnd,
47}
48
49#[derive(Clone)]
52pub enum ResourceTimeValue {
53 Zero,
54 Now,
55 FetchStart,
56 RedirectStart,
57}
58
59#[derive(Clone)]
60pub enum ResourceAttribute {
61 RedirectCount(u16),
62 DomainLookupStart,
63 RequestStart,
64 ResponseStart,
65 RedirectStart(RedirectStartValue),
66 RedirectEnd(RedirectEndValue),
67 FetchStart,
68 ConnectStart(CrossProcessInstant),
69 ConnectEnd(CrossProcessInstant),
70 SecureConnectionStart,
71 ResponseEnd,
72 StartTime(ResourceTimeValue),
73 FinalServiceWorkerStartTime,
74}
75
76#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
77pub enum ResourceTimingType {
78 Resource,
79 Navigation,
80 Error,
81 None,
82}
83
84impl ResourceFetchTiming {
85 pub fn new(timing_type: ResourceTimingType) -> ResourceFetchTiming {
86 ResourceFetchTiming {
87 timing_type,
88 timing_check_passed: true,
89 domain_lookup_start: None,
90 redirect_count: 0,
91 secure_connection_start: None,
92 request_start: None,
93 response_start: None,
94 fetch_start: None,
95 redirect_start: None,
96 redirect_end: None,
97 connect_start: None,
98 connect_end: None,
99 response_end: None,
100 start_time: None,
101 final_service_worker_start_time: None,
102 preloaded: false,
103 }
104 }
105
106 pub fn set_attribute(&mut self, attribute: ResourceAttribute) {
109 let should_attribute_always_be_updated = matches!(
110 attribute,
111 ResourceAttribute::FetchStart |
112 ResourceAttribute::ResponseEnd |
113 ResourceAttribute::StartTime(_)
114 );
115 if !self.timing_check_passed && !should_attribute_always_be_updated {
116 return;
117 }
118 let now = Some(CrossProcessInstant::now());
119 match attribute {
120 ResourceAttribute::DomainLookupStart => self.domain_lookup_start = now,
121 ResourceAttribute::RedirectCount(count) => self.redirect_count = count,
122 ResourceAttribute::RequestStart => self.request_start = now,
123 ResourceAttribute::ResponseStart => self.response_start = now,
124 ResourceAttribute::RedirectStart(val) => match val {
125 RedirectStartValue::Zero => self.redirect_start = None,
126 RedirectStartValue::FetchStart => {
127 if self.redirect_start.is_none() {
128 self.redirect_start = self.fetch_start
129 }
130 },
131 },
132 ResourceAttribute::RedirectEnd(val) => match val {
133 RedirectEndValue::Zero => self.redirect_end = None,
134 RedirectEndValue::ResponseEnd => self.redirect_end = self.response_end,
135 },
136 ResourceAttribute::FetchStart => self.fetch_start = now,
137 ResourceAttribute::ConnectStart(instant) => self.connect_start = Some(instant),
138 ResourceAttribute::ConnectEnd(instant) => self.connect_end = Some(instant),
139 ResourceAttribute::SecureConnectionStart => self.secure_connection_start = now,
140 ResourceAttribute::ResponseEnd => self.response_end = now,
141 ResourceAttribute::StartTime(val) => match val {
142 ResourceTimeValue::RedirectStart
143 if self.redirect_start.is_none() || !self.timing_check_passed => {},
144 _ => self.start_time = self.get_time_value(val),
145 },
146 ResourceAttribute::FinalServiceWorkerStartTime => {
147 self.final_service_worker_start_time = now
148 },
149 }
150 }
151
152 fn get_time_value(&self, time: ResourceTimeValue) -> Option<CrossProcessInstant> {
153 match time {
154 ResourceTimeValue::Zero => None,
155 ResourceTimeValue::Now => Some(CrossProcessInstant::now()),
156 ResourceTimeValue::FetchStart => self.fetch_start,
157 ResourceTimeValue::RedirectStart => self.redirect_start,
158 }
159 }
160
161 pub fn mark_timing_check_failed(&mut self) {
162 self.timing_check_passed = false;
163 self.domain_lookup_start = None;
164 self.redirect_count = 0;
165 self.request_start = None;
166 self.response_start = None;
167 self.redirect_start = None;
168 self.connect_start = None;
169 self.connect_end = None;
170 }
171}
172
173#[derive(Clone, Debug, Serialize, Deserialize, MallocSizeOf)]
174pub struct ResourceFetchTimingContainer(
176 #[conditional_malloc_size_of] Arc<Mutex<ResourceFetchTiming>>,
177);
178
179impl ResourceFetchTimingContainer {
180 pub fn set_attribute(&self, attribute: ResourceAttribute) {
181 self.0.lock().set_attribute(attribute);
182 }
183
184 pub fn set_attributes(&self, attributes: &[ResourceAttribute]) {
186 let mut inner = self.0.lock();
187 for attribute in attributes {
188 inner.set_attribute(attribute.clone());
189 }
190 }
191
192 pub fn inner(&self) -> MutexGuard<'_, ResourceFetchTiming> {
193 self.0.lock()
194 }
195}
196
197impl From<ResourceFetchTiming> for ResourceFetchTimingContainer {
198 fn from(value: ResourceFetchTiming) -> Self {
199 ResourceFetchTimingContainer(Arc::new(Mutex::new(value)))
200 }
201}