1use std::borrow::Cow;
6use std::cell::RefCell;
7
8use base::generic_channel::{GenericCallback, GenericSender};
9use base::id::{Index, PipelineId, PipelineNamespaceId};
10use constellation_traits::ScriptToConstellationChan;
11use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, SourceInfo, WorkerId};
12use dom_struct::dom_struct;
13use embedder_traits::resources::{self, Resource};
14use embedder_traits::{JavaScriptEvaluationError, ScriptToEmbedderChan};
15use js::jsval::UndefinedValue;
16use js::rust::wrappers2::JS_DefineDebuggerObject;
17use net_traits::ResourceThreads;
18use profile_traits::{mem, time};
19use script_bindings::codegen::GenericBindings::DebuggerGetPossibleBreakpointsEventBinding::RecommendedBreakpointLocation;
20use script_bindings::codegen::GenericBindings::DebuggerGlobalScopeBinding::{
21 DebuggerGlobalScopeMethods, NotifyNewSource,
22};
23use script_bindings::realms::InRealm;
24use script_bindings::reflector::DomObject;
25use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
26use storage_traits::StorageThreads;
27
28use crate::dom::bindings::codegen::Bindings::DebuggerGlobalScopeBinding;
29use crate::dom::bindings::codegen::Bindings::DebuggerPauseEventBinding::PauseFrameResult;
30use crate::dom::bindings::error::report_pending_exception;
31use crate::dom::bindings::inheritance::Castable;
32use crate::dom::bindings::root::DomRoot;
33use crate::dom::bindings::utils::define_all_exposed_interfaces;
34use crate::dom::debuggerclearbreakpointevent::DebuggerClearBreakpointEvent;
35use crate::dom::debuggerpauseevent::DebuggerPauseEvent;
36use crate::dom::debuggersetbreakpointevent::DebuggerSetBreakpointEvent;
37use crate::dom::globalscope::GlobalScope;
38use crate::dom::types::{DebuggerAddDebuggeeEvent, DebuggerGetPossibleBreakpointsEvent, Event};
39#[cfg(feature = "webgpu")]
40use crate::dom::webgpu::identityhub::IdentityHub;
41use crate::realms::{enter_auto_realm, enter_realm};
42use crate::script_runtime::{CanGc, IntroductionType, JSContext};
43
44#[dom_struct]
45pub(crate) struct DebuggerGlobalScope {
49 global_scope: GlobalScope,
50 #[no_trace]
51 devtools_to_script_sender: GenericSender<DevtoolScriptControlMsg>,
52 #[no_trace]
53 get_possible_breakpoints_result_sender:
54 RefCell<Option<GenericSender<Vec<devtools_traits::RecommendedBreakpointLocation>>>>,
55 #[no_trace]
56 get_frame_result_sender: RefCell<Option<GenericSender<devtools_traits::PauseFrameResult>>>,
57}
58
59impl DebuggerGlobalScope {
60 #[expect(unsafe_code, clippy::too_many_arguments)]
68 pub(crate) fn new(
69 debugger_pipeline_id: PipelineId,
70 script_to_devtools_sender: Option<GenericCallback<ScriptToDevtoolsControlMsg>>,
71 devtools_to_script_sender: GenericSender<DevtoolScriptControlMsg>,
72 mem_profiler_chan: mem::ProfilerChan,
73 time_profiler_chan: time::ProfilerChan,
74 script_to_constellation_chan: ScriptToConstellationChan,
75 script_to_embedder_chan: ScriptToEmbedderChan,
76 resource_threads: ResourceThreads,
77 storage_threads: StorageThreads,
78 #[cfg(feature = "webgpu")] gpu_id_hub: std::sync::Arc<IdentityHub>,
79 cx: &mut js::context::JSContext,
80 ) -> DomRoot<Self> {
81 let global = Box::new(Self {
82 global_scope: GlobalScope::new_inherited(
83 debugger_pipeline_id,
84 script_to_devtools_sender,
85 mem_profiler_chan,
86 time_profiler_chan,
87 script_to_constellation_chan,
88 script_to_embedder_chan,
89 resource_threads,
90 storage_threads,
91 MutableOrigin::new(ImmutableOrigin::new_opaque()),
92 ServoUrl::parse_with_base(None, "about:internal/debugger")
93 .expect("Guaranteed by argument"),
94 None,
95 #[cfg(feature = "webgpu")]
96 gpu_id_hub,
97 None,
98 false,
99 None, ),
101 devtools_to_script_sender,
102 get_possible_breakpoints_result_sender: RefCell::new(None),
103 get_frame_result_sender: RefCell::new(None),
104 });
105 let global = DebuggerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(cx.into(), global);
106
107 let mut realm = enter_auto_realm(cx, &*global);
108 let mut realm = realm.current_realm();
109 define_all_exposed_interfaces(&mut realm, global.upcast());
110 assert!(unsafe {
111 JS_DefineDebuggerObject(&mut realm, global.global_scope.reflector().get_jsobject())
113 });
114
115 global
116 }
117
118 pub(crate) fn get_cx() -> JSContext {
120 GlobalScope::get_cx()
121 }
122
123 pub(crate) fn as_global_scope(&self) -> &GlobalScope {
124 self.upcast::<GlobalScope>()
125 }
126
127 fn evaluate_js(
128 &self,
129 script: Cow<'_, str>,
130 can_gc: CanGc,
131 ) -> Result<(), JavaScriptEvaluationError> {
132 rooted!(in (*Self::get_cx()) let mut rval = UndefinedValue());
133 self.global_scope
134 .evaluate_js_on_global(script, "", None, rval.handle_mut(), can_gc)
135 }
136
137 pub(crate) fn execute(&self, can_gc: CanGc) {
138 if self
139 .evaluate_js(resources::read_string(Resource::DebuggerJS).into(), can_gc)
140 .is_err()
141 {
142 let ar = enter_realm(self);
143 report_pending_exception(Self::get_cx(), true, InRealm::Entered(&ar), can_gc);
144 }
145 }
146
147 pub(crate) fn fire_add_debuggee(
148 &self,
149 can_gc: CanGc,
150 debuggee_global: &GlobalScope,
151 debuggee_pipeline_id: PipelineId,
152 debuggee_worker_id: Option<WorkerId>,
153 ) {
154 let _realm = enter_realm(self);
155 let debuggee_pipeline_id =
156 crate::dom::pipelineid::PipelineId::new(self.upcast(), debuggee_pipeline_id, can_gc);
157 let event = DomRoot::upcast::<Event>(DebuggerAddDebuggeeEvent::new(
158 self.upcast(),
159 debuggee_global,
160 &debuggee_pipeline_id,
161 debuggee_worker_id.map(|id| id.to_string().into()),
162 can_gc,
163 ));
164 assert!(
165 event.fire(self.upcast(), can_gc),
166 "Guaranteed by DebuggerAddDebuggeeEvent::new"
167 );
168 }
169
170 pub(crate) fn fire_get_possible_breakpoints(
171 &self,
172 can_gc: CanGc,
173 spidermonkey_id: u32,
174 result_sender: GenericSender<Vec<devtools_traits::RecommendedBreakpointLocation>>,
175 ) {
176 assert!(
177 self.get_possible_breakpoints_result_sender
178 .replace(Some(result_sender))
179 .is_none()
180 );
181 let _realm = enter_realm(self);
182 let event = DomRoot::upcast::<Event>(DebuggerGetPossibleBreakpointsEvent::new(
183 self.upcast(),
184 spidermonkey_id,
185 can_gc,
186 ));
187 assert!(
188 event.fire(self.upcast(), can_gc),
189 "Guaranteed by DebuggerGetPossibleBreakpointsEvent::new"
190 );
191 }
192
193 pub(crate) fn fire_set_breakpoint(
194 &self,
195 can_gc: CanGc,
196 spidermonkey_id: u32,
197 script_id: u32,
198 offset: u32,
199 ) {
200 let event = DomRoot::upcast::<Event>(DebuggerSetBreakpointEvent::new(
201 self.upcast(),
202 spidermonkey_id,
203 script_id,
204 offset,
205 can_gc,
206 ));
207 assert!(
208 event.fire(self.upcast(), can_gc),
209 "Guaranteed by DebuggerSetBreakpointEvent::new"
210 );
211 }
212
213 pub(crate) fn fire_pause(
214 &self,
215 can_gc: CanGc,
216 result_sender: GenericSender<devtools_traits::PauseFrameResult>,
217 ) {
218 assert!(
219 self.get_frame_result_sender
220 .replace(Some(result_sender))
221 .is_none()
222 );
223 let event = DomRoot::upcast::<Event>(DebuggerPauseEvent::new(self.upcast(), can_gc));
224 assert!(
225 event.fire(self.upcast(), can_gc),
226 "Guaranteed by DebuggerPauseEvent::new"
227 );
228 }
229
230 pub(crate) fn fire_clear_breakpoint(
231 &self,
232 can_gc: CanGc,
233 spidermonkey_id: u32,
234 script_id: u32,
235 offset: u32,
236 ) {
237 let event = DomRoot::upcast::<Event>(DebuggerClearBreakpointEvent::new(
238 self.upcast(),
239 spidermonkey_id,
240 script_id,
241 offset,
242 can_gc,
243 ));
244 assert!(
245 event.fire(self.upcast(), can_gc),
246 "Guaranteed by DebuggerClearBreakpointEvent::new"
247 );
248 }
249}
250
251impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
252 fn NotifyNewSource(&self, args: &NotifyNewSource) {
254 let Some(devtools_chan) = self.as_global_scope().devtools_chan() else {
255 return;
256 };
257 let pipeline_id = PipelineId {
258 namespace_id: PipelineNamespaceId(args.pipelineId.namespaceId),
259 index: Index::new(args.pipelineId.index).expect("`pipelineId.index` must not be zero"),
260 };
261
262 if let Some(introduction_type) = args.introductionType.as_ref() {
263 let url_original = args.url.str();
276 let url_original = ServoUrl::parse(&url_original).ok();
278
279 let url_override = args
284 .urlOverride
285 .as_ref()
286 .map(|url| url.str())
287 .and_then(|url| ServoUrl::parse_with_base(url_original.as_ref(), &url).ok());
289
290 if [
294 IntroductionType::INJECTED_SCRIPT_STR,
295 IntroductionType::EVAL_STR,
296 IntroductionType::DEBUGGER_EVAL_STR,
297 IntroductionType::FUNCTION_STR,
298 IntroductionType::JAVASCRIPT_URL_STR,
299 IntroductionType::EVENT_HANDLER_STR,
300 IntroductionType::DOM_TIMER_STR,
301 ]
302 .contains(&&*introduction_type.str()) &&
303 url_override.is_none()
304 {
305 debug!(
306 "Not creating debuggee: `introductionType` is `{introduction_type}` but no valid url"
307 );
308 return;
309 }
310
311 let inline = introduction_type.str() == "inlineScript" && url_override.is_none();
319 let Some(url) = url_override.or(url_original) else {
320 debug!("Not creating debuggee: no valid url");
321 return;
322 };
323
324 let worker_id = args.workerId.as_ref().map(|id| id.parse().unwrap());
325
326 let source_info = SourceInfo {
327 url,
328 introduction_type: introduction_type.str().to_owned(),
329 inline,
330 worker_id,
331 content: (!inline).then(|| args.text.to_string()),
332 content_type: None, spidermonkey_id: args.spidermonkeyId,
334 };
335 if let Err(error) = devtools_chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
336 self.devtools_to_script_sender.clone(),
337 pipeline_id,
338 source_info,
339 )) {
340 warn!("Failed to send to devtools server: {error:?}");
341 }
342 } else {
343 debug!("Not creating debuggee for script with no `introductionType`");
344 }
345 }
346
347 fn GetPossibleBreakpointsResult(
348 &self,
349 event: &DebuggerGetPossibleBreakpointsEvent,
350 result: Vec<RecommendedBreakpointLocation>,
351 ) {
352 info!("GetPossibleBreakpointsResult: {event:?} {result:?}");
353 let sender = self
354 .get_possible_breakpoints_result_sender
355 .take()
356 .expect("Guaranteed by Self::fire_get_possible_breakpoints()");
357 let _ = sender.send(
358 result
359 .into_iter()
360 .map(|entry| devtools_traits::RecommendedBreakpointLocation {
361 script_id: entry.scriptId,
362 offset: entry.offset,
363 line_number: entry.lineNumber,
364 column_number: entry.columnNumber,
365 is_step_start: entry.isStepStart,
366 })
367 .collect(),
368 );
369 }
370
371 fn GetFrameResult(&self, event: &DebuggerPauseEvent, result: &PauseFrameResult) {
372 info!("GetFrameResult: {event:?} {result:?}");
373 let sender = self
374 .get_frame_result_sender
375 .take()
376 .expect("Guaranteed by Self::fire_get_frame()");
377 let _ = sender.send(devtools_traits::PauseFrameResult {
378 column: result.column,
379 display_name: result.displayName.clone().into(),
380 line: result.line,
381 on_stack: result.onStack,
382 oldest: result.oldest,
383 terminated: result.terminated,
384 type_: result.type_.clone().into(),
385 url: result.url.clone().into(),
386 });
387 }
388}