1#![crate_name = "devtools"]
11#![crate_type = "rlib"]
12#![deny(unsafe_code)]
13
14use std::borrow::ToOwned;
15use std::collections::HashMap;
16use std::io::Read;
17use std::net::{Shutdown, TcpListener, TcpStream};
18use std::sync::{Arc, Mutex};
19use std::thread;
20
21use base::generic_channel;
22use base::id::{BrowsingContextId, PipelineId, WebViewId};
23use crossbeam_channel::{Receiver, Sender, unbounded};
24use devtools_traits::{
25 ChromeToDevtoolsControlMsg, ConsoleMessage, ConsoleMessageBuilder, DevtoolScriptControlMsg,
26 DevtoolsControlMsg, DevtoolsPageInfo, LogLevel, NavigationState, NetworkEvent, PageError,
27 ScriptToDevtoolsControlMsg, SourceInfo, WorkerId,
28};
29use embedder_traits::{AllowOrDeny, EmbedderMsg, EmbedderProxy};
30use ipc_channel::ipc::IpcSender;
31use log::{trace, warn};
32use rand::{RngCore, rng};
33use resource::{ResourceArrayType, ResourceAvailable};
34use rustc_hash::FxHashMap;
35use serde::Serialize;
36
37use crate::actor::{Actor, ActorRegistry};
38use crate::actors::browsing_context::BrowsingContextActor;
39use crate::actors::console::{ConsoleActor, Root};
40use crate::actors::device::DeviceActor;
41use crate::actors::framerate::FramerateActor;
42use crate::actors::network_event::NetworkEventActor;
43use crate::actors::performance::PerformanceActor;
44use crate::actors::preference::PreferenceActor;
45use crate::actors::process::ProcessActor;
46use crate::actors::root::RootActor;
47use crate::actors::source::SourceActor;
48use crate::actors::thread::ThreadActor;
49use crate::actors::watcher::WatcherActor;
50use crate::actors::worker::{WorkerActor, WorkerType};
51use crate::id::IdMap;
52use crate::network_handler::handle_network_event;
53use crate::protocol::JsonPacketStream;
54
55mod actor;
56mod actors {
58 pub mod breakpoint;
59 pub mod browsing_context;
60 pub mod console;
61 pub mod device;
62 pub mod environment;
63 pub mod frame;
64 pub mod framerate;
65 pub mod inspector;
66 pub mod long_string;
67 pub mod memory;
68 pub mod network_event;
69 pub mod object;
70 pub mod pause;
71 pub mod performance;
72 pub mod preference;
73 pub mod process;
74 pub mod reflow;
75 pub mod root;
76 pub mod source;
77 pub mod stylesheets;
78 pub mod tab;
79 pub mod thread;
80 pub mod timeline;
81 pub mod watcher;
82 pub mod worker;
83}
84mod id;
85mod network_handler;
86mod protocol;
87mod resource;
88
89#[derive(Clone, Debug, Eq, Hash, PartialEq)]
90enum UniqueId {
91 Pipeline(PipelineId),
92 Worker(WorkerId),
93}
94
95#[derive(Serialize)]
96pub struct EmptyReplyMsg {
97 pub from: String,
98}
99
100pub fn start_server(port: u16, embedder: EmbedderProxy) -> Sender<DevtoolsControlMsg> {
102 let (sender, receiver) = unbounded();
103 {
104 let sender = sender.clone();
105 thread::Builder::new()
106 .name("Devtools".to_owned())
107 .spawn(move || {
108 if let Some(instance) = DevtoolsInstance::create(sender, receiver, port, embedder) {
109 instance.run()
110 }
111 })
112 .expect("Thread spawning failed");
113 }
114 sender
115}
116
117#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
118pub(crate) struct StreamId(u32);
119
120struct DevtoolsInstance {
121 actors: Arc<Mutex<ActorRegistry>>,
122 id_map: Arc<Mutex<IdMap>>,
123 browsing_contexts: FxHashMap<BrowsingContextId, String>,
124 receiver: Receiver<DevtoolsControlMsg>,
125 pipelines: FxHashMap<PipelineId, BrowsingContextId>,
126 actor_workers: FxHashMap<WorkerId, String>,
127 actor_requests: HashMap<String, String>,
128 connections: FxHashMap<StreamId, TcpStream>,
129 next_resource_id: u64,
130}
131
132impl DevtoolsInstance {
133 fn create(
134 sender: Sender<DevtoolsControlMsg>,
135 receiver: Receiver<DevtoolsControlMsg>,
136 port: u16,
137 embedder: EmbedderProxy,
138 ) -> Option<Self> {
139 let bound = TcpListener::bind(("0.0.0.0", port)).ok().and_then(|l| {
140 l.local_addr()
141 .map(|addr| addr.port())
142 .ok()
143 .map(|port| (l, port))
144 });
145
146 let port = if bound.is_some() { Ok(port) } else { Err(()) };
148 let token = format!("{:X}", rng().next_u32());
149 embedder.send(EmbedderMsg::OnDevtoolsStarted(port, token.clone()));
150
151 let listener = match bound {
152 Some((l, _)) => l,
153 None => {
154 return None;
155 },
156 };
157
158 let mut registry = ActorRegistry::new();
160 let performance = PerformanceActor::new(registry.new_name("performance"));
161 let device = DeviceActor::new(registry.new_name("device"));
162 let preference = PreferenceActor::new(registry.new_name("preference"));
163 let process = ProcessActor::new(registry.new_name("process"));
164 let root = Box::new(RootActor {
165 tabs: vec![],
166 workers: vec![],
167 device: device.name(),
168 performance: performance.name(),
169 preference: preference.name(),
170 process: process.name(),
171 active_tab: None.into(),
172 });
173
174 registry.register(root);
175 registry.register(Box::new(performance));
176 registry.register(Box::new(device));
177 registry.register(Box::new(preference));
178 registry.register(Box::new(process));
179 registry.find::<RootActor>("root");
180
181 let actors = registry.create_shareable();
182
183 let instance = Self {
184 actors,
185 id_map: Arc::new(Mutex::new(IdMap::default())),
186 browsing_contexts: FxHashMap::default(),
187 pipelines: FxHashMap::default(),
188 receiver,
189 actor_requests: HashMap::new(),
190 actor_workers: FxHashMap::default(),
191 connections: FxHashMap::default(),
192 next_resource_id: 1,
193 };
194
195 thread::Builder::new()
196 .name("DevtoolsCliAcceptor".to_owned())
197 .spawn(move || {
198 for stream in listener.incoming() {
200 let mut stream = stream.expect("Can't retrieve stream");
201 if !allow_devtools_client(&mut stream, &embedder, &token) {
202 continue;
203 };
204 sender
206 .send(DevtoolsControlMsg::FromChrome(
207 ChromeToDevtoolsControlMsg::AddClient(stream),
208 ))
209 .unwrap();
210 }
211 })
212 .expect("Thread spawning failed");
213
214 Some(instance)
215 }
216
217 fn run(mut self) {
218 let mut next_id = StreamId(0);
219 while let Ok(msg) = self.receiver.recv() {
220 trace!("{:?}", msg);
221 match msg {
222 DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::AddClient(stream)) => {
223 let actors = self.actors.clone();
224 let id = next_id;
225 next_id = StreamId(id.0 + 1);
226 self.connections.insert(id, stream.try_clone().unwrap());
227
228 for name in self.browsing_contexts.values() {
230 let actors = actors.lock().unwrap();
231 let browsing_context = actors.find::<BrowsingContextActor>(name);
232 let mut streams = browsing_context.streams.borrow_mut();
233 streams.insert(id, stream.try_clone().unwrap());
234 }
235
236 thread::Builder::new()
237 .name("DevtoolsClientHandler".to_owned())
238 .spawn(move || handle_client(actors, stream.try_clone().unwrap(), id))
239 .expect("Thread spawning failed");
240 },
241 DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::FramerateTick(
242 actor_name,
243 tick,
244 )) => self.handle_framerate_tick(actor_name, tick),
245 DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::TitleChanged(
246 pipeline,
247 title,
248 )) => self.handle_title_changed(pipeline, title),
249 DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::NewGlobal(
250 ids,
251 script_sender,
252 pageinfo,
253 )) => self.handle_new_global(ids, script_sender, pageinfo),
254 DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::Navigate(
255 browsing_context,
256 state,
257 )) => self.handle_navigate(browsing_context, state),
258 DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ConsoleAPI(
259 pipeline_id,
260 console_message,
261 worker_id,
262 )) => self.handle_console_message(pipeline_id, worker_id, console_message),
263 DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::CreateSourceActor(
264 script_sender,
265 pipeline_id,
266 source_info,
267 )) => self.handle_create_source_actor(script_sender, pipeline_id, source_info),
268 DevtoolsControlMsg::FromScript(
269 ScriptToDevtoolsControlMsg::UpdateSourceContent(pipeline_id, source_content),
270 ) => self.handle_update_source_content(pipeline_id, source_content),
271 DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ReportPageError(
272 pipeline_id,
273 page_error,
274 )) => self.handle_page_error(pipeline_id, None, page_error),
275 DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ReportCSSError(
276 pipeline_id,
277 css_error,
278 )) => {
279 let mut console_message = ConsoleMessageBuilder::new(
280 LogLevel::Warn,
281 css_error.filename,
282 css_error.line,
283 css_error.column,
284 );
285 console_message.add_argument(css_error.msg.into());
286
287 self.handle_console_message(pipeline_id, None, console_message.finish())
288 },
289 DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::NetworkEvent(
290 request_id,
291 network_event,
292 )) => {
293 let mut connections = Vec::<TcpStream>::new();
295 for stream in self.connections.values() {
296 connections.push(stream.try_clone().unwrap());
297 }
298
299 self.handle_network_event(connections, request_id, network_event);
300 },
301 DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::ServerExitMsg) => break,
302 }
303 }
304
305 for connection in self.connections.values_mut() {
307 let _ = connection.shutdown(Shutdown::Both);
308 }
309 }
310
311 fn handle_framerate_tick(&self, actor_name: String, tick: f64) {
312 let mut actors = self.actors.lock().unwrap();
313 let framerate_actor = actors.find_mut::<FramerateActor>(&actor_name);
314 framerate_actor.add_tick(tick);
315 }
316
317 fn handle_navigate(&self, browsing_context_id: BrowsingContextId, state: NavigationState) {
318 let actor_name = self.browsing_contexts.get(&browsing_context_id).unwrap();
319 let actors = self.actors.lock().unwrap();
320 let actor = actors.find::<BrowsingContextActor>(actor_name);
321 let mut id_map = self.id_map.lock().expect("Mutex poisoned");
322 if let NavigationState::Start(url) = &state {
323 let mut connections = Vec::<TcpStream>::new();
324 for stream in self.connections.values() {
325 connections.push(stream.try_clone().unwrap());
326 }
327 let watcher_actor = actors.find::<WatcherActor>(&actor.watcher);
328 watcher_actor.emit_will_navigate(
329 browsing_context_id,
330 url.clone(),
331 &mut connections,
332 &mut id_map,
333 );
334 };
335 actor.navigate(state, &mut id_map);
336 }
337
338 fn handle_new_global(
342 &mut self,
343 ids: (BrowsingContextId, PipelineId, Option<WorkerId>, WebViewId),
344 script_sender: IpcSender<DevtoolScriptControlMsg>,
345 page_info: DevtoolsPageInfo,
346 ) {
347 let mut actors = self.actors.lock().unwrap();
348
349 let (browsing_context_id, pipeline_id, worker_id, webview_id) = ids;
350 let id_map = &mut self.id_map.lock().expect("Mutex poisoned");
351 let devtools_browser_id = id_map.browser_id(webview_id);
352 let devtools_browsing_context_id = id_map.browsing_context_id(browsing_context_id);
353 let devtools_outer_window_id = id_map.outer_window_id(pipeline_id);
354
355 let console_name = actors.new_name("console");
356
357 let parent_actor = if let Some(id) = worker_id {
358 assert!(self.pipelines.contains_key(&pipeline_id));
359 assert!(self.browsing_contexts.contains_key(&browsing_context_id));
360
361 let thread = ThreadActor::new(actors.new_name("thread"));
362 let thread_name = thread.name();
363 actors.register(Box::new(thread));
364
365 let worker_name = actors.new_name("worker");
366 let worker = WorkerActor {
367 name: worker_name.clone(),
368 console: console_name.clone(),
369 thread: thread_name,
370 worker_id: id,
371 url: page_info.url.clone(),
372 type_: WorkerType::Dedicated,
373 script_chan: script_sender,
374 streams: Default::default(),
375 };
376 let root = actors.find_mut::<RootActor>("root");
377 root.workers.push(worker.name.clone());
378
379 self.actor_workers.insert(id, worker_name.clone());
380 actors.register(Box::new(worker));
381
382 Root::DedicatedWorker(worker_name)
383 } else {
384 self.pipelines.insert(pipeline_id, browsing_context_id);
385 let name = self
386 .browsing_contexts
387 .entry(browsing_context_id)
388 .or_insert_with(|| {
389 let browsing_context_actor = BrowsingContextActor::new(
390 console_name.clone(),
391 devtools_browser_id,
392 devtools_browsing_context_id,
393 page_info,
394 pipeline_id,
395 devtools_outer_window_id,
396 script_sender,
397 &mut actors,
398 );
399 let name = browsing_context_actor.name();
400 actors.register(Box::new(browsing_context_actor));
401 name
402 });
403
404 let browsing_context = actors.find::<BrowsingContextActor>(name);
406 let mut streams = browsing_context.streams.borrow_mut();
407 for (id, stream) in &self.connections {
408 streams.insert(*id, stream.try_clone().unwrap());
409 }
410
411 Root::BrowsingContext(name.clone())
412 };
413
414 let console = ConsoleActor {
415 name: console_name,
416 cached_events: Default::default(),
417 root: parent_actor,
418 };
419
420 actors.register(Box::new(console));
421 }
422
423 fn handle_title_changed(&self, pipeline_id: PipelineId, title: String) {
424 let bc = match self.pipelines.get(&pipeline_id) {
425 Some(bc) => bc,
426 None => return,
427 };
428 let name = match self.browsing_contexts.get(bc) {
429 Some(name) => name,
430 None => return,
431 };
432 let actors = self.actors.lock().unwrap();
433 let browsing_context = actors.find::<BrowsingContextActor>(name);
434 browsing_context.title_changed(pipeline_id, title);
435 }
436
437 fn handle_page_error(
438 &mut self,
439 pipeline_id: PipelineId,
440 worker_id: Option<WorkerId>,
441 page_error: PageError,
442 ) {
443 let console_actor_name = match self.find_console_actor(pipeline_id, worker_id) {
444 Some(name) => name,
445 None => return,
446 };
447 let actors = self.actors.lock().unwrap();
448 let console_actor = actors.find::<ConsoleActor>(&console_actor_name);
449 let id = worker_id.map_or(UniqueId::Pipeline(pipeline_id), UniqueId::Worker);
450 for stream in self.connections.values_mut() {
451 console_actor.handle_page_error(page_error.clone(), id.clone(), &actors, stream);
452 }
453 }
454
455 fn handle_console_message(
456 &mut self,
457 pipeline_id: PipelineId,
458 worker_id: Option<WorkerId>,
459 console_message: ConsoleMessage,
460 ) {
461 let console_actor_name = match self.find_console_actor(pipeline_id, worker_id) {
462 Some(name) => name,
463 None => return,
464 };
465 let actors = self.actors.lock().unwrap();
466 let console_actor = actors.find::<ConsoleActor>(&console_actor_name);
467 let id = worker_id.map_or(UniqueId::Pipeline(pipeline_id), UniqueId::Worker);
468 for stream in self.connections.values_mut() {
469 console_actor.handle_console_api(console_message.clone(), id.clone(), &actors, stream);
470 }
471 }
472
473 fn find_console_actor(
474 &self,
475 pipeline_id: PipelineId,
476 worker_id: Option<WorkerId>,
477 ) -> Option<String> {
478 let actors = self.actors.lock().unwrap();
479 if let Some(worker_id) = worker_id {
480 let actor_name = self.actor_workers.get(&worker_id)?;
481 Some(actors.find::<WorkerActor>(actor_name).console.clone())
482 } else {
483 let id = self.pipelines.get(&pipeline_id)?;
484 let actor_name = self.browsing_contexts.get(id)?;
485 Some(
486 actors
487 .find::<BrowsingContextActor>(actor_name)
488 .console
489 .clone(),
490 )
491 }
492 }
493
494 fn handle_network_event(
495 &mut self,
496 connections: Vec<TcpStream>,
497 request_id: String,
498 network_event: NetworkEvent,
499 ) {
500 let browsing_context_id = match &network_event {
501 NetworkEvent::HttpRequest(req) => req.browsing_context_id,
502 NetworkEvent::HttpRequestUpdate(req) => req.browsing_context_id,
503 NetworkEvent::HttpResponse(resp) => resp.browsing_context_id,
504 };
505
506 let Some(browsing_context_actor_name) = self.browsing_contexts.get(&browsing_context_id)
507 else {
508 return;
509 };
510 let watcher_name = self
511 .actors
512 .lock()
513 .unwrap()
514 .find::<BrowsingContextActor>(browsing_context_actor_name)
515 .watcher
516 .clone();
517
518 let netevent_actor_name = match self.actor_requests.get(&request_id) {
519 Some(name) => name.clone(),
520 None => self.create_network_event_actor(request_id, watcher_name),
521 };
522
523 handle_network_event(
524 Arc::clone(&self.actors),
525 netevent_actor_name,
526 connections,
527 network_event,
528 )
529 }
530
531 fn create_network_event_actor(&mut self, request_id: String, watcher_name: String) -> String {
533 let mut actors = self.actors.lock().unwrap();
534 let resource_id = self.next_resource_id;
535 self.next_resource_id += 1;
536
537 let actor_name = actors.new_name("netevent");
538 let actor = NetworkEventActor::new(actor_name.clone(), resource_id, watcher_name);
539
540 self.actor_requests.insert(request_id, actor_name.clone());
541 actors.register(Box::new(actor));
542
543 actor_name
544 }
545
546 fn handle_create_source_actor(
547 &mut self,
548 script_sender: IpcSender<DevtoolScriptControlMsg>,
549 pipeline_id: PipelineId,
550 source_info: SourceInfo,
551 ) {
552 let mut actors = self.actors.lock().unwrap();
553
554 let source_content = source_info
555 .content
556 .or_else(|| actors.inline_source_content(pipeline_id));
557 let source_actor = SourceActor::new_registered(
558 &mut actors,
559 pipeline_id,
560 source_info.url,
561 source_content,
562 source_info.content_type,
563 source_info.spidermonkey_id,
564 source_info.introduction_type,
565 script_sender,
566 );
567 let source_actor_name = source_actor.name.clone();
568 let source_form = source_actor.source_form();
569
570 if let Some(worker_id) = source_info.worker_id {
571 let Some(worker_actor_name) = self.actor_workers.get(&worker_id) else {
572 return;
573 };
574
575 let thread_actor_name = actors.find::<WorkerActor>(worker_actor_name).thread.clone();
576 let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name);
577
578 thread_actor.source_manager.add_source(&source_actor_name);
579
580 let worker_actor = actors.find::<WorkerActor>(worker_actor_name);
581
582 for stream in self.connections.values_mut() {
583 worker_actor.resource_array(
584 &source_form,
585 "source".into(),
586 ResourceArrayType::Available,
587 stream,
588 );
589 }
590 } else {
591 let Some(browsing_context_id) = self.pipelines.get(&pipeline_id) else {
592 return;
593 };
594 let Some(actor_name) = self.browsing_contexts.get(browsing_context_id) else {
595 return;
596 };
597
598 let thread_actor_name = {
599 let browsing_context = actors.find::<BrowsingContextActor>(actor_name);
600 browsing_context.thread.clone()
601 };
602
603 let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name);
604
605 thread_actor.source_manager.add_source(&source_actor_name);
606
607 let browsing_context = actors.find::<BrowsingContextActor>(actor_name);
609
610 for stream in self.connections.values_mut() {
611 browsing_context.resource_array(
612 &source_form,
613 "source".into(),
614 ResourceArrayType::Available,
615 stream,
616 );
617 }
618 }
619 }
620
621 fn handle_update_source_content(&mut self, pipeline_id: PipelineId, source_content: String) {
622 let mut actors = self.actors.lock().unwrap();
623
624 for actor_name in actors.source_actor_names_for_pipeline(pipeline_id) {
625 let source_actor: &mut SourceActor = actors.find_mut(&actor_name);
626 if source_actor.content.is_none() {
627 source_actor.content = Some(source_content.clone());
628 }
629 }
630
631 actors.set_inline_source_content(pipeline_id, source_content);
634 }
635}
636
637fn allow_devtools_client(stream: &mut TcpStream, embedder: &EmbedderProxy, token: &str) -> bool {
638 let token = format!("25:{{\"auth_token\":\"{}\"}}", token);
640 let mut buf = [0; 28];
641 let timeout = std::time::Duration::from_millis(500);
642 stream.set_read_timeout(Some(timeout)).unwrap();
644 let peek = stream.peek(&mut buf);
645 stream.set_read_timeout(None).unwrap();
646 if let Ok(len) = peek {
647 if len == buf.len() {
648 if let Ok(s) = std::str::from_utf8(&buf) {
649 if s == token {
650 let _ = stream.read_exact(&mut buf);
652 return true;
653 }
654 }
655 }
656 };
657
658 let (request_sender, request_receiver) =
660 generic_channel::channel().expect("Failed to create IPC channel!");
661 embedder.send(EmbedderMsg::RequestDevtoolsConnection(request_sender));
662 request_receiver.recv().unwrap() == AllowOrDeny::Allow
663}
664
665fn handle_client(actors: Arc<Mutex<ActorRegistry>>, mut stream: TcpStream, stream_id: StreamId) {
667 log::info!("Connection established to {}", stream.peer_addr().unwrap());
668 let msg = actors.lock().unwrap().find::<RootActor>("root").encodable();
669 if let Err(error) = stream.write_json_packet(&msg) {
670 warn!("Failed to send initial packet from root actor: {error:?}");
671 return;
672 }
673
674 loop {
675 match stream.read_json_packet() {
676 Ok(Some(json_packet)) => {
677 if let Err(()) = actors.lock().unwrap().handle_message(
678 json_packet.as_object().unwrap(),
679 &mut stream,
680 stream_id,
681 ) {
682 log::error!("Devtools actor stopped responding");
683 let _ = stream.shutdown(Shutdown::Both);
684 break;
685 }
686 },
687 Ok(None) => {
688 log::info!("Devtools connection closed");
689 break;
690 },
691 Err(err_msg) => {
692 log::error!("Failed to read message from devtools client: {}", err_msg);
693 break;
694 },
695 }
696 }
697
698 actors.lock().unwrap().cleanup(stream_id);
699}