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