use std::cell::RefCell; use std::io::Write; use std::rc::Rc; use crate::v8; type Handler = v8::Global; type Handlers = Rc>>; type Processed = Rc>>>; pub(crate) fn clear(isolate: &mut v8::Isolate) { isolate.remove_slot::(); isolate.remove_slot::(); } pub(crate) fn has_handlers(isolate: &v8::Isolate) -> bool { isolate.get_slot::().unwrap().borrow().is_some() } pub(crate) fn init(isolate: &mut v8::Isolate) { isolate.set_slot::(Rc::new(RefCell::new(None))); isolate.set_slot::(Rc::new(RefCell::new(None))); } pub(crate) fn is_promise( scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, mut result: v8::ReturnValue, ) { let is_promise = v8::Local::::try_from(args.get(0)).is_ok(); result.set(v8::Boolean::new(scope, is_promise).into()); } pub(crate) fn set_event_handlers( scope: &mut v8::PinScope, args: v8::FunctionCallbackArguments, _result: v8::ReturnValue, ) { let handlers = [0, 1, 2].map(|index| v8::Local::::try_from(args.get(index))); let [Ok(exception), Ok(unhandled), Ok(handled)] = handlers else { let message = v8::String::new(scope, "Expected three event handlers").unwrap(); scope.throw_exception(v8::Exception::type_error(scope, message)); return; }; let slot = scope.get_slot::().unwrap().clone(); let mut slot = slot.borrow_mut(); if slot.is_some() { let message = v8::String::new(scope, "Event handlers already registered").unwrap(); scope.throw_exception(v8::Exception::error(scope, message)); return; } *slot = Some([ v8::Global::new(scope, exception), v8::Global::new(scope, unhandled), v8::Global::new(scope, handled), ]); } pub(crate) fn report_exception_with_location<'s, 'i>( scope: &mut v8::PinScope<'s, 'i>, error: v8::Local<'s, v8::Value>, filename: &str, line: u32, column: u32, ) -> bool { let (filename, line, column) = crate::modules::map_source_location(scope, filename, line, column) .unwrap_or_else(|| (filename.to_owned(), line, column)); let filename = v8::String::new(scope, &filename).unwrap(); let line = v8::Integer::new_from_unsigned(scope, line); let column = v8::Integer::new_from_unsigned(scope, column); call_boolean( scope, 0, &[error, filename.into(), line.into(), column.into()], ) } pub(crate) fn report_exception_for_value<'s, 'i>( scope: &mut v8::PinScope<'s, 'i>, error: v8::Local<'s, v8::Value>, ) -> bool { let message = v8::Exception::create_message(scope, error); let filename = message .get_script_resource_name(scope) .map(|name| name.to_rust_string_lossy(scope)) .unwrap_or_default(); let line = message .get_line_number(scope) .unwrap_or_default() .try_into() .unwrap_or_default(); let column = message .get_start_column() .saturating_add(1) .try_into() .unwrap_or_default(); report_exception_with_location(scope, error, &filename, line, column) } pub(crate) fn report_nonfatal_exception<'s>( scope: &mut v8::PinScope<'s, '_>, args: v8::FunctionCallbackArguments<'s>, _result: v8::ReturnValue, ) { let error = args.get(0); if report_exception_for_value(scope, error) { return; } v8::tc_scope!(let scope, scope); let stack = v8::Local::::try_from(error) .ok() .and_then(|object| { let key = v8::String::new(scope, "stack")?; object.get(scope, key.into()) }) .filter(|stack| !stack.is_undefined()); // Hostile stack getters or formatters must not turn reporting into a new failure. scope.reset(); let text = stack.unwrap_or(error).to_rust_string_lossy(scope); scope.reset(); let _ = writeln!(std::io::stderr(), "{text}"); } pub(crate) fn suppress_exception(scope: &mut v8::PinScope, error: v8::Local) { mark_processed_exception(scope, error); } pub(crate) fn mark_processed_exception(scope: &mut v8::PinScope, error: v8::Local) { *scope.get_slot::().unwrap().borrow_mut() = Some(v8::Global::new(scope, error)); } pub(crate) fn was_processed(scope: &mut v8::PinScope, error: v8::Local) -> bool { let processed = scope.get_slot::().unwrap().clone(); let mut processed = processed.borrow_mut(); if processed .as_ref() .is_some_and(|value| v8::Local::new(scope, value) == error) { processed.take(); true } else { false } } pub(crate) fn clear_processed(scope: &mut v8::PinScope) { scope.get_slot::().unwrap().borrow_mut().take(); } pub(crate) fn report_unhandled_rejection<'s, 'i>( scope: &mut v8::PinScope<'s, 'i>, promise: v8::Local<'s, v8::Promise>, reason: v8::Local<'s, v8::Value>, ) -> bool { call_boolean(scope, 1, &[promise.into(), reason]) } pub(crate) fn report_rejection_handled<'s, 'i>( scope: &mut v8::PinScope<'s, 'i>, promise: v8::Local<'s, v8::Promise>, reason: v8::Local<'s, v8::Value>, ) { call(scope, 2, &[promise.into(), reason]); } fn call_boolean<'s, 'i>( scope: &mut v8::PinScope<'s, 'i>, index: usize, args: &[v8::Local<'s, v8::Value>], ) -> bool { call(scope, index, args).is_some_and(|value| value.boolean_value(scope)) } fn call<'s, 'i>( scope: &mut v8::PinScope<'s, 'i>, index: usize, args: &[v8::Local<'s, v8::Value>], ) -> Option> { let handlers = scope.get_slot::()?.clone(); let handler = handlers.borrow().as_ref()?.get(index)?.clone(); let handler = v8::Local::new(scope, &handler); v8::tc_scope!(let scope, scope); let result = handler.call(scope, v8::undefined(scope).into(), args); if result.is_none() { scope.reset(); } result }