1use std::collections::{BTreeMap, BTreeSet};
6use std::str::FromStr;
7use std::sync::Arc;
8
9use atomic_refcell::AtomicRefCell;
10use devtools_traits::DevtoolScriptControlMsg;
11use malloc_size_of_derive::MallocSizeOf;
12use serde::{Deserialize, Serialize};
13use serde_json::{Map, Value};
14use servo_base::generic_channel::{GenericSender, channel};
15use servo_base::id::PipelineId;
16use servo_url::ServoUrl;
17
18use crate::StreamId;
19use crate::actor::{Actor, ActorError, ActorRegistry, DowncastableActorArc, new_actor_name};
20use crate::actors::breakpoint::BreakpointRequestLocation;
21use crate::protocol::ClientRequest;
22
23#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize)]
30#[serde(rename_all = "camelCase")]
31pub(crate) struct SourceForm {
32 actor: String,
33 url: String,
35 is_black_boxed: bool,
36 introduction_type: String,
38}
39
40#[derive(Serialize)]
41pub(crate) struct SourcesReply {
42 pub from: String,
43 pub sources: Vec<SourceForm>,
44}
45
46#[derive(MallocSizeOf)]
47pub(crate) struct SourceManager {
48 source_actor_names: AtomicRefCell<BTreeSet<String>>,
49}
50
51impl SourceManager {
52 pub fn new() -> Self {
53 Self {
54 source_actor_names: AtomicRefCell::new(BTreeSet::default()),
55 }
56 }
57
58 pub fn add_source(&self, actor_name: &str) {
59 self.source_actor_names
60 .borrow_mut()
61 .insert(actor_name.to_owned());
62 }
63
64 pub fn source_forms(&self, registry: &ActorRegistry) -> Vec<SourceForm> {
65 self.source_actor_names
66 .borrow()
67 .iter()
68 .map(|source_name| registry.find::<SourceActor>(source_name).source_form())
69 .collect()
70 }
71
72 pub fn find_source(
73 &self,
74 registry: &ActorRegistry,
75 source_url: &str,
76 ) -> Option<DowncastableActorArc<SourceActor>> {
77 for source_name in self.source_actor_names.borrow().iter() {
78 let source_actor = registry.find::<SourceActor>(source_name);
79 if source_actor.url == ServoUrl::from_str(source_url).ok()? {
80 return Some(source_actor);
81 }
82 }
83 None
84 }
85}
86
87#[derive(Clone, Debug, MallocSizeOf)]
88pub(crate) struct SourceActor {
89 name: String,
91
92 url: ServoUrl,
94
95 is_black_boxed: bool,
98
99 pub content: AtomicRefCell<Option<String>>,
100 content_type: Option<String>,
101
102 pub spidermonkey_id: u32,
103 introduction_type: String,
105
106 pub script_sender: GenericSender<DevtoolScriptControlMsg>,
107}
108
109#[derive(Serialize)]
110struct SourceContentReply {
111 from: String,
112 #[serde(rename = "contentType")]
113 content_type: Option<String>,
114 source: String,
115}
116
117#[derive(Serialize)]
118struct GetBreakableLinesReply {
119 from: String,
120 lines: BTreeSet<u32>,
123}
124
125#[derive(Serialize)]
126struct GetBreakpointPositionsCompressedReply {
127 from: String,
128 positions: BTreeMap<u32, BTreeSet<u32>>,
133}
134
135#[derive(Deserialize)]
136struct GetBreakpointPositionsQuery {
137 start: BreakpointRequestLocation,
138 end: BreakpointRequestLocation,
139}
140
141#[derive(Deserialize)]
142struct GetBreakpointPositionsRequest {
143 query: GetBreakpointPositionsQuery,
144}
145
146impl SourceActor {
147 #[expect(clippy::too_many_arguments)]
148 pub fn register(
149 registry: &ActorRegistry,
150 pipeline_id: PipelineId,
151 url: ServoUrl,
152 content: Option<String>,
153 content_type: Option<String>,
154 spidermonkey_id: u32,
155 introduction_type: String,
156 script_sender: GenericSender<DevtoolScriptControlMsg>,
157 ) -> Arc<Self> {
158 let name = new_actor_name::<Self>();
159 let actor = Self {
160 name: name.clone(),
161 url,
162 content: AtomicRefCell::new(content),
163 content_type,
164 is_black_boxed: false,
165 spidermonkey_id,
166 introduction_type,
167 script_sender,
168 };
169 registry.register_source_actor(pipeline_id, &name);
170 registry.register::<Self>(actor)
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) -> &str {
185 &self.name
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().into(),
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().into(),
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().into(),
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}