1use std::collections::HashMap;
10use std::sync::atomic::{AtomicBool, Ordering};
11
12use atomic_refcell::AtomicRefCell;
13use devtools_traits::{
14 ConsoleMessage, ConsoleMessageFields, DevtoolScriptControlMsg, PageError, StackFrame,
15 get_time_stamp,
16};
17use malloc_size_of_derive::MallocSizeOf;
18use serde::Serialize;
19use serde_json::{self, Map, Value};
20use servo_base::generic_channel::{self, GenericSender};
21use servo_base::id::TEST_PIPELINE_ID;
22use uuid::Uuid;
23
24use crate::actor::{Actor, ActorError, ActorRegistry};
25use crate::actors::browsing_context::BrowsingContextActor;
26use crate::actors::worker::WorkerTargetActor;
27use crate::protocol::{ClientRequest, DevtoolsConnection, JsonPacketStream};
28use crate::resource::{ResourceArrayType, ResourceAvailable};
29use crate::{EmptyReplyMsg, StreamId, UniqueId, debugger_value_to_json};
30
31#[derive(Clone, Serialize, MallocSizeOf)]
32#[serde(rename_all = "camelCase")]
33pub(crate) struct DevtoolsConsoleMessage {
34 #[serde(flatten)]
35 fields: ConsoleMessageFields,
36 #[ignore_malloc_size_of = "Currently no way to have serde_json::Value"]
37 arguments: Vec<Value>,
38 #[serde(skip_serializing_if = "Option::is_none")]
39 stacktrace: Option<Vec<StackFrame>>,
40 }
44
45impl DevtoolsConsoleMessage {
46 pub(crate) fn new(message: ConsoleMessage, registry: &ActorRegistry) -> Self {
47 Self {
48 fields: message.fields,
49 arguments: message
50 .arguments
51 .into_iter()
52 .map(|argument| debugger_value_to_json(registry, argument))
53 .collect(),
54 stacktrace: message.stacktrace,
55 }
56 }
57}
58
59#[derive(Clone, Serialize, MallocSizeOf)]
60#[serde(rename_all = "camelCase")]
61struct DevtoolsPageError {
62 #[serde(flatten)]
63 page_error: PageError,
64 category: String,
65 error: bool,
66 warning: bool,
67 info: bool,
68 private: bool,
69 #[serde(skip_serializing_if = "Option::is_none")]
70 stacktrace: Option<Vec<StackFrame>>,
71 }
77
78impl From<PageError> for DevtoolsPageError {
79 fn from(page_error: PageError) -> Self {
80 Self {
81 page_error,
82 category: "script".to_string(),
83 error: true,
84 warning: false,
85 info: false,
86 private: false,
87 stacktrace: None,
88 }
89 }
90}
91#[derive(Clone, Serialize, MallocSizeOf)]
92#[serde(rename_all = "camelCase")]
93pub(crate) struct PageErrorWrapper {
94 page_error: DevtoolsPageError,
95}
96
97impl From<PageError> for PageErrorWrapper {
98 fn from(page_error: PageError) -> Self {
99 Self {
100 page_error: page_error.into(),
101 }
102 }
103}
104
105#[derive(Clone, Serialize, MallocSizeOf)]
106#[serde(untagged)]
107pub(crate) enum ConsoleResource {
108 ConsoleMessage(DevtoolsConsoleMessage),
109 PageError(PageErrorWrapper),
110}
111
112impl ConsoleResource {
113 pub fn resource_type(&self) -> String {
114 match self {
115 ConsoleResource::ConsoleMessage(_) => "console-message".into(),
116 ConsoleResource::PageError(_) => "error-message".into(),
117 }
118 }
119}
120
121#[derive(Serialize)]
122pub struct ConsoleClearMessage {
123 pub level: String,
124}
125
126#[derive(Serialize)]
127#[serde(rename_all = "camelCase")]
128struct AutocompleteReply {
129 from: String,
130 matches: Vec<String>,
131 match_prop: String,
132}
133
134#[derive(Serialize)]
135#[serde(rename_all = "camelCase")]
136struct EvaluateJSReply {
137 from: String,
138 input: String,
139 result: Value,
140 timestamp: u64,
141 exception: Value,
142 exception_message: Value,
143 has_exception: bool,
144 helper_result: Value,
145}
146
147#[derive(Serialize)]
148#[serde(rename_all = "camelCase")]
149struct EvaluateJSEvent {
150 from: String,
151 #[serde(rename = "type")]
152 type_: String,
153 input: String,
154 result: Value,
155 timestamp: u64,
156 #[serde(rename = "resultID")]
157 result_id: String,
158 exception: Value,
159 exception_message: Value,
160 has_exception: bool,
161 helper_result: Value,
162}
163
164#[derive(Serialize)]
165struct EvaluateJSAsyncReply {
166 from: String,
167 #[serde(rename = "resultID")]
168 result_id: String,
169}
170
171#[derive(Serialize)]
172struct SetPreferencesReply {
173 from: String,
174 updated: Vec<String>,
175}
176
177#[derive(MallocSizeOf)]
178pub(crate) enum Root {
179 BrowsingContext(String),
180 DedicatedWorker(String),
181}
182
183#[derive(MallocSizeOf)]
184pub(crate) struct ConsoleActor {
185 name: String,
186 root: Root,
187 cached_events: AtomicRefCell<HashMap<UniqueId, Vec<ConsoleResource>>>,
188 client_ready_to_receive_messages: AtomicBool,
194}
195
196impl ConsoleActor {
197 pub fn register(registry: &ActorRegistry, name: String, root: Root) -> String {
198 let actor = Self {
199 name: name.clone(),
200 root,
201 cached_events: Default::default(),
202 client_ready_to_receive_messages: false.into(),
203 };
204 registry.register(actor);
205 name
206 }
207
208 fn script_chan(&self, registry: &ActorRegistry) -> GenericSender<DevtoolScriptControlMsg> {
209 match &self.root {
210 Root::BrowsingContext(browsing_context_name) => registry
211 .find::<BrowsingContextActor>(browsing_context_name)
212 .script_chan(),
213 Root::DedicatedWorker(worker_name) => registry
214 .find::<WorkerTargetActor>(worker_name)
215 .script_sender
216 .clone(),
217 }
218 }
219
220 fn current_unique_id(&self, registry: &ActorRegistry) -> UniqueId {
221 match &self.root {
222 Root::BrowsingContext(browsing_context_name) => UniqueId::Pipeline(
223 registry
224 .find::<BrowsingContextActor>(browsing_context_name)
225 .pipeline_id(),
226 ),
227 Root::DedicatedWorker(worker_name) => {
228 UniqueId::Worker(registry.find::<WorkerTargetActor>(worker_name).worker_id)
229 },
230 }
231 }
232
233 fn evaluate_js(
234 &self,
235 registry: &ActorRegistry,
236 msg: &Map<String, Value>,
237 ) -> Result<EvaluateJSReply, ()> {
238 let input = msg.get("text").unwrap().as_str().unwrap().to_owned();
239 let frame_actor_id = msg
240 .get("frameActor")
241 .and_then(|v| v.as_str())
242 .map(String::from);
243 let (chan, port) = generic_channel::channel().unwrap();
244 let pipeline = match self.current_unique_id(registry) {
246 UniqueId::Pipeline(p) => p,
247 UniqueId::Worker(_) => TEST_PIPELINE_ID,
248 };
249 self.script_chan(registry)
250 .send(DevtoolScriptControlMsg::Eval(
251 input.clone(),
252 pipeline,
253 frame_actor_id,
254 chan,
255 ))
256 .unwrap();
257
258 let eval_result = port.recv().map_err(|_| ())?;
259 let has_exception = eval_result.has_exception;
260
261 let reply = EvaluateJSReply {
262 from: self.name(),
263 input,
264 result: debugger_value_to_json(registry, eval_result.value),
265 timestamp: get_time_stamp(),
266 exception: Value::Null,
267 exception_message: Value::Null,
268 has_exception,
269 helper_result: Value::Null,
270 };
271 Ok(reply)
272 }
273
274 pub(crate) fn handle_console_resource(
275 &self,
276 resource: ConsoleResource,
277 id: UniqueId,
278 registry: &ActorRegistry,
279 stream: &mut DevtoolsConnection,
280 ) {
281 self.cached_events
282 .borrow_mut()
283 .entry(id.clone())
284 .or_default()
285 .push(resource.clone());
286 if !self
287 .client_ready_to_receive_messages
288 .load(Ordering::Relaxed)
289 {
290 return;
291 }
292 let resource_type = resource.resource_type();
293 if id == self.current_unique_id(registry) {
294 if let Root::BrowsingContext(browsing_context_name) = &self.root {
295 registry
296 .find::<BrowsingContextActor>(browsing_context_name)
297 .resource_array(
298 resource,
299 resource_type,
300 ResourceArrayType::Available,
301 stream,
302 )
303 };
304 }
305 }
306
307 pub(crate) fn send_clear_message(
308 &self,
309 id: UniqueId,
310 registry: &ActorRegistry,
311 stream: &mut DevtoolsConnection,
312 ) {
313 if id == self.current_unique_id(registry) {
314 if let Root::BrowsingContext(browsing_context_name) = &self.root {
315 registry
316 .find::<BrowsingContextActor>(browsing_context_name)
317 .resource_array(
318 ConsoleClearMessage {
319 level: "clear".to_owned(),
320 },
321 "console-message".into(),
322 ResourceArrayType::Available,
323 stream,
324 )
325 };
326 }
327 }
328
329 pub(crate) fn get_cached_messages(
330 &self,
331 registry: &ActorRegistry,
332 resource: &str,
333 ) -> Vec<ConsoleResource> {
334 let id = self.current_unique_id(registry);
335 let cached_events = self.cached_events.borrow();
336 let Some(events) = cached_events.get(&id) else {
337 return vec![];
338 };
339 events
340 .iter()
341 .filter(|event| event.resource_type() == resource)
342 .cloned()
343 .collect()
344 }
345
346 pub(crate) fn received_first_message_from_client(&self) {
347 self.client_ready_to_receive_messages
348 .store(true, Ordering::Relaxed);
349 }
350}
351
352impl Actor for ConsoleActor {
353 fn name(&self) -> String {
354 self.name.clone()
355 }
356
357 fn handle_message(
358 &self,
359 request: ClientRequest,
360 registry: &ActorRegistry,
361 msg_type: &str,
362 msg: &Map<String, Value>,
363 _id: StreamId,
364 ) -> Result<(), ActorError> {
365 match msg_type {
366 "clearMessagesCacheAsync" => {
367 self.cached_events
368 .borrow_mut()
369 .remove(&self.current_unique_id(registry));
370 let msg = EmptyReplyMsg { from: self.name() };
371 request.reply_final(&msg)?
372 },
373
374 "autocomplete" => {
377 let msg = AutocompleteReply {
378 from: self.name(),
379 matches: vec![],
380 match_prop: "".to_owned(),
381 };
382 request.reply_final(&msg)?
383 },
384
385 "evaluateJS" => {
386 let msg = self.evaluate_js(registry, msg);
387 request.reply_final(&msg)?
388 },
389
390 "evaluateJSAsync" => {
391 let result_id = Uuid::new_v4().to_string();
392 let early_reply = EvaluateJSAsyncReply {
393 from: self.name(),
394 result_id: result_id.clone(),
395 };
396 let mut stream = request.reply(&early_reply)?;
399
400 if msg.get("eager").and_then(|v| v.as_bool()).unwrap_or(false) {
401 return Ok(());
404 }
405
406 let reply = self.evaluate_js(registry, msg).unwrap();
407 let msg = EvaluateJSEvent {
408 from: self.name(),
409 type_: "evaluationResult".to_owned(),
410 input: reply.input,
411 result: reply.result,
412 timestamp: reply.timestamp,
413 result_id,
414 exception: reply.exception,
415 exception_message: reply.exception_message,
416 has_exception: reply.has_exception,
417 helper_result: reply.helper_result,
418 };
419 stream.write_json_packet(&msg)?
421 },
422
423 "setPreferences" => {
424 let msg = SetPreferencesReply {
425 from: self.name(),
426 updated: vec![],
427 };
428 request.reply_final(&msg)?
429 },
430
431 _ => return Err(ActorError::UnrecognizedPacketType),
435 };
436 Ok(())
437 }
438}