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