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