Skip to main content

net_traits/
resource_fetch_timing.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use 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    /// Number of redirects until final resource (currently limited to 20)
17    pub redirect_count: u16,
18    /// <https://fetch.spec.whatwg.org/#fetch-timing-info-start-time>
19    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    /// <https://fetch.spec.whatwg.org/#fetch-timing-info-end-time>
24    pub response_end: Option<CrossProcessInstant>,
25    /// <https://fetch.spec.whatwg.org/#fetch-timing-info-redirect-start-time>
26    pub redirect_start: Option<CrossProcessInstant>,
27    /// <https://fetch.spec.whatwg.org/#fetch-timing-info-redirect-end-time>
28    pub redirect_end: Option<CrossProcessInstant>,
29    pub connect_start: Option<CrossProcessInstant>,
30    pub connect_end: Option<CrossProcessInstant>,
31    pub start_time: Option<CrossProcessInstant>,
32    /// <https://fetch.spec.whatwg.org/#fetch-timing-info-final-service-worker-start-time>
33    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// TODO: refactor existing code to use this enum for setting time attributes
50// suggest using this with all time attributes in the future
51#[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    // TODO currently this is being set with precise time ns when it should be time since
107    // time origin (as described in Performance::now)
108    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)]
174/// A simple container of [`ResourceFetchTiming`] to allow easy sharing between threads and allow to set multiple attributes in sequence.
175pub 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    /// Set multiple attributes in sequence.
185    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}