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};
25
26use crate::dom::bindings::codegen::Bindings::DebuggerGlobalScopeBinding;
27use crate::dom::bindings::error::report_pending_exception;
28use crate::dom::bindings::inheritance::Castable;
29use crate::dom::bindings::root::DomRoot;
30use crate::dom::bindings::utils::define_all_exposed_interfaces;
31use crate::dom::globalscope::GlobalScope;
32use crate::dom::types::{DebuggerAddDebuggeeEvent, DebuggerGetPossibleBreakpointsEvent, Event};
33#[cfg(feature = "testbinding")]
34#[cfg(feature = "webgpu")]
35use crate::dom::webgpu::identityhub::IdentityHub;
36use crate::realms::enter_realm;
37use crate::script_module::ScriptFetchOptions;
38use crate::script_runtime::{CanGc, IntroductionType, JSContext};
39
40#[dom_struct]
41pub(crate) struct DebuggerGlobalScope {
45 global_scope: GlobalScope,
46 #[no_trace]
47 devtools_to_script_sender: IpcSender<DevtoolScriptControlMsg>,
48 #[no_trace]
49 get_possible_breakpoints_result_sender:
50 RefCell<Option<IpcSender<Vec<devtools_traits::RecommendedBreakpointLocation>>>>,
51}
52
53impl DebuggerGlobalScope {
54 #[allow(unsafe_code, clippy::too_many_arguments)]
62 pub(crate) fn new(
63 debugger_pipeline_id: PipelineId,
64 script_to_devtools_sender: Option<IpcSender<ScriptToDevtoolsControlMsg>>,
65 devtools_to_script_sender: IpcSender<DevtoolScriptControlMsg>,
66 mem_profiler_chan: mem::ProfilerChan,
67 time_profiler_chan: time::ProfilerChan,
68 script_to_constellation_chan: ScriptToConstellationChan,
69 script_to_embedder_chan: ScriptToEmbedderChan,
70 resource_threads: ResourceThreads,
71 #[cfg(feature = "webgpu")] gpu_id_hub: std::sync::Arc<IdentityHub>,
72 can_gc: CanGc,
73 ) -> DomRoot<Self> {
74 let global = Box::new(Self {
75 global_scope: GlobalScope::new_inherited(
76 debugger_pipeline_id,
77 script_to_devtools_sender,
78 mem_profiler_chan,
79 time_profiler_chan,
80 script_to_constellation_chan,
81 script_to_embedder_chan,
82 resource_threads,
83 MutableOrigin::new(ImmutableOrigin::new_opaque()),
84 ServoUrl::parse_with_base(None, "about:internal/debugger")
85 .expect("Guaranteed by argument"),
86 None,
87 Default::default(),
88 #[cfg(feature = "webgpu")]
89 gpu_id_hub,
90 None,
91 false,
92 None, ),
94 devtools_to_script_sender,
95 get_possible_breakpoints_result_sender: RefCell::new(None),
96 });
97 let global =
98 DebuggerGlobalScopeBinding::Wrap::<crate::DomTypeHolder>(GlobalScope::get_cx(), global);
99
100 let realm = enter_realm(&*global);
101 define_all_exposed_interfaces(global.upcast(), InRealm::entered(&realm), can_gc);
102 assert!(unsafe {
103 JS_DefineDebuggerObject(
106 *Self::get_cx(),
107 global.global_scope.reflector().get_jsobject(),
108 )
109 });
110
111 global
112 }
113
114 pub(crate) fn get_cx() -> JSContext {
116 GlobalScope::get_cx()
117 }
118
119 pub(crate) fn as_global_scope(&self) -> &GlobalScope {
120 self.upcast::<GlobalScope>()
121 }
122
123 fn evaluate_js(&self, script: &str, can_gc: CanGc) -> Result<(), JavaScriptEvaluationError> {
124 rooted!(in (*Self::get_cx()) let mut rval = UndefinedValue());
125 self.global_scope.evaluate_js_on_global_with_result(
126 script,
127 rval.handle_mut(),
128 ScriptFetchOptions::default_classic_script(&self.global_scope),
129 self.global_scope.api_base_url(),
130 can_gc,
131 None,
132 )
133 }
134
135 pub(crate) fn execute(&self, can_gc: CanGc) {
136 if self
137 .evaluate_js(&resources::read_string(Resource::DebuggerJS), can_gc)
138 .is_err()
139 {
140 let ar = enter_realm(self);
141 report_pending_exception(Self::get_cx(), true, InRealm::Entered(&ar), can_gc);
142 }
143 }
144
145 pub(crate) fn fire_add_debuggee(
146 &self,
147 can_gc: CanGc,
148 debuggee_global: &GlobalScope,
149 debuggee_pipeline_id: PipelineId,
150 debuggee_worker_id: Option<WorkerId>,
151 ) {
152 let debuggee_pipeline_id =
153 crate::dom::pipelineid::PipelineId::new(self.upcast(), debuggee_pipeline_id, can_gc);
154 let event = DomRoot::upcast::<Event>(DebuggerAddDebuggeeEvent::new(
155 self.upcast(),
156 debuggee_global,
157 &debuggee_pipeline_id,
158 debuggee_worker_id.map(|id| id.to_string().into()),
159 can_gc,
160 ));
161 assert!(
162 DomRoot::upcast::<Event>(event).fire(self.upcast(), can_gc),
163 "Guaranteed by DebuggerAddDebuggeeEvent::new"
164 );
165 }
166
167 pub(crate) fn fire_get_possible_breakpoints(
168 &self,
169 can_gc: CanGc,
170 spidermonkey_id: u32,
171 result_sender: IpcSender<Vec<devtools_traits::RecommendedBreakpointLocation>>,
172 ) {
173 assert!(
174 self.get_possible_breakpoints_result_sender
175 .replace(Some(result_sender))
176 .is_none()
177 );
178 let event = DomRoot::upcast::<Event>(DebuggerGetPossibleBreakpointsEvent::new(
179 self.upcast(),
180 spidermonkey_id,
181 can_gc,
182 ));
183 assert!(
184 DomRoot::upcast::<Event>(event).fire(self.upcast(), can_gc),
185 "Guaranteed by DebuggerGetPossibleBreakpointsEvent::new"
186 );
187 }
188}
189
190impl DebuggerGlobalScopeMethods<crate::DomTypeHolder> for DebuggerGlobalScope {
191 fn NotifyNewSource(&self, args: &NotifyNewSource) {
193 let Some(devtools_chan) = self.as_global_scope().devtools_chan() else {
194 return;
195 };
196 let pipeline_id = PipelineId {
197 namespace_id: PipelineNamespaceId(args.pipelineId.namespaceId),
198 index: Index::new(args.pipelineId.index).expect("`pipelineId.index` must not be zero"),
199 };
200
201 if let Some(introduction_type) = args.introductionType.as_ref() {
202 let url_original = args.url.str();
215 let url_original = ServoUrl::parse(url_original).ok();
217
218 let url_override = args
223 .urlOverride
224 .as_ref()
225 .map(|url| url.str())
226 .and_then(|url| ServoUrl::parse_with_base(url_original.as_ref(), url).ok());
228
229 if [
233 IntroductionType::INJECTED_SCRIPT_STR,
234 IntroductionType::EVAL_STR,
235 IntroductionType::DEBUGGER_EVAL_STR,
236 IntroductionType::FUNCTION_STR,
237 IntroductionType::JAVASCRIPT_URL_STR,
238 IntroductionType::EVENT_HANDLER_STR,
239 IntroductionType::DOM_TIMER_STR,
240 ]
241 .contains(&introduction_type.str()) &&
242 url_override.is_none()
243 {
244 debug!(
245 "Not creating debuggee: `introductionType` is `{introduction_type}` but no valid url"
246 );
247 return;
248 }
249
250 let inline = introduction_type.str() == "inlineScript" && url_override.is_none();
258 let Some(url) = url_override.or(url_original) else {
259 debug!("Not creating debuggee: no valid url");
260 return;
261 };
262
263 let worker_id = args.workerId.as_ref().map(|id| id.parse().unwrap());
264
265 let source_info = SourceInfo {
266 url,
267 introduction_type: introduction_type.str().to_owned(),
268 inline,
269 worker_id,
270 content: (!inline).then(|| args.text.to_string()),
271 content_type: None, spidermonkey_id: args.spidermonkeyId,
273 };
274 if let Err(error) = devtools_chan.send(ScriptToDevtoolsControlMsg::CreateSourceActor(
275 self.devtools_to_script_sender.clone(),
276 pipeline_id,
277 source_info,
278 )) {
279 warn!("Failed to send to devtools server: {error:?}");
280 }
281 } else {
282 debug!("Not creating debuggee for script with no `introductionType`");
283 }
284 }
285
286 fn GetPossibleBreakpointsResult(
287 &self,
288 event: &DebuggerGetPossibleBreakpointsEvent,
289 result: Vec<RecommendedBreakpointLocation>,
290 ) {
291 info!("GetPossibleBreakpointsResult: {event:?} {result:?}");
292 let sender = self
293 .get_possible_breakpoints_result_sender
294 .take()
295 .expect("Guaranteed by Self::fire_get_possible_breakpoints()");
296 let _ = sender.send(
297 result
298 .into_iter()
299 .map(|entry| devtools_traits::RecommendedBreakpointLocation {
300 offset: entry.offset,
301 line_number: entry.lineNumber,
302 column_number: entry.columnNumber,
303 is_step_start: entry.isStepStart,
304 })
305 .collect(),
306 );
307 }
308}