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