diff --git a/common/src/web/fedcm/accounts.json b/common/src/web/fedcm/accounts.json
new file mode 100644
index 0000000000000..beea4d1887975
--- /dev/null
+++ b/common/src/web/fedcm/accounts.json
@@ -0,0 +1,17 @@
+{
+ "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"]
+ }, {
+ "id": "5678",
+ "given_name": "Aisha",
+ "name": "Aisha Ahmad",
+ "email": "aisha@idp.example",
+ "picture": "https://idp.example/profile/567",
+ "approved_clients": []
+ }]
+}
diff --git a/common/src/web/fedcm/client_metadata.json b/common/src/web/fedcm/client_metadata.json
new file mode 100644
index 0000000000000..ddde867a9f7bf
--- /dev/null
+++ b/common/src/web/fedcm/client_metadata.json
@@ -0,0 +1,4 @@
+{
+ "privacy_policy_url": "https://rp.example/privacy_policy.html",
+ "terms_of_service_url": "https://rp.example/terms_of_service.html"
+}
diff --git a/common/src/web/fedcm/fedcm.html b/common/src/web/fedcm/fedcm.html
new file mode 100644
index 0000000000000..186d9311c70b8
--- /dev/null
+++ b/common/src/web/fedcm/fedcm.html
@@ -0,0 +1,20 @@
+
+
diff --git a/common/src/web/fedcm/fedcm.json b/common/src/web/fedcm/fedcm.json
new file mode 100644
index 0000000000000..2512240f1e942
--- /dev/null
+++ b/common/src/web/fedcm/fedcm.json
@@ -0,0 +1,6 @@
+{
+ "accounts_endpoint": "accounts.json",
+ "client_metadata_endpoint": "client_metadata.json",
+ "id_assertion_endpoint": "id_assertion",
+ "signin_url": "/signin"
+}
diff --git a/java/src/org/openqa/selenium/BUILD.bazel b/java/src/org/openqa/selenium/BUILD.bazel
index d94215af87b8e..1e494729adfe6 100644
--- a/java/src/org/openqa/selenium/BUILD.bazel
+++ b/java/src/org/openqa/selenium/BUILD.bazel
@@ -12,6 +12,7 @@ java_export(
name = "core",
srcs = glob([
"*.java",
+ "federatedcredentialmanagement/*.java",
"html5/*.java",
"internal/*.java",
"interactions/**/*.java",
diff --git a/java/src/org/openqa/selenium/federatedcredentialmanagement/FederatedCredentialManagementAccount.java b/java/src/org/openqa/selenium/federatedcredentialmanagement/FederatedCredentialManagementAccount.java
new file mode 100644
index 0000000000000..e24fe742af06f
--- /dev/null
+++ b/java/src/org/openqa/selenium/federatedcredentialmanagement/FederatedCredentialManagementAccount.java
@@ -0,0 +1,101 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.openqa.selenium.federatedcredentialmanagement;
+
+import java.util.Map;
+
+/**
+ * Represents an account displayed in a FedCM account list.
+ *
+ * @see
+ * https://fedidcg.github.io/FedCM/#dictdef-identityprovideraccount
+ * @see
+ * https://fedidcg.github.io/FedCM/#webdriver-accountlist
+ */
+public class FederatedCredentialManagementAccount {
+ private final String accountId;
+ private final String email;
+ private final String name;
+ private final String givenName;
+ private final String pictureUrl;
+ /**
+ * The config URL of the identity provider that provided this account.
+ *
+ * This allows identifying the IDP in multi-IDP cases.
+ */
+ private final String idpConfigUrl;
+ /**
+ * The login state for this account.
+ *
+ * One of LOGIN_STATE_SIGNIN and LOGIN_STATE_SIGNUP.
+ */
+ private final String loginState;
+ private final String termsOfServiceUrl;
+ private final String privacyPolicyUrl;
+
+ public static final String LOGIN_STATE_SIGNIN = "SignIn";
+ public static final String LOGIN_STATE_SIGNUP = "SignUp";
+
+ public FederatedCredentialManagementAccount(Map dict) {
+ accountId = (String) dict.getOrDefault("accountId", null);
+ email = (String) dict.getOrDefault("email", null);
+ name = (String) dict.getOrDefault("name", null);
+ givenName = (String) dict.getOrDefault("givenName", null);
+ pictureUrl = (String) dict.getOrDefault("pictureUrl", null);
+ idpConfigUrl = (String) dict.getOrDefault("idpConfigUrl", null);
+ loginState = (String) dict.getOrDefault("loginState", null);
+ termsOfServiceUrl = (String) dict.getOrDefault("termsOfServiceUrl", null);
+ privacyPolicyUrl = (String) dict.getOrDefault("privacyPolicyUrl", null);
+ }
+
+ public String getAccountid() {
+ return accountId;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getGivenName() {
+ return givenName;
+ }
+
+ public String getPictureUrl() {
+ return pictureUrl;
+ }
+
+ public String getIdpConfigUrl() {
+ return idpConfigUrl;
+ }
+
+ public String getLoginState() {
+ return loginState;
+ }
+
+ public String getTermsOfServiceUrl() {
+ return termsOfServiceUrl;
+ }
+
+ public String getPrivacyPolicyUrl() {
+ return privacyPolicyUrl;
+ }
+}
diff --git a/java/src/org/openqa/selenium/federatedcredentialmanagement/FederatedCredentialManagementDialog.java b/java/src/org/openqa/selenium/federatedcredentialmanagement/FederatedCredentialManagementDialog.java
new file mode 100644
index 0000000000000..57abcc9d6328f
--- /dev/null
+++ b/java/src/org/openqa/selenium/federatedcredentialmanagement/FederatedCredentialManagementDialog.java
@@ -0,0 +1,69 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.openqa.selenium.federatedcredentialmanagement;
+
+import java.util.List;
+
+/**
+ * Represents an open dialog of the Federated Credential Management API.
+ *
+ * @see https://fedidcg.github.io/FedCM/
+ */
+public interface FederatedCredentialManagementDialog {
+
+ String DIALOG_TYPE_ACCOUNT_LIST = "AccountChooser";
+ String DIALOG_TYPE_AUTO_REAUTH = "AutoReauthn";
+
+ /**
+ * Closes the dialog as if the user had clicked X.
+ */
+ void cancelDialog();
+
+ /**
+ * Selects an account as if the user had clicked on it.
+ *
+ * @param index The index of the account to select from the list
+ * returned by getAccounts().
+ */
+ void selectAccount(int index);
+
+ /**
+ * Returns the type of the open dialog.
+ *
+ * One of DIALOG_TYPE_ACCOUNT_LIST and DIALOG_TYPE_AUTO_REAUTH.
+ */
+ String getDialogType();
+
+ /**
+ * Returns the title of the dialog.
+ */
+ String getTitle();
+
+ /**
+ * Returns the subtitle of the dialog or null if none.
+ */
+ String getSubtitle();
+
+ /**
+ * Returns the accounts shown in the account chooser.
+ *
+ * If this is an auto reauth dialog, returns the single account
+ * that is being signed in.
+ */
+ List getAccounts();
+}
diff --git a/java/src/org/openqa/selenium/federatedcredentialmanagement/HasFederatedCredentialManagement.java b/java/src/org/openqa/selenium/federatedcredentialmanagement/HasFederatedCredentialManagement.java
new file mode 100644
index 0000000000000..2d20cc1ec204c
--- /dev/null
+++ b/java/src/org/openqa/selenium/federatedcredentialmanagement/HasFederatedCredentialManagement.java
@@ -0,0 +1,55 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.openqa.selenium.federatedcredentialmanagement;
+
+import org.openqa.selenium.Beta;
+
+/**
+ * Used by classes to indicate that they can interact with FedCM dialogs.
+ */
+@Beta
+public interface HasFederatedCredentialManagement {
+ /**
+ * Disables the promise rejection delay.
+ *
+ * FedCM by default delays promise resolution in failure cases for privacy
+ * reasons (https://fedidcg.github.io/FedCM/#ref-for-setdelayenabled);
+ * this function allows turning it off to let tests run faster where this
+ * is not relevant.
+ */
+ void setDelayEnabled(boolean enabled);
+
+ /**
+ * Resets the FedCM dialog cooldown.
+ *
+ * If a user agent triggers a cooldown when the account chooser is dismissed,
+ * this function resets that cooldown so that the dialog can be triggered
+ * again immediately.
+ */
+ void resetCooldown();
+
+ /**
+ * Gets the currently open FedCM dialog, or null if there is no dialog.
+ *
+ * Can be used with WebDriverWait like:
+ * wait.until(driver -> ((HasFederatedCredentialManagement) driver).
+ * getFederatedCredentialManagementDialog() != null);
+ */
+ FederatedCredentialManagementDialog getFederatedCredentialManagementDialog();
+}
+
diff --git a/java/src/org/openqa/selenium/grid/web/ResourceHandler.java b/java/src/org/openqa/selenium/grid/web/ResourceHandler.java
index b514a49c26b08..ac55ddc2391ad 100644
--- a/java/src/org/openqa/selenium/grid/web/ResourceHandler.java
+++ b/java/src/org/openqa/selenium/grid/web/ResourceHandler.java
@@ -22,6 +22,7 @@
import static com.google.common.net.MediaType.GIF;
import static com.google.common.net.MediaType.HTML_UTF_8;
import static com.google.common.net.MediaType.JAVASCRIPT_UTF_8;
+import static com.google.common.net.MediaType.JSON_UTF_8;
import static com.google.common.net.MediaType.JPEG;
import static com.google.common.net.MediaType.OCTET_STREAM;
import static com.google.common.net.MediaType.PLAIN_TEXT_UTF_8;
@@ -160,6 +161,10 @@ private String mediaType(String uri) {
type = JAVASCRIPT_UTF_8;
break;
+ case "json":
+ type = JSON_UTF_8;
+ break;
+
case "md":
case "txt":
type = PLAIN_TEXT_UTF_8;
diff --git a/java/src/org/openqa/selenium/remote/DriverCommand.java b/java/src/org/openqa/selenium/remote/DriverCommand.java
index 993129d30bea7..3d6c90ab8ae5c 100644
--- a/java/src/org/openqa/selenium/remote/DriverCommand.java
+++ b/java/src/org/openqa/selenium/remote/DriverCommand.java
@@ -162,6 +162,15 @@ public interface DriverCommand {
String REMOVE_CREDENTIAL = "removeCredential";
String REMOVE_ALL_CREDENTIALS = "removeAllCredentials";
String SET_USER_VERIFIED = "setUserVerified";
+ // Federated Credential Management API
+ // https://fedidcg.github.io/FedCM/#automation
+ String CANCEL_DIALOG = "cancelDialog";
+ String SELECT_ACCOUNT = "selectAccount";
+ String GET_ACCOUNTS = "getAccounts";
+ String GET_FEDCM_TITLE = "getFedCmTitle";
+ String GET_FEDCM_DIALOG_TYPE = "getFedCmDialogType";
+ String SET_DELAY_ENABLED = "setDelayEnabled";
+ String RESET_COOLDOWN = "resetCooldown";
static CommandPayload NEW_SESSION(Capabilities capabilities) {
Require.nonNull("Capabilities", capabilities);
@@ -401,4 +410,14 @@ static CommandPayload SET_CURRENT_WINDOW_SIZE(Dimension targetSize) {
SET_CURRENT_WINDOW_SIZE,
ImmutableMap.of("width", targetSize.width, "height", targetSize.height));
}
+
+ static CommandPayload SELECT_ACCOUNT(int index) {
+ return new CommandPayload(
+ SELECT_ACCOUNT, ImmutableMap.of("accountIndex", index));
+ }
+
+ static CommandPayload SET_DELAY_ENABLED(boolean enabled) {
+ return new CommandPayload(
+ SET_DELAY_ENABLED, ImmutableMap.of("enabled", enabled));
+ }
}
diff --git a/java/src/org/openqa/selenium/remote/FedCmDialogImpl.java b/java/src/org/openqa/selenium/remote/FedCmDialogImpl.java
new file mode 100644
index 0000000000000..4756a376a34a0
--- /dev/null
+++ b/java/src/org/openqa/selenium/remote/FedCmDialogImpl.java
@@ -0,0 +1,73 @@
+// Licensed to the Software Freedom Conservancy (SFC) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The SFC licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.openqa.selenium.remote;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import com.google.common.collect.ImmutableMap;
+import org.openqa.selenium.federatedcredentialmanagement.FederatedCredentialManagementAccount;
+import org.openqa.selenium.federatedcredentialmanagement.FederatedCredentialManagementDialog;
+import org.openqa.selenium.remote.DriverCommand;
+import org.openqa.selenium.remote.ExecuteMethod;
+
+class FedCmDialogImpl implements FederatedCredentialManagementDialog {
+ private final ExecuteMethod executeMethod;
+
+ FedCmDialogImpl(ExecuteMethod executeMethod) {
+ this.executeMethod = executeMethod;
+ }
+
+ @Override
+ public void cancelDialog() {
+ executeMethod.execute(DriverCommand.CANCEL_DIALOG, null);
+ }
+
+ @Override
+ public void selectAccount(int index) {
+ executeMethod.execute(DriverCommand.SELECT_ACCOUNT, ImmutableMap.of("accountIndex", index));
+ }
+
+ @Override
+ public String getDialogType() {
+ return (String) executeMethod.execute(DriverCommand.GET_FEDCM_DIALOG_TYPE, null);
+ }
+
+ @Override
+ public String getTitle() {
+ Map result = (Map) executeMethod.execute(DriverCommand.GET_FEDCM_TITLE, null);
+ return (String) result.getOrDefault("title", null);
+ }
+
+ @Override
+ public String getSubtitle() {
+ Map result = (Map) executeMethod.execute(DriverCommand.GET_FEDCM_TITLE, null);
+ return (String) result.getOrDefault("subtitle", null);
+ }
+
+ @Override
+ public List getAccounts() {
+ List