Skip to main content

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::{Ipv4Addr, Shutdown, SocketAddr, TcpListener, TcpStream};
18use std::str::FromStr;
19use std::sync::{Arc, Mutex};
20use std::thread;
21
22use crossbeam_channel::{Receiver, Sender, unbounded};
23use devtools_traits::{
24    ChromeToDevtoolsControlMsg, ConsoleLogLevel, ConsoleMessage, ConsoleMessageFields,
25    DebuggerValue, DevtoolScriptControlMsg, DevtoolsControlMsg, DevtoolsPageInfo, DomMutation,
26    EnvironmentInfo, FrameInfo, FrameOffset, NavigationState, NetworkEvent, PauseReason,
27    ScriptToDevtoolsControlMsg, SourceInfo, WorkerId, get_time_stamp,
28};
29use embedder_traits::{AllowOrDeny, EmbedderMsg, EmbedderProxy};
30use log::{trace, warn};
31use malloc_size_of::MallocSizeOf;
32use malloc_size_of_derive::MallocSizeOf;
33use profile_traits::path;
34use rand::{Rng, rng};
35use resource::{ResourceArrayType, ResourceAvailable};
36use rustc_hash::FxHashMap;
37use serde::Serialize;
38use serde_json::{Map, Number, Value};
39use servo_base::generic_channel::{self, GenericSender};
40use servo_base::id::{BrowsingContextId, PipelineId, WebViewId};
41use servo_config::pref;
42
43use crate::actor::{Actor, ActorEncode, ActorError, ActorRegistry, new_actor_name};
44use crate::actors::browsing_context::BrowsingContextActor;
45use crate::actors::console::{ConsoleActor, ConsoleResource, DevtoolsConsoleMessage, Root};
46use crate::actors::environment::EnvironmentActor;
47use crate::actors::frame::FrameActor;
48use crate::actors::framerate::FramerateActor;
49use crate::actors::inspector::InspectorActor;
50use crate::actors::inspector::walker::WalkerActor;
51use crate::actors::network_event::NetworkEventActor;
52use crate::actors::object::ObjectActor;
53use crate::actors::pause::PauseActor;
54use crate::actors::root::RootActor;
55use crate::actors::source::SourceActor;
56use crate::actors::thread::{ThreadActor, ThreadInterruptedReply};
57use crate::actors::watcher::WatcherActor;
58use crate::actors::worker::{WorkerTargetActor, WorkerType};
59use crate::id::IdMap;
60use crate::network_handler::handle_network_event;
61use crate::protocol::{DevtoolsConnection, JsonPacketStream};
62
63mod actor;
64/// <https://searchfox.org/mozilla-central/source/devtools/server/actors>
65mod actors {
66    pub mod blackboxing;
67    pub mod breakpoint;
68    pub mod browsing_context;
69    pub mod console;
70    pub mod device;
71    pub mod environment;
72    pub mod frame;
73    pub mod framerate;
74    pub mod inspector;
75    pub mod long_string;
76    pub mod memory;
77    pub mod network_event;
78    pub mod object;
79    pub mod pause;
80    pub mod performance;
81    pub mod preference;
82    pub mod process;
83    pub mod property_iterator;
84    pub mod reflow;
85    pub mod root;
86    pub mod source;
87    pub mod stylesheets;
88    pub mod symbol_iterator;
89    pub mod tab;
90    pub mod thread;
91    pub mod timeline;
92    pub mod watcher;
93    pub mod worker;
94}
95mod id;
96mod network_handler;
97mod protocol;
98mod resource;
99use profile_traits::mem::{
100    ProcessReports, ProfilerChan, Report, ReportKind, perform_memory_report,
101};
102
103#[derive(Clone, Debug, Eq, Hash, PartialEq, MallocSizeOf)]
104enum UniqueId {
105    Pipeline(PipelineId),
106    Worker(WorkerId),
107}
108
109#[derive(Serialize)]
110pub(crate) struct EmptyReplyMsg {
111    pub from: String,
112}
113
114#[derive(Serialize)]
115pub(crate) struct ActorMsg {
116    pub actor: String,
117}
118
119/// Spin up a devtools server that listens for connections on the specified port.
120pub fn start_server(
121    embedder: EmbedderProxy,
122    mem_profiler_chan: ProfilerChan,
123) -> Sender<DevtoolsControlMsg> {
124    let (sender, receiver) = unbounded();
125    {
126        let sender = sender.clone();
127        let sender2 = sender.clone();
128        thread::Builder::new()
129            .name("Devtools".to_owned())
130            .spawn(move || {
131                mem_profiler_chan.run_with_memory_reporting(
132                    || {
133                        if let Some(instance) = DevtoolsInstance::create(sender, receiver, embedder)
134                        {
135                            instance.run()
136                        }
137                    },
138                    String::from("devtools-reporter"),
139                    sender2,
140                    |chan| {
141                        DevtoolsControlMsg::FromChrome(
142                            ChromeToDevtoolsControlMsg::CollectMemoryReport(chan),
143                        )
144                    },
145                )
146            })
147            .expect("Thread spawning failed");
148    }
149    sender
150}
151
152#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, MallocSizeOf)]
153pub(crate) struct StreamId(u32);
154
155#[derive(MallocSizeOf)]
156struct DevtoolsInstance {
157    #[conditional_malloc_size_of]
158    registry: Arc<ActorRegistry>,
159    #[conditional_malloc_size_of]
160    id_map: Arc<Mutex<IdMap>>,
161    browsing_contexts: FxHashMap<BrowsingContextId, String>,
162    /// This is handed to clients so they can notify the devtools instance when
163    /// their connection closes.
164    sender: Sender<DevtoolsControlMsg>,
165    receiver: Receiver<DevtoolsControlMsg>,
166    pipelines: FxHashMap<PipelineId, BrowsingContextId>,
167    actor_workers: FxHashMap<WorkerId, String>,
168    actor_requests: HashMap<String, String>,
169    /// A map of active TCP connections to devtools clients.
170    ///
171    /// Client threads remove their connection from here once they exit.
172    #[conditional_malloc_size_of]
173    connections: Arc<Mutex<FxHashMap<StreamId, DevtoolsConnection>>>,
174    next_resource_id: u64,
175}
176
177impl DevtoolsInstance {
178    fn create(
179        sender: Sender<DevtoolsControlMsg>,
180        receiver: Receiver<DevtoolsControlMsg>,
181        embedder: EmbedderProxy,
182    ) -> Option<Self> {
183        let address = if pref!(devtools_server_listen_address).is_empty() {
184            SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), 7000)
185        } else if let Ok(addr) = SocketAddr::from_str(&pref!(devtools_server_listen_address)) {
186            addr
187        } else if let Ok(port) = pref!(devtools_server_listen_address).parse() {
188            SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), port)
189        } else {
190            SocketAddr::new(Ipv4Addr::new(127, 0, 0, 1).into(), 7000)
191        };
192        println!("Binding devtools to {address}");
193
194        let bound = TcpListener::bind(address).ok().and_then(|l| {
195            l.local_addr()
196                .map(|addr| addr.port())
197                .ok()
198                .map(|port| (l, port))
199        });
200
201        // A token shared with the embedder to bypass permission prompt.
202        let port = if bound.is_some() {
203            Ok(address.port())
204        } else {
205            Err(())
206        };
207        let token = format!("{:X}", rng().next_u32());
208        embedder.send(EmbedderMsg::OnDevtoolsStarted(port, token.clone()));
209
210        let listener = match bound {
211            Some((l, _)) => l,
212            None => {
213                return None;
214            },
215        };
216
217        // Create basic actors
218        let mut registry = ActorRegistry::default();
219        RootActor::register(&mut registry);
220
221        let instance = Self {
222            registry: Arc::new(registry),
223            id_map: Arc::new(Mutex::new(IdMap::default())),
224            browsing_contexts: FxHashMap::default(),
225            pipelines: FxHashMap::default(),
226            sender: sender.clone(),
227            receiver,
228            actor_requests: HashMap::new(),
229            actor_workers: FxHashMap::default(),
230            connections: Default::default(),
231            next_resource_id: 1,
232        };
233
234        thread::Builder::new()
235            .name("DevtoolsCliAcceptor".to_owned())
236            .spawn(move || {
237                // accept connections and process them, spawning a new thread for each one
238                for stream in listener.incoming() {
239                    let mut stream = stream.expect("Can't retrieve stream");
240                    if !allow_devtools_client(&mut stream, &embedder, &token) {
241                        continue;
242                    };
243                    // connection succeeded and accepted
244                    sender
245                        .send(DevtoolsControlMsg::FromChrome(
246                            ChromeToDevtoolsControlMsg::AddClient(stream),
247                        ))
248                        .unwrap();
249                }
250            })
251            .expect("Thread spawning failed");
252
253        Some(instance)
254    }
255
256    fn run(mut self) {
257        let mut next_id = StreamId(0);
258        while let Ok(msg) = self.receiver.recv() {
259            trace!("{:?}", msg);
260            match msg {
261                DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::AddClient(stream)) => {
262                    let id = next_id;
263                    next_id = StreamId(id.0 + 1);
264
265                    {
266                        let connections = self.connections.lock().unwrap();
267                        if connections.is_empty() {
268                            // We used to have no connection, now we have one.
269                            // Therefore, we need updates from script threads.
270                            for browsing_context_name in self.browsing_contexts.values() {
271                                let browsing_context_actor = self
272                                    .registry
273                                    .find::<BrowsingContextActor>(browsing_context_name);
274                                browsing_context_actor.instruct_script_to_send_live_updates(true);
275                            }
276                        }
277                    }
278
279                    let connection: DevtoolsConnection = stream.into();
280                    let registry = self.registry.clone();
281                    let connections = self.connections.clone();
282                    let sender_clone = self.sender.clone();
283                    thread::Builder::new()
284                        .name("DevtoolsClientHandler".to_owned())
285                        .spawn(move || {
286                            handle_client(registry, connection, id, connections, sender_clone)
287                        })
288                        .expect("Thread spawning failed");
289                },
290                DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::FramerateTick(
291                    actor_name,
292                    tick,
293                )) => self.handle_framerate_tick(actor_name, tick),
294                DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::TitleChanged(
295                    pipeline,
296                    title,
297                )) => self.handle_title_changed(pipeline, title),
298                DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::NewGlobal(
299                    ids,
300                    script_sender,
301                    pageinfo,
302                )) => self.handle_new_global(ids, script_sender, pageinfo),
303                DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::Navigate(
304                    browsing_context,
305                    state,
306                )) => self.handle_navigate(browsing_context, state),
307                DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ConsoleAPI(
308                    pipeline_id,
309                    console_message,
310                    worker_id,
311                )) => {
312                    let console_message =
313                        DevtoolsConsoleMessage::new(console_message, &self.registry);
314                    self.handle_console_resource(
315                        pipeline_id,
316                        worker_id,
317                        ConsoleResource::ConsoleMessage(console_message),
318                    );
319                },
320                DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ClearConsole(
321                    pipeline_id,
322                    worker_id,
323                )) => self.handle_clear_console(pipeline_id, worker_id),
324                DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::CreateSourceActor(
325                    script_sender,
326                    pipeline_id,
327                    source_info,
328                )) => self.handle_create_source_actor(script_sender, pipeline_id, source_info),
329                DevtoolsControlMsg::FromScript(
330                    ScriptToDevtoolsControlMsg::UpdateSourceContent(pipeline_id, source_content),
331                ) => self.handle_update_source_content(pipeline_id, source_content),
332                DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ReportPageError(
333                    pipeline_id,
334                    page_error,
335                )) => self.handle_console_resource(
336                    pipeline_id,
337                    None,
338                    ConsoleResource::PageError(page_error.into()),
339                ),
340                DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ReportCSSError(
341                    pipeline_id,
342                    css_error,
343                )) => {
344                    let console_message = ConsoleMessage {
345                        fields: ConsoleMessageFields {
346                            level: ConsoleLogLevel::Warn,
347                            filename: css_error.filename,
348                            line_number: css_error.line,
349                            column_number: css_error.column,
350                            time_stamp: get_time_stamp(),
351                        },
352                        arguments: vec![DebuggerValue::StringValue(css_error.msg)],
353                        stacktrace: None,
354                    };
355                    let console_message =
356                        DevtoolsConsoleMessage::new(console_message, &self.registry);
357
358                    self.handle_console_resource(
359                        pipeline_id,
360                        None,
361                        ConsoleResource::ConsoleMessage(console_message),
362                    )
363                },
364                DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::DomMutation(
365                    pipeline_id,
366                    dom_mutation,
367                )) => {
368                    self.handle_dom_mutation(pipeline_id, dom_mutation).unwrap();
369                },
370                DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::DebuggerPause(
371                    pipeline_id,
372                    frame_offset,
373                    pause_reason,
374                )) => self.handle_debugger_pause(pipeline_id, frame_offset, pause_reason),
375                DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::CreateFrameActor(
376                    result_sender,
377                    pipeline_id,
378                    frame_info,
379                )) => self.handle_create_frame_actor(result_sender, pipeline_id, frame_info),
380                DevtoolsControlMsg::FromScript(
381                    ScriptToDevtoolsControlMsg::CreateEnvironmentActor(
382                        result_sender,
383                        environment,
384                        parent,
385                        actor,
386                    ),
387                ) => {
388                    self.handle_create_environment_actor(result_sender, environment, parent, actor)
389                },
390                DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::NetworkEvent(
391                    request_id,
392                    network_event,
393                )) => {
394                    // copy the connections vector
395                    // FIXME: Why do we need to do this? Cloning the connections here is
396                    // almost certainly wrong and means that they might shut down without
397                    // us noticing.
398                    let mut connections = Vec::<DevtoolsConnection>::new();
399                    for connection in self.connections.lock().unwrap().values() {
400                        connections.push(connection.clone());
401                    }
402                    self.handle_network_event(connections, request_id, network_event);
403                },
404                DevtoolsControlMsg::FromChrome(ChromeToDevtoolsControlMsg::ServerExitMsg) => break,
405                DevtoolsControlMsg::FromChrome(
406                    ChromeToDevtoolsControlMsg::CollectMemoryReport(chan),
407                ) => {
408                    perform_memory_report(|ops| {
409                        let reports = vec![Report {
410                            path: path!["devtools"],
411                            kind: ReportKind::ExplicitSystemHeapSize,
412                            size: self.size_of(ops),
413                        }];
414                        chan.send(ProcessReports::new(reports));
415                    });
416                },
417                DevtoolsControlMsg::ClientExited => {
418                    if self.connections.lock().unwrap().is_empty() {
419                        // Tell every browsing context to stop sending us updates, because we have nowhere to
420                        // send them to.
421                        for browsing_context_name in self.browsing_contexts.values() {
422                            let browsing_context_actor = self
423                                .registry
424                                .find::<BrowsingContextActor>(browsing_context_name);
425                            browsing_context_actor.instruct_script_to_send_live_updates(false);
426                        }
427                    }
428                },
429            }
430        }
431
432        // Shut down all active connections
433        let mut connections = self.connections.lock().unwrap();
434        for connection in connections.values_mut() {
435            let _ = connection.shutdown(Shutdown::Both);
436        }
437        connections.clear();
438    }
439
440    fn handle_framerate_tick(&self, actor_name: String, tick: f64) {
441        let framerate_actor = self.registry.find::<FramerateActor>(&actor_name);
442        framerate_actor.add_tick(tick);
443    }
444
445    fn handle_navigate(&mut self, browsing_context_id: BrowsingContextId, state: NavigationState) {
446        let Some(browsing_context_name) = self.browsing_contexts.get(&browsing_context_id) else {
447            return;
448        };
449        let browsing_context_name = browsing_context_name.clone();
450        let browsing_context_actor = self
451            .registry
452            .find::<BrowsingContextActor>(&browsing_context_name);
453        let watcher_actor = self
454            .registry
455            .find::<WatcherActor>(&browsing_context_actor.watcher_name);
456        let mut id_map = self.id_map.lock().unwrap();
457        let mut connections = self.connections.lock().unwrap();
458
459        match &state {
460            NavigationState::Start(url) => {
461                watcher_actor.emit_will_navigate(
462                    browsing_context_id,
463                    url.clone(),
464                    &mut connections.values_mut(),
465                    &mut id_map,
466                );
467            },
468            NavigationState::Stop(pipeline_id, page_info) => {
469                watcher_actor.emit_target_available_or_destroyed(
470                    &browsing_context_actor,
471                    &self.registry,
472                    connections.values_mut(),
473                    false,
474                );
475
476                let outer_window_id = id_map.outer_window_id(*pipeline_id);
477                browsing_context_actor.update_pipeline(
478                    *pipeline_id,
479                    outer_window_id,
480                    page_info.clone(),
481                );
482
483                watcher_actor.emit_target_available_or_destroyed(
484                    &browsing_context_actor,
485                    &self.registry,
486                    connections.values_mut(),
487                    true,
488                );
489
490                // TODO: Correctly destroy targets, we probably need to create new browsing context actors too.
491            },
492        }
493    }
494
495    // We need separate actor representations for each script global that exists;
496    // clients can theoretically connect to multiple globals simultaneously.
497    // TODO: move this into the root or target modules?
498    fn handle_new_global(
499        &mut self,
500        ids: (BrowsingContextId, PipelineId, Option<WorkerId>, WebViewId),
501        script_sender: GenericSender<DevtoolScriptControlMsg>,
502        page_info: DevtoolsPageInfo,
503    ) {
504        let (browsing_context_id, pipeline_id, worker_id, webview_id) = ids;
505        let id_map = &mut self.id_map.lock().unwrap();
506        let devtools_browser_id = id_map.browser_id(webview_id);
507        let devtools_browsing_context_id = id_map.browsing_context_id(browsing_context_id);
508        let devtools_outer_window_id = id_map.outer_window_id(pipeline_id);
509
510        let console_name = new_actor_name::<ConsoleActor>();
511
512        let parent_actor = if let Some(id) = worker_id {
513            let thread_actor = ThreadActor::register(&self.registry, script_sender.clone(), None);
514
515            let worker_type = if page_info.is_service_worker {
516                WorkerType::Service
517            } else {
518                WorkerType::Dedicated
519            };
520            let worker_actor = WorkerTargetActor::register(
521                &self.registry,
522                console_name.clone(),
523                thread_actor.name().into(),
524                id,
525                page_info.url,
526                worker_type,
527                script_sender,
528            );
529            let root_actor = self.registry.find::<RootActor>("root");
530            if page_info.is_service_worker {
531                root_actor
532                    .service_workers
533                    .borrow_mut()
534                    .push(worker_actor.name().into());
535            } else {
536                root_actor
537                    .workers
538                    .borrow_mut()
539                    .push(worker_actor.name().into());
540            }
541
542            self.actor_workers.insert(id, worker_actor.name().into());
543
544            Root::DedicatedWorker(worker_actor.name().into())
545        } else {
546            self.pipelines.insert(pipeline_id, browsing_context_id);
547            let browsing_context_name = self
548                .browsing_contexts
549                .entry(browsing_context_id)
550                .or_insert_with(|| {
551                    BrowsingContextActor::register(
552                        &self.registry,
553                        console_name.clone(),
554                        devtools_browser_id,
555                        devtools_browsing_context_id,
556                        page_info,
557                        pipeline_id,
558                        devtools_outer_window_id,
559                        script_sender.clone(),
560                    )
561                    .name()
562                    .into()
563                });
564            let browsing_context_actor = self
565                .registry
566                .find::<BrowsingContextActor>(browsing_context_name);
567            browsing_context_actor.handle_new_global(pipeline_id, script_sender);
568            Root::BrowsingContext(browsing_context_name.clone())
569        };
570
571        ConsoleActor::register(&self.registry, console_name, parent_actor);
572    }
573
574    fn handle_title_changed(&self, pipeline_id: PipelineId, title: String) {
575        let browsing_context_id = match self.pipelines.get(&pipeline_id) {
576            Some(bc) => bc,
577            None => return,
578        };
579        let browsing_context_name = match self.browsing_contexts.get(browsing_context_id) {
580            Some(name) => name,
581            None => return,
582        };
583        let browsing_context_actor = self
584            .registry
585            .find::<BrowsingContextActor>(browsing_context_name);
586        browsing_context_actor.title_changed(pipeline_id, title);
587    }
588
589    fn handle_console_resource(
590        &mut self,
591        pipeline_id: PipelineId,
592        worker_id: Option<WorkerId>,
593        resource: ConsoleResource,
594    ) {
595        let console_actor_name = match self.find_console_actor(pipeline_id, worker_id) {
596            Some(name) => name,
597            None => return,
598        };
599        let console_actor = self.registry.find::<ConsoleActor>(&console_actor_name);
600        let id = worker_id.map_or(UniqueId::Pipeline(pipeline_id), UniqueId::Worker);
601
602        for connection in self.connections.lock().unwrap().values_mut() {
603            console_actor.handle_console_resource(
604                resource.clone(),
605                id.clone(),
606                &self.registry,
607                connection,
608            );
609        }
610    }
611
612    fn handle_dom_mutation(
613        &mut self,
614        pipeline_id: PipelineId,
615        dom_mutation: DomMutation,
616    ) -> Result<(), ActorError> {
617        let Some(browsing_context_id) = self.pipelines.get(&pipeline_id) else {
618            log::warn!("Devtools received notification for unknown pipeline {pipeline_id}");
619            return Err(ActorError::Internal);
620        };
621        let Some(browsing_context_name) = self.browsing_contexts.get(browsing_context_id) else {
622            return Err(ActorError::Internal);
623        };
624        let browsing_context_actor = self
625            .registry
626            .find::<BrowsingContextActor>(browsing_context_name);
627        let inspector_actor = self
628            .registry
629            .find::<InspectorActor>(&browsing_context_actor.inspector_name);
630        let walker_actor = self
631            .registry
632            .find::<WalkerActor>(&inspector_actor.walker_name);
633
634        for connection in self.connections.lock().unwrap().values_mut() {
635            walker_actor.handle_dom_mutation(dom_mutation.clone(), connection)?;
636        }
637
638        Ok(())
639    }
640
641    fn handle_clear_console(&mut self, pipeline_id: PipelineId, worker_id: Option<WorkerId>) {
642        let console_actor_name = match self.find_console_actor(pipeline_id, worker_id) {
643            Some(name) => name,
644            None => return,
645        };
646        let console_actor = self.registry.find::<ConsoleActor>(&console_actor_name);
647        let id = worker_id.map_or(UniqueId::Pipeline(pipeline_id), UniqueId::Worker);
648
649        for stream in self.connections.lock().unwrap().values_mut() {
650            console_actor.send_clear_message(id.clone(), &self.registry, stream);
651        }
652    }
653
654    fn find_console_actor(
655        &self,
656        pipeline_id: PipelineId,
657        worker_id: Option<WorkerId>,
658    ) -> Option<String> {
659        if let Some(worker_id) = worker_id {
660            let worker_name = self.actor_workers.get(&worker_id)?;
661            Some(
662                self.registry
663                    .find::<WorkerTargetActor>(worker_name)
664                    .console_name
665                    .clone(),
666            )
667        } else {
668            let browsing_context_id = self.pipelines.get(&pipeline_id)?;
669            let browsing_context_name = self.browsing_contexts.get(browsing_context_id)?;
670            Some(
671                self.registry
672                    .find::<BrowsingContextActor>(browsing_context_name)
673                    .console_name
674                    .clone(),
675            )
676        }
677    }
678
679    fn handle_network_event(
680        &mut self,
681        connections: Vec<DevtoolsConnection>,
682        request_id: String,
683        network_event: NetworkEvent,
684    ) {
685        let browsing_context_id = match &network_event {
686            NetworkEvent::HttpRequest(req) => req.browsing_context_id,
687            NetworkEvent::HttpRequestUpdate(req) => req.browsing_context_id,
688            NetworkEvent::HttpResponse(resp) => resp.browsing_context_id,
689            NetworkEvent::SecurityInfo(update) => update.browsing_context_id,
690        };
691
692        let Some(browsing_context_name) = self.browsing_contexts.get(&browsing_context_id) else {
693            return;
694        };
695
696        let network_event_name = match self.actor_requests.get(&request_id) {
697            Some(name) => name.clone(),
698            None => self.create_network_event_actor(request_id, browsing_context_name.clone()),
699        };
700
701        handle_network_event(
702            Arc::clone(&self.registry),
703            network_event_name,
704            connections,
705            network_event,
706        )
707    }
708
709    /// Create a new NetworkEventActor for a given request ID and browsing context name.
710    fn create_network_event_actor(
711        &mut self,
712        request_id: String,
713        browsing_context_name: String,
714    ) -> String {
715        let resource_id = self.next_resource_id;
716        self.next_resource_id += 1;
717
718        let network_event_actor =
719            NetworkEventActor::register(&self.registry, resource_id, browsing_context_name);
720
721        self.actor_requests
722            .insert(request_id, network_event_actor.name().into());
723
724        network_event_actor.name().into()
725    }
726
727    fn handle_create_source_actor(
728        &mut self,
729        script_sender: GenericSender<DevtoolScriptControlMsg>,
730        pipeline_id: PipelineId,
731        source_info: SourceInfo,
732    ) {
733        let source_content = source_info
734            .content
735            .or_else(|| self.registry.inline_source_content(pipeline_id));
736        let source_actor = SourceActor::register(
737            &self.registry,
738            pipeline_id,
739            source_info.url,
740            source_content,
741            source_info.content_type,
742            source_info.spidermonkey_id,
743            source_info.introduction_type,
744            script_sender,
745        );
746        let source_form = self
747            .registry
748            .find::<SourceActor>(source_actor.name())
749            .source_form();
750
751        if let Some(worker_id) = source_info.worker_id {
752            let Some(worker_name) = self.actor_workers.get(&worker_id) else {
753                return;
754            };
755
756            let thread_actor_name = self
757                .registry
758                .find::<WorkerTargetActor>(worker_name)
759                .thread_name
760                .clone();
761            let thread_actor = self.registry.find::<ThreadActor>(&thread_actor_name);
762
763            thread_actor.source_manager.add_source(source_actor.name());
764
765            let worker_actor = self.registry.find::<WorkerTargetActor>(worker_name);
766
767            for stream in self.connections.lock().unwrap().values_mut() {
768                worker_actor.resource_array(
769                    &source_form,
770                    "source".into(),
771                    ResourceArrayType::Available,
772                    stream,
773                );
774            }
775        } else {
776            let Some(browsing_context_id) = self.pipelines.get(&pipeline_id) else {
777                return;
778            };
779            let Some(browsing_context_name) = self.browsing_contexts.get(browsing_context_id)
780            else {
781                return;
782            };
783
784            // Notify browsing context about the new source
785            let browsing_context_actor = self
786                .registry
787                .find::<BrowsingContextActor>(browsing_context_name);
788
789            let thread_actor_name = browsing_context_actor.thread_name.clone();
790            let thread_actor = self.registry.find::<ThreadActor>(&thread_actor_name);
791            thread_actor.source_manager.add_source(source_actor.name());
792
793            for stream in self.connections.lock().unwrap().values_mut() {
794                browsing_context_actor.resource_array(
795                    &source_form,
796                    "source".into(),
797                    ResourceArrayType::Available,
798                    stream,
799                );
800            }
801        }
802    }
803
804    fn handle_update_source_content(&mut self, pipeline_id: PipelineId, source_content: String) {
805        for source_name in self.registry.source_actor_names_for_pipeline(pipeline_id) {
806            let source_actor = self.registry.find::<SourceActor>(&source_name);
807            let mut content = source_actor.content.borrow_mut();
808            if content.is_none() {
809                *content = Some(source_content.clone());
810            }
811        }
812
813        // Store the source content separately for any future source actors that get created *after* we finish parsing
814        // the HTML. For example, adding an `import` to an inline module script can delay it until after parsing.
815        self.registry
816            .set_inline_source_content(pipeline_id, source_content);
817    }
818
819    fn handle_debugger_pause(
820        &mut self,
821        pipeline_id: PipelineId,
822        frame_offset: FrameOffset,
823        pause_reason: PauseReason,
824    ) {
825        let Some(browsing_context_name) = self
826            .pipelines
827            .get(&pipeline_id)
828            .and_then(|id| self.browsing_contexts.get(id))
829        else {
830            return;
831        };
832
833        let browsing_context_actor = self
834            .registry
835            .find::<BrowsingContextActor>(browsing_context_name);
836        let thread_actor = self
837            .registry
838            .find::<ThreadActor>(&browsing_context_actor.thread_name);
839
840        let pause_name = PauseActor::register(&self.registry).name().into();
841
842        let frame_actor = self.registry.find::<FrameActor>(&frame_offset.actor);
843        frame_actor.set_offset(frame_offset.column, frame_offset.line);
844
845        let msg = ThreadInterruptedReply {
846            from: thread_actor.name().into(),
847            type_: "paused".to_owned(),
848            actor: pause_name,
849            frame: frame_actor.encode(&self.registry),
850            why: pause_reason,
851        };
852
853        for stream in self.connections.lock().unwrap().values_mut() {
854            let _ = stream.write_json_packet(&msg);
855        }
856    }
857
858    fn handle_create_frame_actor(
859        &mut self,
860        result_sender: GenericSender<String>,
861        pipeline_id: PipelineId,
862        frame: FrameInfo,
863    ) {
864        let Some(browsing_context_id) = self.pipelines.get(&pipeline_id) else {
865            return;
866        };
867        let Some(browsing_context_name) = self.browsing_contexts.get(browsing_context_id) else {
868            return;
869        };
870        let browsing_context_actor = self
871            .registry
872            .find::<BrowsingContextActor>(browsing_context_name);
873        let thread_actor = self
874            .registry
875            .find::<ThreadActor>(&browsing_context_actor.thread_name);
876
877        let source_name = match thread_actor
878            .source_manager
879            .find_source(&self.registry, &frame.url)
880        {
881            Some(source_actor) => source_actor.name().into(),
882            None => {
883                warn!("No source actor found for URL: {}", frame.url);
884                return;
885            },
886        };
887
888        let frame_actor = FrameActor::register(&self.registry, source_name, frame);
889
890        let _ = result_sender.send(frame_actor.name().into());
891    }
892
893    fn handle_create_environment_actor(
894        &mut self,
895        result_sender: GenericSender<String>,
896        environment_info: EnvironmentInfo,
897        parent: Option<String>,
898        actor: Option<String>,
899    ) {
900        let environment_name =
901            EnvironmentActor::register_or_update(&self.registry, environment_info, parent, actor);
902        let _ = result_sender.send(environment_name);
903    }
904}
905
906fn allow_devtools_client(stream: &mut TcpStream, embedder: &EmbedderProxy, token: &str) -> bool {
907    // By-pass prompt if we receive a valid token.
908    let token = format!("25:{{\"auth_token\":\"{}\"}}", token);
909    let mut buf = [0; 28];
910    let timeout = std::time::Duration::from_millis(500);
911    // This will read but not consume the bytes from the stream.
912    stream.set_read_timeout(Some(timeout)).unwrap();
913    let peek = stream.peek(&mut buf);
914    stream.set_read_timeout(None).unwrap();
915    if let Ok(len) = peek &&
916        len == buf.len() &&
917        let Ok(s) = std::str::from_utf8(&buf) &&
918        s == token
919    {
920        // Consume the message as it was relevant to us.
921        let _ = stream.read_exact(&mut buf);
922        return true;
923    };
924
925    // No token found. Prompt user
926    let (request_sender, request_receiver) =
927        generic_channel::channel().expect("Failed to create IPC channel!");
928    embedder.send(EmbedderMsg::RequestDevtoolsConnection(request_sender));
929    request_receiver.recv().unwrap() == AllowOrDeny::Allow
930}
931
932/// Process the input from a single devtools client until EOF.
933fn handle_client(
934    registry: Arc<ActorRegistry>,
935    mut stream: DevtoolsConnection,
936    stream_id: StreamId,
937    connections: Arc<Mutex<FxHashMap<StreamId, DevtoolsConnection>>>,
938    sender: Sender<DevtoolsControlMsg>,
939) {
940    connections
941        .lock()
942        .unwrap()
943        .insert(stream_id, stream.clone());
944
945    log::info!("Connection established to {}", stream.peer_addr().unwrap());
946    let msg = registry.encode::<RootActor, _>("root");
947    if let Err(error) = stream.write_json_packet(&msg) {
948        warn!("Failed to send initial packet from root actor: {error:?}");
949        return;
950    }
951
952    loop {
953        match stream.read_json_packet() {
954            Ok(Some(json_packet)) => {
955                if let Err(()) = registry.handle_message(
956                    json_packet.as_object().unwrap(),
957                    &mut stream,
958                    stream_id,
959                ) {
960                    log::error!("Devtools actor stopped responding");
961                    let _ = stream.shutdown(Shutdown::Both);
962                    break;
963                }
964            },
965            Ok(None) => {
966                log::info!("Devtools connection closed");
967                break;
968            },
969            Err(err_msg) => {
970                log::error!("Failed to read message from devtools client: {}", err_msg);
971                break;
972            },
973        }
974    }
975
976    connections.lock().unwrap().remove(&stream_id);
977    let _ = sender.send(DevtoolsControlMsg::ClientExited);
978
979    registry.cleanup(stream_id);
980}
981
982/// <https://searchfox.org/mozilla-central/source/devtools/server/actors/object/utils.js#148>
983pub(crate) fn debugger_value_to_json(registry: &ActorRegistry, value: DebuggerValue) -> Value {
984    let mut v = Map::new();
985    match value {
986        DebuggerValue::VoidValue => {
987            v.insert("type".to_owned(), Value::String("undefined".to_owned()));
988            Value::Object(v)
989        },
990        DebuggerValue::NullValue(uninitialized) => {
991            v.insert("type".to_owned(), Value::String("null".to_owned()));
992            if uninitialized {
993                v.insert("uninitialized".to_owned(), Value::Bool(true));
994            }
995            Value::Object(v)
996        },
997        DebuggerValue::BooleanValue(boolean) => Value::Bool(boolean),
998        DebuggerValue::NumberValue(val) => {
999            if val.is_nan() {
1000                v.insert("type".to_owned(), Value::String("NaN".to_owned()));
1001                Value::Object(v)
1002            } else if val.is_infinite() {
1003                if val < 0. {
1004                    v.insert("type".to_owned(), Value::String("-Infinity".to_owned()));
1005                } else {
1006                    v.insert("type".to_owned(), Value::String("Infinity".to_owned()));
1007                }
1008                Value::Object(v)
1009            } else if val == 0. && val.is_sign_negative() {
1010                v.insert("type".to_owned(), Value::String("-0".to_owned()));
1011                Value::Object(v)
1012            } else {
1013                Value::Number(Number::from_f64(val).unwrap())
1014            }
1015        },
1016        DebuggerValue::StringValue(str) => Value::String(str),
1017        DebuggerValue::ObjectValue {
1018            uuid,
1019            class,
1020            own_property_length,
1021            preview,
1022        } => {
1023            let object_name = ObjectActor::register(
1024                registry,
1025                Some(uuid),
1026                class,
1027                own_property_length,
1028                preview.map(|preview| *preview),
1029            );
1030            let object_msg = registry.encode::<ObjectActor, _>(&object_name);
1031            let value = serde_json::to_value(object_msg).unwrap_or_default();
1032            Value::Object(value.as_object().cloned().unwrap_or_default())
1033        },
1034    }
1035}