diff --git a/Cargo.lock b/Cargo.lock index 0848ba70dd7..2155f9befe7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1910,6 +1910,7 @@ name = "devtools" version = "0.0.1" dependencies = [ "base", + "base64 0.22.1", "chrono", "crossbeam-channel", "devtools_traits", diff --git a/components/devtools/Cargo.toml b/components/devtools/Cargo.toml index 564bdd0b7fe..0b150d9f715 100644 --- a/components/devtools/Cargo.toml +++ b/components/devtools/Cargo.toml @@ -13,6 +13,7 @@ path = "lib.rs" [dependencies] base = { workspace = true } +base64 = { workspace = true } chrono = { workspace = true } crossbeam-channel = { workspace = true } devtools_traits = { workspace = true } @@ -21,14 +22,15 @@ headers = { workspace = true } http = { workspace = true } ipc-channel = { workspace = true } log = { workspace = true } +net = { path = "../net" } net_traits = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } servo_config = { path = "../config" } servo_rand = { path = "../rand" } servo_url = { path = "../url" } -net = { path = "../net" } uuid = { workspace = true } + [build-dependencies] chrono = { workspace = true } diff --git a/components/devtools/actors/long_string.rs b/components/devtools/actors/long_string.rs new file mode 100644 index 00000000000..fc4217888e1 --- /dev/null +++ b/components/devtools/actors/long_string.rs @@ -0,0 +1,87 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use serde::Serialize; +use serde_json::{Map, Value}; + +use crate::StreamId; +use crate::actor::{Actor, ActorError, ActorRegistry}; +use crate::protocol::ClientRequest; + +const INITIAL_LENGTH: usize = 500; + +pub struct LongStringActor { + name: String, + full_string: String, +} + +#[derive(Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct LongStringObj { + #[serde(rename = "type")] + type_: String, + actor: String, + length: usize, + initial: String, +} + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct SubstringReply { + from: String, + substring: String, +} + +impl Actor for LongStringActor { + fn name(&self) -> String { + self.name.clone() + } + + fn handle_message( + &self, + request: ClientRequest, + _registry: &ActorRegistry, + msg_type: &str, + msg: &Map, + _id: StreamId, + ) -> Result<(), ActorError> { + match msg_type { + "substring" => { + let start = msg.get("start").and_then(|v| v.as_u64()).unwrap_or(0) as usize; + let end = msg + .get("end") + .and_then(|v| v.as_u64()) + .unwrap_or(self.full_string.len() as u64) as usize; + let substring: String = self + .full_string + .chars() + .skip(start) + .take(end - start) + .collect(); + let reply = SubstringReply { + from: self.name(), + substring, + }; + request.reply_final(&reply)? + }, + _ => return Err(ActorError::UnrecognizedPacketType), + } + Ok(()) + } +} + +impl LongStringActor { + pub fn new(registry: &ActorRegistry, full_string: String) -> Self { + let name = registry.new_name("longStringActor"); + LongStringActor { name, full_string } + } + + pub fn long_string_obj(&self) -> LongStringObj { + LongStringObj { + type_: "longString".to_string(), + actor: self.name.clone(), + length: self.full_string.len(), + initial: self.full_string.chars().take(INITIAL_LENGTH).collect(), + } + } +} diff --git a/components/devtools/actors/network_event.rs b/components/devtools/actors/network_event.rs index 93035280e0b..ec7baa27598 100644 --- a/components/devtools/actors/network_event.rs +++ b/components/devtools/actors/network_event.rs @@ -7,6 +7,8 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use base64::engine::Engine; +use base64::engine::general_purpose::STANDARD; use chrono::{Local, LocalResult, TimeZone}; use devtools_traits::{HttpRequest as DevtoolsHttpRequest, HttpResponse as DevtoolsHttpResponse}; use headers::{ContentLength, ContentType, Cookie, HeaderMapExt}; @@ -20,6 +22,7 @@ use servo_url::ServoUrl; use crate::StreamId; use crate::actor::{Actor, ActorError, ActorRegistry}; +use crate::actors::long_string::LongStringActor; use crate::network_handler::Cause; use crate::protocol::ClientRequest; @@ -145,7 +148,7 @@ struct GetResponseHeadersReply { #[serde(rename_all = "camelCase")] struct GetResponseContentReply { from: String, - content: Option>, + content: Option, content_discarded: bool, } @@ -182,6 +185,20 @@ pub struct ResponseCookieObj { pub same_site: Option, } +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +struct ResponseContentObj { + mime_type: String, + text: Value, + body_size: usize, + decoded_body_size: usize, + size: usize, + headers_size: usize, + transferred_size: usize, + #[serde(skip_serializing_if = "Option::is_none")] + encoding: Option, +} + #[derive(Clone, Serialize)] pub struct RequestCookieObj { pub name: String, @@ -226,7 +243,7 @@ impl Actor for NetworkEventActor { fn handle_message( &self, request: ClientRequest, - _registry: &ActorRegistry, + registry: &ActorRegistry, msg_type: &str, _msg: &Map, _id: StreamId, @@ -318,9 +335,61 @@ impl Actor for NetworkEventActor { request.reply_final(&msg)? }, "getResponseContent" => { + let content_obj = self.response_body.as_ref().map(|body| { + let mime_type = self + .response_content + .as_ref() + .map(|c| c.mime_type.clone()) + .unwrap_or_default(); + let headers_size = self + .response_headers + .as_ref() + .map(|h| h.headers_size) + .unwrap_or(0); + let transferred_size = self + .response_content + .as_ref() + .map(|c| c.transferred_size as usize) + .unwrap_or(0); + let body_size = body.len(); + let decoded_body_size = body.len(); + let size = body.len(); + + if Self::is_text_mime(&mime_type) { + let full_str = String::from_utf8_lossy(body).to_string(); + + // Queue a LongStringActor for this body + let long_string_actor = LongStringActor::new(registry, full_str); + let long_string_obj = long_string_actor.long_string_obj(); + registry.register_later(Box::new(long_string_actor)); + + ResponseContentObj { + mime_type, + text: serde_json::to_value(long_string_obj).unwrap(), + body_size, + decoded_body_size, + size, + headers_size, + transferred_size, + encoding: None, + } + } else { + let b64 = STANDARD.encode(body); + ResponseContentObj { + mime_type, + text: serde_json::to_value(b64).unwrap(), + body_size, + decoded_body_size, + size, + headers_size, + transferred_size, + encoding: Some("base64".to_string()), + } + } + }); let msg = GetResponseContentReply { from: self.name(), - content: self.response_body.clone(), + content: content_obj, content_discarded: self.response_body.is_none(), }; request.reply_final(&msg)? @@ -407,8 +476,9 @@ impl NetworkEventActor { .as_ref() .and_then(|url| Self::response_cookies(&response, url)); self.response_start = Some(Self::response_start(&response)); - self.response_content = Self::response_content(&response); - self.response_body = response.body.clone(); + if let Some(response_content) = Self::response_content(self, &response) { + self.response_content = Some(response_content); + } self.response_headers_raw = response.headers.clone(); } @@ -459,8 +529,12 @@ impl NetworkEventActor { } } - pub fn response_content(response: &DevtoolsHttpResponse) -> Option { - let _body = response.body.as_ref()?; + pub fn response_content( + &mut self, + response: &DevtoolsHttpResponse, + ) -> Option { + let body = response.body.as_ref()?; + self.response_body = Some(body.clone()); let mime_type = response .headers @@ -481,7 +555,7 @@ impl NetworkEventActor { mime_type, content_size: content_size.unwrap_or(0) as u32, transferred_size: transferred_size.unwrap_or(0) as u32, - discard_response_body: true, + discard_response_body: false, }) } @@ -566,6 +640,16 @@ impl NetworkEventActor { } } + pub fn is_text_mime(mime: &str) -> bool { + let lower = mime.to_ascii_lowercase(); + lower.starts_with("text/") || + lower.contains("json") || + lower.contains("javascript") || + lower.contains("xml") || + lower.contains("csv") || + lower.contains("html") + } + fn insert_serialized_map(map: &mut Map, obj: &Option) { if let Some(value) = obj { if let Ok(Value::Object(serialized)) = serde_json::to_value(value) { diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index d99742c942b..d82b7b255da 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -60,6 +60,7 @@ mod actors { pub mod device; pub mod framerate; pub mod inspector; + pub mod long_string; pub mod memory; pub mod network_event; pub mod object;