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 crossbeam_channel::Sender;
8use devtools_traits::{
9    ChromeToDevtoolsControlMsg, DevtoolsControlMsg, HttpRequest as DevtoolsHttpRequest,
10    HttpResponse as DevtoolsHttpResponse, NetworkEvent, SecurityInfoUpdate,
11};
12use http::{HeaderMap, Method};
13use hyper_serde::Serde;
14use log::error;
15use net_traits::http_status::HttpStatus;
16use net_traits::request::{Destination, Request};
17use net_traits::response::{CacheState, Response};
18use net_traits::{DebugVec, FetchMetadata};
19use servo_base::id::{BrowsingContextId, PipelineId};
20use servo_url::ServoUrl;
21
22use crate::fetch::methods::FetchContext;
23
24#[allow(clippy::too_many_arguments)]
25pub(crate) fn prepare_devtools_request(
26    request_id: String,
27    url: ServoUrl,
28    method: Method,
29    headers: HeaderMap,
30    body: Option<Vec<u8>>,
31    pipeline_id: PipelineId,
32    connect_time: Duration,
33    send_time: Duration,
34    destination: Destination,
35    is_xhr: bool,
36    browsing_context_id: BrowsingContextId,
37) -> ChromeToDevtoolsControlMsg {
38    let started_date_time = SystemTime::now();
39    let request = DevtoolsHttpRequest {
40        url,
41        method,
42        headers,
43        body: body.map(DebugVec::from),
44        pipeline_id,
45        started_date_time,
46        time_stamp: started_date_time
47            .duration_since(UNIX_EPOCH)
48            .unwrap_or_default()
49            .as_secs() as i64,
50        connect_time,
51        send_time,
52        destination,
53        is_xhr,
54        browsing_context_id,
55    };
56    let net_event = NetworkEvent::HttpRequestUpdate(request);
57
58    ChromeToDevtoolsControlMsg::NetworkEvent(request_id, net_event)
59}
60
61pub(crate) fn send_request_to_devtools(
62    msg: ChromeToDevtoolsControlMsg,
63    devtools_chan: &Sender<DevtoolsControlMsg>,
64) {
65    if matches!(msg, ChromeToDevtoolsControlMsg::NetworkEvent(_, ref network_event) if !network_event.forward_to_devtools())
66    {
67        return;
68    }
69    if let Err(e) = devtools_chan.send(DevtoolsControlMsg::FromChrome(msg)) {
70        error!("DevTools send failed: {e}");
71    }
72}
73
74pub(crate) fn send_response_to_devtools(
75    request: &Request,
76    context: &FetchContext,
77    response: &Response,
78    body_data: Option<Vec<u8>>,
79) {
80    let meta = match response.metadata() {
81        Ok(FetchMetadata::Unfiltered(m)) => m,
82        Ok(FetchMetadata::Filtered { unsafe_, .. }) => unsafe_,
83        Err(_) => {
84            log::warn!("No metadata available, skipping devtools response.");
85            return;
86        },
87    };
88    send_response_values_to_devtools(
89        meta.headers.map(Serde::into_inner),
90        meta.status,
91        body_data,
92        response.cache_state,
93        request,
94        context.devtools_chan.clone(),
95    );
96}
97
98#[allow(clippy::too_many_arguments)]
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(DebugVec::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}