script/
network_listener.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 std::sync::{Arc, Mutex};
6
7use content_security_policy::Violation;
8use net_traits::request::RequestId;
9use net_traits::{
10    BoxedFetchCallback, FetchMetadata, FetchResponseMsg, NetworkError, ResourceFetchTiming,
11    ResourceTimingType,
12};
13use servo_url::ServoUrl;
14
15use crate::dom::bindings::inheritance::Castable;
16use crate::dom::bindings::root::DomRoot;
17use crate::dom::globalscope::GlobalScope;
18use crate::dom::performance::performanceentry::PerformanceEntry;
19use crate::dom::performance::performanceresourcetiming::{
20    InitiatorType, PerformanceResourceTiming,
21};
22use crate::script_runtime::CanGc;
23use crate::task_source::SendableTaskSource;
24
25pub(crate) trait ResourceTimingListener {
26    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl);
27    fn resource_timing_global(&self) -> DomRoot<GlobalScope>;
28}
29
30pub(crate) fn submit_timing<T: ResourceTimingListener + FetchResponseListener>(
31    listener: &T,
32    resource_timing: &ResourceFetchTiming,
33    can_gc: CanGc,
34) {
35    // TODO timing check https://w3c.github.io/resource-timing/#dfn-timing-allow-check
36    //
37    // TODO Resources for which the fetch was initiated, but was later aborted
38    // (e.g. due to a network error) MAY be included as PerformanceResourceTiming
39    // objects in the Performance Timeline and MUST contain initialized attribute
40    // values for processed substeps of the processing model.
41    if resource_timing.timing_type != ResourceTimingType::Resource {
42        warn!(
43            "Submitting non-resource ({:?}) timing as resource",
44            resource_timing.timing_type
45        );
46        return;
47    }
48
49    let (initiator_type, url) = listener.resource_timing_information();
50    if initiator_type == InitiatorType::Other {
51        warn!("Ignoring InitiatorType::Other resource {:?}", url);
52        return;
53    }
54
55    submit_timing_data(
56        &listener.resource_timing_global(),
57        url,
58        initiator_type,
59        resource_timing,
60        can_gc,
61    );
62}
63
64pub(crate) fn submit_timing_data(
65    global: &GlobalScope,
66    url: ServoUrl,
67    initiator_type: InitiatorType,
68    resource_timing: &ResourceFetchTiming,
69    can_gc: CanGc,
70) {
71    let performance_entry =
72        PerformanceResourceTiming::new(global, url, initiator_type, None, resource_timing, can_gc);
73    global
74        .performance()
75        .queue_entry(performance_entry.upcast::<PerformanceEntry>(), can_gc);
76}
77
78pub(crate) trait FetchResponseListener: Send + 'static {
79    /// A gating mechanism that runs before invoking the listener methods on the target
80    /// thread. If the `should_invoke` method returns false, the listener does not receive
81    /// the notification.
82    fn should_invoke(&self) -> bool {
83        true
84    }
85
86    fn process_request_body(&mut self, request_id: RequestId);
87    fn process_request_eof(&mut self, request_id: RequestId);
88    fn process_response(
89        &mut self,
90        request_id: RequestId,
91        metadata: Result<FetchMetadata, NetworkError>,
92    );
93    fn process_response_chunk(&mut self, request_id: RequestId, chunk: Vec<u8>);
94    fn process_response_eof(
95        self,
96        request_id: RequestId,
97        response: Result<ResourceFetchTiming, NetworkError>,
98    );
99    fn process_csp_violations(&mut self, request_id: RequestId, violations: Vec<Violation>);
100}
101
102/// An off-thread sink for async network event tasks. All such events are forwarded to
103/// a target thread, where they are invoked on the provided context object.
104pub(crate) struct NetworkListener<Listener: FetchResponseListener> {
105    pub(crate) context: Arc<Mutex<Option<Listener>>>,
106    pub(crate) task_source: SendableTaskSource,
107}
108
109impl<Listener: FetchResponseListener> NetworkListener<Listener> {
110    pub(crate) fn new(context: Listener, task_source: SendableTaskSource) -> Self {
111        Self {
112            context: Arc::new(Mutex::new(Some(context))),
113            task_source,
114        }
115    }
116
117    pub(crate) fn notify(&mut self, message: FetchResponseMsg) {
118        let context = self.context.clone();
119        self.task_source
120            .queue(task!(network_listener_response: move || {
121                let mut context = context.lock().unwrap();
122                let Some(fetch_listener) = &mut *context else {
123                    return;
124                };
125
126                if !fetch_listener.should_invoke() {
127                    return;
128                }
129
130                match message {
131                    FetchResponseMsg::ProcessRequestBody(request_id) => {
132                        fetch_listener.process_request_body(request_id)
133                    },
134                    FetchResponseMsg::ProcessRequestEOF(request_id) => {
135                        fetch_listener.process_request_eof(request_id)
136                    },
137                    FetchResponseMsg::ProcessResponse(request_id, meta) => {
138                        fetch_listener.process_response(request_id, meta)
139                    },
140                    FetchResponseMsg::ProcessResponseChunk(request_id, data) => {
141                        fetch_listener.process_response_chunk(request_id, data.0)
142                    },
143                    FetchResponseMsg::ProcessResponseEOF(request_id, resource_timing_result) => {
144                        if let Some(fetch_listener) = context.take() {
145                            fetch_listener.process_response_eof(request_id, resource_timing_result);
146                        };
147                    },
148                    FetchResponseMsg::ProcessCspViolations(request_id, violations) => {
149                        fetch_listener.process_csp_violations(request_id, violations)
150                    },
151                }
152            }));
153    }
154
155    pub(crate) fn into_callback(mut self) -> BoxedFetchCallback {
156        Box::new(move |response_msg| self.notify(response_msg))
157    }
158}