Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 2 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /digital-credentials/concurrent-requests.https.html - WPT Dashboard Interop Dashboard
<!doctype html>
<meta charset="utf-8" />
<title>Digital Credential concurrent request rejection tests</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js?feature=bidi"></script>
<script src="/resources/testdriver-vendor.js"></script>
<body></body>
<script type="module">
import { makeGetOptions } from "./support/helper.js";
promise_test(async (t) => {
// Park the first request ("wait") so the second hits the concurrency guard.
await test_driver.set_virtual_wallet_behavior("wait");
t.add_cleanup(() => test_driver.set_virtual_wallet_behavior("clear"));
const abortController = new AbortController();
const firstOptions = makeGetOptions({ signal: abortController.signal });
const secondOptions = makeGetOptions();
await test_driver.bless("user activation for first request");
const firstRequest = navigator.credentials.get(firstOptions);
// Use bless's action callback so the second get() is called within
// the same microtask as activation grant. This prevents the first
// request's IPC response from completing (clearing the pending state
// in the browser process) before the second call is dispatched.
let secondRequest;
await test_driver.bless("user activation for second request", () => {
assert_true(navigator.userActivation.isActive);
secondRequest = navigator.credentials.get(secondOptions);
});
await promise_rejects_dom(
t,
"NotAllowedError",
secondRequest,
"Second concurrent request should be rejected with NotAllowedError",
);
abortController.abort();
await promise_rejects_dom(
t,
"AbortError",
firstRequest,
"First request should be aborted",
);
}, "A second get() call while another is pending should reject with NotAllowedError.");
promise_test(async (t) => {
// The concurrency rejection must be queued as a global task on the DOM
// manipulation task source (per "prepare credential requests"), not settled
// synchronously. A synchronous rejection would settle the promise within
// the get() call, scheduling its rejection reaction ahead of a microtask
// queued in the same tick; a task-queued rejection lets the microtask run
// first.
await test_driver.set_virtual_wallet_behavior("wait");
t.add_cleanup(() => test_driver.set_virtual_wallet_behavior("clear"));
const abortController = new AbortController();
await test_driver.bless("user activation for first request");
const firstRequest = navigator.credentials.get(
makeGetOptions({ signal: abortController.signal }),
);
const order = [];
let secondRequest;
await test_driver.bless("user activation for second request", () => {
secondRequest = navigator.credentials.get(makeGetOptions());
secondRequest.catch(() => order.push("rejection"));
Promise.resolve().then(() => order.push("microtask"));
});
await promise_rejects_dom(t, "NotAllowedError", secondRequest);
assert_array_equals(
order,
["microtask", "rejection"],
"The concurrent-request rejection must be queued as a task, not settled synchronously.",
);
abortController.abort();
await promise_rejects_dom(t, "AbortError", firstRequest);
}, "A concurrent get() rejection is queued on the DOM manipulation task source, not settled synchronously.");
</script>