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