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