1use std::ffi::CString;
8use std::ptr::NonNull;
9use std::slice::from_raw_parts;
10
11#[cfg(feature = "js_backtrace")]
12use backtrace::Backtrace;
13use embedder_traits::JavaScriptErrorInfo;
14use js::context::JSContext;
15use js::conversions::jsstr_to_string;
16use js::error::{throw_range_error, throw_type_error};
17#[cfg(feature = "js_backtrace")]
18use js::jsapi::StackFormat as JSStackFormat;
19use js::jsapi::{ExceptionStackBehavior, JS_ClearPendingException, JS_IsExceptionPending};
20use js::jsval::UndefinedValue;
21use js::realm::CurrentRealm;
22use js::rust::wrappers::{JS_ErrorFromException, JS_GetPendingException, JS_SetPendingException};
23use js::rust::wrappers2::JS_GetProperty;
24use js::rust::{HandleObject, HandleValue, MutableHandleValue, describe_scripted_caller};
25use libc::c_uint;
26use script_bindings::conversions::SafeToJSValConvertible;
27pub(crate) use script_bindings::error::*;
28use script_bindings::root::DomRoot;
29use script_bindings::str::DOMString;
30
31#[cfg(feature = "js_backtrace")]
32use crate::dom::bindings::cell::DomRefCell;
33use crate::dom::bindings::conversions::{
34 ConversionResult, SafeFromJSValConvertible, root_from_object,
35};
36use crate::dom::bindings::str::USVString;
37use crate::dom::domexception::{DOMErrorName, DOMException};
38use crate::dom::globalscope::GlobalScope;
39use crate::dom::types::QuotaExceededError;
40use crate::realms::InRealm;
41use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
42
43#[cfg(feature = "js_backtrace")]
44thread_local! {
45 pub(crate) static LAST_EXCEPTION_BACKTRACE: DomRefCell<Option<(Option<String>, String)>> = DomRefCell::new(None);
48}
49
50pub(crate) enum JsEngineError {
52 Type(CString),
54 Range(CString),
56 JSFailed,
58}
59
60pub(crate) fn throw_dom_exception(
62 cx: SafeJSContext,
63 global: &GlobalScope,
64 result: Error,
65 can_gc: CanGc,
66) {
67 #[cfg(feature = "js_backtrace")]
68 unsafe {
69 capture_stack!(in(*cx) let stack);
70 let js_stack = stack.and_then(|stack| stack.as_string(None, JSStackFormat::Default));
71 let rust_stack = Backtrace::new();
72 LAST_EXCEPTION_BACKTRACE.with(|backtrace| {
73 *backtrace.borrow_mut() = Some((js_stack, format!("{:?}", rust_stack)));
74 });
75 }
76
77 match create_dom_exception(global, result, can_gc) {
78 Ok(exception) => unsafe {
79 assert!(!JS_IsExceptionPending(*cx));
80 rooted!(in(*cx) let mut thrown = UndefinedValue());
81 exception.safe_to_jsval(cx, thrown.handle_mut(), can_gc);
82 JS_SetPendingException(*cx, thrown.handle(), ExceptionStackBehavior::Capture);
83 },
84
85 Err(JsEngineError::Type(message)) => unsafe {
86 assert!(!JS_IsExceptionPending(*cx));
87 throw_type_error(*cx, &message);
88 },
89
90 Err(JsEngineError::Range(message)) => unsafe {
91 assert!(!JS_IsExceptionPending(*cx));
92 throw_range_error(*cx, &message);
93 },
94
95 Err(JsEngineError::JSFailed) => unsafe {
96 assert!(JS_IsExceptionPending(*cx));
97 },
98 }
99}
100
101pub(crate) fn create_dom_exception(
105 global: &GlobalScope,
106 result: Error,
107 can_gc: CanGc,
108) -> Result<DomRoot<DOMException>, JsEngineError> {
109 let new_custom_exception = |error_name, message| {
110 Ok(DOMException::new_with_custom_message(
111 global, error_name, message, can_gc,
112 ))
113 };
114
115 let code = match result {
116 Error::IndexSize(Some(custom_message)) => {
117 return new_custom_exception(DOMErrorName::IndexSizeError, custom_message);
118 },
119 Error::IndexSize(None) => DOMErrorName::IndexSizeError,
120 Error::NotFound(Some(custom_message)) => {
121 return new_custom_exception(DOMErrorName::NotFoundError, custom_message);
122 },
123 Error::NotFound(None) => DOMErrorName::NotFoundError,
124 Error::HierarchyRequest(Some(custom_message)) => {
125 return new_custom_exception(DOMErrorName::HierarchyRequestError, custom_message);
126 },
127 Error::HierarchyRequest(None) => DOMErrorName::HierarchyRequestError,
128 Error::WrongDocument(Some(doc_err_custom_message)) => {
129 return new_custom_exception(DOMErrorName::WrongDocumentError, doc_err_custom_message);
130 },
131 Error::WrongDocument(None) => DOMErrorName::WrongDocumentError,
132 Error::InvalidCharacter(Some(custom_message)) => {
133 return new_custom_exception(DOMErrorName::InvalidCharacterError, custom_message);
134 },
135 Error::InvalidCharacter(None) => DOMErrorName::InvalidCharacterError,
136 Error::NotSupported(Some(custom_message)) => {
137 return new_custom_exception(DOMErrorName::NotSupportedError, custom_message);
138 },
139 Error::NotSupported(None) => DOMErrorName::NotSupportedError,
140 Error::InUseAttribute(Some(custom_message)) => {
141 return new_custom_exception(DOMErrorName::InUseAttributeError, custom_message);
142 },
143 Error::InUseAttribute(None) => DOMErrorName::InUseAttributeError,
144 Error::InvalidState(Some(custom_message)) => {
145 return new_custom_exception(DOMErrorName::InvalidStateError, custom_message);
146 },
147 Error::InvalidState(None) => DOMErrorName::InvalidStateError,
148 Error::Syntax(Some(custom_message)) => {
149 return new_custom_exception(DOMErrorName::SyntaxError, custom_message);
150 },
151 Error::Syntax(None) => DOMErrorName::SyntaxError,
152 Error::Namespace(Some(custom_message)) => {
153 return new_custom_exception(DOMErrorName::NamespaceError, custom_message);
154 },
155 Error::Namespace(None) => DOMErrorName::NamespaceError,
156 Error::InvalidAccess(Some(custom_message)) => {
157 return new_custom_exception(DOMErrorName::InvalidAccessError, custom_message);
158 },
159 Error::InvalidAccess(None) => DOMErrorName::InvalidAccessError,
160 Error::Security(Some(custom_message)) => {
161 return new_custom_exception(DOMErrorName::SecurityError, custom_message);
162 },
163 Error::Security(None) => DOMErrorName::SecurityError,
164 Error::Network(Some(custom_message)) => {
165 return new_custom_exception(DOMErrorName::NetworkError, custom_message);
166 },
167 Error::Network(None) => DOMErrorName::NetworkError,
168 Error::Abort(Some(custom_message)) => {
169 return new_custom_exception(DOMErrorName::AbortError, custom_message);
170 },
171 Error::Abort(None) => DOMErrorName::AbortError,
172 Error::Timeout(Some(custom_message)) => {
173 return new_custom_exception(DOMErrorName::TimeoutError, custom_message);
174 },
175 Error::Timeout(None) => DOMErrorName::TimeoutError,
176 Error::InvalidNodeType(Some(custom_message)) => {
177 return new_custom_exception(DOMErrorName::InvalidNodeTypeError, custom_message);
178 },
179 Error::InvalidNodeType(None) => DOMErrorName::InvalidNodeTypeError,
180 Error::DataClone(Some(custom_message)) => {
181 return new_custom_exception(DOMErrorName::DataCloneError, custom_message);
182 },
183 Error::DataClone(None) => DOMErrorName::DataCloneError,
184 Error::Data(Some(custom_message)) => {
185 return new_custom_exception(DOMErrorName::DataError, custom_message);
186 },
187 Error::Data(None) => DOMErrorName::DataError,
188 Error::TransactionInactive(Some(custom_message)) => {
189 return new_custom_exception(DOMErrorName::TransactionInactiveError, custom_message);
190 },
191 Error::TransactionInactive(None) => DOMErrorName::TransactionInactiveError,
192 Error::ReadOnly(Some(custom_message)) => {
193 return new_custom_exception(DOMErrorName::ReadOnlyError, custom_message);
194 },
195 Error::ReadOnly(None) => DOMErrorName::ReadOnlyError,
196 Error::Version(Some(custom_message)) => {
197 return new_custom_exception(DOMErrorName::VersionError, custom_message);
198 },
199 Error::Version(None) => DOMErrorName::VersionError,
200 Error::NoModificationAllowed(Some(custom_message)) => {
201 return new_custom_exception(DOMErrorName::NoModificationAllowedError, custom_message);
202 },
203 Error::NoModificationAllowed(None) => DOMErrorName::NoModificationAllowedError,
204 Error::QuotaExceeded { quota, requested } => {
205 return Ok(DomRoot::upcast(QuotaExceededError::new(
206 global,
207 DOMString::new(),
208 quota,
209 requested,
210 can_gc,
211 )));
212 },
213 Error::TypeMismatch(Some(custom_message)) => {
214 return new_custom_exception(DOMErrorName::TypeMismatchError, custom_message);
215 },
216 Error::TypeMismatch(None) => DOMErrorName::TypeMismatchError,
217 Error::InvalidModification(Some(custom_message)) => {
218 return new_custom_exception(DOMErrorName::InvalidModificationError, custom_message);
219 },
220 Error::InvalidModification(None) => DOMErrorName::InvalidModificationError,
221 Error::NotReadable(Some(custom_message)) => {
222 return new_custom_exception(DOMErrorName::NotReadableError, custom_message);
223 },
224 Error::NotReadable(None) => DOMErrorName::NotReadableError,
225 Error::Operation(Some(custom_message)) => {
226 return new_custom_exception(DOMErrorName::OperationError, custom_message);
227 },
228 Error::Operation(None) => DOMErrorName::OperationError,
229 Error::NotAllowed(Some(custom_message)) => {
230 return new_custom_exception(DOMErrorName::NotAllowedError, custom_message);
231 },
232 Error::NotAllowed(None) => DOMErrorName::NotAllowedError,
233 Error::Encoding(Some(custom_message)) => {
234 return new_custom_exception(DOMErrorName::EncodingError, custom_message);
235 },
236 Error::Encoding(None) => DOMErrorName::EncodingError,
237 Error::Constraint(Some(custom_message)) => {
238 return new_custom_exception(DOMErrorName::ConstraintError, custom_message);
239 },
240 Error::Constraint(None) => DOMErrorName::ConstraintError,
241 Error::Type(message) => return Err(JsEngineError::Type(message)),
242 Error::Range(message) => return Err(JsEngineError::Range(message)),
243 Error::JSFailed => return Err(JsEngineError::JSFailed),
244 };
245 Ok(DOMException::new(global, code, can_gc))
246}
247
248#[derive(Default)]
250pub(crate) struct ErrorInfo {
251 pub(crate) message: String,
253 pub(crate) filename: String,
255 pub(crate) lineno: c_uint,
257 pub(crate) column: c_uint,
259}
260
261impl ErrorInfo {
262 fn from_native_error(object: HandleObject, cx: SafeJSContext) -> Option<ErrorInfo> {
263 let report = unsafe { JS_ErrorFromException(*cx, object) };
264 if report.is_null() {
265 return None;
266 }
267
268 let filename = {
269 let filename = unsafe { (*report)._base.filename.data_ as *const u8 };
270 if !filename.is_null() {
271 let filename = unsafe {
272 let length = (0..).find(|idx| *filename.offset(*idx) == 0).unwrap();
273 from_raw_parts(filename, length as usize)
274 };
275 String::from_utf8_lossy(filename).into_owned()
276 } else {
277 "none".to_string()
278 }
279 };
280
281 let lineno = unsafe { (*report)._base.lineno };
282 let column = unsafe { (*report)._base.column._base };
283
284 let message = {
285 let message = unsafe { (*report)._base.message_.data_ as *const u8 };
286 let message = unsafe {
287 let length = (0..).find(|idx| *message.offset(*idx) == 0).unwrap();
288 from_raw_parts(message, length as usize)
289 };
290 String::from_utf8_lossy(message).into_owned()
291 };
292
293 Some(ErrorInfo {
294 filename,
295 message,
296 lineno,
297 column,
298 })
299 }
300
301 fn from_dom_exception(object: HandleObject, cx: SafeJSContext) -> Option<ErrorInfo> {
302 let exception = unsafe { root_from_object::<DOMException>(object.get(), *cx).ok()? };
303 let scripted_caller = unsafe { describe_scripted_caller(*cx) }.unwrap_or_default();
304 Some(ErrorInfo {
305 message: exception.stringifier().into(),
306 filename: scripted_caller.filename,
307 lineno: scripted_caller.line,
308 column: scripted_caller.col + 1,
309 })
310 }
311
312 fn from_object(object: HandleObject, cx: SafeJSContext) -> Option<ErrorInfo> {
313 if let Some(info) = ErrorInfo::from_native_error(object, cx) {
314 return Some(info);
315 }
316 if let Some(info) = ErrorInfo::from_dom_exception(object, cx) {
317 return Some(info);
318 }
319 None
320 }
321
322 pub(crate) fn from_value(value: HandleValue, cx: SafeJSContext, can_gc: CanGc) -> ErrorInfo {
324 if value.is_object() {
325 rooted!(in(*cx) let object = value.to_object());
326 if let Some(info) = ErrorInfo::from_object(object.handle(), cx) {
327 return info;
328 }
329 }
330
331 match USVString::safe_from_jsval(cx, value, (), can_gc) {
332 Ok(ConversionResult::Success(USVString(string))) => {
333 let scripted_caller = unsafe { describe_scripted_caller(*cx) }.unwrap_or_default();
334 ErrorInfo {
335 message: format!("uncaught exception: {}", string),
336 filename: scripted_caller.filename,
337 lineno: scripted_caller.line,
338 column: scripted_caller.col + 1,
339 }
340 },
341 _ => {
342 panic!("uncaught exception: failed to stringify primitive");
343 },
344 }
345 }
346}
347
348pub(crate) fn report_pending_exception(cx: SafeJSContext, realm: InRealm, can_gc: CanGc) {
350 rooted!(in(*cx) let mut value = UndefinedValue());
351 if take_pending_exception(cx, value.handle_mut()) {
352 GlobalScope::from_safe_context(cx, realm).report_an_exception(cx, value.handle(), can_gc);
353 }
354}
355
356fn take_pending_exception(cx: SafeJSContext, value: MutableHandleValue) -> bool {
357 unsafe {
358 if !JS_IsExceptionPending(*cx) {
359 return false;
360 }
361 }
362
363 unsafe {
364 if !JS_GetPendingException(*cx, value) {
365 JS_ClearPendingException(*cx);
366 error!("Uncaught exception: JS_GetPendingException failed");
367 return false;
368 }
369
370 JS_ClearPendingException(*cx);
371 }
372 true
373}
374
375pub(crate) fn javascript_error_info_from_error_info(
376 cx: &mut JSContext,
377 error_info: &ErrorInfo,
378 value: HandleValue,
379) -> JavaScriptErrorInfo {
380 let mut stack = || {
381 if !value.is_object() {
382 return None;
383 }
384
385 rooted!(&in(cx) let object = value.to_object());
386 rooted!(&in(cx) let mut stack_value = UndefinedValue());
387 if unsafe {
388 !JS_GetProperty(
389 cx,
390 object.handle(),
391 c"stack".as_ptr(),
392 stack_value.handle_mut(),
393 )
394 } {
395 return None;
396 }
397 if !stack_value.is_string() {
398 return None;
399 }
400 let stack_string = NonNull::new(stack_value.to_string())?;
401 Some(unsafe { jsstr_to_string(cx.raw_cx(), stack_string) })
402 };
403
404 JavaScriptErrorInfo {
405 message: error_info.message.clone(),
406 filename: error_info.filename.clone(),
407 line_number: error_info.lineno as u64,
408 column: error_info.column as u64,
409 stack: stack(),
410 }
411}
412
413pub(crate) fn take_and_report_pending_exception_for_api(
414 cx: &mut CurrentRealm,
415) -> Option<JavaScriptErrorInfo> {
416 let in_realm_proof = cx.into();
417 let in_realm = InRealm::Already(&in_realm_proof);
418
419 rooted!(&in(cx) let mut value = UndefinedValue());
420 if !take_pending_exception(cx.into(), value.handle_mut()) {
421 return None;
422 }
423
424 let error_info = ErrorInfo::from_value(value.handle(), cx.into(), CanGc::from_cx(cx));
425 let return_value = javascript_error_info_from_error_info(cx, &error_info, value.handle());
426 GlobalScope::from_safe_context(cx.into(), in_realm).report_an_error(
427 error_info,
428 value.handle(),
429 CanGc::from_cx(cx),
430 );
431
432 Some(return_value)
433}
434
435pub(crate) trait ErrorToJsval {
436 fn to_jsval(
437 self,
438 cx: SafeJSContext,
439 global: &GlobalScope,
440 rval: MutableHandleValue,
441 can_gc: CanGc,
442 );
443}
444
445impl ErrorToJsval for Error {
446 fn to_jsval(
448 self,
449 cx: SafeJSContext,
450 global: &GlobalScope,
451 rval: MutableHandleValue,
452 can_gc: CanGc,
453 ) {
454 match self {
455 Error::JSFailed => (),
456 _ => unsafe { assert!(!JS_IsExceptionPending(*cx)) },
457 }
458 throw_dom_exception(cx, global, self, can_gc);
459 unsafe {
460 assert!(JS_IsExceptionPending(*cx));
461 assert!(JS_GetPendingException(*cx, rval));
462 JS_ClearPendingException(*cx);
463 }
464 }
465}