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