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