Skip to content

Commit

Permalink
Adding queueMicrotask() to global scope (#218)
Browse files Browse the repository at this point in the history
* Adding queueMicrotask() to the global scope

* Updating the README.md

* Bumping version to v0.7

* Catching exceptions from microtask callbacks

* Fixing multiple queueMicrotask calls throwing exceptions
  • Loading branch information
aalykiot authored Apr 27, 2024
1 parent ad12c08 commit 8dbdc63
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 21 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "dune"
version = "0.6.0"
version = "0.7.0"
authors = ["Alex Alikiotis <[email protected]>"]
edition = "2021"
license = "MIT"
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ For more examples look at the <a href="./examples">examples</a> directory.
- [x] `structuredClone`: Creates a deep clone of a given value.
- [x] `AbortController` / `AbortSignal`: Allows you to communicate with a request and abort it.
- [x] `fetch`: A wrapper around `http.request` (not fully compatible with WHATWG fetch).
- [x] `queueMicrotask`: Queues a microtask to invoke a callback.

### Module Metadata

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dune",
"version": "0.6.0",
"version": "0.7.0",
"description": "A hobby runtime for JavaScript and TypeScript 🚀",
"homepage": "https://github.com/aalykiot/dune#readme",
"keywords": [],
Expand Down
61 changes: 49 additions & 12 deletions src/bindings.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::dns;
use crate::errors::extract_error_code;
use crate::errors::report_and_exit;
use crate::errors::IoError;
use crate::exceptions;
use crate::file;
Expand All @@ -8,6 +9,8 @@ use crate::net;
use crate::perf_hooks;
use crate::process;
use crate::promise;
use crate::runtime::check_exceptions;
use crate::runtime::JsRuntime;
use crate::signals;
use crate::stdio;
use crate::timers;
Expand Down Expand Up @@ -48,24 +51,58 @@ pub fn create_new_context<'s>(scope: &mut v8::HandleScope<'s, ()>) -> v8::Local<
let global = context.global(scope);
let scope = &mut v8::ContextScope::new(scope, context);

// Simple print function bound to Rust's println! macro (synchronous call).
set_function_to(
scope,
global,
"print",
|scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
mut _rv: v8::ReturnValue| {
let value = args.get(0).to_rust_string_lossy(scope);
println!("{value}");
},
);
set_function_to(scope, global, "print", global_print);
set_function_to(scope, global, "reportError", global_report_error);
set_function_to(scope, global, "$$queueMicro", global_queue_micro);

// Expose low-level functions to JavaScript.
process::initialize(scope, global);
scope.escape(context)
}

// Simple print function bound to Rust's println! macro.
fn global_print(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
_: v8::ReturnValue,
) {
let value = args.get(0).to_rust_string_lossy(scope);
println!("{value}");
}

// This method may be used to report errors to global event handlers.
// https://html.spec.whatwg.org/multipage/webappapis.html#report-the-exception
fn global_report_error(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
_: v8::ReturnValue,
) {
let exception = v8::Global::new(scope, args.get(0));
let state_rc = JsRuntime::state(scope);
let mut state = state_rc.borrow_mut();

state.exceptions.capture_exception(exception);
drop(state);

if let Some(error) = check_exceptions(scope) {
report_and_exit(error);
}
}

// This method queues a microtask to invoke callback.
fn global_queue_micro(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
_: v8::ReturnValue,
) {
let callback = v8::Local::<v8::Function>::try_from(args.get(0)).unwrap();
let state_rc = JsRuntime::state(scope);
let state = state_rc.borrow();
let ctx = state.context.open(scope);

ctx.get_microtask_queue().enqueue_microtask(scope, callback);
}

/// Adds a property with the given name and value, into the given object.
pub fn set_property_to(
scope: &mut v8::HandleScope<'_>,
Expand Down
8 changes: 2 additions & 6 deletions src/exceptions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ pub fn initialize(scope: &mut v8::HandleScope) -> v8::Global<v8::Object> {
fn set_uncaught_exception_callback(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
_: v8::ReturnValue,
) {
// Note: Passing `null` from JavaScript essentially will unset the defined callback.
let callback = match v8::Local::<v8::Function>::try_from(args.get(0)) {
Expand All @@ -110,15 +110,13 @@ fn set_uncaught_exception_callback(
let mut state = state_rc.borrow_mut();

state.exceptions.set_uncaught_exception_callback(callback);

rv.set(v8::Boolean::new(scope, true).into());
}

/// Setting the `unhandled_rejection_callback` from JavaScript.
fn set_unhandled_rejection_callback(
scope: &mut v8::HandleScope,
args: v8::FunctionCallbackArguments,
mut rv: v8::ReturnValue,
_: v8::ReturnValue,
) {
// Note: Passing `null` from JavaScript essentially will unset the defined callback.
let callback = match v8::Local::<v8::Function>::try_from(args.get(0)) {
Expand All @@ -130,6 +128,4 @@ fn set_unhandled_rejection_callback(
let mut state = state_rc.borrow_mut();

state.exceptions.set_unhandled_rejection_callback(callback);

rv.set(v8::Boolean::new(scope, true).into());
}
21 changes: 21 additions & 0 deletions src/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,26 @@ function makeGlobal(name, value) {
globalThis[name] = value;
}

const { $$queueMicro, reportError } = globalThis;

// Note: We wrap `queueMicrotask` and manually emit the exception because
// v8 doesn't provide any mechanism to handle callback exceptions during
// the microtask_checkpoint phase.
function queueMicrotask(callback) {
// Check if the callback argument is a valid type.
if (typeof callback !== 'function') {
throw new TypeError(`The "callback" argument must be of type function.`);
}

$$queueMicro(() => {
try {
callback();
} catch (err) {
reportError(err);
}
});
}

const console = new Console();
const consoleFromV8 = globalThis['console'];

Expand All @@ -23,6 +43,7 @@ wrapConsole(console, consoleFromV8);
/* Initialize global environment for user script */

makeGlobal('process', process);
makeGlobal('queueMicrotask', queueMicrotask);
makeGlobal('console', console);
makeGlobal('prompt', prompt);

Expand Down

0 comments on commit 8dbdc63

Please sign in to comment.