Skip to content

Commit 4681c2e

Browse files
authored
feat(ext/web): queueMicrotask
feat(ext/web): queueMicrotask
2 parents d9234e3 + 704e0ab commit 4681c2e

File tree

6 files changed

+115
-4
lines changed

6 files changed

+115
-4
lines changed

.github/workflows/release.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ jobs:
5454
if [ "${{ matrix.os }}" = "windows-latest" ]; then
5555
mv andromeda.exe ${{ matrix.asset-name }}
5656
else
57+
mv andromeda ${{ matrix.asset-name }}
58+
fi
59+
5760
- name: Upload Binary to Release
5861
if: github.ref == 'refs/heads/main'
5962
uses: svenstaro/upload-release-action@v2

cli/src/repl.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
use crate::error::{AndromedaError, Result, print_error};
66
use crate::styles::format_js_value;
77
use andromeda_core::{HostData, RuntimeHostHooks};
8-
use andromeda_runtime::{recommended_builtins, recommended_extensions};
8+
use andromeda_runtime::{RuntimeMacroTask, recommended_builtins, recommended_extensions};
99
use console::Style;
1010
use nova_vm::{
1111
ecmascript::{
@@ -585,7 +585,7 @@ pub fn run_repl(expose_internals: bool, print_internals: bool, disable_gc: bool)
585585
let host_data = HostData::new(_macro_task_tx);
586586

587587
let host_hooks = RuntimeHostHooks::new(host_data);
588-
let host_hooks: &RuntimeHostHooks<()> = &*Box::leak(Box::new(host_hooks));
588+
let host_hooks: &RuntimeHostHooks<RuntimeMacroTask> = &*Box::leak(Box::new(host_hooks));
589589

590590
let mut agent = GcAgent::new(
591591
Options {

examples/queue_microtask.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
console.log("Testing queueMicrotask error reporting behavior...");
2+
3+
// This should execute successfully
4+
queueMicrotask(() => {
5+
console.log("✓ First microtask executed successfully");
6+
});
7+
8+
// This should throw an error but not crash the program
9+
queueMicrotask(() => {
10+
console.log("About to throw an error in microtask...");
11+
throw new Error("This is a test error in a microtask callback");
12+
});
13+
14+
// This should still execute despite the previous error
15+
queueMicrotask(() => {
16+
console.log("✓ Third microtask executed successfully after error");
17+
});
18+
19+
console.log("All microtasks queued. They will execute asynchronously...");
20+
21+
// Wait a bit to see the results
22+
setTimeout(() => {
23+
console.log("✓ Test completed - error reporting working correctly!");
24+
}, 50);

runtime/src/event_loop.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

55
use crate::ext::{interval::IntervalId, timeout::TimeoutId};
6+
use nova_vm::{ecmascript::types::Value, engine::Global};
67

78
pub enum RuntimeMacroTask {
89
/// Run an interval.
@@ -13,4 +14,6 @@ pub enum RuntimeMacroTask {
1314
RunAndClearTimeout(TimeoutId),
1415
/// Stop a timeout from running no further.
1516
ClearTimeout(TimeoutId),
17+
/// Run a microtask callback
18+
RunMicrotaskCallback(Global<Value<'static>>),
1619
}

runtime/src/ext/web/mod.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5+
use crate::RuntimeMacroTask;
56
use andromeda_core::{Extension, ExtensionOp};
7+
use andromeda_core::{HostData, MacroTask};
8+
use nova_vm::engine::Global;
69
use nova_vm::{
710
ecmascript::{
811
builtins::ArgumentsList,
@@ -11,6 +14,7 @@ use nova_vm::{
1114
},
1215
engine::context::{Bindable, GcScope, NoGcScope},
1316
};
17+
1418
use std::time::{Instant, SystemTime, UNIX_EPOCH};
1519

1620
// TODO: Get the time origin from when the runtime starts
@@ -45,6 +49,7 @@ impl WebExt {
4549
Self::internal_performance_time_origin,
4650
0,
4751
),
52+
ExtensionOp::new("queueMicrotask", Self::queue_microtask, 1),
4853
],
4954
storage: None,
5055
files: vec![
@@ -420,4 +425,50 @@ impl WebExt {
420425

421426
Ok(Value::from_f64(agent, origin_ms, gc).unbind())
422427
}
428+
429+
/// Implementation of queueMicrotask as per HTML specification:
430+
/// https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#dom-queuemicrotask
431+
pub fn queue_microtask<'gc>(
432+
agent: &mut Agent,
433+
_this: Value,
434+
args: ArgumentsList,
435+
gc: GcScope<'gc, '_>,
436+
) -> JsResult<'gc, Value<'gc>> {
437+
let callback = args.get(0);
438+
let _: nova_vm::ecmascript::types::Function = match callback.try_into() {
439+
Ok(function) => function,
440+
Err(_) => {
441+
return Err(agent
442+
.throw_exception(
443+
ExceptionType::TypeError,
444+
"The callback provided as an argument to queueMicrotask must be a function.".to_string(),
445+
gc.nogc(),
446+
)
447+
.unbind());
448+
}
449+
};
450+
451+
// TODO: Implement proper microtask queueing when Nova APIs become available
452+
453+
// Store the callback globally so it can be called later
454+
let root_callback = Global::new(agent, callback.unbind());
455+
456+
// Get host data to access the macro task system
457+
let host_data = agent.get_host_data();
458+
let host_data: &HostData<RuntimeMacroTask> = host_data.downcast_ref().unwrap();
459+
let macro_task_tx = host_data.macro_task_tx();
460+
461+
// Schedule an immediate task to call the callback
462+
// Using spawn_macro_task with an immediately resolving future
463+
host_data.spawn_macro_task(async move {
464+
// Send a macro task to call the callback
465+
macro_task_tx
466+
.send(MacroTask::User(RuntimeMacroTask::RunMicrotaskCallback(
467+
root_callback,
468+
)))
469+
.unwrap();
470+
});
471+
472+
Ok(Value::Undefined)
473+
}
423474
}

runtime/src/recommended.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22
// License, v. 2.0. If a copy of the MPL was not distributed with this
33
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
44

5-
use andromeda_core::{Extension, HostData};
6-
use nova_vm::ecmascript::execution::agent::{GcAgent, RealmRoot};
5+
use andromeda_core::{AndromedaError, ErrorReporter, Extension, HostData};
6+
use nova_vm::{
7+
ecmascript::execution::agent::{GcAgent, RealmRoot},
8+
ecmascript::types::{Function, Value},
9+
};
710

811
use crate::{ConsoleExt, FsExt, HeadersExt, ProcessExt, RuntimeMacroTask, TimeExt, URLExt, WebExt};
912

@@ -44,5 +47,32 @@ pub fn recommended_eventloop_handler(
4447
RuntimeMacroTask::ClearTimeout(timeout_id) => {
4548
timeout_id.clear_and_abort(host_data);
4649
}
50+
RuntimeMacroTask::RunMicrotaskCallback(root_callback) => {
51+
// Execute the microtask callback - this is for queueMicrotask implementation
52+
agent.run_in_realm(realm_root, |agent, gc| {
53+
let callback = root_callback.get(agent, gc.nogc());
54+
if let Ok(callback_function) = Function::try_from(callback) {
55+
// Call the callback with no arguments as per HTML spec
56+
// If the callback throws an error, it should be reported but not stop execution
57+
if let Err(error) = callback_function.call(agent, Value::Undefined, &mut [], gc)
58+
{
59+
// Report the error as per Web IDL "invoke a callback function" with "report" error handling
60+
// This is the proper implementation of error reporting for queueMicrotask
61+
report_microtask_error(error);
62+
}
63+
}
64+
});
65+
}
4766
}
4867
}
68+
69+
/// Report a microtask error
70+
/// This is called when a queueMicrotask callback throws an exception.
71+
fn report_microtask_error<E>(error: E)
72+
where
73+
E: std::fmt::Debug,
74+
{
75+
let error_message = format!("Uncaught error in microtask callback: {:?}", error);
76+
let andromeda_error = AndromedaError::runtime_error(error_message);
77+
ErrorReporter::print_error(&andromeda_error);
78+
}

0 commit comments

Comments
 (0)