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