Require a user gesture for the Contact API
Also provide a series of new tests to cover the implementation,
particularly this requirement.
Bug: 860467
Change-Id: I274e433fadba5228ac5dc0853b5a30354904d43e
Reviewed-on: https://chromium-review.googlesource.com/c/1430014
Commit-Queue: Peter Beverloo <peter@chromium.org>
Reviewed-by: Finnur Thorarinsson <finnur@chromium.org>
Cr-Commit-Position: refs/heads/master@{#625333}
diff --git a/third_party/blink/renderer/modules/contacts_picker/contacts_manager.cc b/third_party/blink/renderer/modules/contacts_picker/contacts_manager.cc
index 75e6642d..2512851 100644
--- a/third_party/blink/renderer/modules/contacts_picker/contacts_manager.cc
+++ b/third_party/blink/renderer/modules/contacts_picker/contacts_manager.cc
@@ -8,8 +8,10 @@
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
+#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
+#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/modules/contacts_picker/contact_info.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/visitor.h"
@@ -80,6 +82,15 @@
ScriptPromise ContactsManager::select(ScriptState* script_state,
ContactsSelectOptions* options) {
+ Document* document = To<Document>(ExecutionContext::From(script_state));
+ if (!LocalFrame::HasTransientUserActivation(document ? document->GetFrame()
+ : nullptr)) {
+ return ScriptPromise::Reject(
+ script_state, V8ThrowException::CreateTypeError(
+ script_state->GetIsolate(),
+ "A user gesture is required to call this method"));
+ }
+
if (!options->hasProperties() || !options->properties().size()) {
return ScriptPromise::Reject(script_state,
V8ThrowException::CreateTypeError(
@@ -114,14 +125,15 @@
void ContactsManager::OnContactsSelected(
ScriptPromiseResolver* resolver,
base::Optional<Vector<mojom::blink::ContactInfoPtr>> contacts) {
+ ScriptState* script_state = resolver->GetScriptState();
+ ScriptState::Scope scope(script_state);
+
if (!contacts.has_value()) {
- resolver->Reject(DOMException::Create(DOMExceptionCode::kAbortError,
- "Unable to open a contact selector"));
+ resolver->Reject(V8ThrowException::CreateTypeError(
+ script_state->GetIsolate(), "Unable to open a contact selector"));
return;
}
- ScriptState::Scope scope(resolver->GetScriptState());
-
HeapVector<Member<ContactInfo>> contacts_list;
for (const auto& contact : *contacts)
contacts_list.push_back(contact.To<blink::ContactInfo*>());
diff --git a/third_party/blink/web_tests/http/tests/contacts/idl-NavigatorContacts.html b/third_party/blink/web_tests/http/tests/contacts/idl-NavigatorContacts.html
deleted file mode 100644
index b942de5..0000000
--- a/third_party/blink/web_tests/http/tests/contacts/idl-NavigatorContacts.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<!DOCTYPE html>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script>
-test(function() {
- assert_true('contacts' in navigator,
- 'navigator.contacts exists in navigator.');
-}, 'navigator.contacts IDL test');
-</script>
diff --git a/third_party/blink/web_tests/http/tests/contacts/select-function.html b/third_party/blink/web_tests/http/tests/contacts/select-function.html
index 0fcf211..346017a 100644
--- a/third_party/blink/web_tests/http/tests/contacts/select-function.html
+++ b/third_party/blink/web_tests/http/tests/contacts/select-function.html
@@ -1,13 +1,74 @@
-<!DOCTYPE html>
-<script src="../resources/testharness.js"></script>
-<script src="../resources/testharnessreport.js"></script>
-<script src="resources.js"></script>
-
+<!doctype html>
+<meta charset="utf-8">
+<title>Contact API: Behaviour of the select() function</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
<script>
-promise_test(function() {
- return assert_promise_rejects_with_message(
- navigator.contacts.select({multiple: true, properties: ['name', 'email', 'tel']}),
- new DOMException('Unable to open a contact selector', 'AbortError'),
- 'navigator.contacts.select');
-}, 'Select function throws exception.');
+'use strict';
+
+// Creates a "user gesture" using Blink's test-only eventSender.
+function triggerUserGesture() {
+ if (!window.eventSender)
+ throw new Error('The `eventSender` must be available for this test');
+
+ eventSender.mouseDown();
+ eventSender.mouseUp();
+}
+
+// Verifies that |func|, when invoked, throws a TypeError exception.
+async function expectTypeError(func) {
+ try {
+ await func();
+ } catch (e) {
+ assert_equals(e.name, 'TypeError');
+ return;
+ }
+
+ assert_unreached('expected a TypeError, but none was thrown');
+}
+
+test(() => {
+ // Exposure of the interface and method.
+ assert_own_property(window, 'ContactsManager');
+ assert_own_property(ContactsManager.prototype, 'select');
+
+ // Exposure of the instance.
+ assert_idl_attribute(navigator, 'contacts');
+ assert_idl_attribute(navigator.contacts, 'select');
+
+}, 'The Contact API is exposed on the Window context');
+
+promise_test(async () => {
+ await expectTypeError(() =>
+ navigator.contacts.select({ properties: ['name'] }));
+
+}, 'The Contact API requires a user gesture')
+
+promise_test(async () => {
+ triggerUserGesture();
+
+ // At least one property must be provided.
+ await expectTypeError(() => navigator.contacts.select());
+ await expectTypeError(() => navigator.contacts.select({ properties: [] }));
+
+ // Per WebIDL parsing, no invalid values may be provided.
+ await expectTypeError(() =>
+ navigator.contacts.select({ properties: [''] }));
+ await expectTypeError(() =>
+ navigator.contacts.select({ properties: ['foo'] }));
+ await expectTypeError(() =>
+ navigator.contacts.select({ properties: ['name', 'photo'] }));
+
+}, 'The Contact API requires at least one valid property to be provided');
+
+promise_test(async () => {
+ triggerUserGesture();
+
+ // TODO(peter): Fake the Mojo interface so that we can extend this test with
+ // valid behaviour, and actually verify the API's functionality.
+ await expectTypeError(() =>
+ navigator.contacts.select({ properties: ['name'] }));
+
+}, 'The Contact API can fail when the selector cannot be opened');
+
</script>