Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions src/main/java/org/htmlunit/javascript/host/Window.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.htmlunit.javascript.host;

Check warning on line 15 in src/main/java/org/htmlunit/javascript/host/Window.java

View workflow job for this annotation

GitHub Actions / PMD

[PMD] reported by reviewdog 🐶 Too many static imports may lead to messy code Raw Output: {"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"file:///home/runner/work/htmlunit/htmlunit/src/main/java/org/htmlunit/javascript/host/Window.java"},"region":{"endColumn":3,"endLine":3888,"startColumn":1,"startLine":15}}}],"message":{"text":"Too many static imports may lead to messy code"},"ruleId":"TooManyStaticImports","ruleIndex":62}

import static org.htmlunit.BrowserVersionFeatures.EVENT_SCROLL_UIEVENT;
import static org.htmlunit.BrowserVersionFeatures.JS_WINDOW_SELECTION_NULL_IF_INVISIBLE;
Expand Down Expand Up @@ -139,6 +139,7 @@
* @author Colin Alworth
* @author Atsushi Nakagawa
* @author Sven Strickroth
* @author Lai Quang Duong
*
* @see <a href="http://msdn.microsoft.com/en-us/library/ms535873.aspx">MSDN documentation</a>
*/
Expand Down Expand Up @@ -492,6 +493,24 @@
return WindowOrWorkerGlobalScopeMixin.setTimeout(context, thisObj, args, function);
}

/**
* Queues a microtask to be executed.
*
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask">
* MDN web docs</a>
* @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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -36,6 +37,7 @@
*
* @author Ronald Brill
* @author Rural Hunter
* @author Lai Quang Duong
*/
public final class WindowOrWorkerGlobalScopeMixin {

Expand Down Expand Up @@ -151,6 +153,38 @@
return setTimeoutIntervalImpl((Window) thisObj, args[0], timeout, false, params);
}

/**
* Queues a microtask to be executed.
*
* @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask">
* MDN web docs</a>
* @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();

Check warning on line 175 in src/main/java/org/htmlunit/javascript/host/WindowOrWorkerGlobalScopeMixin.java

View workflow job for this annotation

GitHub Actions / PMD

[PMD] reported by reviewdog 🐶 Ensure that resources like this Context object are closed after use Raw Output: {"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"file:///home/runner/work/htmlunit/htmlunit/src/main/java/org/htmlunit/javascript/host/WindowOrWorkerGlobalScopeMixin.java"},"region":{"endColumn":25,"endLine":175,"startColumn":23,"startLine":175}}}],"message":{"text":"Ensure that resources like this Context object are closed after use"},"ruleId":"CloseResource","ruleIndex":207}
cx.enqueueMicrotask(() -> {
try {
callback.call(cx, scope, thisObj, JavaScriptEngine.EMPTY_ARGS);
}
catch (final Exception e) {

Check warning on line 180 in src/main/java/org/htmlunit/javascript/host/WindowOrWorkerGlobalScopeMixin.java

View workflow job for this annotation

GitHub Actions / PMD

[PMD] reported by reviewdog 🐶 Avoid catching Exception in try-catch block Raw Output: {"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"file:///home/runner/work/htmlunit/htmlunit/src/main/java/org/htmlunit/javascript/host/WindowOrWorkerGlobalScopeMixin.java"},"region":{"endColumn":35,"endLine":180,"startColumn":26,"startLine":180}}}],"message":{"text":"Avoid catching Exception in try-catch block"},"ruleId":"AvoidCatchingGenericException","ruleIndex":41}

Check warning on line 180 in src/main/java/org/htmlunit/javascript/host/WindowOrWorkerGlobalScopeMixin.java

View workflow job for this annotation

GitHub Actions / PMD

[PMD] reported by reviewdog 🐶 Avoid empty catch blocks Raw Output: {"level":"warning","locations":[{"physicalLocation":{"artifactLocation":{"uri":"file:///home/runner/work/htmlunit/htmlunit/src/main/java/org/htmlunit/javascript/host/WindowOrWorkerGlobalScopeMixin.java"},"region":{"endColumn":14,"endLine":182,"startColumn":13,"startLine":180}}}],"message":{"text":"Avoid empty catch blocks"},"ruleId":"EmptyCatchBlock","ruleIndex":146}
// 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) {
Expand Down
56 changes: 56 additions & 0 deletions src/test/java/org/htmlunit/javascript/host/Window2Test.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -43,6 +44,7 @@
* @author Carsten Steul
* @author Colin Alworth
* @author Christoph Burgmer
* @author Lai Quang Duong
*/
public class Window2Test extends WebDriverTestCase {

Expand Down Expand Up @@ -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
+ "<html><head><script>\n"
+ LOG_TITLE_FUNCTION
+ " try { queueMicrotask(); } catch(e) { logEx(e); }\n"
+ "</script></head></html>";

loadPageVerifyTitle2(html);
}

/**
* @throws Exception if the test fails
*/
@Test
@Alerts("TypeError")
public void queueMicrotaskNonFunction() throws Exception {
final String html = DOCTYPE_HTML
+ "<html><head><script>\n"
+ LOG_TITLE_FUNCTION
+ " try { queueMicrotask('str'); } catch(e) { logEx(e); }\n"
+ "</script></head></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
+ "<html><head><script>\n"
+ LOG_TITLE_FUNCTION
+ " var r = [];\n"
+ " queueMicrotask(function() { r.push('2-microtask'); });\n"
+ " setTimeout(function() { r.push('5-timeout'); }, 0);\n"
+ " queueMicrotask(function() {\n"
+ " r.push('3-microtask');\n"
+ " queueMicrotask(function() { r.push('4-nested-microtask'); });\n"
+ " });\n"
+ " r.push('1-sync');\n"
+ " setTimeout(function() { log(r.join(', ')); }, 200);\n"
+ "</script></head></html>";

final WebDriver driver = loadPage2(html);
verifyTitle2(Duration.ofSeconds(1), driver, getExpectedAlerts());
}
}
Loading