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    /// Get a unique XPath selector for the node.
263    GetXPath(PipelineId, String, IpcSender<String>),
264    /// Update a given node's attributes with a list of modifications.
265    ModifyAttribute(PipelineId, String, Vec<AttrModification>),
266    /// Update a given node's style rules with a list of modifications.
267    ModifyRule(PipelineId, String, Vec<RuleModification>),
268    /// Request live console messages for a given pipeline (true if desired, false otherwise).
269    WantsLiveNotifications(PipelineId, bool),
270    /// Request live notifications for a given set of timeline events for a given pipeline.
271    SetTimelineMarkers(
272        PipelineId,
273        Vec<TimelineMarkerType>,
274        IpcSender<Option<TimelineMarker>>,
275    ),
276    /// Withdraw request for live timeline notifications for a given pipeline.
277    DropTimelineMarkers(PipelineId, Vec<TimelineMarkerType>),
278    /// Request a callback directed at the given actor name from the next animation frame
279    /// executed in the given pipeline.
280    RequestAnimationFrame(PipelineId, String),
281    /// Direct the given pipeline to reload the current page.
282    Reload(PipelineId),
283    /// Gets the list of all allowed CSS rules and possible values.
284    GetCssDatabase(IpcSender<HashMap<String, CssDatabaseProperty>>),
285    /// Simulates a light or dark color scheme for the given pipeline
286    SimulateColorScheme(PipelineId, Theme),
287    /// Highlight the given DOM node
288    HighlightDomNode(PipelineId, Option<String>),
289
290    GetPossibleBreakpoints(u32, IpcSender<Vec<RecommendedBreakpointLocation>>),
291}
292
293#[derive(Clone, Debug, Deserialize, Serialize)]
294#[serde(rename_all = "camelCase")]
295pub struct AttrModification {
296    pub attribute_name: String,
297    pub new_value: Option<String>,
298}
299
300#[derive(Clone, Debug, Deserialize, Serialize)]
301#[serde(rename_all = "camelCase")]
302pub struct RuleModification {
303    #[serde(rename = "type")]
304    pub type_: String,
305    pub index: u32,
306    pub name: String,
307    pub value: String,
308    pub priority: String,
309}
310
311#[derive(Clone, Debug, Deserialize, Serialize)]
312pub enum LogLevel {
313    Log,
314    Debug,
315    Info,
316    Warn,
317    Error,
318    Clear,
319    Trace,
320}
321
322impl From<LogLevel> for log::Level {
323    fn from(value: LogLevel) -> Self {
324        match value {
325            LogLevel::Log => log::Level::Info,
326            LogLevel::Clear => log::Level::Info,
327
328            LogLevel::Debug => log::Level::Debug,
329            LogLevel::Info => log::Level::Info,
330            LogLevel::Warn => log::Level::Warn,
331            LogLevel::Error => log::Level::Error,
332            LogLevel::Trace => log::Level::Trace,
333        }
334    }
335}
336
337/// A console message as it is sent from script to the constellation
338#[derive(Clone, Debug, Deserialize, Serialize)]
339#[serde(rename_all = "camelCase")]
340pub struct ConsoleMessage {
341    pub log_level: LogLevel,
342    pub filename: String,
343    pub line_number: usize,
344    pub column_number: usize,
345    pub arguments: Vec<ConsoleMessageArgument>,
346    pub stacktrace: Option<Vec<StackFrame>>,
347}
348
349#[derive(Clone, Debug, Deserialize, Serialize)]
350pub enum ConsoleMessageArgument {
351    String(String),
352    Integer(i32),
353    Number(f64),
354}
355
356#[derive(Clone, Debug, Deserialize, Serialize)]
357pub struct StackFrame {
358    pub filename: String,
359
360    #[serde(rename = "functionName")]
361    pub function_name: String,
362
363    #[serde(rename = "columnNumber")]
364    pub column_number: u32,
365
366    #[serde(rename = "lineNumber")]
367    pub line_number: u32,
368}
369
370bitflags! {
371    #[derive(Deserialize, Serialize)]
372    pub struct CachedConsoleMessageTypes: u8 {
373        const PAGE_ERROR  = 1 << 0;
374        const CONSOLE_API = 1 << 1;
375    }
376}
377
378#[derive(Clone, Debug, Deserialize, Serialize)]
379#[serde(rename_all = "camelCase")]
380pub struct PageError {
381    #[serde(rename = "_type")]
382    pub type_: String,
383    pub error_message: String,
384    pub source_name: String,
385    pub line_text: String,
386    pub line_number: u32,
387    pub column_number: u32,
388    pub category: String,
389    pub time_stamp: u64,
390    pub error: bool,
391    pub warning: bool,
392    pub exception: bool,
393    pub strict: bool,
394    pub private: bool,
395}
396
397/// Represents a console message as it is sent to the devtools
398#[derive(Clone, Debug, Deserialize, Serialize)]
399pub struct ConsoleLog {
400    pub level: String,
401    pub filename: String,
402    pub line_number: u32,
403    pub column_number: u32,
404    pub time_stamp: u64,
405    pub arguments: Vec<ConsoleArgument>,
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub stacktrace: Option<Vec<StackFrame>>,
408}
409
410impl From<ConsoleMessage> for ConsoleLog {
411    fn from(value: ConsoleMessage) -> Self {
412        let level = match value.log_level {
413            LogLevel::Debug => "debug",
414            LogLevel::Info => "info",
415            LogLevel::Warn => "warn",
416            LogLevel::Error => "error",
417            LogLevel::Clear => "clear",
418            LogLevel::Trace => "trace",
419            LogLevel::Log => "log",
420        }
421        .to_owned();
422
423        let time_stamp = SystemTime::now()
424            .duration_since(UNIX_EPOCH)
425            .unwrap_or_default()
426            .as_millis() as u64;
427
428        Self {
429            level,
430            filename: value.filename,
431            line_number: value.line_number as u32,
432            column_number: value.column_number as u32,
433            time_stamp,
434            arguments: value.arguments.into_iter().map(|arg| arg.into()).collect(),
435            stacktrace: value.stacktrace,
436        }
437    }
438}
439
440#[derive(Debug, Deserialize, Serialize)]
441pub enum CachedConsoleMessage {
442    PageError(PageError),
443    ConsoleLog(ConsoleLog),
444}
445
446#[derive(Debug, PartialEq)]
447pub struct HttpRequest {
448    pub url: ServoUrl,
449    pub method: Method,
450    pub headers: HeaderMap,
451    pub body: Option<Vec<u8>>,
452    pub pipeline_id: PipelineId,
453    pub started_date_time: SystemTime,
454    pub time_stamp: i64,
455    pub connect_time: Duration,
456    pub send_time: Duration,
457    pub destination: Destination,
458    pub is_xhr: bool,
459    pub browsing_context_id: BrowsingContextId,
460}
461
462#[derive(Debug, PartialEq)]
463pub struct HttpResponse {
464    pub headers: Option<HeaderMap>,
465    pub status: HttpStatus,
466    pub body: Option<Vec<u8>>,
467    pub from_cache: bool,
468    pub pipeline_id: PipelineId,
469    pub browsing_context_id: BrowsingContextId,
470}
471
472#[derive(Debug)]
473pub enum NetworkEvent {
474    HttpRequest(HttpRequest),
475    HttpRequestUpdate(HttpRequest),
476    HttpResponse(HttpResponse),
477}
478
479impl NetworkEvent {
480    pub fn forward_to_devtools(&self) -> bool {
481        !matches!(self, NetworkEvent::HttpRequest(http_request) if http_request.url.scheme() == "data") ||
482            match self {
483                NetworkEvent::HttpRequest(http_request) => http_request.url.scheme() != "data",
484                NetworkEvent::HttpRequestUpdate(..) => true,
485                NetworkEvent::HttpResponse(..) => true,
486            }
487    }
488}
489
490impl TimelineMarker {
491    pub fn start(name: String) -> StartedTimelineMarker {
492        StartedTimelineMarker {
493            name,
494            start_time: CrossProcessInstant::now(),
495            start_stack: None,
496        }
497    }
498}
499
500impl StartedTimelineMarker {
501    pub fn end(self) -> TimelineMarker {
502        TimelineMarker {
503            name: self.name,
504            start_time: self.start_time,
505            start_stack: self.start_stack,
506            end_time: CrossProcessInstant::now(),
507            end_stack: None,
508        }
509    }
510}
511#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
512pub struct WorkerId(pub Uuid);
513impl Display for WorkerId {
514    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
515        write!(f, "{}", self.0)
516    }
517}
518impl FromStr for WorkerId {
519    type Err = uuid::Error;
520
521    fn from_str(s: &str) -> Result<Self, Self::Err> {
522        Ok(Self(s.parse()?))
523    }
524}
525
526#[derive(Debug, Deserialize, Serialize)]
527#[serde(rename_all = "camelCase")]
528pub struct CssDatabaseProperty {
529    pub is_inherited: bool,
530    pub values: Vec<String>,
531    pub supports: Vec<String>,
532    pub subproperties: Vec<String>,
533}
534
535#[derive(Clone, Debug, Deserialize, Serialize)]
536#[serde(untagged)]
537pub enum ConsoleArgument {
538    String(String),
539    Integer(i32),
540    Number(f64),
541}
542
543impl From<ConsoleMessageArgument> for ConsoleArgument {
544    fn from(value: ConsoleMessageArgument) -> Self {
545        match value {
546            ConsoleMessageArgument::String(string) => Self::String(string),
547            ConsoleMessageArgument::Integer(integer) => Self::Integer(integer),
548            ConsoleMessageArgument::Number(number) => Self::Number(number),
549        }
550    }
551}
552
553impl From<String> for ConsoleMessageArgument {
554    fn from(value: String) -> Self {
555        Self::String(value)
556    }
557}
558
559pub struct ConsoleMessageBuilder {
560    level: LogLevel,
561    filename: String,
562    line_number: u32,
563    column_number: u32,
564    arguments: Vec<ConsoleMessageArgument>,
565    stack_trace: Option<Vec<StackFrame>>,
566}
567
568impl ConsoleMessageBuilder {
569    pub fn new(level: LogLevel, filename: String, line_number: u32, column_number: u32) -> Self {
570        Self {
571            level,
572            filename,
573            line_number,
574            column_number,
575            arguments: vec![],
576            stack_trace: None,
577        }
578    }
579
580    pub fn attach_stack_trace(&mut self, stack_trace: Vec<StackFrame>) -> &mut Self {
581        self.stack_trace = Some(stack_trace);
582        self
583    }
584
585    pub fn add_argument(&mut self, argument: ConsoleMessageArgument) -> &mut Self {
586        self.arguments.push(argument);
587        self
588    }
589
590    pub fn finish(self) -> ConsoleMessage {
591        ConsoleMessage {
592            log_level: self.level,
593            filename: self.filename,
594            line_number: self.line_number as usize,
595            column_number: self.column_number as usize,
596            arguments: self.arguments,
597            stacktrace: self.stack_trace,
598        }
599    }
600}
601
602#[derive(Debug, Deserialize, Serialize)]
603pub enum ShadowRootMode {
604    Open,
605    Closed,
606}
607
608impl fmt::Display for ShadowRootMode {
609    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
610        match self {
611            Self::Open => write!(f, "open"),
612            Self::Closed => write!(f, "close"),
613        }
614    }
615}
616
617#[derive(Debug, Deserialize, Serialize)]
618pub struct SourceInfo {
619    pub url: ServoUrl,
620    pub introduction_type: String,
621    pub inline: bool,
622    pub worker_id: Option<WorkerId>,
623    pub content: Option<String>,
624    pub content_type: Option<String>,
625    pub spidermonkey_id: u32,
626}
627
628#[derive(Clone, Debug, Deserialize, Serialize)]
629#[serde(rename_all = "camelCase")]
630pub struct RecommendedBreakpointLocation {
631    pub offset: u32,
632    pub line_number: u32,
633    pub column_number: u32,
634    pub is_step_start: bool,
635}