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