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