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    pub preloaded: bool,
33}
34
35#[derive(Clone)]
36pub enum RedirectStartValue {
37    Zero,
38    FetchStart,
39}
40
41#[derive(Clone)]
42pub enum RedirectEndValue {
43    Zero,
44    ResponseEnd,
45}
46
47// TODO: refactor existing code to use this enum for setting time attributes
48// suggest using this with all time attributes in the future
49#[derive(Clone)]
50pub enum ResourceTimeValue {
51    Zero,
52    Now,
53    FetchStart,
54    RedirectStart,
55}
56
57#[derive(Clone)]
58pub enum ResourceAttribute {
59    RedirectCount(u16),
60    DomainLookupStart,
61    RequestStart,
62    ResponseStart,
63    RedirectStart(RedirectStartValue),
64    RedirectEnd(RedirectEndValue),
65    FetchStart,
66    ConnectStart(CrossProcessInstant),
67    ConnectEnd(CrossProcessInstant),
68    SecureConnectionStart,
69    ResponseEnd,
70    StartTime(ResourceTimeValue),
71}
72
73#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
74pub enum ResourceTimingType {
75    Resource,
76    Navigation,
77    Error,
78    None,
79}
80
81impl ResourceFetchTiming {
82    pub fn new(timing_type: ResourceTimingType) -> ResourceFetchTiming {
83        ResourceFetchTiming {
84            timing_type,
85            timing_check_passed: true,
86            domain_lookup_start: None,
87            redirect_count: 0,
88            secure_connection_start: None,
89            request_start: None,
90            response_start: None,
91            fetch_start: None,
92            redirect_start: None,
93            redirect_end: None,
94            connect_start: None,
95            connect_end: None,
96            response_end: None,
97            start_time: None,
98            preloaded: false,
99        }
100    }
101
102    // TODO currently this is being set with precise time ns when it should be time since
103    // time origin (as described in Performance::now)
104    pub fn set_attribute(&mut self, attribute: ResourceAttribute) {
105        let should_attribute_always_be_updated = matches!(
106            attribute,
107            ResourceAttribute::FetchStart |
108                ResourceAttribute::ResponseEnd |
109                ResourceAttribute::StartTime(_)
110        );
111        if !self.timing_check_passed && !should_attribute_always_be_updated {
112            return;
113        }
114        let now = Some(CrossProcessInstant::now());
115        match attribute {
116            ResourceAttribute::DomainLookupStart => self.domain_lookup_start = now,
117            ResourceAttribute::RedirectCount(count) => self.redirect_count = count,
118            ResourceAttribute::RequestStart => self.request_start = now,
119            ResourceAttribute::ResponseStart => self.response_start = now,
120            ResourceAttribute::RedirectStart(val) => match val {
121                RedirectStartValue::Zero => self.redirect_start = None,
122                RedirectStartValue::FetchStart => {
123                    if self.redirect_start.is_none() {
124                        self.redirect_start = self.fetch_start
125                    }
126                },
127            },
128            ResourceAttribute::RedirectEnd(val) => match val {
129                RedirectEndValue::Zero => self.redirect_end = None,
130                RedirectEndValue::ResponseEnd => self.redirect_end = self.response_end,
131            },
132            ResourceAttribute::FetchStart => self.fetch_start = now,
133            ResourceAttribute::ConnectStart(instant) => self.connect_start = Some(instant),
134            ResourceAttribute::ConnectEnd(instant) => self.connect_end = Some(instant),
135            ResourceAttribute::SecureConnectionStart => self.secure_connection_start = now,
136            ResourceAttribute::ResponseEnd => self.response_end = now,
137            ResourceAttribute::StartTime(val) => match val {
138                ResourceTimeValue::RedirectStart
139                    if self.redirect_start.is_none() || !self.timing_check_passed => {},
140                _ => self.start_time = self.get_time_value(val),
141            },
142        }
143    }
144
145    fn get_time_value(&self, time: ResourceTimeValue) -> Option<CrossProcessInstant> {
146        match time {
147            ResourceTimeValue::Zero => None,
148            ResourceTimeValue::Now => Some(CrossProcessInstant::now()),
149            ResourceTimeValue::FetchStart => self.fetch_start,
150            ResourceTimeValue::RedirectStart => self.redirect_start,
151        }
152    }
153
154    pub fn mark_timing_check_failed(&mut self) {
155        self.timing_check_passed = false;
156        self.domain_lookup_start = None;
157        self.redirect_count = 0;
158        self.request_start = None;
159        self.response_start = None;
160        self.redirect_start = None;
161        self.connect_start = None;
162        self.connect_end = None;
163    }
164}
165
166#[derive(Clone, Debug, Serialize, Deserialize, MallocSizeOf)]
167/// A simple container of [`ResourceFetchTiming`] to allow easy sharing between threads and allow to set multiple attributes in sequence.
168pub struct ResourceFetchTimingContainer(
169    #[conditional_malloc_size_of] Arc<Mutex<ResourceFetchTiming>>,
170);
171
172impl ResourceFetchTimingContainer {
173    pub fn set_attribute(&self, attribute: ResourceAttribute) {
174        self.0.lock().set_attribute(attribute);
175    }
176
177    /// Set multiple attributes in sequence.
178    pub fn set_attributes(&self, attributes: &[ResourceAttribute]) {
179        let mut inner = self.0.lock();
180        for attribute in attributes {
181            inner.set_attribute(attribute.clone());
182        }
183    }
184
185    pub fn inner(&self) -> MutexGuard<'_, ResourceFetchTiming> {
186        self.0.lock()
187    }
188}
189
190impl From<ResourceFetchTiming> for ResourceFetchTimingContainer {
191    fn from(value: ResourceFetchTiming) -> Self {
192        ResourceFetchTimingContainer(Arc::new(Mutex::new(value)))
193    }
194}