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