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