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 servo_base::generic_channel::{GenericCallback, GenericSender, channel};
18use servo_base::id::{Index, PipelineId, PipelineNamespaceId};
19use servo_constellation_traits::ScriptToConstellationChan;
20use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
21use storage_traits::StorageThreads;
22
23use crate::dom::bindings::codegen::Bindings::DebuggerEvalEventBinding::DebuggerValue;
24use crate::dom::bindings::codegen::Bindings::DebuggerEvalEventBinding::GenericBindings::ObjectPreview;
25use crate::dom::bindings::codegen::Bindings::DebuggerGetEnvironmentEventBinding::{
26 EnvironmentInfo, EnvironmentVariable,
27};
28use crate::dom::bindings::codegen::Bindings::DebuggerGlobalScopeBinding;
29use crate::dom::bindings::codegen::Bindings::DebuggerInterruptEventBinding::{
30 FrameInfo, FrameOffset, PauseReason,
31};
32use crate::dom::bindings::codegen::GenericBindings::DebuggerEvalEventBinding::{
33 EvalResult, PropertyDescriptor,
34};
35use crate::dom::bindings::codegen::GenericBindings::DebuggerGetPossibleBreakpointsEventBinding::RecommendedBreakpointLocation;
36use crate::dom::bindings::codegen::GenericBindings::DebuggerGlobalScopeBinding::{
37 DebuggerGlobalScopeMethods, NotifyNewSource, PipelineIdInit,
38};
39use crate::dom::bindings::inheritance::Castable;
40use crate::dom::bindings::reflector::DomObject;
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 can_gc: CanGc,
160 debuggee_global: &GlobalScope,
161 debuggee_pipeline_id: PipelineId,
162 debuggee_worker_id: Option<WorkerId>,
163 ) {
164 let _realm = enter_realm(self);
165 let debuggee_pipeline_id =
166 crate::dom::pipelineid::PipelineId::new(self.upcast(), debuggee_pipeline_id, can_gc);
167 let event = DomRoot::upcast::<Event>(DebuggerAddDebuggeeEvent::new(
168 self.upcast(),
169 debuggee_global,
170 &debuggee_pipeline_id,
171 debuggee_worker_id.map(|id| id.to_string().into()),
172 can_gc,
173 ));
174 assert!(
175 event.fire(self.upcast(), can_gc),
176 "Guaranteed by DebuggerAddDebuggeeEvent::new"
177 );
178 }
179
180 pub(crate) fn fire_eval(
181 &self,
182 can_gc: CanGc,
183 code: DOMString,
184 debuggee_pipeline_id: PipelineId,
185 debuggee_worker_id: Option<WorkerId>,
186 frame_actor_id: Option<String>,
187 result_sender: GenericSender<EvaluateJSReply>,
188 ) {
189 assert!(
190 self.eval_result_sender
191 .replace(Some(result_sender))
192 .is_none()
193 );
194 let _realm = enter_realm(self);
195 let debuggee_pipeline_id =
196 crate::dom::pipelineid::PipelineId::new(self.upcast(), debuggee_pipeline_id, can_gc);
197 let event = DomRoot::upcast::<Event>(DebuggerEvalEvent::new(
198 self.upcast(),
199 code,
200 &debuggee_pipeline_id,
201 debuggee_worker_id.map(|id| id.to_string().into()),
202 frame_actor_id.map(|id| id.into()),
203 can_gc,
204 ));
205 assert!(
206 event.fire(self.upcast(), can_gc),
207 "Guaranteed by DebuggerEvalEvent::new"
208 );
209 }
210
211 pub(crate) fn fire_get_possible_breakpoints(
212 &self,
213 can_gc: CanGc,
214 spidermonkey_id: u32,
215 result_sender: GenericSender<Vec<devtools_traits::RecommendedBreakpointLocation>>,
216 ) {
217 assert!(
218 self.get_possible_breakpoints_result_sender
219 .replace(Some(result_sender))
220 .is_none()
221 );
222 let _realm = enter_realm(self);
223 let event = DomRoot::upcast::<Event>(DebuggerGetPossibleBreakpointsEvent::new(
224 self.upcast(),
225 spidermonkey_id,
226 can_gc,
227 ));
228 assert!(
229 event.fire(self.upcast(), can_gc),
230 "Guaranteed by DebuggerGetPossibleBreakpointsEvent::new"
231 );
232 }
233
234 pub(crate) fn fire_set_breakpoint(
235 &self,
236 can_gc: CanGc,
237 spidermonkey_id: u32,
238 script_id: u32,
239 offset: u32,
240 ) {
241 let event = DomRoot::upcast::<Event>(DebuggerSetBreakpointEvent::new(
242 self.upcast(),
243 spidermonkey_id,
244 script_id,
245 offset,
246 can_gc,
247 ));
248 assert!(
249 event.fire(self.upcast(), can_gc),
250 "Guaranteed by DebuggerSetBreakpointEvent::new"
251 );
252 }
253
254 pub(crate) fn fire_interrupt(&self, can_gc: CanGc) {
255 let event = DomRoot::upcast::<Event>(DebuggerInterruptEvent::new(self.upcast(), can_gc));
256 assert!(
257 event.fire(self.upcast(), can_gc),
258 "Guaranteed by DebuggerInterruptEvent::new"
259 );
260 }
261
262 pub(crate) fn fire_list_frames(
263 &self,
264 pipeline_id: PipelineId,
265 start: u32,
266 count: u32,
267 result_sender: GenericSender<Vec<String>>,
268 can_gc: CanGc,
269 ) {
270 assert!(
271 self.get_list_frame_result_sender
272 .replace(Some(result_sender))
273 .is_none()
274 );
275 let _realm = enter_realm(self);
276 let pipeline_id =
277 crate::dom::pipelineid::PipelineId::new(self.upcast(), pipeline_id, can_gc);
278 let event = DomRoot::upcast::<Event>(DebuggerFrameEvent::new(
279 self.upcast(),
280 &pipeline_id,
281 start,
282 count,
283 can_gc,
284 ));
285 assert!(
286 event.fire(self.upcast(), can_gc),
287 "Guaranteed by DebuggerFrameEvent::new"
288 );
289 }
290
291 pub(crate) fn fire_get_environment(
292 &self,
293 frame_actor_id: String,
294 result_sender: GenericSender<String>,
295 can_gc: CanGc,
296 ) {
297 assert!(
298 self.get_environment_result_sender
299 .replace(Some(result_sender))
300 .is_none()
301 );
302 let _realm = enter_realm(self);
303 let event = DomRoot::upcast::<Event>(DebuggerGetEnvironmentEvent::new(
304 self.upcast(),
305 frame_actor_id.into(),
306 can_gc,
307 ));
308 assert!(
309 event.fire(self.upcast(), can_gc),
310 "Guaranteed by DebuggerGetEnvironmentEvent::new"
311 );
312 }
313
314 pub(crate) fn fire_resume(
315 &self,
316 resume_limit_type: Option<String>,
317 frame_actor_id: Option<String>,
318 can_gc: CanGc,
319 ) {
320 let event = DomRoot::upcast::<Event>(DebuggerResumeEvent::new(
321 self.upcast(),
322 resume_limit_type.map(DOMString::from),
323 frame_actor_id.map(DOMString::from),
324 can_gc,
325 ));
326 assert!(
327 event.fire(self.upcast(), can_gc),
328 "Guaranteed by DebuggerResumeEvent::new"
329 );
330 }
331
332 pub(crate) fn fire_clear_breakpoint(
333 &self,
334 can_gc: CanGc,
335 spidermonkey_id: u32,
336 script_id: u32,
337 offset: u32,
338 ) {
339 let event = DomRoot::upcast::<Event>(DebuggerClearBreakpointEvent::new(
340 self.upcast(),
341 spidermonkey_id,
342 script_id,
343 offset,
344 can_gc,
345 ));
346 assert!(
347 event.fire(self.upcast(), can_gc),
348 "Guaranteed by DebuggerClearBreakpointEvent::new"
349 );
350 }
351}
352
353impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
354 fn NotifyNewSource(&self, args: &NotifyNewSource) {
356 let Some(devtools_chan) = self.as_global_scope().devtools_chan() else {
357 return;
358 };
359 let pipeline_id = PipelineId {
360 namespace_id: PipelineNamespaceId(args.pipelineId.namespaceId),
361 index: Index::new(args.pipelineId.index).expect("`pipelineId.index` must not be zero"),
362 };
363
364 if let Some(introduction_type) = args.introductionType.as_ref() {
365 let url_original = args.url.str();
378 let url_original = ServoUrl::parse(&url_original).ok();
380
381 let url_override = args
386 .urlOverride
387 .as_ref()
388 .map(|url| url.str())
389 .and_then(|url| ServoUrl::parse_with_base(url_original.as_ref(), &url).ok());
391
392 if [
396 IntroductionType::INJECTED_SCRIPT_STR,
397 IntroductionType::EVAL_STR,
398 IntroductionType::DEBUGGER_EVAL_STR,
399 IntroductionType::FUNCTION_STR,
400 IntroductionType::JAVASCRIPT_URL_STR,
401 IntroductionType::EVENT_HANDLER_STR,
402 IntroductionType::DOM_TIMER_STR,
403 ]
404 .contains(&&*introduction_type.str()) &&
405 url_override.is_none()
406 {
407 debug!(
408 "Not creating debuggee: `introductionType` is `{introduction_type}` but no valid url"
409 );
410 return;
411 }
412
413 let inline = introduction_type.str() == "inlineScript" && url_override.is_none();
421 let Some(url) = url_override.or(url_original) else {
422 debug!("Not creating debuggee: no valid url");
423 return;
424 };
425
426 let worker_id = args.workerId.as_ref().map(|id| id.parse().unwrap());
427
428 let source_info = SourceInfo {
429 url,
430 introduction_type: introduction_type.str().to_owned(),
431 inline,
432 worker_id,
433 content: (!inline).then(|| args.text.to_string()),
434 content_type: None, spidermonkey_id: args.spidermonkeyId,
436 };
437 if let Err(error) = devtools_chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
438 self.devtools_to_script_sender.clone(),
439 pipeline_id,
440 source_info,
441 )) {
442 warn!("Failed to send to devtools server: {error:?}");
443 }
444 } else {
445 debug!("Not creating debuggee for script with no `introductionType`");
446 }
447 }
448
449 fn GetPossibleBreakpointsResult(
450 &self,
451 event: &DebuggerGetPossibleBreakpointsEvent,
452 result: Vec<RecommendedBreakpointLocation>,
453 ) {
454 info!("GetPossibleBreakpointsResult: {event:?} {result:?}");
455 let sender = self
456 .get_possible_breakpoints_result_sender
457 .take()
458 .expect("Guaranteed by Self::fire_get_possible_breakpoints()");
459 let _ = sender.send(
460 result
461 .into_iter()
462 .map(|entry| devtools_traits::RecommendedBreakpointLocation {
463 script_id: entry.scriptId,
464 offset: entry.offset,
465 line_number: entry.lineNumber,
466 column_number: entry.columnNumber,
467 is_step_start: entry.isStepStart,
468 })
469 .collect(),
470 );
471 }
472
473 fn EvalResult(&self, _event: &DebuggerEvalEvent, result: &EvalResult) {
478 let sender = self
479 .eval_result_sender
480 .take()
481 .expect("Guaranteed by Self::fire_eval()");
482
483 let reply = EvaluateJSReply {
484 value: parse_debugger_value(&result.value, result.preview.as_ref()),
485 has_exception: result.hasException.unwrap_or(false),
486 };
487
488 let _ = sender.send(reply);
489 }
490
491 fn PauseAndRespond(
492 &self,
493 pipeline_id: &PipelineIdInit,
494 frame_offset: &FrameOffset,
495 pause_reason: &PauseReason,
496 ) {
497 let pipeline_id = PipelineId {
498 namespace_id: PipelineNamespaceId(pipeline_id.namespaceId),
499 index: Index::new(pipeline_id.index).expect("`pipelineId.index` must not be zero"),
500 };
501
502 let frame_offset = devtools_traits::FrameOffset {
503 actor: frame_offset.frameActorId.clone().into(),
504 column: frame_offset.column,
505 line: frame_offset.line,
506 };
507
508 let pause_reason = devtools_traits::PauseReason {
509 type_: pause_reason.type_.clone().into(),
510 on_next: pause_reason.onNext,
511 };
512
513 if let Some(chan) = self.upcast::<GlobalScope>().devtools_chan() {
514 let msg =
515 ScriptToDevtoolsControlMsg::DebuggerPause(pipeline_id, frame_offset, pause_reason);
516 let _ = chan.send(msg);
517 }
518
519 with_script_thread(|script_thread| {
520 script_thread.enter_debugger_pause_loop();
521 });
522 }
523
524 fn RegisterFrameActor(
525 &self,
526 pipeline_id: &PipelineIdInit,
527 result: &FrameInfo,
528 ) -> Option<DOMString> {
529 let pipeline_id = PipelineId {
530 namespace_id: PipelineNamespaceId(pipeline_id.namespaceId),
531 index: Index::new(pipeline_id.index).expect("`pipelineId.index` must not be zero"),
532 };
533
534 let chan = self.upcast::<GlobalScope>().devtools_chan()?;
535 let (tx, rx) = channel::<String>().unwrap();
536
537 let frame = devtools_traits::FrameInfo {
538 display_name: result.displayName.clone().into(),
539 on_stack: result.onStack,
540 oldest: result.oldest,
541 terminated: result.terminated,
542 type_: result.type_.clone().into(),
543 url: result.url.clone().into(),
544 };
545 let msg = ScriptToDevtoolsControlMsg::CreateFrameActor(tx, pipeline_id, frame);
546 let _ = chan.send(msg);
547
548 rx.recv().ok().map(DOMString::from)
549 }
550
551 fn ListFramesResult(&self, frame_actor_ids: Vec<DOMString>) {
552 info!("ListFramesResult: {frame_actor_ids:?}");
553 let sender = self
554 .get_list_frame_result_sender
555 .take()
556 .expect("Guaranteed by Self::fire_list_frames()");
557
558 let _ = sender.send(frame_actor_ids.into_iter().map(|i| i.into()).collect());
559 }
560
561 fn RegisterEnvironmentActor(
562 &self,
563 environment: &EnvironmentInfo,
564 parent: Option<DOMString>,
565 ) -> Option<DOMString> {
566 let chan = self.upcast::<GlobalScope>().devtools_chan()?;
567 let (tx, rx) = channel::<String>().unwrap();
568
569 let environment = devtools_traits::EnvironmentInfo {
570 type_: environment.type_.clone().map(String::from),
571 scope_kind: environment.scopeKind.clone().map(String::from),
572 function_display_name: environment.functionDisplayName.clone().map(String::from),
573 binding_variables: environment
574 .bindingVariables
575 .as_deref()
576 .into_iter()
577 .flatten()
578 .map(|EnvironmentVariable { property, preview }| {
579 parse_property_descriptor(property, preview.as_ref())
580 })
581 .collect(),
582 };
583
584 let msg = ScriptToDevtoolsControlMsg::CreateEnvironmentActor(
585 tx,
586 environment,
587 parent.map(String::from),
588 );
589 let _ = chan.send(msg);
590
591 rx.recv().ok().map(DOMString::from)
592 }
593
594 fn GetEnvironmentResult(&self, environment_actor_id: DOMString) {
595 let sender = self
596 .get_environment_result_sender
597 .take()
598 .expect("Guaranteed by Self::fire_get_environment()");
599
600 let _ = sender.send(environment_actor_id.into());
601 }
602}
603
604fn parse_property_descriptor(
605 property: &PropertyDescriptor,
606 preview: Option<&ObjectPreview>,
607) -> devtools_traits::PropertyDescriptor {
608 devtools_traits::PropertyDescriptor {
609 name: property.name.to_string(),
610 value: parse_debugger_value(&property.value, preview),
611 configurable: property.configurable,
612 enumerable: property.enumerable,
613 writable: property.writable,
614 is_accessor: property.isAccessor,
615 }
616}
617
618fn parse_object_preview(preview: &ObjectPreview) -> devtools_traits::ObjectPreview {
619 devtools_traits::ObjectPreview {
620 kind: preview.kind.clone().into(),
621 own_properties: preview.ownProperties.as_ref().map(|properties| {
622 properties
623 .iter()
624 .map(|property| parse_property_descriptor(property, None))
625 .collect()
626 }),
627 own_properties_length: preview.ownPropertiesLength,
628 function: preview
629 .function
630 .as_ref()
631 .map(|fields| devtools_traits::FunctionPreview {
632 name: fields.name.as_ref().map(|s| s.to_string()),
633 display_name: fields.displayName.as_ref().map(|s| s.to_string()),
634 parameter_names: fields
635 .parameterNames
636 .iter()
637 .map(|p| p.to_string())
638 .collect(),
639 is_async: fields.isAsync,
640 is_generator: fields.isGenerator,
641 }),
642 array_length: preview.arrayLength,
643 }
644}
645
646fn parse_debugger_value(
647 value: &DebuggerValue,
648 preview: Option<&ObjectPreview>,
649) -> devtools_traits::DebuggerValue {
650 use devtools_traits::DebuggerValue::*;
651 match &*value.valueType.str() {
652 "undefined" => VoidValue,
653 "null" => NullValue,
654 "boolean" => BooleanValue(value.booleanValue.unwrap_or(false)),
655 "number" => {
656 let num = value.numberValue.map(|f| *f).unwrap_or(0.0);
657 NumberValue(num)
658 },
659 "string" => StringValue(
660 value
661 .stringValue
662 .as_ref()
663 .map(|s| s.to_string())
664 .unwrap_or_default(),
665 ),
666 "object" => {
667 let class = value
668 .objectClass
669 .as_ref()
670 .map(|s| s.to_string())
671 .unwrap_or_else(|| "Object".to_string());
672
673 ObjectValue {
674 uuid: uuid::Uuid::new_v4().to_string(),
675 class,
676 preview: preview.map(parse_object_preview),
677 }
678 },
679 _ => unreachable!(),
680 }
681}