1use std::borrow::Cow;
6use std::ffi::CStr;
7use std::rc::Rc;
8
9use bitflags::bitflags;
10use content_security_policy::sandboxing_directive::SandboxingFlagSet;
11use js::context::JSContext;
12use js::gc::Handle;
13use js::jsapi::{
14 ExceptionStackBehavior, HadFrontendErrors, Heap, JSObject, JSScript, ModuleErrorBehaviour,
15 SetScriptPrivate,
16};
17use js::jsval::{JSVal, PrivateValue, UndefinedValue};
18use js::offthread::CompilationResult;
19use js::panic::maybe_resume_unwind;
20use js::rust::wrappers2::{
21 Compile1, ConvertFrontendErrorsToRuntimeErrors, InstantiateGlobalStencil,
22 JS_ClearPendingException, JS_ExecuteScript, JS_GetScriptPrivate, ModuleEvaluate,
23 ThrowOnModuleEvaluationFailure,
24};
25use js::rust::{
26 CompileOptionsWrapper, HandleValue, MutableHandleValue, transform_str_to_source_text,
27};
28use script_bindings::cformat;
29use script_bindings::js::{JS_IsExceptionPending, JS_SetPendingException};
30use script_bindings::settings_stack::run_a_script;
31use script_bindings::trace::RootedTraceableBox;
32use servo_url::ServoUrl;
33
34use crate::DomTypeHolder;
35use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
36use crate::dom::bindings::error::{Error, ErrorInfo, ErrorResult, report_pending_exception};
37use crate::dom::bindings::inheritance::Castable;
38use crate::dom::globalscope::GlobalScope;
39use crate::dom::window::Window;
40use crate::realms::enter_auto_realm;
41use crate::scripting::fetching_options::{BaseScript, ScriptFetchOptions};
42use crate::scripting::script_module::{ModuleTree, RethrowError};
43use crate::unminify::{ScriptSource, unminify_js};
44
45#[derive(Clone, Copy, JSTraceable, MallocSizeOf)]
47pub(crate) struct ScriptOptions(u8);
48
49bitflags! {
50 impl ScriptOptions: u8 {
51 const MutedErrors = 1 << 0;
53 const ReturnsAValue = 1 << 1;
55 const External = 1 << 2;
57 }
58}
59
60#[derive(JSTraceable, MallocSizeOf)]
62#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
63pub(crate) struct ClassicScript {
64 #[ignore_malloc_size_of = "mozjs"]
65 record: Option<Heap<*mut JSScript>>,
67 #[ignore_malloc_size_of = "mozjs"]
68 error_to_rethrow: Option<Heap<JSVal>>,
71 options: ScriptOptions,
73}
74
75impl ClassicScript {
76 #[expect(unsafe_code)]
77 fn record<'a>(&'a self) -> Option<Handle<'a, *mut JSScript>> {
78 self.record
79 .as_ref()
80 .map(|value| unsafe { Handle::from_raw(value.handle()) })
81 }
82
83 #[expect(unsafe_code)]
84 fn error_to_rethrow<'a>(&'a self) -> Option<HandleValue<'a>> {
85 self.error_to_rethrow
86 .as_ref()
87 .map(|value| unsafe { Handle::from_raw(value.handle()) })
88 }
89
90 pub(crate) fn has_rethrow_error(&self) -> bool {
91 self.error_to_rethrow.is_some()
92 }
93
94 #[expect(unsafe_code)]
95 pub(crate) fn from_stencil(
96 cx: &mut JSContext,
97 compilation_result: CompilationResult,
98 url: ServoUrl,
99 script_options: ScriptOptions,
100 fetch_options: ScriptFetchOptions,
101 ) -> RootedTraceableBox<ClassicScript> {
102 let mut classic_script = RootedTraceableBox::new(ClassicScript {
103 record: None,
104 error_to_rethrow: None,
105 options: script_options,
106 });
107
108 let CompilationResult {
109 stencil,
110 mut storage,
111 fc,
112 compile_options,
113 } = compilation_result;
114
115 match stencil {
116 Some(stencil) => {
117 debug_assert!(!stencil.is_null());
118 let instantiate_options = (&compile_options).into();
119
120 rooted!(&in(cx) let script = unsafe { InstantiateGlobalStencil(
121 cx,
122 &instantiate_options,
123 *stencil,
124 storage.as_mut_ptr(),
125 )});
126 maybe_associate_with_script(cx, script.handle(), url, fetch_options);
127
128 classic_script.record = Some(Heap::default());
129 classic_script.record.as_ref().unwrap().set(script.get());
130 },
131 None => {
132 debug_assert!(unsafe { HadFrontendErrors(*fc) });
133
134 let options = compile_options.read_only();
135 unsafe { ConvertFrontendErrorsToRuntimeErrors(cx, *fc, options) };
136
137 let error = RethrowError::from_pending_exception(cx);
138
139 classic_script.error_to_rethrow = Some(Heap::default());
140 classic_script
141 .error_to_rethrow
142 .as_ref()
143 .unwrap()
144 .set(error.handle().get());
145 },
146 }
147
148 classic_script
149 }
150}
151
152pub(crate) enum RethrowErrors {
153 Yes,
154 No,
155}
156
157impl GlobalScope {
158 #[expect(clippy::too_many_arguments)]
160 #[expect(unsafe_code)]
161 pub(crate) fn create_a_classic_script(
162 &self,
163 cx: &mut JSContext,
164 source: Cow<'_, str>,
165 url: ServoUrl,
166 options: ScriptOptions,
167 fetch_options: ScriptFetchOptions,
168 introduction_type: Option<&'static CStr>,
169 line_number: u32,
170 ) -> RootedTraceableBox<ClassicScript> {
171 let mut script_source = ScriptSource {
172 source,
173 external: options.contains(ScriptOptions::External),
174 url: &url,
175 };
176 if let Some(unminified_js_dir) = self.unminified_js_dir() {
177 unminify_js(&mut script_source, unminified_js_dir);
178 }
179 let mut source = transform_str_to_source_text(&script_source.source);
180
181 let mut script = RootedTraceableBox::new(ClassicScript {
189 record: None,
190 error_to_rethrow: None,
191 options,
192 });
193
194 let compilation_options =
199 fill_compile_options(cx, url.as_str(), options, introduction_type, line_number);
200
201 rooted!(&in(cx) let result = unsafe { Compile1(cx, compilation_options.ptr, &mut source) });
203
204 if result.is_null() {
206 let error = RethrowError::from_pending_exception(cx);
207
208 script.error_to_rethrow = Some(Heap::default());
210 script
211 .error_to_rethrow
212 .as_ref()
213 .unwrap()
214 .set(error.handle().get());
215
216 return script;
218 }
219
220 maybe_associate_with_script(cx, result.handle(), url, fetch_options);
223
224 script.record = Some(Heap::default());
226 script.record.as_ref().unwrap().set(result.get());
227
228 script
230 }
231
232 #[expect(unsafe_code)]
234 pub(crate) fn run_a_classic_script(
235 &self,
236 cx: &mut JSContext,
237 script: RootedTraceableBox<ClassicScript>,
238 rethrow_errors: RethrowErrors,
239 return_value: Option<MutableHandleValue>,
240 ) -> ErrorResult {
241 assert_eq!(
242 return_value.is_some(),
243 script.options.contains(ScriptOptions::ReturnsAValue),
244 "Classic script returns a value option does not match `return_value` argument"
245 );
246
247 if !self.can_run_script() {
252 return Ok(());
253 }
254
255 let mut realm = enter_auto_realm(cx, self);
258 let cx = &mut realm.current_realm();
259
260 run_a_script::<DomTypeHolder, _, _>(cx, self, |cx| {
263 let mut result = false;
265
266 if let Some(error) = script.error_to_rethrow() {
269 JS_SetPendingException(cx, error, ExceptionStackBehavior::Capture);
270 } else if let Some(record) = script.record() {
271 rooted!(&in(cx) let mut fallback_value = UndefinedValue());
273 let return_value = return_value.unwrap_or_else(|| fallback_value.handle_mut());
274 result = unsafe { JS_ExecuteScript(cx, record, return_value) };
275 }
276
277 if JS_IsExceptionPending(cx) {
279 warn!("Error evaluating script");
280
281 match (
282 rethrow_errors,
283 script.options.contains(ScriptOptions::MutedErrors),
284 ) {
285 (RethrowErrors::Yes, false) => {
286 return Err(Error::JSFailed);
289 },
290 (RethrowErrors::Yes, true) => {
291 unsafe { JS_ClearPendingException(cx) };
293 return Err(Error::Network(None));
295 },
296 (RethrowErrors::No, false) => {
300 report_pending_exception(cx);
301 return Err(Error::JSFailed);
302 },
303 (RethrowErrors::No, true) => {
304 unsafe { JS_ClearPendingException(cx) };
305 self.report_an_error(
306 cx,
307 ErrorInfo {
308 message: String::from("Script error."),
309 ..Default::default()
310 },
311 HandleValue::null(),
312 );
313 return Err(Error::JSFailed);
314 },
315 }
316 }
317
318 maybe_resume_unwind();
319
320 if result {
322 return Ok(());
323 }
324
325 Err(Error::QuotaExceeded {
328 quota: None,
329 requested: None,
330 })
331 })
332 }
333
334 #[expect(unsafe_code)]
336 pub(crate) fn run_a_module_script(
337 &self,
338 cx: &mut JSContext,
339 module_tree: RootedTraceableBox<Rc<ModuleTree>>,
340 _rethrow_errors: bool,
341 ) {
342 if !self.can_run_script() {
348 return;
349 }
350
351 let mut realm = enter_auto_realm(cx, self);
355 let cx = &mut realm.current_realm();
356
357 run_a_script::<DomTypeHolder, _, _>(cx, self, |cx| {
359 if let Some(exception) = module_tree.rethrow_error() {
362 JS_SetPendingException(cx, exception.handle(), ExceptionStackBehavior::Capture);
363 report_pending_exception(cx);
364 return;
365 }
366
367 let record = module_tree.record().unwrap();
369
370 rooted!(&in(cx) let mut rval = UndefinedValue());
372 let ok = unsafe { ModuleEvaluate(cx, record.handle(), rval.handle_mut()) };
373 assert!(ok, "module evaluation failed");
374
375 rooted!(&in(cx) let mut evaluation_promise = std::ptr::null_mut::<JSObject>());
376 if rval.is_object() {
377 evaluation_promise.set(rval.to_object());
378 }
379
380 let throw_result = unsafe {
384 ThrowOnModuleEvaluationFailure(
385 cx,
386 evaluation_promise.handle(),
387 ModuleErrorBehaviour::ThrowModuleErrorsSync,
388 )
389 };
390
391 if !throw_result {
392 report_pending_exception(cx);
393 }
394 });
395 }
396
397 pub(crate) fn can_run_script(&self) -> bool {
399 if let Some(window) = self.downcast::<Window>() {
410 let doc = window.Document();
411 doc.is_fully_active() &&
412 !doc.has_active_sandboxing_flag(
413 SandboxingFlagSet::SANDBOXED_SCRIPTS_BROWSING_CONTEXT_FLAG,
414 )
415 } else {
416 true
417 }
418 }
419}
420
421pub(crate) fn fill_compile_options(
422 cx: &mut JSContext,
423 filename: &str,
424 script_options: ScriptOptions,
425 introduction_type: Option<&'static CStr>,
426 line_number: u32,
427) -> CompileOptionsWrapper {
428 let mut options = CompileOptionsWrapper::new(cx, cformat!("{filename}"), line_number);
431 if let Some(introduction_type) = introduction_type {
432 options.set_introduction_type(introduction_type);
433 }
434
435 options.set_muted_errors(script_options.contains(ScriptOptions::MutedErrors));
437
438 options.set_is_run_once(true);
440 options.set_no_script_rval(!script_options.contains(ScriptOptions::ReturnsAValue));
441
442 options
443}
444
445#[expect(unsafe_code)]
446pub(crate) fn maybe_associate_with_script(
447 cx: &JSContext,
448 script: Handle<*mut JSScript>,
449 url: ServoUrl,
450 fetch_options: ScriptFetchOptions,
451) {
452 rooted!(&in(cx) let mut script_private = UndefinedValue());
453 unsafe { JS_GetScriptPrivate(script.get(), script_private.handle_mut()) };
454
455 if script_private.is_undefined() {
458 let module_script_data = Rc::new(BaseScript::new(url, fetch_options));
459
460 unsafe {
461 SetScriptPrivate(
462 script.get(),
463 &PrivateValue(Rc::into_raw(module_script_data) as *const _),
464 );
465 }
466 }
467}