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