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 = PauseActor::register(registry);
153                let msg = ThreadAttached {
154                    from: self.name(),
155                    type_: "paused".to_owned(),
156                    actor: pause_name,
157                    frame: 0,
158                    error: 0,
159                    recording_endpoint: 0,
160                    execution_point: 0,
161                    popped_frames: vec![],
162                    why: PauseReason {
163                        type_: "attached".to_owned(),
164                        on_next: None,
165                    },
166                };
167                request.write_json_packet(&msg)?;
168                request.reply_final(&EmptyReplyMsg { from: self.name() })?
169            },
170
171            "resume" => {
172                let resume: ResumeRequest =
173                    serde_json::from_value(msg.clone().into()).map_err(|_| ActorError::Internal)?;
174
175                let _ = self.script_sender.send(DevtoolScriptControlMsg::Resume(
176                    resume.get_type(),
177                    resume.frame_actor_id,
178                ));
179
180                let msg = ThreadResumedReply {
181                    from: self.name(),
182                    type_: "resumed".to_owned(),
183                };
184                request.write_json_packet(&msg)?;
185                request.reply_final(&EmptyReplyMsg { from: self.name() })?
186            },
187
188            "interrupt" => {
189                self.script_sender
190                    .send(DevtoolScriptControlMsg::Interrupt)
191                    .map_err(|_| ActorError::Internal)?;
192
193                request.reply_final(&EmptyReplyMsg { from: self.name() })?
194            },
195
196            "reconfigure" => request.reply_final(&EmptyReplyMsg { from: self.name() })?,
197
198            "getAvailableEventBreakpoints" => {
199                // TODO: Send list of available event breakpoints (animation, clipboard, load...)
200                let msg = GetAvailableEventBreakpointsReply {
201                    from: self.name(),
202                    value: vec![],
203                };
204                request.reply_final(&msg)?
205            },
206
207            // Client has attached to the thread and wants to load script sources.
208            // <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#loading-script-sources>
209            "sources" => {
210                let msg = SourcesReply {
211                    from: self.name(),
212                    sources: self.source_manager.source_forms(registry),
213                };
214                request.reply_final(&msg)?
215            },
216
217            "frames" => {
218                let Some(ref browsing_context_name) = self.browsing_context_name else {
219                    return Err(ActorError::Internal);
220                };
221                let browsing_context_actor =
222                    registry.find::<BrowsingContextActor>(browsing_context_name);
223
224                let frames: FramesRequest =
225                    serde_json::from_value(msg.clone().into()).map_err(|_| ActorError::Internal)?;
226
227                let Some((tx, rx)) = channel() else {
228                    return Err(ActorError::Internal);
229                };
230                self.script_sender
231                    .send(DevtoolScriptControlMsg::ListFrames(
232                        browsing_context_actor.pipeline_id(),
233                        frames.start,
234                        frames.count,
235                        tx,
236                    ))
237                    .map_err(|_| ActorError::Internal)?;
238
239                let result = rx.recv().map_err(|_| ActorError::Internal)?;
240                // Frame actors should be registered here
241                // https://searchfox.org/firefox-main/source/devtools/server/actors/thread.js#1425
242                let msg = FramesReply {
243                    from: self.name(),
244                    frames: result
245                        .iter()
246                        .map(|frame_name| registry.encode::<FrameActor, _>(frame_name))
247                        .collect(),
248                };
249                request.reply_final(&msg)?
250            },
251
252            _ => return Err(ActorError::UnrecognizedPacketType),
253        };
254        Ok(())
255    }
256}