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