Files
servo/components/shared/devtools/lib.rs
atbrakhi 9e5e5603df devtools: Use DebuggerValue in console (#44064)
Use same `DebuggerValue` in console actor as well. This helps us have
single source of truth!

Testing: Added a new test
Fixes: part of https://github.com/servo/servo/issues/39858

Signed-off-by: atbrakhi <atbrakhi@igalia.com>
Co-authored-by: eri <eri@igalia.com>
2026-04-09 14:25:02 +00:00

646 lines
20 KiB
Rust

/* 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};
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_base::cross_process_instant::CrossProcessInstant;
use servo_base::generic_channel::GenericSender;
use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
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,
pub is_service_worker: 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<WorkerId>, WebViewId),
GenericSender<DevtoolScriptControlMsg>,
DevtoolsPageInfo,
),
/// The given browsing context is performing a navigation.
Navigate(BrowsingContextId, NavigationState),
/// A particular page has invoked the console API.
ConsoleAPI(PipelineId, ConsoleMessage, Option<WorkerId>),
/// Request to clear the console for a given pipeline.
ClearConsole(PipelineId, Option<WorkerId>),
/// 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<DevtoolScriptControlMsg>,
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<String>, PipelineId, FrameInfo),
/// Get environment information from script
CreateEnvironmentActor(GenericSender<String>, EnvironmentInfo, Option<String>),
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub enum DomMutation {
AttributeModified {
node: String,
attribute_name: String,
new_value: Option<String>,
},
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct ObjectPreview {
pub kind: String,
pub own_properties: Option<Vec<PropertyDescriptor>>,
pub own_properties_length: Option<u32>,
pub function: Option<FunctionPreview>,
pub array_length: Option<u32>,
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct FunctionPreview {
pub name: Option<String>,
pub display_name: Option<String>,
pub parameter_names: Vec<String>,
pub is_async: Option<bool>,
pub is_generator: Option<bool>,
}
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub enum DebuggerValue {
VoidValue,
NullValue,
BooleanValue(bool),
NumberValue(f64),
StringValue(String),
ObjectValue {
uuid: String,
class: String,
preview: Option<ObjectPreview>,
},
}
/// <https://searchfox.org/mozilla-central/source/devtools/server/actors/object/property-iterator.js#51>
#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
pub struct PropertyDescriptor {
pub name: String,
pub value: DebuggerValue,
pub configurable: bool,
pub enumerable: bool,
pub writable: bool,
pub is_accessor: bool,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct EvaluateJSReply {
pub value: DebuggerValue,
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<String>,
#[serde(rename = "baseURI")]
pub base_uri: String,
pub parent: String,
pub node_type: u16,
pub node_name: String,
pub node_value: Option<String>,
pub num_children: usize,
pub attrs: Vec<AttrInfo>,
pub is_top_level_document: bool,
pub shadow_root_mode: Option<ShadowRootMode>,
pub is_shadow_host: bool,
pub display: Option<String>,
/// 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<String>,
/// The `DOCTYPE` public identifier if this is a `DocumentType` node , `None` otherwise
pub doctype_public_identifier: Option<String>,
/// The `DOCTYPE` system identifier if this is a `DocumentType` node, `None` otherwise
pub doctype_system_identifier: Option<String>,
pub has_event_listeners: bool,
}
pub struct StartedTimelineMarker {
name: String,
start_time: CrossProcessInstant,
start_stack: Option<Vec<()>>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct TimelineMarker {
pub name: String,
pub start_time: CrossProcessInstant,
pub start_stack: Option<Vec<()>>,
pub end_time: CrossProcessInstant,
pub end_stack: Option<Vec<()>>,
}
#[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,
}
#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf, PartialEq, Eq, Hash)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum AncestorData {
Layer {
actor_id: Option<String>,
value: Option<String>,
},
}
#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf, PartialEq, Eq, Hash)]
#[serde(rename_all = "camelCase")]
pub struct MatchedRule {
pub selector: String,
pub stylesheet_index: usize,
pub block_id: usize,
pub ancestor_data: Vec<AncestorData>,
}
/// 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<Option<NodeInfo>>),
/// Retrieve the details of the document element for the given pipeline.
GetDocumentElement(PipelineId, GenericSender<Option<NodeInfo>>),
/// Retrieve the details of the child nodes of the given node in the given pipeline.
GetChildren(PipelineId, String, GenericSender<Option<Vec<NodeInfo>>>),
/// Retrieve the CSS style properties defined in the attribute tag for the given node.
GetAttributeStyle(PipelineId, String, GenericSender<Option<Vec<NodeStyle>>>),
/// Retrieve the CSS style properties defined in an stylesheet for the given selector.
GetStylesheetStyle(
PipelineId,
String,
MatchedRule,
GenericSender<Option<Vec<NodeStyle>>>,
),
/// 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<Option<Vec<MatchedRule>>>),
/// Retrieve the computed CSS style properties for the given node.
GetComputedStyle(PipelineId, String, GenericSender<Option<Vec<NodeStyle>>>),
/// Get information about event listeners on a node.
GetEventListenerInfo(PipelineId, String, GenericSender<Vec<EventListenerInfo>>),
/// Retrieve the computed layout properties of the given node in the given pipeline.
GetLayout(
PipelineId,
String,
GenericSender<Option<(ComputedNodeLayout, AutoMargins)>>,
),
/// Get a unique XPath selector for the node.
GetXPath(PipelineId, String, GenericSender<String>),
/// Update a given node's attributes with a list of modifications.
ModifyAttribute(PipelineId, String, Vec<AttrModification>),
/// Update a given node's style rules with a list of modifications.
ModifyRule(PipelineId, String, Vec<RuleModification>),
/// 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<TimelineMarkerType>,
GenericSender<Option<TimelineMarker>>,
),
/// Withdraw request for live timeline notifications for a given pipeline.
DropTimelineMarkers(PipelineId, Vec<TimelineMarkerType>),
/// 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<HashMap<String, CssDatabaseProperty>>),
/// Simulates a light or dark color scheme for the given pipeline
SimulateColorScheme(PipelineId, Theme),
/// Highlight the given DOM node
HighlightDomNode(PipelineId, Option<String>),
Eval(
String,
PipelineId,
Option<String>,
GenericSender<EvaluateJSReply>,
),
GetPossibleBreakpoints(u32, GenericSender<Vec<RecommendedBreakpointLocation>>),
SetBreakpoint(u32, u32, u32),
ClearBreakpoint(u32, u32, u32),
Interrupt,
Resume(Option<String>, Option<String>),
ListFrames(PipelineId, u32, u32, GenericSender<Vec<String>>),
GetEnvironment(String, GenericSender<String>),
}
#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
#[serde(rename_all = "camelCase")]
pub struct AttrModification {
pub attribute_name: String,
pub new_value: Option<String>,
}
#[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 struct ConsoleMessage {
pub fields: ConsoleMessageFields,
pub arguments: Vec<DebuggerValue>,
pub stacktrace: Option<Vec<StackFrame>>,
}
#[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,
pub method: Method,
pub headers: HeaderMap,
pub body: Option<DebugVec>,
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<HeaderMap>,
pub status: HttpStatus,
pub body: Option<DebugVec>,
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<TlsSecurityInfo>,
}
#[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<Self, Self::Err> {
Ok(Self(s.parse()?))
}
}
#[derive(Debug, Deserialize, Serialize, MallocSizeOf)]
#[serde(rename_all = "camelCase")]
pub struct CssDatabaseProperty {
pub is_inherited: bool,
pub values: Vec<String>,
pub supports: Vec<String>,
pub subproperties: Vec<String>,
}
#[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<WorkerId>,
pub content: Option<String>,
pub content_type: Option<String>,
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<String>,
pub scope_kind: Option<String>,
pub function_display_name: Option<String>,
pub binding_variables: Vec<PropertyDescriptor>,
}
#[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<bool>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct FrameOffset {
pub actor: String,
pub column: u32,
pub line: u32,
}