diff --git a/spec/index.bs b/spec/index.bs index e27d516eb..7ed255d42 100644 --- a/spec/index.bs +++ b/spec/index.bs @@ -453,6 +453,11 @@ NOTE: The {{CredentialRequestOptions/mediation}} flag is currently not used. The {{CredentialRequestOptions/signal}} is used as an abort signal for the requests. +Each {{Document}} has an associated pending fedID request list, an initially empty [=list=]. + +Each {{Document}} has an associated fedID task status, which is initially set to +"[=no task=]". Its possible values are "no task", "pending", or "running". +
When the {{IdentityCredential}}'s \[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors) @@ -461,9 +466,67 @@ algorithm is invoked, the user agent MUST execute the following steps. This retu 1. Assert: These steps are running [=in parallel=]. 1. Assert: |options|["{{CredentialRequestOptions/identity}}"]["{{IdentityCredentialRequestOptions/providers}}"] [=map/exists=]. - 1. Assert: |options|["{{CredentialRequestOptions/identity}}"]["{{IdentityCredentialRequestOptions/providers}}"] [=list/size=] is 1. + 1. Let |document| be |globalObject|'s [=associated Document=]. - Issue: Support choosing accounts from multiple [=IDP=]s, as described [here](https://github.com/fedidcg/FedCM/issues/319). + Note: The |globalObject| is not currently passed onto the + {{Credential/[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)}} + algorithm. See issue. + + 1. Let |credentialOrError| be a variable set to null. + 1. If |document|'s [=fedID task status=] is "[=running=]": + 1. [=Queue a global task=] with |globalObject| on the [=DOM manipulation task source=] to + set |credentialOrError| to a new "{{NotAllowedError}}" {{DOMException}}. + 1. Wait until |credentialOrError| is set, and return it. + 1. If |document|'s [=fedID task status=] is "[=no task=]": + 1. If |document|'s {{Document/readyState}} is "complete": + 1. [=queue a global task=] with |globalObject| on the [=DOM manipulation task source=] to + [=run pending fedID requests=] with |document|. + 1. Set |document|'s [=fedID task status=] to "[=pending=]". + 1. Otherwise: + 1. Let |fedCMListener| be an [=event listener=] set as follows: + + : type + :: "load" + : callback + :: new {{EventListener}} whose {{EventListener/handleEvent(event)}} invokes + [=run pending fedID requests=] with |document|. + : once + :: true + 1. [=Add an event listener=] passing |document| and |fedCMListener|. + + Issue: [better](https://github.com/fedidcg/FedCM/issues/440) invoke the method after + the {{Document}} has loaded. + + 1. Set |document|'s [=fedID task status=] to "[=pending=]". + 1. Let |providers| be |options|["{{CredentialRequestOptions/identity}}"]["{{IdentityCredentialRequestOptions/providers}}"]. + 1. Let |allConfigURLs| be an [=ordered set=]. + 1. For each (|providerList|, cred) in |document|'s [=pending fedID request list=]: + 1. For each |provider| in |providerList|: + 1. [=set/Append=] |provider|'s {{IdentityProviderConfig/configURL}} to + |allConfigURLs|. + 1. For each |provider| in |providers|: + 1. If |allConfigURLs| [=list/contains=] |provider|'s {{IdentityProviderConfig/configURL}}: + 1. [=Queue a global task=] with |globalObject| on the [=DOM manipulation task source=] to + set |credentialOrError| to a new "{{NotAllowedError}}" {{DOMException}}. + 1. Wait until |credentialOrError| is set, and return it. + 1. [=set/Append=] |provider|'s {{IdentityProviderConfig/configURL}} to + |allConfigURLs|. + 1. [=list/Append=] (|providers|, |credentialOrError|) to |document|'s [=pending fedID request list=]. + 1. Wait until |credentialOrError| becomes set, either as an {{IdentityCredential}} or as an + exception. + 1. Return |credentialOrError|. +
+ +
+When asked to run pending fedID requests with a {{Document}} |document|, run the +following steps [=in parallel=]: + 1. The [=user agent=] MAY wait some time before proceeding with the following steps. This + provides the [=user agent=] with flexibility on how to bundle together multiple federated + identity requests from a site. + 1. Set |document|'s [=fedID task status=] to "[=running=]". + 1. Let |allProviders| be an initially empty [=list=]. + 1. For each (|providers|, |credentialOrError|) in |document|'s [=pending fedID request list=]: + 1. [=Extend=] |allProviders| with |providers|. 1. Run {{WindowOrWorkerGlobalScope/setTimeout()}} passing a [=task=] which throws a {{NetworkError}}, after a timeout of 60 seconds. @@ -473,63 +536,76 @@ algorithm is invoked, the user agent MUST execute the following steps. This retu Note: the purpose of having a timer here is to avoid leaking the reason causing this method to throw an error. If there was no such timer, the developer could easily infer whether the user has an account with the [=IDP=] or not, or whether the user closed the UI without granting permission to share the [=IDP=] account information with the [=RP=]. - 1. Let |provider| be |options|["{{CredentialRequestOptions/identity}}"]["{{IdentityCredentialRequestOptions/providers}}"][0]. - 1. Let |credential| be the result of running [=create an IdentityCredential=] with |provider| and - |globalObject|. - - Note: The |globalObject| is not currently passed onto the - {{Credential/[[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors)}} - algorithm. See issue. - - 1. If |credential| is failure, [=queue a global task=] on the [=DOM manipulation task source=] - to throw a new "{{NetworkError}}" {{DOMException}}. + 1. Let |globalObject| be |document|'s [=relevant global object=] + 1. Let (|credential|, |selectedProvider|) be the result of running [=create an IdentityCredential=] + with |allProviders| and |globalObject|. + 1. For each (|providers|, |credentialOrError|) in |document|'s [=pending fedID request list=]: + 1. If |credential| is failure or if |providers| does not [=list/contain=] |selectedProvider|, + [=queue a global task=] on the [=DOM manipulation task source=] to set + |credentialOrError| to a new "{{NetworkError}}" {{DOMException}}. + 1. Otherwise, set |credentialOrError| to |credential|. + 1. Set |document|'s [=fedID task status=] to "[=no task=]". + 1. [=list/Empty=] |document|'s [=pending fedID request list=].
-To create an IdentityCredential given an {{IdentityProviderConfig}} -|provider| and a |globalObject|, run the following steps. This returns an {{IdentityCredential}} or -failure. +To create an IdentityCredential given a [=list=] of {{IdentityProviderConfig}}s +|providers| and a |globalObject|, run the following steps. This returns an ({{IdentityCredential}}, +{{IdentityProviderConfig}}) [=tuple=] or the [=tuple=] (failure, failure). 1. Assert: These steps are running [=in parallel=]. - 1. Let |config| be the result of running [=fetch the config file=] with |provider| and - |globalObject|. - 1. If |config| is failure, return failure. - 1. Let |accountsList| be the result of [=fetch the accounts list=] with |config|, |provider|, - and |globalObject|. - 1. For each |account| in |accountsList|: - 1. If |account|["{{IdentityProviderAccount/picture}}"] is present, - [=fetch the account picture=] with |account| and |globalObject|. - - Note: The [=user agent=] may choose to show UI which does not initially require fetching the - account pictures. In these cases, the [=user agent=] may delay these fetches until they are - needed. - 1. If |accountsList|'s size is 1: - 1. Let |account| be |accountsList|[0]. + 1. Let |configMap| be an initially empty [=map=]. + 1. For each {{IdentityProviderConfig}} |provider| in |providers|: + 1. Let |config| be the result of running [=fetch the config file=] with |provider| and + |globalObject|. + 1. Set |configMap|[|provider|] to |config|. + 1. Let |allAccountsAndProviders| be an initially empty [=list=]. + 1. The user agent MAY show an [=IDP=] chooser to the user, and if it does so then it should set + |providers| to a list including only the selected [=IDP=]. + 1. For each |provider| over |providers|: + 1. Let |config| be |configMap|[|provider|]. + 1. If |config| is failure, continue. + 1. Let |accountsList| be the result of [=fetch the accounts list=] with |config|, |provider|, + and |globalObject|. + 1. For each |account| in |accountsList|: + 1. If |account|["{{IdentityProviderAccount/picture}}"] is present, + [=fetch the account picture=] with |account| and |globalObject|. + + Note: The [=user agent=] may choose to show UI which does not initially require fetching the + account pictures. In these cases, the [=user agent=] may delay these fetches until they are + needed. + 1. [=list/Append=] (|account|, |provider|) to |allAccountsAndProviders|. + 1. If |allAccountsAndProviders|'s size is 0, return failure. + 1. Let |account|, |provider| be both initially set to null. + 1. If |allAccountsAndProviders|'s size is 1: + 1. Set (|account|, |provider|) be |allAccountsAndProviders|[0]. 1. Let |accountState| be the result of running the [=compute account state=] algorithm given |provider|, |account|, and |globalObject|. 1. If |accountState|'s {{AccountState/registration state}} is {{unregistered}}, let |permission| be the result of running [=request permission to sign-up=] algorithm - with |account|, |accountState|, |config|, |provider|, and |globalObject|. + with |account|, |accountState|, |configMap|[|provider|], |provider|, and |globalObject|. 1. Otherwise, show a dialog to request user permission to sign in via |account|, and set the result in |permission|. 1. If |permission|, [=sign-in=] with |accountState|. 1. Otherwise: - 1. Let |account| be the result of running the [=select an account=] from the - |accountsList|. + 1. Set (|account|, |provider|) to the result of running the [=select an account=] from + |allAccountsAndProviders|. 1. If |account| is failure, return failure. 1. Let |accountState| be the result of running the [=compute account state=] algorithm given |provider| and |account|. 1. If |accountState|'s {{AccountState/registration state}} is {{unregistered}}: 1. Let |permission| be the result of running the [=request permission to sign-up=] - algorithm with |account|, |accountState|, |config|, |provider|, and |globalObject|. + algorithm with |account|, |accountState|, |configMap|[|provider|], |provider|, and + |globalObject|. 1. If |permission|, [=sign-in=] with |accountState|. 1. Otherwise, [=sign-in=] with |accountState|. 1. Wait until the [=user agent=]'s dialog is closed. 1. If |accountState|'s {{AccountState/registration state}} is {{unregistered}} then return failure. + 1. Assert: |account| and |provider| are not null or failure. 1. Let |credential| be the result of running the [=fetch an identity assertion=] algorithm with - |accountState|, |account|'s {{IdentityProviderAccount/id}}, |provider|, |config|, and - |globalObject|. - 1. Return |credential|. + |accountState|, |account|'s {{IdentityProviderAccount/id}}, |provider|, + |configMap|[|provider|], and |globalObject|. + 1. Return (|credential|, |provider|).
@@ -939,13 +1015,15 @@ dictionary IdentityProviderClientMetadata {
-To select an account given an |accountsList|, run the following steps. This returns an -{{IdentityProviderAccount}} or failure. - 1. Assert |accountsList|'s [=list/size=] is greater than 1. - 1. Display an account chooser displaying the options from |accountsList|. - 1. Let |account| be the {{IdentityProviderAccount}} of the account that the user - manually selects from the accounts chooser, or failure if no account is selected. - 1. Return |account|. +To select an account given |allAccountsAndProviders|, run the following steps. This +returns an ({{IdentityProviderAccount}}, {{IdentityProviderAPIConfig}}) [=tuple=] or the [=tuple=] +(failure, failure). + 1. Assert |allAccountsAndProviders|'s [=list/size=] is greater than 1. + 1. Display an account chooser displaying the options from |allAccountsAndProviders|. + 1. Let (|account|, |provider|) be the {{IdentityProviderAccount}} and associated + {{IdentityProviderConfig}} of the account that the user manually selects from the accounts + chooser, or (failure, failure) if no account is selected. + 1. Return (|account|, |provider|).