mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-04-22 12:35:14 +00:00
Merge bab6b232c5
into 8e37cd2f71
This commit is contained in:
commit
3d0395d666
12 changed files with 899 additions and 26 deletions
|
@ -12,15 +12,15 @@ namespace Web::CredentialManagement {
|
|||
// https://www.w3.org/TR/credential-management-1/#dom-credential-isconditionalmediationavailable
|
||||
GC::Ref<WebIDL::Promise> Credential::is_conditional_mediation_available(JS::VM& vm)
|
||||
{
|
||||
auto* realm = vm.current_realm();
|
||||
return WebIDL::create_rejected_promise_from_exception(*realm, vm.throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "is conditional mediation available"sv));
|
||||
// 1. Return a promise resolved with false.
|
||||
return WebIDL::create_resolved_promise(*vm.current_realm(), JS::Value(false));
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/credential-management-1/#dom-credential-willrequestconditionalcreation
|
||||
GC::Ref<WebIDL::Promise> Credential::will_request_conditional_creation(JS::VM& vm)
|
||||
{
|
||||
auto* realm = vm.current_realm();
|
||||
return WebIDL::create_rejected_promise_from_exception(*realm, vm.throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "will request conditional creation"sv));
|
||||
// 1. Return a promise resolved with undefined.
|
||||
return WebIDL::create_resolved_promise(*vm.current_realm(), JS::js_undefined());
|
||||
}
|
||||
|
||||
Credential::~Credential() { }
|
||||
|
|
|
@ -13,6 +13,74 @@
|
|||
|
||||
namespace Web::CredentialManagement {
|
||||
|
||||
typedef GC::Function<JS::ThrowCompletionOr<GC::Ref<Credential>>(JS::Object const&)> CreateCredentialAlgorithm;
|
||||
|
||||
#define CREDENTIAL_INTERFACE(class_name) \
|
||||
public: \
|
||||
static class_name const* the() \
|
||||
{ \
|
||||
static class_name* instance = nullptr; \
|
||||
if (!instance) \
|
||||
instance = new class_name(); \
|
||||
return instance; \
|
||||
}
|
||||
|
||||
class CredentialInterface {
|
||||
AK_MAKE_NONCOPYABLE(CredentialInterface);
|
||||
|
||||
protected:
|
||||
CredentialInterface() { }
|
||||
|
||||
public:
|
||||
virtual ~CredentialInterface() = default;
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#credential-type-registry-credential-type
|
||||
virtual String type() const = 0;
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#credential-type-registry-options-member-identifier
|
||||
virtual String options_member_identifier() const = 0;
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#credential-type-registry-get-permissions-policy
|
||||
virtual Optional<String> get_permission_policy() const = 0;
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#credential-type-registry-create-permissions-policy
|
||||
virtual Optional<String> create_permission_policy() const = 0;
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#dom-credential-discovery-slot
|
||||
virtual String discovery() const = 0;
|
||||
|
||||
// NOTE: This is not explicitly present in the spec, it is inferred.
|
||||
virtual bool supports_conditional_user_mediation() const = 0;
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#algorithm-create-cred
|
||||
virtual JS::ThrowCompletionOr<Variant<Empty, GC::Ref<Credential>, GC::Ref<CreateCredentialAlgorithm>>> create(JS::Realm&, URL::Origin const&, CredentialCreationOptions const&, bool) const
|
||||
{
|
||||
// 1. Return null.
|
||||
return Empty {};
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#algorithm-store-cred
|
||||
virtual JS::ThrowCompletionOr<void> store(JS::Realm& realm, bool) const
|
||||
{
|
||||
// 1. Throw a NotSupportedError.
|
||||
return throw_completion(WebIDL::NotSupportedError::create(realm, "store"_string));
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#algorithm-discover-creds
|
||||
virtual JS::ThrowCompletionOr<Variant<Empty, GC::Ref<Credential>>> discover_from_external_source(JS::Realm&, URL::Origin const&, CredentialRequestOptions const&, bool) const
|
||||
{
|
||||
// 1. Return null.
|
||||
return Empty {};
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#algorithm-collect-creds
|
||||
virtual JS::ThrowCompletionOr<Vector<Credential>> collect_from_credential_store(JS::Realm&, URL::Origin const&, CredentialRequestOptions const&, bool) const
|
||||
{
|
||||
// 1. Return an empty set.
|
||||
return Vector<Credential> {};
|
||||
}
|
||||
};
|
||||
|
||||
class Credential : public Bindings::PlatformObject {
|
||||
WEB_PLATFORM_OBJECT(Credential, Bindings::PlatformObject);
|
||||
GC_DECLARE_ALLOCATOR(Credential);
|
||||
|
@ -25,11 +93,12 @@ public:
|
|||
|
||||
virtual ~Credential() override;
|
||||
|
||||
String const& id() { return m_id; }
|
||||
String const& name() { return m_name; }
|
||||
String const& icon_url() { return m_icon_url; }
|
||||
String const& id() const { return m_id; }
|
||||
String const& name() const { return m_name; }
|
||||
String const& icon_url() const { return m_icon_url; }
|
||||
|
||||
virtual String type() = 0;
|
||||
virtual String type() const = 0;
|
||||
virtual CredentialInterface const* interface() const = 0;
|
||||
|
||||
protected:
|
||||
explicit Credential(JS::Realm&);
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
*/
|
||||
|
||||
#include <LibWeb/CredentialManagement/CredentialsContainer.h>
|
||||
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
|
||||
#include <LibWeb/HTML/Window.h>
|
||||
#include <LibWeb/Platform/EventLoopPlugin.h>
|
||||
|
||||
namespace Web::CredentialManagement {
|
||||
|
||||
|
@ -17,25 +20,487 @@ GC::Ref<CredentialsContainer> CredentialsContainer::create(JS::Realm& realm)
|
|||
|
||||
CredentialsContainer::~CredentialsContainer() { }
|
||||
|
||||
// https://www.w3.org/TR/credential-management-1/#dom-credentialscontainer-get
|
||||
GC::Ref<WebIDL::Promise> CredentialsContainer::get(CredentialRequestOptions const&)
|
||||
// https://w3c.github.io/webappsec-credential-management/#algorithm-same-origin-with-ancestors
|
||||
static bool is_same_origin_with_its_ancestors(HTML::EnvironmentSettingsObject& settings)
|
||||
{
|
||||
auto* realm = vm().current_realm();
|
||||
return WebIDL::create_rejected_promise_from_exception(*realm, vm().throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "get"sv));
|
||||
auto& global = settings.global_object();
|
||||
|
||||
// TODO: 1. If settings’s relevant global object has no associated Document, return false.
|
||||
// 2. Let document be settings’ relevant global object's associated Document.
|
||||
auto& document = as<HTML::Window>(global).associated_document();
|
||||
|
||||
// 3. If document has no browsing context, return false.
|
||||
if (!document.browsing_context())
|
||||
return false;
|
||||
|
||||
// 4. Let origin be settings’ origin.
|
||||
auto origin = settings.origin();
|
||||
|
||||
// 5. Let navigable be document’s node navigable.
|
||||
auto navigable = document.navigable();
|
||||
|
||||
// 6. While navigable has a non-null parent:
|
||||
while (navigable->parent()) {
|
||||
// 1. Set navigable to navigable’s parent.
|
||||
navigable = navigable->parent();
|
||||
|
||||
// 2. If navigable’s active document's origin is not same origin with origin, return false.
|
||||
if (!origin.is_same_origin(navigable->active_document()->origin()))
|
||||
return false;
|
||||
}
|
||||
|
||||
// 7. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/credential-management-1/#dom-credentialscontainer-store
|
||||
GC::Ref<WebIDL::Promise> CredentialsContainer::store(Credential const&)
|
||||
// https://w3c.github.io/webappsec-credential-management/#credentialrequestoptions-relevant-credential-interface-objects
|
||||
template<typename OptionsType>
|
||||
static Vector<CredentialInterface const*> relevant_credential_interface_objects(OptionsType const& options)
|
||||
{
|
||||
auto* realm = vm().current_realm();
|
||||
return WebIDL::create_rejected_promise_from_exception(*realm, vm().throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "store"sv));
|
||||
// 1. Let settings be the current settings object.
|
||||
// 2. Let relevant interface objects be an empty set.
|
||||
Vector<CredentialInterface const*> interfaces;
|
||||
|
||||
// 3. For each optionKey → optionValue of options:
|
||||
// NOTE: We cannot iterate like the spec says.
|
||||
// 1. Let credentialInterfaceObject be the Appropriate Interface Object (on settings’ global object) whose Options Member Identifier is optionKey.
|
||||
// 2. Assert: credentialInterfaceObject’s [[type]] slot equals the Credential Type whose Options Member Identifier is optionKey.
|
||||
// 3. Append credentialInterfaceObject to relevant interface objects.
|
||||
|
||||
#define APPEND_CREDENTIAL_INTERFACE_OBJECT(key, type_) \
|
||||
if (options.key.has_value()) { \
|
||||
auto credential_interface_object = type_##Interface::the(); \
|
||||
VERIFY(credential_interface_object->options_member_identifier() == #key); \
|
||||
interfaces.append(credential_interface_object); \
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#credential-type-registry-appropriate-interface-object
|
||||
APPEND_CREDENTIAL_INTERFACE_OBJECT(password, PasswordCredential);
|
||||
APPEND_CREDENTIAL_INTERFACE_OBJECT(federated, FederatedCredential);
|
||||
// TODO: digital
|
||||
// TODO: identity
|
||||
// TODO: otp
|
||||
// TODO: publicKey
|
||||
|
||||
#undef APPEND_CREDENTIAL_INTERFACE_OBJECT
|
||||
|
||||
// 4. Return relevant interface objects.
|
||||
return interfaces;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/credential-management-1/#dom-credentialscontainer-create
|
||||
GC::Ref<WebIDL::Promise> CredentialsContainer::create(CredentialCreationOptions const&)
|
||||
// https://w3c.github.io/webappsec-credential-management/#algorithm-collect-known
|
||||
static JS::ThrowCompletionOr<Vector<GC::Ref<Credential>>> collect_credentials_from_store(JS::Realm& realm, URL::Origin const& origin, CredentialRequestOptions const& options, bool same_origin_with_ancestors)
|
||||
{
|
||||
auto* realm = vm().current_realm();
|
||||
return WebIDL::create_rejected_promise_from_exception(*realm, vm().throw_completion<JS::InternalError>(JS::ErrorType::NotImplemented, "create"sv));
|
||||
// 1. Let possible matches be an empty set.
|
||||
Vector<GC::Ref<Credential>> possible_matches;
|
||||
|
||||
// 2. For each interface in options’ relevant credential interface objects:
|
||||
for (auto& interface : relevant_credential_interface_objects(options)) {
|
||||
// 1. Let r be the result of executing interface’s [[CollectFromCredentialStore]](origin, options, sameOriginWithAncestors)
|
||||
// internal method on origin, options, and sameOriginWithAncestors. If that threw an exception, rethrow that exception.
|
||||
auto maybe_r = interface->collect_from_credential_store(realm, origin, options, same_origin_with_ancestors);
|
||||
if (maybe_r.is_error())
|
||||
return maybe_r.error();
|
||||
|
||||
auto r = maybe_r.release_value();
|
||||
|
||||
// TODO: 2. Assert: r is a list of interface objects.
|
||||
|
||||
// 3. For each c in r:
|
||||
for (auto& c : r) {
|
||||
// 1. Append c to possible matches.
|
||||
possible_matches.append(c);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Return possible matches.
|
||||
return possible_matches;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#abstract-opdef-ask-the-user-to-choose-a-credential
|
||||
static Variant<Empty, GC::Ref<Credential>, CredentialInterface*> ask_the_user_to_choose_a_credential(CredentialRequestOptions const&, Vector<GC::Ref<Credential>> const&)
|
||||
{
|
||||
// TODO: This algorithm returns either null if the user chose not to share a credential with the site,
|
||||
// a Credential object if the user chose a specific credential, or a Credential interface object
|
||||
// if the user chose a type of credential.
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#credentialrequestoptions-matchable-a-priori
|
||||
static bool is_matchable_a_priori(CredentialRequestOptions const& options)
|
||||
{
|
||||
// 1. For each interface in options’ relevant credential interface objects:
|
||||
for (auto& interface : relevant_credential_interface_objects(options)) {
|
||||
// 1. If interface’s [[discovery]] slot’s value is not "credential store", return false.
|
||||
if (interface->discovery() != "credential store")
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#algorithm-request
|
||||
GC::Ref<WebIDL::Promise> CredentialsContainer::get(CredentialRequestOptions const& options)
|
||||
{
|
||||
// 1. Let settings be the current settings object.
|
||||
auto& settings = HTML::current_principal_settings_object();
|
||||
|
||||
// 2. Assert: settings is a secure context.
|
||||
VERIFY(HTML::is_secure_context(settings));
|
||||
|
||||
// 3. Let document be settings’s relevant global object's associated Document.
|
||||
auto& document = as<HTML::Window>(settings.global_object()).associated_document();
|
||||
|
||||
// 4. If document is not fully active, then return a promise rejected with an "InvalidStateError" DOMException.
|
||||
if (!document.is_fully_active())
|
||||
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::InvalidStateError::create(realm(), "Document is not fully active"_string));
|
||||
|
||||
// 5. If options.signal is aborted, then return a promise rejected with options.signal’s abort reason.
|
||||
if (options.signal && options.signal->aborted())
|
||||
return WebIDL::create_rejected_promise(realm(), options.signal->reason());
|
||||
|
||||
// 6. Let interfaces be options’s relevant credential interface objects.
|
||||
auto interfaces = relevant_credential_interface_objects(options);
|
||||
|
||||
// 7. If interfaces is empty, then return a promise rejected with a "NotSupportedError" DOMException.
|
||||
if (interfaces.is_empty())
|
||||
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::NotSupportedError::create(realm(), "No credential types"_string));
|
||||
|
||||
// 8. For each interface of interfaces:
|
||||
for (auto& interface : interfaces) {
|
||||
// 1. If options.mediation is conditional and interface does not support conditional user mediation,
|
||||
// return a promise rejected with a "TypeError" DOMException.
|
||||
if (options.mediation == Bindings::CredentialMediationRequirement::Conditional && !interface->supports_conditional_user_mediation())
|
||||
return WebIDL::create_rejected_promise(realm(), JS::TypeError::create(realm(), "Conditional user mediation is not supported"sv));
|
||||
|
||||
// 2. If settings’ active credential types contains interface’s [[type]],
|
||||
// return a promise rejected with a "NotAllowedError" DOMException.
|
||||
if (settings.active_credential_types().contains_slow(interface->type()))
|
||||
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::NotAllowedError::create(realm(), "Credential type is not allowed"_string));
|
||||
|
||||
// 3. Append interface’s [[type]] to settings’ active credential types.
|
||||
settings.active_credential_types().append(interface->type());
|
||||
}
|
||||
|
||||
// 9. Let origin be settings’ origin.
|
||||
auto origin = settings.origin();
|
||||
|
||||
// 10. Let sameOriginWithAncestors be true if settings is same-origin with its ancestors, and false otherwise.
|
||||
auto same_origin_with_ancestors = is_same_origin_with_its_ancestors(settings);
|
||||
|
||||
// 11. For each interface in options’ relevant credential interface objects:
|
||||
for (auto& interface : interfaces) {
|
||||
// 1. Let permission be the interface’s [[type]] Get Permissions Policy.
|
||||
auto permission = interface->get_permission_policy();
|
||||
|
||||
// 2. If permission is null, continue.
|
||||
if (!permission.has_value())
|
||||
continue;
|
||||
|
||||
// TODO: 3. If document is not allowed to use permission, return a promise rejected with a "NotAllowedError" DOMException.
|
||||
}
|
||||
|
||||
// 12. Let p be a new promise.
|
||||
auto promise = WebIDL::create_promise(realm());
|
||||
|
||||
// 13. Run the following steps in parallel:
|
||||
// FIXME: The promises resolving/rejection must be wrapped in a queued task.
|
||||
// https://github.com/w3c/webappsec-credential-management/issues/260
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm().heap(), [this, promise, origin, options, same_origin_with_ancestors, &settings] {
|
||||
HTML::TemporaryExecutionContext execution_context { realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
|
||||
|
||||
// 1. Let credentials be the result of collecting Credentials from the credential store, given origin, options, and sameOriginWithAncestors.
|
||||
auto maybe_credentials = collect_credentials_from_store(realm(), origin, options, same_origin_with_ancestors);
|
||||
|
||||
// 2. If credentials is an exception, reject p with credentials.
|
||||
if (maybe_credentials.is_error()) {
|
||||
WebIDL::reject_promise(realm(), *promise, maybe_credentials.error_value());
|
||||
return;
|
||||
}
|
||||
|
||||
auto credentials = maybe_credentials.release_value();
|
||||
|
||||
// 3. If all of the following statements are true, resolve p with credentials[0] and skip the remaining steps:
|
||||
// 1. credentials’ size is 1
|
||||
// TODO: 2. origin does not require user mediation
|
||||
// 3. options is matchable a priori.
|
||||
// 4. options.mediation is not "required".
|
||||
// 5. options.mediation is not "conditional".
|
||||
if (credentials.size() == 1
|
||||
&& is_matchable_a_priori(options)
|
||||
&& options.mediation != Bindings::CredentialMediationRequirement::Required
|
||||
&& options.mediation != Bindings::CredentialMediationRequirement::Conditional) {
|
||||
WebIDL::resolve_promise(realm(), *promise, credentials[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. If options’ mediation is "silent", resolve p with null, and skip the remaining steps.
|
||||
if (options.mediation == Bindings::CredentialMediationRequirement::Silent) {
|
||||
WebIDL::resolve_promise(realm(), *promise, JS::js_null());
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Let result be the result of asking the user to choose a Credential, given options and credentials.
|
||||
auto result = ask_the_user_to_choose_a_credential(options, credentials);
|
||||
|
||||
// 6. If result is an interface object:
|
||||
if (result.has<CredentialInterface*>()) {
|
||||
// 1. Set result to the result of executing result’s [[DiscoverFromExternalSource]](origin, options, sameOriginWithAncestors),
|
||||
// given origin, options, and sameOriginWithAncestors.
|
||||
auto maybe_result = result.get<CredentialInterface*>()->discover_from_external_source(realm(), origin, options, same_origin_with_ancestors);
|
||||
// If that threw an exception:
|
||||
if (maybe_result.is_error()) {
|
||||
// 1. Let e be the thrown exception.
|
||||
auto e = maybe_result.error_value();
|
||||
// 2. Queue a task on global’s DOM manipulation task source to run the following substeps:
|
||||
queue_global_task(HTML::Task::Source::DOMManipulation, settings.global_object(), GC::create_function(realm().heap(), [&] {
|
||||
HTML::TemporaryExecutionContext execution_context { realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
|
||||
|
||||
// 1. Reject p with e.
|
||||
WebIDL::reject_promise(realm(), *promise, e);
|
||||
}));
|
||||
// 3. Terminate these substeps.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Assert: result is null, or a Credential.
|
||||
VERIFY(result.has<Empty>() || result.has<GC::Ref<Credential>>());
|
||||
|
||||
// 8. If result is a Credential, resolve p with result.
|
||||
if (result.has<GC::Ref<Credential>>()) {
|
||||
WebIDL::resolve_promise(realm(), *promise, result.get<GC::Ref<Credential>>());
|
||||
return;
|
||||
}
|
||||
|
||||
// 9. If result is null and options.mediation is not conditional, resolve p with result.
|
||||
if (result.has<Empty>() && options.mediation != Bindings::CredentialMediationRequirement::Conditional)
|
||||
WebIDL::resolve_promise(realm(), *promise, JS::js_null());
|
||||
|
||||
// FIXME: It is unclear what to do if result is null and options.mediation is conditional.
|
||||
// https://github.com/w3c/webappsec-credential-management/issues/271
|
||||
VERIFY_NOT_REACHED();
|
||||
}));
|
||||
|
||||
// 14. React to p:
|
||||
auto on_completion = GC::create_function(realm().heap(), [&settings, interfaces = move(interfaces)](JS::Value) -> WebIDL::ExceptionOr<JS::Value> {
|
||||
// 1. For each interface in interfaces:
|
||||
for (auto const& interface : interfaces) {
|
||||
// 1. Remove interface’s [[type]] from settings’ active credential types.
|
||||
settings.active_credential_types().remove_first_matching([&](auto& v) { return v == interface->type(); });
|
||||
}
|
||||
|
||||
return JS::js_undefined();
|
||||
});
|
||||
WebIDL::react_to_promise(*promise, on_completion, on_completion);
|
||||
|
||||
// 15. Return p.
|
||||
return promise;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#algorithm-store
|
||||
GC::Ref<WebIDL::Promise> CredentialsContainer::store(Credential const& credential)
|
||||
{
|
||||
// 1. Let settings be the current settings object.
|
||||
auto& settings = HTML::current_principal_settings_object();
|
||||
|
||||
// 2. Assert: settings is a secure context.
|
||||
VERIFY(HTML::is_secure_context(settings));
|
||||
|
||||
// 3. If settings’s relevant global object's associated Document is not fully active,
|
||||
// then return a promise rejected with an "InvalidStateError" DOMException.
|
||||
if (!as<HTML::Window>(settings.global_object()).associated_document().is_fully_active())
|
||||
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::InvalidStateError::create(realm(), "Document is not fully active"_string));
|
||||
|
||||
// 4. Let sameOriginWithAncestors be true if the current settings object is same-origin with its ancestors, and false otherwise.
|
||||
auto same_origin_with_ancestors = is_same_origin_with_its_ancestors(settings);
|
||||
|
||||
// 5. Let p be a new promise.
|
||||
auto promise = WebIDL::create_promise(realm());
|
||||
|
||||
// 6. If settings’ active credential types contains credential’s [[type]], return a promise rejected with a "NotAllowedError" DOMException.
|
||||
if (settings.active_credential_types().contains_slow(credential.type()))
|
||||
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::NotAllowedError::create(realm(), "Credential type is not allowed"_string));
|
||||
|
||||
// 7. Append credential’s [[type]] to settings’ active credential types.
|
||||
settings.active_credential_types().append(credential.type());
|
||||
|
||||
// 8. Run the following steps in parallel:
|
||||
// FIXME: The promises resolving/rejection must be wrapped in a queued task.
|
||||
// https://github.com/w3c/webappsec-credential-management/issues/260
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm().heap(), [this, promise, &settings, &credential, same_origin_with_ancestors] {
|
||||
HTML::TemporaryExecutionContext execution_context { realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
|
||||
|
||||
// 1. Execute credential’s interface object's [[Store]](credential, sameOriginWithAncestors)
|
||||
// internal method on credential and sameOriginWithAncestors.
|
||||
auto maybe_error = credential.interface()->store(realm(), same_origin_with_ancestors);
|
||||
// If that threw an exception:
|
||||
if (maybe_error.is_error()) {
|
||||
// 1. Let e be the thrown exception.
|
||||
auto e = maybe_error.error_value();
|
||||
// 2. Queue a task on global’s DOM manipulation task source to run the following substeps:
|
||||
queue_global_task(HTML::Task::Source::DOMManipulation, settings.global_object(), GC::create_function(realm().heap(), [&] {
|
||||
HTML::TemporaryExecutionContext execution_context { realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
|
||||
// 1. Reject p with e.
|
||||
WebIDL::reject_promise(realm(), *promise, e);
|
||||
}));
|
||||
}
|
||||
// Otherwise, resolve p with undefined.
|
||||
else {
|
||||
WebIDL::resolve_promise(realm(), *promise, JS::js_undefined());
|
||||
}
|
||||
}));
|
||||
|
||||
// 9. React to p:
|
||||
auto on_completion = GC::create_function(realm().heap(), [&settings, &credential](JS::Value) -> WebIDL::ExceptionOr<JS::Value> {
|
||||
// 1. Remove credential’s [[type]] from settings’ active credential types.
|
||||
settings.active_credential_types().remove_first_matching([&](auto& v) { return v == credential.type(); });
|
||||
|
||||
return JS::js_undefined();
|
||||
});
|
||||
WebIDL::react_to_promise(*promise, on_completion, on_completion);
|
||||
|
||||
// 10. Return p.
|
||||
return promise;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#algorithm-create
|
||||
GC::Ref<WebIDL::Promise> CredentialsContainer::create(CredentialCreationOptions const& options)
|
||||
{
|
||||
// 1. Let settings be the current settings object.
|
||||
auto& settings = HTML::current_principal_settings_object();
|
||||
|
||||
// 2. Assert: settings is a secure context.
|
||||
VERIFY(HTML::is_secure_context(settings));
|
||||
|
||||
// 3. Let global be settings’ global object.
|
||||
auto& global = settings.global_object();
|
||||
|
||||
// 4. Let document be the relevant global object's associated Document.
|
||||
auto& document = as<HTML::Window>(global).associated_document();
|
||||
|
||||
// 5. If document is not fully active, then return a promise rejected with an "InvalidStateError" DOMException.
|
||||
if (!document.is_fully_active())
|
||||
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::InvalidStateError::create(realm(), "Document is not fully active"_string));
|
||||
|
||||
// 6. Let sameOriginWithAncestors be true if the current settings object is same-origin with its ancestors, and false otherwise.
|
||||
auto same_origin_with_ancestors = is_same_origin_with_its_ancestors(settings);
|
||||
|
||||
// 7. Let interfaces be the set of options’ relevant credential interface objects.
|
||||
auto interfaces = relevant_credential_interface_objects(options);
|
||||
|
||||
// 8. Return a promise rejected with NotSupportedError if any of the following statements are true:
|
||||
// TODO: 1. global does not have an associated Document.
|
||||
// 2. interfaces’ size is greater than 1.
|
||||
if (interfaces.size() > 1)
|
||||
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::NotSupportedError::create(realm(), "Too many crendetial types"_string));
|
||||
|
||||
// 9. For each interface in interfaces:
|
||||
for (auto& interface : interfaces) {
|
||||
// 1. Let permission be the interface’s [[type]] Create Permissions Policy.
|
||||
auto permission = interface->create_permission_policy();
|
||||
|
||||
// 2. If permission is null, continue.
|
||||
if (!permission.has_value())
|
||||
continue;
|
||||
|
||||
// TODO: 3. If document is not allowed to use permission, return a promise rejected with a "NotAllowedError" DOMException.
|
||||
}
|
||||
|
||||
// 10. If options.signal is aborted, then return a promise rejected with options.signal’s abort reason.
|
||||
if (options.signal && options.signal->aborted())
|
||||
return WebIDL::create_rejected_promise(realm(), options.signal->reason());
|
||||
|
||||
// NOTE: The spec does not mention this check
|
||||
// https://github.com/w3c/webappsec-credential-management/issues/267
|
||||
if (interfaces.size() < 1)
|
||||
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::NotSupportedError::create(realm(), "No credential types"_string));
|
||||
|
||||
// 11. Let type be interfaces[0]'s [[type]].
|
||||
auto type = interfaces[0]->type();
|
||||
|
||||
// 12. If settings’ active credential types contains type, return a promise rejected with a "NotAllowedError" DOMException.
|
||||
if (settings.active_credential_types().contains_slow(type))
|
||||
return WebIDL::create_rejected_promise_from_exception(realm(), WebIDL::NotAllowedError::create(realm(), "Credential type is not allowed"_string));
|
||||
|
||||
// 13. Append type to settings’ active credential types.
|
||||
settings.active_credential_types().append(type);
|
||||
|
||||
// 14. Let origin be settings’s origin.
|
||||
auto origin = settings.origin();
|
||||
|
||||
// 15. Let p be a new promise.
|
||||
auto promise = WebIDL::create_promise(realm());
|
||||
|
||||
// 16. Run the following steps in parallel:
|
||||
// FIXME: The promises resolving/rejection must be wrapped in a queued task.
|
||||
// https://github.com/w3c/webappsec-credential-management/issues/260
|
||||
Platform::EventLoopPlugin::the().deferred_invoke(GC::create_function(realm().heap(), [this, promise, &global, interfaces = move(interfaces), origin, options, same_origin_with_ancestors] {
|
||||
HTML::TemporaryExecutionContext execution_context { realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
|
||||
|
||||
// 1. Let r be the result of executing interfaces[0]'s [[Create]](origin, options, sameOriginWithAncestors)
|
||||
// internal method on origin, options, and sameOriginWithAncestors.
|
||||
auto maybe_r = interfaces[0]->create(realm(), origin, options, same_origin_with_ancestors);
|
||||
// If that threw an exception:
|
||||
if (maybe_r.is_error()) {
|
||||
// 1. Let e be the thrown exception.
|
||||
auto e = maybe_r.error_value();
|
||||
// 2. Queue a task on global’s DOM manipulation task source to run the following substeps:
|
||||
queue_global_task(HTML::Task::Source::DOMManipulation, global, GC::create_function(realm().heap(), [&] {
|
||||
HTML::TemporaryExecutionContext execution_context { realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
|
||||
|
||||
// 1. Reject p with e.
|
||||
WebIDL::reject_promise(realm(), *promise, e);
|
||||
}));
|
||||
// 3. Terminate these substeps.
|
||||
return;
|
||||
}
|
||||
|
||||
auto r = maybe_r.release_value();
|
||||
|
||||
// 2. If r is a Credential or null, resolve p with r, and terminate these substeps.
|
||||
if (r.has<Empty>()) {
|
||||
WebIDL::resolve_promise(realm(), *promise, JS::js_null());
|
||||
return;
|
||||
}
|
||||
if (r.has<GC::Ref<Credential>>()) {
|
||||
auto& credential = r.get<GC::Ref<Credential>>();
|
||||
WebIDL::resolve_promise(realm(), *promise, credential);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Assert: r is an algorithm (as defined in §2.2.1.4 [[Create]] internal method).
|
||||
VERIFY(r.has<GC::Ref<CreateCredentialAlgorithm>>());
|
||||
|
||||
// 4. Queue a task on global’s DOM manipulation task source to run the following substeps:
|
||||
auto& r_algo = r.get<GC::Ref<CreateCredentialAlgorithm>>();
|
||||
queue_global_task(HTML::Task::Source::DOMManipulation, global, GC::create_function(realm().heap(), [this, &global, promise, r_algo] {
|
||||
HTML::TemporaryExecutionContext execution_context { realm(), HTML::TemporaryExecutionContext::CallbacksEnabled::Yes };
|
||||
|
||||
// 1. Resolve p with the result of promise-calling r given global.
|
||||
auto maybe_result = r_algo->function()(global);
|
||||
if (maybe_result.is_error()) {
|
||||
WebIDL::reject_promise(realm(), *promise, maybe_result.error_value());
|
||||
return;
|
||||
}
|
||||
|
||||
auto& result = maybe_result.value();
|
||||
WebIDL::resolve_promise(realm(), *promise, result);
|
||||
}));
|
||||
}));
|
||||
|
||||
// 17. React to p:
|
||||
auto on_completion = GC::create_function(realm().heap(), [&settings, type](JS::Value) -> WebIDL::ExceptionOr<JS::Value> {
|
||||
// 1. Remove type from settings’ active credential types.
|
||||
settings.active_credential_types().remove_first_matching([&](auto& v) { return v == type; });
|
||||
|
||||
return JS::js_undefined();
|
||||
});
|
||||
WebIDL::react_to_promise(*promise, on_completion, on_completion);
|
||||
|
||||
// 18. Return p.
|
||||
return promise;
|
||||
}
|
||||
|
||||
// https://www.w3.org/TR/credential-management-1/#dom-credentialscontainer-preventsilentaccess
|
||||
|
|
|
@ -13,6 +13,24 @@
|
|||
|
||||
namespace Web::CredentialManagement {
|
||||
|
||||
class FederatedCredentialInterface final : public CredentialInterface {
|
||||
CREDENTIAL_INTERFACE(FederatedCredentialInterface);
|
||||
|
||||
public:
|
||||
virtual String type() const override { return "federated"_string; }
|
||||
virtual String options_member_identifier() const override { return "federated"_string; }
|
||||
virtual Optional<String> get_permission_policy() const override { return {}; }
|
||||
virtual Optional<String> create_permission_policy() const override { return {}; }
|
||||
|
||||
virtual String discovery() const override { return "credential store"_string; }
|
||||
virtual bool supports_conditional_user_mediation() const override
|
||||
{
|
||||
// NOTE: FederatedCredential does not override is_conditional_mediation_available(),
|
||||
// therefore conditional mediation is not supported.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class FederatedCredential final : public Credential {
|
||||
WEB_PLATFORM_OBJECT(FederatedCredential, Credential);
|
||||
GC_DECLARE_ALLOCATOR(FederatedCredential);
|
||||
|
@ -23,10 +41,14 @@ public:
|
|||
|
||||
virtual ~FederatedCredential() override;
|
||||
|
||||
String const& provider() { return m_provider; }
|
||||
Optional<String> const& protocol() { return m_protocol; }
|
||||
String const& provider() const { return m_provider; }
|
||||
Optional<String> const& protocol() const { return m_protocol; }
|
||||
|
||||
String type() override { return "federated"_string; }
|
||||
String type() const override { return "federated"_string; }
|
||||
virtual CredentialInterface const* interface() const override
|
||||
{
|
||||
return FederatedCredentialInterface::the();
|
||||
}
|
||||
|
||||
private:
|
||||
explicit FederatedCredential(JS::Realm&);
|
||||
|
|
|
@ -14,6 +14,24 @@
|
|||
|
||||
namespace Web::CredentialManagement {
|
||||
|
||||
class PasswordCredentialInterface final : public CredentialInterface {
|
||||
CREDENTIAL_INTERFACE(PasswordCredentialInterface);
|
||||
|
||||
public:
|
||||
virtual String type() const override { return "password"_string; }
|
||||
virtual String options_member_identifier() const override { return "password"_string; }
|
||||
virtual Optional<String> get_permission_policy() const override { return {}; }
|
||||
virtual Optional<String> create_permission_policy() const override { return {}; }
|
||||
|
||||
virtual String discovery() const override { return "credential store"_string; }
|
||||
virtual bool supports_conditional_user_mediation() const override
|
||||
{
|
||||
// NOTE: PasswordCredential does not override is_conditional_mediation_available(),
|
||||
// therefore conditional mediation is not supported.
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
class PasswordCredential final : public Credential {
|
||||
WEB_PLATFORM_OBJECT(PasswordCredential, Credential);
|
||||
GC_DECLARE_ALLOCATOR(PasswordCredential);
|
||||
|
@ -25,9 +43,13 @@ public:
|
|||
|
||||
virtual ~PasswordCredential() override;
|
||||
|
||||
String const& password() { return m_password; }
|
||||
String const& password() const { return m_password; }
|
||||
|
||||
String type() override { return "password"_string; }
|
||||
String type() const override { return "password"_string; }
|
||||
virtual CredentialInterface const* interface() const override
|
||||
{
|
||||
return PasswordCredentialInterface::the();
|
||||
}
|
||||
|
||||
private:
|
||||
explicit PasswordCredential(JS::Realm&);
|
||||
|
|
|
@ -9,7 +9,10 @@ interface PasswordCredential : Credential {
|
|||
PasswordCredential includes CredentialUserData;
|
||||
|
||||
partial dictionary CredentialRequestOptions {
|
||||
boolean password = false;
|
||||
// FIXME: Spec bug?
|
||||
// https://github.com/w3c/webappsec-credential-management/issues/270
|
||||
// boolean password = false;
|
||||
boolean password;
|
||||
};
|
||||
|
||||
dictionary PasswordCredentialData : CredentialData {
|
||||
|
|
|
@ -646,4 +646,10 @@ GC::Ref<ServiceWorker::ServiceWorker> EnvironmentSettingsObject::get_service_wor
|
|||
return *object_map.get(service_worker);
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#active-credential-types
|
||||
Vector<String>& EnvironmentSettingsObject::active_credential_types()
|
||||
{
|
||||
return m_active_credential_types;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -124,6 +124,9 @@ public:
|
|||
// https://w3c.github.io/ServiceWorker/#get-the-service-worker-object
|
||||
GC::Ref<ServiceWorker::ServiceWorker> get_service_worker_object(ServiceWorker::ServiceWorkerRecord*);
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#active-credential-types
|
||||
Vector<String>& active_credential_types();
|
||||
|
||||
[[nodiscard]] bool discarded() const { return m_discarded; }
|
||||
void set_discarded(bool b) { m_discarded = b; }
|
||||
|
||||
|
@ -158,6 +161,10 @@ private:
|
|||
// a map where the keys are service workers and the values are ServiceWorker objects.
|
||||
HashMap<ServiceWorker::ServiceWorkerRecord*, GC::Ref<ServiceWorker::ServiceWorker>> m_service_worker_object_map;
|
||||
|
||||
// https://w3c.github.io/webappsec-credential-management/#active-credential-types
|
||||
// Each environment settings object has an associated active credential types, a set which is initially empty.
|
||||
Vector<String> m_active_credential_types;
|
||||
|
||||
// https://w3c.github.io/ServiceWorker/#service-worker-client-discarded-flag
|
||||
// A service worker client has an associated discarded flag. It is initially unset.
|
||||
bool m_discarded { false };
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 17 tests
|
||||
|
||||
11 Pass
|
||||
6 Fail
|
||||
Pass navigator.credentials.create() with no argument.
|
||||
Pass navigator.credentials.create() with empty argument.
|
||||
Fail navigator.credentials.create() with valid PasswordCredentialData
|
||||
Fail navigator.credentials.create() with valid HTMLFormElement
|
||||
Pass navigator.credentials.create() with bogus password data
|
||||
Fail navigator.credentials.create() with valid FederatedCredentialData
|
||||
Pass navigator.credentials.create() with bogus federated data
|
||||
Fail navigator.credentials.create() with bogus publicKey data
|
||||
Fail navigator.credentials.create() with both PasswordCredentialData and FederatedCredentialData
|
||||
Pass navigator.credentials.create() with bogus password and federated data
|
||||
Pass navigator.credentials.create() with bogus federated and publicKey data
|
||||
Pass navigator.credentials.create() with bogus password and publicKey data
|
||||
Pass navigator.credentials.create() with bogus password, federated, and publicKey data
|
||||
Pass navigator.credentials.create() with bogus data
|
||||
Pass navigator.credentials.create() aborted with custom reason
|
||||
Pass navigator.credentials.create() aborted with different objects
|
||||
Fail navigator.credentials.create() rejects when aborted after the promise creation
|
|
@ -0,0 +1,10 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 4 tests
|
||||
|
||||
3 Pass
|
||||
1 Fail
|
||||
Pass Calling navigator.credentials.get() without a valid matching interface.
|
||||
Pass navigator.credentials.get() aborted with custom reason
|
||||
Pass navigator.credentials.get() aborted with different objects
|
||||
Fail navigator.credentials.get() rejects when aborted after the promise creation
|
|
@ -0,0 +1,162 @@
|
|||
<!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");
|
||||
|
||||
promise_test(function(t) {
|
||||
const controller = new AbortController();
|
||||
controller.abort("custom reason");
|
||||
|
||||
return promise_rejects_exactly(t, "custom reason",
|
||||
navigator.credentials.create({ signal: controller.signal }));
|
||||
}, "navigator.credentials.create() aborted with custom reason");
|
||||
|
||||
promise_test(async function(t) {
|
||||
const reasons = [{}, [], new Error("custom error")];
|
||||
|
||||
for (let reason of reasons) {
|
||||
const result = navigator.credentials.create({ signal: AbortSignal.abort(reason) });
|
||||
await promise_rejects_exactly(t, reason, result);
|
||||
}
|
||||
}, "navigator.credentials.create() aborted with different objects");
|
||||
|
||||
promise_test(function(t) {
|
||||
const error = new Error("custom error");
|
||||
const controller = new AbortController();
|
||||
|
||||
const result = navigator.credentials.create({ signal: controller.signal });
|
||||
controller.abort(error); // aborted after the promise is created
|
||||
|
||||
return promise_rejects_exactly(t, error, result);
|
||||
}, "navigator.credentials.create() rejects when aborted after the promise creation");
|
||||
</script>
|
|
@ -0,0 +1,84 @@
|
|||
<!DOCTYPE html>
|
||||
<title>Credential Management API: get() basics.</title>
|
||||
<script src="../resources/testharness.js"></script>
|
||||
<script src="../resources/testharnessreport.js"></script>
|
||||
<script>
|
||||
promise_test(async (t) => {
|
||||
await promise_rejects_dom(
|
||||
t,
|
||||
"NotSupportedError",
|
||||
navigator.credentials.get()
|
||||
);
|
||||
|
||||
await promise_rejects_dom(
|
||||
t,
|
||||
"NotSupportedError",
|
||||
navigator.credentials.get({})
|
||||
);
|
||||
|
||||
await promise_rejects_dom(
|
||||
t,
|
||||
"NotSupportedError",
|
||||
navigator.credentials.get({ x: "y" })
|
||||
);
|
||||
|
||||
await promise_rejects_dom(
|
||||
t,
|
||||
"NotSupportedError",
|
||||
navigator.credentials.get({ x: "y", y: "z" })
|
||||
);
|
||||
|
||||
await promise_rejects_dom(
|
||||
t,
|
||||
"NotSupportedError",
|
||||
navigator.credentials.get({ x: "y" })
|
||||
);
|
||||
|
||||
await promise_rejects_dom(
|
||||
t,
|
||||
"NotSupportedError",
|
||||
navigator.credentials.get({ mediation: "required" })
|
||||
);
|
||||
|
||||
const abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
await promise_rejects_dom(
|
||||
t,
|
||||
"NotSupportedError",
|
||||
navigator.credentials.get({ signal })
|
||||
);
|
||||
|
||||
await promise_rejects_dom(
|
||||
t,
|
||||
"NotSupportedError",
|
||||
navigator.credentials.get({ signal, mediation: "required" })
|
||||
);
|
||||
}, "Calling navigator.credentials.get() without a valid matching interface.");
|
||||
|
||||
promise_test(function(t) {
|
||||
const controller = new AbortController();
|
||||
controller.abort("custom reason");
|
||||
|
||||
return promise_rejects_exactly(t, "custom reason",
|
||||
navigator.credentials.get({ signal: controller.signal }));
|
||||
}, "navigator.credentials.get() aborted with custom reason");
|
||||
|
||||
promise_test(async function(t) {
|
||||
const reasons = [{}, [], new Error("custom error")];
|
||||
|
||||
for (let reason of reasons) {
|
||||
const result = navigator.credentials.get({ signal: AbortSignal.abort(reason) });
|
||||
await promise_rejects_exactly(t, reason, result);
|
||||
}
|
||||
}, "navigator.credentials.get() aborted with different objects");
|
||||
|
||||
promise_test(function(t) {
|
||||
const error = new Error("custom error");
|
||||
const controller = new AbortController();
|
||||
|
||||
const result = navigator.credentials.get({ signal: controller.signal });
|
||||
controller.abort(error); // aborted after the promise is created
|
||||
|
||||
return promise_rejects_exactly(t, error, result);
|
||||
}, "navigator.credentials.get() rejects when aborted after the promise creation");
|
||||
</script>
|
Loading…
Add table
Reference in a new issue