Skip to main content

net/
devtools.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::time::{Duration, SystemTime, UNIX_EPOCH};
6
7use bytes::Bytes;
8use crossbeam_channel::Sender;
9use devtools_traits::{
10    ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest,
11    HttpResponse as DevtoolsHttpResponse, NetworkEvent, SecurityInfoUpdate,
12};
13use http::{HeaderMap, Method};
14use hyper_serde::Serde;
15use log::error;
16use net_traits::FetchMetadata;
17use net_traits::http_status::HttpStatus;
18use net_traits::request::{Destination, Request};
19use net_traits::response::{CacheState, Response};
20use servo_base::id::{BrowsingContextId, PipelineId};
21use servo_url::ServoUrl;
22
23use crate::fetch::methods::FetchContext;
24
25#[expect(clippy::too_many_arguments)]
26pub(crate) fn prepare_devtools_request(
27    request_id: String,
28    url: ServoUrl,
29    method: Method,
30    headers: HeaderMap,
31    body: Option<Vec<u8>>,
32    pipeline_id: PipelineId,
33    connect_time: Duration,
34    send_time: Duration,
35    destination: Destination,
36    is_xhr: bool,
37    browsing_context_id: BrowsingContextId,
38) -> ChromeToDevtoolsControlMsg {
39    let started_date_time = SystemTime::now();
40    let request = DevtoolsHttpRequest {
41        url,
42        method,
43        headers,
44        body: body.map(Bytes::from),
45        pipeline_id,
46        started_date_time,
47        time_stamp: started_date_time
48            .duration_since(UNIX_EPOCH)
49            .unwrap_or_default()
50            .as_secs() as i64,
51        connect_time,
52        send_time,
53        destination,
54        is_xhr,
55        browsing_context_id,
56    };
57    let net_event = NetworkEvent::HttpRequestUpdate(request);
58
59    ChromeToDevtoolsControlMsg::NetworkEvent(request_id, net_event)
60}
61
62pub(crate) fn send_request_to_devtools(
63    msg: ChromeToDevtoolsControlMsg,
64    devtools_chan: &Sender<DevtoolsControlMsg>,
65) {
66    if matches!(msg, ChromeToDevtoolsControlMsg::NetworkEvent(_, ref network_event) if !network_event.forward_to_devtools())
67    {
68        return;
69    }
70    if let Err(e) = devtools_chan.send(DevtoolsControlMsg::FromChrome(msg)) {
71        error!("DevTools send failed: {e}");
72    }
73}
74
75pub(crate) fn send_response_to_devtools(
76    request: &Request,
77    context: &FetchContext,
78    response: &Response,
79    body_data: Option<Vec<u8>>,
80) {
81    let meta = match response.metadata() {
82        Ok(FetchMetadata::Unfiltered(m)) => m,
83        Ok(FetchMetadata::Filtered { unsafe_, .. }) => unsafe_,
84        Err(_) => {
85            log::warn!("No metadata available, skipping devtools response.");
86            return;
87        },
88    };
89    send_response_values_to_devtools(
90        meta.headers.map(Serde::into_inner),
91        meta.status,
92        body_data,
93        response.cache_state,
94        request,
95        context.devtools_chan.clone(),
96    );
97}
98
99pub(crate) fn send_response_values_to_devtools(
100    headers: Option<HeaderMap>,
101    status: HttpStatus,
102    body: Option<Vec<u8>>,
103    cache_state: CacheState,
104    request: &Request,
105    devtools_chan: Option<Sender<DevtoolsControlMsg>>,
106) {
107    if let (Some(devtools_chan), Some(pipeline_id), Some(webview_id)) = (
108        devtools_chan,
109        request.pipeline_id,
110        request.target_webview_id,
111    ) {
112        let browsing_context_id = webview_id.into();
113        let from_cache = matches!(cache_state, CacheState::Local | CacheState::Validated);
114
115        let devtoolsresponse = DevtoolsHttpResponse {
116            headers,
117            status,
118            body: body.map(Bytes::from),
119            from_cache,
120            pipeline_id,
121            browsing_context_id,
122        };
123        let net_event_response = NetworkEvent::HttpResponse(devtoolsresponse);
124
125        let msg =
126            ChromeToDevtoolsControlMsg::NetworkEvent(request.id.0.to_string(), net_event_response);
127
128        let _ = devtools_chan.send(DevtoolsControlMsg::FromChrome(msg));
129    }
130}
131
132pub(crate) fn send_security_info_to_devtools(
133    request: &Request,
134    context: &FetchContext,
135    response: &Response,
136) {
137    let meta = match response.metadata() {
138        Ok(FetchMetadata::Unfiltered(m)) => m,
139        Ok(FetchMetadata::Filtered { unsafe_, .. }) => unsafe_,
140        Err(_) => {
141            log::warn!("No metadata available, skipping devtools security info.");
142            return;
143        },
144    };
145
146    if let (Some(devtools_chan), Some(security_info), Some(webview_id)) = (
147        context.devtools_chan.clone(),
148        meta.tls_security_info,
149        request.target_webview_id,
150    ) {
151        let update = NetworkEvent::SecurityInfo(SecurityInfoUpdate {
152            browsing_context_id: webview_id.into(),
153            security_info: Some(security_info),
154        });
155
156        let msg = ChromeToDevtoolsControlMsg::NetworkEvent(request.id.0.to_string(), update);
157
158        let _ = devtools_chan.send(DevtoolsControlMsg::FromChrome(msg));
159    }
160}
161
162pub(crate) fn send_early_httprequest_to_devtools(request: &Request, context: &FetchContext) {
163    // Do not forward data requests to devtools
164    if request.url().scheme() == "data" {
165        return;
166    }
167    if let (Some(devtools_chan), Some(browsing_context_id), Some(pipeline_id)) = (
168        context.devtools_chan.as_ref(),
169        request.target_webview_id.map(|id| id.into()),
170        request.pipeline_id,
171    ) {
172        // Build the partial DevtoolsHttpRequest
173        let devtools_request = DevtoolsHttpRequest {
174            url: request.current_url(),
175            method: request.method.clone(),
176            headers: request.headers.clone(),
177            body: None,
178            pipeline_id,
179            started_date_time: SystemTime::now(),
180            time_stamp: 0,
181            connect_time: Duration::from_millis(0),
182            send_time: Duration::from_millis(0),
183            destination: request.destination,
184            is_xhr: false,
185            browsing_context_id,
186        };
187
188        let msg = ChromeToDevtoolsControlMsg::NetworkEvent(
189            request.id.0.to_string(),
190            NetworkEvent::HttpRequest(devtools_request),
191        );
192
193        send_request_to_devtools(msg, devtools_chan);
194    }
195}