Skip to main content

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    pub items: Option<Vec<DebuggerValue>>,
163}
164
165#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
166pub struct FunctionPreview {
167    pub name: Option<String>,
168    pub display_name: Option<String>,
169    pub parameter_names: Vec<String>,
170    pub is_async: Option<bool>,
171    pub is_generator: Option<bool>,
172}
173
174#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
175pub enum DebuggerValue {
176    VoidValue,
177    NullValue,
178    BooleanValue(bool),
179    NumberValue(f64),
180    StringValue(String),
181    ObjectValue {
182        uuid: String,
183        class: String,
184        preview: Option<ObjectPreview>,
185    },
186}
187
188/// <https://searchfox.org/mozilla-central/source/devtools/server/actors/object/property-iterator.js#51>
189#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
190pub struct PropertyDescriptor {
191    pub name: String,
192    pub value: DebuggerValue,
193    pub configurable: bool,
194    pub enumerable: bool,
195    pub writable: bool,
196    pub is_accessor: bool,
197}
198
199#[derive(Debug, Deserialize, Serialize)]
200pub struct EvaluateJSReply {
201    pub value: DebuggerValue,
202    pub has_exception: bool,
203}
204
205#[derive(Debug, Deserialize, Serialize)]
206pub struct AttrInfo {
207    pub namespace: String,
208    pub name: String,
209    pub value: String,
210}
211
212#[derive(Debug, Deserialize, Serialize)]
213#[serde(rename_all = "camelCase")]
214pub struct NodeInfo {
215    pub unique_id: String,
216    pub host: Option<String>,
217    #[serde(rename = "baseURI")]
218    pub base_uri: String,
219    pub parent: String,
220    pub node_type: u16,
221    pub node_name: String,
222    pub node_value: Option<String>,
223    pub num_children: usize,
224    pub attrs: Vec<AttrInfo>,
225    pub is_top_level_document: bool,
226    pub shadow_root_mode: Option<ShadowRootMode>,
227    pub is_shadow_host: bool,
228    pub display: Option<String>,
229    /// Whether this node is currently displayed.
230    ///
231    /// For example, the node might have `display: none`.
232    pub is_displayed: bool,
233
234    /// The `DOCTYPE` name if this is a `DocumentType` node, `None` otherwise
235    pub doctype_name: Option<String>,
236
237    /// The `DOCTYPE` public identifier if this is a `DocumentType` node , `None` otherwise
238    pub doctype_public_identifier: Option<String>,
239
240    /// The `DOCTYPE` system identifier if this is a `DocumentType` node, `None` otherwise
241    pub doctype_system_identifier: Option<String>,
242
243    pub has_event_listeners: bool,
244}
245
246pub struct StartedTimelineMarker {
247    name: String,
248    start_time: CrossProcessInstant,
249    start_stack: Option<Vec<()>>,
250}
251
252#[derive(Debug, Deserialize, Serialize)]
253pub struct TimelineMarker {
254    pub name: String,
255    pub start_time: CrossProcessInstant,
256    pub start_stack: Option<Vec<()>>,
257    pub end_time: CrossProcessInstant,
258    pub end_stack: Option<Vec<()>>,
259}
260
261#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
262pub enum TimelineMarkerType {
263    Reflow,
264    DOMEvent,
265}
266
267#[derive(Debug, Deserialize, Serialize)]
268#[serde(rename_all = "camelCase")]
269pub struct NodeStyle {
270    pub name: String,
271    pub value: String,
272    pub priority: String,
273}
274
275#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf, PartialEq, Eq, Hash)]
276#[serde(tag = "type", rename_all = "camelCase")]
277pub enum AncestorData {
278    Layer {
279        actor_id: Option<String>,
280        value: Option<String>,
281    },
282}
283
284#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf, PartialEq, Eq, Hash)]
285#[serde(rename_all = "camelCase")]
286pub struct MatchedRule {
287    pub selector: String,
288    pub stylesheet_index: usize,
289    pub block_id: usize,
290    pub ancestor_data: Vec<AncestorData>,
291}
292
293/// The properties of a DOM node as computed by layout.
294#[derive(Debug, Deserialize, Serialize)]
295#[serde(rename_all = "kebab-case")]
296pub struct ComputedNodeLayout {
297    pub display: String,
298    pub position: String,
299    pub z_index: String,
300    pub box_sizing: String,
301
302    pub margin_top: String,
303    pub margin_right: String,
304    pub margin_bottom: String,
305    pub margin_left: String,
306
307    pub border_top_width: String,
308    pub border_right_width: String,
309    pub border_bottom_width: String,
310    pub border_left_width: String,
311
312    pub padding_top: String,
313    pub padding_right: String,
314    pub padding_bottom: String,
315    pub padding_left: String,
316
317    pub width: f32,
318    pub height: f32,
319}
320
321#[derive(Debug, Default, Deserialize, Serialize)]
322pub struct AutoMargins {
323    pub top: bool,
324    pub right: bool,
325    pub bottom: bool,
326    pub left: bool,
327}
328
329/// Messages to process in a particular script thread, as instructed by a devtools client.
330/// TODO: better error handling, e.g. if pipeline id lookup fails?
331#[derive(Debug, Deserialize, Serialize)]
332pub enum DevtoolScriptControlMsg {
333    /// Retrieve the details of the root node (ie. the document) for the given pipeline.
334    GetRootNode(PipelineId, GenericSender<Option<NodeInfo>>),
335    /// Retrieve the details of the document element for the given pipeline.
336    GetDocumentElement(PipelineId, GenericSender<Option<NodeInfo>>),
337    /// Retrieve the details of the child nodes of the given node in the given pipeline.
338    GetChildren(PipelineId, String, GenericSender<Option<Vec<NodeInfo>>>),
339    /// Retrieve the CSS style properties defined in the attribute tag for the given node.
340    GetAttributeStyle(PipelineId, String, GenericSender<Option<Vec<NodeStyle>>>),
341    /// Retrieve the CSS style properties defined in an stylesheet for the given selector.
342    GetStylesheetStyle(
343        PipelineId,
344        String,
345        MatchedRule,
346        GenericSender<Option<Vec<NodeStyle>>>,
347    ),
348    /// Retrieve the list of stylesheets for the given pipeline and node.
349    GetStyleSheets(PipelineId, GenericSender<Vec<StyleSheetInfo>>),
350    /// Retrieve the actual CSS text for the stylesheet with the given node ID and index.
351    GetStyleSheetText(PipelineId, i32, GenericSender<Option<String>>),
352    /// Retrieves the CSS selectors for the given node. A selector is comprised of the text
353    /// of the selector and the id of the stylesheet that contains it.
354    GetSelectors(PipelineId, String, GenericSender<Option<Vec<MatchedRule>>>),
355    /// Retrieve the computed CSS style properties for the given node.
356    GetComputedStyle(PipelineId, String, GenericSender<Option<Vec<NodeStyle>>>),
357    /// Get information about event listeners on a node.
358    GetEventListenerInfo(PipelineId, String, GenericSender<Vec<EventListenerInfo>>),
359    /// Retrieve the computed layout properties of the given node in the given pipeline.
360    GetLayout(
361        PipelineId,
362        String,
363        GenericSender<Option<(ComputedNodeLayout, AutoMargins)>>,
364    ),
365    /// Get a unique XPath selector for the node.
366    GetXPath(PipelineId, String, GenericSender<String>),
367    /// Update a given node's attributes with a list of modifications.
368    ModifyAttribute(PipelineId, String, Vec<AttrModification>),
369    /// Update a given node's style rules with a list of modifications.
370    ModifyRule(PipelineId, String, Vec<RuleModification>),
371    /// Request live console messages for a given pipeline (true if desired, false otherwise).
372    WantsLiveNotifications(PipelineId, bool),
373    /// Request live notifications for a given set of timeline events for a given pipeline.
374    SetTimelineMarkers(
375        PipelineId,
376        Vec<TimelineMarkerType>,
377        GenericSender<Option<TimelineMarker>>,
378    ),
379    /// Withdraw request for live timeline notifications for a given pipeline.
380    DropTimelineMarkers(PipelineId, Vec<TimelineMarkerType>),
381    /// Request a callback directed at the given actor name from the next animation frame
382    /// executed in the given pipeline.
383    RequestAnimationFrame(PipelineId, String),
384    /// Direct the WebView containing the given pipeline to load a new URL,
385    /// as if it was typed by the user.
386    NavigateTo(PipelineId, ServoUrl),
387    /// Direct the WebView containing the given pipeline to traverse history backward
388    /// up to one step.
389    GoBack(PipelineId),
390    /// Direct the WebView containing the given pipeline to traverse history forward
391    /// up to one step.
392    GoForward(PipelineId),
393    /// Direct the given pipeline to reload the current page.
394    Reload(PipelineId),
395    /// Gets the list of all allowed CSS rules and possible values.
396    GetCssDatabase(GenericSender<HashMap<String, CssDatabaseProperty>>),
397    /// Simulates a light or dark color scheme for the given pipeline
398    SimulateColorScheme(PipelineId, Theme),
399    /// Highlight the given DOM node
400    HighlightDomNode(PipelineId, Option<String>),
401
402    Eval(
403        String,
404        PipelineId,
405        Option<String>,
406        GenericSender<EvaluateJSReply>,
407    ),
408    GetPossibleBreakpoints(u32, GenericSender<Vec<RecommendedBreakpointLocation>>),
409    SetBreakpoint(u32, u32, u32),
410    ClearBreakpoint(u32, u32, u32),
411    Interrupt,
412    Resume(Option<String>, Option<String>),
413    ListFrames(PipelineId, u32, u32, GenericSender<Vec<String>>),
414    GetEnvironment(String, GenericSender<String>),
415}
416
417#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
418#[serde(rename_all = "camelCase")]
419pub struct AttrModification {
420    pub attribute_name: String,
421    pub new_value: Option<String>,
422}
423
424#[derive(Clone, Debug, Deserialize, Serialize)]
425#[serde(rename_all = "camelCase")]
426pub struct RuleModification {
427    #[serde(rename = "type")]
428    pub type_: String,
429    pub index: u32,
430    pub name: String,
431    pub value: String,
432    pub priority: String,
433}
434
435#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
436#[serde(rename_all = "camelCase")]
437pub struct StackFrame {
438    pub filename: String,
439    pub function_name: String,
440    pub column_number: u32,
441    pub line_number: u32,
442    // Not implemented in Servo
443    // source_id
444}
445
446pub fn get_time_stamp() -> u64 {
447    SystemTime::now()
448        .duration_since(UNIX_EPOCH)
449        .unwrap_or_default()
450        .as_millis() as u64
451}
452
453#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
454#[serde(rename_all = "camelCase")]
455pub struct ConsoleMessageFields {
456    pub level: ConsoleLogLevel,
457    pub filename: String,
458    pub line_number: u32,
459    pub column_number: u32,
460    pub time_stamp: u64,
461}
462
463#[derive(Clone, Debug, Deserialize, Serialize)]
464pub struct ConsoleMessage {
465    pub fields: ConsoleMessageFields,
466    pub arguments: Vec<DebuggerValue>,
467    pub stacktrace: Option<Vec<StackFrame>>,
468}
469
470#[derive(Clone, Debug, Deserialize, Serialize, MallocSizeOf)]
471#[serde(rename_all = "camelCase")]
472pub struct PageError {
473    pub error_message: String,
474    pub source_name: String,
475    pub line_number: u32,
476    pub column_number: u32,
477    pub time_stamp: u64,
478}
479
480#[derive(Debug, PartialEq, MallocSizeOf)]
481pub struct HttpRequest {
482    pub url: ServoUrl,
483    pub method: Method,
484    pub headers: HeaderMap,
485    pub body: Option<DebugVec>,
486    pub pipeline_id: PipelineId,
487    pub started_date_time: SystemTime,
488    pub time_stamp: i64,
489    pub connect_time: Duration,
490    pub send_time: Duration,
491    pub destination: Destination,
492    pub is_xhr: bool,
493    pub browsing_context_id: BrowsingContextId,
494}
495
496#[derive(Debug, PartialEq, MallocSizeOf)]
497pub struct HttpResponse {
498    #[ignore_malloc_size_of = "Http type"]
499    pub headers: Option<HeaderMap>,
500    pub status: HttpStatus,
501    pub body: Option<DebugVec>,
502    pub from_cache: bool,
503    pub pipeline_id: PipelineId,
504    pub browsing_context_id: BrowsingContextId,
505}
506
507#[derive(Debug, PartialEq)]
508pub struct SecurityInfoUpdate {
509    pub browsing_context_id: BrowsingContextId,
510    pub security_info: Option<TlsSecurityInfo>,
511}
512
513#[derive(Debug)]
514pub enum NetworkEvent {
515    HttpRequest(HttpRequest),
516    HttpRequestUpdate(HttpRequest),
517    HttpResponse(HttpResponse),
518    SecurityInfo(SecurityInfoUpdate),
519}
520
521impl NetworkEvent {
522    pub fn forward_to_devtools(&self) -> bool {
523        match self {
524            NetworkEvent::HttpRequest(http_request) => http_request.url.scheme() != "data",
525            NetworkEvent::HttpRequestUpdate(_) => true,
526            NetworkEvent::HttpResponse(_) => true,
527            NetworkEvent::SecurityInfo(_) => true,
528        }
529    }
530}
531
532impl TimelineMarker {
533    pub fn start(name: String) -> StartedTimelineMarker {
534        StartedTimelineMarker {
535            name,
536            start_time: CrossProcessInstant::now(),
537            start_stack: None,
538        }
539    }
540}
541
542impl StartedTimelineMarker {
543    pub fn end(self) -> TimelineMarker {
544        TimelineMarker {
545            name: self.name,
546            start_time: self.start_time,
547            start_stack: self.start_stack,
548            end_time: CrossProcessInstant::now(),
549            end_stack: None,
550        }
551    }
552}
553#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
554pub struct WorkerId(pub Uuid);
555impl Display for WorkerId {
556    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
557        write!(f, "{}", self.0)
558    }
559}
560impl FromStr for WorkerId {
561    type Err = uuid::Error;
562
563    fn from_str(s: &str) -> Result<Self, Self::Err> {
564        Ok(Self(s.parse()?))
565    }
566}
567
568#[derive(Debug, Deserialize, Serialize, MallocSizeOf)]
569#[serde(rename_all = "camelCase")]
570pub struct CssDatabaseProperty {
571    pub is_inherited: bool,
572    pub values: Vec<String>,
573    pub supports: Vec<String>,
574    pub subproperties: Vec<String>,
575}
576
577#[derive(Debug, Deserialize, Serialize)]
578pub enum ShadowRootMode {
579    Open,
580    Closed,
581}
582
583impl fmt::Display for ShadowRootMode {
584    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
585        match self {
586            Self::Open => write!(f, "open"),
587            Self::Closed => write!(f, "close"),
588        }
589    }
590}
591
592#[derive(Debug, Deserialize, Serialize)]
593pub struct SourceInfo {
594    pub url: ServoUrl,
595    pub introduction_type: String,
596    pub inline: bool,
597    pub worker_id: Option<WorkerId>,
598    pub content: Option<String>,
599    pub content_type: Option<String>,
600    pub spidermonkey_id: u32,
601}
602
603#[derive(Clone, Debug, Deserialize, Serialize)]
604#[serde(rename_all = "camelCase")]
605pub struct RecommendedBreakpointLocation {
606    pub script_id: u32,
607    pub offset: u32,
608    pub line_number: u32,
609    pub column_number: u32,
610    pub is_step_start: bool,
611}
612
613#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
614pub struct FrameInfo {
615    pub display_name: String,
616    pub on_stack: bool,
617    pub oldest: bool,
618    pub terminated: bool,
619    pub type_: String,
620    pub url: String,
621}
622
623#[derive(Clone, Debug, Default, Deserialize, MallocSizeOf, Serialize)]
624pub struct EnvironmentInfo {
625    pub type_: Option<String>,
626    pub scope_kind: Option<String>,
627    pub function_display_name: Option<String>,
628    pub binding_variables: Vec<PropertyDescriptor>,
629}
630
631#[derive(Clone, Debug, Deserialize, Serialize)]
632pub struct StyleSheetInfo {
633    pub href: Option<String>,
634    pub disabled: bool,
635    pub title: String,
636    pub style_sheet_index: i32,
637    pub system: bool,
638    pub rule_count: u32,
639}
640
641#[derive(Clone, Debug, Deserialize, Serialize)]
642pub struct EventListenerInfo {
643    pub event_type: String,
644    pub capturing: bool,
645}
646
647#[derive(Debug, Deserialize, Serialize)]
648#[serde(rename_all = "camelCase")]
649pub struct PauseReason {
650    #[serde(rename = "type")]
651    pub type_: String,
652    pub on_next: Option<bool>,
653}
654
655#[derive(Debug, Deserialize, Serialize)]
656pub struct FrameOffset {
657    pub actor: String,
658    pub column: u32,
659    pub line: u32,
660}