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