devtools_traits/
lib.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
5//! This module contains shared types and messages for use by devtools/script.
6//! The traits are here instead of in script so that the devtools crate can be
7//! modified independently of the rest of Servo.
8
9#![crate_name = "devtools_traits"]
10#![crate_type = "rlib"]
11#![deny(unsafe_code)]
12
13use core::fmt;
14use std::collections::HashMap;
15use std::fmt::Display;
16use std::net::TcpStream;
17use std::str::FromStr;
18use std::time::{Duration, SystemTime, UNIX_EPOCH};
19
20use base::cross_process_instant::CrossProcessInstant;
21use base::id::{BrowsingContextId, PipelineId, WebViewId};
22use bitflags::bitflags;
23use embedder_traits::Theme;
24use http::{HeaderMap, Method};
25use ipc_channel::ipc::IpcSender;
26use malloc_size_of_derive::MallocSizeOf;
27use net_traits::DebugVec;
28use net_traits::http_status::HttpStatus;
29use net_traits::request::Destination;
30use serde::{Deserialize, Serialize};
31use servo_url::ServoUrl;
32use uuid::Uuid;
33
34// Information would be attached to NewGlobal to be received and show in devtools.
35// Extend these fields if we need more information.
36#[derive(Clone, Debug, Deserialize, Serialize)]
37pub struct DevtoolsPageInfo {
38    pub title: String,
39    pub url: ServoUrl,
40    pub is_top_level_global: bool,
41}
42
43#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
44pub struct CSSError {
45    pub filename: String,
46    pub line: u32,
47    pub column: u32,
48    pub msg: String,
49}
50
51/// Messages to instruct the devtools server to update its known actors/state
52/// according to changes in the browser.
53#[derive(Debug)]
54pub enum DevtoolsControlMsg {
55    /// Messages from threads in the chrome process (resource/constellation/devtools)
56    FromChrome(ChromeToDevtoolsControlMsg),
57    /// Messages from script threads
58    FromScript(ScriptToDevtoolsControlMsg),
59}
60
61/// Events that the devtools server must act upon.
62// FIXME: https://github.com/servo/servo/issues/34591
63#[expect(clippy::large_enum_variant)]
64#[derive(Debug)]
65pub enum ChromeToDevtoolsControlMsg {
66    /// A new client has connected to the server.
67    AddClient(TcpStream),
68    /// The browser is shutting down.
69    ServerExitMsg,
70    /// A network event occurred (request, reply, etc.). The actor with the
71    /// provided name should be notified.
72    NetworkEvent(String, NetworkEvent),
73}
74
75/// The state of a page navigation.
76#[derive(Debug, Deserialize, Serialize)]
77pub enum NavigationState {
78    /// A browsing context is about to navigate to a given URL.
79    Start(ServoUrl),
80    /// A browsing context has completed navigating to the provided pipeline.
81    Stop(PipelineId, DevtoolsPageInfo),
82}
83
84#[derive(Debug, Deserialize, Serialize)]
85/// Events that the devtools server must act upon.
86pub enum ScriptToDevtoolsControlMsg {
87    /// A new global object was created, associated with a particular pipeline.
88    /// The means of communicating directly with it are provided.
89    NewGlobal(
90        (BrowsingContextId, PipelineId, Option<WorkerId>, WebViewId),
91        IpcSender<DevtoolScriptControlMsg>,
92        DevtoolsPageInfo,
93    ),
94    /// The given browsing context is performing a navigation.
95    Navigate(BrowsingContextId, NavigationState),
96    /// A particular page has invoked the console API.
97    ConsoleAPI(PipelineId, ConsoleMessage, Option<WorkerId>),
98    /// An animation frame with the given timestamp was processed in a script thread.
99    /// The actor with the provided name should be notified.
100    FramerateTick(String, f64),
101
102    /// Report a CSS parse error for the given pipeline
103    ReportCSSError(PipelineId, CSSError),
104
105    /// Report a page error for the given pipeline
106    ReportPageError(PipelineId, PageError),
107
108    /// Report a page title change
109    TitleChanged(PipelineId, String),
110
111    /// Get source information from script
112    CreateSourceActor(IpcSender<DevtoolScriptControlMsg>, PipelineId, SourceInfo),
113
114    UpdateSourceContent(PipelineId, String),
115}
116
117/// Serialized JS return values
118/// TODO: generalize this beyond the EvaluateJS message?
119#[derive(Debug, Deserialize, Serialize)]
120pub enum EvaluateJSReply {
121    VoidValue,
122    NullValue,
123    BooleanValue(bool),
124    NumberValue(f64),
125    StringValue(String),
126    ActorValue { class: String, uuid: String },
127}
128
129#[derive(Debug, Deserialize, Serialize)]
130pub struct AttrInfo {
131    pub namespace: String,
132    pub name: String,
133    pub value: String,
134}
135
136#[derive(Debug, Deserialize, Serialize)]
137#[serde(rename_all = "camelCase")]
138pub struct NodeInfo {
139    pub unique_id: String,
140    pub host: Option<String>,
141    #[serde(rename = "baseURI")]
142    pub base_uri: String,
143    pub parent: String,
144    pub node_type: u16,
145    pub node_name: String,
146    pub node_value: Option<String>,
147    pub num_children: usize,
148    pub attrs: Vec<AttrInfo>,
149    pub is_top_level_document: bool,
150    pub shadow_root_mode: Option<ShadowRootMode>,
151    pub is_shadow_host: bool,
152    pub display: Option<String>,
153    /// Whether this node is currently displayed.
154    ///
155    /// For example, the node might have `display: none`.
156    pub is_displayed: bool,
157
158    /// The `DOCTYPE` name if this is a `DocumentType` node, `None` otherwise
159    pub doctype_name: Option<String>,
160
161    /// The `DOCTYPE` public identifier if this is a `DocumentType` node , `None` otherwise
162    pub doctype_public_identifier: Option<String>,
163
164    /// The `DOCTYPE` system identifier if this is a `DocumentType` node, `None` otherwise
165    pub doctype_system_identifier: Option<String>,
166}
167
168pub struct StartedTimelineMarker {
169    name: String,
170    start_time: CrossProcessInstant,
171    start_stack: Option<Vec<()>>,
172}
173
174#[derive(Debug, Deserialize, Serialize)]
175pub struct TimelineMarker {
176    pub name: String,
177    pub start_time: CrossProcessInstant,
178    pub start_stack: Option<Vec<()>>,
179    pub end_time: CrossProcessInstant,
180    pub end_stack: Option<Vec<()>>,
181}
182
183#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
184pub enum TimelineMarkerType {
185    Reflow,
186    DOMEvent,
187}
188
189#[derive(Debug, Deserialize, Serialize)]
190#[serde(rename_all = "camelCase")]
191pub struct NodeStyle {
192    pub name: String,
193    pub value: String,
194    pub priority: String,
195}
196
197/// The properties of a DOM node as computed by layout.
198#[derive(Debug, Deserialize, Serialize)]
199#[serde(rename_all = "camelCase")]
200pub struct ComputedNodeLayout {
201    pub display: String,
202    pub position: String,
203    pub z_index: String,
204    pub box_sizing: String,
205
206    pub auto_margins: AutoMargins,
207    pub margin_top: String,
208    pub margin_right: String,
209    pub margin_bottom: String,
210    pub margin_left: String,
211
212    pub border_top_width: String,
213    pub border_right_width: String,
214    pub border_bottom_width: String,
215    pub border_left_width: String,
216
217    pub padding_top: String,
218    pub padding_right: String,
219    pub padding_bottom: String,
220    pub padding_left: String,
221
222    pub width: f32,
223    pub height: f32,
224}
225
226#[derive(Debug, Deserialize, Serialize)]
227pub struct AutoMargins {
228    pub top: bool,
229    pub right: bool,
230    pub bottom: bool,
231    pub left: bool,
232}
233
234/// Messages to process in a particular script thread, as instructed by a devtools client.
235/// TODO: better error handling, e.g. if pipeline id lookup fails?
236#[derive(Debug, Deserialize, Serialize)]
237pub enum DevtoolScriptControlMsg {
238    /// Evaluate a JS snippet in the context of the global for the given pipeline.
239    EvaluateJS(PipelineId, String, IpcSender<EvaluateJSReply>),
240    /// Retrieve the details of the root node (ie. the document) for the given pipeline.
241    GetRootNode(PipelineId, IpcSender<Option<NodeInfo>>),
242    /// Retrieve the details of the document element for the given pipeline.
243    GetDocumentElement(PipelineId, IpcSender<Option<NodeInfo>>),
244    /// Retrieve the details of the child nodes of the given node in the given pipeline.
245    GetChildren(PipelineId, String, IpcSender<Option<Vec<NodeInfo>>>),
246    /// Retrieve the CSS style properties defined in the attribute tag for the given node.
247    GetAttributeStyle(PipelineId, String, IpcSender<Option<Vec<NodeStyle>>>),
248    /// Retrieve the CSS style properties defined in an stylesheet for the given selector.
249    GetStylesheetStyle(
250        PipelineId,
251        String,
252        String,
253        usize,
254        IpcSender<Option<Vec<NodeStyle>>>,
255    ),
256    /// Retrieves the CSS selectors for the given node. A selector is comprised of the text
257    /// of the selector and the id of the stylesheet that contains it.
258    GetSelectors(PipelineId, String, IpcSender<Option<Vec<(String, usize)>>>),
259    /// Retrieve the computed CSS style properties for the given node.
260    GetComputedStyle(PipelineId, String, IpcSender<Option<Vec<NodeStyle>>>),
261    /// Retrieve the computed layout properties of the given node in the given pipeline.
262    GetLayout(PipelineId, String, IpcSender<Option<ComputedNodeLayout>>),
263    /// Get a unique XPath selector for the node.
264    GetXPath(PipelineId, String, IpcSender<String>),
265    /// Update a given node's attributes with a list of modifications.
266    ModifyAttribute(PipelineId, String, Vec<AttrModification>),
267    /// Update a given node's style rules with a list of modifications.
268    ModifyRule(PipelineId, String, Vec<RuleModification>),
269    /// Request live console messages for a given pipeline (true if desired, false otherwise).
270    WantsLiveNotifications(PipelineId, bool),
271    /// Request live notifications for a given set of timeline events for a given pipeline.
272    SetTimelineMarkers(
273        PipelineId,
274        Vec<TimelineMarkerType>,
275        IpcSender<Option<TimelineMarker>>,
276    ),
277    /// Withdraw request for live timeline notifications for a given pipeline.
278    DropTimelineMarkers(PipelineId, Vec<TimelineMarkerType>),
279    /// Request a callback directed at the given actor name from the next animation frame
280    /// executed in the given pipeline.
281    RequestAnimationFrame(PipelineId, String),
282    /// Direct the given pipeline to reload the current page.
283    Reload(PipelineId),
284    /// Gets the list of all allowed CSS rules and possible values.
285    GetCssDatabase(IpcSender<HashMap<String, CssDatabaseProperty>>),
286    /// Simulates a light or dark color scheme for the given pipeline
287    SimulateColorScheme(PipelineId, Theme),
288    /// Highlight the given DOM node
289    HighlightDomNode(PipelineId, Option<String>),
290
291    GetPossibleBreakpoints(u32, IpcSender<Vec<RecommendedBreakpointLocation>>),
292}
293
294#[derive(Clone, Debug, Deserialize, Serialize)]
295#[serde(rename_all = "camelCase")]
296pub struct AttrModification {
297    pub attribute_name: String,
298    pub new_value: Option<String>,
299}
300
301#[derive(Clone, Debug, Deserialize, Serialize)]
302#[serde(rename_all = "camelCase")]
303pub struct RuleModification {
304    #[serde(rename = "type")]
305    pub type_: String,
306    pub index: u32,
307    pub name: String,
308    pub value: String,
309    pub priority: String,
310}
311
312#[derive(Clone, Debug, Deserialize, Serialize)]
313pub enum LogLevel {
314    Log,
315    Debug,
316    Info,
317    Warn,
318    Error,
319    Clear,
320    Trace,
321}
322
323impl From<LogLevel> for log::Level {
324    fn from(value: LogLevel) -> Self {
325        match value {
326            LogLevel::Log => log::Level::Info,
327            LogLevel::Clear => log::Level::Info,
328
329            LogLevel::Debug => log::Level::Debug,
330            LogLevel::Info => log::Level::Info,
331            LogLevel::Warn => log::Level::Warn,
332            LogLevel::Error => log::Level::Error,
333            LogLevel::Trace => log::Level::Trace,
334        }
335    }
336}
337
338/// A console message as it is sent from script to the constellation
339#[derive(Clone, Debug, Deserialize, Serialize)]
340#[serde(rename_all = "camelCase")]
341pub struct ConsoleMessage {
342    pub log_level: LogLevel,
343    pub filename: String,
344    pub line_number: usize,
345    pub column_number: usize,
346    pub arguments: Vec<ConsoleMessageArgument>,
347    pub stacktrace: Option<Vec<StackFrame>>,
348}
349
350#[derive(Clone, Debug, Deserialize, Serialize)]
351pub enum ConsoleMessageArgument {
352    String(String),
353    Integer(i32),
354    Number(f64),
355}
356
357#[derive(Clone, Debug, Deserialize, Serialize)]
358pub struct StackFrame {
359    pub filename: String,
360
361    #[serde(rename = "functionName")]
362    pub function_name: String,
363
364    #[serde(rename = "columnNumber")]
365    pub column_number: u32,
366
367    #[serde(rename = "lineNumber")]
368    pub line_number: u32,
369}
370
371bitflags! {
372    #[derive(Deserialize, Serialize)]
373    pub struct CachedConsoleMessageTypes: u8 {
374        const PAGE_ERROR  = 1 << 0;
375        const CONSOLE_API = 1 << 1;
376    }
377}
378
379#[derive(Clone, Debug, Deserialize, Serialize)]
380#[serde(rename_all = "camelCase")]
381pub struct PageError {
382    #[serde(rename = "_type")]
383    pub type_: String,
384    pub error_message: String,
385    pub source_name: String,
386    pub line_text: String,
387    pub line_number: u32,
388    pub column_number: u32,
389    pub category: String,
390    pub time_stamp: u64,
391    pub error: bool,
392    pub warning: bool,
393    pub exception: bool,
394    pub strict: bool,
395    pub private: bool,
396}
397
398/// Represents a console message as it is sent to the devtools
399#[derive(Clone, Debug, Deserialize, Serialize)]
400pub struct ConsoleLog {
401    pub level: String,
402    pub filename: String,
403    pub line_number: u32,
404    pub column_number: u32,
405    pub time_stamp: u64,
406    pub arguments: Vec<ConsoleArgument>,
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub stacktrace: Option<Vec<StackFrame>>,
409}
410
411impl From<ConsoleMessage> for ConsoleLog {
412    fn from(value: ConsoleMessage) -> Self {
413        let level = match value.log_level {
414            LogLevel::Debug => "debug",
415            LogLevel::Info => "info",
416            LogLevel::Warn => "warn",
417            LogLevel::Error => "error",
418            LogLevel::Clear => "clear",
419            LogLevel::Trace => "trace",
420            LogLevel::Log => "log",
421        }
422        .to_owned();
423
424        let time_stamp = SystemTime::now()
425            .duration_since(UNIX_EPOCH)
426            .unwrap_or_default()
427            .as_millis() as u64;
428
429        Self {
430            level,
431            filename: value.filename,
432            line_number: value.line_number as u32,
433            column_number: value.column_number as u32,
434            time_stamp,
435            arguments: value.arguments.into_iter().map(|arg| arg.into()).collect(),
436            stacktrace: value.stacktrace,
437        }
438    }
439}
440
441#[derive(Debug, Deserialize, Serialize)]
442pub enum CachedConsoleMessage {
443    PageError(PageError),
444    ConsoleLog(ConsoleLog),
445}
446
447#[derive(Debug, PartialEq)]
448pub struct HttpRequest {
449    pub url: ServoUrl,
450    pub method: Method,
451    pub headers: HeaderMap,
452    pub body: Option<DebugVec>,
453    pub pipeline_id: PipelineId,
454    pub started_date_time: SystemTime,
455    pub time_stamp: i64,
456    pub connect_time: Duration,
457    pub send_time: Duration,
458    pub destination: Destination,
459    pub is_xhr: bool,
460    pub browsing_context_id: BrowsingContextId,
461}
462
463#[derive(Debug, PartialEq)]
464pub struct HttpResponse {
465    pub headers: Option<HeaderMap>,
466    pub status: HttpStatus,
467    pub body: Option<DebugVec>,
468    pub from_cache: bool,
469    pub pipeline_id: PipelineId,
470    pub browsing_context_id: BrowsingContextId,
471}
472
473#[derive(Debug)]
474pub enum NetworkEvent {
475    HttpRequest(HttpRequest),
476    HttpRequestUpdate(HttpRequest),
477    HttpResponse(HttpResponse),
478}
479
480impl NetworkEvent {
481    pub fn forward_to_devtools(&self) -> bool {
482        !matches!(self, NetworkEvent::HttpRequest(http_request) if http_request.url.scheme() == "data") ||
483            match self {
484                NetworkEvent::HttpRequest(http_request) => http_request.url.scheme() != "data",
485                NetworkEvent::HttpRequestUpdate(..) => true,
486                NetworkEvent::HttpResponse(..) => true,
487            }
488    }
489}
490
491impl TimelineMarker {
492    pub fn start(name: String) -> StartedTimelineMarker {
493        StartedTimelineMarker {
494            name,
495            start_time: CrossProcessInstant::now(),
496            start_stack: None,
497        }
498    }
499}
500
501impl StartedTimelineMarker {
502    pub fn end(self) -> TimelineMarker {
503        TimelineMarker {
504            name: self.name,
505            start_time: self.start_time,
506            start_stack: self.start_stack,
507            end_time: CrossProcessInstant::now(),
508            end_stack: None,
509        }
510    }
511}
512#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
513pub struct WorkerId(pub Uuid);
514impl Display for WorkerId {
515    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
516        write!(f, "{}", self.0)
517    }
518}
519impl FromStr for WorkerId {
520    type Err = uuid::Error;
521
522    fn from_str(s: &str) -> Result<Self, Self::Err> {
523        Ok(Self(s.parse()?))
524    }
525}
526
527#[derive(Debug, Deserialize, Serialize)]
528#[serde(rename_all = "camelCase")]
529pub struct CssDatabaseProperty {
530    pub is_inherited: bool,
531    pub values: Vec<String>,
532    pub supports: Vec<String>,
533    pub subproperties: Vec<String>,
534}
535
536#[derive(Clone, Debug, Deserialize, Serialize)]
537#[serde(untagged)]
538pub enum ConsoleArgument {
539    String(String),
540    Integer(i32),
541    Number(f64),
542}
543
544impl From<ConsoleMessageArgument> for ConsoleArgument {
545    fn from(value: ConsoleMessageArgument) -> Self {
546        match value {
547            ConsoleMessageArgument::String(string) => Self::String(string),
548            ConsoleMessageArgument::Integer(integer) => Self::Integer(integer),
549            ConsoleMessageArgument::Number(number) => Self::Number(number),
550        }
551    }
552}
553
554impl From<String> for ConsoleMessageArgument {
555    fn from(value: String) -> Self {
556        Self::String(value)
557    }
558}
559
560pub struct ConsoleMessageBuilder {
561    level: LogLevel,
562    filename: String,
563    line_number: u32,
564    column_number: u32,
565    arguments: Vec<ConsoleMessageArgument>,
566    stack_trace: Option<Vec<StackFrame>>,
567}
568
569impl ConsoleMessageBuilder {
570    pub fn new(level: LogLevel, filename: String, line_number: u32, column_number: u32) -> Self {
571        Self {
572            level,
573            filename,
574            line_number,
575            column_number,
576            arguments: vec![],
577            stack_trace: None,
578        }
579    }
580
581    pub fn attach_stack_trace(&mut self, stack_trace: Vec<StackFrame>) -> &mut Self {
582        self.stack_trace = Some(stack_trace);
583        self
584    }
585
586    pub fn add_argument(&mut self, argument: ConsoleMessageArgument) -> &mut Self {
587        self.arguments.push(argument);
588        self
589    }
590
591    pub fn finish(self) -> ConsoleMessage {
592        ConsoleMessage {
593            log_level: self.level,
594            filename: self.filename,
595            line_number: self.line_number as usize,
596            column_number: self.column_number as usize,
597            arguments: self.arguments,
598            stacktrace: self.stack_trace,
599        }
600    }
601}
602
603#[derive(Debug, Deserialize, Serialize)]
604pub enum ShadowRootMode {
605    Open,
606    Closed,
607}
608
609impl fmt::Display for ShadowRootMode {
610    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
611        match self {
612            Self::Open => write!(f, "open"),
613            Self::Closed => write!(f, "close"),
614        }
615    }
616}
617
618#[derive(Debug, Deserialize, Serialize)]
619pub struct SourceInfo {
620    pub url: ServoUrl,
621    pub introduction_type: String,
622    pub inline: bool,
623    pub worker_id: Option<WorkerId>,
624    pub content: Option<String>,
625    pub content_type: Option<String>,
626    pub spidermonkey_id: u32,
627}
628
629#[derive(Clone, Debug, Deserialize, Serialize)]
630#[serde(rename_all = "camelCase")]
631pub struct RecommendedBreakpointLocation {
632    pub offset: u32,
633    pub line_number: u32,
634    pub column_number: u32,
635    pub is_step_start: bool,
636}