/* 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. //! //! Since these types can be sent through the IPC channel and use non //! self-describing serializers, the `flatten`, `skip*`, `tag` and `untagged` //! serde annotations are not supported. Types like `serde_json::Value` aren't //! supported either. For JSON serialization it is preferred to use a wrapper //! struct in the devtools crate instead. #![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 profile_traits::mem::ReportsChan; 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), /// Sent when a devtools client thread terminates. ClientExited, } /// 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), /// Perform a memory report. CollectMemoryReport(ReportsChan), } /// 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), DomMutation(PipelineId, DomMutation), /// The debugger is paused, sending frame information. DebuggerPause(PipelineId, FrameOffset, PauseReason), /// Get frame information from script CreateFrameActor(GenericSender, PipelineId, FrameInfo), /// Get environment information from script CreateEnvironmentActor(GenericSender, EnvironmentInfo, Option), } #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub enum DomMutation { AttributeModified { node: String, attribute_name: String, new_value: Option, }, } /// #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] #[serde(rename_all = "camelCase")] pub struct PropertyPreview { pub name: String, pub configurable: bool, pub enumerable: bool, pub writable: bool, pub is_accessor: bool, pub value_type: String, #[serde(skip_serializing_if = "Option::is_none")] pub boolean_value: Option, #[serde(skip_serializing_if = "Option::is_none")] pub number_value: Option, #[serde(skip_serializing_if = "Option::is_none")] pub string_value: Option, #[serde(skip_serializing_if = "Option::is_none")] pub object_class: Option, #[serde(skip_serializing_if = "Option::is_none")] pub value_name: Option, } #[derive(Debug, Deserialize, Serialize)] pub enum EvaluateJSReplyValue { VoidValue, NullValue, BooleanValue(bool), NumberValue(f64), StringValue(String), ActorValue { class: String, uuid: String, name: Option, // Function-specific display_name: Option, parameter_names: Option>, is_async: Option, is_generator: Option, // Object preview own_properties: Option>, own_properties_length: Option, // Array-specific kind: Option, array_length: Option, }, } #[derive(Debug, Deserialize, Serialize)] pub struct EvaluateJSReply { pub value: EvaluateJSReplyValue, pub has_exception: bool, } #[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 has_event_listeners: bool, } 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, 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 { pub top: bool, pub right: bool, pub bottom: bool, pub left: bool, } /// 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 { /// 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>>), /// Get information about event listeners on a node. GetEventListenerInfo(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 WebView containing the given pipeline to load a new URL, /// as if it was typed by the user. NavigateTo(PipelineId, ServoUrl), /// Direct the WebView containing the given pipeline to traverse history backward /// up to one step. GoBack(PipelineId), /// Direct the WebView containing the given pipeline to traverse history forward /// up to one step. GoForward(PipelineId), /// 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), Eval( String, PipelineId, Option, GenericSender, ), GetPossibleBreakpoints(u32, GenericSender>), SetBreakpoint(u32, u32, u32), ClearBreakpoint(u32, u32, u32), Interrupt, Resume(Option, Option), ListFrames(PipelineId, u32, u32, GenericSender>), GetEnvironment(String, GenericSender), } #[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)] #[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, MallocSizeOf)] #[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 } pub fn get_time_stamp() -> u64 { SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_millis() as u64 } #[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)] #[serde(rename_all = "camelCase")] pub struct ConsoleMessageFields { pub level: ConsoleLogLevel, pub filename: String, pub line_number: u32, pub column_number: u32, pub time_stamp: u64, } #[derive(Clone, Debug, Deserialize, Serialize)] pub enum ConsoleArgument { String(String), Integer(i32), Number(f64), Boolean(bool), Object(ConsoleArgumentObject), } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ConsoleArgumentObject { pub class: String, pub own_properties: Vec, } /// A property on a JS object passed as a console argument. #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ConsoleArgumentPropertyValue { pub key: String, pub configurable: bool, pub enumerable: bool, pub writable: bool, pub value: ConsoleArgument, } impl From for ConsoleArgument { fn from(value: String) -> Self { Self::String(value) } } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct ConsoleMessage { pub fields: ConsoleMessageFields, pub arguments: Vec, pub stacktrace: Option>, } #[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)] #[serde(rename_all = "camelCase")] pub struct PageError { pub error_message: String, pub source_name: String, pub line_number: u32, pub column_number: u32, pub time_stamp: u64, } #[derive(Debug, PartialEq, MallocSizeOf)] pub struct HttpRequest { pub url: ServoUrl, #[ignore_malloc_size_of = "http type"] pub method: Method, #[ignore_malloc_size_of = "http type"] 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, MallocSizeOf)] pub struct HttpResponse { #[ignore_malloc_size_of = "Http type"] 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, MallocSizeOf)] #[serde(rename_all = "camelCase")] pub struct CssDatabaseProperty { pub is_inherited: bool, pub values: Vec, pub supports: Vec, pub subproperties: Vec, } #[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, } #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct FrameInfo { pub display_name: String, pub on_stack: bool, pub oldest: bool, pub terminated: bool, pub type_: String, pub url: String, } #[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)] pub struct EnvironmentInfo { pub type_: Option, pub scope_kind: Option, pub function_display_name: Option, pub binding_variables: HashMap, } #[derive(Clone, Debug, Deserialize, Serialize)] pub struct EventListenerInfo { pub event_type: String, pub capturing: bool, } #[derive(Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct PauseReason { #[serde(rename = "type")] pub type_: String, pub on_next: Option, } #[derive(Debug, Deserialize, Serialize)] pub struct FrameOffset { pub actor: String, pub column: u32, pub line: u32, }