1use 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 profile_traits::{mem, time};
17use script_bindings::reflector::DomObject;
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::root::DomRoot;
42use crate::dom::bindings::str::DOMString;
43use crate::dom::bindings::utils::define_all_exposed_interfaces;
44use crate::dom::debugger::debuggerclearbreakpointevent::DebuggerClearBreakpointEvent;
45use crate::dom::debugger::debuggerframeevent::DebuggerFrameEvent;
46use crate::dom::debugger::debuggergetenvironmentevent::DebuggerGetEnvironmentEvent;
47use crate::dom::debugger::debuggerinterruptevent::DebuggerInterruptEvent;
48use crate::dom::debugger::debuggerresumeevent::DebuggerResumeEvent;
49use crate::dom::debugger::debuggersetbreakpointevent::DebuggerSetBreakpointEvent;
50use crate::dom::globalscope::GlobalScope;
51use crate::dom::types::{
52 DebuggerAddDebuggeeEvent, DebuggerEvalEvent, DebuggerGetPossibleBreakpointsEvent, Event,
53};
54#[cfg(feature = "webgpu")]
55use crate::dom::webgpu::identityhub::IdentityHub;
56use crate::realms::{enter_auto_realm, enter_realm};
57use crate::script_runtime::{CanGc, IntroductionType};
58use crate::script_thread::with_script_thread;
59
60#[dom_struct]
61pub(crate) struct DebuggerGlobalScope {
65 global_scope: GlobalScope,
66 #[no_trace]
67 devtools_to_script_sender: GenericSender<DevtoolScriptControlMsg>,
68 #[no_trace]
69 get_possible_breakpoints_result_sender:
70 RefCell<Option<GenericSender<Vec<devtools_traits::RecommendedBreakpointLocation>>>>,
71 #[no_trace]
72 eval_result_sender: RefCell<Option<GenericSender<EvaluateJSReply>>>,
73 #[no_trace]
74 get_list_frame_result_sender: RefCell<Option<GenericSender<Vec<String>>>>,
75 #[no_trace]
76 get_environment_result_sender: RefCell<Option<GenericSender<String>>>,
77}
78
79impl DebuggerGlobalScope {
80 #[expect(unsafe_code, clippy::too_many_arguments)]
88 pub(crate) fn new(
89 debugger_pipeline_id: PipelineId,
90 script_to_devtools_sender: Option<GenericCallback<ScriptToDevtoolsControlMsg>>,
91 devtools_to_script_sender: GenericSender<DevtoolScriptControlMsg>,
92 mem_profiler_chan: mem::ProfilerChan,
93 time_profiler_chan: time::ProfilerChan,
94 script_to_constellation_chan: ScriptToConstellationChan,
95 script_to_embedder_chan: ScriptToEmbedderChan,
96 resource_threads: ResourceThreads,
97 storage_threads: StorageThreads,
98 #[cfg(feature = "webgpu")] gpu_id_hub: std::sync::Arc<IdentityHub>,
99 cx: &mut JSContext,
100 ) -> DomRoot<Self> {
101 let global = Box::new(Self {
102 global_scope: GlobalScope::new_inherited(
103 debugger_pipeline_id,
104 script_to_devtools_sender,
105 mem_profiler_chan,
106 time_profiler_chan,
107 script_to_constellation_chan,
108 script_to_embedder_chan,
109 resource_threads,
110 storage_threads,
111 MutableOrigin::new(ImmutableOrigin::new_opaque()),
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, ),
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 });
127 let global = DebuggerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(cx, global);
128
129 let mut realm = enter_auto_realm(cx, &*global);
130 let mut realm = realm.current_realm();
131 define_all_exposed_interfaces(&mut realm, global.upcast());
132 assert!(unsafe {
133 JS_DefineDebuggerObject(&mut realm, global.global_scope.reflector().get_jsobject())
135 });
136
137 global
138 }
139
140 pub(crate) fn as_global_scope(&self) -> &GlobalScope {
141 self.upcast::<GlobalScope>()
142 }
143
144 pub(crate) fn execute(&self, cx: &mut JSContext) {
145 let mut realm = enter_auto_realm(cx, self);
146 let cx = &mut realm.current_realm();
147
148 let _ = self.global_scope.evaluate_js_on_global(
149 cx,
150 resources::read_string(Resource::DebuggerJS).into(),
151 "",
152 None,
153 None,
154 );
155 }
156
157 pub(crate) fn fire_add_debuggee(
158 &self,
159 cx: &mut JSContext,
160 debuggee_global: &GlobalScope,
161 debuggee_pipeline_id: PipelineId,
162 debuggee_worker_id: Option<WorkerId>,
163 ) {
164 let mut realm = enter_auto_realm(cx, self);
165 let cx = &mut realm;
166 let debuggee_pipeline_id = crate::dom::pipelineid::PipelineId::new(
167 self.upcast(),
168 debuggee_pipeline_id,
169 CanGc::from_cx(cx),
170 );
171 let event = DomRoot::upcast::<Event>(DebuggerAddDebuggeeEvent::new(
172 self.upcast(),
173 debuggee_global,
174 &debuggee_pipeline_id,
175 debuggee_worker_id.map(|id| id.to_string().into()),
176 CanGc::from_cx(cx),
177 ));
178 assert!(
179 event.fire(self.upcast(), CanGc::from_cx(cx)),
180 "Guaranteed by DebuggerAddDebuggeeEvent::new"
181 );
182 }
183
184 pub(crate) fn fire_eval(
185 &self,
186 cx: &mut JSContext,
187 code: DOMString,
188 debuggee_pipeline_id: PipelineId,
189 debuggee_worker_id: Option<WorkerId>,
190 frame_actor_id: Option<String>,
191 result_sender: GenericSender<EvaluateJSReply>,
192 ) {
193 assert!(
194 self.eval_result_sender
195 .replace(Some(result_sender))
196 .is_none()
197 );
198 let mut realm = enter_auto_realm(cx, self);
199 let cx = &mut realm;
200 let debuggee_pipeline_id = crate::dom::pipelineid::PipelineId::new(
201 self.upcast(),
202 debuggee_pipeline_id,
203 CanGc::from_cx(cx),
204 );
205 let event = DomRoot::upcast::<Event>(DebuggerEvalEvent::new(
206 self.upcast(),
207 code,
208 &debuggee_pipeline_id,
209 debuggee_worker_id.map(|id| id.to_string().into()),
210 frame_actor_id.map(|id| id.into()),
211 CanGc::from_cx(cx),
212 ));
213 assert!(
214 event.fire(self.upcast(), CanGc::from_cx(cx)),
215 "Guaranteed by DebuggerEvalEvent::new"
216 );
217 }
218
219 pub(crate) fn fire_get_possible_breakpoints(
220 &self,
221 cx: &mut JSContext,
222 spidermonkey_id: u32,
223 result_sender: GenericSender<Vec<devtools_traits::RecommendedBreakpointLocation>>,
224 ) {
225 assert!(
226 self.get_possible_breakpoints_result_sender
227 .replace(Some(result_sender))
228 .is_none()
229 );
230 let _realm = enter_realm(self);
231 let event = DomRoot::upcast::<Event>(DebuggerGetPossibleBreakpointsEvent::new(
232 self.upcast(),
233 spidermonkey_id,
234 CanGc::from_cx(cx),
235 ));
236 assert!(
237 event.fire(self.upcast(), CanGc::from_cx(cx)),
238 "Guaranteed by DebuggerGetPossibleBreakpointsEvent::new"
239 );
240 }
241
242 pub(crate) fn fire_set_breakpoint(
243 &self,
244 cx: &mut JSContext,
245 spidermonkey_id: u32,
246 script_id: u32,
247 offset: u32,
248 ) {
249 let event = DomRoot::upcast::<Event>(DebuggerSetBreakpointEvent::new(
250 self.upcast(),
251 spidermonkey_id,
252 script_id,
253 offset,
254 CanGc::from_cx(cx),
255 ));
256 assert!(
257 event.fire(self.upcast(), CanGc::from_cx(cx)),
258 "Guaranteed by DebuggerSetBreakpointEvent::new"
259 );
260 }
261
262 pub(crate) fn fire_interrupt(&self, cx: &mut js::context::JSContext) {
263 let event = DomRoot::upcast::<Event>(DebuggerInterruptEvent::new(
264 self.upcast(),
265 CanGc::from_cx(cx),
266 ));
267 assert!(
268 event.fire(self.upcast(), CanGc::from_cx(cx)),
269 "Guaranteed by DebuggerInterruptEvent::new"
270 );
271 }
272
273 pub(crate) fn fire_list_frames(
274 &self,
275 cx: &mut js::context::JSContext,
276 pipeline_id: PipelineId,
277 start: u32,
278 count: u32,
279 result_sender: GenericSender<Vec<String>>,
280 ) {
281 assert!(
282 self.get_list_frame_result_sender
283 .replace(Some(result_sender))
284 .is_none()
285 );
286 let _realm = enter_realm(self);
287 let pipeline_id =
288 crate::dom::pipelineid::PipelineId::new(self.upcast(), pipeline_id, CanGc::from_cx(cx));
289 let event = DomRoot::upcast::<Event>(DebuggerFrameEvent::new(
290 self.upcast(),
291 &pipeline_id,
292 start,
293 count,
294 CanGc::from_cx(cx),
295 ));
296 assert!(
297 event.fire(self.upcast(), CanGc::from_cx(cx)),
298 "Guaranteed by DebuggerFrameEvent::new"
299 );
300 }
301
302 pub(crate) fn fire_get_environment(
303 &self,
304 cx: &mut JSContext,
305 frame_actor_id: String,
306 result_sender: GenericSender<String>,
307 ) {
308 assert!(
309 self.get_environment_result_sender
310 .replace(Some(result_sender))
311 .is_none()
312 );
313 let _realm = enter_realm(self);
314 let event = DomRoot::upcast::<Event>(DebuggerGetEnvironmentEvent::new(
315 self.upcast(),
316 frame_actor_id.into(),
317 CanGc::from_cx(cx),
318 ));
319 assert!(
320 event.fire(self.upcast(), CanGc::from_cx(cx)),
321 "Guaranteed by DebuggerGetEnvironmentEvent::new"
322 );
323 }
324
325 pub(crate) fn fire_resume(
326 &self,
327 cx: &mut JSContext,
328 resume_limit_type: Option<String>,
329 frame_actor_id: Option<String>,
330 ) {
331 let event = DomRoot::upcast::<Event>(DebuggerResumeEvent::new(
332 self.upcast(),
333 resume_limit_type.map(DOMString::from),
334 frame_actor_id.map(DOMString::from),
335 CanGc::from_cx(cx),
336 ));
337 assert!(
338 event.fire(self.upcast(), CanGc::from_cx(cx)),
339 "Guaranteed by DebuggerResumeEvent::new"
340 );
341 }
342
343 pub(crate) fn fire_clear_breakpoint(
344 &self,
345 cx: &mut JSContext,
346 spidermonkey_id: u32,
347 script_id: u32,
348 offset: u32,
349 ) {
350 let event = DomRoot::upcast::<Event>(DebuggerClearBreakpointEvent::new(
351 self.upcast(),
352 spidermonkey_id,
353 script_id,
354 offset,
355 CanGc::from_cx(cx),
356 ));
357 assert!(
358 event.fire(self.upcast(), CanGc::from_cx(cx)),
359 "Guaranteed by DebuggerClearBreakpointEvent::new"
360 );
361 }
362}
363
364impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
365 fn NotifyNewSource(&self, args: &NotifyNewSource) {
367 let Some(devtools_chan) = self.as_global_scope().devtools_chan() else {
368 return;
369 };
370 let pipeline_id = PipelineId {
371 namespace_id: PipelineNamespaceId(args.pipelineId.namespaceId),
372 index: Index::new(args.pipelineId.index).expect("`pipelineId.index` must not be zero"),
373 };
374
375 if let Some(introduction_type) = args.introductionType.as_ref() {
376 let url_original = args.url.str();
389 let url_original = ServoUrl::parse(&url_original).ok();
391
392 let url_override = args
397 .urlOverride
398 .as_ref()
399 .map(|url| url.str())
400 .and_then(|url| ServoUrl::parse_with_base(url_original.as_ref(), &url).ok());
402
403 if [
407 IntroductionType::INJECTED_SCRIPT_STR,
408 IntroductionType::EVAL_STR,
409 IntroductionType::DEBUGGER_EVAL_STR,
410 IntroductionType::FUNCTION_STR,
411 IntroductionType::JAVASCRIPT_URL_STR,
412 IntroductionType::EVENT_HANDLER_STR,
413 IntroductionType::DOM_TIMER_STR,
414 ]
415 .contains(&&*introduction_type.str()) &&
416 url_override.is_none()
417 {
418 debug!(
419 "Not creating debuggee: `introductionType` is `{introduction_type}` but no valid url"
420 );
421 return;
422 }
423
424 let inline = introduction_type.str() == "inlineScript" && url_override.is_none();
432 let Some(url) = url_override.or(url_original) else {
433 debug!("Not creating debuggee: no valid url");
434 return;
435 };
436
437 let worker_id = args.workerId.as_ref().map(|id| id.parse().unwrap());
438
439 let source_info = SourceInfo {
440 url,
441 introduction_type: introduction_type.str().to_owned(),
442 inline,
443 worker_id,
444 content: (!inline).then(|| args.text.to_string()),
445 content_type: None, spidermonkey_id: args.spidermonkeyId,
447 };
448 if let Err(error) = devtools_chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
449 self.devtools_to_script_sender.clone(),
450 pipeline_id,
451 source_info,
452 )) {
453 warn!("Failed to send to devtools server: {error:?}");
454 }
455 } else {
456 debug!("Not creating debuggee for script with no `introductionType`");
457 }
458 }
459
460 fn GetPossibleBreakpointsResult(
461 &self,
462 event: &DebuggerGetPossibleBreakpointsEvent,
463 result: Vec<RecommendedBreakpointLocation>,
464 ) {
465 info!("GetPossibleBreakpointsResult: {event:?} {result:?}");
466 let sender = self
467 .get_possible_breakpoints_result_sender
468 .take()
469 .expect("Guaranteed by Self::fire_get_possible_breakpoints()");
470 let _ = sender.send(
471 result
472 .into_iter()
473 .map(|entry| devtools_traits::RecommendedBreakpointLocation {
474 script_id: entry.scriptId,
475 offset: entry.offset,
476 line_number: entry.lineNumber,
477 column_number: entry.columnNumber,
478 is_step_start: entry.isStepStart,
479 })
480 .collect(),
481 );
482 }
483
484 fn EvalResult(&self, _event: &DebuggerEvalEvent, result: &EvalResult) {
489 let sender = self
490 .eval_result_sender
491 .take()
492 .expect("Guaranteed by Self::fire_eval()");
493
494 let reply = EvaluateJSReply {
495 value: parse_debugger_value(&result.value, result.preview.as_ref()),
496 has_exception: result.hasException.unwrap_or(false),
497 };
498
499 let _ = sender.send(reply);
500 }
501
502 fn PauseAndRespond(
503 &self,
504 pipeline_id: &PipelineIdInit,
505 frame_offset: &FrameOffset,
506 pause_reason: &PauseReason,
507 ) {
508 let pipeline_id = PipelineId {
509 namespace_id: PipelineNamespaceId(pipeline_id.namespaceId),
510 index: Index::new(pipeline_id.index).expect("`pipelineId.index` must not be zero"),
511 };
512
513 let frame_offset = devtools_traits::FrameOffset {
514 actor: frame_offset.frameActorId.clone().into(),
515 column: frame_offset.column,
516 line: frame_offset.line,
517 };
518
519 let pause_reason = devtools_traits::PauseReason {
520 type_: pause_reason.type_.clone().into(),
521 on_next: pause_reason.onNext,
522 };
523
524 if let Some(chan) = self.upcast::<GlobalScope>().devtools_chan() {
525 let msg =
526 ScriptToDevtoolsControlMsg::DebuggerPause(pipeline_id, frame_offset, pause_reason);
527 let _ = chan.send(msg);
528 }
529
530 with_script_thread(|script_thread| {
531 script_thread.enter_debugger_pause_loop();
532 });
533 }
534
535 fn RegisterFrameActor(
536 &self,
537 pipeline_id: &PipelineIdInit,
538 result: &FrameInfo,
539 ) -> Option<DOMString> {
540 let pipeline_id = PipelineId {
541 namespace_id: PipelineNamespaceId(pipeline_id.namespaceId),
542 index: Index::new(pipeline_id.index).expect("`pipelineId.index` must not be zero"),
543 };
544
545 let chan = self.upcast::<GlobalScope>().devtools_chan()?;
546 let (tx, rx) = channel::<String>().unwrap();
547
548 let frame = devtools_traits::FrameInfo {
549 display_name: result.displayName.clone().into(),
550 on_stack: result.onStack,
551 oldest: result.oldest,
552 terminated: result.terminated,
553 type_: result.type_.clone().into(),
554 url: result.url.clone().into(),
555 };
556 let msg = ScriptToDevtoolsControlMsg::CreateFrameActor(tx, pipeline_id, frame);
557 let _ = chan.send(msg);
558
559 rx.recv().ok().map(DOMString::from)
560 }
561
562 fn ListFramesResult(&self, frame_actor_ids: Vec<DOMString>) {
563 info!("ListFramesResult: {frame_actor_ids:?}");
564 let sender = self
565 .get_list_frame_result_sender
566 .take()
567 .expect("Guaranteed by Self::fire_list_frames()");
568
569 let _ = sender.send(frame_actor_ids.into_iter().map(|i| i.into()).collect());
570 }
571
572 fn RegisterEnvironmentActor(
573 &self,
574 environment: &EnvironmentInfo,
575 parent: Option<DOMString>,
576 actor: Option<DOMString>,
577 ) -> Option<DOMString> {
578 let chan = self.upcast::<GlobalScope>().devtools_chan()?;
579 let (tx, rx) = channel::<String>().unwrap();
580
581 let environment = devtools_traits::EnvironmentInfo {
582 type_: environment.type_.clone().map(String::from),
583 scope_kind: environment.scopeKind.clone().map(String::from),
584 function_display_name: environment.functionDisplayName.clone().map(String::from),
585 binding_variables: environment
586 .bindingVariables
587 .as_deref()
588 .into_iter()
589 .flatten()
590 .map(|EnvironmentVariable { property, preview }| {
591 parse_property_descriptor(property, preview.as_ref())
592 })
593 .collect(),
594 };
595
596 let msg = ScriptToDevtoolsControlMsg::CreateEnvironmentActor(
597 tx,
598 environment,
599 parent.map(String::from),
600 actor.map(String::from),
601 );
602 let _ = chan.send(msg);
603
604 rx.recv().ok().map(DOMString::from)
605 }
606
607 fn GetEnvironmentResult(&self, environment_actor_id: DOMString) {
608 let sender = self
609 .get_environment_result_sender
610 .take()
611 .expect("Guaranteed by Self::fire_get_environment()");
612
613 let _ = sender.send(environment_actor_id.into());
614 }
615}
616
617fn parse_property_descriptor(
618 property: &PropertyDescriptor,
619 preview: Option<&ObjectPreview>,
620) -> devtools_traits::PropertyDescriptor {
621 devtools_traits::PropertyDescriptor {
622 name: property.name.to_string(),
623 value: parse_debugger_value(&property.value, preview),
624 configurable: property.configurable,
625 enumerable: property.enumerable,
626 writable: property.writable,
627 is_accessor: property.isAccessor,
628 }
629}
630
631fn parse_object_preview(preview: &ObjectPreview) -> devtools_traits::ObjectPreview {
632 devtools_traits::ObjectPreview {
633 kind: preview.kind.clone().into(),
634 own_properties: preview.ownProperties.as_ref().map(|properties| {
635 properties
636 .iter()
637 .map(|property| parse_property_descriptor(property, None))
638 .collect()
639 }),
640 own_properties_length: preview.ownPropertiesLength,
641 function: preview
642 .function
643 .as_ref()
644 .map(|fields| devtools_traits::FunctionPreview {
645 name: fields.name.as_ref().map(|s| s.to_string()),
646 display_name: fields.displayName.as_ref().map(|s| s.to_string()),
647 parameter_names: fields
648 .parameterNames
649 .iter()
650 .map(|p| p.to_string())
651 .collect(),
652 is_async: fields.isAsync,
653 is_generator: fields.isGenerator,
654 }),
655 array_length: preview.arrayLength,
656 items: preview.items.as_ref().map(|items| {
657 items
658 .iter()
659 .map(|item| parse_debugger_value(item, None))
660 .collect()
661 }),
662 }
663}
664
665fn parse_debugger_value(
666 value: &DebuggerValue,
667 preview: Option<&ObjectPreview>,
668) -> devtools_traits::DebuggerValue {
669 use devtools_traits::DebuggerValue::*;
670 match &*value.valueType.str() {
671 "undefined" => VoidValue,
672 "null" => NullValue,
673 "boolean" => BooleanValue(value.booleanValue.unwrap_or(false)),
674 "Infinity" => NumberValue(f64::INFINITY),
675 "-Infinity" => NumberValue(f64::NEG_INFINITY),
676 "NaN" => NumberValue(f64::NAN),
677 "-0" => NumberValue(-0.0),
678 "number" => {
679 let num = value.numberValue.map(|f| *f).unwrap_or(0.0);
680 NumberValue(num)
681 },
682 "string" => StringValue(
683 value
684 .stringValue
685 .as_ref()
686 .map(|s| s.to_string())
687 .unwrap_or_default(),
688 ),
689 "object" => {
690 let class = value
691 .objectClass
692 .as_ref()
693 .map(|s| s.to_string())
694 .unwrap_or_else(|| "Object".to_string());
695
696 ObjectValue {
697 uuid: uuid::Uuid::new_v4().to_string(),
698 class,
699 preview: preview.map(parse_object_preview),
700 }
701 },
702 _ => unreachable!(),
703 }
704}