summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/credential-management
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/credential-management')
-rw-r--r--testing/web-platform/tests/credential-management/META.yml3
-rw-r--r--testing/web-platform/tests/credential-management/credentialscontainer-create-basics.https.html135
-rw-r--r--testing/web-platform/tests/credential-management/credentialscontainer-prevent-silent-access.https.html12
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-abort.https.html22
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-after-abort.https.html25
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-authz/fedcm-continue-on.https.html24
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-authz/fedcm-userinfo-after-resolve.https.html45
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-auto-reauthn-without-approved-clients.https.html32
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-auto-selected-flag.https.html29
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-basic.https.html22
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-client-metadata-not-cached.https.html41
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-context.https.html48
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html21
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html.sub.headers1
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-csp.https.html19
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-csp.https.html.sub.headers2
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-disconnect-errors.https.html69
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-disconnect-iframe.sub.https.html47
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-disconnect.sub.https.html84
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-domainhint.https.html58
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-endpoint-redirects.https.html47
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-error-basic.https.html74
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-iframe.https.html84
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-login-status/confirm-idp-login.https.html41
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-login-status/cross-origin-status.https.html87
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-login-status/logged-out.https.html47
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-loginhint.https.html43
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-manifest-not-in-list.https.html19
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-first-idp.https.html35
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-second-idp.https.html35
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-after-onload.https.html47
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-during-onload.https.html42
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-onload-and-during-dom-content-loaded.https.html42
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-abort.https.html49
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-onload.https.html38
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-before-onload.https.html37
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-during-onload.https.html36
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-after-onload.https.html29
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-before-onload.https.html37
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-during-onload.https.html30
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-no-login-url.https.html25
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-nonce-is-optional.https.html21
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-not-observed-by-service-worker.https.html53
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-pending-call-rejected.https.html29
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-pending-disconnect.https.html29
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-pending-userinfo.https.html36
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-reject-invalid-responses.https.html48
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-returning-account-auto-reauthn.https.html34
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-store.https.html19
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-token-returned-with-http-error.https.html23
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-too-many-disconnect-calls.https.html31
-rw-r--r--testing/web-platform/tests/credential-management/fedcm-userinfo.https.html70
-rw-r--r--testing/web-platform/tests/credential-management/federatedcredential-framed-get.sub.https.html76
-rw-r--r--testing/web-platform/tests/credential-management/idlharness.https.window.js37
-rw-r--r--testing/web-platform/tests/credential-management/otpcredential-get-basics.https.html77
-rw-r--r--testing/web-platform/tests/credential-management/otpcredential-iframe.https.html54
-rw-r--r--testing/web-platform/tests/credential-management/otpcredential-store.https.html17
-rw-r--r--testing/web-platform/tests/credential-management/passwordcredential-framed-get.sub.https.html76
-rw-r--r--testing/web-platform/tests/credential-management/require_securecontext.html13
-rw-r--r--testing/web-platform/tests/credential-management/support/README.md54
-rw-r--r--testing/web-platform/tests/credential-management/support/echoing-nester.html16
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm-helper.sub.js263
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm-iframe-level2.html23
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm-iframe.html42
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm-mock.js147
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm-mojojs-helper.js20
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/accounts.py24
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py25
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py.headers1
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/client_metadata_clear_count.py15
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/continue_on.py12
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/disconnect-iframe.html61
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/disconnect.py14
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/disconnect_failure.py8
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/error_with_code_and_url.py12
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/intercept_service_worker.js10
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/keys.py2
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/login.html9
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest-not-in-list.json6
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest.py19
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_id_assertion_endpoint_returns_error.json6
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_no_login_url.json5
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_accounts.json6
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_token.json7
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_token_with_http_error.json6
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_with_auto_selected_flag.json6
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_with_continue_on.json8
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_with_cross_origin_disconnect.sub.json7
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_with_disconnect_failure.json7
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_with_no_accounts.json6
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_with_single_account.json6
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_with_two_accounts.json6
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/manifest_with_variable_accounts.json6
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/no_accounts.py17
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/pending-userinfo-iframe.html22
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/request-params-check.py101
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/resolve.html8
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/select_manifest_in_root_manifest.py17
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/simple.html3
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/single_account.py24
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/token.py11
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/token_with_account_id.py12
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/token_with_auto_selected_flag.py12
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/token_with_http_error.py12
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/two_accounts.py34
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/userinfo-iframe.html37
-rw-r--r--testing/web-platform/tests/credential-management/support/fedcm/variable_accounts.py33
-rw-r--r--testing/web-platform/tests/credential-management/support/federatedcredential-get.html17
-rw-r--r--testing/web-platform/tests/credential-management/support/fencedframe-mark-signedin.html15
-rw-r--r--testing/web-platform/tests/credential-management/support/iframe-mark-signedin.html4
-rw-r--r--testing/web-platform/tests/credential-management/support/mark_signedin13
-rw-r--r--testing/web-platform/tests/credential-management/support/mark_signedin.sub.headers5
-rw-r--r--testing/web-platform/tests/credential-management/support/mark_signedout13
-rw-r--r--testing/web-platform/tests/credential-management/support/mark_signedout.sub.headers5
-rw-r--r--testing/web-platform/tests/credential-management/support/otpcredential-helper.js114
-rw-r--r--testing/web-platform/tests/credential-management/support/otpcredential-iframe.html26
-rw-r--r--testing/web-platform/tests/credential-management/support/passwordcredential-get.html17
-rw-r--r--testing/web-platform/tests/credential-management/support/set_cookie12
-rw-r--r--testing/web-platform/tests/credential-management/support/set_cookie.headers2
119 files changed, 3857 insertions, 0 deletions
diff --git a/testing/web-platform/tests/credential-management/META.yml b/testing/web-platform/tests/credential-management/META.yml
new file mode 100644
index 0000000000..7b18609213
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/META.yml
@@ -0,0 +1,3 @@
+spec: https://w3c.github.io/webappsec-credential-management/
+suggested_reviewers:
+ - mikewest
diff --git a/testing/web-platform/tests/credential-management/credentialscontainer-create-basics.https.html b/testing/web-platform/tests/credential-management/credentialscontainer-create-basics.https.html
new file mode 100644
index 0000000000..ea2326c4ae
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/credentialscontainer-create-basics.https.html
@@ -0,0 +1,135 @@
+<!DOCTYPE html>
+<title>Credential Management API: create() basics.</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(function(t) {
+ return promise_rejects_dom(t, "NotSupportedError",
+ navigator.credentials.create());
+}, "navigator.credentials.create() with no argument.");
+
+promise_test(function(t) {
+ return promise_rejects_dom(t, "NotSupportedError",
+ navigator.credentials.create({}));
+}, "navigator.credentials.create() with empty argument.");
+
+promise_test(function(t) {
+ var credential_data = {
+ id: 'id',
+ password: 'pencil',
+ };
+
+ return navigator.credentials.create({password: credential_data})
+ .then(function(credential) {
+ assert_equals(credential.id, 'id');
+ assert_equals(credential.name, '');
+ assert_equals(credential.iconURL, '');
+ assert_equals(credential.type, 'password');
+ assert_equals(credential.password, 'pencil');
+ });
+}, "navigator.credentials.create() with valid PasswordCredentialData");
+
+promise_test(function(t) {
+ var f = document.createElement('form');
+ f.innerHTML = "<input type='text' name='theId' value='musterman' autocomplete='username'>"
+ + "<input type='text' name='thePassword' value='sekrit' autocomplete='current-password'>"
+ + "<input type='text' name='theIcon' value='https://example.com/photo' autocomplete='photo'>"
+ + "<input type='text' name='theExtraField' value='extra'>"
+ + "<input type='text' name='theName' value='friendly name' autocomplete='name'>";
+
+ return navigator.credentials.create({password: f})
+ .then(function(credential) {
+ assert_equals(credential.id, 'musterman');
+ assert_equals(credential.name, 'friendly name');
+ assert_equals(credential.iconURL, 'https://example.com/photo');
+ assert_equals(credential.type, 'password');
+ assert_equals(credential.password, 'sekrit');
+ });
+}, "navigator.credentials.create() with valid HTMLFormElement");
+
+promise_test(function(t) {
+ return promise_rejects_js(t, TypeError,
+ navigator.credentials.create({password: "bogus password data"}));
+}, "navigator.credentials.create() with bogus password data");
+
+promise_test(function(t) {
+ var federated_data = {
+ id: 'id',
+ provider: 'https://example.com/',
+ };
+
+ return navigator.credentials.create({federated: federated_data})
+ .then(function(credential) {
+ assert_equals(credential.id, 'id');
+ assert_equals(credential.name, '');
+ assert_equals(credential.iconURL, '');
+ assert_equals(credential.type, 'federated');
+ });
+}, "navigator.credentials.create() with valid FederatedCredentialData");
+
+promise_test(function(t) {
+ return promise_rejects_js(t, TypeError,
+ navigator.credentials.create({federated: "bogus federated data"}));
+}, "navigator.credentials.create() with bogus federated data");
+
+promise_test(function(t) {
+ return promise_rejects_js(t, TypeError,
+ navigator.credentials.create({publicKey: "bogus publicKey data"}));
+}, "navigator.credentials.create() with bogus publicKey data");
+
+promise_test(function(t) {
+ var credential_data = {
+ id: 'id',
+ password: 'pencil',
+ };
+
+ var federated_data = {
+ id: 'id',
+ provider: 'https://example.com/',
+ };
+
+ return promise_rejects_dom(t, "NotSupportedError",
+ navigator.credentials.create({
+ password: credential_data,
+ federated: federated_data,
+ }));
+}, "navigator.credentials.create() with both PasswordCredentialData and FederatedCredentialData");
+
+promise_test(function(t) {
+ return promise_rejects_js(t, TypeError,
+ navigator.credentials.create({
+ password: "bogus password data",
+ federated: "bogus federated data",
+ }));
+}, "navigator.credentials.create() with bogus password and federated data");
+
+promise_test(function(t) {
+ return promise_rejects_js(t, TypeError,
+ navigator.credentials.create({
+ federated: "bogus federated data",
+ publicKey: "bogus publicKey data",
+ }));
+}, "navigator.credentials.create() with bogus federated and publicKey data");
+
+promise_test(function(t) {
+ return promise_rejects_js(t, TypeError,
+ navigator.credentials.create({
+ password: "bogus password data",
+ publicKey: "bogus publicKey data",
+ }));
+}, "navigator.credentials.create() with bogus password and publicKey data");
+
+promise_test(function(t) {
+ return promise_rejects_js(t, TypeError,
+ navigator.credentials.create({
+ password: "bogus password data",
+ federated: "bogus federated data",
+ publicKey: "bogus publicKey data",
+ }));
+}, "navigator.credentials.create() with bogus password, federated, and publicKey data");
+
+promise_test(function(t) {
+ return promise_rejects_dom(t, "NotSupportedError",
+ navigator.credentials.create({bogus_key: "bogus data"}));
+}, "navigator.credentials.create() with bogus data");
+</script>
diff --git a/testing/web-platform/tests/credential-management/credentialscontainer-prevent-silent-access.https.html b/testing/web-platform/tests/credential-management/credentialscontainer-prevent-silent-access.https.html
new file mode 100644
index 0000000000..5a83e556a7
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/credentialscontainer-prevent-silent-access.https.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<title>Credential Management API: preventSilentAccess().</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+promise_test(function() {
+ return navigator.credentials.preventSilentAccess()
+ .then((result) => {
+ assert_equals(result, undefined);
+ });
+}, "navigator.credentials.preventSilentAccess() resolves with undefined.");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-abort.https.html b/testing/web-platform/tests/credential-management/fedcm-abort.https.html
new file mode 100644
index 0000000000..0f03bff832
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-abort.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ fedcm_test,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ let controller = new AbortController();
+ let test_options = request_options_with_mediation_required();
+ test_options.signal = controller.signal;
+ const cred = fedcm_get_and_select_first_account(t, test_options);
+ controller.abort();
+ return promise_rejects_dom(t, 'AbortError', cred);
+}, "Test the abort signal");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-after-abort.https.html b/testing/web-platform/tests/credential-management/fedcm-after-abort.https.html
new file mode 100644
index 0000000000..3c2f981e82
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-after-abort.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ fedcm_test,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ let controller = new AbortController();
+ let test_options = request_options_with_mediation_required();
+ test_options.signal = controller.signal;
+ const first_cred = fedcm_get_and_select_first_account(t, test_options);
+ controller.abort();
+ await promise_rejects_dom(t, 'AbortError', first_cred);
+
+ const second_cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+ assert_equals(second_cred.token, "token");
+}, "Get after abort should work");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-authz/fedcm-continue-on.https.html b/testing/web-platform/tests/credential-management/fedcm-authz/fedcm-continue-on.https.html
new file mode 100644
index 0000000000..3ce1f51e37
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-authz/fedcm-continue-on.https.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {fedcm_test,
+ request_options_with_mediation_required,
+ select_manifest,
+ fedcm_get_and_select_first_account} from '../support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ const options = request_options_with_mediation_required('manifest_with_continue_on.json');
+ await select_manifest(t, options);
+ const cred = await fedcm_get_and_select_first_account(t, options);
+ assert_equals(cred.token, "resolved token");
+}, "continue_on and IdentityProvider.resolve work correctly.");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-authz/fedcm-userinfo-after-resolve.https.html b/testing/web-platform/tests/credential-management/fedcm-authz/fedcm-userinfo-after-resolve.https.html
new file mode 100644
index 0000000000..ef53ed4ffc
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-authz/fedcm-userinfo-after-resolve.https.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API authz getUserInfo() tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {alt_manifest_origin,
+ alt_request_options_with_mediation_required,
+ select_manifest,
+ fedcm_test,
+ fedcm_get_and_select_first_account} from '../support/fedcm-helper.sub.js';
+
+async function createIframeWithPermissionPolicyAndWaitForMessage(test, iframeUrl) {
+ const messageWatcher = new EventWatcher(test, window, "message");
+ let iframe = document.createElement("iframe");
+ iframe.src = iframeUrl;
+ iframe.allow = "identity-credentials-get";
+ document.body.appendChild(iframe);
+ const message = await messageWatcher.wait_for("message");
+ return message.data;
+}
+
+fedcm_test(async t => {
+ const options = alt_request_options_with_mediation_required('manifest_with_continue_on.json');
+ await select_manifest(t, options);
+ const cred = await fedcm_get_and_select_first_account(t, options);
+ assert_equals(cred.token, "resolved token");
+
+ const iframe_in_idp_scope = `${alt_manifest_origin}/\
+credential-management/support/fedcm/userinfo-iframe.html`;
+ const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope);
+ assert_equals(message.result, "Pass");
+ assert_equals(message.numAccounts, 1);
+ assert_equals(message.firstAccountEmail, "john_doe@idp.example");
+ assert_equals(message.firstAccountName, "John Doe");
+ assert_equals(message.firstAccountGivenName, "John");
+ assert_equals(message.firstAccountPicture, "https://idp.example/profile/123");
+}, 'Test getUserInfo() after resolve() to verify that resolve stores the RP/IDP connection');
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-auto-reauthn-without-approved-clients.https.html b/testing/web-platform/tests/credential-management/fedcm-auto-reauthn-without-approved-clients.https.html
new file mode 100644
index 0000000000..fb93cb632d
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-auto-reauthn-without-approved-clients.https.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API auto reauthentication without approved client.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_optional,
+ fedcm_test,
+ mark_signed_in,
+ select_manifest,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ await mark_signed_in();
+ let test_options = request_options_with_mediation_optional(
+ "manifest_with_single_account.json");
+ test_options.identity.providers[0].clientId = 'nomatch';
+ await select_manifest(t, test_options);
+
+ // Signs in john_doe so that they will be a returning user
+ let cred = await fedcm_get_and_select_first_account(t, test_options);
+ assert_equals(cred.token, "account_id=john_doe");
+
+ test_options.mediation = 'silent';
+ cred = navigator.credentials.get(test_options);
+ return promise_rejects_dom(t, 'NetworkError', cred);
+}, "Test that if the clientId is missing from approved_clients then auto\
+reauthentication cannot occur.");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-auto-selected-flag.https.html b/testing/web-platform/tests/credential-management/fedcm-auto-selected-flag.https.html
new file mode 100644
index 0000000000..d06aba73bc
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-auto-selected-flag.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API auto selected flag tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_optional,
+ fedcm_test,
+ select_manifest,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ let test_options = request_options_with_mediation_optional("manifest_with_auto_selected_flag.json");
+ await select_manifest(t, test_options);
+
+ let cred = await fedcm_get_and_select_first_account(t, test_options);
+ assert_equals(cred.token, "is_auto_selected=false");
+
+ test_options = request_options_with_mediation_optional("manifest_with_auto_selected_flag.json");
+ await select_manifest(t, test_options);
+
+ cred = await navigator.credentials.get(test_options);
+ assert_equals(cred.token, "is_auto_selected=true");
+ assert_equals(cred.isAutoSelected, true);
+}, "Test that the is_auto_selected bit is properly sent.");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-basic.https.html b/testing/web-platform/tests/credential-management/fedcm-basic.https.html
new file mode 100644
index 0000000000..3d20f4cfb7
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-basic.https.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {fedcm_test,
+ request_options_with_mediation_required,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+ assert_equals(cred.token, "token");
+ assert_equals(cred.isAutoSelected, false);
+}, "Successfully obtaining token should resolve the promise.");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-client-metadata-not-cached.https.html b/testing/web-platform/tests/credential-management/fedcm-client-metadata-not-cached.https.html
new file mode 100644
index 0000000000..b802369256
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-client-metadata-not-cached.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ fedcm_test,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ // Reset the client_metadata fetch count.
+ const clear_metadata_count_path = `support/fedcm/client_metadata_clear_count.py`;
+ await fetch(clear_metadata_count_path);
+
+ // FedCM flow causes the counter of client metadata to increase by 1.
+ const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+ assert_equals(cred.token, "token");
+
+ await new Promise(resolve => {
+ // Fetch the client metadata from a popup window.
+ let popup_window = window.open('support/fedcm/client_metadata.py?skip_checks=1');
+ const popup_window_load_handler = (event) => {
+ popup_window.removeEventListener('load', popup_window_load_handler);
+ popup_window.close();
+ resolve();
+ };
+ popup_window.addEventListener('load', popup_window_load_handler);
+ });
+
+ const client_metadata_counter = await fetch(clear_metadata_count_path);
+ const client_metadata_counter_text = await client_metadata_counter.text();
+ // Check that the client metadata response is not cached. If the client metadata response was
+ // cached, when the user visits the IDP as a first party, the IDP would be able to determine the
+ // last RP the user visited regardless of whether the user granted consent via the FedCM prompt.
+ assert_equals(client_metadata_counter_text, "2");
+}, 'Test client_metadata request is not cached');
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-context.https.html b/testing/web-platform/tests/credential-management/fedcm-context.https.html
new file mode 100644
index 0000000000..bc1f96eafa
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-context.https.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API context tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ request_options_with_context,
+ fedcm_get_title_promise,
+ fedcm_test} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ let p = navigator.credentials.get(request_options_with_mediation_required());
+ const title = await fedcm_get_title_promise(t);
+ assert_true(title.toLowerCase().includes('sign in'));
+ window.test_driver.select_fedcm_account(0);
+ return p;
+}, "FedCM call defaults to 'signin' context.");
+
+fedcm_test(async t => {
+ let p = navigator.credentials.get(request_options_with_context("manifest.py", "signup"));
+ const title = await fedcm_get_title_promise(t);
+ assert_true(title.toLowerCase().includes('sign up'));
+ window.test_driver.select_fedcm_account(0);
+ return p;
+}, "FedCM with 'signup' context.");
+
+fedcm_test(async t => {
+ let p = navigator.credentials.get(request_options_with_context("manifest.py", "use"));
+ const title = await fedcm_get_title_promise(t);
+ assert_true(title.toLowerCase().includes('use'));
+ window.test_driver.select_fedcm_account(0);
+ return p;
+}, "FedCM with 'use' context.");
+
+fedcm_test(async t => {
+ let p = navigator.credentials.get(request_options_with_context("manifest.py", "continue"));
+ const title = await fedcm_get_title_promise(t);
+ assert_true(title.toLowerCase().includes('continue'));
+ window.test_driver.select_fedcm_account(0);
+ return p;
+}, "FedCM with 'continue' context.");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html b/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html
new file mode 100644
index 0000000000..1e3b4c71a8
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API Cross-Origin-Embedder-Policy tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ fedcm_test,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+ assert_equals(cred.token, 'token');
+}, 'Test that COEP policy do not apply to FedCM requests');
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html.sub.headers b/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html.sub.headers
new file mode 100644
index 0000000000..32523a6978
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-cross-origin-policy.https.html.sub.headers
@@ -0,0 +1 @@
+Cross-Origin-Embedder-Policy: credentialless
diff --git a/testing/web-platform/tests/credential-management/fedcm-csp.https.html b/testing/web-platform/tests/credential-management/fedcm-csp.https.html
new file mode 100644
index 0000000000..5925741438
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-csp.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<body>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ fedcm_test,
+ set_fedcm_cookie} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ const cred = navigator.credentials.get(request_options_with_mediation_required());
+ return promise_rejects_dom(t, "NetworkError", cred);
+}, "Provider configURL should honor Content-Security-Policy.");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-csp.https.html.sub.headers b/testing/web-platform/tests/credential-management/fedcm-csp.https.html.sub.headers
new file mode 100644
index 0000000000..c1e6fd6c4c
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-csp.https.html.sub.headers
@@ -0,0 +1,2 @@
+Cross-Origin-Embedder-Policy: credentialless
+Content-Security-Policy: default-src 'none'; script-src 'self' 'unsafe-inline'; img-src 'self'
diff --git a/testing/web-platform/tests/credential-management/fedcm-disconnect-errors.https.html b/testing/web-platform/tests/credential-management/fedcm-disconnect-errors.https.html
new file mode 100644
index 0000000000..dbf42c4083
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-disconnect-errors.https.html
@@ -0,0 +1,69 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API disconnect() basic errors.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {fedcm_test,
+ mark_signed_in,
+ set_fedcm_cookie,
+ fedcm_get_and_select_first_account,
+ manifest_origin,
+ request_options_with_mediation_required,
+ select_manifest,
+ disconnect_options} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ await mark_signed_in();
+ await set_fedcm_cookie();
+ // Get at least one connected account that can be disconnected.
+ const cred = await fedcm_get_and_select_first_account(t,
+ request_options_with_mediation_required());
+ const manifest = `${manifest_origin}/\
+credential-management/support/fedcm/manifest.py`;
+ await promise_rejects_js(t, TypeError, IdentityCredential.disconnect({
+ configURL: manifest,
+ clientId: '1'
+ }));
+ await promise_rejects_js(t, TypeError, IdentityCredential.disconnect({
+ configURL: manifest,
+ accountHint: 'hint'
+ }));
+ return promise_rejects_js(t, TypeError, IdentityCredential.disconnect({
+ clientId: '1',
+ accountHint: 'hint'
+ }));
+}, "disconnect requires 3 parameters: configURL, clientId, and accountHint");
+
+fedcm_test(async t => {
+ await mark_signed_in();
+ await set_fedcm_cookie();
+ // Get at least one connected account that can be disconnected.
+ const cred = await fedcm_get_and_select_first_account(t,
+ request_options_with_mediation_required());
+
+ const manifest = `manifest_with_cross_origin_disconnect.sub.json`;
+ await select_manifest(t, request_options_with_mediation_required(manifest));
+ return promise_rejects_dom(t, 'NetworkError',
+ IdentityCredential.disconnect(disconnect_options('1234', manifest)));
+}, "disconnect fails if the disconnect endpoint is cross-origin with respect\
+to the config file");
+
+fedcm_test(async t => {
+ await mark_signed_in();
+ await set_fedcm_cookie();
+ // Get at least one connected account that can be disconnected.
+ const cred = await fedcm_get_and_select_first_account(t,
+ request_options_with_mediation_required());
+
+ const manifest = `manifest_with_disconnect_failure.json`;
+ await select_manifest(t, request_options_with_mediation_required(manifest));
+ return promise_rejects_dom(t, 'NetworkError',
+ IdentityCredential.disconnect(disconnect_options('1234', manifest)));
+}, "disconnect fails if the server sends failure");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-disconnect-iframe.sub.https.html b/testing/web-platform/tests/credential-management/fedcm-disconnect-iframe.sub.https.html
new file mode 100644
index 0000000000..3d31be60b1
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-disconnect-iframe.sub.https.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API disconnect() tests in iframes.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {alt_manifest_origin,
+ alt_request_options_with_mediation_required,
+ fedcm_test,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+async function createIframeAndWaitForMessage(test, iframeUrl, allow = false) {
+ const messageWatcher = new EventWatcher(test, window, "message");
+ let iframe = document.createElement("iframe");
+ iframe.src = iframeUrl;
+ if (allow) {
+ iframe.allow = "identity-credentials-get";
+ }
+ document.body.appendChild(iframe);
+ const message = await messageWatcher.wait_for("message");
+ return message.data;
+}
+
+fedcm_test(async t => {
+ const message = await createIframeAndWaitForMessage(t, 'support/fedcm/disconnect-iframe.html');
+ assert_equals(message.result, "Pass");
+}, 'Same-origin iframe does not need explicit identity-credentials-get');
+
+fedcm_test(async t => {
+ const message = await createIframeAndWaitForMessage(t,
+ 'https://{{hosts[alt][]}}:{{ports[https][0]}}/credential-management/support/fedcm/disconnect-iframe.html?skip_get');
+ assert_equals(message.result, "Failed disconnect");
+ assert_equals(message.errorType, "NotAllowedError");
+}, 'Cross-origin iframe fails disconnect() without explicit identity-credentials-get');
+
+fedcm_test(async t => {
+ const message = await createIframeAndWaitForMessage(t,
+ 'https://{{hosts[alt][]}}:{{ports[https][0]}}/credential-management/support/fedcm/disconnect-iframe.html',
+ /*allow=*/true);
+ assert_equals(message.result, "Pass");
+}, 'Cross-origin iframe can disconnect with explicit identity-credentials-get');
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-disconnect.sub.https.html b/testing/web-platform/tests/credential-management/fedcm-disconnect.sub.https.html
new file mode 100644
index 0000000000..300144fa72
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-disconnect.sub.https.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API disconnect() tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {fedcm_test,
+ mark_signed_in,
+ set_fedcm_cookie,
+ disconnect_options,
+ fedcm_get_and_select_first_account,
+ request_options_with_mediation_required,
+ alt_manifest_origin,
+ alt_request_options_with_mediation_required,
+ alt_disconnect_options,
+ set_alt_fedcm_cookie} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ await mark_signed_in();
+ await set_fedcm_cookie();
+ // Get at least one connected account that can be disconnected.
+ const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+ // The IDP implementation will accept any account hint, so this is really testing that the user
+ // agent eventually stops sending the requests to the IDP.
+ // This test clears the connection just created above, but it also clears any previously existing
+ // connected accounts, which helps the logic of the other tests.
+ return new Promise(async resolve => {
+ while (true) {
+ try {
+ await IdentityCredential.disconnect(disconnect_options("1234"));
+ } catch(e) {
+ resolve();
+ break;
+ }
+ }
+ });
+}, "Repeatedly calling disconnect should eventually fail");
+
+fedcm_test(async t => {
+ const disconnect = IdentityCredential.disconnect(
+ disconnect_options("nonExistent"));
+ return promise_rejects_dom(t, 'NetworkError', disconnect);
+}, 'Test that disconnect fails when there is no account to disconnect');
+
+fedcm_test(async t => {
+ const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+
+ return IdentityCredential.disconnect(disconnect_options("1234"));
+}, 'Test that disconnect succeeds when there is an account to disconnect');
+
+fedcm_test(async t => {
+ const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+
+ await IdentityCredential.disconnect(disconnect_options("1234"));
+
+ const disconnect = IdentityCredential.disconnect(disconnect_options("1234"));
+ return promise_rejects_dom(t, 'NetworkError', disconnect);
+}, 'Test that disconnecting the same account twice results in failure.');
+
+fedcm_test(async t => {
+ const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+ // A connected account is guaranteed by the above, and IDP accepts any account hint, so this tests
+ // that the user agent allows the request to go through to the IDP.
+ return IdentityCredential.disconnect(disconnect_options("noMatch"));
+}, 'Disconnect passing an incorrect ID can still succeed');
+
+fedcm_test(async t => {
+ await set_alt_fedcm_cookie();
+ await mark_signed_in(alt_manifest_origin);
+ await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required());
+ await fedcm_get_and_select_first_account(t,
+ request_options_with_mediation_required());
+
+ // Await the first disconnect since they cannot happen in parallel. Both
+ // should succeed.
+ await IdentityCredential.disconnect(disconnect_options("1"));
+ return IdentityCredential.disconnect(alt_disconnect_options("2"));
+}, 'Disconnect is bound to each IDP');
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-domainhint.https.html b/testing/web-platform/tests/credential-management/fedcm-domainhint.https.html
new file mode 100644
index 0000000000..3e07491d48
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-domainhint.https.html
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API domain hint tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<body>
+
+<script type="module">
+import {
+ fedcm_test,
+ request_options_with_domain_hint,
+ select_manifest,
+ mark_signed_in,
+ fedcm_get_dialog_type_promise,
+ fedcm_get_and_select_first_account
+} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ await mark_signed_in();
+
+ let options = request_options_with_domain_hint('manifest.py',
+ 'nomatch');
+ const cred = fedcm_get_and_select_first_account(t, options);
+ // We expect a mismatch dialog.
+ const type = await fedcm_get_dialog_type_promise(t);
+ assert_equals(type, 'ConfirmIdpLogin');
+ window.test_driver.cancel_fedcm_dialog();
+
+ return promise_rejects_dom(t, "NetworkError", cred);
+}, "No domain hint matches an account.");
+
+fedcm_test(async t => {
+ const options = request_options_with_domain_hint('manifest.py',
+ 'idp.example');
+ const cred = await fedcm_get_and_select_first_account(t, options);
+ assert_equals(cred.token, 'token');
+}, "Domain hint matches an account.");
+
+fedcm_test(async t => {
+ let options = request_options_with_domain_hint(
+ 'manifest_with_two_accounts.json', 'example');
+ await select_manifest(t, options);
+
+ const cred = await fedcm_get_and_select_first_account(t, options);
+ assert_equals(cred.token, 'account_id=john_doe');
+}, "Domain hint matches an account from two accounts.");
+
+fedcm_test(async t => {
+ let options = request_options_with_domain_hint(
+ 'manifest_with_two_accounts.json', 'any');
+ await select_manifest(t, options);
+
+ const cred = await fedcm_get_and_select_first_account(t, options);
+ assert_equals(cred.token, 'account_id=john_doe');
+}, "Domain hint 'any' matches an account with any domain hint.");
+ </script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-endpoint-redirects.https.html b/testing/web-platform/tests/credential-management/fedcm-endpoint-redirects.https.html
new file mode 100644
index 0000000000..cff5036f39
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-endpoint-redirects.https.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ fedcm_test,
+ select_manifest,
+ mark_signed_in,
+ fedcm_get_dialog_type_promise,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ await mark_signed_in();
+
+ let test_options = request_options_with_mediation_required("manifest_redirect_accounts.json");
+ await select_manifest(t, test_options);
+
+ const cred = fedcm_get_and_select_first_account(t, test_options);
+ // We expect a mismatch dialog.
+ const type = await fedcm_get_dialog_type_promise(t);
+ assert_equals(type, 'ConfirmIdpLogin');
+ window.test_driver.cancel_fedcm_dialog();
+ return promise_rejects_dom(t, 'NetworkError', cred);
+}, 'Test that promise is rejected if accounts endpoint redirects');
+
+fedcm_test(async t => {
+ await mark_signed_in();
+
+ let test_options = request_options_with_mediation_required("manifest_redirect_token.json");
+ await select_manifest(t, test_options);
+
+ try {
+ const cred = await fedcm_get_and_select_first_account(t, test_options);
+ assert_unreached("An IdentityCredentialError exception should be thrown.");
+ } catch (e) {
+ assert_true(e instanceof DOMException);
+ assert_equals(e.name, "IdentityCredentialError");
+ // 308 should not produce a valid error code
+ assert_equals(e.code, "");
+ }
+}, 'Test that token endpoint does not follow redirects');
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-error-basic.https.html b/testing/web-platform/tests/credential-management/fedcm-error-basic.https.html
new file mode 100644
index 0000000000..49d6ea50df
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-error-basic.https.html
@@ -0,0 +1,74 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API Error API tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ fedcm_test,
+ manifest_origin,
+ select_manifest,
+ fedcm_get_and_select_first_account,
+ fedcm_error_dialog_dismiss,
+ fedcm_error_dialog_click_button} from './support/fedcm-helper.sub.js';
+
+const url_prefix = manifest_origin + '/credential-management/support/fedcm/';
+
+fedcm_test(async t => {
+ let test_options =
+ request_options_with_mediation_required("manifest_id_assertion_endpoint_returns_error.json");
+ await select_manifest(t, test_options);
+
+ try {
+ const cred = await fedcm_get_and_select_first_account(t, test_options);
+ fedcm_error_dialog_dismiss(t);
+ await cred;
+ assert_unreached("An IdentityCredentialError exception should be thrown.");
+ } catch (e) {
+ assert_true(e instanceof DOMException);
+ assert_equals(e.name, "IdentityCredentialError");
+ assert_equals(e.code, "unauthorized_client");
+ assert_equals(e.url, url_prefix + "error.html");
+ }
+}, 'Test that the promise is rejected with proper error details when dialog is dismissed');
+
+fedcm_test(async t => {
+ let test_options =
+ request_options_with_mediation_required("manifest_id_assertion_endpoint_returns_error.json");
+ await select_manifest(t, test_options);
+
+ try {
+ const cred = await fedcm_get_and_select_first_account(t, test_options);
+ fedcm_error_dialog_click_button(t, "ErrorGotIt");
+ await cred;
+ assert_unreached("An IdentityCredentialError exception should be thrown.");
+ } catch (e) {
+ assert_true(e instanceof DOMException);
+ assert_equals(e.name, "IdentityCredentialError");
+ assert_equals(e.code, "unauthorized_client");
+ assert_equals(e.url, url_prefix + "error.html");
+ }
+}, 'Test that the promise is rejected with proper error details when got it is clicked');
+
+fedcm_test(async t => {
+ let test_options =
+ request_options_with_mediation_required("manifest_id_assertion_endpoint_returns_error.json");
+ await select_manifest(t, test_options);
+
+ try {
+ const cred = await fedcm_get_and_select_first_account(t, test_options);
+ fedcm_error_dialog_click_button(t, "ErrorMoreDetails");
+ await cred;
+ assert_unreached("An IdentityCredentialError exception should be thrown.");
+ } catch (e) {
+ assert_true(e instanceof DOMException);
+ assert_equals(e.name, "IdentityCredentialError");
+ assert_equals(e.code, "unauthorized_client");
+ assert_equals(e.url, url_prefix + "error.html");
+ }
+}, 'Test that the promise is rejected with proper error details when more details is clicked');
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-iframe.https.html b/testing/web-platform/tests/credential-management/fedcm-iframe.https.html
new file mode 100644
index 0000000000..dc0c17dea6
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-iframe.https.html
@@ -0,0 +1,84 @@
+<!doctype html>
+<link rel="help" href="https://wicg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<div id=log>
+<script type="module">
+'use strict';
+
+import {fedcm_test, set_fedcm_cookie} from './support/fedcm-helper.sub.js';
+
+const host = get_host_info();
+// This regex removes the filename from the path so that we just get
+// the directory.
+const basePath = window.location.pathname.replace(/\/[^\/]*$/, '/');
+const remoteBaseURL = host.HTTPS_REMOTE_ORIGIN + basePath;
+const localhostBaseURL = "http://localhost:" + host.HTTP_PORT + basePath;
+
+async function createIframeAndWaitForMessage(test, iframeUrl, setPermissionPolicy, style = "") {
+ const messageWatcher = new EventWatcher(test, window, "message");
+ var iframe = document.createElement("iframe");
+ iframe.src = iframeUrl;
+ if (setPermissionPolicy) {
+ iframe.allow = "identity-credentials-get";
+ }
+ if (style !== "") {
+ iframe.style = style;
+ }
+ document.body.appendChild(iframe);
+ const message = await messageWatcher.wait_for("message");
+ return message.data;
+}
+
+fedcm_test(async t => {
+ const message = await createIframeAndWaitForMessage(
+ t, remoteBaseURL + "support/fedcm-iframe.html",
+ /*setPermissionPolicy=*/false);
+ assert_equals(message.result, "Fail");
+ assert_equals(message.errorType, "NotAllowedError");
+}, "FedCM disabled in cross origin iframe without permissions policy");
+
+fedcm_test(async t => {
+ const message = await createIframeAndWaitForMessage(
+ t, remoteBaseURL + "support/fedcm-iframe-level2.html",
+ /*setPermissionPolicy=*/true);
+ assert_equals(message.result, "Pass");
+ assert_equals(message.token, "token");
+}, "FedCM enabled in 2 level deep nested iframe. FedCM should be enabled regardless of iframe nesting depth");
+
+fedcm_test(async t => {
+ const message = await createIframeAndWaitForMessage(
+ t, remoteBaseURL + "support/fedcm-iframe.html",
+ /*setPermissionPolicy=*/true, /*style=*/"display:none;");
+ assert_equals(message.result, "Pass");
+ assert_equals(message.token, "token");
+}, "FedCM enabled in invisible iframe. FedCM should be enabled as long as the top frame is visible");
+
+fedcm_test(async t => {
+ const message = await createIframeAndWaitForMessage(
+ t, remoteBaseURL + "support/fedcm-iframe-level2.html",
+ /*setPermissionPolicy=*/false);
+ assert_equals(message.result, "Fail");
+ assert_equals(message.errorType, "NotAllowedError");
+}, "FedCM disabled in 2 level deep nested iframe where middle iframe does not have permission policy");
+
+fedcm_test(async t => {
+ const message = await createIframeAndWaitForMessage(
+ t, remoteBaseURL + "support/fedcm-iframe-level2.html?permission=0",
+ /*setPermissionPolicy=*/true);
+ assert_equals(message.result, "Fail");
+ assert_equals(message.errorType, "NotAllowedError");
+}, "FedCM disabled in 2 level deep nested iframe where innermost iframe does not have permission policy");
+
+fedcm_test(async t => {
+ // This is only an iframe because there's no other way to have this URL
+ // loaded from localhost.
+ const message = await createIframeAndWaitForMessage(
+ t, localhostBaseURL + "support/fedcm-iframe.html",
+ /*setPermissionPolicy=*/true);
+ assert_equals(message.result, "Pass");
+ assert_equals(message.token, "token");
+}, "FedCM should work in non-HTTPS URLs on localhost");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-login-status/confirm-idp-login.https.html b/testing/web-platform/tests/credential-management/fedcm-login-status/confirm-idp-login.https.html
new file mode 100644
index 0000000000..0f8df72b61
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-login-status/confirm-idp-login.https.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FedCM IDP log-in status API tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ fedcm_test,
+ fedcm_get_dialog_type_promise,
+ select_manifest,
+ mark_signed_in} from '../support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ await mark_signed_in();
+
+ let test_options = request_options_with_mediation_required("manifest_with_variable_accounts.json");
+ await select_manifest(t, test_options);
+
+ let cred_promise = navigator.credentials.get(test_options);
+ let type = await fedcm_get_dialog_type_promise(t);
+ assert_equals(type, "ConfirmIdpLogin");
+
+ // Manifest selection only persists for a single fetch, so we need to set it
+ // again because Chrome's implementation re-fetches the manifest as well, not
+ // just the accounts endpoint.
+ // (This is not technically spec-compliant)
+ await select_manifest(t, test_options);
+ await window.test_driver.click_fedcm_dialog_button("ConfirmIdpLoginContinue");
+
+ // Now wait for the account chooser.
+ type = await fedcm_get_dialog_type_promise(t);
+ assert_equals(type, "AccountChooser");
+ window.test_driver.select_fedcm_account(0);
+
+ let cred = await cred_promise;
+ assert_equals(cred.token, "account_id=1234");
+}, 'Tests the IDP login dialog and subsequent account chooser.');
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-login-status/cross-origin-status.https.html b/testing/web-platform/tests/credential-management/fedcm-login-status/cross-origin-status.https.html
new file mode 100644
index 0000000000..f32e18d40e
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-login-status/cross-origin-status.https.html
@@ -0,0 +1,87 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FedCM IDP login status API tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {fedcm_test,
+ alt_manifest_origin,
+ same_site_manifest_origin,
+ set_fedcm_cookie,
+ select_manifest,
+ request_options_with_mediation_required,
+ alt_request_options_with_mediation_required,
+ fedcm_get_and_select_first_account,
+ open_and_wait_for_popup,
+ mark_signed_out} from '../support/fedcm-helper.sub.js';
+
+const path = '/credential-management/support/'
+const url_prefix = alt_manifest_origin + path;
+const same_site_url_prefix = same_site_manifest_origin + path;
+
+fedcm_test(async t => {
+ await set_fedcm_cookie(same_site_manifest_origin);
+ await mark_signed_out(same_site_manifest_origin);
+ // The header should be processed successfully because it is same-site.
+ const fetch_result = await fetch(same_site_url_prefix + "mark_signedin");
+ assert_true(fetch_result.ok);
+
+ const config = request_options_with_mediation_required(undefined, same_site_manifest_origin);
+ await select_manifest(t, config);
+ const cred = await fedcm_get_and_select_first_account(t, config);
+ assert_equals(cred.token, "token");
+}, 'Cross-origin same-site status header should work from fetch()');
+
+fedcm_test(async t => {
+ await mark_signed_out(alt_manifest_origin);
+ // The header should be ignored because it's a cross-site fetch.
+ const fetch_result = await fetch(url_prefix + "mark_signedin");
+ assert_true(fetch_result.ok);
+
+ const config = alt_request_options_with_mediation_required();
+ const result = navigator.credentials.get(config);
+ return promise_rejects_dom(t, 'NetworkError', result);
+}, 'Cross-origin status header should be ignored from fetch()');
+
+fedcm_test(async t => {
+ await mark_signed_out(alt_manifest_origin);
+ // The header should be ignored because it's a cross-site iframe.
+ let iframe = document.createElement("iframe");
+ let iframe_load = new Promise(resolve => {iframe.onload = resolve;});
+ iframe.src = url_prefix + "mark_signedin";
+ document.body.appendChild(iframe);
+ await iframe_load;
+
+ const config = alt_request_options_with_mediation_required();
+ const result = navigator.credentials.get(config);
+ return promise_rejects_dom(t, 'NetworkError', result);
+}, 'Status header should be ignored from cross-site iframe');
+
+fedcm_test(async t => {
+ await mark_signed_out(alt_manifest_origin);
+ // The header in the subresource should be ignored because the iframe is cross-site.
+ let iframe = document.createElement("iframe");
+ let iframe_load = new Promise(resolve => {iframe.onload = resolve;});
+ iframe.src = url_prefix + "iframe-mark-signedin.html";
+ document.body.appendChild(iframe);
+ await iframe_load;
+
+ const config = alt_request_options_with_mediation_required();
+ const result = navigator.credentials.get(config);
+ return promise_rejects_dom(t, 'NetworkError', result);
+}, 'Status header should be ignored from cross-site iframe that contains a subresource with the header');
+
+fedcm_test(async t => {
+ await mark_signed_out(alt_manifest_origin);
+ await open_and_wait_for_popup(alt_manifest_origin, "/credential-management/support/fencedframe-mark-signedin.html");
+
+ const config = alt_request_options_with_mediation_required();
+ const result = navigator.credentials.get(config);
+ return promise_rejects_dom(t, 'NetworkError', result);
+}, 'Status header should be ignored from a fenced frame, even if it is same-origin');
+
+</script>
+
diff --git a/testing/web-platform/tests/credential-management/fedcm-login-status/logged-out.https.html b/testing/web-platform/tests/credential-management/fedcm-login-status/logged-out.https.html
new file mode 100644
index 0000000000..09750ff096
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-login-status/logged-out.https.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>FedCM IDP sign-in status API tests</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ fedcm_test,
+ alt_manifest_origin,
+ alt_request_options_with_mediation_required,
+ fedcm_get_and_select_first_account,
+ mark_signed_out} from '../support/fedcm-helper.sub.js';
+
+async function createIframeWithPermissionPolicyAndWaitForMessage(test, iframeUrl) {
+ const messageWatcher = new EventWatcher(test, window, "message");
+ let iframe = document.createElement("iframe");
+ iframe.src = iframeUrl;
+ iframe.allow = "identity-credentials-get";
+ document.body.appendChild(iframe);
+ const message = await messageWatcher.wait_for("message");
+ return message.data;
+}
+
+fedcm_test(async t => {
+ await mark_signed_out();
+ const config = request_options_with_mediation_required();
+ const result = navigator.credentials.get(config);
+ return promise_rejects_dom(t, 'NetworkError', result);
+}, 'FedCM request should fail because we are marked as not logged in');
+
+fedcm_test(async t => {
+ // Log in so that the browser allows the later user info request.
+ const cred = await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required());
+ assert_equals(cred.token, "token");
+
+ await mark_signed_out(alt_manifest_origin);
+
+ const iframe_in_idp_scope = `${alt_manifest_origin}/\
+credential-management/support/fedcm/userinfo-iframe.html`;
+ const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope);
+ assert_equals(message.result, "Fail");
+
+}, 'User info request should fail because we are marked as not logged in');
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-loginhint.https.html b/testing/web-platform/tests/credential-management/fedcm-loginhint.https.html
new file mode 100644
index 0000000000..edae955a76
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-loginhint.https.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API login hint tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<body>
+
+<script type="module">
+import {fedcm_test,
+ request_options_with_login_hint,
+ select_manifest,
+ mark_signed_in,
+ fedcm_get_dialog_type_promise,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ await mark_signed_in();
+
+ let options = request_options_with_login_hint('manifest.py', 'nomatch');
+ const cred = fedcm_get_and_select_first_account(t, options);
+ // We expect a mismatch dialog.
+ const type = await fedcm_get_dialog_type_promise(t);
+ assert_equals(type, 'ConfirmIdpLogin');
+ window.test_driver.cancel_fedcm_dialog();
+ return promise_rejects_dom(t, "NetworkError", cred);
+}, "No login hint matches an account.");
+
+fedcm_test(async t => {
+ const options = request_options_with_login_hint('manifest.py', 'john_doe');
+ const cred = await fedcm_get_and_select_first_account(t, options);
+ assert_equals(cred.token, 'token');
+}, "Login hint matches an account.");
+
+fedcm_test(async t => {
+ let options = request_options_with_login_hint('manifest_with_two_accounts.json', 'john_doe');
+ await select_manifest(t, options);
+
+ const cred = await fedcm_get_and_select_first_account(t, options);
+ assert_equals(cred.token, 'account_id=john_doe');
+}, "Login hint matches an account from two accounts.");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-manifest-not-in-list.https.html b/testing/web-platform/tests/credential-management/fedcm-manifest-not-in-list.https.html
new file mode 100644
index 0000000000..087af384b1
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-manifest-not-in-list.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {alt_request_options_with_mediation_required,
+ fedcm_test,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ let test_options = alt_request_options_with_mediation_required('manifest-not-in-list.json');
+ const cred = fedcm_get_and_select_first_account(t, test_options);
+ return promise_rejects_dom(t, 'NetworkError', cred);
+}, 'Test that the promise is rejected if the manifest is not in the manifest list');
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-first-idp.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-first-idp.https.html
new file mode 100644
index 0000000000..ed7c1300bd
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-first-idp.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API multi IDP abort first IDP test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script type="module">
+ import {
+ set_fedcm_cookie,
+ set_alt_fedcm_cookie,
+ request_options_with_mediation_required,
+ alt_request_options_with_mediation_required
+ } from '../support/fedcm-helper.sub.js';
+
+ let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]);
+
+ promise_test(async t => {
+ let first_controller = new AbortController();
+ let first_test_options = request_options_with_mediation_required();
+ first_test_options.signal = first_controller.signal;
+ const first_cred = navigator.credentials.get(first_test_options);
+
+ let second_controller = new AbortController();
+ let second_test_options = alt_request_options_with_mediation_required();
+ second_test_options.signal = second_controller.signal;
+ const second_cred = navigator.credentials.get(second_test_options);
+
+ await cookies_promise;
+ first_controller.abort();
+ return Promise.all([
+ promise_rejects_dom(t, 'AbortError', first_cred),
+ promise_rejects_dom(t, 'AbortError', second_cred)
+ ]);
+ }, "Test abort signal for a multi IDP request by aborting the first IDP");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-second-idp.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-second-idp.https.html
new file mode 100644
index 0000000000..dfe8969932
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/abort-multiple-gets-through-second-idp.https.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API multi IDP abort second IDP test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+
+<script type="module">
+ import {
+ set_fedcm_cookie,
+ set_alt_fedcm_cookie,
+ request_options_with_mediation_required,
+ alt_request_options_with_mediation_required
+ } from '../support/fedcm-helper.sub.js';
+
+ let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]);
+
+ promise_test(async t => {
+ let first_controller = new AbortController();
+ let first_test_options = request_options_with_mediation_required();
+ first_test_options.signal = first_controller.signal;
+ const first_cred = navigator.credentials.get(first_test_options);
+
+ let second_controller = new AbortController();
+ let second_test_options = alt_request_options_with_mediation_required();
+ second_test_options.signal = second_controller.signal;
+ const second_cred = navigator.credentials.get(second_test_options);
+
+ await cookies_promise;
+ second_controller.abort();
+ return Promise.all([
+ promise_rejects_dom(t, 'AbortError', first_cred),
+ promise_rejects_dom(t, 'AbortError', second_cred)
+ ]);
+ }, "Test abort signal for a multi IDP request by aborting the second IDP");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-after-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-after-onload.https.html
new file mode 100644
index 0000000000..12e0eb4d81
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-after-onload.https.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API multi IDP get before and after onload test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {set_fedcm_cookie, set_alt_fedcm_cookie,
+ request_options_with_mediation_required,
+ alt_request_options_with_mediation_required,
+ fedcm_select_account_promise} from '../support/fedcm-helper.sub.js';
+
+let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]);
+let has_window_loaded = false;
+const window_loaded = new Promise(resolve => {
+ window.addEventListener('load', () => {
+ has_window_loaded = true;
+ resolve();
+ });
+});
+
+promise_test(async t => {
+ let first_cred_resolved = false;
+ assert_false(has_window_loaded);
+ // First navigator.credentials.get() is called prior to window.onload
+ const first_cred = navigator.credentials.get(request_options_with_mediation_required()).finally(() => { first_cred_resolved = true; });
+ await Promise.all([cookies_promise, window_loaded]);
+ assert_true(has_window_loaded);
+ assert_false(first_cred_resolved);
+
+ // Second navigator.credentials.get() is called after window.onload but before first navigator.credentials.get()
+ // resolves. Should be rejected because it occurs after onload, and the first get() call is pending.
+ const second_cred = navigator.credentials.get(alt_request_options_with_mediation_required());
+ const rejection = promise_rejects_dom(t, 'NotAllowedError', second_cred);
+
+ // Select first account from the first get() call.
+ await fedcm_select_account_promise(t, 0);
+ const first = await first_cred;
+ assert_equals(first.token, "token");
+ return rejection;
+}, "When there's a `get` call before onload, a `get` call which occurs after onload but before the first `get` call resolves, should be rejected.");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-during-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-during-onload.https.html
new file mode 100644
index 0000000000..3e2f134f20
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-and-during-onload.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API multi IDP get before and during onload test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {set_fedcm_cookie, set_alt_fedcm_cookie,
+ request_options_with_mediation_required,
+ alt_request_options_with_mediation_required,
+ fedcm_select_account_promise} from '../support/fedcm-helper.sub.js';
+
+let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]);
+
+promise_test(async t => {
+ let has_window_loaded = false;
+ let rejection;
+ const window_loaded = new Promise(resolve => {
+ window.addEventListener('load', async () => {
+ const second_cred = navigator.credentials.get(alt_request_options_with_mediation_required());
+ rejection = promise_rejects_dom(t, 'NetworkError', second_cred);
+ has_window_loaded = true;
+ resolve();
+ });
+ });
+ assert_false(has_window_loaded);
+ const first_cred = navigator.credentials.get(request_options_with_mediation_required());
+ await Promise.all([cookies_promise, window_loaded]);
+
+ // Select first account from the first get() call.
+ await fedcm_select_account_promise(t, 0);
+ assert_true(has_window_loaded);
+ const first = await first_cred;
+ assert_equals(first.token, "token");
+ return rejection;
+}, "A `get` call before onload and a `get` call during onload should be combined.");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-onload-and-during-dom-content-loaded.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-onload-and-during-dom-content-loaded.https.html
new file mode 100644
index 0000000000..95495948b7
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/get-before-onload-and-during-dom-content-loaded.https.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API multi IDP get before onload and during DOMContentLoaded test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {set_fedcm_cookie,
+ set_alt_fedcm_cookie,
+ request_options_with_mediation_required,
+ alt_request_options_with_mediation_required,
+ fedcm_select_account_promise} from '../support/fedcm-helper.sub.js';
+
+let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]);
+
+promise_test(async t => {
+ let has_dom_content_loaded = false;
+ let rejection;
+ const dom_content_loaded = new Promise(resolve => {
+ document.addEventListener('DOMContentLoaded', async () => {
+ const second_cred = navigator.credentials.get(alt_request_options_with_mediation_required());
+ rejection = promise_rejects_dom(t, 'NetworkError', second_cred);
+ has_dom_content_loaded = true;
+ resolve();
+ });
+ });
+ assert_false(has_dom_content_loaded);
+ const first_cred = navigator.credentials.get(request_options_with_mediation_required());
+ await Promise.all([cookies_promise, dom_content_loaded]);
+ assert_true(has_dom_content_loaded);
+
+ await fedcm_select_account_promise(t, 0);
+ const first = await first_cred;
+ assert_equals(first.token, "token");
+ return rejection;
+}, "A `get` call before onload and a `get` call during DOMContentLoaded event should combine despite being called from different tasks.");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-abort.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-abort.https.html
new file mode 100644
index 0000000000..899302fb22
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-abort.https.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API multi IDP get after abort test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+ import {
+ set_fedcm_cookie,
+ set_alt_fedcm_cookie,
+ request_options_with_mediation_required,
+ alt_request_options_with_mediation_required,
+ fedcm_select_account_promise
+ } from '../support/fedcm-helper.sub.js';
+
+ let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]);
+
+ promise_test(async t => {
+ let first_controller = new AbortController();
+ let first_test_options = request_options_with_mediation_required();
+ first_test_options.signal = first_controller.signal;
+ const first_cred = navigator.credentials.get(first_test_options);
+
+ let second_controller = new AbortController();
+ let second_test_options = alt_request_options_with_mediation_required();
+ second_test_options.signal = second_controller.signal;
+ const second_cred = navigator.credentials.get(second_test_options);
+
+ await cookies_promise;
+ second_controller.abort();
+ await Promise.all([
+ promise_rejects_dom(t, 'AbortError', first_cred),
+ promise_rejects_dom(t, 'AbortError', second_cred)
+ ]);
+
+ const third_cred = navigator.credentials.get(request_options_with_mediation_required());
+ const fourth_cred = navigator.credentials.get(alt_request_options_with_mediation_required());
+
+ // Select first account, i.e. from the `third_cred`.
+ await fedcm_select_account_promise(t, 0);
+
+ // NetworkError is returned when another IDP is selected.
+ await promise_rejects_dom(t, 'NetworkError', fourth_cred);
+ const cred = await third_cred;
+ assert_equals(cred.token, "token");
+ }, "Multiple gets after aborting a multi IDP request should work");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-onload.https.html
new file mode 100644
index 0000000000..1b5d744e8f
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-after-onload.https.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API multi IDP multiple gets after onload test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {set_fedcm_cookie,
+ set_alt_fedcm_cookie,
+ request_options_with_mediation_required,
+ alt_request_options_with_mediation_required,
+ fedcm_select_account_promise} from '../support/fedcm-helper.sub.js';
+
+let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]);
+const window_loaded = new Promise(resolve => {
+ window.addEventListener('load', () => {
+ resolve();
+ });
+});
+
+promise_test(async t => {
+ await Promise.all([cookies_promise, window_loaded]);
+ const first_cred = navigator.credentials.get(request_options_with_mediation_required());
+ const second_cred = navigator.credentials.get(alt_request_options_with_mediation_required());
+
+ // Select first account from the first get() call.
+ await fedcm_select_account_promise(t, 0);
+ // NetworkError is returned when another IDP is selected.
+ await promise_rejects_dom(t, 'NetworkError', second_cred);
+ const first = await first_cred;
+ assert_equals(first.token, "token");
+}, "No `get` calls before or during onload, multiple `get` calls after onload in the same task are allowed.");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-before-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-before-onload.https.html
new file mode 100644
index 0000000000..8c98bf53b0
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-before-onload.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API multi IDP multiple gets before onload test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {set_fedcm_cookie,
+ set_alt_fedcm_cookie,
+ request_options_with_mediation_required,
+ alt_request_options_with_mediation_required,
+ fedcm_select_account_promise} from '../support/fedcm-helper.sub.js';
+
+let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]);
+let has_window_loaded = false;
+window.addEventListener('load', () => {
+ has_window_loaded = true;
+});
+
+promise_test(async t => {
+ assert_false(has_window_loaded);
+ const first_cred = navigator.credentials.get(alt_request_options_with_mediation_required());
+ const second_cred = navigator.credentials.get(request_options_with_mediation_required());
+ await cookies_promise;
+
+ // Select second account, i.e. from the second get() call.
+ await fedcm_select_account_promise(t, 1);
+ await promise_rejects_dom(t, 'NetworkError', first_cred);
+ const cred = await second_cred;
+ assert_equals(cred.token, "token");
+}, "Multiple get calls before window onload are allowed.");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-during-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-during-onload.https.html
new file mode 100644
index 0000000000..bcf70a31c7
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/multiple-gets-during-onload.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API multi IDP multiple gets during onload test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {set_fedcm_cookie,
+ set_alt_fedcm_cookie,
+ request_options_with_mediation_required,
+ alt_request_options_with_mediation_required,
+ fedcm_select_account_promise} from '../support/fedcm-helper.sub.js';
+
+let cookies_promise = Promise.all([set_fedcm_cookie(), set_alt_fedcm_cookie()]);
+
+promise_test(async t => {
+ const window_loaded = new Promise(resolve => {
+ window.addEventListener('load', async () => {
+ const first_cred = navigator.credentials.get(request_options_with_mediation_required());
+ const second_cred = navigator.credentials.get(alt_request_options_with_mediation_required());
+ await cookies_promise;
+ await fedcm_select_account_promise(t, 0);
+ await promise_rejects_dom(t, 'NetworkError', second_cred);
+ const first = await first_cred;
+ assert_equals(first.token, "token");
+ resolve();
+ });
+ });
+ await window_loaded;
+}, "No `get` calls before onload, multiple `get` calls during onload are allowed.");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-after-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-after-onload.https.html
new file mode 100644
index 0000000000..de6a7c5371
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-after-onload.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API multi IDP single get after onload test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {set_fedcm_cookie,
+ request_options_with_mediation_required,
+ fedcm_get_and_select_first_account} from '../support/fedcm-helper.sub.js';
+
+const window_loaded = new Promise(resolve => {
+ window.addEventListener('load', () => {
+ resolve();
+ });
+});
+
+promise_test(async t => {
+ await set_fedcm_cookie();
+ await window_loaded;
+ const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+ assert_equals(cred.token, "token");
+}, "Single `get` call after onload is allowed.");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-before-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-before-onload.https.html
new file mode 100644
index 0000000000..0ac9b0e920
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-before-onload.https.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API multi IDP single get before onload test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {set_fedcm_cookie,
+ request_options_with_mediation_required,
+ fedcm_select_account_promise} from '../support/fedcm-helper.sub.js';
+
+let has_window_loaded = false;
+const window_loaded = new Promise(resolve => {
+ window.addEventListener('load', () => {
+ has_window_loaded = true;
+ resolve();
+ });
+});
+
+promise_test(async t => {
+ const first_cred = navigator.credentials.get(request_options_with_mediation_required());
+ assert_false(has_window_loaded);
+ await set_fedcm_cookie();
+ await window_loaded;
+ assert_true(has_window_loaded);
+
+ // Select first account after onload.
+ await fedcm_select_account_promise(t, 0);
+ const first = await first_cred;
+ assert_equals(first.token, "token");
+}, "Single `get` call before onload is allowed even if account is selected after.");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-during-onload.https.html b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-during-onload.https.html
new file mode 100644
index 0000000000..832565744d
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-multi-idp/single-get-during-onload.https.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API multi IDP single get during onload test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ set_fedcm_cookie,
+ fedcm_get_and_select_first_account} from '../support/fedcm-helper.sub.js';
+
+promise_test(async t => {
+ const window_loaded = new Promise(resolve => {
+ window.addEventListener('load', async () => {
+ await set_fedcm_cookie();
+ const first_cred = fedcm_get_and_select_first_account(t,
+ request_options_with_mediation_required());
+ const cred = await first_cred;
+ assert_equals(cred.token, "token");
+ resolve();
+ });
+ });
+ await window_loaded;
+}, "Single `get` call during onload is allowed.");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-no-login-url.https.html b/testing/web-platform/tests/credential-management/fedcm-no-login-url.https.html
new file mode 100644
index 0000000000..94592d2dbf
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-no-login-url.https.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API: Missing login URL should fail.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ fedcm_test,
+ select_manifest,
+ mark_signed_in,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ await mark_signed_in();
+
+ let test_options = request_options_with_mediation_required("manifest_no_login_url.json");
+ await select_manifest(t, test_options);
+
+ const cred = fedcm_get_and_select_first_account(t, test_options);
+ return promise_rejects_dom(t, 'NetworkError', cred);
+}, 'Test that promise is rejected if the manifest has no login URL');
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-nonce-is-optional.https.html b/testing/web-platform/tests/credential-management/fedcm-nonce-is-optional.https.html
new file mode 100644
index 0000000000..dafd6c9e98
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-nonce-is-optional.https.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ fedcm_test,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ let test_options = request_options_with_mediation_required();
+ assert_true("nonce" in test_options.identity.providers[0]);
+ delete test_options.identity.providers[0].nonce;
+ const cred = await fedcm_get_and_select_first_account(t, test_options);
+ assert_equals(cred.token, "token");
+}, "nonce is not required in FederatedIdentityProvider.");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-not-observed-by-service-worker.https.html b/testing/web-platform/tests/credential-management/fedcm-not-observed-by-service-worker.https.html
new file mode 100644
index 0000000000..072d669665
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-not-observed-by-service-worker.https.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script src="/service-workers/service-worker/resources/test-helpers.sub.js"></script>
+
+<body>
+
+<script type="module">
+import {fedcm_test,
+ request_options_with_mediation_required,
+ set_fedcm_cookie,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+function loadUrlInIframe(url) {
+ let iframe = document.createElement("iframe");
+ return new Promise(resolve => {
+ iframe.src = url;
+ iframe.onload = function() { resolve(iframe); };
+ document.body.appendChild(iframe);
+ });
+}
+
+fedcm_test(async t => {
+ const service_worker_url = 'support/fedcm/intercept_service_worker.js';
+ const sw_scope_url = '/credential-management/support/fedcm/';
+ // URL for querying number of page loads observed by service worker.
+ const query_sw_intercepts_url = 'support/fedcm/query_service_worker_intercepts.html';
+ const page_in_sw_scope_url = 'support/fedcm/simple.html';
+
+ const sw_registration = await service_worker_unregister_and_register(
+ t, service_worker_url, sw_scope_url);
+ t.add_cleanup(() => service_worker_unregister(t, sw_scope_url));
+ await wait_for_state(t, sw_registration.installing, 'activated');
+
+ // Verify that service worker works.
+ await loadUrlInIframe(page_in_sw_scope_url);
+ let query_sw_iframe = await loadUrlInIframe(query_sw_intercepts_url);
+ assert_equals(query_sw_iframe.contentDocument.body.textContent, "1");
+
+ await set_fedcm_cookie();
+ const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+ assert_equals(cred.token, "token");
+
+ // Use cache buster query parameter to avoid cached response.
+ let query_sw_iframe2 = await loadUrlInIframe(query_sw_intercepts_url + "?2");
+ assert_equals(query_sw_iframe2.contentDocument.body.textContent, "1");
+}, 'Test that service worker cannot observe fetches performed by FedCM API');
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-pending-call-rejected.https.html b/testing/web-platform/tests/credential-management/fedcm-pending-call-rejected.https.html
new file mode 100644
index 0000000000..feb3f903d8
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-pending-call-rejected.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ alt_request_options_with_mediation_required,
+ fedcm_test,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ const first = fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+ const second = navigator.credentials.get(alt_request_options_with_mediation_required());
+
+ // We have to call promise_rejects_dom here, because if we call it after
+ // the promise gets rejected, the unhandled rejection event handler is called
+ // and fails the test even if we handle the rejection later.
+ const rej = promise_rejects_dom(t, 'AbortError', second);
+
+ const first_cred = await first;
+ assert_equals(first_cred.token, "token");
+
+ return rej;
+}, "When there's a pending request, a second `get` call should be rejected.");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-pending-disconnect.https.html b/testing/web-platform/tests/credential-management/fedcm-pending-disconnect.https.html
new file mode 100644
index 0000000000..1b60acc108
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-pending-disconnect.https.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API pending disconnect() test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {fedcm_test,
+ mark_signed_in,
+ set_fedcm_cookie,
+ disconnect_options,
+ fedcm_get_and_select_first_account,
+ request_options_with_mediation_required} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ await mark_signed_in();
+ await set_fedcm_cookie();
+ // Go through the FedCM flow so that the disconnect() call is not trivial.
+ const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+
+ // Invoke disconnect without awaiting it to test that the browser can handle
+ // the page being destroyed while there is a pending disconnect call.
+ IdentityCredential.disconnect(disconnect_options("1234"));
+}, 'Test that disconnect can be pending when the test finishes.');
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-pending-userinfo.https.html b/testing/web-platform/tests/credential-management/fedcm-pending-userinfo.https.html
new file mode 100644
index 0000000000..0ecae3e80a
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-pending-userinfo.https.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API pending getUserInfo() test.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {alt_manifest_origin,
+ alt_request_options_with_mediation_required,
+ fedcm_test,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+async function createIframeWithPermissionPolicyAndWaitForMessage(test, iframeUrl) {
+ const messageWatcher = new EventWatcher(test, window, "message");
+ let iframe = document.createElement("iframe");
+ iframe.src = iframeUrl;
+ iframe.allow = "identity-credentials-get";
+ document.body.appendChild(iframe);
+ const message = await messageWatcher.wait_for("message");
+ return message.data;
+}
+
+fedcm_test(async t => {
+ const cred = await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required());
+ assert_equals(cred.token, "token");
+
+ const iframe_in_idp_scope = `${alt_manifest_origin}/\
+credential-management/support/fedcm/pending-userinfo-iframe.html`;
+ const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope);
+ assert_equals(message, "Pass");
+}, 'Test basic User InFo API flow');
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-reject-invalid-responses.https.html b/testing/web-platform/tests/credential-management/fedcm-reject-invalid-responses.https.html
new file mode 100644
index 0000000000..f450d56824
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-reject-invalid-responses.https.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {fedcm_test,
+ request_options_with_mediation_required,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ let test_options = request_options_with_mediation_required();
+ test_options.identity.providers = [];
+ const cred = fedcm_get_and_select_first_account(t, test_options);
+ return promise_rejects_js(t, TypeError, cred);
+}, "Reject when provider list is empty");
+
+fedcm_test(async t => {
+ let test_options = request_options_with_mediation_required();
+ delete test_options.identity.providers[0].configURL;
+ const cred = fedcm_get_and_select_first_account(t, test_options);
+ return promise_rejects_js(t, TypeError, cred);
+}, "Reject when configURL is missing" );
+
+fedcm_test(async t => {
+ let test_options = request_options_with_mediation_required();
+ test_options.identity.providers[0].configURL = 'test';
+ const cred = fedcm_get_and_select_first_account(t, test_options);
+ return promise_rejects_dom(t, "InvalidStateError", cred);
+}, "Reject when configURL is invalid");
+
+fedcm_test(async t => {
+ let test_options = request_options_with_mediation_required();
+ test_options.identity.providers[0].clientId = '';
+ const cred = fedcm_get_and_select_first_account(t, test_options);
+ return promise_rejects_dom(t, "InvalidStateError", cred);
+}, "Reject when clientId is empty");
+
+fedcm_test(async t => {
+ let test_options = request_options_with_mediation_required();
+ delete test_options.identity.providers[0].clientId;
+ const cred = fedcm_get_and_select_first_account(t, test_options);
+ return promise_rejects_js(t, TypeError, cred);
+}, "Reject when clientId is missing" );
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-returning-account-auto-reauthn.https.html b/testing/web-platform/tests/credential-management/fedcm-returning-account-auto-reauthn.https.html
new file mode 100644
index 0000000000..c9fe10a485
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-returning-account-auto-reauthn.https.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API network request tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_optional,
+ fedcm_test,
+ select_manifest,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ let test_options = request_options_with_mediation_optional("manifest_with_single_account.json");
+ await select_manifest(t, test_options);
+
+ // Signs in john_doe so that they will be a returning user
+ let cred = await fedcm_get_and_select_first_account(t, test_options);
+ assert_equals(cred.token, "account_id=john_doe");
+
+ test_options = request_options_with_mediation_optional("manifest_with_two_accounts.json");
+ await select_manifest(t, test_options);
+
+ // There are two accounts "Jane" and "John" returned in that order. Without
+ // auto re-authn, the first account "Jane" would be selected and an token
+ // would be issued to that account. However, since "John" is returning and
+ // "Jane" is a new user, the second account "John" will be selected.
+ cred = await navigator.credentials.get(test_options);
+ assert_equals(cred.token, "account_id=john_doe");
+ assert_equals(cred.isAutoSelected, true);
+}, "Test that the returning account from the two accounts will be auto re-authenticated.");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-store.https.html b/testing/web-platform/tests/credential-management/fedcm-store.https.html
new file mode 100644
index 0000000000..d1e6ef464c
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-store.https.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API store() tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {fedcm_test,
+ request_options_with_mediation_required,
+ fedcm_get_and_select_first_account} from "./support/fedcm-helper.sub.js";
+
+fedcm_test(async t => {
+ const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+ return promise_rejects_dom(t, "NotSupportedError", navigator.credentials.store(cred));
+}, "navigator.credentials.store() with an identity credential returns NotSupportedError");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-token-returned-with-http-error.https.html b/testing/web-platform/tests/credential-management/fedcm-token-returned-with-http-error.https.html
new file mode 100644
index 0000000000..2337829add
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-token-returned-with-http-error.https.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API token response tests</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<script type="module">
+import {request_options_with_mediation_required,
+ fedcm_test,
+ select_manifest,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ const test_options =
+ request_options_with_mediation_required("manifest_token_with_http_error.json");
+ await select_manifest(t, test_options);
+
+ const cred = fedcm_get_and_select_first_account(t, test_options);
+ return promise_rejects_dom(t, 'NetworkError', cred);
+}, 'Test that the promise will be rejected if the response has http error');
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-too-many-disconnect-calls.https.html b/testing/web-platform/tests/credential-management/fedcm-too-many-disconnect-calls.https.html
new file mode 100644
index 0000000000..cb5dfa615f
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-too-many-disconnect-calls.https.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API two disconnect() at the same time.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {fedcm_test,
+ mark_signed_in,
+ set_fedcm_cookie,
+ fedcm_get_and_select_first_account,
+ manifest_origin,
+ request_options_with_mediation_required,
+ disconnect_options} from './support/fedcm-helper.sub.js';
+
+fedcm_test(async t => {
+ await mark_signed_in();
+ await set_fedcm_cookie();
+ // Get at least one connected account that can be disconnected.
+ const cred = await fedcm_get_and_select_first_account(t, request_options_with_mediation_required());
+ const manifest = `${manifest_origin}/\
+credential-management/support/fedcm/manifest.py`;
+ const options = disconnect_options("1234");
+ IdentityCredential.disconnect(options);
+ await promise_rejects_dom(t, 'NetworkError', IdentityCredential.disconnect(options));
+}, "When disconnect is called while there is a pending one, it is rejected.");
+</script>
diff --git a/testing/web-platform/tests/credential-management/fedcm-userinfo.https.html b/testing/web-platform/tests/credential-management/fedcm-userinfo.https.html
new file mode 100644
index 0000000000..d460d82845
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/fedcm-userinfo.https.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<title>Federated Credential Management API getUserInfo() tests.</title>
+<link rel="help" href="https://fedidcg.github.io/FedCM">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+
+<body>
+
+<script type="module">
+import {alt_manifest_origin,
+ alt_request_options_with_mediation_required,
+ fedcm_test,
+ fedcm_get_and_select_first_account} from './support/fedcm-helper.sub.js';
+
+async function createIframeWithPermissionPolicyAndWaitForMessage(test, iframeUrl) {
+ const messageWatcher = new EventWatcher(test, window, "message");
+ let iframe = document.createElement("iframe");
+ iframe.src = iframeUrl;
+ iframe.allow = "identity-credentials-get";
+ document.body.appendChild(iframe);
+ const message = await messageWatcher.wait_for("message");
+ return message.data;
+}
+
+fedcm_test(async t => {
+ const cred = await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required());
+ assert_equals(cred.token, "token");
+
+ const iframe_in_idp_scope = `${alt_manifest_origin}/\
+credential-management/support/fedcm/userinfo-iframe.html`;
+ const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope);
+ assert_equals(message.result, "Pass");
+ assert_equals(message.numAccounts, 1);
+ assert_equals(message.firstAccountEmail, "john_doe@idp.example");
+ assert_equals(message.firstAccountName, "John Doe");
+ assert_equals(message.firstAccountGivenName, "John");
+ assert_equals(message.firstAccountPicture, "https://idp.example/profile/123");
+}, 'Test basic User InFo API flow');
+
+fedcm_test(async t => {
+ const cred = await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required());
+ assert_equals(cred.token, "token");
+
+ const iframe_in_idp_scope = `support/fedcm/userinfo-iframe.html`;
+ const message = await createIframeWithPermissionPolicyAndWaitForMessage(t, iframe_in_idp_scope);
+ assert_equals(message.result, "Fail");
+}, 'Test that User Info API only works when invoked from iframe that is same origin as the IDP');
+
+fedcm_test(async t => {
+ const cred = await fedcm_get_and_select_first_account(t, alt_request_options_with_mediation_required());
+ assert_equals(cred.token, "token");
+
+ try {
+ const manifest_path = `${alt_manifest_origin}/\
+credential-management/support/fedcm/manifest.py`;
+ const user_info = await IdentityProvider.getUserInfo({
+ configURL: manifest_path,
+ // Approved client
+ clientId: '123',
+ });
+ assert_unreached("Failure message");
+ } catch (error) {
+ assert_equals(error.message, "UserInfo request must be initiated from a frame that is the same origin with the provider.");
+ // Expect failure
+ }
+}, 'Test that User Info API does not work in the top frame');
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/federatedcredential-framed-get.sub.https.html b/testing/web-platform/tests/credential-management/federatedcredential-framed-get.sub.https.html
new file mode 100644
index 0000000000..7883c8ebf2
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/federatedcredential-framed-get.sub.https.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+assert_implements('FederatedCredential' in window, "`FederatedCredential` is supported.");
+
+// Ensure that the check is "same origin", not "same origin-domain".
+document.domain = window.location.hostname;
+
+function create_iframe_test(origin, expectation) {
+ return function (t) {
+ window.addEventListener("load", _ => {
+ var iframe = document.createElement("iframe");
+ iframe.src = origin + "/credential-management/support/federatedcredential-get.html";
+ window.addEventListener("message", t.step_func(e => {
+ if (e.source == iframe.contentWindow) {
+ if (expectation == "blocked") {
+ assert_equals(e.data.exception, "NotAllowedError");
+ } else {
+ if (e.data.exception)
+ assert_not_equals(e.data.exception, "NotAllowedError");
+ }
+ t.done();
+ }
+ }));
+ document.body.appendChild(iframe);
+ });
+ };
+}
+
+function create_nested_iframe_test(outerOrigin, innerOrigin, expectation) {
+ return function (t) {
+ window.addEventListener("load", _ => {
+ var iframe = document.createElement("iframe");
+ iframe.src = outerOrigin + "/credential-management/support/echoing-nester.html?origin=" + innerOrigin + "&file=federatedcredential-get.html";
+ window.addEventListener("message", t.step_func(e => {
+ if (e.source == iframe.contentWindow) {
+ if (expectation == "blocked") {
+ assert_equals(e.data.exception, "NotAllowedError");
+ } else {
+ assert_equals(e.data.exception, null);
+ }
+ t.done();
+ }
+ }));
+ document.body.appendChild(iframe);
+ });
+ };
+}
+
+const SAME_ORIGIN = window.origin;
+const CROSS_ORIGIN = "https://{{domains[élève]}}:{{ports[https][0]}}";
+
+async_test(
+ create_iframe_test(SAME_ORIGIN, "allowed"),
+ "Same-origin IFrame does not throw.");
+async_test(
+ create_iframe_test(CROSS_ORIGIN, "blocked"),
+ "Cross-origin IFrame throws 'NotAllowedError'.");
+
+async_test(
+ create_nested_iframe_test(SAME_ORIGIN, SAME_ORIGIN, "allowed"),
+ "Same-origin IFrame in same-origin IFrame does not throw.");
+
+async_test(
+ create_nested_iframe_test(SAME_ORIGIN, CROSS_ORIGIN, "blocked"),
+ "Same-origin IFrame in same-origin IFrame throws 'NotAllowedError'.");
+
+async_test(
+ create_nested_iframe_test(CROSS_ORIGIN, SAME_ORIGIN, "blocked"),
+ "Cross-origin IFrame in same-origin IFrame throws 'NotAllowedError'.");
+
+async_test(
+ create_nested_iframe_test(CROSS_ORIGIN, CROSS_ORIGIN, "blocked"),
+ "Cross-origin IFrame in same-cross-origin throws 'NotAllowedError'.");
+</script>
diff --git a/testing/web-platform/tests/credential-management/idlharness.https.window.js b/testing/web-platform/tests/credential-management/idlharness.https.window.js
new file mode 100644
index 0000000000..26d7c493b0
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/idlharness.https.window.js
@@ -0,0 +1,37 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+// META: timeout=long
+
+// https://w3c.github.io/webappsec-credential-management/
+
+'use strict';
+
+idl_test(
+ ['credential-management'],
+ ['html', 'dom'],
+ idl_array => {
+ idl_array.add_objects({
+ CredentialsContainer: ['navigator.credentials'],
+ PasswordCredential: ['passwordCredential'],
+ FederatedCredential: ['federatedCredential'],
+ });
+
+ try {
+ self.passwordCredential = new PasswordCredential({
+ id: "id",
+ password: "pencil",
+ iconURL: "https://example.com/",
+ name: "name"
+ });
+ } catch (e) {}
+
+ try {
+ self.federatedCredential = new FederatedCredential({
+ id: "id",
+ provider: "https://example.com",
+ iconURL: "https://example.com/",
+ name: "name"
+ });
+ } catch (e) {}
+ }
+)
diff --git a/testing/web-platform/tests/credential-management/otpcredential-get-basics.https.html b/testing/web-platform/tests/credential-management/otpcredential-get-basics.https.html
new file mode 100644
index 0000000000..3907f85450
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/otpcredential-get-basics.https.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<link rel="help" href="https://github.com/WICG/WebOTP">
+<title>Tests OTPCredential</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="module">
+import {Status, expectOTPRequest} from './support/otpcredential-helper.js';
+
+promise_test(async t => {
+ await expectOTPRequest().andReturn(
+ () => ({status: Status.SUCCESS, otp: "ABC"}));
+
+ let cred = await navigator.credentials.get({otp: {transport: ["sms"]}});
+
+ assert_equals(cred.code, "ABC");
+}, 'Basic usage');
+
+promise_test(async t => {
+ await expectOTPRequest().andReturn(
+ () => ({status: Status.SUCCESS, otp: "ABC"}));
+ await expectOTPRequest().andReturn(
+ () => ({status: Status.SUCCESS, otp: "ABC2"}));
+
+ let sms1 = navigator.credentials.get({otp: {transport: ["sms"]}});
+ let sms2 = navigator.credentials.get({otp: {transport: ["sms"]}});
+
+ let cred2 = await sms2;
+ let cred1 = await sms1;
+
+ assert_equals(cred1.code, "ABC");
+ assert_equals(cred2.code, "ABC2");
+}, 'Handle multiple requests in different order.');
+
+promise_test(async t => {
+ await expectOTPRequest().andReturn(() => ({status: Status.CANCELLED}));
+ await expectOTPRequest().andReturn(
+ () => ({status: Status.SUCCESS, otp: "success"}));
+
+ let cancelledRequest = navigator.credentials.get({otp: {transport: ["sms"]}});
+ let successfulCred =
+ await navigator.credentials.get({otp: {transport: ["sms"]}});
+
+ assert_equals(successfulCred.code, "success");
+
+ try {
+ await cancelledRequest;
+ assert_unreached('Expected AbortError to be thrown.');
+ } catch (error) {
+ assert_equals(error.name, "AbortError");
+ }
+}, 'Handle multiple requests with success and error.');
+
+promise_test(async t => {
+ await expectOTPRequest().andReturn(() => ({status: Status.CANCELLED}));
+
+ await promise_rejects_dom(t, 'AbortError', navigator.credentials.get(
+ {otp: {transport: ["sms"]}}));
+}, 'Deal with cancelled requests');
+
+promise_test(async t => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+
+ controller.abort();
+ await promise_rejects_dom(t, 'AbortError', navigator.credentials.get(
+ {otp: {transport: ["sms"]}, signal: signal}));
+}, 'Should abort request');
+
+promise_test(async t => {
+ const controller = new AbortController();
+ const signal = controller.signal;
+
+ controller.abort('CustomError');
+ await promise_rejects_exactly(t, 'CustomError', navigator.credentials.get(
+ {otp: {transport: ["sms"]}, signal: signal}));
+}, 'Should abort request with reason');
+</script>
diff --git a/testing/web-platform/tests/credential-management/otpcredential-iframe.https.html b/testing/web-platform/tests/credential-management/otpcredential-iframe.https.html
new file mode 100644
index 0000000000..da3e572b6b
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/otpcredential-iframe.https.html
@@ -0,0 +1,54 @@
+<!doctype html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script src="/common/get-host-info.sub.js"></script>
+<div id=log>
+<script>
+'use strict';
+
+const host = get_host_info();
+const remoteBaseURL =
+ host.HTTPS_REMOTE_ORIGIN +
+ window.location.pathname.replace(/\/[^\/]*$/, '/');
+const localBaseURL =
+ host.HTTPS_ORIGIN +
+ window.location.pathname.replace(/\/[^\/]*$/, '/');
+
+promise_test(async t => {
+ const messageWatcher = new EventWatcher(t, window, "message");
+ var iframe = document.createElement("iframe");
+ iframe.src = localBaseURL + "support/otpcredential-iframe.html";
+
+ document.body.appendChild(iframe);
+
+ const message = await messageWatcher.wait_for("message");
+ assert_equals(message.data.result, "Pass");
+ assert_equals(message.data.code, "ABC123");
+
+}, "Test OTPCredential enabled in same origin iframes");
+
+promise_test(async t => {
+ const messageWatcher = new EventWatcher(t, window, "message");
+ var iframe = document.createElement("iframe");
+ iframe.src = remoteBaseURL + "support/otpcredential-iframe.html"
+ iframe.allow = "otp-credentials";
+ document.body.appendChild(iframe);
+
+ const message = await messageWatcher.wait_for("message");
+ assert_equals(message.data.result, "Pass");
+ assert_equals(message.data.code, "ABC123");
+
+}, "OTPCredential enabled in cross origin iframes with permissions policy");
+
+promise_test(async t => {
+ const messageWatcher = new EventWatcher(t, window, "message");
+ var iframe = document.createElement("iframe");
+ iframe.src = remoteBaseURL + "support/otpcredential-iframe.html"
+ document.body.appendChild(iframe);
+
+ const message = await messageWatcher.wait_for("message");
+ assert_equals(message.data.result, "Fail");
+ assert_equals(message.data.errorType, "NotAllowedError");
+
+}, "OTPCredential disabled in cross origin iframes without permissions policy");
+</script>
diff --git a/testing/web-platform/tests/credential-management/otpcredential-store.https.html b/testing/web-platform/tests/credential-management/otpcredential-store.https.html
new file mode 100644
index 0000000000..fa2ae8933d
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/otpcredential-store.https.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<link rel="help" href="https://github.com/WICG/WebOTP">
+<title>Tests OTPCredential handing of store()</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script type="module">
+import {Status, expectOTPRequest} from "./support/otpcredential-helper.js";
+
+promise_test(async t => {
+ await expectOTPRequest().andReturn(
+ () => ({status: Status.SUCCESS, otp: "ABC"}));
+
+ const cred = await navigator.credentials.get({otp: {transport: ["sms"]}});
+ return promise_rejects_dom(t, "NotSupportedError", navigator.credentials.store(cred));
+}, "navigator.credentials.store() with an otp credential returns NotSupportedError");
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/passwordcredential-framed-get.sub.https.html b/testing/web-platform/tests/credential-management/passwordcredential-framed-get.sub.https.html
new file mode 100644
index 0000000000..b4afc1eb91
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/passwordcredential-framed-get.sub.https.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+assert_implements('PasswordCredential' in window, "`PasswordCredential` is supported.");
+
+// Ensure that the check is "same origin", not "same origin-domain".
+document.domain = window.location.hostname;
+
+function create_iframe_test(origin, expectation) {
+ return function (t) {
+ window.addEventListener("load", _ => {
+ var iframe = document.createElement("iframe");
+ iframe.src = origin + "/credential-management/support/passwordcredential-get.html";
+ window.addEventListener("message", t.step_func(e => {
+ if (e.source == iframe.contentWindow) {
+ if (expectation == "blocked") {
+ assert_equals(e.data.exception, "NotAllowedError");
+ } else {
+ if (e.data.exception)
+ assert_not_equals(e.data.exception, "NotAllowedError");
+ }
+ t.done();
+ }
+ }));
+ document.body.appendChild(iframe);
+ });
+ };
+}
+
+function create_nested_iframe_test(outerOrigin, innerOrigin, expectation) {
+ return function (t) {
+ window.addEventListener("load", _ => {
+ var iframe = document.createElement("iframe");
+ iframe.src = outerOrigin + "/credential-management/support/echoing-nester.html?origin=" + innerOrigin + "&file=passwordcredential-get.html";
+ window.addEventListener("message", t.step_func(e => {
+ if (e.source == iframe.contentWindow) {
+ if (expectation == "blocked") {
+ assert_equals(e.data.exception, "NotAllowedError");
+ } else {
+ assert_equals(e.data.exception, null);
+ }
+ t.done();
+ }
+ }));
+ document.body.appendChild(iframe);
+ });
+ };
+}
+
+const SAME_ORIGIN = window.origin;
+const CROSS_ORIGIN = "https://{{domains[élève]}}:{{ports[https][0]}}";
+
+async_test(
+ create_iframe_test(SAME_ORIGIN, "allowed"),
+ "Same-origin IFrame does not throw.");
+async_test(
+ create_iframe_test(CROSS_ORIGIN, "blocked"),
+ "Cross-origin IFrame throws 'NotAllowedError'.");
+
+async_test(
+ create_nested_iframe_test(SAME_ORIGIN, SAME_ORIGIN, "allowed"),
+ "Same-origin IFrame in same-origin IFrame does not throw.");
+
+async_test(
+ create_nested_iframe_test(SAME_ORIGIN, CROSS_ORIGIN, "blocked"),
+ "Same-origin IFrame in same-origin IFrame throws 'NotAllowedError'.");
+
+async_test(
+ create_nested_iframe_test(CROSS_ORIGIN, SAME_ORIGIN, "blocked"),
+ "Cross-origin IFrame in same-origin IFrame throws 'NotAllowedError'.");
+
+async_test(
+ create_nested_iframe_test(CROSS_ORIGIN, CROSS_ORIGIN, "blocked"),
+ "Cross-origin IFrame in same-cross-origin throws 'NotAllowedError'.");
+</script>
diff --git a/testing/web-platform/tests/credential-management/require_securecontext.html b/testing/web-platform/tests/credential-management/require_securecontext.html
new file mode 100644
index 0000000000..b1f3103da0
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/require_securecontext.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Test that Credential Management requires secure contexts</title>
+<link rel="help" href="https://w3c.github.io/webappsec-credential-management/#idl-index">
+<script src=/resources/testharness.js></script>
+<script src=/resources/testharnessreport.js></script>
+<script>
+"use strict";
+ test(() => {
+ assert_false(isSecureContext);
+ assert_false('credentials' in navigator);
+ }, "Credential Management must not be accessible in insecure contexts");
+</script>
diff --git a/testing/web-platform/tests/credential-management/support/README.md b/testing/web-platform/tests/credential-management/support/README.md
new file mode 100644
index 0000000000..a6d33ff6f7
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/README.md
@@ -0,0 +1,54 @@
+# CredentialManagement Testing
+
+## OTPCredential Testing
+
+In this test suite `otpcredential-helper.js` is a testing framework that enables
+engines to test OTPCredential by intercepting the connection between the browser
+and the underlying operating system and mock its behavior.
+
+Usage:
+
+1. Include the following in your test:
+```html
+<script src="/resources/test-only-api.js"></script>
+<script src="support/otpcredential-helper.js"></script>
+```
+2. Set expectations
+```javascript
+await expect(receive).andReturn(() => {
+ // mock behavior
+})
+```
+3. Call `navigator.credentials.get({otp: {transport: ["sms"]}})`
+4. Verify results
+
+The mocking API is browser agnostic and is designed such that other engines
+could implement it too.
+
+Here are the symbols that are exposed to tests that need to be implemented
+per engine:
+
+- function receive(): the main/only function that can be mocked
+- function expect(): the main/only function that enables us to mock it
+- enum State {kSuccess, kTimeout}: allows you to mock success/failures
+
+## FedCM Testing
+
+`fedcm-mojojs-helper.js` exposes `fedcm_mojo_mock_test` which is a specialized
+`promise_test` which comes pre-setup with the appropriate mocking infrastructure
+to emulate platform federated auth backend. The mock is passed to the test
+function as the second parameter.
+
+Example usage:
+```
+<script type="module">
+ import {fedcm_mojo_mock_test} from './support/fedcm-mojojs-helper.js';
+
+ fedcm_mojo_mock_test(async (t, mock) => {
+ mock.returnToken("https://idp.test/fedcm.json", "a_token");
+ assert_equals("a_token", await navigator.credentials.get(options));
+ }, "Successfully obtaining a token using mock.");
+</script>
+```
+
+The chromium implementation uses the MojoJS shim.
diff --git a/testing/web-platform/tests/credential-management/support/echoing-nester.html b/testing/web-platform/tests/credential-management/support/echoing-nester.html
new file mode 100644
index 0000000000..d4f5899da7
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/echoing-nester.html
@@ -0,0 +1,16 @@
+<body>
+ <script>
+ window.addEventListener('message', m => {
+ window.parent.postMessage(m.data, '*');
+ });
+
+ var u = new URL(window.location.href);
+ var origin = u.searchParams.has('origin') ? u.searchParams.get('origin') : window.origin;
+ var file = u.searchParams.has('file') ? u.searchParams.get('file') : 'passwordcredential-get.html';
+
+ var url = origin + "/credential-management/support/" + file;
+ var i = document.createElement('iframe');
+ i.src = url;
+ document.body.appendChild(i);
+ </script>
+</body>
diff --git a/testing/web-platform/tests/credential-management/support/fedcm-helper.sub.js b/testing/web-platform/tests/credential-management/support/fedcm-helper.sub.js
new file mode 100644
index 0000000000..765b3cc48a
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm-helper.sub.js
@@ -0,0 +1,263 @@
+export const manifest_origin = "https://{{host}}:{{ports[https][0]}}";
+export const alt_manifest_origin = 'https://{{hosts[alt][]}}:{{ports[https][0]}}';
+export const same_site_manifest_origin = 'https://{{hosts[][www1]}}:{{ports[https][0]}}';
+
+export function open_and_wait_for_popup(origin, path) {
+ return new Promise(resolve => {
+ let popup_window = window.open(origin + path);
+
+ // We rely on the popup page to send us a message when done.
+ const popup_message_handler = (event) => {
+ if (event.origin == origin) {
+ popup_window.close();
+ window.removeEventListener('message', popup_message_handler);
+ resolve();
+ }
+ };
+
+ window.addEventListener('message', popup_message_handler);
+ });
+}
+
+// Set the identity provider cookie.
+export function set_fedcm_cookie(host) {
+ if (host == undefined) {
+ document.cookie = 'cookie=1; SameSite=Strict; Path=/credential-management/support; Secure';
+ return Promise.resolve();
+ } else {
+ return open_and_wait_for_popup(host, '/credential-management/support/set_cookie');
+ }
+}
+
+// Set the alternate identity provider cookie.
+export function set_alt_fedcm_cookie() {
+ return set_fedcm_cookie(alt_manifest_origin);
+}
+
+export function mark_signed_in(origin = manifest_origin) {
+ return open_and_wait_for_popup(origin, '/credential-management/support/mark_signedin');
+}
+
+export function mark_signed_out(origin = manifest_origin) {
+ return open_and_wait_for_popup(origin, '/credential-management/support/mark_signedout');
+}
+
+// Returns FedCM CredentialRequestOptions for which navigator.credentials.get()
+// succeeds.
+export function request_options_with_mediation_required(manifest_filename, origin = manifest_origin) {
+ if (manifest_filename === undefined) {
+ manifest_filename = "manifest.py";
+ }
+ const manifest_path = `${origin}/\
+credential-management/support/fedcm/${manifest_filename}`;
+ return {
+ identity: {
+ providers: [{
+ configURL: manifest_path,
+ clientId: '1',
+ nonce: '2'
+ }]
+ },
+ mediation: 'required'
+ };
+}
+
+// Returns alternate FedCM CredentialRequestOptions for which navigator.credentials.get()
+// succeeds.
+export function alt_request_options_with_mediation_required(manifest_filename) {
+ return request_options_with_mediation_required(manifest_filename, alt_manifest_origin);
+}
+
+// Returns FedCM CredentialRequestOptions with auto re-authentication.
+// succeeds.
+export function request_options_with_mediation_optional(manifest_filename) {
+ let options = alt_request_options_with_mediation_required(manifest_filename);
+ // Approved client
+ options.identity.providers[0].clientId = '123';
+ options.mediation = 'optional';
+
+ return options;
+}
+
+export function request_options_with_context(manifest_filename, context) {
+ if (manifest_filename === undefined) {
+ manifest_filename = "manifest.py";
+ }
+ const manifest_path = `${manifest_origin}/\
+credential-management/support/fedcm/${manifest_filename}`;
+ return {
+ identity: {
+ providers: [{
+ configURL: manifest_path,
+ clientId: '1',
+ nonce: '2'
+ }],
+ context: context
+ },
+ mediation: 'required'
+ };
+}
+
+
+// Test wrapper which does FedCM-specific setup.
+export function fedcm_test(test_func, test_name) {
+ promise_test(async t => {
+ await set_fedcm_cookie();
+ await set_alt_fedcm_cookie();
+ await test_func(t);
+ }, test_name);
+}
+
+function select_manifest_impl(manifest_url) {
+ const url_query = (manifest_url === undefined)
+ ? '' : `?manifest_url=${manifest_url}`;
+
+ return new Promise(resolve => {
+ const img = document.createElement('img');
+ img.src = `/credential-management/support/fedcm/select_manifest_in_root_manifest.py${url_query}`;
+ img.addEventListener('error', resolve);
+ document.body.appendChild(img);
+ });
+}
+
+// Sets the manifest returned by the next fetch of /.well-known/web_identity
+// select_manifest() only affects the next fetch and not any subsequent fetches
+// (ex second next fetch).
+export function select_manifest(test, test_options) {
+ // Add cleanup in case that /.well-known/web_identity is not fetched at all.
+ test.add_cleanup(async () => {
+ await select_manifest_impl();
+ });
+ const manifest_url = test_options.identity.providers[0].configURL;
+ return select_manifest_impl(manifest_url);
+}
+
+export function request_options_with_login_hint(manifest_filename, login_hint) {
+ let options = request_options_with_mediation_required(manifest_filename);
+ options.identity.providers[0].loginHint = login_hint;
+
+ return options;
+}
+
+export function request_options_with_domain_hint(manifest_filename, domain_hint) {
+ let options = request_options_with_mediation_required(manifest_filename);
+ options.identity.providers[0].domainHint = domain_hint;
+
+ return options;
+}
+
+export function fedcm_get_dialog_type_promise(t) {
+ return new Promise(resolve => {
+ async function helper() {
+ // Try to get the dialog type. If the UI is not up yet, we'll catch an exception
+ // and try again in 100ms.
+ try {
+ const type = await window.test_driver.get_fedcm_dialog_type();
+ resolve(type);
+ } catch (ex) {
+ t.step_timeout(helper, 100);
+ }
+ }
+ helper();
+ });
+}
+
+export function fedcm_get_title_promise(t) {
+ return new Promise(resolve => {
+ async function helper() {
+ // Try to get the title. If the UI is not up yet, we'll catch an exception
+ // and try again in 100ms.
+ try {
+ const title = await window.test_driver.get_fedcm_dialog_title();
+ resolve(title);
+ } catch (ex) {
+ t.step_timeout(helper, 100);
+ }
+ }
+ helper();
+ });
+}
+
+export function fedcm_select_account_promise(t, account_index) {
+ return new Promise(resolve => {
+ async function helper() {
+ // Try to select the account. If the UI is not up yet, we'll catch an exception
+ // and try again in 100ms.
+ try {
+ await window.test_driver.select_fedcm_account(account_index);
+ resolve();
+ } catch (ex) {
+ t.step_timeout(helper, 100);
+ }
+ }
+ helper();
+ });
+}
+
+export function fedcm_get_and_select_first_account(t, options) {
+ const credentialPromise = navigator.credentials.get(options);
+ fedcm_select_account_promise(t, 0);
+ return credentialPromise;
+}
+
+export function fedcm_error_dialog_dismiss(t) {
+ return new Promise(resolve => {
+ async function helper() {
+ // Try to select the account. If the UI is not up yet, we'll catch an exception
+ // and try again in 100ms.
+ try {
+ let type = await fedcm_get_dialog_type_promise(t);
+ assert_equals(type, "Error");
+ await window.test_driver.cancel_fedcm_dialog();
+ resolve();
+ } catch (ex) {
+ t.step_timeout(helper, 100);
+ }
+ }
+ helper();
+ });
+}
+
+export function fedcm_error_dialog_click_button(t, button) {
+ return new Promise(resolve => {
+ async function helper() {
+ // Try to select the account. If the UI is not up yet, we'll catch an exception
+ // and try again in 100ms.
+ try {
+ let type = await fedcm_get_dialog_type_promise(t);
+ assert_equals(type, "Error");
+ await window.test_driver.click_fedcm_dialog_button(button);
+ resolve();
+ } catch (ex) {
+ t.step_timeout(helper, 100);
+ }
+ }
+ helper();
+ });
+}
+
+export function disconnect_options(accountHint, manifest_filename) {
+ if (manifest_filename === undefined) {
+ manifest_filename = "manifest.py";
+ }
+ const manifest_path = `${manifest_origin}/\
+credential-management/support/fedcm/${manifest_filename}`;
+ return {
+ configURL: manifest_path,
+ clientId: '1',
+ accountHint: accountHint
+ };
+}
+
+export function alt_disconnect_options(accountHint, manifest_filename) {
+ if (manifest_filename === undefined) {
+ manifest_filename = "manifest.py";
+ }
+ const manifest_path = `${alt_manifest_origin}/\
+credential-management/support/fedcm/${manifest_filename}`;
+ return {
+ configURL: manifest_path,
+ clientId: '1',
+ accountHint: accountHint
+ };
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm-iframe-level2.html b/testing/web-platform/tests/credential-management/support/fedcm-iframe-level2.html
new file mode 100644
index 0000000000..7622d988ff
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm-iframe-level2.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<link rel="help" href="https://wicg.github.io/FedCM">
+<script src="/common/get-host-info.sub.js"></script>
+<div id=log>
+<script>
+'use strict';
+
+const host = get_host_info();
+const remoteBaseURL =
+ host.AUTHENTICATED_ORIGIN +
+ window.location.pathname.replace(/\/[^\/]*$/, '/');
+
+window.onload = async () => {
+ const urlParams = new URLSearchParams(window.location.search);
+ var iframe = document.createElement("iframe");
+ iframe.src = remoteBaseURL + "fedcm-iframe.html";
+ if (urlParams.get("permission") != '0') {
+ iframe.allow = "identity-credentials-get";
+ }
+ document.body.appendChild(iframe);
+};
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/support/fedcm-iframe.html b/testing/web-platform/tests/credential-management/support/fedcm-iframe.html
new file mode 100644
index 0000000000..ba79c4cf9e
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm-iframe.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script type="module">
+import {request_options_with_mediation_required} from './fedcm-helper.sub.js';
+
+// Loading fedcm-iframe.html in the test will make a FedCM call on load, and
+// trigger a postMessage upon completion.
+//
+// message {
+// string result: "Pass" | "Fail"
+// string token: token.token
+// string errorType: error.name
+// }
+
+window.onload = async () => {
+ // Use this variable to stop trying to select an account once the get() promise is resolved.
+ let cancelHelper = false;
+ try {
+ const credentialPromise = navigator.credentials.get(request_options_with_mediation_required());
+ async function helper() {
+ try {
+ if (cancelHelper)
+ return;
+
+ await window.test_driver.select_fedcm_account(0);
+ } catch (ex) {
+ setTimeout(helper, 100);
+ }
+ }
+ helper();
+ const cred = await credentialPromise;
+ window.top.postMessage({result: "Pass", token: cred.token}, '*');
+ } catch (error) {
+ window.top.postMessage({result: "Fail", errorType: error.name}, '*');
+ }
+ // In case the get() call fails and no accounts may be selected, force the
+ // helper function to stop calling itself.
+ cancelHelper = true;
+};
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/support/fedcm-mock.js b/testing/web-platform/tests/credential-management/support/fedcm-mock.js
new file mode 100644
index 0000000000..271dd9cd94
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm-mock.js
@@ -0,0 +1,147 @@
+import { RequestTokenStatus, LogoutRpsStatus, DisconnectStatus, FederatedAuthRequest, FederatedAuthRequestReceiver } from '/gen/third_party/blink/public/mojom/webid/federated_auth_request.mojom.m.js';
+
+function toMojoTokenStatus(status) {
+ return RequestTokenStatus["k" + status];
+}
+
+// A mock service for responding to federated auth requests.
+export class MockFederatedAuthRequest {
+ constructor() {
+ this.receiver_ = new FederatedAuthRequestReceiver(this);
+ this.interceptor_ = new MojoInterfaceInterceptor(FederatedAuthRequest.$interfaceName);
+ this.interceptor_.oninterfacerequest = e => {
+ this.receiver_.$.bindHandle(e.handle);
+ }
+ this.interceptor_.start();
+ this.token_ = null;
+ this.selected_identity_provider_config_url_ = null;
+ this.status_ = RequestTokenStatus.kError;
+ this.logoutRpsStatus_ = LogoutRpsStatus.kError;
+ this.disconnectStatus_ = DisconnectStatus.kError;
+ this.returnPending_ = false;
+ this.pendingPromiseResolve_ = null;
+ }
+
+ // Causes the subsequent `navigator.credentials.get()` to resolve with the token.
+ returnToken(selected_identity_provider_config_url, token) {
+ this.status_ = RequestTokenStatus.kSuccess;
+ this.selected_identity_provider_config_url_ = selected_identity_provider_config_url;
+ this.token_ = token;
+ this.returnPending_ = false;
+ }
+
+ // Causes the subsequent `navigator.credentials.get()` to reject with the error.
+ returnError(error) {
+ if (error == "Success")
+ throw new Error("Success is not a valid error");
+ this.status_ = toMojoTokenStatus(error);
+ this.selected_identity_provider_config_url_ = null;
+ this.token_ = null;
+ this.returnPending_ = false;
+ }
+
+ // Causes the subsequent `navigator.credentials.get()` to return a pending promise
+ // that can be cancelled using `cancelTokenRequest()`.
+ returnPendingPromise() {
+ this.returnPending_ = true;
+ }
+
+ logoutRpsReturn(status) {
+ let validated = LogoutRpsStatus[status];
+ if (validated === undefined)
+ throw new Error("Invalid status: " + status);
+ this.logoutRpsStatus_ = validated;
+ }
+
+ // Causes the subsequent `FederatedCredential.disconnect` to reject with this
+ // status.
+ disconnectReturn(status) {
+ let validated = DisconnectStatus[status];
+ if (validated === undefined)
+ throw new Error("Invalid status: " + status);
+ this.disconnectStatus_ = validated;
+ }
+
+ // Implements
+ // RequestToken(array<IdentityProviderGetParameters> idp_get_params) =>
+ // (RequestTokenStatus status,
+ // url.mojom.Url? selected_identity_provider_config_url,
+ // string? token);
+ async requestToken(idp_get_params) {
+ if (this.returnPending_) {
+ this.pendingPromise_ = new Promise((resolve, reject) => {
+ this.pendingPromiseResolve_ = resolve;
+ });
+ return this.pendingPromise_;
+ }
+ return Promise.resolve({
+ status: this.status_,
+ selected_identity_provider_config_url: this.selected_identity_provider_config_url_,
+ token: this.token_
+ });
+ }
+
+ async cancelTokenRequest() {
+ this.pendingPromiseResolve_({
+ status: toMojoTokenStatus("ErrorCanceled"),
+ selected_identity_provider_config_url: null,
+ token: null
+ });
+ this.pendingPromiseResolve_ = null;
+ }
+
+ // Implements
+ // RequestUserInfo(IdentityProviderGetParameters idp_get_param) =>
+ // (RequestUserInfoStatus status, array<IdentityUserInfo>? user_info);
+ async requestUserInfo(idp_get_param) {
+ return Promise.resolve({
+ status: "",
+ user_info: ""
+ });
+ }
+
+ async logoutRps(logout_endpoints) {
+ return Promise.resolve({
+ status: this.logoutRpsStatus_
+ });
+ }
+
+ async disconnect(provider, client_id, account_id) {
+ return Promise.resolve({
+ status: this.disconnectStatus_
+ });
+ }
+
+ async setIdpSigninStatus(origin, status) {
+ }
+
+ async registerIdP(configURL) {
+ }
+
+ async unregisterIdP(configURL) {
+ }
+
+ async resolveTokenRequest(token) {
+ }
+
+ async closeModalDialogView() {
+ }
+
+ async preventSilentAccess() {
+ }
+
+ async reset() {
+ this.token_ = null;
+ this.selected_identity_provider_config_url_ = null;
+ this.status_ = RequestTokenStatus.kError;
+ this.logoutRpsStatus_ = LogoutRpsStatus.kError;
+ this.disconnectStatus_ = DisconnectStatus.kError;
+ this.receiver_.$.close();
+ this.interceptor_.stop();
+
+ // Clean up and reset mock stubs asynchronously, so that the blink side
+ // closes its proxies and notifies JS sensor objects before new test is
+ // started.
+ await new Promise(resolve => { step_timeout(resolve, 0); });
+ }
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm-mojojs-helper.js b/testing/web-platform/tests/credential-management/support/fedcm-mojojs-helper.js
new file mode 100644
index 0000000000..40ab729b1f
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm-mojojs-helper.js
@@ -0,0 +1,20 @@
+// The testing infra for FedCM is loaded using mojo js shim. To enable these
+// tests the browser must be run with these options:
+//
+// --enable-blink-features=MojoJS,MojoJSTest
+
+import { MockFederatedAuthRequest } from './fedcm-mock.js';
+
+export function fedcm_mojo_mock_test(test_func, name, exception, properties) {
+ promise_test(async (t) => {
+ assert_implements(navigator.credentials, 'missing navigator.credentials');
+ const mock = new MockFederatedAuthRequest();
+ try {
+ await test_func(t, mock);
+ } catch (e) {
+ assert_equals(exception, e.message)
+ } finally {
+ await mock.reset();
+ }
+ }, name, properties);
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/accounts.py b/testing/web-platform/tests/credential-management/support/fedcm/accounts.py
new file mode 100644
index 0000000000..126f911a58
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/accounts.py
@@ -0,0 +1,24 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.accountsCheck(request)
+ if (request_error):
+ return request_error
+
+ response.headers.set(b"Content-Type", b"application/json")
+
+ return """
+{
+ "accounts": [{
+ "id": "1234",
+ "given_name": "John",
+ "name": "John Doe",
+ "email": "john_doe@idp.example",
+ "picture": "https://idp.example/profile/123",
+ "approved_clients": ["123", "456", "789"],
+ "login_hints": ["john_doe"],
+ "domain_hints": ["idp.example", "example"]
+ }]
+}
+"""
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py
new file mode 100644
index 0000000000..72ddcc5cd6
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py
@@ -0,0 +1,25 @@
+# 'import credential-management.support.fedcm.keys' does not work.
+import importlib
+keys = importlib.import_module("credential-management.support.fedcm.keys")
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.clientMetadataCheck(request)
+ if (request_error):
+ return request_error
+
+ counter = request.server.stash.take(keys.CLIENT_METADATA_COUNTER_KEY)
+ try:
+ counter = int(counter) + 1
+ except (TypeError, ValueError):
+ counter = 1
+
+ request.server.stash.put(keys.CLIENT_METADATA_COUNTER_KEY, str(counter).encode())
+
+ response.headers.set(b"Content-Type", b"application/json")
+
+ return """
+{{
+ "privacy_policy_url": "https://privacypolicy{0}.com"
+}}
+""".format(str(counter))
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py.headers b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py.headers
new file mode 100644
index 0000000000..7164e5f818
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata.py.headers
@@ -0,0 +1 @@
+Cache-Control: public, max-age=86400
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/client_metadata_clear_count.py b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata_clear_count.py
new file mode 100644
index 0000000000..3c31bf5077
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/client_metadata_clear_count.py
@@ -0,0 +1,15 @@
+# 'import credential-management.support.fedcm.keys' does not work.
+import importlib
+keys = importlib.import_module("credential-management.support.fedcm.keys")
+
+def main(request, response):
+ client_metadata_url = "/credential-management/support/fedcm/client_metadata.py"
+ counter = request.server.stash.take(keys.CLIENT_METADATA_COUNTER_KEY,
+ client_metadata_url)
+
+ try:
+ counter = counter.decode()
+ except (UnicodeDecodeError, AttributeError):
+ pass
+
+ return counter
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/continue_on.py b/testing/web-platform/tests/credential-management/support/fedcm/continue_on.py
new file mode 100644
index 0000000000..42b4f3f8fd
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/continue_on.py
@@ -0,0 +1,12 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.tokenCheck(request)
+ if (request_error):
+ return request_error
+
+ response.headers.set(b"Content-Type", b"application/json")
+
+ return "{\"continue_on\": \"resolve.html\"}"
+
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/disconnect-iframe.html b/testing/web-platform/tests/credential-management/support/fedcm/disconnect-iframe.html
new file mode 100644
index 0000000000..f65763932b
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/disconnect-iframe.html
@@ -0,0 +1,61 @@
+<!doctype html>
+<script src="/resources/testdriver.js"></script>
+<script src="/resources/testdriver-vendor.js"></script>
+<script type="module">
+ import {disconnect_options,
+ request_options_with_mediation_required,
+ set_fedcm_cookie, manifest_origin} from './../fedcm-helper.sub.js';
+
+// Loading this iframe in the test will make a FedCM call on load, and
+// trigger a postMessage upon completion.
+//
+// message {
+// string result: "Pass" | "Failed get" | "Failed disconnect"
+// string errorType: error.name
+// }
+async function attemptDisconnect() {
+ try {
+ await IdentityCredential.disconnect(disconnect_options("1234"));
+ window.top.postMessage({result: "Pass"}, "*");
+ } catch (error) {
+ window.top.postMessage({result: "Failed disconnect", errorType: error.name},
+ "*");
+ }
+}
+
+window.onload = async () => {
+ const params = new URLSearchParams(document.location.search);
+ if (params.has("skip_get")) {
+ attemptDisconnect();
+ return;
+ }
+
+ // Use this variable to stop trying to select an account once the get()
+ // promise is resolved.
+ let cancelHelper = false;
+ try {
+ const credentialPromise = navigator.credentials.get(request_options_with_mediation_required());
+ async function helper() {
+ try {
+ if (cancelHelper)
+ return;
+
+ await window.test_driver.select_fedcm_account(0);
+ } catch (ex) {
+ setTimeout(helper, 100);
+ }
+ }
+ helper();
+ const cred = await credentialPromise;
+ await set_fedcm_cookie(manifest_origin);
+ // Now that we have a get(), attempt to disconnect permission.
+ attemptDisconnect();
+ } catch (error) {
+ window.top.postMessage({result: "Failed get", errorType: error.name}, '*');
+ }
+ // In case the get() call fails and no accounts may be selected, force the
+ // helper function to stop calling itself.
+ cancelHelper = true;
+};
+</script>
+
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/disconnect.py b/testing/web-platform/tests/credential-management/support/fedcm/disconnect.py
new file mode 100644
index 0000000000..cf62ceda22
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/disconnect.py
@@ -0,0 +1,14 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ response.headers.set(b"Content-Type", b"application/json")
+ response.headers.set(b"Access-Control-Allow-Origin", request.headers.get(b"origin"))
+ response.headers.set(b"Access-Control-Allow-Credentials", b"true")
+ request_error = error_checker.revokeCheck(request)
+ if request_error:
+ return request_error
+
+ # Pass the account_hint as the accountId.
+ account_hint = request.POST.get(b"account_hint")
+ return f"{{\"account_id\": \"{account_hint}\"}}"
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/disconnect_failure.py b/testing/web-platform/tests/credential-management/support/fedcm/disconnect_failure.py
new file mode 100644
index 0000000000..f880218b2f
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/disconnect_failure.py
@@ -0,0 +1,8 @@
+import importlib
+
+def main(request, response):
+ response.headers.set(b"Content-Type", b"application/json")
+ response.headers.set(b"Access-Control-Allow-Origin", request.headers.get(b"origin"))
+ response.headers.set(b"Access-Control-Allow-Credentials", b"true")
+
+ return (599, [], "Server failure")
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/error_with_code_and_url.py b/testing/web-platform/tests/credential-management/support/fedcm/error_with_code_and_url.py
new file mode 100644
index 0000000000..71bfea00f4
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/error_with_code_and_url.py
@@ -0,0 +1,12 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.tokenCheck(request)
+ if (request_error):
+ return request_error
+
+ response.headers.set(b"Content-Type", b"application/json")
+ response.status = (401, b"Unauthorized")
+
+ return "{\"error\": {\"code\": \"unauthorized_client\", \"url\": \"error.html\"}}"
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/intercept_service_worker.js b/testing/web-platform/tests/credential-management/support/fedcm/intercept_service_worker.js
new file mode 100644
index 0000000000..773e38fd21
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/intercept_service_worker.js
@@ -0,0 +1,10 @@
+var num_overridden = 0;
+
+self.addEventListener('fetch', event => {
+ const url = event.request.url;
+ if (url.indexOf('query_service_worker_intercepts.html') != -1) {
+ event.respondWith(new Response(num_overridden));
+ } else if (url.indexOf('credential-management/support') != -1) {
+ ++num_overridden;
+ }
+});
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/keys.py b/testing/web-platform/tests/credential-management/support/fedcm/keys.py
new file mode 100644
index 0000000000..6b7d67e21e
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/keys.py
@@ -0,0 +1,2 @@
+CLIENT_METADATA_COUNTER_KEY = b"bdc14e3e-b8bc-44a1-8eec-78da5fdacbc3"
+MANIFEST_URL_IN_MANIFEST_LIST_KEY = b"7f3f7478-b7f0-41c5-b357-f3ac16f5f25a"
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/login.html b/testing/web-platform/tests/credential-management/support/fedcm/login.html
new file mode 100644
index 0000000000..78d241cda9
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/login.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<script>
+async function doLogin() {
+ document.cookie = "accounts=1";
+ navigator.login.setStatus("logged-in");
+ IdentityProvider.close();
+}
+window.onload = doLogin;
+</script>
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest-not-in-list.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest-not-in-list.json
new file mode 100644
index 0000000000..0070066667
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest-not-in-list.json
@@ -0,0 +1,6 @@
+{
+ "accounts_endpoint": "accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "token.py",
+ "login_url": "login.html"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest.py b/testing/web-platform/tests/credential-management/support/fedcm/manifest.py
new file mode 100644
index 0000000000..a40fc100ee
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest.py
@@ -0,0 +1,19 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.manifestCheck(request)
+ if (request_error):
+ return request_error
+
+ response.headers.set(b"Content-Type", b"application/json")
+
+ return """
+{
+ "accounts_endpoint": "accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "token.py",
+ "disconnect_endpoint": "disconnect.py",
+ "login_url": "login.html"
+}
+"""
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_id_assertion_endpoint_returns_error.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_id_assertion_endpoint_returns_error.json
new file mode 100644
index 0000000000..e098cc4511
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_id_assertion_endpoint_returns_error.json
@@ -0,0 +1,6 @@
+{
+ "accounts_endpoint": "accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "error_with_code_and_url.py",
+ "login_url": "login.html"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_no_login_url.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_no_login_url.json
new file mode 100644
index 0000000000..15a657c679
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_no_login_url.json
@@ -0,0 +1,5 @@
+{
+ "accounts_endpoint": "single_account.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "token_with_account_id.py"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_accounts.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_accounts.json
new file mode 100644
index 0000000000..6a8972feeb
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_accounts.json
@@ -0,0 +1,6 @@
+{
+ "accounts_endpoint": "/common/redirect.py?location=/credential-management/support/fedcm/accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "token.py",
+ "login_url": "login.html"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_token.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_token.json
new file mode 100644
index 0000000000..867b4dffb7
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_redirect_token.json
@@ -0,0 +1,7 @@
+{
+ "accounts_endpoint": "accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "/common/redirect.py?location=/credential-management/support/fedcm/token.py&status=308",
+ "disconnect_endpoint": "disconnect.py",
+ "login_url": "login.html"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_token_with_http_error.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_token_with_http_error.json
new file mode 100644
index 0000000000..691a1e8d3a
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_token_with_http_error.json
@@ -0,0 +1,6 @@
+{
+ "accounts_endpoint": "accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "token_with_http_error.py",
+ "login_url": "login.html"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_auto_selected_flag.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_auto_selected_flag.json
new file mode 100644
index 0000000000..591c927153
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_auto_selected_flag.json
@@ -0,0 +1,6 @@
+{
+ "accounts_endpoint": "two_accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "token_with_auto_selected_flag.py",
+ "login_url": "login.html"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_continue_on.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_continue_on.json
new file mode 100644
index 0000000000..3f5a954b87
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_continue_on.json
@@ -0,0 +1,8 @@
+{
+ "accounts_endpoint": "accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "continue_on.py",
+ "disconnect_endpoint": "disconnect.py",
+ "login_url": "login.html"
+}
+
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_cross_origin_disconnect.sub.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_cross_origin_disconnect.sub.json
new file mode 100644
index 0000000000..a1ad5c71ac
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_cross_origin_disconnect.sub.json
@@ -0,0 +1,7 @@
+{
+ "accounts_endpoint": "accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "token.py",
+ "disconnect_endpoint": "https://{{hosts[alt][]}}:{{ports[https][0]}}/credential-management/support/fedcm/disconnect.py",
+ "login_url": "login.html"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_disconnect_failure.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_disconnect_failure.json
new file mode 100644
index 0000000000..96035e7e8b
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_disconnect_failure.json
@@ -0,0 +1,7 @@
+{
+ "accounts_endpoint": "accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "token.py",
+ "disconnect_endpoint": "disconnect_failure.py",
+ "login_url": "login.html"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_no_accounts.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_no_accounts.json
new file mode 100644
index 0000000000..0d38f26d35
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_no_accounts.json
@@ -0,0 +1,6 @@
+{
+ "accounts_endpoint": "no_accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "token_with_account_id.py",
+ "login_url": "login.html"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_single_account.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_single_account.json
new file mode 100644
index 0000000000..5f9b7a81b9
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_single_account.json
@@ -0,0 +1,6 @@
+{
+ "accounts_endpoint": "single_account.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "token_with_account_id.py",
+ "login_url": "login.html"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_two_accounts.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_two_accounts.json
new file mode 100644
index 0000000000..6310fb0a0b
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_two_accounts.json
@@ -0,0 +1,6 @@
+{
+ "accounts_endpoint": "two_accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "token_with_account_id.py",
+ "login_url": "login.html"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_variable_accounts.json b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_variable_accounts.json
new file mode 100644
index 0000000000..10c2ddd55d
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/manifest_with_variable_accounts.json
@@ -0,0 +1,6 @@
+{
+ "accounts_endpoint": "variable_accounts.py",
+ "client_metadata_endpoint": "client_metadata.py",
+ "id_assertion_endpoint": "token_with_account_id.py",
+ "login_url": "login.html"
+}
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/no_accounts.py b/testing/web-platform/tests/credential-management/support/fedcm/no_accounts.py
new file mode 100644
index 0000000000..8767c50afb
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/no_accounts.py
@@ -0,0 +1,17 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.accountsCheck(request)
+ if (request_error):
+ return request_error
+
+ response.headers.set(b"Content-Type", b"application/json")
+
+ return """
+{
+ "accounts": [
+ ]
+}
+"""
+
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/pending-userinfo-iframe.html b/testing/web-platform/tests/credential-management/support/fedcm/pending-userinfo-iframe.html
new file mode 100644
index 0000000000..0afe279bcc
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/pending-userinfo-iframe.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<script type="module">
+import {alt_manifest_origin} from './../fedcm-helper.sub.js';
+
+// Invokes getUserInfo and immediately sends a message to the parent frame.
+window.onload = async () => {
+ try {
+ const manifest_path = `${alt_manifest_origin}/\
+credential-management/support/fedcm/manifest.py`;
+ IdentityProvider.getUserInfo({
+ configURL: manifest_path,
+ // Approved client
+ clientId: '123',
+ });
+ window.top.postMessage("Pass", '*');
+ } catch (error) {
+ window.top.postMessage("Fail", '*');
+ }
+};
+
+</script>
+
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/request-params-check.py b/testing/web-platform/tests/credential-management/support/fedcm/request-params-check.py
new file mode 100644
index 0000000000..daf91aad8f
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/request-params-check.py
@@ -0,0 +1,101 @@
+def commonCheck(request, mode=b"no-cors"):
+ if request.headers.get(b"Accept") != b"application/json":
+ return (531, [], "Wrong Accept")
+ if request.headers.get(b"Sec-Fetch-Dest") != b"webidentity":
+ return (532, [], "Wrong Sec-Fetch-Dest header")
+ if request.headers.get(b"Referer"):
+ return (533, [], "Should not have Referer")
+ if request.headers.get(b"Sec-Fetch-Mode") != mode:
+ return (534, [], "Wrong Sec-Fetch-Mode header")
+
+def commonUncredentialedRequestCheck(request):
+ if len(request.cookies) > 0:
+ return (535, [], "Cookie should not be sent to this endpoint")
+ if request.headers.get(b"Sec-Fetch-Site") != b"cross-site":
+ return (536, [], "Wrong Sec-Fetch-Site header")
+
+def commonCredentialedRequestCheck(request):
+ if request.cookies.get(b"cookie") != b"1":
+ return (537, [], "Missing cookie")
+ if request.headers.get(b"Sec-Fetch-Site") != b"none":
+ return (538, [], "Wrong Sec-Fetch-Site header")
+
+def commonPostCheck(request):
+ if not request.headers.get(b"Origin"):
+ return (540, [], "Missing Origin")
+ if request.method != "POST":
+ return (541, [], "Method is not POST")
+ if request.headers.get(b"Content-Type") != b"application/x-www-form-urlencoded":
+ return (542, [], "Wrong Content-Type")
+ if not request.POST.get(b"client_id"):
+ return (543, [], "Missing 'client_id' POST parameter")
+
+def manifestCheck(request):
+ common_error = commonCheck(request)
+ if (common_error):
+ return common_error
+ common_uncredentialed_error = commonUncredentialedRequestCheck(request)
+ if (common_uncredentialed_error):
+ return common_uncredentialed_error
+
+ if request.headers.get(b"Origin"):
+ return (539, [], "Should not have Origin")
+
+def clientMetadataCheck(request):
+ if (request.GET.get(b'skip_checks', b'0') != b'1'):
+ common_error = commonCheck(request)
+ if (common_error):
+ return common_error
+ common_uncredentialed_error = commonUncredentialedRequestCheck(request)
+ if (common_uncredentialed_error):
+ return common_uncredentialed_error
+
+ if not request.headers.get(b"Origin"):
+ return (540, [], "Missing Origin")
+
+def accountsCheck(request):
+ common_error = commonCheck(request)
+ if (common_error):
+ return common_error
+ common_credentialed_error = commonCredentialedRequestCheck(request)
+ if (common_credentialed_error):
+ return common_credentialed_error
+
+ if request.headers.get(b"Origin"):
+ return (539, [], "Should not have Origin")
+
+def tokenCheck(request):
+ common_error = commonCheck(request)
+ if (common_error):
+ return common_error
+ common_credentialed_error = commonCredentialedRequestCheck(request)
+ if (common_credentialed_error):
+ return common_credentialed_error
+
+ post_error = commonPostCheck(request)
+ if (post_error):
+ return post_error
+
+ if not request.POST.get(b"account_id"):
+ return (544, [], "Missing 'account_id' POST parameter")
+ if not request.POST.get(b"disclosure_text_shown"):
+ return (545, [], "Missing 'disclosure_text_shown' POST parameter")
+
+def revokeCheck(request):
+ common_error = commonCheck(request, b"cors")
+ if (common_error):
+ return common_error
+
+ if request.cookies.get(b"cookie") != b"1":
+ return (537, [], "Missing cookie")
+ # The value of the Sec-Fetch-Site header can vary depending on the IdP origin
+ # but it should not be 'none'.
+ if request.headers.get(b"Sec-Fetch-Site") == b"none":
+ return (538, [], "Wrong Sec-Fetch-Site header")
+
+ post_error = commonPostCheck(request)
+ if (post_error):
+ return post_error
+
+ if not request.POST.get(b"account_hint"):
+ return (544, [], "Missing 'account_hint' POST parameter")
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/resolve.html b/testing/web-platform/tests/credential-management/support/fedcm/resolve.html
new file mode 100644
index 0000000000..87f5112cfd
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/resolve.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<script>
+async function doResolve() {
+ IdentityProvider.resolve("resolved token");
+}
+window.onload = doResolve;
+</script>
+
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/select_manifest_in_root_manifest.py b/testing/web-platform/tests/credential-management/support/fedcm/select_manifest_in_root_manifest.py
new file mode 100644
index 0000000000..d4f1efff6a
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/select_manifest_in_root_manifest.py
@@ -0,0 +1,17 @@
+import importlib
+from urllib.parse import urlsplit
+
+# 'import credential-management.support.fedcm.keys' does not work.
+keys = importlib.import_module("credential-management.support.fedcm.keys")
+
+def main(request, response):
+ root_manifest_url = "/.well-known/web-identity"
+
+ # Clear stash so that a new value can be written.
+ request.server.stash.take(keys.MANIFEST_URL_IN_MANIFEST_LIST_KEY, root_manifest_url)
+
+ request.server.stash.put(keys.MANIFEST_URL_IN_MANIFEST_LIST_KEY,
+ request.GET.first(b"manifest_url", b""),
+ root_manifest_url)
+
+ return root_manifest_url
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/simple.html b/testing/web-platform/tests/credential-management/support/fedcm/simple.html
new file mode 100644
index 0000000000..d62419ce8a
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/simple.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<html><body>
+Simple
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/single_account.py b/testing/web-platform/tests/credential-management/support/fedcm/single_account.py
new file mode 100644
index 0000000000..7c8906ae7b
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/single_account.py
@@ -0,0 +1,24 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.accountsCheck(request)
+ if (request_error):
+ return request_error
+
+ response.headers.set(b"Content-Type", b"application/json")
+
+ return """
+{
+ "accounts": [
+ {
+ "id": "john_doe",
+ "given_name": "John",
+ "name": "John Doe",
+ "email": "john_doe@idp.example",
+ "picture": "https://idp.example/profile/123",
+ "approved_clients": ["123", "456", "789"]
+ }
+ ]
+}
+"""
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/token.py b/testing/web-platform/tests/credential-management/support/fedcm/token.py
new file mode 100644
index 0000000000..b914eb2d96
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/token.py
@@ -0,0 +1,11 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.tokenCheck(request)
+ if (request_error):
+ return request_error
+
+ response.headers.set(b"Content-Type", b"application/json")
+
+ return "{\"token\": \"token\"}"
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/token_with_account_id.py b/testing/web-platform/tests/credential-management/support/fedcm/token_with_account_id.py
new file mode 100644
index 0000000000..52fb20184b
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/token_with_account_id.py
@@ -0,0 +1,12 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.tokenCheck(request)
+ if (request_error):
+ return request_error
+
+ response.headers.set(b"Content-Type", b"application/json")
+
+ account_id = request.POST.get(b"account_id")
+ return "{\"token\": \"account_id=" + account_id.decode("utf-8") + "\"}"
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/token_with_auto_selected_flag.py b/testing/web-platform/tests/credential-management/support/fedcm/token_with_auto_selected_flag.py
new file mode 100644
index 0000000000..93ccf3ee7e
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/token_with_auto_selected_flag.py
@@ -0,0 +1,12 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.tokenCheck(request)
+ if (request_error):
+ return request_error
+
+ response.headers.set(b"Content-Type", b"application/json")
+
+ is_auto_selected = request.POST.get(b"is_auto_selected")
+ return "{\"token\": \"is_auto_selected=" + is_auto_selected.decode("utf-8") + "\"}"
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/token_with_http_error.py b/testing/web-platform/tests/credential-management/support/fedcm/token_with_http_error.py
new file mode 100644
index 0000000000..c8d95ab63d
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/token_with_http_error.py
@@ -0,0 +1,12 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.tokenCheck(request)
+ if (request_error):
+ return request_error
+
+ response.headers.set(b"Content-Type", b"application/json")
+ response.status = (403, b"Forbidden")
+
+ return "{\"token\": \"token\"}"
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/two_accounts.py b/testing/web-platform/tests/credential-management/support/fedcm/two_accounts.py
new file mode 100644
index 0000000000..4022561ff7
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/two_accounts.py
@@ -0,0 +1,34 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.accountsCheck(request)
+ if (request_error):
+ return request_error
+
+ response.headers.set(b"Content-Type", b"application/json")
+
+ return """
+{
+ "accounts": [
+ {
+ "id": "jane_doe",
+ "given_name": "Jane",
+ "name": "Jane Doe",
+ "email": "jane_doe@idp.example",
+ "picture": "https://idp.example/profile/5678",
+ "approved_clients": ["123", "abc"]
+ },
+ {
+ "id": "john_doe",
+ "given_name": "John",
+ "name": "John Doe",
+ "email": "john_doe@idp.example",
+ "picture": "https://idp.example/profile/123",
+ "approved_clients": ["123", "456", "789"],
+ "login_hints": ["john_doe"],
+ "domain_hints": ["idp.example", "example"]
+ }
+ ]
+}
+"""
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/userinfo-iframe.html b/testing/web-platform/tests/credential-management/support/fedcm/userinfo-iframe.html
new file mode 100644
index 0000000000..45a1a34ce9
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/userinfo-iframe.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<script type="module">
+import {alt_manifest_origin} from './../fedcm-helper.sub.js';
+
+// Loading fedcm-iframe.html in the test will make a FedCM call on load, and
+// trigger a postMessage upon completion.
+//
+// message {
+// string result: "Pass" | "Fail"
+// string token: token.token
+// string errorType: error.name
+// }
+window.onload = async () => {
+ try {
+ const manifest_path = `${alt_manifest_origin}/\
+credential-management/support/fedcm/manifest.py`;
+ const user_info = await IdentityProvider.getUserInfo({
+ configURL: manifest_path,
+ // Approved client
+ clientId: '123',
+ });
+ let results = {
+ result: "Pass",
+ numAccounts: user_info.length,
+ firstAccountEmail: user_info[0].email,
+ firstAccountName: user_info[0].name,
+ firstAccountGivenName: user_info[0].givenName,
+ firstAccountPicture: user_info[0].picture
+ };
+ window.top.postMessage(results, '*');
+ } catch (error) {
+ window.top.postMessage({result: "Fail", errorType: error.name}, '*');
+ }
+};
+
+</script>
+
diff --git a/testing/web-platform/tests/credential-management/support/fedcm/variable_accounts.py b/testing/web-platform/tests/credential-management/support/fedcm/variable_accounts.py
new file mode 100644
index 0000000000..c9db2c4528
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fedcm/variable_accounts.py
@@ -0,0 +1,33 @@
+import importlib
+error_checker = importlib.import_module("credential-management.support.fedcm.request-params-check")
+
+def main(request, response):
+ request_error = error_checker.accountsCheck(request)
+ if (request_error):
+ return request_error
+
+ response.headers.set(b"Content-Type", b"application/json")
+
+ if request.cookies.get(b"accounts") != b"1":
+ return """
+{
+ "accounts": [
+ ]
+}
+"""
+
+
+ return """
+{
+ "accounts": [{
+ "id": "1234",
+ "given_name": "John",
+ "name": "John Doe",
+ "email": "john_doe@idp.example",
+ "picture": "https://idp.example/profile/123",
+ "approved_clients": ["123", "456", "789"],
+ "login_hints": ["john_doe"],
+ "hosted_domains": ["idp.example", "example"]
+ }]
+}
+"""
diff --git a/testing/web-platform/tests/credential-management/support/federatedcredential-get.html b/testing/web-platform/tests/credential-management/support/federatedcredential-get.html
new file mode 100644
index 0000000000..476f32688f
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/federatedcredential-get.html
@@ -0,0 +1,17 @@
+<script>
+ navigator.credentials.get({ 'federated': { 'providers': ['https://example.com' ] } })
+ .then(c => {
+ window.parent.postMessage({
+ "status": "resolved",
+ "credential": c,
+ "exception": null
+ }, "*");
+ })
+ .catch(omg => {
+ window.parent.postMessage({
+ "status": "rejected",
+ "credential": null,
+ "exception": omg.name
+ }, "*");
+ });
+</script>
diff --git a/testing/web-platform/tests/credential-management/support/fencedframe-mark-signedin.html b/testing/web-platform/tests/credential-management/support/fencedframe-mark-signedin.html
new file mode 100644
index 0000000000..532db7047a
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/fencedframe-mark-signedin.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<title>A page that uses fencedframe to set the login status to signed in</title>
+
+<fencedframe></fencedframe>
+<script>
+const url = new URL("mark_signedin", location.href);
+document.querySelector("fencedframe").config = new FencedFrameConfig(url);
+
+// If this page was opened as a popup, notify the opener when we are done loading.
+if (window.opener) {
+ window.onload = function() {
+ window.opener.postMessage("done_loading", "*");
+ };
+}
+</script>
diff --git a/testing/web-platform/tests/credential-management/support/iframe-mark-signedin.html b/testing/web-platform/tests/credential-management/support/iframe-mark-signedin.html
new file mode 100644
index 0000000000..4ca0125cde
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/iframe-mark-signedin.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<title>A page that includes mark_signedin, for use in an iframe</title>
+
+<img src="mark_signedin">
diff --git a/testing/web-platform/tests/credential-management/support/mark_signedin b/testing/web-platform/tests/credential-management/support/mark_signedin
new file mode 100644
index 0000000000..d9adcaa762
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/mark_signedin
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+// The important part of this page are the headers.
+
+// If this page was opened as a popup, notify the opener.
+if (window.opener) {
+ window.opener.postMessage("done_loading", "*");
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/credential-management/support/mark_signedin.sub.headers b/testing/web-platform/tests/credential-management/support/mark_signedin.sub.headers
new file mode 100644
index 0000000000..d560fade5a
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/mark_signedin.sub.headers
@@ -0,0 +1,5 @@
+Content-Type: text/html
+Set-Login: logged-in
+Access-Control-Allow-Origin: https://{{host}}:{{ports[https][0]}}
+Access-Control-Allow-Credentials: true
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/credential-management/support/mark_signedout b/testing/web-platform/tests/credential-management/support/mark_signedout
new file mode 100644
index 0000000000..d9adcaa762
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/mark_signedout
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<body>
+<script>
+// The important part of this page are the headers.
+
+// If this page was opened as a popup, notify the opener.
+if (window.opener) {
+ window.opener.postMessage("done_loading", "*");
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/credential-management/support/mark_signedout.sub.headers b/testing/web-platform/tests/credential-management/support/mark_signedout.sub.headers
new file mode 100644
index 0000000000..69157b3a37
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/mark_signedout.sub.headers
@@ -0,0 +1,5 @@
+Content-Type: text/html
+Set-Login: logged-out
+Access-Control-Allow-Origin: https://{{host}}:{{ports[https][0]}}
+Access-Control-Allow-Credentials: true
+Supports-Loading-Mode: fenced-frame
diff --git a/testing/web-platform/tests/credential-management/support/otpcredential-helper.js b/testing/web-platform/tests/credential-management/support/otpcredential-helper.js
new file mode 100644
index 0000000000..e07e9f5be3
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/otpcredential-helper.js
@@ -0,0 +1,114 @@
+// These tests rely on the User Agent providing an implementation of
+// MockWebOTPService.
+//
+// In Chromium-based browsers this implementation is provided by a polyfill
+// in order to reduce the amount of test-only code shipped to users. To enable
+// these tests the browser must be run with these options:
+// // --enable-blink-features=MojoJS,MojoJSTest
+
+import {isChromiumBased} from '/resources/test-only-api.m.js';
+
+/**
+ * This enumeration is used by WebOTP WPTs to control mock backend behavior.
+ * See MockWebOTPService below.
+ */
+export const Status = {
+ SUCCESS: 0,
+ UNHANDLED_REQUEST: 1,
+ CANCELLED: 2,
+ ABORTED: 3,
+};
+
+/**
+ * A interface which must be implemented by browsers to support WebOTP WPTs.
+ */
+export class MockWebOTPService {
+ /**
+ * Accepts a function to be invoked in response to the next OTP request
+ * received by the mock. The (optionally async) function, when executed, must
+ * return an object with a `status` field holding one of the `Status` values
+ * defined above, and -- if successful -- an `otp` field containing a
+ * simulated OTP string.
+ *
+ * Tests will call this method directly to inject specific response behavior
+ * into the browser-specific mock implementation.
+ */
+ async handleNextOTPRequest(responseFunc) {}
+}
+
+/**
+ * Returns a Promise resolving to a browser-specific MockWebOTPService subclass
+ * instance if one is available.
+ */
+async function createBrowserSpecificMockImpl() {
+ if (isChromiumBased) {
+ return await createChromiumMockImpl();
+ }
+ throw new Error('Unsupported browser.');
+}
+
+const asyncMock = createBrowserSpecificMockImpl();
+
+export function expectOTPRequest() {
+ return {
+ async andReturn(callback) {
+ const mock = await asyncMock;
+ mock.handleNextOTPRequest(callback);
+ }
+ }
+}
+
+/**
+ * Instantiates a Chromium-specific subclass of MockWebOTPService.
+ */
+async function createChromiumMockImpl() {
+ const {SmsStatus, WebOTPService, WebOTPServiceReceiver} = await import(
+ '/gen/third_party/blink/public/mojom/sms/webotp_service.mojom.m.js');
+ const MockWebOTPServiceChromium = class extends MockWebOTPService {
+ constructor() {
+ super();
+ this.mojoReceiver_ = new WebOTPServiceReceiver(this);
+ this.interceptor_ =
+ new MojoInterfaceInterceptor(WebOTPService.$interfaceName);
+ this.interceptor_.oninterfacerequest = (e) => {
+ this.mojoReceiver_.$.bindHandle(e.handle);
+ };
+ this.interceptor_.start();
+ this.requestHandlers_ = [];
+ Object.freeze(this);
+ }
+
+ handleNextOTPRequest(responseFunc) {
+ this.requestHandlers_.push(responseFunc);
+ }
+
+ async receive() {
+ if (this.requestHandlers_.length == 0) {
+ throw new Error('Mock received unexpected OTP request.');
+ }
+
+ const responseFunc = this.requestHandlers_.shift();
+ const response = await responseFunc();
+ switch (response.status) {
+ case Status.SUCCESS:
+ if (typeof response.otp != 'string') {
+ throw new Error('Mock success results require an OTP string.');
+ }
+ return {status: SmsStatus.kSuccess, otp: response.otp};
+ case Status.UNHANDLED_REQUEST:
+ return {status: SmsStatus.kUnhandledRequest};
+ case Status.CANCELLED:
+ return {status: SmsStatus.kCancelled};
+ case Status.ABORTED:
+ return {status: SmsStatus.kAborted};
+ default:
+ throw new Error(
+ `Mock result contains unknown status: ${response.status}`);
+ }
+ }
+
+ async abort() {}
+ };
+ return new MockWebOTPServiceChromium();
+}
+
diff --git a/testing/web-platform/tests/credential-management/support/otpcredential-iframe.html b/testing/web-platform/tests/credential-management/support/otpcredential-iframe.html
new file mode 100644
index 0000000000..4affc00ecd
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/otpcredential-iframe.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<script type="module">
+import {Status, expectOTPRequest} from './otpcredential-helper.js';
+
+// Loading otpcredential-iframe.html in the test will make an OTPCredentials
+// call on load, and trigger a postMessage upon completion.
+//
+// message {
+// string result: "Pass" | "Fail"
+// string code: credentials.code
+// string errorType: error.name
+// }
+
+window.onload = async () => {
+ try {
+ await expectOTPRequest().andReturn(
+ () => ({status: Status.SUCCESS, otp: "ABC123"}));
+ const credentials =
+ await navigator.credentials.get({otp: {transport: ["sms"]}});
+ window.parent.postMessage({result: "Pass", code: credentials.code}, '*');
+ } catch (error) {
+ window.parent.postMessage({result: "Fail", errorType: error.name}, '*');
+ }
+}
+
+</script>
diff --git a/testing/web-platform/tests/credential-management/support/passwordcredential-get.html b/testing/web-platform/tests/credential-management/support/passwordcredential-get.html
new file mode 100644
index 0000000000..0ec584d73d
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/passwordcredential-get.html
@@ -0,0 +1,17 @@
+<script>
+ navigator.credentials.get({ 'password': true })
+ .then(c => {
+ window.parent.postMessage({
+ "status": "resolved",
+ "credential": c,
+ "exception": null
+ }, "*");
+ })
+ .catch(omg => {
+ window.parent.postMessage({
+ "status": "rejected",
+ "credential": null,
+ "exception": omg.name
+ }, "*");
+ });
+</script>
diff --git a/testing/web-platform/tests/credential-management/support/set_cookie b/testing/web-platform/tests/credential-management/support/set_cookie
new file mode 100644
index 0000000000..1080b366e4
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/set_cookie
@@ -0,0 +1,12 @@
+<html>
+<body>
+<script>
+// The important part of this page are the headers.
+
+// If this page was opened as a popup, notify the opener.
+if (window.opener) {
+ window.opener.postMessage("done_loading", "*");
+}
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/credential-management/support/set_cookie.headers b/testing/web-platform/tests/credential-management/support/set_cookie.headers
new file mode 100644
index 0000000000..b19ff933a6
--- /dev/null
+++ b/testing/web-platform/tests/credential-management/support/set_cookie.headers
@@ -0,0 +1,2 @@
+Content-Type: text/html
+Set-Cookie: cookie=1; SameSite=None; Secure