diff --git a/src/main/java/org/htmlunit/javascript/host/Window.java b/src/main/java/org/htmlunit/javascript/host/Window.java index c8cf7f1f9c..c76bdb6380 100644 --- a/src/main/java/org/htmlunit/javascript/host/Window.java +++ b/src/main/java/org/htmlunit/javascript/host/Window.java @@ -139,6 +139,7 @@ * @author Colin Alworth * @author Atsushi Nakagawa * @author Sven Strickroth + * @author Lai Quang Duong * * @see MSDN documentation */ @@ -492,6 +493,24 @@ public static Object setTimeout(final Context context, final Scriptable scope, return WindowOrWorkerGlobalScopeMixin.setTimeout(context, thisObj, args, function); } + /** + * Queues a microtask to be executed. + * + * @see + * MDN web docs + * @param context the JavaScript context + * @param scope the scope + * @param thisObj the scriptable + * @param args the arguments passed into the method + * @param function the function + * @return undefined + */ + @JsxFunction + public static Object queueMicrotask(final Context context, final Scriptable scope, + final Scriptable thisObj, final Object[] args, final Function function) { + return WindowOrWorkerGlobalScopeMixin.queueMicrotask(thisObj, args); + } + /** * Sets a chunk of JavaScript to be invoked each time a specified number of milliseconds has elapsed. * diff --git a/src/main/java/org/htmlunit/javascript/host/WindowOrWorkerGlobalScopeMixin.java b/src/main/java/org/htmlunit/javascript/host/WindowOrWorkerGlobalScopeMixin.java index 8cb0a01442..8c121935ef 100644 --- a/src/main/java/org/htmlunit/javascript/host/WindowOrWorkerGlobalScopeMixin.java +++ b/src/main/java/org/htmlunit/javascript/host/WindowOrWorkerGlobalScopeMixin.java @@ -24,6 +24,7 @@ import org.htmlunit.corejs.javascript.Function; import org.htmlunit.corejs.javascript.FunctionObject; import org.htmlunit.corejs.javascript.Scriptable; +import org.htmlunit.corejs.javascript.ScriptableObject; import org.htmlunit.javascript.HtmlUnitScriptable; import org.htmlunit.javascript.JavaScriptEngine; import org.htmlunit.javascript.background.BackgroundJavaScriptFactory; @@ -36,6 +37,7 @@ * * @author Ronald Brill * @author Rural Hunter + * @author Lai Quang Duong */ public final class WindowOrWorkerGlobalScopeMixin { @@ -151,6 +153,38 @@ public static Object setInterval(final Context context, final Scriptable thisObj return setTimeoutIntervalImpl((Window) thisObj, args[0], timeout, false, params); } + /** + * Queues a microtask to be executed. + * + * @see + * MDN web docs + * @param thisObj the scriptable + * @param args the arguments passed into the method + * @return undefined + */ + public static Object queueMicrotask(final Scriptable thisObj, final Object[] args) { + if (args.length < 1) { + throw JavaScriptEngine.typeError("At least 1 argument required"); + } + if (!(args[0] instanceof Function)) { + throw JavaScriptEngine.typeError("Argument 1 is not callable"); + } + + final Function callback = (Function) args[0]; + final Scriptable scope = ScriptableObject.getTopLevelScope(thisObj); + final Context cx = Context.getCurrentContext(); + cx.enqueueMicrotask(() -> { + try { + callback.call(cx, scope, thisObj, JavaScriptEngine.EMPTY_ARGS); + } + catch (final Exception e) { + // uncaught exception in a microtask must not prevent remaining microtasks from running. + } + }); + + return JavaScriptEngine.UNDEFINED; + } + private static int setTimeoutIntervalImpl(final Window window, final Object code, int timeout, final boolean isTimeout, final Object[] params) { if (timeout < MIN_TIMER_DELAY) { diff --git a/src/test/java/org/htmlunit/javascript/host/Window2Test.java b/src/test/java/org/htmlunit/javascript/host/Window2Test.java index 569aab7682..c3b047031d 100644 --- a/src/test/java/org/htmlunit/javascript/host/Window2Test.java +++ b/src/test/java/org/htmlunit/javascript/host/Window2Test.java @@ -19,6 +19,7 @@ import java.io.OutputStreamWriter; import java.net.URL; import java.nio.charset.StandardCharsets; +import java.time.Duration; import org.apache.commons.io.FileUtils; import org.htmlunit.CookieManager4Test; @@ -43,6 +44,7 @@ * @author Carsten Steul * @author Colin Alworth * @author Christoph Burgmer + * @author Lai Quang Duong */ public class Window2Test extends WebDriverTestCase { @@ -3496,4 +3498,58 @@ public void overwriteProperty_top() throws Exception { final WebDriver driver = loadPage2(html); verifySessionStorage2(driver, getExpectedAlerts()); } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts("TypeError") + public void queueMicrotaskNoArgs() throws Exception { + final String html = DOCTYPE_HTML + + ""; + + loadPageVerifyTitle2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts("TypeError") + public void queueMicrotaskNonFunction() throws Exception { + final String html = DOCTYPE_HTML + + ""; + + loadPageVerifyTitle2(html); + } + + /** + * @throws Exception if the test fails + */ + @Test + @Alerts("1-sync, 2-microtask, 3-microtask, 4-nested-microtask, 5-timeout") + public void queueMicrotaskExecutionOrder() throws Exception { + final String html = DOCTYPE_HTML + + ""; + + final WebDriver driver = loadPage2(html); + verifyTitle2(Duration.ofSeconds(1), driver, getExpectedAlerts()); + } }