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