Skip to main content

devtools/actors/
frame.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
5use std::sync::Arc;
6
7use atomic_refcell::AtomicRefCell;
8use devtools_traits::{DevtoolScriptControlMsg, FrameInfo, GetEnvironmentRequest};
9use malloc_size_of_derive::MallocSizeOf;
10use serde::Serialize;
11use serde_json::{Map, Value};
12use servo_base::generic_channel::channel;
13
14use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry, new_actor_name};
15use crate::actors::environment::{EnvironmentActor, EnvironmentActorMsg};
16use crate::actors::source::SourceActor;
17use crate::protocol::{ClientRequest, JsonPacketStream};
18use crate::{StreamId, debugger_value_to_json};
19
20#[derive(Serialize)]
21struct FrameEnvironmentReply {
22    from: String,
23    #[serde(flatten)]
24    environment: EnvironmentActorMsg,
25}
26
27#[derive(Serialize)]
28#[serde(rename_all = "kebab-case")]
29pub enum FrameState {
30    OnStack,
31    Suspended,
32    Dead,
33}
34
35#[derive(Serialize)]
36pub(crate) struct FrameWhere {
37    actor: String,
38    line: u32,
39    column: u32,
40}
41
42#[derive(Serialize)]
43#[serde(rename_all = "camelCase")]
44pub(crate) struct FrameActorMsg {
45    actor: String,
46    #[serde(rename = "type")]
47    type_: String,
48    arguments: Vec<Value>,
49    async_cause: Option<String>,
50    display_name: Option<String>,
51    oldest: bool,
52    state: FrameState,
53    this: Value,
54    #[serde(rename = "where")]
55    where_: FrameWhere,
56}
57
58/// Represents an stack frame. Used by `ThreadActor` when replying to interrupt messages.
59/// <https://searchfox.org/firefox-main/source/devtools/server/actors/frame.js>
60#[derive(MallocSizeOf)]
61pub(crate) struct FrameActor {
62    name: String,
63    source_name: String,
64    frame_result: FrameInfo,
65    current_offset: AtomicRefCell<(u32, u32)>,
66}
67
68impl Actor for FrameActor {
69    fn name(&self) -> &str {
70        &self.name
71    }
72
73    // https://searchfox.org/firefox-main/source/devtools/shared/specs/frame.js
74    fn handle_message(
75        &self,
76        mut request: ClientRequest,
77        registry: &ActorRegistry,
78        msg_type: &str,
79        _msg: &Map<String, Value>,
80        _id: StreamId,
81    ) -> Result<(), ActorError> {
82        match msg_type {
83            "getEnvironment" => {
84                let Some((tx, rx)) = channel() else {
85                    return Err(ActorError::Internal);
86                };
87                let source_actor = registry.find::<SourceActor>(&self.source_name);
88                source_actor
89                    .script_sender
90                    .send(DevtoolScriptControlMsg::GetEnvironment(
91                        GetEnvironmentRequest::Frame(self.name().into()),
92                        tx,
93                    ))
94                    .map_err(|_| ActorError::Internal)?;
95                let environment_name = rx.recv().map_err(|_| ActorError::Internal)?;
96
97                let msg = FrameEnvironmentReply {
98                    from: self.name().into(),
99                    environment: registry.encode::<EnvironmentActor, _>(&environment_name),
100                };
101                // This reply has a `type` field but it doesn't need a followup,
102                // unlike most messages. We need to skip the validity check.
103                request.write_json_packet(&msg)?;
104                request.mark_handled();
105            },
106            _ => return Err(ActorError::UnrecognizedPacketType),
107        };
108        Ok(())
109    }
110}
111
112impl FrameActor {
113    pub fn register(
114        registry: &ActorRegistry,
115        source_name: String,
116        frame_result: FrameInfo,
117    ) -> Arc<Self> {
118        let name = new_actor_name::<Self>();
119        let actor = Self {
120            name,
121            source_name,
122            frame_result,
123            current_offset: Default::default(),
124        };
125        registry.register::<Self>(actor)
126    }
127
128    pub(crate) fn set_offset(&self, column: u32, line: u32) {
129        *self.current_offset.borrow_mut() = (column, line);
130    }
131}
132
133impl ActorEncode<FrameActorMsg> for FrameActor {
134    fn encode(&self, registry: &ActorRegistry) -> FrameActorMsg {
135        let state = if self.frame_result.terminated {
136            FrameState::Dead
137        } else if self.frame_result.on_stack {
138            FrameState::OnStack
139        } else {
140            FrameState::Suspended
141        };
142        let async_cause = if let FrameState::OnStack = state {
143            None
144        } else {
145            Some("await".into())
146        };
147        let (column, line) = *self.current_offset.borrow();
148        // <https://searchfox.org/firefox-main/source/devtools/docs/user/debugger-api/debugger.frame/index.rst>
149        FrameActorMsg {
150            actor: self.name().into(),
151            type_: self.frame_result.type_.clone(),
152            arguments: vec![],
153            async_cause,
154            display_name: self.frame_result.display_name.clone(),
155            this: debugger_value_to_json(registry, self.frame_result.this_value.clone()),
156            oldest: self.frame_result.oldest,
157            state,
158            where_: FrameWhere {
159                actor: self.source_name.clone(),
160                line,
161                column,
162            },
163        }
164    }
165}