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