devtools/
lib.rs

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