-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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>
- Loading branch information
Showing
7 changed files
with
196 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
packages/graalvm/src/main/kotlin/elide/runtime/javascript/QueueMicrotaskCallable.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
95 changes: 95 additions & 0 deletions
95
packages/graalvm/src/test/kotlin/elide/runtime/javascript/QueueMicrotaskTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)) } | ||
} | ||
} |