1use std::collections::{BTreeMap, BTreeSet};
6use std::str::FromStr;
7
8use atomic_refcell::AtomicRefCell;
9use devtools_traits::DevtoolScriptControlMsg;
10use malloc_size_of_derive::MallocSizeOf;
11use serde::{Deserialize, Serialize};
12use serde_json::{Map, Value};
13use servo_base::generic_channel::{GenericSender, channel};
14use servo_base::id::PipelineId;
15use servo_url::ServoUrl;
16
17use crate::StreamId;
18use crate::actor::{Actor, ActorError, ActorRegistry, DowncastableActorArc};
19use crate::actors::breakpoint::BreakpointRequestLocation;
20use crate::protocol::ClientRequest;
21
22#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
29#[serde(rename_all = "camelCase")]
30pub(crate) struct SourceForm {
31 actor: String,
32 url: String,
34 is_black_boxed: bool,
35 introduction_type: String,
37}
38
39#[derive(Serialize)]
40pub(crate) struct SourcesReply {
41 pub from: String,
42 pub sources: Vec<SourceForm>,
43}
44
45#[derive(MallocSizeOf)]
46pub(crate) struct SourceManager {
47 source_actor_names: AtomicRefCell<BTreeSet<String>>,
48}
49
50impl SourceManager {
51 pub fn new() -> Self {
52 Self {
53 source_actor_names: AtomicRefCell::new(BTreeSet::default()),
54 }
55 }
56
57 pub fn add_source(&self, actor_name: &str) {
58 self.source_actor_names
59 .borrow_mut()
60 .insert(actor_name.to_owned());
61 }
62
63 pub fn source_forms(&self, registry: &ActorRegistry) -> Vec<SourceForm> {
64 self.source_actor_names
65 .borrow()
66 .iter()
67 .map(|source_name| registry.find::<SourceActor>(source_name).source_form())
68 .collect()
69 }
70
71 pub fn find_source(
72 &self,
73 registry: &ActorRegistry,
74 source_url: &str,
75 ) -> Option<DowncastableActorArc<SourceActor>> {
76 for source_name in self.source_actor_names.borrow().iter() {
77 let source_actor = registry.find::<SourceActor>(source_name);
78 if source_actor.url == ServoUrl::from_str(source_url).ok()? {
79 return Some(source_actor);
80 }
81 }
82 None
83 }
84}
85
86#[derive(Clone, Debug, MallocSizeOf)]
87pub(crate) struct SourceActor {
88 name: String,
90
91 url: ServoUrl,
93
94 is_black_boxed: bool,
97
98 pub content: AtomicRefCell<Option<String>>,
99 content_type: Option<String>,
100
101 pub spidermonkey_id: u32,
102 introduction_type: String,
104
105 pub script_sender: GenericSender<DevtoolScriptControlMsg>,
106}
107
108#[derive(Serialize)]
109struct SourceContentReply {
110 from: String,
111 #[serde(rename = "contentType")]
112 content_type: Option<String>,
113 source: String,
114}
115
116#[derive(Serialize)]
117struct GetBreakableLinesReply {
118 from: String,
119 lines: BTreeSet<u32>,
122}
123
124#[derive(Serialize)]
125struct GetBreakpointPositionsCompressedReply {
126 from: String,
127 positions: BTreeMap<u32, BTreeSet<u32>>,
132}
133
134#[derive(Deserialize)]
135struct GetBreakpointPositionsQuery {
136 start: BreakpointRequestLocation,
137 end: BreakpointRequestLocation,
138}
139
140#[derive(Deserialize)]
141struct GetBreakpointPositionsRequest {
142 query: GetBreakpointPositionsQuery,
143}
144
145impl SourceActor {
146 #[expect(clippy::too_many_arguments)]
147 pub fn register(
148 registry: &ActorRegistry,
149 pipeline_id: PipelineId,
150 url: ServoUrl,
151 content: Option<String>,
152 content_type: Option<String>,
153 spidermonkey_id: u32,
154 introduction_type: String,
155 script_sender: GenericSender<DevtoolScriptControlMsg>,
156 ) -> String {
157 let name = registry.new_name::<Self>();
158 let actor = Self {
159 name: name.clone(),
160 url,
161 content: AtomicRefCell::new(content),
162 content_type,
163 is_black_boxed: false,
164 spidermonkey_id,
165 introduction_type,
166 script_sender,
167 };
168 registry.register::<Self>(actor);
169 registry.register_source_actor(pipeline_id, &name);
170 name
171 }
172
173 pub fn source_form(&self) -> SourceForm {
174 SourceForm {
175 actor: self.name.clone(),
176 url: self.url.to_string(),
177 is_black_boxed: self.is_black_boxed,
178 introduction_type: self.introduction_type.clone(),
179 }
180 }
181}
182
183impl Actor for SourceActor {
184 fn name(&self) -> String {
185 self.name.clone()
186 }
187
188 fn handle_message(
189 &self,
190 request: ClientRequest,
191 _registry: &ActorRegistry,
192 msg_type: &str,
193 msg: &Map<String, Value>,
194 _id: StreamId,
195 ) -> Result<(), ActorError> {
196 match msg_type {
197 "source" => {
199 let reply = SourceContentReply {
200 from: self.name(),
201 content_type: self.content_type.clone(),
202 source: self
209 .content
210 .borrow()
211 .as_deref()
212 .unwrap_or("<!-- not available; please reload! -->")
213 .to_owned(),
214 };
215 request.reply_final(&reply)?
216 },
217 "getBreakableLines" => {
220 let Some((tx, rx)) = channel() else {
221 return Err(ActorError::Internal);
222 };
223 self.script_sender
224 .send(DevtoolScriptControlMsg::GetPossibleBreakpoints(
225 self.spidermonkey_id,
226 tx,
227 ))
228 .map_err(|_| ActorError::Internal)?;
229 let result = rx.recv().map_err(|_| ActorError::Internal)?;
230 let lines = result
231 .into_iter()
232 .map(|location| location.line_number)
233 .collect::<BTreeSet<_>>();
234 let reply = GetBreakableLinesReply {
235 from: self.name(),
236 lines,
237 };
238 request.reply_final(&reply)?
239 },
240 "getBreakpointPositionsCompressed" => {
243 let query =
244 serde_json::from_value::<GetBreakpointPositionsRequest>(msg.clone().into())
245 .ok()
246 .map(|msg| (msg.query.start, msg.query.end));
247
248 let (tx, rx) = channel().ok_or(ActorError::Internal)?;
249 self.script_sender
250 .send(DevtoolScriptControlMsg::GetPossibleBreakpoints(
251 self.spidermonkey_id,
252 tx,
253 ))
254 .map_err(|_| ActorError::Internal)?;
255 let result = rx.recv().map_err(|_| ActorError::Internal)?;
256
257 let mut positions: BTreeMap<u32, BTreeSet<u32>> = BTreeMap::default();
258 for location in result {
259 if query.as_ref().is_some_and(|(start, end)| {
263 location.line_number < start.line || location.line_number > end.line
264 }) {
265 continue;
266 }
267 positions
268 .entry(location.line_number)
269 .or_default()
270 .insert(location.column_number - 1);
271 }
272 let reply = GetBreakpointPositionsCompressedReply {
273 from: self.name(),
274 positions,
275 };
276 request.reply_final(&reply)?
277 },
278 _ => return Err(ActorError::UnrecognizedPacketType),
279 };
280 Ok(())
281 }
282}
283
284impl SourceActor {
285 pub fn find_offset(&self, line: u32, column: u32) -> Option<(u32, u32)> {
286 let (tx, rx) = channel()?;
287 self.script_sender
288 .send(DevtoolScriptControlMsg::GetPossibleBreakpoints(
289 self.spidermonkey_id,
290 tx,
291 ))
292 .ok()?;
293 let result = rx.recv().ok()?;
294 for location in result {
295 if location.line_number == line && location.column_number - 1 == column {
299 return Some((location.script_id, location.offset));
300 }
301 }
302 None
303 }
304}