char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somsnapshot: capture and restore extension layouts in one artifactd71572b

main
5.9 KiB189 linesraw
1use std::cell::RefCell;
2use std::io::Write;
3use std::rc::Rc;
4
5use crate::v8;
6
7type Handler = v8::Global<v8::Function>;
8type Handlers = Rc<RefCell<Option<[Handler; 3]>>>;
9type Processed = Rc<RefCell<Option<v8::Global<v8::Value>>>>;
10
11pub(crate) fn clear(isolate: &mut v8::Isolate) {
12    isolate.remove_slot::<Handlers>();
13    isolate.remove_slot::<Processed>();
14}
15
16pub(crate) fn has_handlers(isolate: &v8::Isolate) -> bool {
17    isolate.get_slot::<Handlers>().unwrap().borrow().is_some()
18}
19
20pub(crate) fn init(isolate: &mut v8::Isolate) {
21    isolate.set_slot::<Handlers>(Rc::new(RefCell::new(None)));
22    isolate.set_slot::<Processed>(Rc::new(RefCell::new(None)));
23}
24
25pub(crate) fn is_promise(
26    scope: &mut v8::PinScope,
27    args: v8::FunctionCallbackArguments,
28    mut result: v8::ReturnValue,
29) {
30    let is_promise = v8::Local::<v8::Promise>::try_from(args.get(0)).is_ok();
31    result.set(v8::Boolean::new(scope, is_promise).into());
32}
33
34pub(crate) fn set_event_handlers(
35    scope: &mut v8::PinScope,
36    args: v8::FunctionCallbackArguments,
37    _result: v8::ReturnValue,
38) {
39    let handlers = [0, 1, 2].map(|index| v8::Local::<v8::Function>::try_from(args.get(index)));
40    let [Ok(exception), Ok(unhandled), Ok(handled)] = handlers else {
41        let message = v8::String::new(scope, "Expected three event handlers").unwrap();
42        scope.throw_exception(v8::Exception::type_error(scope, message));
43        return;
44    };
45    let slot = scope.get_slot::<Handlers>().unwrap().clone();
46    let mut slot = slot.borrow_mut();
47    if slot.is_some() {
48        let message = v8::String::new(scope, "Event handlers already registered").unwrap();
49        scope.throw_exception(v8::Exception::error(scope, message));
50        return;
51    }
52    *slot = Some([
53        v8::Global::new(scope, exception),
54        v8::Global::new(scope, unhandled),
55        v8::Global::new(scope, handled),
56    ]);
57}
58
59pub(crate) fn report_exception_with_location<'s, 'i>(
60    scope: &mut v8::PinScope<'s, 'i>,
61    error: v8::Local<'s, v8::Value>,
62    filename: &str,
63    line: u32,
64    column: u32,
65) -> bool {
66    let (filename, line, column) =
67        crate::modules::map_source_location(scope, filename, line, column)
68            .unwrap_or_else(|| (filename.to_owned(), line, column));
69    let filename = v8::String::new(scope, &filename).unwrap();
70    let line = v8::Integer::new_from_unsigned(scope, line);
71    let column = v8::Integer::new_from_unsigned(scope, column);
72    call_boolean(
73        scope,
74        0,
75        &[error, filename.into(), line.into(), column.into()],
76    )
77}
78
79pub(crate) fn report_exception_for_value<'s, 'i>(
80    scope: &mut v8::PinScope<'s, 'i>,
81    error: v8::Local<'s, v8::Value>,
82) -> bool {
83    let message = v8::Exception::create_message(scope, error);
84    let filename = message
85        .get_script_resource_name(scope)
86        .map(|name| name.to_rust_string_lossy(scope))
87        .unwrap_or_default();
88    let line = message
89        .get_line_number(scope)
90        .unwrap_or_default()
91        .try_into()
92        .unwrap_or_default();
93    let column = message
94        .get_start_column()
95        .saturating_add(1)
96        .try_into()
97        .unwrap_or_default();
98    report_exception_with_location(scope, error, &filename, line, column)
99}
100
101pub(crate) fn report_nonfatal_exception<'s>(
102    scope: &mut v8::PinScope<'s, '_>,
103    args: v8::FunctionCallbackArguments<'s>,
104    _result: v8::ReturnValue,
105) {
106    let error = args.get(0);
107    if report_exception_for_value(scope, error) {
108        return;
109    }
110    v8::tc_scope!(let scope, scope);
111    let stack = v8::Local::<v8::Object>::try_from(error)
112        .ok()
113        .and_then(|object| {
114            let key = v8::String::new(scope, "stack")?;
115            object.get(scope, key.into())
116        })
117        .filter(|stack| !stack.is_undefined());
118    // Hostile stack getters or formatters must not turn reporting into a new failure.
119    scope.reset();
120    let text = stack.unwrap_or(error).to_rust_string_lossy(scope);
121    scope.reset();
122    let _ = writeln!(std::io::stderr(), "{text}");
123}
124
125pub(crate) fn suppress_exception(scope: &mut v8::PinScope, error: v8::Local<v8::Value>) {
126    mark_processed_exception(scope, error);
127}
128
129pub(crate) fn mark_processed_exception(scope: &mut v8::PinScope, error: v8::Local<v8::Value>) {
130    *scope.get_slot::<Processed>().unwrap().borrow_mut() = Some(v8::Global::new(scope, error));
131}
132
133pub(crate) fn was_processed(scope: &mut v8::PinScope, error: v8::Local<v8::Value>) -> bool {
134    let processed = scope.get_slot::<Processed>().unwrap().clone();
135    let mut processed = processed.borrow_mut();
136    if processed
137        .as_ref()
138        .is_some_and(|value| v8::Local::new(scope, value) == error)
139    {
140        processed.take();
141        true
142    } else {
143        false
144    }
145}
146
147pub(crate) fn clear_processed(scope: &mut v8::PinScope) {
148    scope.get_slot::<Processed>().unwrap().borrow_mut().take();
149}
150
151pub(crate) fn report_unhandled_rejection<'s, 'i>(
152    scope: &mut v8::PinScope<'s, 'i>,
153    promise: v8::Local<'s, v8::Promise>,
154    reason: v8::Local<'s, v8::Value>,
155) -> bool {
156    call_boolean(scope, 1, &[promise.into(), reason])
157}
158
159pub(crate) fn report_rejection_handled<'s, 'i>(
160    scope: &mut v8::PinScope<'s, 'i>,
161    promise: v8::Local<'s, v8::Promise>,
162    reason: v8::Local<'s, v8::Value>,
163) {
164    call(scope, 2, &[promise.into(), reason]);
165}
166
167fn call_boolean<'s, 'i>(
168    scope: &mut v8::PinScope<'s, 'i>,
169    index: usize,
170    args: &[v8::Local<'s, v8::Value>],
171) -> bool {
172    call(scope, index, args).is_some_and(|value| value.boolean_value(scope))
173}
174
175fn call<'s, 'i>(
176    scope: &mut v8::PinScope<'s, 'i>,
177    index: usize,
178    args: &[v8::Local<'s, v8::Value>],
179) -> Option<v8::Local<'s, v8::Value>> {
180    let handlers = scope.get_slot::<Handlers>()?.clone();
181    let handler = handlers.borrow().as_ref()?.get(index)?.clone();
182    let handler = v8::Local::new(scope, &handler);
183    v8::tc_scope!(let scope, scope);
184    let result = handler.call(scope, v8::undefined(scope).into(), args);
185    if result.is_none() {
186        scope.reset();
187    }
188    result
189}