1use std::cell::RefCell;
6
7use devtools_traits::{
8 BlackboxCoverage, DebuggerValue, DevtoolScriptControlMsg, EvaluateJSReply,
9 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::realms::enter_auto_realm;
55use crate::script_runtime::IntroductionType;
56use crate::script_thread::with_script_thread;
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 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 pipeline_id: debugger_pipeline_id,
127 origin: MutableOrigin::new(ImmutableOrigin::new_opaque()),
128 });
129 let global =
130 DebuggerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(cx, &global.origin(), global);
131
132 let mut realm = enter_auto_realm(cx, &*global);
133 let mut realm = realm.current_realm();
134 define_all_exposed_interfaces(&mut realm, global.upcast());
135 assert!(unsafe {
136 JS_DefineDebuggerObject(&mut realm, global.global_scope.reflector().get_jsobject())
138 });
139
140 global
141 }
142
143 pub(crate) fn origin(&self) -> MutableOrigin {
144 self.origin.clone()
145 }
146
147 pub(crate) fn as_global_scope(&self) -> &GlobalScope {
148 self.upcast::<GlobalScope>()
149 }
150
151 pub(crate) fn execute(&self, cx: &mut JSContext) {
152 let mut realm = enter_auto_realm(cx, self);
153 let cx = &mut realm.current_realm();
154
155 let _ = self.global_scope.evaluate_js_on_global(
156 cx,
157 resources::read_string(Resource::DebuggerJS).into(),
158 "",
159 None,
160 None,
161 );
162 }
163
164 pub(crate) fn fire_add_debuggee(
165 &self,
166 cx: &mut JSContext,
167 debuggee_global: &GlobalScope,
168 debuggee_pipeline_id: PipelineId,
169 debuggee_worker_id: Option<WorkerId>,
170 ) {
171 let mut realm = enter_auto_realm(cx, self);
172 let cx = &mut realm;
173 let debuggee_pipeline_id =
174 crate::dom::pipelineid::PipelineId::new(cx, self.upcast(), debuggee_pipeline_id);
175 let event = DomRoot::upcast::<Event>(DebuggerAddDebuggeeEvent::new(
176 cx,
177 self.upcast(),
178 debuggee_global,
179 &debuggee_pipeline_id,
180 debuggee_worker_id.map(|id| id.to_string().into()),
181 ));
182 assert!(
183 event.fire(cx, self.upcast()),
184 "Guaranteed by DebuggerAddDebuggeeEvent::new"
185 );
186 }
187
188 pub(crate) fn fire_eval(
189 &self,
190 cx: &mut JSContext,
191 code: DOMString,
192 debuggee_pipeline_id: PipelineId,
193 debuggee_worker_id: Option<WorkerId>,
194 frame_actor_id: Option<String>,
195 result_sender: GenericSender<EvaluateJSReply>,
196 ) {
197 assert!(
198 self.eval_result_sender
199 .replace(Some(result_sender))
200 .is_none()
201 );
202 let mut realm = enter_auto_realm(cx, self);
203 let cx = &mut realm;
204 let debuggee_pipeline_id =
205 crate::dom::pipelineid::PipelineId::new(cx, self.upcast(), debuggee_pipeline_id);
206 let event = DomRoot::upcast::<Event>(DebuggerEvalEvent::new(
207 cx,
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 ));
214 assert!(
215 event.fire(cx, self.upcast()),
216 "Guaranteed by DebuggerEvalEvent::new"
217 );
218 }
219
220 pub(crate) fn fire_get_possible_breakpoints(
221 &self,
222 cx: &mut JSContext,
223 spidermonkey_id: u32,
224 result_sender: GenericSender<Vec<devtools_traits::RecommendedBreakpointLocation>>,
225 ) {
226 assert!(
227 self.get_possible_breakpoints_result_sender
228 .replace(Some(result_sender))
229 .is_none()
230 );
231 let mut realm = enter_auto_realm(cx, self);
232 let cx = &mut realm.current_realm();
233 let event = DomRoot::upcast::<Event>(DebuggerGetPossibleBreakpointsEvent::new(
234 cx,
235 self.upcast(),
236 spidermonkey_id,
237 ));
238 assert!(
239 event.fire(cx, self.upcast()),
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 cx,
253 self.upcast(),
254 spidermonkey_id,
255 script_id,
256 offset,
257 ));
258 assert!(
259 event.fire(cx, self.upcast()),
260 "Guaranteed by DebuggerSetBreakpointEvent::new"
261 );
262 }
263
264 pub(crate) fn fire_interrupt(&self, cx: &mut js::context::JSContext) {
265 let event = DomRoot::upcast::<Event>(DebuggerInterruptEvent::new(cx, self.upcast()));
266 assert!(
267 event.fire(cx, self.upcast()),
268 "Guaranteed by DebuggerInterruptEvent::new"
269 );
270 }
271
272 pub(crate) fn fire_list_frames(
273 &self,
274 cx: &mut js::context::JSContext,
275 pipeline_id: PipelineId,
276 start: u32,
277 count: u32,
278 result_sender: GenericSender<Vec<String>>,
279 ) {
280 assert!(
281 self.get_list_frame_result_sender
282 .replace(Some(result_sender))
283 .is_none()
284 );
285 let mut realm = enter_auto_realm(cx, self);
286 let cx = &mut realm.current_realm();
287 let pipeline_id = crate::dom::pipelineid::PipelineId::new(cx, self.upcast(), pipeline_id);
288 let event = DomRoot::upcast::<Event>(DebuggerFrameEvent::new(
289 cx,
290 self.upcast(),
291 &pipeline_id,
292 start,
293 count,
294 ));
295 assert!(
296 event.fire(cx, self.upcast()),
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 mut realm = enter_auto_realm(cx, self);
313 let cx = &mut realm.current_realm();
314 let event = DomRoot::upcast::<Event>(DebuggerGetEnvironmentEvent::new(
315 cx,
316 self.upcast(),
317 frame_actor_id.into(),
318 ));
319 assert!(
320 event.fire(cx, self.upcast()),
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 cx,
333 self.upcast(),
334 resume_limit_type.map(DOMString::from),
335 frame_actor_id.map(DOMString::from),
336 ));
337 assert!(
338 event.fire(cx, self.upcast()),
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 cx,
352 self.upcast(),
353 spidermonkey_id,
354 script_id,
355 offset,
356 ));
357 assert!(
358 event.fire(cx, self.upcast()),
359 "Guaranteed by DebuggerClearBreakpointEvent::new"
360 );
361 }
362
363 pub(crate) fn fire_blackbox(
364 &self,
365 cx: &mut JSContext,
366 spidermonkey_id: u32,
367 coverage: BlackboxCoverage,
368 ) {
369 let event = DomRoot::upcast::<Event>(DebuggerBlackboxEvent::new(
370 cx,
371 self.upcast(),
372 spidermonkey_id,
373 coverage,
374 ));
375 assert!(
376 event.fire(cx, self.upcast()),
377 "Guaranteed by DebuggerBlackboxEvent::new"
378 );
379 }
380
381 pub(crate) fn fire_unblackbox(
382 &self,
383 cx: &mut JSContext,
384 spidermonkey_id: u32,
385 coverage: BlackboxCoverage,
386 ) {
387 let event = DomRoot::upcast::<Event>(DebuggerUnblackboxEvent::new(
388 cx,
389 self.upcast(),
390 spidermonkey_id,
391 coverage,
392 ));
393 assert!(
394 event.fire(cx, self.upcast()),
395 "Guaranteed by DebuggerUnblackboxEvent::new"
396 );
397 }
398
399 pub(crate) fn pipeline_id(&self) -> PipelineId {
400 self.pipeline_id
401 }
402}
403
404impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
405 fn NotifyNewSource(&self, args: &NotifyNewSource) {
407 let Some(devtools_chan) = self.as_global_scope().devtools_chan() else {
408 return;
409 };
410 let pipeline_id = PipelineId {
411 namespace_id: PipelineNamespaceId(args.pipelineId.namespaceId),
412 index: Index::new(args.pipelineId.index).expect("`pipelineId.index` must not be zero"),
413 };
414
415 if let Some(introduction_type) = args.introductionType.as_ref() {
416 let url_original = args.url.str();
429 let url_original = ServoUrl::parse(&url_original).ok();
431
432 let url_override = args
437 .urlOverride
438 .as_ref()
439 .map(|url| url.str())
440 .and_then(|url| ServoUrl::parse_with_base(url_original.as_ref(), &url).ok());
442
443 if [
447 IntroductionType::INJECTED_SCRIPT_STR,
448 IntroductionType::EVAL_STR,
449 IntroductionType::DEBUGGER_EVAL_STR,
450 IntroductionType::FUNCTION_STR,
451 IntroductionType::JAVASCRIPT_URL_STR,
452 IntroductionType::EVENT_HANDLER_STR,
453 IntroductionType::DOM_TIMER_STR,
454 ]
455 .contains(&&*introduction_type.str()) &&
456 url_override.is_none()
457 {
458 debug!(
459 "Not creating debuggee: `introductionType` is `{introduction_type}` but no valid url"
460 );
461 return;
462 }
463
464 let inline = introduction_type.str() == "inlineScript" && url_override.is_none();
472 let Some(url) = url_override.or(url_original) else {
473 debug!("Not creating debuggee: no valid url");
474 return;
475 };
476
477 let worker_id = args.workerId.as_ref().map(|id| id.parse().unwrap());
478
479 let source_info = SourceInfo {
480 url,
481 introduction_type: introduction_type.str().to_owned(),
482 inline,
483 worker_id,
484 content: (!inline).then(|| args.text.to_string()),
485 content_type: None, spidermonkey_id: args.spidermonkeyId,
487 };
488 if let Err(error) = devtools_chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
489 self.devtools_to_script_sender.clone(),
490 pipeline_id,
491 source_info,
492 )) {
493 warn!("Failed to send to devtools server: {error:?}");
494 }
495 } else {
496 debug!("Not creating debuggee for script with no `introductionType`");
497 }
498 }
499
500 fn GetPossibleBreakpointsResult(
501 &self,
502 event: &DebuggerGetPossibleBreakpointsEvent,
503 result: Vec<RecommendedBreakpointLocation>,
504 ) {
505 info!("GetPossibleBreakpointsResult: {event:?} {result:?}");
506 let sender = self
507 .get_possible_breakpoints_result_sender
508 .take()
509 .expect("Guaranteed by Self::fire_get_possible_breakpoints()");
510 let _ = sender.send(
511 result
512 .into_iter()
513 .map(|entry| devtools_traits::RecommendedBreakpointLocation {
514 script_id: entry.scriptId,
515 offset: entry.offset,
516 line_number: entry.lineNumber,
517 column_number: entry.columnNumber,
518 is_step_start: entry.isStepStart,
519 })
520 .collect(),
521 );
522 }
523
524 fn EvalResult(&self, _event: &DebuggerEvalEvent, result: &EvalResult) {
529 let sender = self
530 .eval_result_sender
531 .take()
532 .expect("Guaranteed by Self::fire_eval()");
533
534 let has_exception = result.hasException.unwrap_or(false);
535 let value = match serde_json::from_str::<devtools_traits::DebuggerValue>(
536 &result.serializedValue.str(),
537 ) {
538 Ok(value) => value,
539 Err(error) => {
540 warn!("Failed to parse serialized debugger eval value: {error}");
541 devtools_traits::DebuggerValue::StringValue(
542 "failed to parse eval result".to_string(),
543 )
544 },
545 };
546
547 let exception_message = result
548 .exceptionMessage
549 .as_ref()
550 .map(|message| message.str().to_string());
551
552 let reply = EvaluateJSReply {
553 value,
554 exception_message,
555 has_exception,
556 };
557
558 let _ = sender.send(reply);
559 }
560
561 fn RegisterObjectActor(&self, serialized_value: DOMString) -> Option<DOMString> {
562 let chan = self.upcast::<GlobalScope>().devtools_chan()?;
563 let (tx, rx) = channel::<String>().unwrap();
564
565 let value =
566 match serde_json::from_str::<devtools_traits::DebuggerValue>(&serialized_value.str()) {
567 Ok(value) => value,
568 Err(error) => {
569 warn!("Failed to parse serialized debugger object value: {error}");
570 return None;
571 },
572 };
573
574 let msg = ScriptToDevtoolsControlMsg::CreateObjectActor(tx, value);
575 let _ = chan.send(msg);
576
577 rx.recv().ok().map(DOMString::from)
578 }
579
580 fn PauseAndRespond(
581 &self,
582 pipeline_id: &PipelineIdInit,
583 frame_offset: &FrameOffset,
584 pause_reason: &PauseReason,
585 ) {
586 let pipeline_id = PipelineId {
587 namespace_id: PipelineNamespaceId(pipeline_id.namespaceId),
588 index: Index::new(pipeline_id.index).expect("`pipelineId.index` must not be zero"),
589 };
590
591 let frame_offset = devtools_traits::FrameOffset {
592 actor: frame_offset.frameActorId.clone().into(),
593 column: frame_offset.column,
594 line: frame_offset.line,
595 };
596
597 let pause_reason = devtools_traits::PauseReason {
598 type_: pause_reason.type_.clone().into(),
599 on_next: pause_reason.onNext,
600 };
601
602 if let Some(chan) = self.upcast::<GlobalScope>().devtools_chan() {
603 let msg =
604 ScriptToDevtoolsControlMsg::DebuggerPause(pipeline_id, frame_offset, pause_reason);
605 let _ = chan.send(msg);
606 }
607
608 with_script_thread(|script_thread| {
609 script_thread.enter_debugger_pause_loop();
610 });
611 }
612
613 fn RegisterFrameActor(
614 &self,
615 pipeline_id: &PipelineIdInit,
616 result: &FrameInfo,
617 ) -> Option<DOMString> {
618 let pipeline_id = PipelineId {
619 namespace_id: PipelineNamespaceId(pipeline_id.namespaceId),
620 index: Index::new(pipeline_id.index).expect("`pipelineId.index` must not be zero"),
621 };
622
623 let chan = self.upcast::<GlobalScope>().devtools_chan()?;
624 let (tx, rx) = channel::<String>().unwrap();
625
626 let this_value = match serde_json::from_str::<devtools_traits::DebuggerValue>(
627 &result.serializedThis.str(),
628 ) {
629 Ok(this_value) => this_value,
630 Err(error) => {
631 warn!("Failed to parse serialized debugger frame this value: {error}");
632 return None;
633 },
634 };
635
636 let frame = devtools_traits::FrameInfo {
637 display_name: result.displayName.clone().map(String::from),
638 on_stack: result.onStack,
639 oldest: result.oldest,
640 this_value,
641 terminated: result.terminated,
642 type_: result.type_.clone().into(),
643 url: result.url.clone().into(),
644 };
645 let msg = ScriptToDevtoolsControlMsg::CreateFrameActor(tx, pipeline_id, frame);
646 let _ = chan.send(msg);
647
648 rx.recv().ok().map(DOMString::from)
649 }
650
651 fn ListFramesResult(&self, frame_actor_ids: Vec<DOMString>) {
652 info!("ListFramesResult: {frame_actor_ids:?}");
653 let sender = self
654 .get_list_frame_result_sender
655 .take()
656 .expect("Guaranteed by Self::fire_list_frames()");
657
658 let _ = sender.send(frame_actor_ids.into_iter().map(|i| i.into()).collect());
659 }
660
661 fn RegisterEnvironmentActor(
662 &self,
663 environment: &EnvironmentInfo,
664 parent: Option<DOMString>,
665 actor: Option<DOMString>,
666 ) -> Option<DOMString> {
667 let chan = self.upcast::<GlobalScope>().devtools_chan()?;
668 let (tx, rx) = channel::<String>().unwrap();
669
670 let binding_variables = match serde_json::from_str::<Vec<devtools_traits::PropertyDescriptor>>(
671 &environment.serializedBindings.str(),
672 ) {
673 Ok(binding_variables) => binding_variables,
674 Err(error) => {
675 warn!("Failed to parse serialized debugger environment bindings: {error}");
676 return None;
677 },
678 };
679 let object = match environment.serializedObject.as_ref() {
680 Some(serialized_object) => {
681 match serde_json::from_str::<DebuggerValue>(&serialized_object.str()) {
682 Ok(object) => Some(object),
683 Err(error) => {
684 warn!("Failed to parse serialized debugger environment object: {error}");
685 return None;
686 },
687 }
688 },
689 None => None,
690 };
691 let environment = devtools_traits::EnvironmentInfo {
692 type_: environment.type_.clone().map(String::from),
693 scope_kind: environment.scopeKind.clone().map(String::from),
694 function_display_name: environment.functionDisplayName.clone().map(String::from),
695 object,
696 binding_variables,
697 };
698
699 let msg = ScriptToDevtoolsControlMsg::CreateEnvironmentActor(
700 tx,
701 environment,
702 parent.map(String::from),
703 actor.map(String::from),
704 );
705 let _ = chan.send(msg);
706
707 rx.recv().ok().map(DOMString::from)
708 }
709
710 fn GetEnvironmentResult(&self, environment_actor_id: DOMString) {
711 let sender = self
712 .get_environment_result_sender
713 .take()
714 .expect("Guaranteed by Self::fire_get_environment()");
715
716 let _ = sender.send(environment_actor_id.into());
717 }
718}
719
720impl HasOrigin for DebuggerGlobalScope {
721 fn origin(&self) -> MutableOrigin {
722 DebuggerGlobalScope::origin(self)
723 }
724}