Skip to content

Commit

Permalink
feat(graalvm): add support for queueMicrotask
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
sgammon committed Mar 9, 2025
1 parent ab3eccc commit dc7c24c
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ public interface PolyglotContext {
*
* @param source The guest code to be executed.
* @param internals Whether to allow access to internal runtime features; the provided [source] must be marked as
* internal to enable this acesss.
* internal to enable this access.
* @return The result of evaluating the [source].
*/
public fun evaluate(source: Source, internals: Boolean): PolyglotValue =
Expand Down
31 changes: 31 additions & 0 deletions packages/graalvm/api/graalvm.api
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,14 @@ public final class elide/runtime/gvm/internals/intrinsics/ElideIntrinsicKt {
public static final fun installElideBuiltin (Ljava/lang/String;Ljava/lang/Object;)V
}

public abstract class elide/runtime/gvm/internals/intrinsics/js/AbstractJsIntrinsic : elide/runtime/intrinsics/GuestIntrinsic {
public fun <init> ()V
public fun displayName ()Ljava/lang/String;
public fun language ()Lelide/runtime/gvm/GuestLanguage;
public fun symbolicName ()Ljava/lang/String;
public fun toString ()Ljava/lang/String;
}

public synthetic class elide/runtime/gvm/internals/intrinsics/js/abort/$AbortControllerIntrinsic$Definition : io/micronaut/context/AbstractInitializableBeanDefinitionAndReference {
public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata;
public fun <init> ()V
Expand Down Expand Up @@ -5771,6 +5779,29 @@ public abstract interface class elide/runtime/intrinsics/testing/TestingAPI$Test
public abstract interface class elide/runtime/intrinsics/testing/TestingAPI$TestGraphNode$Test : elide/runtime/intrinsics/testing/TestingAPI$TestGraphNode {
}

public synthetic class elide/runtime/javascript/$QueueMicrotaskCallable$Definition : io/micronaut/context/AbstractInitializableBeanDefinitionAndReference {
public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata;
public fun <init> ()V
protected fun <init> (Ljava/lang/Class;Lio/micronaut/context/AbstractInitializableBeanDefinition$MethodOrFieldReference;)V
public fun instantiate (Lio/micronaut/context/BeanResolutionContext;Lio/micronaut/context/BeanContext;)Ljava/lang/Object;
public fun isEnabled (Lio/micronaut/context/BeanContext;)Z
public fun isEnabled (Lio/micronaut/context/BeanContext;Lio/micronaut/context/BeanResolutionContext;)Z
public fun load ()Lio/micronaut/inject/BeanDefinition;
}

public final synthetic class elide/runtime/javascript/$QueueMicrotaskCallable$Introspection : io/micronaut/inject/beans/AbstractInitializableBeanIntrospectionAndReference {
public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata;
public fun <init> ()V
public fun hasBuilder ()Z
public fun isBuildable ()Z
}

public final class elide/runtime/javascript/QueueMicrotaskCallable : elide/runtime/gvm/internals/intrinsics/js/AbstractJsIntrinsic, org/graalvm/polyglot/proxy/ProxyExecutable {
public fun <init> (Lelide/runtime/exec/GuestExecutorProvider;)V
public fun execute ([Lorg/graalvm/polyglot/Value;)Ljava/lang/Object;
public fun install (Lelide/runtime/intrinsics/GuestIntrinsic$MutableIntrinsicBindings;)V
}

public synthetic class elide/runtime/node/asserts/$NodeAssertModule$Definition : io/micronaut/context/AbstractInitializableBeanDefinitionAndReference {
public static final field $ANNOTATION_METADATA Lio/micronaut/core/annotation/AnnotationMetadata;
public fun <init> ()V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import elide.runtime.gvm.GraalVMGuest
import elide.runtime.intrinsics.GuestIntrinsic

/** Abstract base class for all intrinsic implementations. */
internal abstract class AbstractJsIntrinsic : GuestIntrinsic {
public abstract class AbstractJsIntrinsic : GuestIntrinsic {
override fun language(): GuestLanguage = GraalVMGuest.JAVASCRIPT
override fun symbolicName(): String = "native code"
@Deprecated("Use symbolicName instead", ReplaceWith("symbolicName"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright (c) 2024-2025 Elide Technologies, Inc.
*
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://opensource.org/license/mit/
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under the License.
*/
@file:OptIn(DelicateElideApi::class)

package elide.runtime.javascript

import elide.runtime.exec.GuestExecutorProvider
import elide.runtime.gvm.api.Intrinsic
import elide.runtime.gvm.internals.intrinsics.js.AbstractJsIntrinsic
import elide.runtime.gvm.js.JsError
import elide.runtime.intrinsics.GuestIntrinsic
import jakarta.inject.Inject
import jakarta.inject.Singleton
import org.graalvm.polyglot.Value
import org.graalvm.polyglot.proxy.ProxyExecutable
import elide.runtime.core.DelicateElideApi
import elide.runtime.gvm.js.JsSymbol.JsSymbols.asPublicJsSymbol
import elide.runtime.gvm.js.undefined

// Name of the `queueMicrotask` function in the global scope.
private const val QUEUE_MICROTASK_NAME = "queueMicrotask"

// Public JavaScript symbol for the `queueMicrotask` function.
private val QUEUE_MICROTASK_SYMBOL = QUEUE_MICROTASK_NAME.asPublicJsSymbol()

/**
* ## Queue Microtask Callable
*
* Mounts a callable intrinsic function at the name `queueMicrotask`, in compliance with Web JavaScript standards which
* expect this function to be available in the global scope. The `queueMicrotask` function is used to queue a chunk of
* code to execute safely on the JavaScript event loop.
*
* [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window/queueMicrotask)
*/
@Singleton
@Intrinsic(QUEUE_MICROTASK_NAME) public class QueueMicrotaskCallable @Inject constructor (
private val executorProvider: GuestExecutorProvider,
) : ProxyExecutable, AbstractJsIntrinsic() {
override fun install(bindings: GuestIntrinsic.MutableIntrinsicBindings) {
bindings[QUEUE_MICROTASK_SYMBOL] = this
}

internal operator fun invoke(callable: () -> Unit) {
executorProvider.executor().execute {
callable.invoke()
}
}

override fun execute(vararg arguments: Value?): Any? {
val first = arguments.firstOrNull() ?: throw JsError.typeError("First argument to `queueMicrotask` is required")
if (!first.canExecute()) throw JsError.typeError("First argument to `queueMicrotask` must be a function")
invoke(first::executeVoid)
return undefined()
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Elide Technologies, Inc.
* Copyright (c) 2024-2025 Elide Technologies, Inc.
*
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
Expand Down Expand Up @@ -61,7 +61,7 @@ import elide.runtime.core.evaluate
* [PolyglotContext.evaluate] and selecting [JavaScript] as source language.
*
* @param source The interpreted JavaScript source code to be executed.
* @return The result of the invocation. If [esm] is `true`, an object is returned, with exported values as members.
* @return The result of the invocation; an object is returned, with exported values as members.
*/
@DelicateElideApi public fun PolyglotContext.javascript(source: Source): PolyglotValue =
evaluate(source)
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ private const val ENABLE_SUPPRESSIONS = true
"isNaN",
"parseFloat",
"parseInt",
"queueMicrotask",
"decodeURI",
"decodeURIComponent",
"encodeURI",
Expand Down Expand Up @@ -158,7 +159,6 @@ private const val ENABLE_SUPPRESSIONS = true
"PerformanceObserverEntryList",
"performance",
"process",
"queueMicrotask",
"ReadableByteStreamController",
"ReadableStream",
"ReadableStreamBYOBReader",
Expand Down Expand Up @@ -214,7 +214,6 @@ private const val ENABLE_SUPPRESSIONS = true
"navigator", // not yet implemented
"setImmediate", // not yet implemented
"clearImmediate", // not yet implemented
"queueMicrotask", // not yet implemented
"structuredClone", // not yet implemented
"InternalError", // web-standard only, not present in non-browser runtimes
"BroadcastChannel", // not yet implemented
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
/*
* Copyright (c) 2024-2025 Elide Technologies, Inc.
*
* Licensed under the MIT license (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* https://opensource.org/license/mit/
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under the License.
*/
package elide.runtime.javascript

import org.graalvm.polyglot.Value
import org.graalvm.polyglot.proxy.ProxyExecutable
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertNotNull
import org.junit.jupiter.api.assertThrows
import kotlin.test.assertTrue
import elide.annotations.Inject
import elide.runtime.core.DelicateElideApi
import elide.runtime.exec.GuestExecution
import elide.runtime.exec.GuestExecutorProvider
import elide.runtime.gvm.internals.js.AbstractJsIntrinsicTest
import elide.runtime.gvm.js.undefined
import elide.runtime.intrinsics.js.err.TypeError
import elide.runtime.plugins.js.javascript
import elide.testing.annotations.Test
import elide.testing.annotations.TestCase

@TestCase internal class QueueMicrotaskTest : AbstractJsIntrinsicTest<QueueMicrotaskCallable>() {
@Inject lateinit var queueMicrotask: QueueMicrotaskCallable
override fun provide(): QueueMicrotaskCallable = queueMicrotask

@Test override fun testInjectable() {
assertNotNull(queueMicrotask)
}

@Test fun testExecMicrotask() {
val exec = GuestExecution.direct()
val prov = GuestExecutorProvider { exec }
val fresh = QueueMicrotaskCallable(prov)
var didExec = false
val invocable = { didExec = true }
assertDoesNotThrow { fresh.invoke(invocable) }
assertTrue(didExec)
}

@Test fun testExecMicrotaskGuest() = dual {
val exec = GuestExecution.direct()
val prov = GuestExecutorProvider { exec }
val fresh = QueueMicrotaskCallable(prov)
var didExec = false
val invocable = { didExec = true }
assertDoesNotThrow { fresh.invoke(invocable) }
assertTrue(didExec)
}.guest {
// language=JavaScript
"""
let didExec = false;
queueMicrotask(() => didExec = true);
test(didExec).isEqualTo(true);
"""
}

@OptIn(DelicateElideApi::class)
@Test fun testExecMicrotaskGuestDirect() {
val exec = GuestExecution.direct()
val prov = GuestExecutorProvider { exec }
val fresh = QueueMicrotaskCallable(prov)
val guestFn = withContext {
javascript(
// language=JavaScript
"""
const fn = (() => {
// hello
});
fn;
"""
)
}

assertNotNull(guestFn)
assertDoesNotThrow { fresh.execute(guestFn) }
}

@Test fun testExecMicrotaskRejectsNulls() {
assertThrows<TypeError> { queueMicrotask.execute(Value.asValue(null)) }
}

@Test fun testExecMicrotaskRejectsNonExecutable() {
assertThrows<TypeError> { queueMicrotask.execute(Value.asValue(5)) }
}
}

0 comments on commit dc7c24c

Please sign in to comment.