Skip to content

Commit 92f33da

Browse files
authored
feat: add support for queueMicrotask (#1266)
feat(graalvm): add support for `queueMicrotask` feat(graalvm): add support for `queueMicrotask` test(graalvm): add tests for `queueMicrotask` Fixes and closes #1265 Relates-to: #1265 Signed-off-by: Sam Gammon <sam@elide.dev>
1 parent ab3eccc commit 92f33da

File tree

7 files changed

+200
-6
lines changed

7 files changed

+200
-6
lines changed

packages/engine/src/main/kotlin/elide/runtime/core/PolyglotContext.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ public interface PolyglotContext {
176176
*
177177
* @param source The guest code to be executed.
178178
* @param internals Whether to allow access to internal runtime features; the provided [source] must be marked as
179-
* internal to enable this acesss.
179+
* internal to enable this access.
180180
* @return The result of evaluating the [source].
181181
*/
182182
public fun evaluate(source: Source, internals: Boolean): PolyglotValue =

packages/graalvm/api/graalvm.api

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,14 @@ public final class elide/runtime/gvm/internals/intrinsics/ElideIntrinsicKt {
517517
public static final fun installElideBuiltin (Ljava/lang/String;Ljava/lang/Object;)V
518518
}
519519

520+
public abstract class elide/runtime/gvm/internals/intrinsics/js/AbstractJsIntrinsic : elide/runtime/intrinsics/GuestIntrinsic {
521+
public fun <init> ()V
522+
public fun displayName ()Ljava/lang/String;
523+
public fun language ()Lelide/runtime/gvm/GuestLanguage;
524+
public fun symbolicName ()Ljava/lang/String;
525+
public fun toString ()Ljava/lang/String;
526+
}
527+
520528
public synthetic class elide/runtime/gvm/internals/intrinsics/js/abort/$AbortControllerIntrinsic$Definition : io/micronaut/context/AbstractInitializableBeanDefinitionAndReference {
521529
public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata;
522530
public fun <init> ()V
@@ -5771,6 +5779,29 @@ public abstract interface class elide/runtime/intrinsics/testing/TestingAPI$Test
57715779
public abstract interface class elide/runtime/intrinsics/testing/TestingAPI$TestGraphNode$Test : elide/runtime/intrinsics/testing/TestingAPI$TestGraphNode {
57725780
}
57735781

5782+
public synthetic class elide/runtime/javascript/$QueueMicrotaskCallable$Definition : io/micronaut/context/AbstractInitializableBeanDefinitionAndReference {
5783+
public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata;
5784+
public fun <init> ()V
5785+
protected fun <init> (Ljava/lang/Class;Lio/micronaut/context/AbstractInitializableBeanDefinition$MethodOrFieldReference;)V
5786+
public fun instantiate (Lio/micronaut/context/BeanResolutionContext;Lio/micronaut/context/BeanContext;)Ljava/lang/Object;
5787+
public fun isEnabled (Lio/micronaut/context/BeanContext;)Z
5788+
public fun isEnabled (Lio/micronaut/context/BeanContext;Lio/micronaut/context/BeanResolutionContext;)Z
5789+
public fun load ()Lio/micronaut/inject/BeanDefinition;
5790+
}
5791+
5792+
public final synthetic class elide/runtime/javascript/$QueueMicrotaskCallable$Introspection : io/micronaut/inject/beans/AbstractInitializableBeanIntrospectionAndReference {
5793+
public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata;
5794+
public fun <init> ()V
5795+
public fun hasBuilder ()Z
5796+
public fun isBuildable ()Z
5797+
}
5798+
5799+
public final class elide/runtime/javascript/QueueMicrotaskCallable : elide/runtime/gvm/internals/intrinsics/js/AbstractJsIntrinsic, org/graalvm/polyglot/proxy/ProxyExecutable {
5800+
public fun <init> (Lelide/runtime/exec/GuestExecutorProvider;)V
5801+
public fun execute ([Lorg/graalvm/polyglot/Value;)Ljava/lang/Object;
5802+
public fun install (Lelide/runtime/intrinsics/GuestIntrinsic$MutableIntrinsicBindings;)V
5803+
}
5804+
57745805
public synthetic class elide/runtime/node/asserts/$NodeAssertModule$Definition : io/micronaut/context/AbstractInitializableBeanDefinitionAndReference {
57755806
public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata;
57765807
public fun <init> ()V

packages/graalvm/src/main/kotlin/elide/runtime/gvm/internals/intrinsics/js/AbstractJsIntrinsic.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import elide.runtime.gvm.GraalVMGuest
1717
import elide.runtime.intrinsics.GuestIntrinsic
1818

1919
/** Abstract base class for all intrinsic implementations. */
20-
internal abstract class AbstractJsIntrinsic : GuestIntrinsic {
20+
public abstract class AbstractJsIntrinsic : GuestIntrinsic {
2121
override fun language(): GuestLanguage = GraalVMGuest.JAVASCRIPT
2222
override fun symbolicName(): String = "native code"
2323
@Deprecated("Use symbolicName instead", ReplaceWith("symbolicName"))
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright (c) 2024-2025 Elide Technologies, Inc.
3+
*
4+
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
5+
* with the License. You may obtain a copy of the License at
6+
*
7+
* https://opensource.org/license/mit/
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
* License for the specific language governing permissions and limitations under the License.
12+
*/
13+
@file:OptIn(DelicateElideApi::class)
14+
15+
package elide.runtime.javascript
16+
17+
import elide.runtime.exec.GuestExecutorProvider
18+
import elide.runtime.gvm.api.Intrinsic
19+
import elide.runtime.gvm.internals.intrinsics.js.AbstractJsIntrinsic
20+
import elide.runtime.gvm.js.JsError
21+
import elide.runtime.intrinsics.GuestIntrinsic
22+
import jakarta.inject.Inject
23+
import jakarta.inject.Singleton
24+
import org.graalvm.polyglot.Value
25+
import org.graalvm.polyglot.proxy.ProxyExecutable
26+
import elide.runtime.core.DelicateElideApi
27+
import elide.runtime.gvm.js.JsSymbol.JsSymbols.asPublicJsSymbol
28+
import elide.runtime.gvm.js.undefined
29+
30+
// Name of the `queueMicrotask` function in the global scope.
31+
private const val QUEUE_MICROTASK_NAME = "queueMicrotask"
32+
33+
// Public JavaScript symbol for the `queueMicrotask` function.
34+
private val QUEUE_MICROTASK_SYMBOL = QUEUE_MICROTASK_NAME.asPublicJsSymbol()
35+
36+
/**
37+
* ## Queue Microtask Callable
38+
*
39+
* Mounts a callable intrinsic function at the name `queueMicrotask`, in compliance with Web JavaScript standards which
40+
* expect this function to be available in the global scope. The `queueMicrotask` function is used to queue a chunk of
41+
* code to execute safely on the JavaScript event loop.
42+
*
43+
* [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window/queueMicrotask)
44+
*/
45+
@Singleton
46+
@Intrinsic(QUEUE_MICROTASK_NAME) public class QueueMicrotaskCallable @Inject constructor (
47+
private val executorProvider: GuestExecutorProvider,
48+
) : ProxyExecutable, AbstractJsIntrinsic() {
49+
override fun install(bindings: GuestIntrinsic.MutableIntrinsicBindings) {
50+
bindings[QUEUE_MICROTASK_SYMBOL] = this
51+
}
52+
53+
internal operator fun invoke(callable: () -> Unit) {
54+
executorProvider.executor().execute {
55+
callable.invoke()
56+
}
57+
}
58+
59+
override fun execute(vararg arguments: Value?): Any? {
60+
val first = arguments.firstOrNull() ?: throw JsError.typeError("First argument to `queueMicrotask` is required")
61+
if (!first.canExecute()) throw JsError.typeError("First argument to `queueMicrotask` must be a function")
62+
invoke(first::executeVoid)
63+
return undefined()
64+
}
65+
}

packages/graalvm/src/main/kotlin/elide/runtime/plugins/js/Extensions.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2024 Elide Technologies, Inc.
2+
* Copyright (c) 2024-2025 Elide Technologies, Inc.
33
*
44
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
55
* with the License. You may obtain a copy of the License at
@@ -61,7 +61,7 @@ import elide.runtime.core.evaluate
6161
* [PolyglotContext.evaluate] and selecting [JavaScript] as source language.
6262
*
6363
* @param source The interpreted JavaScript source code to be executed.
64-
* @return The result of the invocation. If [esm] is `true`, an object is returned, with exported values as members.
64+
* @return The result of the invocation; an object is returned, with exported values as members.
6565
*/
6666
@DelicateElideApi public fun PolyglotContext.javascript(source: Source): PolyglotValue =
6767
evaluate(source)

packages/graalvm/src/test/kotlin/elide/runtime/intrinsics/js/JsGlobalsTest.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ private const val ENABLE_SUPPRESSIONS = true
5959
"isNaN",
6060
"parseFloat",
6161
"parseInt",
62+
"queueMicrotask",
6263
"decodeURI",
6364
"decodeURIComponent",
6465
"encodeURI",
@@ -158,7 +159,6 @@ private const val ENABLE_SUPPRESSIONS = true
158159
"PerformanceObserverEntryList",
159160
"performance",
160161
"process",
161-
"queueMicrotask",
162162
"ReadableByteStreamController",
163163
"ReadableStream",
164164
"ReadableStreamBYOBReader",
@@ -214,7 +214,6 @@ private const val ENABLE_SUPPRESSIONS = true
214214
"navigator", // not yet implemented
215215
"setImmediate", // not yet implemented
216216
"clearImmediate", // not yet implemented
217-
"queueMicrotask", // not yet implemented
218217
"structuredClone", // not yet implemented
219218
"InternalError", // web-standard only, not present in non-browser runtimes
220219
"BroadcastChannel", // not yet implemented
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Copyright (c) 2024-2025 Elide Technologies, Inc.
3+
*
4+
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
5+
* with the License. You may obtain a copy of the License at
6+
*
7+
* https://opensource.org/license/mit/
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
10+
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11+
* License for the specific language governing permissions and limitations under the License.
12+
*/
13+
package elide.runtime.javascript
14+
15+
import org.graalvm.polyglot.Value
16+
import org.graalvm.polyglot.proxy.ProxyExecutable
17+
import org.junit.jupiter.api.assertDoesNotThrow
18+
import org.junit.jupiter.api.assertNotNull
19+
import org.junit.jupiter.api.assertThrows
20+
import kotlin.test.assertTrue
21+
import elide.annotations.Inject
22+
import elide.runtime.core.DelicateElideApi
23+
import elide.runtime.exec.GuestExecution
24+
import elide.runtime.exec.GuestExecutorProvider
25+
import elide.runtime.gvm.internals.js.AbstractJsIntrinsicTest
26+
import elide.runtime.gvm.js.undefined
27+
import elide.runtime.intrinsics.js.err.TypeError
28+
import elide.runtime.plugins.js.javascript
29+
import elide.testing.annotations.Test
30+
import elide.testing.annotations.TestCase
31+
32+
@TestCase internal class QueueMicrotaskTest : AbstractJsIntrinsicTest<QueueMicrotaskCallable>() {
33+
@Inject lateinit var queueMicrotask: QueueMicrotaskCallable
34+
override fun provide(): QueueMicrotaskCallable = queueMicrotask
35+
36+
@Test override fun testInjectable() {
37+
assertNotNull(queueMicrotask)
38+
}
39+
40+
@Test fun testExecMicrotask() {
41+
val exec = GuestExecution.direct()
42+
val prov = GuestExecutorProvider { exec }
43+
val fresh = QueueMicrotaskCallable(prov)
44+
var didExec = false
45+
val invocable = { didExec = true }
46+
assertDoesNotThrow { fresh.invoke(invocable) }
47+
assertTrue(didExec)
48+
}
49+
50+
@Test fun testExecMicrotaskGuest() = dual {
51+
val exec = GuestExecution.direct()
52+
val prov = GuestExecutorProvider { exec }
53+
val fresh = QueueMicrotaskCallable(prov)
54+
var didExec = false
55+
val invocable = { didExec = true }
56+
assertDoesNotThrow { fresh.invoke(invocable) }
57+
assertTrue(didExec)
58+
}.guest {
59+
// language=JavaScript
60+
"""
61+
let didExec = false;
62+
queueMicrotask(() => didExec = true);
63+
test(didExec).isEqualTo(true);
64+
"""
65+
}
66+
67+
@OptIn(DelicateElideApi::class)
68+
@Test fun testExecMicrotaskGuestDirect() {
69+
val exec = GuestExecution.direct()
70+
val prov = GuestExecutorProvider { exec }
71+
val fresh = QueueMicrotaskCallable(prov)
72+
val guestFn = withContext {
73+
javascript(
74+
// language=JavaScript
75+
"""
76+
const fn = (() => {
77+
// hello
78+
});
79+
fn;
80+
"""
81+
)
82+
}
83+
84+
assertNotNull(guestFn)
85+
assertDoesNotThrow { fresh.execute(guestFn) }
86+
}
87+
88+
@Test fun testExecMicrotaskRejectsNulls() {
89+
assertThrows<TypeError> { queueMicrotask.execute(Value.asValue(null)) }
90+
}
91+
92+
@Test fun testExecMicrotaskRejectsNonExecutable() {
93+
assertThrows<TypeError> { queueMicrotask.execute(Value.asValue(5)) }
94+
}
95+
96+
@Test fun testExecMicrotaskRejectsNoArgs() {
97+
assertThrows<TypeError> { queueMicrotask.execute() }
98+
}
99+
}

0 commit comments

Comments
 (0)