Skip to main content

script/dom/debugger/
debuggerglobalscope.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#![cfg_attr(crown, allow(crown::jscontext_first_arg))]
6
7use std::cell::RefCell;
8
9use devtools_traits::{
10    BlackboxCoverage, DebuggerValue, DevtoolScriptControlMsg, EvaluateJSReply,
11    GetEnvironmentRequest, ScriptToDevtoolsControlMsg, SourceInfo, WorkerId,
12};
13use dom_struct::dom_struct;
14use embedder_traits::ScriptToEmbedderChan;
15use embedder_traits::resources::{self, Resource};
16use js::context::JSContext;
17use js::rust::wrappers2::JS_DefineDebuggerObject;
18use net_traits::ResourceThreads;
19use profile_traits::{mem, time};
20use script_bindings::interfaces::HasOrigin;
21use script_bindings::reflector::DomObject;
22use servo_base::generic_channel::{GenericCallback, GenericSender, channel};
23use servo_base::id::{Index, PipelineId, PipelineNamespaceId};
24use servo_constellation_traits::ScriptToConstellationSender;
25use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
26use storage_traits::StorageThreads;
27
28use crate::dom::bindings::codegen::Bindings::DebuggerGetEnvironmentEventBinding::EnvironmentInfo;
29use crate::dom::bindings::codegen::Bindings::DebuggerGlobalScopeBinding;
30use crate::dom::bindings::codegen::Bindings::DebuggerInterruptEventBinding::{
31    FrameInfo, FrameOffset, PauseReason,
32};
33use crate::dom::bindings::codegen::GenericBindings::DebuggerEvalEventBinding::EvalResult;
34use crate::dom::bindings::codegen::GenericBindings::DebuggerGetPossibleBreakpointsEventBinding::RecommendedBreakpointLocation;
35use crate::dom::bindings::codegen::GenericBindings::DebuggerGlobalScopeBinding::{
36    DebuggerGlobalScopeMethods, NotifyNewSource, PipelineIdInit,
37};
38use crate::dom::bindings::inheritance::Castable;
39use crate::dom::bindings::root::DomRoot;
40use crate::dom::bindings::str::DOMString;
41use crate::dom::bindings::utils::define_all_exposed_interfaces;
42use crate::dom::debugger::debuggerblackboxevent::DebuggerBlackboxEvent;
43use crate::dom::debugger::debuggerclearbreakpointevent::DebuggerClearBreakpointEvent;
44use crate::dom::debugger::debuggerframeevent::DebuggerFrameEvent;
45use crate::dom::debugger::debuggergetenvironmentevent::DebuggerGetEnvironmentEvent;
46use crate::dom::debugger::debuggerinterruptevent::DebuggerInterruptEvent;
47use crate::dom::debugger::debuggerresumeevent::DebuggerResumeEvent;
48use crate::dom::debugger::debuggersetbreakpointevent::DebuggerSetBreakpointEvent;
49use crate::dom::debugger::debuggerunblackboxevent::DebuggerUnblackboxEvent;
50use crate::dom::globalscope::GlobalScope;
51use crate::dom::types::{
52    DebuggerAddDebuggeeEvent, DebuggerEvalEvent, DebuggerGetPossibleBreakpointsEvent, Event,
53};
54#[cfg(feature = "webgpu")]
55use crate::dom::webgpu::identityhub::IdentityHub;
56use crate::event_loop::script_thread::with_script_thread;
57use crate::realms::enter_auto_realm;
58use crate::runtime::script_runtime::IntroductionType;
59
60#[dom_struct]
61/// Global scope for interacting with the devtools Debugger API.
62///
63/// <https://firefox-source-docs.mozilla.org/js/Debugger/>
64pub(crate) struct DebuggerGlobalScope {
65    global_scope: GlobalScope,
66    #[no_trace]
67    devtools_to_script_sender: GenericSender<DevtoolScriptControlMsg>,
68    #[no_trace]
69    get_possible_breakpoints_result_sender:
70        RefCell<Option<GenericSender<Vec<devtools_traits::RecommendedBreakpointLocation>>>>,
71    #[no_trace]
72    eval_result_sender: RefCell<Option<GenericSender<EvaluateJSReply>>>,
73    #[no_trace]
74    get_list_frame_result_sender: RefCell<Option<GenericSender<Vec<String>>>>,
75    #[no_trace]
76    get_environment_result_sender: RefCell<Option<GenericSender<String>>>,
77    #[no_trace]
78    pipeline_id: PipelineId,
79    #[no_trace]
80    origin: MutableOrigin,
81}
82
83impl DebuggerGlobalScope {
84    /// Create a new heap-allocated `DebuggerGlobalScope`.
85    ///
86    /// `debugger_pipeline_id` is the pipeline id to use when creating the debugger’s [`GlobalScope`]:
87    /// - in normal script threads, it should be set to `PipelineId::new()`, because those threads can generate
88    ///   pipeline ids, and they may contain debuggees from more than one pipeline
89    /// - in web worker threads, it should be set to the pipeline id of the page that created the thread, because
90    ///   those threads can’t generate pipeline ids, and they only contain one debuggee from one pipeline
91    #[expect(unsafe_code, clippy::too_many_arguments)]
92    pub(crate) fn new(
93        debugger_pipeline_id: PipelineId,
94        script_to_devtools_sender: Option<GenericCallback<ScriptToDevtoolsControlMsg>>,
95        devtools_to_script_sender: GenericSender<DevtoolScriptControlMsg>,
96        mem_profiler_chan: mem::ProfilerChan,
97        time_profiler_chan: time::ProfilerChan,
98        script_to_constellation_sender: ScriptToConstellationSender,
99        script_to_embedder_chan: ScriptToEmbedderChan,
100        resource_threads: ResourceThreads,
101        storage_threads: StorageThreads,
102        #[cfg(feature = "webgpu")] gpu_id_hub: std::sync::Arc<IdentityHub>,
103        cx: &mut JSContext,
104    ) -> DomRoot<Self> {
105        let global = Box::new(Self {
106            global_scope: GlobalScope::new_inherited(
107                script_to_devtools_sender,
108                mem_profiler_chan,
109                time_profiler_chan,
110                script_to_constellation_sender,
111                script_to_embedder_chan,
112                resource_threads,
113                storage_threads,
114                ServoUrl::parse_with_base(None, "about:internal/debugger")
115                    .expect("Guaranteed by argument"),
116                None,
117                #[cfg(feature = "webgpu")]
118                gpu_id_hub,
119                None,
120                false,
121            ),
122            devtools_to_script_sender,
123            get_possible_breakpoints_result_sender: RefCell::new(None),
124            get_list_frame_result_sender: RefCell::new(None),
125            get_environment_result_sender: RefCell::new(None),
126            eval_result_sender: RefCell::new(None),
127            pipeline_id: debugger_pipeline_id,
128            origin: MutableOrigin::new(ImmutableOrigin::new_opaque()),
129        });
130        let global =
131            DebuggerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(cx, &global.origin(), global);
132
133        let mut realm = enter_auto_realm(cx, &*global);
134        let mut realm = realm.current_realm();
135        define_all_exposed_interfaces(&mut realm, global.upcast());
136        assert!(unsafe {
137            // Invariants: `obj` must be a handle to a JS global object.
138            JS_DefineDebuggerObject(&mut realm, global.global_scope.reflector().get_jsobject())
139        });
140
141        global
142    }
143
144    pub(crate) fn origin(&self) -> MutableOrigin {
145        self.origin.clone()
146    }
147
148    pub(crate) fn as_global_scope(&self) -> &GlobalScope {
149        self.upcast::<GlobalScope>()
150    }
151
152    pub(crate) fn execute(&self, cx: &mut JSContext) {
153        let mut realm = enter_auto_realm(cx, self);
154        let cx = &mut realm.current_realm();
155
156        let _ = self.global_scope.evaluate_js_on_global(
157            cx,
158            resources::read_string(Resource::DebuggerJS).into(),
159            "",
160            None,
161            None,
162        );
163    }
164
165    pub(crate) fn fire_add_debuggee(
166        &self,
167        cx: &mut JSContext,
168        debuggee_global: &GlobalScope,
169        debuggee_pipeline_id: PipelineId,
170        debuggee_worker_id: Option<WorkerId>,
171    ) {
172        let mut realm = enter_auto_realm(cx, self);
173        let cx = &mut realm;
174        let debuggee_pipeline_id =
175            crate::dom::pipelineid::PipelineId::new(cx, self.upcast(), debuggee_pipeline_id);
176        let event = DomRoot::upcast::<Event>(DebuggerAddDebuggeeEvent::new(
177            cx,
178            self.upcast(),
179            debuggee_global,
180            &debuggee_pipeline_id,
181            debuggee_worker_id.map(|id| id.to_string().into()),
182        ));
183        assert!(
184            event.fire(cx, self.upcast()),
185            "Guaranteed by DebuggerAddDebuggeeEvent::new"
186        );
187    }
188
189    #[expect(clippy::too_many_arguments)]
190    pub(crate) fn fire_eval(
191        &self,
192        cx: &mut JSContext,
193        code: DOMString,
194        debuggee_pipeline_id: PipelineId,
195        debuggee_worker_id: Option<WorkerId>,
196        frame_actor_id: Option<String>,
197        eager: bool,
198        result_sender: GenericSender<EvaluateJSReply>,
199    ) {
200        assert!(
201            self.eval_result_sender
202                .replace(Some(result_sender))
203                .is_none()
204        );
205        let mut realm = enter_auto_realm(cx, self);
206        let cx = &mut realm;
207        let debuggee_pipeline_id =
208            crate::dom::pipelineid::PipelineId::new(cx, self.upcast(), debuggee_pipeline_id);
209        let event = DomRoot::upcast::<Event>(DebuggerEvalEvent::new(
210            cx,
211            self.upcast(),
212            code,
213            &debuggee_pipeline_id,
214            debuggee_worker_id.map(|id| id.to_string().into()),
215            frame_actor_id.map(|id| id.into()),
216            eager,
217        ));
218        assert!(
219            event.fire(cx, self.upcast()),
220            "Guaranteed by DebuggerEvalEvent::new"
221        );
222    }
223
224    pub(crate) fn fire_get_possible_breakpoints(
225        &self,
226        cx: &mut JSContext,
227        spidermonkey_id: u32,
228        result_sender: GenericSender<Vec<devtools_traits::RecommendedBreakpointLocation>>,
229    ) {
230        assert!(
231            self.get_possible_breakpoints_result_sender
232                .replace(Some(result_sender))
233                .is_none()
234        );
235        let mut realm = enter_auto_realm(cx, self);
236        let cx = &mut realm.current_realm();
237        let event = DomRoot::upcast::<Event>(DebuggerGetPossibleBreakpointsEvent::new(
238            cx,
239            self.upcast(),
240            spidermonkey_id,
241        ));
242        assert!(
243            event.fire(cx, self.upcast()),
244            "Guaranteed by DebuggerGetPossibleBreakpointsEvent::new"
245        );
246    }
247
248    pub(crate) fn fire_set_breakpoint(
249        &self,
250        cx: &mut JSContext,
251        spidermonkey_id: u32,
252        script_id: u32,
253        offset: u32,
254    ) {
255        let event = DomRoot::upcast::<Event>(DebuggerSetBreakpointEvent::new(
256            cx,
257            self.upcast(),
258            spidermonkey_id,
259            script_id,
260            offset,
261        ));
262        assert!(
263            event.fire(cx, self.upcast()),
264            "Guaranteed by DebuggerSetBreakpointEvent::new"
265        );
266    }
267
268    pub(crate) fn fire_interrupt(&self, cx: &mut js::context::JSContext) {
269        let event = DomRoot::upcast::<Event>(DebuggerInterruptEvent::new(cx, self.upcast()));
270        assert!(
271            event.fire(cx, self.upcast()),
272            "Guaranteed by DebuggerInterruptEvent::new"
273        );
274    }
275
276    pub(crate) fn fire_list_frames(
277        &self,
278        cx: &mut js::context::JSContext,
279        pipeline_id: PipelineId,
280        start: u32,
281        count: u32,
282        result_sender: GenericSender<Vec<String>>,
283    ) {
284        assert!(
285            self.get_list_frame_result_sender
286                .replace(Some(result_sender))
287                .is_none()
288        );
289        let mut realm = enter_auto_realm(cx, self);
290        let cx = &mut realm.current_realm();
291        let pipeline_id = crate::dom::pipelineid::PipelineId::new(cx, self.upcast(), pipeline_id);
292        let event = DomRoot::upcast::<Event>(DebuggerFrameEvent::new(
293            cx,
294            self.upcast(),
295            &pipeline_id,
296            start,
297            count,
298        ));
299        assert!(
300            event.fire(cx, self.upcast()),
301            "Guaranteed by DebuggerFrameEvent::new"
302        );
303    }
304
305    pub(crate) fn fire_get_environment(
306        &self,
307        cx: &mut JSContext,
308        request: GetEnvironmentRequest,
309        result_sender: GenericSender<String>,
310    ) {
311        assert!(
312            self.get_environment_result_sender
313                .replace(Some(result_sender))
314                .is_none()
315        );
316        let mut realm = enter_auto_realm(cx, self);
317        let cx = &mut realm.current_realm();
318
319        let event = DomRoot::upcast::<Event>(DebuggerGetEnvironmentEvent::new(cx, self, request));
320        assert!(
321            event.fire(cx, self.upcast()),
322            "Guaranteed by DebuggerGetEnvironmentEvent::new"
323        );
324    }
325
326    pub(crate) fn fire_resume(
327        &self,
328        cx: &mut JSContext,
329        resume_limit_type: Option<String>,
330        frame_actor_id: Option<String>,
331    ) {
332        let event = DomRoot::upcast::<Event>(DebuggerResumeEvent::new(
333            cx,
334            self.upcast(),
335            resume_limit_type.map(DOMString::from),
336            frame_actor_id.map(DOMString::from),
337        ));
338        assert!(
339            event.fire(cx, self.upcast()),
340            "Guaranteed by DebuggerResumeEvent::new"
341        );
342    }
343
344    pub(crate) fn fire_clear_breakpoint(
345        &self,
346        cx: &mut JSContext,
347        spidermonkey_id: u32,
348        script_id: u32,
349        offset: u32,
350    ) {
351        let event = DomRoot::upcast::<Event>(DebuggerClearBreakpointEvent::new(
352            cx,
353            self.upcast(),
354            spidermonkey_id,
355            script_id,
356            offset,
357        ));
358        assert!(
359            event.fire(cx, self.upcast()),
360            "Guaranteed by DebuggerClearBreakpointEvent::new"
361        );
362    }
363
364    pub(crate) fn fire_blackbox(
365        &self,
366        cx: &mut JSContext,
367        spidermonkey_id: u32,
368        coverage: BlackboxCoverage,
369    ) {
370        let event = DomRoot::upcast::<Event>(DebuggerBlackboxEvent::new(
371            cx,
372            self.upcast(),
373            spidermonkey_id,
374            coverage,
375        ));
376        assert!(
377            event.fire(cx, self.upcast()),
378            "Guaranteed by DebuggerBlackboxEvent::new"
379        );
380    }
381
382    pub(crate) fn fire_unblackbox(
383        &self,
384        cx: &mut JSContext,
385        spidermonkey_id: u32,
386        coverage: BlackboxCoverage,
387    ) {
388        let event = DomRoot::upcast::<Event>(DebuggerUnblackboxEvent::new(
389            cx,
390            self.upcast(),
391            spidermonkey_id,
392            coverage,
393        ));
394        assert!(
395            event.fire(cx, self.upcast()),
396            "Guaranteed by DebuggerUnblackboxEvent::new"
397        );
398    }
399
400    pub(crate) fn pipeline_id(&self) -> PipelineId {
401        self.pipeline_id
402    }
403}
404
405impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
406    // check-tidy: no specs after this line
407    fn NotifyNewSource(&self, args: &NotifyNewSource) {
408        let Some(devtools_chan) = self.as_global_scope().devtools_chan() else {
409            return;
410        };
411        let pipeline_id = PipelineId {
412            namespace_id: PipelineNamespaceId(args.pipelineId.namespaceId),
413            index: Index::new(args.pipelineId.index).expect("`pipelineId.index` must not be zero"),
414        };
415
416        if let Some(introduction_type) = args.introductionType.as_ref() {
417            // Check the `introductionType` and `url`, decide whether or not to create a source actor, and if so,
418            // tell the devtools server to create a source actor. Based on the Firefox impl in:
419            // - getDebuggerSourceURL() <https://searchfox.org/mozilla-central/rev/85667ab51e4b2a3352f7077a9ee43513049ed2d6/devtools/server/actors/utils/source-url.js#7-42>
420            // - getSourceURL() <https://searchfox.org/mozilla-central/rev/85667ab51e4b2a3352f7077a9ee43513049ed2d6/devtools/server/actors/source.js#67-109>
421            // - resolveSourceURL() <https://searchfox.org/mozilla-central/rev/85667ab51e4b2a3352f7077a9ee43513049ed2d6/devtools/server/actors/source.js#48-66>
422            // - SourceActor#_isInlineSource <https://searchfox.org/mozilla-central/rev/85667ab51e4b2a3352f7077a9ee43513049ed2d6/devtools/server/actors/source.js#130-143>
423            // - SourceActor#url <https://searchfox.org/mozilla-central/rev/85667ab51e4b2a3352f7077a9ee43513049ed2d6/devtools/server/actors/source.js#157-162>
424
425            // Firefox impl: getDebuggerSourceURL(), getSourceURL()
426            // TODO: handle `about:srcdoc` case (see Firefox getDebuggerSourceURL())
427            // TODO: remove trailing details that may have been appended by SpiderMonkey
428            // (currently impossible to do robustly due to <https://bugzilla.mozilla.org/show_bug.cgi?id=1982001>)
429            let url_original = args.url.str();
430            // FIXME: use page/worker url as base here
431            let url_original = ServoUrl::parse(&url_original).ok();
432
433            // If the source has a `urlOverride` (aka `displayURL` aka `//# sourceURL`), it should be a valid url,
434            // possibly relative to the page/worker url, and we should treat the source as coming from that url for
435            // devtools purposes, including the file tree in the Sources tab.
436            // Firefox impl: getSourceURL()
437            let url_override = args
438                .urlOverride
439                .as_ref()
440                .map(|url| url.str())
441                // FIXME: use page/worker url as base here, not `url_original`
442                .and_then(|url| ServoUrl::parse_with_base(url_original.as_ref(), &url).ok());
443
444            // If the `introductionType` is “eval or eval-like”, the `url` won’t be meaningful, so ignore these
445            // sources unless we have a `urlOverride` (aka `displayURL` aka `//# sourceURL`).
446            // Firefox impl: getDebuggerSourceURL(), getSourceURL()
447            if [
448                IntroductionType::INJECTED_SCRIPT_STR,
449                IntroductionType::EVAL_STR,
450                IntroductionType::DEBUGGER_EVAL_STR,
451                IntroductionType::FUNCTION_STR,
452                IntroductionType::JAVASCRIPT_URL_STR,
453                IntroductionType::EVENT_HANDLER_STR,
454                IntroductionType::DOM_TIMER_STR,
455            ]
456            .contains(&&*introduction_type.str()) &&
457                url_override.is_none()
458            {
459                debug!(
460                    "Not creating debuggee: `introductionType` is `{introduction_type}` but no valid url"
461                );
462                return;
463            }
464
465            // Sources with an `introductionType` of `inlineScript` are generally inline, meaning their contents
466            // are a substring of the page markup (hence not known to SpiderMonkey, requiring plumbing in Servo).
467            // But sources with a `urlOverride` are not inline, since they get their own place in the Sources tree.
468            // nor are sources created for `<iframe srcdoc>`, since they are not necessarily a substring of the
469            // page markup as originally sent by the server.
470            // Firefox impl: SourceActor#_isInlineSource
471            // TODO: handle `about:srcdoc` case (see Firefox SourceActor#_isInlineSource)
472            let inline = introduction_type.str() == "inlineScript" && url_override.is_none();
473            let Some(url) = url_override.or(url_original) else {
474                debug!("Not creating debuggee: no valid url");
475                return;
476            };
477
478            let worker_id = args.workerId.as_ref().map(|id| id.parse().unwrap());
479
480            let source_info = SourceInfo {
481                url,
482                introduction_type: introduction_type.str().to_owned(),
483                inline,
484                worker_id,
485                content: (!inline).then(|| args.text.to_string()),
486                content_type: None, // TODO
487                spidermonkey_id: args.spidermonkeyId,
488            };
489            if let Err(error) = devtools_chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
490                self.devtools_to_script_sender.clone(),
491                pipeline_id,
492                source_info,
493            )) {
494                warn!("Failed to send to devtools server: {error:?}");
495            }
496        } else {
497            debug!("Not creating debuggee for script with no `introductionType`");
498        }
499    }
500
501    fn GetPossibleBreakpointsResult(
502        &self,
503        event: &DebuggerGetPossibleBreakpointsEvent,
504        result: Vec<RecommendedBreakpointLocation>,
505    ) {
506        info!("GetPossibleBreakpointsResult: {event:?} {result:?}");
507        let sender = self
508            .get_possible_breakpoints_result_sender
509            .take()
510            .expect("Guaranteed by Self::fire_get_possible_breakpoints()");
511        let _ = sender.send(
512            result
513                .into_iter()
514                .map(|entry| devtools_traits::RecommendedBreakpointLocation {
515                    script_id: entry.scriptId,
516                    offset: entry.offset,
517                    line_number: entry.lineNumber,
518                    column_number: entry.columnNumber,
519                    is_step_start: entry.isStepStart,
520                })
521                .collect(),
522        );
523    }
524
525    /// Handle the result from debugger.js executeInGlobal() call.
526    ///
527    /// The result contains completion value information from the SpiderMonkey Debugger API:
528    /// <https://firefox-source-docs.mozilla.org/js/Debugger/Conventions.html#completion-values>
529    fn EvalResult(&self, _event: &DebuggerEvalEvent, result: &EvalResult) {
530        let sender = self
531            .eval_result_sender
532            .take()
533            .expect("Guaranteed by Self::fire_eval()");
534
535        let has_exception = result.hasException.unwrap_or(false);
536        let value = match serde_json::from_str::<devtools_traits::DebuggerValue>(
537            &result.serializedValue.str(),
538        ) {
539            Ok(value) => value,
540            Err(error) => {
541                warn!("Failed to parse serialized debugger eval value: {error}");
542                devtools_traits::DebuggerValue::StringValue(
543                    "failed to parse eval result".to_string(),
544                )
545            },
546        };
547
548        let exception_message = result
549            .exceptionMessage
550            .as_ref()
551            .map(|message| message.str().to_string());
552
553        let reply = EvaluateJSReply {
554            value,
555            exception_message,
556            has_exception,
557        };
558
559        let _ = sender.send(reply);
560    }
561
562    fn RegisterObjectActor(&self, serialized_value: DOMString) -> Option<DOMString> {
563        let chan = self.upcast::<GlobalScope>().devtools_chan()?;
564        let (tx, rx) = channel::<String>().unwrap();
565
566        let value =
567            match serde_json::from_str::<devtools_traits::DebuggerValue>(&serialized_value.str()) {
568                Ok(value) => value,
569                Err(error) => {
570                    warn!("Failed to parse serialized debugger object value: {error}");
571                    return None;
572                },
573            };
574
575        let msg = ScriptToDevtoolsControlMsg::CreateObjectActor(tx, value);
576        let _ = chan.send(msg);
577
578        rx.recv().ok().map(DOMString::from)
579    }
580
581    fn PauseAndRespond(
582        &self,
583        pipeline_id: &PipelineIdInit,
584        frame_offset: &FrameOffset,
585        pause_reason: &PauseReason,
586    ) {
587        let pipeline_id = PipelineId {
588            namespace_id: PipelineNamespaceId(pipeline_id.namespaceId),
589            index: Index::new(pipeline_id.index).expect("`pipelineId.index` must not be zero"),
590        };
591
592        let frame_offset = devtools_traits::FrameOffset {
593            actor: frame_offset.frameActorId.clone().into(),
594            column: frame_offset.column,
595            line: frame_offset.line,
596        };
597
598        let pause_reason = devtools_traits::PauseReason {
599            type_: pause_reason.type_.clone().into(),
600            on_next: pause_reason.onNext,
601        };
602
603        if let Some(chan) = self.upcast::<GlobalScope>().devtools_chan() {
604            let msg =
605                ScriptToDevtoolsControlMsg::DebuggerPause(pipeline_id, frame_offset, pause_reason);
606            let _ = chan.send(msg);
607        }
608
609        with_script_thread(|script_thread| {
610            script_thread.enter_debugger_pause_loop();
611        });
612    }
613
614    fn RegisterFrameActor(
615        &self,
616        pipeline_id: &PipelineIdInit,
617        result: &FrameInfo,
618    ) -> Option<DOMString> {
619        let pipeline_id = PipelineId {
620            namespace_id: PipelineNamespaceId(pipeline_id.namespaceId),
621            index: Index::new(pipeline_id.index).expect("`pipelineId.index` must not be zero"),
622        };
623
624        let chan = self.upcast::<GlobalScope>().devtools_chan()?;
625        let (tx, rx) = channel::<String>().unwrap();
626
627        let this_value = match serde_json::from_str::<devtools_traits::DebuggerValue>(
628            &result.serializedThis.str(),
629        ) {
630            Ok(this_value) => this_value,
631            Err(error) => {
632                warn!("Failed to parse serialized debugger frame this value: {error}");
633                return None;
634            },
635        };
636
637        let frame = devtools_traits::FrameInfo {
638            display_name: result.displayName.clone().map(String::from),
639            on_stack: result.onStack,
640            oldest: result.oldest,
641            this_value,
642            terminated: result.terminated,
643            type_: result.type_.clone().into(),
644            url: result.url.clone().into(),
645        };
646        let msg = ScriptToDevtoolsControlMsg::CreateFrameActor(tx, pipeline_id, frame);
647        let _ = chan.send(msg);
648
649        rx.recv().ok().map(DOMString::from)
650    }
651
652    fn ListFramesResult(&self, frame_actor_ids: Vec<DOMString>) {
653        info!("ListFramesResult: {frame_actor_ids:?}");
654        let sender = self
655            .get_list_frame_result_sender
656            .take()
657            .expect("Guaranteed by Self::fire_list_frames()");
658
659        let _ = sender.send(frame_actor_ids.into_iter().map(|i| i.into()).collect());
660    }
661
662    fn RegisterEnvironmentActor(
663        &self,
664        environment: &EnvironmentInfo,
665        parent: Option<DOMString>,
666        actor: Option<DOMString>,
667    ) -> Option<DOMString> {
668        let chan = self.upcast::<GlobalScope>().devtools_chan()?;
669        let (tx, rx) = channel::<String>().unwrap();
670
671        let binding_variables = match serde_json::from_str::<Vec<devtools_traits::PropertyDescriptor>>(
672            &environment.serializedBindings.str(),
673        ) {
674            Ok(binding_variables) => binding_variables,
675            Err(error) => {
676                warn!("Failed to parse serialized debugger environment bindings: {error}");
677                return None;
678            },
679        };
680        let object = match environment.serializedObject.as_ref() {
681            Some(serialized_object) => {
682                match serde_json::from_str::<DebuggerValue>(&serialized_object.str()) {
683                    Ok(object) => Some(object),
684                    Err(error) => {
685                        warn!("Failed to parse serialized debugger environment object: {error}");
686                        return None;
687                    },
688                }
689            },
690            None => None,
691        };
692        let environment = devtools_traits::EnvironmentInfo {
693            type_: environment.type_.clone().map(String::from),
694            scope_kind: environment.scopeKind.clone().map(String::from),
695            function_display_name: environment.functionDisplayName.clone().map(String::from),
696            object,
697            binding_variables,
698        };
699
700        let msg = ScriptToDevtoolsControlMsg::CreateEnvironmentActor(
701            tx,
702            environment,
703            parent.map(String::from),
704            actor.map(String::from),
705        );
706        let _ = chan.send(msg);
707
708        rx.recv().ok().map(DOMString::from)
709    }
710
711    fn GetEnvironmentResult(&self, environment_actor_id: DOMString) {
712        let sender = self
713            .get_environment_result_sender
714            .take()
715            .expect("Guaranteed by Self::fire_get_environment()");
716
717        let _ = sender.send(environment_actor_id.into());
718    }
719}
720
721impl HasOrigin for DebuggerGlobalScope {
722    fn origin(&self) -> MutableOrigin {
723        DebuggerGlobalScope::origin(self)
724    }
725}