1use std::cell::RefCell;
10use std::collections::HashMap;
11use std::net::TcpStream;
12use std::time::{SystemTime, UNIX_EPOCH};
13
14use base::id::TEST_PIPELINE_ID;
15use devtools_traits::EvaluateJSReply::{
16 ActorValue, BooleanValue, NullValue, NumberValue, StringValue, VoidValue,
17};
18use devtools_traits::{
19 CachedConsoleMessage, CachedConsoleMessageTypes, ConsoleLog, ConsoleMessage,
20 DevtoolScriptControlMsg, PageError,
21};
22use ipc_channel::ipc::{self, IpcSender};
23use log::debug;
24use serde::Serialize;
25use serde_json::{self, Map, Number, Value};
26use uuid::Uuid;
27
28use crate::actor::{Actor, ActorError, ActorRegistry};
29use crate::actors::browsing_context::BrowsingContextActor;
30use crate::actors::object::ObjectActor;
31use crate::actors::worker::WorkerActor;
32use crate::protocol::{ClientRequest, JsonPacketStream};
33use crate::resource::{ResourceArrayType, ResourceAvailable};
34use crate::{StreamId, UniqueId};
35
36trait EncodableConsoleMessage {
37 fn encode(&self) -> serde_json::Result<String>;
38}
39
40impl EncodableConsoleMessage for CachedConsoleMessage {
41 fn encode(&self) -> serde_json::Result<String> {
42 match *self {
43 CachedConsoleMessage::PageError(ref a) => serde_json::to_string(a),
44 CachedConsoleMessage::ConsoleLog(ref a) => serde_json::to_string(a),
45 }
46 }
47}
48
49#[derive(Serialize)]
50struct StartedListenersTraits;
51
52#[derive(Serialize)]
53#[serde(rename_all = "camelCase")]
54struct StartedListenersReply {
55 from: String,
56 native_console_api: bool,
57 started_listeners: Vec<String>,
58 traits: StartedListenersTraits,
59}
60
61#[derive(Serialize)]
62struct GetCachedMessagesReply {
63 from: String,
64 messages: Vec<Map<String, Value>>,
65}
66
67#[derive(Serialize)]
68#[serde(rename_all = "camelCase")]
69struct StopListenersReply {
70 from: String,
71 stopped_listeners: Vec<String>,
72}
73
74#[derive(Serialize)]
75#[serde(rename_all = "camelCase")]
76struct AutocompleteReply {
77 from: String,
78 matches: Vec<String>,
79 match_prop: String,
80}
81
82#[derive(Serialize)]
83#[serde(rename_all = "camelCase")]
84struct EvaluateJSReply {
85 from: String,
86 input: String,
87 result: Value,
88 timestamp: u64,
89 exception: Value,
90 exception_message: Value,
91 helper_result: Value,
92}
93
94#[derive(Serialize)]
95#[serde(rename_all = "camelCase")]
96struct EvaluateJSEvent {
97 from: String,
98 #[serde(rename = "type")]
99 type_: String,
100 input: String,
101 result: Value,
102 timestamp: u64,
103 #[serde(rename = "resultID")]
104 result_id: String,
105 exception: Value,
106 exception_message: Value,
107 helper_result: Value,
108}
109
110#[derive(Serialize)]
111struct EvaluateJSAsyncReply {
112 from: String,
113 #[serde(rename = "resultID")]
114 result_id: String,
115}
116
117#[derive(Serialize)]
118struct SetPreferencesReply {
119 from: String,
120 updated: Vec<String>,
121}
122
123#[derive(Serialize)]
124#[serde(rename_all = "camelCase")]
125struct PageErrorWrapper {
126 page_error: PageError,
127}
128
129pub(crate) enum Root {
130 BrowsingContext(String),
131 DedicatedWorker(String),
132}
133
134pub(crate) struct ConsoleActor {
135 pub name: String,
136 pub root: Root,
137 pub cached_events: RefCell<HashMap<UniqueId, Vec<CachedConsoleMessage>>>,
138}
139
140impl ConsoleActor {
141 fn script_chan<'a>(
142 &self,
143 registry: &'a ActorRegistry,
144 ) -> &'a IpcSender<DevtoolScriptControlMsg> {
145 match &self.root {
146 Root::BrowsingContext(bc) => ®istry.find::<BrowsingContextActor>(bc).script_chan,
147 Root::DedicatedWorker(worker) => ®istry.find::<WorkerActor>(worker).script_chan,
148 }
149 }
150
151 fn current_unique_id(&self, registry: &ActorRegistry) -> UniqueId {
152 match &self.root {
153 Root::BrowsingContext(bc) => UniqueId::Pipeline(
154 registry
155 .find::<BrowsingContextActor>(bc)
156 .active_pipeline_id
157 .get(),
158 ),
159 Root::DedicatedWorker(w) => UniqueId::Worker(registry.find::<WorkerActor>(w).worker_id),
160 }
161 }
162
163 fn evaluate_js(
164 &self,
165 registry: &ActorRegistry,
166 msg: &Map<String, Value>,
167 ) -> Result<EvaluateJSReply, ()> {
168 let input = msg.get("text").unwrap().as_str().unwrap().to_owned();
169 let (chan, port) = ipc::channel().unwrap();
170 let pipeline = match self.current_unique_id(registry) {
173 UniqueId::Pipeline(p) => p,
174 UniqueId::Worker(_) => TEST_PIPELINE_ID,
175 };
176 self.script_chan(registry)
177 .send(DevtoolScriptControlMsg::EvaluateJS(
178 pipeline,
179 input.clone(),
180 chan,
181 ))
182 .unwrap();
183
184 let result = match port.recv().map_err(|_| ())? {
186 VoidValue => {
187 let mut m = Map::new();
188 m.insert("type".to_owned(), Value::String("undefined".to_owned()));
189 Value::Object(m)
190 },
191 NullValue => {
192 let mut m = Map::new();
193 m.insert("type".to_owned(), Value::String("null".to_owned()));
194 Value::Object(m)
195 },
196 BooleanValue(val) => Value::Bool(val),
197 NumberValue(val) => {
198 if val.is_nan() {
199 let mut m = Map::new();
200 m.insert("type".to_owned(), Value::String("NaN".to_owned()));
201 Value::Object(m)
202 } else if val.is_infinite() {
203 let mut m = Map::new();
204 if val < 0. {
205 m.insert("type".to_owned(), Value::String("-Infinity".to_owned()));
206 } else {
207 m.insert("type".to_owned(), Value::String("Infinity".to_owned()));
208 }
209 Value::Object(m)
210 } else if val == 0. && val.is_sign_negative() {
211 let mut m = Map::new();
212 m.insert("type".to_owned(), Value::String("-0".to_owned()));
213 Value::Object(m)
214 } else {
215 Value::Number(Number::from_f64(val).unwrap())
216 }
217 },
218 StringValue(s) => Value::String(s),
219 ActorValue { class, uuid } => {
220 let mut m = Map::new();
222 let actor = ObjectActor::register(registry, uuid);
223
224 m.insert("type".to_owned(), Value::String("object".to_owned()));
225 m.insert("class".to_owned(), Value::String(class));
226 m.insert("actor".to_owned(), Value::String(actor));
227 m.insert("extensible".to_owned(), Value::Bool(true));
228 m.insert("frozen".to_owned(), Value::Bool(false));
229 m.insert("sealed".to_owned(), Value::Bool(false));
230 Value::Object(m)
231 },
232 };
233
234 let reply = EvaluateJSReply {
236 from: self.name(),
237 input,
238 result,
239 timestamp: SystemTime::now()
240 .duration_since(UNIX_EPOCH)
241 .unwrap_or_default()
242 .as_millis() as u64,
243 exception: Value::Null,
244 exception_message: Value::Null,
245 helper_result: Value::Null,
246 };
247 std::result::Result::Ok(reply)
248 }
249
250 pub(crate) fn handle_page_error(
251 &self,
252 page_error: PageError,
253 id: UniqueId,
254 registry: &ActorRegistry,
255 stream: &mut TcpStream,
256 ) {
257 self.cached_events
258 .borrow_mut()
259 .entry(id.clone())
260 .or_default()
261 .push(CachedConsoleMessage::PageError(page_error.clone()));
262 if id == self.current_unique_id(registry) {
263 if let Root::BrowsingContext(bc) = &self.root {
264 registry.find::<BrowsingContextActor>(bc).resource_array(
265 PageErrorWrapper { page_error },
266 "error-message".into(),
267 ResourceArrayType::Available,
268 stream,
269 )
270 };
271 }
272 }
273
274 pub(crate) fn handle_console_api(
275 &self,
276 console_message: ConsoleMessage,
277 id: UniqueId,
278 registry: &ActorRegistry,
279 stream: &mut TcpStream,
280 ) {
281 let log_message: ConsoleLog = console_message.into();
282 self.cached_events
283 .borrow_mut()
284 .entry(id.clone())
285 .or_default()
286 .push(CachedConsoleMessage::ConsoleLog(log_message.clone()));
287 if id == self.current_unique_id(registry) {
288 if let Root::BrowsingContext(bc) = &self.root {
289 registry.find::<BrowsingContextActor>(bc).resource_array(
290 log_message,
291 "console-message".into(),
292 ResourceArrayType::Available,
293 stream,
294 )
295 };
296 }
297 }
298}
299
300impl Actor for ConsoleActor {
301 fn name(&self) -> String {
302 self.name.clone()
303 }
304
305 fn handle_message(
306 &self,
307 request: ClientRequest,
308 registry: &ActorRegistry,
309 msg_type: &str,
310 msg: &Map<String, Value>,
311 _id: StreamId,
312 ) -> Result<(), ActorError> {
313 match msg_type {
314 "clearMessagesCache" => {
315 self.cached_events
316 .borrow_mut()
317 .remove(&self.current_unique_id(registry));
318 return Err(ActorError::UnrecognizedPacketType);
320 },
321
322 "getCachedMessages" => {
323 let str_types = msg
324 .get("messageTypes")
325 .unwrap()
326 .as_array()
327 .unwrap()
328 .iter()
329 .map(|json_type| json_type.as_str().unwrap());
330 let mut message_types = CachedConsoleMessageTypes::empty();
331 for str_type in str_types {
332 match str_type {
333 "PageError" => message_types.insert(CachedConsoleMessageTypes::PAGE_ERROR),
334 "ConsoleAPI" => {
335 message_types.insert(CachedConsoleMessageTypes::CONSOLE_API)
336 },
337 s => debug!("unrecognized message type requested: \"{}\"", s),
338 };
339 }
340 let mut messages = vec![];
341 for event in self
342 .cached_events
343 .borrow()
344 .get(&self.current_unique_id(registry))
345 .unwrap_or(&vec![])
346 .iter()
347 {
348 let include = match event {
349 CachedConsoleMessage::PageError(_)
350 if message_types.contains(CachedConsoleMessageTypes::PAGE_ERROR) =>
351 {
352 true
353 },
354 CachedConsoleMessage::ConsoleLog(_)
355 if message_types.contains(CachedConsoleMessageTypes::CONSOLE_API) =>
356 {
357 true
358 },
359 _ => false,
360 };
361 if include {
362 let json_string = event.encode().unwrap();
363 let json = serde_json::from_str::<Value>(&json_string).unwrap();
364 messages.push(json.as_object().unwrap().to_owned())
365 }
366 }
367
368 let msg = GetCachedMessagesReply {
369 from: self.name(),
370 messages,
371 };
372 request.reply_final(&msg)?
373 },
374
375 "startListeners" => {
376 let listeners = msg.get("listeners").unwrap().as_array().unwrap().to_owned();
378 let msg = StartedListenersReply {
379 from: self.name(),
380 native_console_api: true,
381 started_listeners: listeners
382 .into_iter()
383 .map(|s| s.as_str().unwrap().to_owned())
384 .collect(),
385 traits: StartedListenersTraits,
386 };
387 request.reply_final(&msg)?
388 },
389
390 "stopListeners" => {
391 let msg = StopListenersReply {
393 from: self.name(),
394 stopped_listeners: msg
395 .get("listeners")
396 .unwrap()
397 .as_array()
398 .unwrap_or(&vec![])
399 .iter()
400 .map(|listener| listener.as_str().unwrap().to_owned())
401 .collect(),
402 };
403 request.reply_final(&msg)?
404 },
405
406 "autocomplete" => {
409 let msg = AutocompleteReply {
410 from: self.name(),
411 matches: vec![],
412 match_prop: "".to_owned(),
413 };
414 request.reply_final(&msg)?
415 },
416
417 "evaluateJS" => {
418 let msg = self.evaluate_js(registry, msg);
419 request.reply_final(&msg)?
420 },
421
422 "evaluateJSAsync" => {
423 let result_id = Uuid::new_v4().to_string();
424 let early_reply = EvaluateJSAsyncReply {
425 from: self.name(),
426 result_id: result_id.clone(),
427 };
428 let stream = request.reply(&early_reply)?;
431
432 if msg.get("eager").and_then(|v| v.as_bool()).unwrap_or(false) {
433 return Ok(());
436 }
437
438 let reply = self.evaluate_js(registry, msg).unwrap();
439 let msg = EvaluateJSEvent {
440 from: self.name(),
441 type_: "evaluationResult".to_owned(),
442 input: reply.input,
443 result: reply.result,
444 timestamp: reply.timestamp,
445 result_id,
446 exception: reply.exception,
447 exception_message: reply.exception_message,
448 helper_result: reply.helper_result,
449 };
450 stream.write_json_packet(&msg)?
452 },
453
454 "setPreferences" => {
455 let msg = SetPreferencesReply {
456 from: self.name(),
457 updated: vec![],
458 };
459 request.reply_final(&msg)?
460 },
461
462 _ => return Err(ActorError::UnrecognizedPacketType),
463 };
464 Ok(())
465 }
466}