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