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