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 pub spidermonkey_id: u32,
63 pub introduction_type: String,
65
66 script_sender: IpcSender<DevtoolScriptControlMsg>,
67}
68
69#[derive(Serialize)]
70struct SourceContentReply {
71 from: String,
72 #[serde(rename = "contentType")]
73 content_type: Option<String>,
74 source: String,
75}
76
77#[derive(Serialize)]
78struct GetBreakableLinesReply {
79 from: String,
80 lines: BTreeSet<u32>,
83}
84
85#[derive(Serialize)]
86struct GetBreakpointPositionsCompressedReply {
87 from: String,
88 positions: BTreeMap<u32, BTreeSet<u32>>,
93}
94
95impl SourceManager {
96 pub fn new() -> Self {
97 Self {
98 source_actor_names: RefCell::new(BTreeSet::default()),
99 }
100 }
101
102 pub fn add_source(&self, actor_name: &str) {
103 self.source_actor_names
104 .borrow_mut()
105 .insert(actor_name.to_owned());
106 }
107
108 pub fn source_forms(&self, actors: &ActorRegistry) -> Vec<SourceForm> {
109 self.source_actor_names
110 .borrow()
111 .iter()
112 .map(|actor_name| actors.find::<SourceActor>(actor_name).source_form())
113 .collect()
114 }
115}
116
117impl SourceActor {
118 pub fn new(
119 name: String,
120 url: ServoUrl,
121 content: Option<String>,
122 content_type: Option<String>,
123 spidermonkey_id: u32,
124 introduction_type: String,
125 script_sender: IpcSender<DevtoolScriptControlMsg>,
126 ) -> SourceActor {
127 SourceActor {
128 name,
129 url,
130 content,
131 content_type,
132 is_black_boxed: false,
133 spidermonkey_id,
134 introduction_type,
135 script_sender,
136 }
137 }
138
139 #[allow(clippy::too_many_arguments)]
140 pub fn new_registered(
141 actors: &mut ActorRegistry,
142 pipeline_id: PipelineId,
143 url: ServoUrl,
144 content: Option<String>,
145 content_type: Option<String>,
146 spidermonkey_id: u32,
147 introduction_type: String,
148 script_sender: IpcSender<DevtoolScriptControlMsg>,
149 ) -> &SourceActor {
150 let source_actor_name = actors.new_name("source");
151
152 let source_actor = SourceActor::new(
153 source_actor_name.clone(),
154 url,
155 content,
156 content_type,
157 spidermonkey_id,
158 introduction_type,
159 script_sender,
160 );
161 actors.register(Box::new(source_actor));
162 actors.register_source_actor(pipeline_id, &source_actor_name);
163
164 actors.find(&source_actor_name)
165 }
166
167 pub fn source_form(&self) -> SourceForm {
168 SourceForm {
169 actor: self.name.clone(),
170 url: self.url.to_string(),
171 is_black_boxed: self.is_black_boxed,
172 introduction_type: self.introduction_type.clone(),
173 }
174 }
175}
176
177impl Actor for SourceActor {
178 fn name(&self) -> String {
179 self.name.clone()
180 }
181
182 fn handle_message(
183 &self,
184 request: ClientRequest,
185 _registry: &ActorRegistry,
186 msg_type: &str,
187 _msg: &Map<String, Value>,
188 _id: StreamId,
189 ) -> Result<(), ActorError> {
190 match msg_type {
191 "source" => {
193 let reply = SourceContentReply {
194 from: self.name(),
195 content_type: self.content_type.clone(),
196 source: self
203 .content
204 .as_deref()
205 .unwrap_or("<!-- not available; please reload! -->")
206 .to_owned(),
207 };
208 request.reply_final(&reply)?
209 },
210 "getBreakableLines" => {
213 let (tx, rx) = channel().map_err(|_| ActorError::Internal)?;
214 self.script_sender
215 .send(DevtoolScriptControlMsg::GetPossibleBreakpoints(
216 self.spidermonkey_id,
217 tx,
218 ))
219 .map_err(|_| ActorError::Internal)?;
220 let result = rx.recv().map_err(|_| ActorError::Internal)?;
221 let lines = result
222 .into_iter()
223 .map(|entry| entry.line_number)
224 .collect::<BTreeSet<_>>();
225 let reply = GetBreakableLinesReply {
226 from: self.name(),
227 lines,
228 };
229 request.reply_final(&reply)?
230 },
231 "getBreakpointPositionsCompressed" => {
234 let (tx, rx) = channel().map_err(|_| ActorError::Internal)?;
235 self.script_sender
236 .send(DevtoolScriptControlMsg::GetPossibleBreakpoints(
237 self.spidermonkey_id,
238 tx,
239 ))
240 .map_err(|_| ActorError::Internal)?;
241 let result = rx.recv().map_err(|_| ActorError::Internal)?;
242 let mut positions: BTreeMap<u32, BTreeSet<u32>> = BTreeMap::default();
243 for entry in result {
244 positions
248 .entry(entry.line_number)
249 .or_default()
250 .insert(entry.column_number - 1);
251 }
252 let reply = GetBreakpointPositionsCompressedReply {
253 from: self.name(),
254 positions,
255 };
256 request.reply_final(&reply)?
257 },
258 _ => return Err(ActorError::UnrecognizedPacketType),
259 };
260 Ok(())
261 }
262}