/* 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/. */ //! This module contains shared types and messages for use by devtools/script. //! The traits are here instead of in script so that the devtools crate can be //! modified independently of the rest of Servo. #![crate_name = "devtools_traits"] #![crate_type = "rlib"] #![deny(unsafe_code)] use core::fmt; use std::collections::HashMap; use std::fmt::Display; use std::net::TcpStream; use std::str::FromStr; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use base::cross_process_instant::CrossProcessInstant; use base::generic_channel::GenericSender; use base::id::{BrowsingContextId, PipelineId, WebViewId}; pub use embedder_traits::ConsoleLogLevel; use embedder_traits::Theme; use http::{HeaderMap, Method}; use malloc_size_of_derive::MallocSizeOf; use net_traits::http_status::HttpStatus; use net_traits::request::Destination; use net_traits::{DebugVec, TlsSecurityInfo}; use serde::{Deserialize, Serialize}; use servo_url::ServoUrl; use uuid::Uuid; // Information would be attached to NewGlobal to be received and show in devtools. // Extend these fields if we need more information. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct DevtoolsPageInfo { pub title: String, pub url: ServoUrl, pub is_top_level_global: bool, } #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct CSSError { pub filename: String, pub line: u32, pub column: u32, pub msg: String, } /// Messages to instruct the devtools server to update its known actors/state /// according to changes in the browser. #[derive(Debug)] pub enum DevtoolsControlMsg { /// Messages from threads in the chrome process (resource/constellation/devtools) FromChrome(ChromeToDevtoolsControlMsg), /// Messages from script threads FromScript(ScriptToDevtoolsControlMsg), } /// Events that the devtools server must act upon. // FIXME: https://github.com/servo/servo/issues/34591 #[expect(clippy::large_enum_variant)] #[derive(Debug)] pub enum ChromeToDevtoolsControlMsg { /// A new client has connected to the server. AddClient(TcpStream), /// The browser is shutting down. ServerExitMsg, /// A network event occurred (request, reply, etc.). The actor with the /// provided name should be notified. NetworkEvent(String, NetworkEvent), } /// The state of a page navigation. #[derive(Debug, Deserialize, Serialize)] pub enum NavigationState { /// A browsing context is about to navigate to a given URL. Start(ServoUrl), /// A browsing context has completed navigating to the provided pipeline. Stop(PipelineId, DevtoolsPageInfo), } #[derive(Debug, Deserialize, Serialize)] /// Events that the devtools server must act upon. pub enum ScriptToDevtoolsControlMsg { /// A new global object was created, associated with a particular pipeline. /// The means of communicating directly with it are provided. NewGlobal( (BrowsingContextId, PipelineId, Option, WebViewId), GenericSender, DevtoolsPageInfo, ), /// The given browsing context is performing a navigation. Navigate(BrowsingContextId, NavigationState), /// A particular page has invoked the console API. ConsoleAPI(PipelineId, ConsoleMessage, Option), /// Request to clear the console for a given pipeline. ClearConsole(PipelineId, Option), /// An animation frame with the given timestamp was processed in a script thread. /// The actor with the provided name should be notified. FramerateTick(String, f64), /// Report a CSS parse error for the given pipeline ReportCSSError(PipelineId, CSSError), /// Report a page error for the given pipeline ReportPageError(PipelineId, PageError), /// Report a page title change TitleChanged(PipelineId, String), /// Get source information from script CreateSourceActor( GenericSender, PipelineId, SourceInfo, ), UpdateSourceContent(PipelineId, String), } /// Serialized JS return values /// TODO: generalize this beyond the EvaluateJS message? #[derive(Debug, Deserialize, Serialize)] pub enum EvaluateJSReply { VoidValue, NullValue, BooleanValue(bool), NumberValue(f64), StringValue(String), ActorValue { class: String, uuid: String }, } #[derive(Debug, Deserialize, Serialize)] pub struct AttrInfo { pub namespace: String, pub name: String, pub value: String, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct NodeInfo { pub unique_id: String, pub host: Option, #[serde(rename = "baseURI")] pub base_uri: String, pub parent: String, pub node_type: u16, pub node_name: String, pub node_value: Option, pub num_children: usize, pub attrs: Vec, pub is_top_level_document: bool, pub shadow_root_mode: Option, pub is_shadow_host: bool, pub display: Option, /// Whether this node is currently displayed. /// /// For example, the node might have `display: none`. pub is_displayed: bool, /// The `DOCTYPE` name if this is a `DocumentType` node, `None` otherwise pub doctype_name: Option, /// The `DOCTYPE` public identifier if this is a `DocumentType` node , `None` otherwise pub doctype_public_identifier: Option, /// The `DOCTYPE` system identifier if this is a `DocumentType` node, `None` otherwise pub doctype_system_identifier: Option, } pub struct StartedTimelineMarker { name: String, start_time: CrossProcessInstant, start_stack: Option>, } #[derive(Debug, Deserialize, Serialize)] pub struct TimelineMarker { pub name: String, pub start_time: CrossProcessInstant, pub start_stack: Option>, pub end_time: CrossProcessInstant, pub end_stack: Option>, } #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub enum TimelineMarkerType { Reflow, DOMEvent, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct NodeStyle { pub name: String, pub value: String, pub priority: String, } /// The properties of a DOM node as computed by layout. #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "kebab-case")] pub struct ComputedNodeLayout { pub display: String, pub position: String, pub z_index: String, pub box_sizing: String, #[serde(rename = "autoMargins")] pub auto_margins: AutoMargins, pub margin_top: String, pub margin_right: String, pub margin_bottom: String, pub margin_left: String, pub border_top_width: String, pub border_right_width: String, pub border_bottom_width: String, pub border_left_width: String, pub padding_top: String, pub padding_right: String, pub padding_bottom: String, pub padding_left: String, pub width: f32, pub height: f32, } #[derive(Debug, Default, Deserialize, Serialize)] pub struct AutoMargins { #[serde(skip_serializing_if = "Option::is_none")] pub top: Option, #[serde(skip_serializing_if = "Option::is_none")] pub right: Option, #[serde(skip_serializing_if = "Option::is_none")] pub bottom: Option, #[serde(skip_serializing_if = "Option::is_none")] pub left: Option, } /// Messages to process in a particular script thread, as instructed by a devtools client. /// TODO: better error handling, e.g. if pipeline id lookup fails? #[derive(Debug, Deserialize, Serialize)] pub enum DevtoolScriptControlMsg { /// Evaluate a JS snippet in the context of the global for the given pipeline. EvaluateJS(PipelineId, String, GenericSender), /// Retrieve the details of the root node (ie. the document) for the given pipeline. GetRootNode(PipelineId, GenericSender>), /// Retrieve the details of the document element for the given pipeline. GetDocumentElement(PipelineId, GenericSender>), /// Retrieve the details of the child nodes of the given node in the given pipeline. GetChildren(PipelineId, String, GenericSender>>), /// Retrieve the CSS style properties defined in the attribute tag for the given node. GetAttributeStyle(PipelineId, String, GenericSender>>), /// Retrieve the CSS style properties defined in an stylesheet for the given selector. GetStylesheetStyle( PipelineId, String, String, usize, GenericSender>>, ), /// Retrieves the CSS selectors for the given node. A selector is comprised of the text /// of the selector and the id of the stylesheet that contains it. GetSelectors( PipelineId, String, GenericSender>>, ), /// Retrieve the computed CSS style properties for the given node. GetComputedStyle(PipelineId, String, GenericSender>>), /// Retrieve the computed layout properties of the given node in the given pipeline. GetLayout( PipelineId, String, GenericSender>, ), /// Get a unique XPath selector for the node. GetXPath(PipelineId, String, GenericSender), /// Update a given node's attributes with a list of modifications. ModifyAttribute(PipelineId, String, Vec), /// Update a given node's style rules with a list of modifications. ModifyRule(PipelineId, String, Vec), /// Request live console messages for a given pipeline (true if desired, false otherwise). WantsLiveNotifications(PipelineId, bool), /// Request live notifications for a given set of timeline events for a given pipeline. SetTimelineMarkers( PipelineId, Vec, GenericSender>, ), /// Withdraw request for live timeline notifications for a given pipeline. DropTimelineMarkers(PipelineId, Vec), /// Request a callback directed at the given actor name from the next animation frame /// executed in the given pipeline. RequestAnimationFrame(PipelineId, String), /// Direct the given pipeline to reload the current page. Reload(PipelineId), /// Gets the list of all allowed CSS rules and possible values. GetCssDatabase(GenericSender>), /// Simulates a light or dark color scheme for the given pipeline SimulateColorScheme(PipelineId, Theme), /// Highlight the given DOM node HighlightDomNode(PipelineId, Option), GetPossibleBreakpoints(u32, GenericSender>), SetBreakpoint(u32, u32, u32), } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct AttrModification { pub attribute_name: String, pub new_value: Option, } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RuleModification { #[serde(rename = "type")] pub type_: String, pub index: u32, pub name: String, pub value: String, pub priority: String, } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct StackFrame { pub filename: String, pub function_name: String, pub column_number: u32, pub line_number: u32, // Not implemented in Servo // source_id } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum ConsoleMessageArgument { String(String), Integer(i32), Number(f64), } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConsoleMessage { pub level: ConsoleLogLevel, pub filename: String, pub line_number: u32, pub column_number: u32, pub time_stamp: u64, pub arguments: Vec, #[serde(skip_serializing_if = "Option::is_none")] pub stacktrace: Option>, // Not implemented in Servo // inner_window_id // source_id } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PageError { pub error_message: String, pub source_name: String, pub line_number: u32, pub column_number: u32, pub category: String, pub time_stamp: u64, pub error: bool, pub warning: bool, pub info: bool, pub private: bool, #[serde(skip_serializing_if = "Option::is_none")] pub stacktrace: Option>, // Not implemented in Servo // inner_window_id // source_id // has_exception // exception } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PageErrorWrapper { pub page_error: PageError, } impl From for PageErrorWrapper { fn from(page_error: PageError) -> Self { Self { page_error } } } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum ConsoleResource { ConsoleMessage(ConsoleMessage), PageError(PageErrorWrapper), } impl ConsoleResource { pub fn resource_type(&self) -> String { match self { ConsoleResource::ConsoleMessage(_) => "console-message".into(), ConsoleResource::PageError(_) => "error-message".into(), } } } #[derive(Debug, PartialEq)] pub struct HttpRequest { pub url: ServoUrl, pub method: Method, pub headers: HeaderMap, pub body: Option, pub pipeline_id: PipelineId, pub started_date_time: SystemTime, pub time_stamp: i64, pub connect_time: Duration, pub send_time: Duration, pub destination: Destination, pub is_xhr: bool, pub browsing_context_id: BrowsingContextId, } #[derive(Debug, PartialEq)] pub struct HttpResponse { pub headers: Option, pub status: HttpStatus, pub body: Option, pub from_cache: bool, pub pipeline_id: PipelineId, pub browsing_context_id: BrowsingContextId, } #[derive(Debug, PartialEq)] pub struct SecurityInfoUpdate { pub browsing_context_id: BrowsingContextId, pub security_info: Option, } #[derive(Debug)] pub enum NetworkEvent { HttpRequest(HttpRequest), HttpRequestUpdate(HttpRequest), HttpResponse(HttpResponse), SecurityInfo(SecurityInfoUpdate), } impl NetworkEvent { pub fn forward_to_devtools(&self) -> bool { match self { NetworkEvent::HttpRequest(http_request) => http_request.url.scheme() != "data", NetworkEvent::HttpRequestUpdate(_) => true, NetworkEvent::HttpResponse(_) => true, NetworkEvent::SecurityInfo(_) => true, } } } impl TimelineMarker { pub fn start(name: String) -> StartedTimelineMarker { StartedTimelineMarker { name, start_time: CrossProcessInstant::now(), start_stack: None, } } } impl StartedTimelineMarker { pub fn end(self) -> TimelineMarker { TimelineMarker { name: self.name, start_time: self.start_time, start_stack: self.start_stack, end_time: CrossProcessInstant::now(), end_stack: None, } } } #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub struct WorkerId(pub Uuid); impl Display for WorkerId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.0) } } impl FromStr for WorkerId { type Err = uuid::Error; fn from_str(s: &str) -> Result { Ok(Self(s.parse()?)) } } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct CssDatabaseProperty { pub is_inherited: bool, pub values: Vec, pub supports: Vec, pub subproperties: Vec, } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum ConsoleArgument { String(String), Integer(i32), Number(f64), } impl From for ConsoleArgument { fn from(value: ConsoleMessageArgument) -> Self { match value { ConsoleMessageArgument::String(string) => Self::String(string), ConsoleMessageArgument::Integer(integer) => Self::Integer(integer), ConsoleMessageArgument::Number(number) => Self::Number(number), } } } impl From for ConsoleMessageArgument { fn from(value: String) -> Self { Self::String(value) } } pub struct ConsoleMessageBuilder { level: ConsoleLogLevel, filename: String, line_number: u32, column_number: u32, arguments: Vec, stack_trace: Option>, } impl ConsoleMessageBuilder { pub fn new( level: ConsoleLogLevel, filename: String, line_number: u32, column_number: u32, ) -> Self { Self { level, filename, line_number, column_number, arguments: vec![], stack_trace: None, } } pub fn attach_stack_trace(&mut self, stack_trace: Vec) -> &mut Self { self.stack_trace = Some(stack_trace); self } pub fn add_argument(&mut self, argument: ConsoleMessageArgument) -> &mut Self { self.arguments.push(argument); self } pub fn finish(self) -> ConsoleMessage { let time_stamp = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_millis() as u64; ConsoleMessage { level: self.level, filename: self.filename, line_number: self.line_number, column_number: self.column_number, time_stamp, arguments: self.arguments, stacktrace: self.stack_trace, } } } #[derive(Debug, Deserialize, Serialize)] pub enum ShadowRootMode { Open, Closed, } impl fmt::Display for ShadowRootMode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Open => write!(f, "open"), Self::Closed => write!(f, "close"), } } } #[derive(Debug, Deserialize, Serialize)] pub struct SourceInfo { pub url: ServoUrl, pub introduction_type: String, pub inline: bool, pub worker_id: Option, pub content: Option, pub content_type: Option, pub spidermonkey_id: u32, } #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct RecommendedBreakpointLocation { pub script_id: u32, pub offset: u32, pub line_number: u32, pub column_number: u32, pub is_step_start: bool, }