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 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]
62pub(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 #[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, 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 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 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 let url_original = args.url.str();
388 let url_original = ServoUrl::parse(&url_original).ok();
390
391 let url_override = args
396 .urlOverride
397 .as_ref()
398 .map(|url| url.str())
399 .and_then(|url| ServoUrl::parse_with_base(url_original.as_ref(), &url).ok());
401
402 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 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, 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 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}