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