1use std::cell::RefCell;
6
7use devtools_traits::{
8 BlackboxCoverage, DebuggerValue, DevtoolScriptControlMsg, EvaluateJSReply,
9 GetEnvironmentRequest, ScriptToDevtoolsControlMsg, SourceInfo, WorkerId,
10};
11use dom_struct::dom_struct;
12use embedder_traits::ScriptToEmbedderChan;
13use embedder_traits::resources::{self, Resource};
14use js::context::JSContext;
15use js::rust::wrappers2::JS_DefineDebuggerObject;
16use net_traits::ResourceThreads;
17use profile_traits::{mem, time};
18use script_bindings::interfaces::HasOrigin;
19use script_bindings::reflector::DomObject;
20use servo_base::generic_channel::{GenericCallback, GenericSender, channel};
21use servo_base::id::{Index, PipelineId, PipelineNamespaceId};
22use servo_constellation_traits::ScriptToConstellationSender;
23use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
24use storage_traits::StorageThreads;
25
26use crate::dom::bindings::codegen::Bindings::DebuggerGetEnvironmentEventBinding::EnvironmentInfo;
27use crate::dom::bindings::codegen::Bindings::DebuggerGlobalScopeBinding;
28use crate::dom::bindings::codegen::Bindings::DebuggerInterruptEventBinding::{
29 FrameInfo, FrameOffset, PauseReason,
30};
31use crate::dom::bindings::codegen::GenericBindings::DebuggerEvalEventBinding::EvalResult;
32use crate::dom::bindings::codegen::GenericBindings::DebuggerGetPossibleBreakpointsEventBinding::RecommendedBreakpointLocation;
33use crate::dom::bindings::codegen::GenericBindings::DebuggerGlobalScopeBinding::{
34 DebuggerGlobalScopeMethods, NotifyNewSource, PipelineIdInit,
35};
36use crate::dom::bindings::inheritance::Castable;
37use crate::dom::bindings::root::DomRoot;
38use crate::dom::bindings::str::DOMString;
39use crate::dom::bindings::utils::define_all_exposed_interfaces;
40use crate::dom::debugger::debuggerblackboxevent::DebuggerBlackboxEvent;
41use crate::dom::debugger::debuggerclearbreakpointevent::DebuggerClearBreakpointEvent;
42use crate::dom::debugger::debuggerframeevent::DebuggerFrameEvent;
43use crate::dom::debugger::debuggergetenvironmentevent::DebuggerGetEnvironmentEvent;
44use crate::dom::debugger::debuggerinterruptevent::DebuggerInterruptEvent;
45use crate::dom::debugger::debuggerresumeevent::DebuggerResumeEvent;
46use crate::dom::debugger::debuggersetbreakpointevent::DebuggerSetBreakpointEvent;
47use crate::dom::debugger::debuggerunblackboxevent::DebuggerUnblackboxEvent;
48use crate::dom::globalscope::GlobalScope;
49use crate::dom::types::{
50 DebuggerAddDebuggeeEvent, DebuggerEvalEvent, DebuggerGetPossibleBreakpointsEvent, Event,
51};
52#[cfg(feature = "webgpu")]
53use crate::dom::webgpu::identityhub::IdentityHub;
54use crate::event_loop::script_thread::with_script_thread;
55use crate::realms::enter_auto_realm;
56use crate::script_runtime::IntroductionType;
57
58#[dom_struct]
59pub(crate) struct DebuggerGlobalScope {
63 global_scope: GlobalScope,
64 #[no_trace]
65 devtools_to_script_sender: GenericSender<DevtoolScriptControlMsg>,
66 #[no_trace]
67 get_possible_breakpoints_result_sender:
68 RefCell<Option<GenericSender<Vec<devtools_traits::RecommendedBreakpointLocation>>>>,
69 #[no_trace]
70 eval_result_sender: RefCell<Option<GenericSender<EvaluateJSReply>>>,
71 #[no_trace]
72 get_list_frame_result_sender: RefCell<Option<GenericSender<Vec<String>>>>,
73 #[no_trace]
74 get_environment_result_sender: RefCell<Option<GenericSender<String>>>,
75 #[no_trace]
76 pipeline_id: PipelineId,
77 #[no_trace]
78 origin: MutableOrigin,
79}
80
81impl DebuggerGlobalScope {
82 #[expect(unsafe_code, clippy::too_many_arguments)]
90 pub(crate) fn new(
91 debugger_pipeline_id: PipelineId,
92 script_to_devtools_sender: Option<GenericCallback<ScriptToDevtoolsControlMsg>>,
93 devtools_to_script_sender: GenericSender<DevtoolScriptControlMsg>,
94 mem_profiler_chan: mem::ProfilerChan,
95 time_profiler_chan: time::ProfilerChan,
96 script_to_constellation_sender: ScriptToConstellationSender,
97 script_to_embedder_chan: ScriptToEmbedderChan,
98 resource_threads: ResourceThreads,
99 storage_threads: StorageThreads,
100 #[cfg(feature = "webgpu")] gpu_id_hub: std::sync::Arc<IdentityHub>,
101 cx: &mut JSContext,
102 ) -> DomRoot<Self> {
103 let global = Box::new(Self {
104 global_scope: GlobalScope::new_inherited(
105 script_to_devtools_sender,
106 mem_profiler_chan,
107 time_profiler_chan,
108 script_to_constellation_sender,
109 script_to_embedder_chan,
110 resource_threads,
111 storage_threads,
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 ),
120 devtools_to_script_sender,
121 get_possible_breakpoints_result_sender: RefCell::new(None),
122 get_list_frame_result_sender: RefCell::new(None),
123 get_environment_result_sender: RefCell::new(None),
124 eval_result_sender: RefCell::new(None),
125 pipeline_id: debugger_pipeline_id,
126 origin: MutableOrigin::new(ImmutableOrigin::new_opaque()),
127 });
128 let global =
129 DebuggerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(cx, &global.origin(), 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 origin(&self) -> MutableOrigin {
143 self.origin.clone()
144 }
145
146 pub(crate) fn as_global_scope(&self) -> &GlobalScope {
147 self.upcast::<GlobalScope>()
148 }
149
150 pub(crate) fn execute(&self, cx: &mut JSContext) {
151 let mut realm = enter_auto_realm(cx, self);
152 let cx = &mut realm.current_realm();
153
154 let _ = self.global_scope.evaluate_js_on_global(
155 cx,
156 resources::read_string(Resource::DebuggerJS).into(),
157 "",
158 None,
159 None,
160 );
161 }
162
163 pub(crate) fn fire_add_debuggee(
164 &self,
165 cx: &mut JSContext,
166 debuggee_global: &GlobalScope,
167 debuggee_pipeline_id: PipelineId,
168 debuggee_worker_id: Option<WorkerId>,
169 ) {
170 let mut realm = enter_auto_realm(cx, self);
171 let cx = &mut realm;
172 let debuggee_pipeline_id =
173 crate::dom::pipelineid::PipelineId::new(cx, self.upcast(), debuggee_pipeline_id);
174 let event = DomRoot::upcast::<Event>(DebuggerAddDebuggeeEvent::new(
175 cx,
176 self.upcast(),
177 debuggee_global,
178 &debuggee_pipeline_id,
179 debuggee_worker_id.map(|id| id.to_string().into()),
180 ));
181 assert!(
182 event.fire(cx, self.upcast()),
183 "Guaranteed by DebuggerAddDebuggeeEvent::new"
184 );
185 }
186
187 pub(crate) fn fire_eval(
188 &self,
189 cx: &mut JSContext,
190 code: DOMString,
191 debuggee_pipeline_id: PipelineId,
192 debuggee_worker_id: Option<WorkerId>,
193 frame_actor_id: Option<String>,
194 result_sender: GenericSender<EvaluateJSReply>,
195 ) {
196 assert!(
197 self.eval_result_sender
198 .replace(Some(result_sender))
199 .is_none()
200 );
201 let mut realm = enter_auto_realm(cx, self);
202 let cx = &mut realm;
203 let debuggee_pipeline_id =
204 crate::dom::pipelineid::PipelineId::new(cx, self.upcast(), debuggee_pipeline_id);
205 let event = DomRoot::upcast::<Event>(DebuggerEvalEvent::new(
206 cx,
207 self.upcast(),
208 code,
209 &debuggee_pipeline_id,
210 debuggee_worker_id.map(|id| id.to_string().into()),
211 frame_actor_id.map(|id| id.into()),
212 ));
213 assert!(
214 event.fire(cx, self.upcast()),
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 mut realm = enter_auto_realm(cx, self);
231 let cx = &mut realm.current_realm();
232 let event = DomRoot::upcast::<Event>(DebuggerGetPossibleBreakpointsEvent::new(
233 cx,
234 self.upcast(),
235 spidermonkey_id,
236 ));
237 assert!(
238 event.fire(cx, self.upcast()),
239 "Guaranteed by DebuggerGetPossibleBreakpointsEvent::new"
240 );
241 }
242
243 pub(crate) fn fire_set_breakpoint(
244 &self,
245 cx: &mut JSContext,
246 spidermonkey_id: u32,
247 script_id: u32,
248 offset: u32,
249 ) {
250 let event = DomRoot::upcast::<Event>(DebuggerSetBreakpointEvent::new(
251 cx,
252 self.upcast(),
253 spidermonkey_id,
254 script_id,
255 offset,
256 ));
257 assert!(
258 event.fire(cx, self.upcast()),
259 "Guaranteed by DebuggerSetBreakpointEvent::new"
260 );
261 }
262
263 pub(crate) fn fire_interrupt(&self, cx: &mut js::context::JSContext) {
264 let event = DomRoot::upcast::<Event>(DebuggerInterruptEvent::new(cx, self.upcast()));
265 assert!(
266 event.fire(cx, self.upcast()),
267 "Guaranteed by DebuggerInterruptEvent::new"
268 );
269 }
270
271 pub(crate) fn fire_list_frames(
272 &self,
273 cx: &mut js::context::JSContext,
274 pipeline_id: PipelineId,
275 start: u32,
276 count: u32,
277 result_sender: GenericSender<Vec<String>>,
278 ) {
279 assert!(
280 self.get_list_frame_result_sender
281 .replace(Some(result_sender))
282 .is_none()
283 );
284 let mut realm = enter_auto_realm(cx, self);
285 let cx = &mut realm.current_realm();
286 let pipeline_id = crate::dom::pipelineid::PipelineId::new(cx, self.upcast(), pipeline_id);
287 let event = DomRoot::upcast::<Event>(DebuggerFrameEvent::new(
288 cx,
289 self.upcast(),
290 &pipeline_id,
291 start,
292 count,
293 ));
294 assert!(
295 event.fire(cx, self.upcast()),
296 "Guaranteed by DebuggerFrameEvent::new"
297 );
298 }
299
300 pub(crate) fn fire_get_environment(
301 &self,
302 cx: &mut JSContext,
303 request: GetEnvironmentRequest,
304 result_sender: GenericSender<String>,
305 ) {
306 assert!(
307 self.get_environment_result_sender
308 .replace(Some(result_sender))
309 .is_none()
310 );
311 let mut realm = enter_auto_realm(cx, self);
312 let cx = &mut realm.current_realm();
313
314 let event = DomRoot::upcast::<Event>(DebuggerGetEnvironmentEvent::new(cx, self, request));
315 assert!(
316 event.fire(cx, self.upcast()),
317 "Guaranteed by DebuggerGetEnvironmentEvent::new"
318 );
319 }
320
321 pub(crate) fn fire_resume(
322 &self,
323 cx: &mut JSContext,
324 resume_limit_type: Option<String>,
325 frame_actor_id: Option<String>,
326 ) {
327 let event = DomRoot::upcast::<Event>(DebuggerResumeEvent::new(
328 cx,
329 self.upcast(),
330 resume_limit_type.map(DOMString::from),
331 frame_actor_id.map(DOMString::from),
332 ));
333 assert!(
334 event.fire(cx, self.upcast()),
335 "Guaranteed by DebuggerResumeEvent::new"
336 );
337 }
338
339 pub(crate) fn fire_clear_breakpoint(
340 &self,
341 cx: &mut JSContext,
342 spidermonkey_id: u32,
343 script_id: u32,
344 offset: u32,
345 ) {
346 let event = DomRoot::upcast::<Event>(DebuggerClearBreakpointEvent::new(
347 cx,
348 self.upcast(),
349 spidermonkey_id,
350 script_id,
351 offset,
352 ));
353 assert!(
354 event.fire(cx, self.upcast()),
355 "Guaranteed by DebuggerClearBreakpointEvent::new"
356 );
357 }
358
359 pub(crate) fn fire_blackbox(
360 &self,
361 cx: &mut JSContext,
362 spidermonkey_id: u32,
363 coverage: BlackboxCoverage,
364 ) {
365 let event = DomRoot::upcast::<Event>(DebuggerBlackboxEvent::new(
366 cx,
367 self.upcast(),
368 spidermonkey_id,
369 coverage,
370 ));
371 assert!(
372 event.fire(cx, self.upcast()),
373 "Guaranteed by DebuggerBlackboxEvent::new"
374 );
375 }
376
377 pub(crate) fn fire_unblackbox(
378 &self,
379 cx: &mut JSContext,
380 spidermonkey_id: u32,
381 coverage: BlackboxCoverage,
382 ) {
383 let event = DomRoot::upcast::<Event>(DebuggerUnblackboxEvent::new(
384 cx,
385 self.upcast(),
386 spidermonkey_id,
387 coverage,
388 ));
389 assert!(
390 event.fire(cx, self.upcast()),
391 "Guaranteed by DebuggerUnblackboxEvent::new"
392 );
393 }
394
395 pub(crate) fn pipeline_id(&self) -> PipelineId {
396 self.pipeline_id
397 }
398}
399
400impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
401 fn NotifyNewSource(&self, args: &NotifyNewSource) {
403 let Some(devtools_chan) = self.as_global_scope().devtools_chan() else {
404 return;
405 };
406 let pipeline_id = PipelineId {
407 namespace_id: PipelineNamespaceId(args.pipelineId.namespaceId),
408 index: Index::new(args.pipelineId.index).expect("`pipelineId.index` must not be zero"),
409 };
410
411 if let Some(introduction_type) = args.introductionType.as_ref() {
412 let url_original = args.url.str();
425 let url_original = ServoUrl::parse(&url_original).ok();
427
428 let url_override = args
433 .urlOverride
434 .as_ref()
435 .map(|url| url.str())
436 .and_then(|url| ServoUrl::parse_with_base(url_original.as_ref(), &url).ok());
438
439 if [
443 IntroductionType::INJECTED_SCRIPT_STR,
444 IntroductionType::EVAL_STR,
445 IntroductionType::DEBUGGER_EVAL_STR,
446 IntroductionType::FUNCTION_STR,
447 IntroductionType::JAVASCRIPT_URL_STR,
448 IntroductionType::EVENT_HANDLER_STR,
449 IntroductionType::DOM_TIMER_STR,
450 ]
451 .contains(&&*introduction_type.str()) &&
452 url_override.is_none()
453 {
454 debug!(
455 "Not creating debuggee: `introductionType` is `{introduction_type}` but no valid url"
456 );
457 return;
458 }
459
460 let inline = introduction_type.str() == "inlineScript" && url_override.is_none();
468 let Some(url) = url_override.or(url_original) else {
469 debug!("Not creating debuggee: no valid url");
470 return;
471 };
472
473 let worker_id = args.workerId.as_ref().map(|id| id.parse().unwrap());
474
475 let source_info = SourceInfo {
476 url,
477 introduction_type: introduction_type.str().to_owned(),
478 inline,
479 worker_id,
480 content: (!inline).then(|| args.text.to_string()),
481 content_type: None, spidermonkey_id: args.spidermonkeyId,
483 };
484 if let Err(error) = devtools_chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
485 self.devtools_to_script_sender.clone(),
486 pipeline_id,
487 source_info,
488 )) {
489 warn!("Failed to send to devtools server: {error:?}");
490 }
491 } else {
492 debug!("Not creating debuggee for script with no `introductionType`");
493 }
494 }
495
496 fn GetPossibleBreakpointsResult(
497 &self,
498 event: &DebuggerGetPossibleBreakpointsEvent,
499 result: Vec<RecommendedBreakpointLocation>,
500 ) {
501 info!("GetPossibleBreakpointsResult: {event:?} {result:?}");
502 let sender = self
503 .get_possible_breakpoints_result_sender
504 .take()
505 .expect("Guaranteed by Self::fire_get_possible_breakpoints()");
506 let _ = sender.send(
507 result
508 .into_iter()
509 .map(|entry| devtools_traits::RecommendedBreakpointLocation {
510 script_id: entry.scriptId,
511 offset: entry.offset,
512 line_number: entry.lineNumber,
513 column_number: entry.columnNumber,
514 is_step_start: entry.isStepStart,
515 })
516 .collect(),
517 );
518 }
519
520 fn EvalResult(&self, _event: &DebuggerEvalEvent, result: &EvalResult) {
525 let sender = self
526 .eval_result_sender
527 .take()
528 .expect("Guaranteed by Self::fire_eval()");
529
530 let has_exception = result.hasException.unwrap_or(false);
531 let value = match serde_json::from_str::<devtools_traits::DebuggerValue>(
532 &result.serializedValue.str(),
533 ) {
534 Ok(value) => value,
535 Err(error) => {
536 warn!("Failed to parse serialized debugger eval value: {error}");
537 devtools_traits::DebuggerValue::StringValue(
538 "failed to parse eval result".to_string(),
539 )
540 },
541 };
542
543 let exception_message = result
544 .exceptionMessage
545 .as_ref()
546 .map(|message| message.str().to_string());
547
548 let reply = EvaluateJSReply {
549 value,
550 exception_message,
551 has_exception,
552 };
553
554 let _ = sender.send(reply);
555 }
556
557 fn RegisterObjectActor(&self, serialized_value: DOMString) -> Option<DOMString> {
558 let chan = self.upcast::<GlobalScope>().devtools_chan()?;
559 let (tx, rx) = channel::<String>().unwrap();
560
561 let value =
562 match serde_json::from_str::<devtools_traits::DebuggerValue>(&serialized_value.str()) {
563 Ok(value) => value,
564 Err(error) => {
565 warn!("Failed to parse serialized debugger object value: {error}");
566 return None;
567 },
568 };
569
570 let msg = ScriptToDevtoolsControlMsg::CreateObjectActor(tx, value);
571 let _ = chan.send(msg);
572
573 rx.recv().ok().map(DOMString::from)
574 }
575
576 fn PauseAndRespond(
577 &self,
578 pipeline_id: &PipelineIdInit,
579 frame_offset: &FrameOffset,
580 pause_reason: &PauseReason,
581 ) {
582 let pipeline_id = PipelineId {
583 namespace_id: PipelineNamespaceId(pipeline_id.namespaceId),
584 index: Index::new(pipeline_id.index).expect("`pipelineId.index` must not be zero"),
585 };
586
587 let frame_offset = devtools_traits::FrameOffset {
588 actor: frame_offset.frameActorId.clone().into(),
589 column: frame_offset.column,
590 line: frame_offset.line,
591 };
592
593 let pause_reason = devtools_traits::PauseReason {
594 type_: pause_reason.type_.clone().into(),
595 on_next: pause_reason.onNext,
596 };
597
598 if let Some(chan) = self.upcast::<GlobalScope>().devtools_chan() {
599 let msg =
600 ScriptToDevtoolsControlMsg::DebuggerPause(pipeline_id, frame_offset, pause_reason);
601 let _ = chan.send(msg);
602 }
603
604 with_script_thread(|script_thread| {
605 script_thread.enter_debugger_pause_loop();
606 });
607 }
608
609 fn RegisterFrameActor(
610 &self,
611 pipeline_id: &PipelineIdInit,
612 result: &FrameInfo,
613 ) -> Option<DOMString> {
614 let pipeline_id = PipelineId {
615 namespace_id: PipelineNamespaceId(pipeline_id.namespaceId),
616 index: Index::new(pipeline_id.index).expect("`pipelineId.index` must not be zero"),
617 };
618
619 let chan = self.upcast::<GlobalScope>().devtools_chan()?;
620 let (tx, rx) = channel::<String>().unwrap();
621
622 let this_value = match serde_json::from_str::<devtools_traits::DebuggerValue>(
623 &result.serializedThis.str(),
624 ) {
625 Ok(this_value) => this_value,
626 Err(error) => {
627 warn!("Failed to parse serialized debugger frame this value: {error}");
628 return None;
629 },
630 };
631
632 let frame = devtools_traits::FrameInfo {
633 display_name: result.displayName.clone().map(String::from),
634 on_stack: result.onStack,
635 oldest: result.oldest,
636 this_value,
637 terminated: result.terminated,
638 type_: result.type_.clone().into(),
639 url: result.url.clone().into(),
640 };
641 let msg = ScriptToDevtoolsControlMsg::CreateFrameActor(tx, pipeline_id, frame);
642 let _ = chan.send(msg);
643
644 rx.recv().ok().map(DOMString::from)
645 }
646
647 fn ListFramesResult(&self, frame_actor_ids: Vec<DOMString>) {
648 info!("ListFramesResult: {frame_actor_ids:?}");
649 let sender = self
650 .get_list_frame_result_sender
651 .take()
652 .expect("Guaranteed by Self::fire_list_frames()");
653
654 let _ = sender.send(frame_actor_ids.into_iter().map(|i| i.into()).collect());
655 }
656
657 fn RegisterEnvironmentActor(
658 &self,
659 environment: &EnvironmentInfo,
660 parent: Option<DOMString>,
661 actor: Option<DOMString>,
662 ) -> Option<DOMString> {
663 let chan = self.upcast::<GlobalScope>().devtools_chan()?;
664 let (tx, rx) = channel::<String>().unwrap();
665
666 let binding_variables = match serde_json::from_str::<Vec<devtools_traits::PropertyDescriptor>>(
667 &environment.serializedBindings.str(),
668 ) {
669 Ok(binding_variables) => binding_variables,
670 Err(error) => {
671 warn!("Failed to parse serialized debugger environment bindings: {error}");
672 return None;
673 },
674 };
675 let object = match environment.serializedObject.as_ref() {
676 Some(serialized_object) => {
677 match serde_json::from_str::<DebuggerValue>(&serialized_object.str()) {
678 Ok(object) => Some(object),
679 Err(error) => {
680 warn!("Failed to parse serialized debugger environment object: {error}");
681 return None;
682 },
683 }
684 },
685 None => None,
686 };
687 let environment = devtools_traits::EnvironmentInfo {
688 type_: environment.type_.clone().map(String::from),
689 scope_kind: environment.scopeKind.clone().map(String::from),
690 function_display_name: environment.functionDisplayName.clone().map(String::from),
691 object,
692 binding_variables,
693 };
694
695 let msg = ScriptToDevtoolsControlMsg::CreateEnvironmentActor(
696 tx,
697 environment,
698 parent.map(String::from),
699 actor.map(String::from),
700 );
701 let _ = chan.send(msg);
702
703 rx.recv().ok().map(DOMString::from)
704 }
705
706 fn GetEnvironmentResult(&self, environment_actor_id: DOMString) {
707 let sender = self
708 .get_environment_result_sender
709 .take()
710 .expect("Guaranteed by Self::fire_get_environment()");
711
712 let _ = sender.send(environment_actor_id.into());
713 }
714}
715
716impl HasOrigin for DebuggerGlobalScope {
717 fn origin(&self) -> MutableOrigin {
718 DebuggerGlobalScope::origin(self)
719 }
720}