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