1use std::borrow::Cow;
6use std::cell::RefCell;
7
8use base::id::{Index, PipelineId, PipelineNamespaceId};
9use constellation_traits::ScriptToConstellationChan;
10use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, SourceInfo, WorkerId};
11use dom_struct::dom_struct;
12use embedder_traits::resources::{self, Resource};
13use embedder_traits::{JavaScriptEvaluationError, ScriptToEmbedderChan};
14use ipc_channel::ipc::IpcSender;
15use js::jsval::UndefinedValue;
16use js::rust::wrappers::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::error::report_pending_exception;
30use crate::dom::bindings::inheritance::Castable;
31use crate::dom::bindings::root::DomRoot;
32use crate::dom::bindings::utils::define_all_exposed_interfaces;
33use crate::dom::globalscope::GlobalScope;
34use crate::dom::types::{DebuggerAddDebuggeeEvent, DebuggerGetPossibleBreakpointsEvent, Event};
35#[cfg(feature = "testbinding")]
36#[cfg(feature = "webgpu")]
37use crate::dom::webgpu::identityhub::IdentityHub;
38use crate::realms::enter_realm;
39use crate::script_module::ScriptFetchOptions;
40use crate::script_runtime::{CanGc, IntroductionType, JSContext};
41
42#[dom_struct]
43pub(crate) struct DebuggerGlobalScope {
47 global_scope: GlobalScope,
48 #[no_trace]
49 devtools_to_script_sender: IpcSender<DevtoolScriptControlMsg>,
50 #[no_trace]
51 get_possible_breakpoints_result_sender:
52 RefCell<Option<IpcSender<Vec<devtools_traits::RecommendedBreakpointLocation>>>>,
53}
54
55impl DebuggerGlobalScope {
56 #[expect(unsafe_code, clippy::too_many_arguments)]
64 pub(crate) fn new(
65 debugger_pipeline_id: PipelineId,
66 script_to_devtools_sender: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
67 devtools_to_script_sender: IpcSender<DevtoolScriptControlMsg>,
68 mem_profiler_chan: mem::ProfilerChan,
69 time_profiler_chan: time::ProfilerChan,
70 script_to_constellation_chan: ScriptToConstellationChan,
71 script_to_embedder_chan: ScriptToEmbedderChan,
72 resource_threads: ResourceThreads,
73 storage_threads: StorageThreads,
74 #[cfg(feature = "webgpu")] gpu_id_hub: std::sync::Arc<IdentityHub>,
75 can_gc: CanGc,
76 ) -> DomRoot<Self> {
77 let global = Box::new(Self {
78 global_scope: GlobalScope::new_inherited(
79 debugger_pipeline_id,
80 script_to_devtools_sender,
81 mem_profiler_chan,
82 time_profiler_chan,
83 script_to_constellation_chan,
84 script_to_embedder_chan,
85 resource_threads,
86 storage_threads,
87 MutableOrigin::new(ImmutableOrigin::new_opaque()),
88 ServoUrl::parse_with_base(None, "about:internal/debugger")
89 .expect("Guaranteed by argument"),
90 None,
91 #[cfg(feature = "webgpu")]
92 gpu_id_hub,
93 None,
94 false,
95 None, ),
97 devtools_to_script_sender,
98 get_possible_breakpoints_result_sender: RefCell::new(None),
99 });
100 let global =
101 DebuggerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(GlobalScope::get_cx(), global);
102
103 let realm = enter_realm(&*global);
104 define_all_exposed_interfaces(global.upcast(), InRealm::entered(&realm), can_gc);
105 assert!(unsafe {
106 JS_DefineDebuggerObject(
109 *Self::get_cx(),
110 global.global_scope.reflector().get_jsobject(),
111 )
112 });
113
114 global
115 }
116
117 pub(crate) fn get_cx() -> JSContext {
119 GlobalScope::get_cx()
120 }
121
122 pub(crate) fn as_global_scope(&self) -> &GlobalScope {
123 self.upcast::<GlobalScope>()
124 }
125
126 fn evaluate_js(
127 &self,
128 script: Cow<'_, str>,
129 can_gc: CanGc,
130 ) -> Result<(), JavaScriptEvaluationError> {
131 rooted!(in (*Self::get_cx()) let mut rval = UndefinedValue());
132 self.global_scope.evaluate_js_on_global_with_result(
133 script,
134 rval.handle_mut(),
135 ScriptFetchOptions::default_classic_script(&self.global_scope),
136 self.global_scope.api_base_url(),
137 can_gc,
138 None,
139 )
140 }
141
142 pub(crate) fn execute(&self, can_gc: CanGc) {
143 if self
144 .evaluate_js(resources::read_string(Resource::DebuggerJS).into(), can_gc)
145 .is_err()
146 {
147 let ar = enter_realm(self);
148 report_pending_exception(Self::get_cx(), true, InRealm::Entered(&ar), can_gc);
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 DomRoot::upcast::<Event>(event).fire(self.upcast(), can_gc),
171 "Guaranteed by DebuggerAddDebuggeeEvent::new"
172 );
173 }
174
175 pub(crate) fn fire_get_possible_breakpoints(
176 &self,
177 can_gc: CanGc,
178 spidermonkey_id: u32,
179 result_sender: IpcSender<Vec<devtools_traits::RecommendedBreakpointLocation>>,
180 ) {
181 assert!(
182 self.get_possible_breakpoints_result_sender
183 .replace(Some(result_sender))
184 .is_none()
185 );
186 let _realm = enter_realm(self);
187 let event = DomRoot::upcast::<Event>(DebuggerGetPossibleBreakpointsEvent::new(
188 self.upcast(),
189 spidermonkey_id,
190 can_gc,
191 ));
192 assert!(
193 DomRoot::upcast::<Event>(event).fire(self.upcast(), can_gc),
194 "Guaranteed by DebuggerGetPossibleBreakpointsEvent::new"
195 );
196 }
197}
198
199impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
200 fn NotifyNewSource(&self, args: &NotifyNewSource) {
202 let Some(devtools_chan) = self.as_global_scope().devtools_chan() else {
203 return;
204 };
205 let pipeline_id = PipelineId {
206 namespace_id: PipelineNamespaceId(args.pipelineId.namespaceId),
207 index: Index::new(args.pipelineId.index).expect("`pipelineId.index` must not be zero"),
208 };
209
210 if let Some(introduction_type) = args.introductionType.as_ref() {
211 let url_original = args.url.str();
224 let url_original = ServoUrl::parse(&url_original).ok();
226
227 let url_override = args
232 .urlOverride
233 .as_ref()
234 .map(|url| url.str())
235 .and_then(|url| ServoUrl::parse_with_base(url_original.as_ref(), &url).ok());
237
238 if [
242 IntroductionType::INJECTED_SCRIPT_STR,
243 IntroductionType::EVAL_STR,
244 IntroductionType::DEBUGGER_EVAL_STR,
245 IntroductionType::FUNCTION_STR,
246 IntroductionType::JAVASCRIPT_URL_STR,
247 IntroductionType::EVENT_HANDLER_STR,
248 IntroductionType::DOM_TIMER_STR,
249 ]
250 .contains(&&*introduction_type.str()) &&
251 url_override.is_none()
252 {
253 debug!(
254 "Not creating debuggee: `introductionType` is `{introduction_type}` but no valid url"
255 );
256 return;
257 }
258
259 let inline = introduction_type.str() == "inlineScript" && url_override.is_none();
267 let Some(url) = url_override.or(url_original) else {
268 debug!("Not creating debuggee: no valid url");
269 return;
270 };
271
272 let worker_id = args.workerId.as_ref().map(|id| id.parse().unwrap());
273
274 let source_info = SourceInfo {
275 url,
276 introduction_type: introduction_type.str().to_owned(),
277 inline,
278 worker_id,
279 content: (!inline).then(|| args.text.to_string()),
280 content_type: None, spidermonkey_id: args.spidermonkeyId,
282 };
283 if let Err(error) = devtools_chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
284 self.devtools_to_script_sender.clone(),
285 pipeline_id,
286 source_info,
287 )) {
288 warn!("Failed to send to devtools server: {error:?}");
289 }
290 } else {
291 debug!("Not creating debuggee for script with no `introductionType`");
292 }
293 }
294
295 fn GetPossibleBreakpointsResult(
296 &self,
297 event: &DebuggerGetPossibleBreakpointsEvent,
298 result: Vec<RecommendedBreakpointLocation>,
299 ) {
300 info!("GetPossibleBreakpointsResult: {event:?} {result:?}");
301 let sender = self
302 .get_possible_breakpoints_result_sender
303 .take()
304 .expect("Guaranteed by Self::fire_get_possible_breakpoints()");
305 let _ = sender.send(
306 result
307 .into_iter()
308 .map(|entry| devtools_traits::RecommendedBreakpointLocation {
309 offset: entry.offset,
310 line_number: entry.lineNumber,
311 column_number: entry.columnNumber,
312 is_step_start: entry.isStepStart,
313 })
314 .collect(),
315 );
316 }
317}