Skip to main content

devtools/actors/
thread.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::collections::HashSet;
6use std::sync::Arc;
7
8use atomic_refcell::AtomicRefCell;
9use devtools_traits::{DevtoolScriptControlMsg, PauseReason};
10use malloc_size_of_derive::MallocSizeOf;
11use serde::{Deserialize, Serialize};
12use serde_json::{Map, Value};
13use servo_base::generic_channel::GenericSender;
14
15use super::source::{SourceManager, SourcesReply};
16use crate::actor::{Actor, ActorError, ActorRegistry, new_actor_name};
17use crate::actors::browsing_context::BrowsingContextActor;
18use crate::actors::frame::{FrameActor, FrameActorMsg};
19use crate::actors::pause::PauseActor;
20use crate::generic_channel::channel;
21use crate::protocol::{ClientRequest, JsonPacketStream};
22use crate::{EmptyReplyMsg, StreamId};
23
24#[derive(Serialize)]
25#[serde(rename_all = "camelCase")]
26struct ThreadAttached {
27    from: String,
28    #[serde(rename = "type")]
29    type_: String,
30    actor: String,
31    frame: u32,
32    error: u32,
33    recording_endpoint: u32,
34    execution_point: u32,
35    popped_frames: Vec<PoppedFrameMsg>,
36    why: PauseReason,
37}
38
39#[derive(Serialize)]
40enum PoppedFrameMsg {}
41
42#[derive(Serialize)]
43struct ThreadResumedReply {
44    from: String,
45    #[serde(rename = "type")]
46    type_: String,
47}
48
49#[derive(Serialize)]
50pub(crate) struct ThreadInterruptedReply {
51    pub from: String,
52    #[serde(rename = "type")]
53    pub type_: String,
54    pub actor: String,
55    pub frame: FrameActorMsg,
56    pub why: PauseReason,
57}
58
59#[derive(Serialize)]
60struct GetAvailableEventBreakpointsReply {
61    from: String,
62    value: Vec<()>,
63}
64
65#[derive(Serialize)]
66struct FramesReply {
67    from: String,
68    frames: Vec<FrameActorMsg>,
69}
70
71#[derive(Debug, Deserialize, Serialize)]
72#[serde(rename_all = "lowercase")]
73enum ResumeLimitType {
74    Break,
75    Finish,
76    Next,
77    Restart,
78    Step,
79}
80
81#[derive(Debug, Deserialize, Serialize)]
82struct ResumeLimit {
83    #[serde(rename = "type")]
84    type_: ResumeLimitType,
85}
86
87#[derive(Deserialize, Debug)]
88#[serde(rename_all = "camelCase")]
89struct ResumeRequest {
90    resume_limit: Option<ResumeLimit>,
91    #[serde(rename = "frameActorID")]
92    #[serde(skip_serializing_if = "Option::is_none")]
93    frame_actor_id: Option<String>,
94}
95
96#[derive(Deserialize, Debug)]
97struct FramesRequest {
98    start: u32,
99    count: u32,
100}
101
102impl ResumeRequest {
103    fn get_type(&self) -> Option<String> {
104        let resume_limit = self.resume_limit.as_ref()?;
105        serde_json::to_string(&resume_limit.type_)
106            .ok()
107            .map(|s| s.trim_matches('"').into())
108    }
109}
110
111#[derive(MallocSizeOf)]
112pub(crate) struct ThreadActor {
113    name: String,
114    pub source_manager: SourceManager,
115    script_sender: GenericSender<DevtoolScriptControlMsg>,
116    pub frames: AtomicRefCell<HashSet<String>>,
117    browsing_context_name: Option<String>,
118}
119
120impl ThreadActor {
121    pub fn register(
122        registry: &ActorRegistry,
123        script_sender: GenericSender<DevtoolScriptControlMsg>,
124        browsing_context_name: Option<String>,
125    ) -> Arc<Self> {
126        let name = new_actor_name::<Self>();
127        let actor = ThreadActor {
128            name,
129            source_manager: SourceManager::new(),
130            script_sender,
131            frames: Default::default(),
132            browsing_context_name,
133        };
134        registry.register::<Self>(actor)
135    }
136}
137
138impl Actor for ThreadActor {
139    fn name(&self) -> &str {
140        &self.name
141    }
142
143    fn handle_message(
144        &self,
145        mut request: ClientRequest,
146        registry: &ActorRegistry,
147        msg_type: &str,
148        msg: &Map<String, Value>,
149        _id: StreamId,
150    ) -> Result<(), ActorError> {
151        match msg_type {
152            "attach" => {
153                let pause_actor = PauseActor::register(registry);
154                let msg = ThreadAttached {
155                    from: self.name().into(),
156                    type_: "paused".to_owned(),
157                    actor: pause_actor.name().into(),
158                    frame: 0,
159                    error: 0,
160                    recording_endpoint: 0,
161                    execution_point: 0,
162                    popped_frames: vec![],
163                    why: PauseReason {
164                        type_: "attached".to_owned(),
165                        on_next: None,
166                    },
167                };
168                request.write_json_packet(&msg)?;
169                request.reply_final(&EmptyReplyMsg {
170                    from: self.name().into(),
171                })?
172            },
173
174            "resume" => {
175                let resume: ResumeRequest =
176                    serde_json::from_value(msg.clone().into()).map_err(|_| ActorError::Internal)?;
177
178                let _ = self.script_sender.send(DevtoolScriptControlMsg::Resume(
179                    resume.get_type(),
180                    resume.frame_actor_id,
181                ));
182
183                let msg = ThreadResumedReply {
184                    from: self.name().into(),
185                    type_: "resumed".to_owned(),
186                };
187                request.write_json_packet(&msg)?;
188                request.reply_final(&EmptyReplyMsg {
189                    from: self.name().into(),
190                })?
191            },
192
193            "interrupt" => {
194                self.script_sender
195                    .send(DevtoolScriptControlMsg::Interrupt)
196                    .map_err(|_| ActorError::Internal)?;
197
198                request.reply_final(&EmptyReplyMsg {
199                    from: self.name().into(),
200                })?
201            },
202
203            "reconfigure" => request.reply_final(&EmptyReplyMsg {
204                from: self.name().into(),
205            })?,
206
207            "getAvailableEventBreakpoints" => {
208                // TODO: Send list of available event breakpoints (animation, clipboard, load...)
209                let msg = GetAvailableEventBreakpointsReply {
210                    from: self.name().into(),
211                    value: vec![],
212                };
213                request.reply_final(&msg)?
214            },
215
216            // Client has attached to the thread and wants to load script sources.
217            // <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#loading-script-sources>
218            "sources" => {
219                let msg = SourcesReply {
220                    from: self.name().into(),
221                    sources: self.source_manager.source_forms(registry),
222                };
223                request.reply_final(&msg)?
224            },
225
226            "frames" => {
227                let Some(ref browsing_context_name) = self.browsing_context_name else {
228                    return Err(ActorError::Internal);
229                };
230                let browsing_context_actor =
231                    registry.find::<BrowsingContextActor>(browsing_context_name);
232
233                let frames: FramesRequest =
234                    serde_json::from_value(msg.clone().into()).map_err(|_| ActorError::Internal)?;
235
236                let Some((tx, rx)) = channel() else {
237                    return Err(ActorError::Internal);
238                };
239                self.script_sender
240                    .send(DevtoolScriptControlMsg::ListFrames(
241                        browsing_context_actor.pipeline_id(),
242                        frames.start,
243                        frames.count,
244                        tx,
245                    ))
246                    .map_err(|_| ActorError::Internal)?;
247
248                let result = rx.recv().map_err(|_| ActorError::Internal)?;
249                // Frame actors should be registered here
250                // https://searchfox.org/firefox-main/source/devtools/server/actors/thread.js#1425
251                let msg = FramesReply {
252                    from: self.name().into(),
253                    frames: result
254                        .iter()
255                        .map(|frame_name| registry.encode::<FrameActor, _>(frame_name))
256                        .collect(),
257                };
258                request.reply_final(&msg)?
259            },
260
261            _ => return Err(ActorError::UnrecognizedPacketType),
262        };
263        Ok(())
264    }
265}