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