1use std::cell::RefCell;
6use std::collections::{BTreeMap, BTreeSet};
7
8use base::id::PipelineId;
9use devtools_traits::DevtoolScriptControlMsg;
10use ipc_channel::ipc::{IpcSender, channel};
11use serde::Serialize;
12use serde_json::{Map, Value};
13use servo_url::ServoUrl;
14
15use crate::StreamId;
16use crate::actor::{Actor, ActorError, ActorRegistry};
17use crate::protocol::ClientRequest;
18
19#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
26#[serde(rename_all = "camelCase")]
27pub(crate) struct SourceForm {
28 pub actor: String,
29 pub url: String,
31 pub is_black_boxed: bool,
32 pub introduction_type: String,
34}
35
36#[derive(Serialize)]
37pub(crate) struct SourcesReply {
38 pub from: String,
39 pub sources: Vec<SourceForm>,
40}
41
42pub(crate) struct SourceManager {
43 source_actor_names: RefCell<BTreeSet<String>>,
44}
45
46#[derive(Clone, Debug)]
47pub struct SourceActor {
48 pub name: String,
50
51 pub url: ServoUrl,
53
54 pub is_black_boxed: bool,
57
58 pub content: Option<String>,
59 pub content_type: Option<String>,
60
61 #[allow(unused)]
63 pub spidermonkey_id: u32,
64 pub introduction_type: String,
66
67 script_sender: IpcSender<DevtoolScriptControlMsg>,
68}
69
70#[derive(Serialize)]
71struct SourceContentReply {
72 from: String,
73 #[serde(rename = "contentType")]
74 content_type: Option<String>,
75 source: String,
76}
77
78#[derive(Serialize)]
79struct GetBreakableLinesReply {
80 from: String,
81 lines: BTreeSet<u32>,
84}
85
86#[derive(Serialize)]
87struct GetBreakpointPositionsCompressedReply {
88 from: String,
89 positions: BTreeMap<u32, BTreeSet<u32>>,
94}
95
96impl SourceManager {
97 pub fn new() -> Self {
98 Self {
99 source_actor_names: RefCell::new(BTreeSet::default()),
100 }
101 }
102
103 pub fn add_source(&self, actor_name: &str) {
104 self.source_actor_names
105 .borrow_mut()
106 .insert(actor_name.to_owned());
107 }
108
109 pub fn source_forms(&self, actors: &ActorRegistry) -> Vec<SourceForm> {
110 self.source_actor_names
111 .borrow()
112 .iter()
113 .map(|actor_name| actors.find::<SourceActor>(actor_name).source_form())
114 .collect()
115 }
116}
117
118impl SourceActor {
119 pub fn new(
120 name: String,
121 url: ServoUrl,
122 content: Option<String>,
123 content_type: Option<String>,
124 spidermonkey_id: u32,
125 introduction_type: String,
126 script_sender: IpcSender<DevtoolScriptControlMsg>,
127 ) -> SourceActor {
128 SourceActor {
129 name,
130 url,
131 content,
132 content_type,
133 is_black_boxed: false,
134 spidermonkey_id,
135 introduction_type,
136 script_sender,
137 }
138 }
139
140 #[allow(clippy::too_many_arguments)]
141 pub fn new_registered(
142 actors: &mut ActorRegistry,
143 pipeline_id: PipelineId,
144 url: ServoUrl,
145 content: Option<String>,
146 content_type: Option<String>,
147 spidermonkey_id: u32,
148 introduction_type: String,
149 script_sender: IpcSender<DevtoolScriptControlMsg>,
150 ) -> &SourceActor {
151 let source_actor_name = actors.new_name("source");
152
153 let source_actor = SourceActor::new(
154 source_actor_name.clone(),
155 url,
156 content,
157 content_type,
158 spidermonkey_id,
159 introduction_type,
160 script_sender,
161 );
162 actors.register(Box::new(source_actor));
163 actors.register_source_actor(pipeline_id, &source_actor_name);
164
165 actors.find(&source_actor_name)
166 }
167
168 pub fn source_form(&self) -> SourceForm {
169 SourceForm {
170 actor: self.name.clone(),
171 url: self.url.to_string(),
172 is_black_boxed: self.is_black_boxed,
173 introduction_type: self.introduction_type.clone(),
174 }
175 }
176}
177
178impl Actor for SourceActor {
179 fn name(&self) -> String {
180 self.name.clone()
181 }
182
183 fn handle_message(
184 &self,
185 request: ClientRequest,
186 _registry: &ActorRegistry,
187 msg_type: &str,
188 _msg: &Map<String, Value>,
189 _id: StreamId,
190 ) -> Result<(), ActorError> {
191 match msg_type {
192 "source" => {
194 let reply = SourceContentReply {
195 from: self.name(),
196 content_type: self.content_type.clone(),
197 source: self
204 .content
205 .as_deref()
206 .unwrap_or("<!-- not available; please reload! -->")
207 .to_owned(),
208 };
209 request.reply_final(&reply)?
210 },
211 "getBreakableLines" => {
214 let (tx, rx) = channel().map_err(|_| ActorError::Internal)?;
215 self.script_sender
216 .send(DevtoolScriptControlMsg::GetPossibleBreakpoints(
217 self.spidermonkey_id,
218 tx,
219 ))
220 .map_err(|_| ActorError::Internal)?;
221 let result = rx.recv().map_err(|_| ActorError::Internal)?;
222 let lines = result
223 .into_iter()
224 .map(|entry| entry.line_number)
225 .collect::<BTreeSet<_>>();
226 let reply = GetBreakableLinesReply {
227 from: self.name(),
228 lines,
229 };
230 request.reply_final(&reply)?
231 },
232 "getBreakpointPositionsCompressed" => {
235 let (tx, rx) = channel().map_err(|_| ActorError::Internal)?;
236 self.script_sender
237 .send(DevtoolScriptControlMsg::GetPossibleBreakpoints(
238 self.spidermonkey_id,
239 tx,
240 ))
241 .map_err(|_| ActorError::Internal)?;
242 let result = rx.recv().map_err(|_| ActorError::Internal)?;
243 let mut positions: BTreeMap<u32, BTreeSet<u32>> = BTreeMap::default();
244 for entry in result {
245 positions
249 .entry(entry.line_number)
250 .or_default()
251 .insert(entry.column_number - 1);
252 }
253 let reply = GetBreakpointPositionsCompressedReply {
254 from: self.name(),
255 positions,
256 };
257 request.reply_final(&reply)?
258 },
259 _ => return Err(ActorError::UnrecognizedPacketType),
260 };
261 Ok(())
262 }
263}