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