1use std::cell::RefCell;
6
7use base::id::{Index, PipelineId, PipelineNamespaceId};
8use constellation_traits::ScriptToConstellationChan;
9use devtools_traits::{DevtoolScriptControlMsg, ScriptToDevtoolsControlMsg, SourceInfo, WorkerId};
10use dom_struct::dom_struct;
11use embedder_traits::resources::{self, Resource};
12use embedder_traits::{JavaScriptEvaluationError, ScriptToEmbedderChan};
13use ipc_channel::ipc::IpcSender;
14use js::jsval::UndefinedValue;
15use js::rust::wrappers::JS_DefineDebuggerObject;
16use net_traits::ResourceThreads;
17use profile_traits::{mem, time};
18use script_bindings::codegen::GenericBindings::DebuggerGetPossibleBreakpointsEventBinding::RecommendedBreakpointLocation;
19use script_bindings::codegen::GenericBindings::DebuggerGlobalScopeBinding::{
20 DebuggerGlobalScopeMethods, NotifyNewSource,
21};
22use script_bindings::realms::InRealm;
23use script_bindings::reflector::DomObject;
24use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
25use storage_traits::StorageThreads;
26
27use crate::dom::bindings::codegen::Bindings::DebuggerGlobalScopeBinding;
28use crate::dom::bindings::error::report_pending_exception;
29use crate::dom::bindings::inheritance::Castable;
30use crate::dom::bindings::root::DomRoot;
31use crate::dom::bindings::utils::define_all_exposed_interfaces;
32use crate::dom::globalscope::GlobalScope;
33use crate::dom::types::{DebuggerAddDebuggeeEvent, DebuggerGetPossibleBreakpointsEvent, Event};
34#[cfg(feature = "testbinding")]
35#[cfg(feature = "webgpu")]
36use crate::dom::webgpu::identityhub::IdentityHub;
37use crate::realms::enter_realm;
38use crate::script_module::ScriptFetchOptions;
39use crate::script_runtime::{CanGc, IntroductionType, JSContext};
40
41#[dom_struct]
42pub(crate) struct DebuggerGlobalScope {
46 global_scope: GlobalScope,
47 #[no_trace]
48 devtools_to_script_sender: IpcSender<DevtoolScriptControlMsg>,
49 #[no_trace]
50 get_possible_breakpoints_result_sender:
51 RefCell<Option<IpcSender<Vec<devtools_traits::RecommendedBreakpointLocation>>>>,
52}
53
54impl DebuggerGlobalScope {
55 #[allow(unsafe_code, clippy::too_many_arguments)]
63 pub(crate) fn new(
64 debugger_pipeline_id: PipelineId,
65 script_to_devtools_sender: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
66 devtools_to_script_sender: IpcSender<DevtoolScriptControlMsg>,
67 mem_profiler_chan: mem::ProfilerChan,
68 time_profiler_chan: time::ProfilerChan,
69 script_to_constellation_chan: ScriptToConstellationChan,
70 script_to_embedder_chan: ScriptToEmbedderChan,
71 resource_threads: ResourceThreads,
72 storage_threads: StorageThreads,
73 #[cfg(feature = "webgpu")] gpu_id_hub: std::sync::Arc<IdentityHub>,
74 can_gc: CanGc,
75 ) -> DomRoot<Self> {
76 let global = Box::new(Self {
77 global_scope: GlobalScope::new_inherited(
78 debugger_pipeline_id,
79 script_to_devtools_sender,
80 mem_profiler_chan,
81 time_profiler_chan,
82 script_to_constellation_chan,
83 script_to_embedder_chan,
84 resource_threads,
85 storage_threads,
86 MutableOrigin::new(ImmutableOrigin::new_opaque()),
87 ServoUrl::parse_with_base(None, "about:internal/debugger")
88 .expect("Guaranteed by argument"),
89 None,
90 Default::default(),
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(&self, script: &str, can_gc: CanGc) -> Result<(), JavaScriptEvaluationError> {
127 rooted!(in (*Self::get_cx()) let mut rval = UndefinedValue());
128 self.global_scope.evaluate_js_on_global_with_result(
129 script,
130 rval.handle_mut(),
131 ScriptFetchOptions::default_classic_script(&self.global_scope),
132 self.global_scope.api_base_url(),
133 can_gc,
134 None,
135 )
136 }
137
138 pub(crate) fn execute(&self, can_gc: CanGc) {
139 if self
140 .evaluate_js(&resources::read_string(Resource::DebuggerJS), can_gc)
141 .is_err()
142 {
143 let ar = enter_realm(self);
144 report_pending_exception(Self::get_cx(), true, InRealm::Entered(&ar), can_gc);
145 }
146 }
147
148 pub(crate) fn fire_add_debuggee(
149 &self,
150 can_gc: CanGc,
151 debuggee_global: &GlobalScope,
152 debuggee_pipeline_id: PipelineId,
153 debuggee_worker_id: Option<WorkerId>,
154 ) {
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 DomRoot::upcast::<Event>(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: IpcSender<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 event = DomRoot::upcast::<Event>(DebuggerGetPossibleBreakpointsEvent::new(
182 self.upcast(),
183 spidermonkey_id,
184 can_gc,
185 ));
186 assert!(
187 DomRoot::upcast::<Event>(event).fire(self.upcast(), can_gc),
188 "Guaranteed by DebuggerGetPossibleBreakpointsEvent::new"
189 );
190 }
191}
192
193impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
194 fn NotifyNewSource(&self, args: &NotifyNewSource) {
196 let Some(devtools_chan) = self.as_global_scope().devtools_chan() else {
197 return;
198 };
199 let pipeline_id = PipelineId {
200 namespace_id: PipelineNamespaceId(args.pipelineId.namespaceId),
201 index: Index::new(args.pipelineId.index).expect("`pipelineId.index` must not be zero"),
202 };
203
204 if let Some(introduction_type) = args.introductionType.as_ref() {
205 let url_original = args.url.str();
218 let url_original = ServoUrl::parse(&url_original).ok();
220
221 let url_override = args
226 .urlOverride
227 .as_ref()
228 .map(|url| url.str())
229 .and_then(|url| ServoUrl::parse_with_base(url_original.as_ref(), &url).ok());
231
232 if [
236 IntroductionType::INJECTED_SCRIPT_STR,
237 IntroductionType::EVAL_STR,
238 IntroductionType::DEBUGGER_EVAL_STR,
239 IntroductionType::FUNCTION_STR,
240 IntroductionType::JAVASCRIPT_URL_STR,
241 IntroductionType::EVENT_HANDLER_STR,
242 IntroductionType::DOM_TIMER_STR,
243 ]
244 .contains(&&*introduction_type.str()) &&
245 url_override.is_none()
246 {
247 debug!(
248 "Not creating debuggee: `introductionType` is `{introduction_type}` but no valid url"
249 );
250 return;
251 }
252
253 let inline = introduction_type.str() == "inlineScript" && url_override.is_none();
261 let Some(url) = url_override.or(url_original) else {
262 debug!("Not creating debuggee: no valid url");
263 return;
264 };
265
266 let worker_id = args.workerId.as_ref().map(|id| id.parse().unwrap());
267
268 let source_info = SourceInfo {
269 url,
270 introduction_type: introduction_type.str().to_owned(),
271 inline,
272 worker_id,
273 content: (!inline).then(|| args.text.to_string()),
274 content_type: None, spidermonkey_id: args.spidermonkeyId,
276 };
277 if let Err(error) = devtools_chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
278 self.devtools_to_script_sender.clone(),
279 pipeline_id,
280 source_info,
281 )) {
282 warn!("Failed to send to devtools server: {error:?}");
283 }
284 } else {
285 debug!("Not creating debuggee for script with no `introductionType`");
286 }
287 }
288
289 fn GetPossibleBreakpointsResult(
290 &self,
291 event: &DebuggerGetPossibleBreakpointsEvent,
292 result: Vec<RecommendedBreakpointLocation>,
293 ) {
294 info!("GetPossibleBreakpointsResult: {event:?} {result:?}");
295 let sender = self
296 .get_possible_breakpoints_result_sender
297 .take()
298 .expect("Guaranteed by Self::fire_get_possible_breakpoints()");
299 let _ = sender.send(
300 result
301 .into_iter()
302 .map(|entry| devtools_traits::RecommendedBreakpointLocation {
303 offset: entry.offset,
304 line_number: entry.lineNumber,
305 column_number: entry.columnNumber,
306 is_step_start: entry.isStepStart,
307 })
308 .collect(),
309 );
310 }
311}