Commit 21f1c419 authored by Leon Han's avatar Leon Han Committed by Commit Bot

[webnfc] Make NDEFReader#scan() return a Promise

Previously NDEFReader#scan() returns void and an NDEFErrorEvent will be
dispatched in case that the scan operation cannot be started
successfully.

This is not a well accepted pattern, and also to align with
NDEFWriter#push(), this CL makes NDEFReader#scan() return a Promise
instead.

Note that now NDEFErrorEvent is only used to notify Mojo disconnection,
a follow-up CL will remove it completely by using ErrorEvent instead.

The spec change:
https://github.com/w3c/web-nfc/pull/398
https://github.com/w3c/web-nfc/pull/432

BUG=520391

Change-Id: I1477258ab70f7e40da31ea8795d63125b6a13af0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1899586
Commit-Queue: Leon Han <leon.han@intel.com>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#716904}
parent e4b9e599
......@@ -7,6 +7,7 @@
#include <utility>
#include "services/device/public/mojom/nfc.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/core/dom/abort_signal.h"
#include "third_party/blink/renderer/modules/event_target_modules.h"
#include "third_party/blink/renderer/modules/nfc/ndef_error_event.h"
......@@ -19,6 +20,19 @@
namespace blink {
namespace {
void OnScanRequestCompleted(ScriptPromiseResolver* resolver,
device::mojom::blink::NDEFErrorPtr error) {
if (error) {
resolver->Reject(NDEFErrorTypeToDOMException(error->error_type));
return;
}
resolver->Resolve();
}
} // namespace
// static
NDEFReader* NDEFReader::Create(ExecutionContext* context) {
return MakeGarbageCollected<NDEFReader>(context);
......@@ -47,39 +61,70 @@ bool NDEFReader::HasPendingActivity() const {
}
// https://w3c.github.io/web-nfc/#the-scan-method
void NDEFReader::scan(const NDEFScanOptions* options) {
if (!CheckSecurity())
return;
ScriptPromise NDEFReader::scan(ScriptState* script_state,
const NDEFScanOptions* options,
ExceptionState& exception_state) {
ExecutionContext* execution_context = GetExecutionContext();
// https://w3c.github.io/web-nfc/#security-policies
// WebNFC API must be only accessible from top level browsing context.
if (!execution_context || !To<Document>(execution_context)->IsInMainFrame()) {
exception_state.ThrowDOMException(DOMExceptionCode::kNotAllowedError,
"NFC interfaces are only avaliable "
"in a top-level browsing context");
return ScriptPromise();
}
if (options->hasSignal()) {
// 6. If reader.[[Signal]]'s aborted flag is set, then return.
if (options->signal()->aborted())
return;
// 7. If reader.[[Signal]] is not null, then add the following abort steps
// to reader.[[Signal]]:
options->signal()->AddAlgorithm(
WTF::Bind(&NDEFReader::Abort, WrapPersistent(this)));
// 7. If reader.[[Signal]]'s aborted flag is set, then reject p with a
// "AbortError" DOMException and return p.
if (options->hasSignal() && options->signal()->aborted()) {
exception_state.ThrowDOMException(DOMExceptionCode::kAbortError,
"The NFC operation was cancelled.");
return ScriptPromise();
}
// Step 8.4, if the url is not an empty string and it is not a valid URL
// pattern, fire a NDEFErrorEvent with "SyntaxError" DOMException, then
// return.
// 9.4 If the reader.[[Id]] is not an empty string and it is not a valid URL
// pattern, then reject p with a "SyntaxError" DOMException and return p.
// TODO(https://crbug.com/520391): Instead of NDEFScanOptions#url, introduce
// and use NDEFScanOptions#id to do the filtering after we add support for
// writing NDEFRecord#id.
if (options->hasURL() && !options->url().IsEmpty()) {
KURL pattern_url(options->url());
if (!pattern_url.IsValid() || pattern_url.Protocol() != "https") {
DispatchEvent(*MakeGarbageCollected<NDEFErrorEvent>(
event_type_names::kError, MakeGarbageCollected<DOMException>(
DOMExceptionCode::kSyntaxError,
"Invalid URL pattern was provided.")));
return;
exception_state.ThrowDOMException(DOMExceptionCode::kSyntaxError,
"Invalid URL pattern was provided.");
return ScriptPromise();
}
}
GetNfcProxy()->StartReading(this, options);
// TODO(https://crbug.com/520391): With the note in
// https://w3c.github.io/web-nfc/#the-ndefreader-and-ndefwriter-objects,
// successive invocations of NDEFReader.scan() with new options should replace
// existing filters. For now we just reject this new scan() when there is an
// ongoing filter active.
if (GetNfcProxy()->IsReading(this)) {
exception_state.ThrowDOMException(
DOMExceptionCode::kInvalidStateError,
"There is already a scan() operation ongoing.");
return ScriptPromise();
}
resolver_ = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
// 8. If reader.[[Signal]] is not null, then add the following abort steps to
// reader.[[Signal]]:
if (options->hasSignal()) {
options->signal()->AddAlgorithm(WTF::Bind(&NDEFReader::Abort,
WrapPersistent(this),
WrapPersistent(resolver_.Get())));
}
GetNfcProxy()->StartReading(
this, options,
WTF::Bind(&OnScanRequestCompleted, WrapPersistent(resolver_.Get())));
return resolver_->Promise();
}
void NDEFReader::Trace(blink::Visitor* visitor) {
visitor->Trace(resolver_);
EventTargetWithInlineData::Trace(visitor);
ActiveScriptWrappable::Trace(visitor);
ContextLifecycleObserver::Trace(visitor);
......@@ -98,29 +143,31 @@ void NDEFReader::OnError(device::mojom::blink::NDEFErrorType error) {
event_type_names::kError, NDEFErrorTypeToDOMException(error)));
}
void NDEFReader::ContextDestroyed(ExecutionContext*) {
GetNfcProxy()->StopReading(this);
void NDEFReader::OnMojoConnectionError() {
// If |resolver_| has already settled this rejection is silently ignored.
if (resolver_) {
resolver_->Reject(NDEFErrorTypeToDOMException(
device::mojom::blink::NDEFErrorType::NOT_SUPPORTED));
}
// Dispatches an error event.
OnError(device::mojom::blink::NDEFErrorType::NOT_SUPPORTED);
}
void NDEFReader::Abort() {
void NDEFReader::ContextDestroyed(ExecutionContext*) {
// If |resolver_| has already settled this rejection is silently ignored.
if (resolver_) {
resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kAbortError,
"The execution context is going to be gone."));
}
GetNfcProxy()->StopReading(this);
}
bool NDEFReader::CheckSecurity() {
ExecutionContext* execution_context = GetExecutionContext();
if (!execution_context)
return false;
// https://w3c.github.io/web-nfc/#security-policies
// WebNFC API must be only accessible from top level browsing context.
if (!To<Document>(execution_context)->IsInMainFrame()) {
DispatchEvent(*MakeGarbageCollected<NDEFErrorEvent>(
event_type_names::kError,
MakeGarbageCollected<DOMException>(DOMExceptionCode::kNotAllowedError,
"NFC interfaces are only avaliable "
"in a top-level browsing context")));
return false;
}
return true;
void NDEFReader::Abort(ScriptPromiseResolver* resolver) {
// If |resolver| has already settled this rejection is silently ignored.
resolver->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kAbortError, "The NFC operation was cancelled."));
GetNfcProxy()->StopReading(this);
}
NFCProxy* NDEFReader::GetNfcProxy() const {
......
......@@ -7,6 +7,7 @@
#include "services/device/public/mojom/nfc.mojom-blink-forward.h"
#include "third_party/blink/renderer/bindings/core/v8/active_script_wrappable.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/core/dom/events/event_target.h"
#include "third_party/blink/renderer/core/execution_context/context_lifecycle_observer.h"
#include "third_party/blink/renderer/modules/modules_export.h"
......@@ -17,6 +18,7 @@ namespace blink {
class ExecutionContext;
class NFCProxy;
class NDEFScanOptions;
class ScriptPromiseResolver;
class MODULES_EXPORT NDEFReader : public EventTargetWithInlineData,
public ActiveScriptWrappable<NDEFReader>,
......@@ -39,24 +41,30 @@ class MODULES_EXPORT NDEFReader : public EventTargetWithInlineData,
DEFINE_ATTRIBUTE_EVENT_LISTENER(error, kError)
DEFINE_ATTRIBUTE_EVENT_LISTENER(reading, kReading)
void scan(const NDEFScanOptions*);
ScriptPromise scan(ScriptState*, const NDEFScanOptions*, ExceptionState&);
void Trace(blink::Visitor*) override;
// Called by NFCProxy for dispatching events.
virtual void OnReading(const String& serial_number,
const device::mojom::blink::NDEFMessage& message);
virtual void OnError(device::mojom::blink::NDEFErrorType error);
const device::mojom::blink::NDEFMessage&);
virtual void OnError(device::mojom::blink::NDEFErrorType);
// Called by NFCProxy for notification about connection error.
void OnMojoConnectionError();
private:
// ContextLifecycleObserver overrides.
void ContextDestroyed(ExecutionContext*) override;
void Abort();
void Abort(ScriptPromiseResolver*);
NFCProxy* GetNfcProxy() const;
bool CheckSecurity();
// |resolver_| is kept here to handle Mojo connection failures because in that
// case the callback passed to Watch() won't be called and
// mojo::WrapCallbackWithDefaultInvokeIfNotRun() is forbidden in Blink.
Member<ScriptPromiseResolver> resolver_;
};
} // namespace blink
......
......@@ -15,5 +15,6 @@
attribute EventHandler onreading;
attribute EventHandler onerror;
void scan(optional NDEFScanOptions options={});
[CallWith=ScriptState, RaisesException] Promise<void> scan(
optional NDEFScanOptions options={});
};
......@@ -52,16 +52,16 @@ void NFCProxy::Trace(blink::Visitor* visitor) {
}
void NFCProxy::StartReading(NDEFReader* reader,
const NDEFScanOptions* options) {
const NDEFScanOptions* options,
device::mojom::blink::NFC::WatchCallback callback) {
DCHECK(reader);
if (readers_.Contains(reader))
return;
DCHECK(!readers_.Contains(reader));
EnsureMojoConnection();
nfc_remote_->Watch(
device::mojom::blink::NDEFScanOptions::From(options), next_watch_id_,
WTF::Bind(&NFCProxy::OnReaderRegistered, WrapPersistent(this),
WrapPersistent(reader), next_watch_id_));
WrapPersistent(reader), next_watch_id_, std::move(callback)));
readers_.insert(reader, next_watch_id_);
next_watch_id_++;
}
......@@ -119,9 +119,11 @@ void NFCProxy::OnWatch(const Vector<uint32_t>& watch_ids,
}
}
void NFCProxy::OnReaderRegistered(NDEFReader* reader,
uint32_t watch_id,
device::mojom::blink::NDEFErrorPtr error) {
void NFCProxy::OnReaderRegistered(
NDEFReader* reader,
uint32_t watch_id,
device::mojom::blink::NFC::WatchCallback callback,
device::mojom::blink::NDEFErrorPtr error) {
DCHECK(reader);
// |reader| may have already stopped reading.
if (!readers_.Contains(reader))
......@@ -134,13 +136,15 @@ void NFCProxy::OnReaderRegistered(NDEFReader* reader,
return;
if (error) {
reader->OnError(error->error_type);
readers_.erase(reader);
std::move(callback).Run(std::move(error));
return;
}
// It's good the watch request has been accepted, we do nothing here but just
// wait for message notifications in OnWatch().
std::move(callback).Run(nullptr);
// It's good the watch request has been accepted, next we just wait for
// message notifications in OnWatch().
}
void NFCProxy::PageVisibilityChanged() {
......@@ -182,9 +186,7 @@ void NFCProxy::OnMojoConnectionError() {
// Notify all active readers about the connection error and clear the list.
ReaderMap readers = std::move(readers_);
for (auto& pair : readers) {
// The reader may call StopReading() to remove itself from |readers_| when
// handling the error.
pair.key->OnError(device::mojom::blink::NDEFErrorType::NOT_SUPPORTED);
pair.key->OnMojoConnectionError();
}
// Each connection maintains its own watch ID numbering, so reset to 1 on
......
......@@ -45,7 +45,9 @@ class MODULES_EXPORT NFCProxy final : public GarbageCollected<NFCProxy>,
// collected.
void AddWriter(NDEFWriter*);
void StartReading(NDEFReader*, const NDEFScanOptions*);
void StartReading(NDEFReader*,
const NDEFScanOptions*,
device::mojom::blink::NFC::WatchCallback);
void StopReading(NDEFReader*);
bool IsReading(const NDEFReader*);
void Push(device::mojom::blink::NDEFMessagePtr,
......@@ -61,6 +63,7 @@ class MODULES_EXPORT NFCProxy final : public GarbageCollected<NFCProxy>,
void OnReaderRegistered(NDEFReader*,
uint32_t watch_id,
device::mojom::blink::NFC::WatchCallback,
device::mojom::blink::NDEFErrorPtr);
// Implementation of PageVisibilityObserver.
......
......@@ -53,7 +53,6 @@ class MockNDEFReader : public NDEFReader {
MOCK_METHOD2(OnReading,
void(const String& serial_number,
const device::mojom::blink::NDEFMessage& message));
MOCK_METHOD1(OnError, void(device::mojom::blink::NDEFErrorType error));
};
class FakeNfcService : public device::mojom::blink::NFC {
......@@ -96,6 +95,10 @@ class FakeNfcService : public device::mojom::blink::NFC {
tag_message_ = std::move(message);
}
void set_watch_error(device::mojom::blink::NDEFErrorPtr error) {
watch_error_ = std::move(error);
}
WTF::Vector<uint32_t> GetWatches() {
WTF::Vector<uint32_t> ids;
for (auto& pair : watches_) {
......@@ -123,6 +126,10 @@ class FakeNfcService : public device::mojom::blink::NFC {
void Watch(device::mojom::blink::NDEFScanOptionsPtr options,
uint32_t id,
WatchCallback callback) override {
if (watch_error_) {
std::move(callback).Run(watch_error_.Clone());
return;
}
watches_.emplace(id, std::move(options));
std::move(callback).Run(nullptr);
}
......@@ -141,6 +148,7 @@ class FakeNfcService : public device::mojom::blink::NFC {
void SuspendNFCOperations() override {}
void ResumeNFCOperations() override {}
device::mojom::blink::NDEFErrorPtr watch_error_;
device::mojom::blink::NDEFMessagePtr tag_message_;
mojo::Remote<device::mojom::blink::NFCClient> client_;
std::map<uint32_t, device::mojom::blink::NDEFScanOptionsPtr> watches_;
......@@ -167,8 +175,6 @@ class NFCProxyTest : public PageTestBase {
FakeNfcService* nfc_service() { return nfc_service_.get(); }
void DestroyNfcService() { nfc_service_.reset(); }
private:
std::unique_ptr<FakeNfcService> nfc_service_;
};
......@@ -180,10 +186,18 @@ TEST_F(NFCProxyTest, SuccessfulPath) {
scan_options->setURL(kTestUrl);
auto* reader = MakeGarbageCollected<MockNDEFReader>(&document);
nfc_proxy->StartReading(reader, scan_options);
EXPECT_TRUE(nfc_proxy->IsReading(reader));
test::RunPendingTasks();
EXPECT_EQ(nfc_service()->GetWatches().size(), 1u);
{
base::RunLoop loop;
nfc_proxy->StartReading(reader, scan_options,
base::BindLambdaForTesting(
[&](device::mojom::blink::NDEFErrorPtr error) {
EXPECT_TRUE(error.is_null());
loop.Quit();
}));
EXPECT_TRUE(nfc_proxy->IsReading(reader));
loop.Run();
EXPECT_EQ(nfc_service()->GetWatches().size(), 1u);
}
// Construct a NDEFMessagePtr
auto message = device::mojom::blink::NDEFMessage::New();
......@@ -191,24 +205,26 @@ TEST_F(NFCProxyTest, SuccessfulPath) {
auto record = device::mojom::blink::NDEFRecord::New();
WTF::Vector<uint8_t> record_data(
{0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10});
record->record_type = "opaque";
record->record_type = "mime";
record->data = WTF::Vector<uint8_t>(record_data);
message->data.push_back(std::move(record));
base::RunLoop loop;
EXPECT_CALL(*reader, OnReading(String(kFakeNfcTagSerialNumber),
MessageEquals(record_data)))
.WillOnce(Invoke([&](const String& serial_number,
const device::mojom::blink::NDEFMessage& message) {
loop.Quit();
}));
nfc_proxy->Push(
std::move(message), /*options=*/nullptr,
base::BindLambdaForTesting([&](device::mojom::blink::NDEFErrorPtr error) {
nfc_service()->TriggerWatchEvent();
}));
loop.Run();
{
base::RunLoop loop;
EXPECT_CALL(*reader, OnReading(String(kFakeNfcTagSerialNumber),
MessageEquals(record_data)))
.WillOnce(Invoke([&](const String& serial_number,
const device::mojom::blink::NDEFMessage& message) {
loop.Quit();
}));
nfc_proxy->Push(std::move(message), /*options=*/nullptr,
base::BindLambdaForTesting(
[&](device::mojom::blink::NDEFErrorPtr error) {
nfc_service()->TriggerWatchEvent();
}));
loop.Run();
}
nfc_proxy->StopReading(reader);
EXPECT_FALSE(nfc_proxy->IsReading(reader));
......@@ -223,16 +239,23 @@ TEST_F(NFCProxyTest, ErrorPath) {
scan_options->setURL(kTestUrl);
auto* reader = MakeGarbageCollected<MockNDEFReader>(&document);
nfc_proxy->StartReading(reader, scan_options);
EXPECT_TRUE(nfc_proxy->IsReading(reader));
test::RunPendingTasks();
// Make the fake NFC service return an error for the incoming watch request.
nfc_service()->set_watch_error(device::mojom::blink::NDEFError::New(
device::mojom::blink::NDEFErrorType::NOT_READABLE));
base::RunLoop loop;
EXPECT_CALL(*reader, OnError(_))
.WillOnce(
Invoke([&](device::mojom::blink::NDEFErrorType) { loop.Quit(); }));
DestroyNfcService();
nfc_proxy->StartReading(
reader, scan_options,
base::BindLambdaForTesting([&](device::mojom::blink::NDEFErrorPtr error) {
// We got the error prepared before.
EXPECT_FALSE(error.is_null());
EXPECT_EQ(error->error_type,
device::mojom::blink::NDEFErrorType::NOT_READABLE);
loop.Quit();
}));
EXPECT_TRUE(nfc_proxy->IsReading(reader));
loop.Run();
EXPECT_EQ(nfc_service()->GetWatches().size(), 0u);
EXPECT_FALSE(nfc_proxy->IsReading(reader));
}
......
......@@ -9,15 +9,13 @@
promise_test(async t => {
const reader = new NDEFReader();
reader.scan();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
return await new Promise((resolve, reject) => {
readerWatcher.wait_for("reading").then(event => {
if (document.hidden) reject();
resolve();
});
const promise = readerWatcher.wait_for("reading").then(event => {
if (document.hidden) reject();
resolve();
});
await reader.scan();
await promise;
}, "Test NDEFReader.onreading is not fired when document is hidden");
</script>
......
......@@ -22,59 +22,56 @@ const invalid_signals = [
function waitSyntaxErrorPromise(t, scan_options) {
const reader = new NDEFReader();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
const promise = readerWatcher.wait_for("error").then(event => {
assert_equals(event.error.name, 'SyntaxError');
});
// NDEFReader#scan() asynchronously dispatches the syntax error event.
reader.scan(scan_options);
return promise;
return promise_rejects(t, 'SyntaxError', reader.scan(scan_options));
}
promise_test(async t => {
const reader = new NDEFReader();
const promises = [];
invalid_signals.forEach(invalid_signal => {
promises.push(promise_rejects(t, new TypeError(),
reader.scan({ signal: invalid_signal })));
});
await Promise.all(promises);
}, "Test that NDEFReader.scan rejects if signal is not an AbortSignal.");
promise_test(async t => {
await waitSyntaxErrorPromise(t, {url: "www.a.com"});
}, "Test that NDEFReader.scan fails if NDEFScanOptions.url is missing \
}, "Test that NDEFReader.scan rejects if NDEFScanOptions.url is missing \
components.");
promise_test(async t => {
await waitSyntaxErrorPromise(t, {url: "invalid"});
}, "Test that NDEFReader.scan fails if NDEFScanOptions.url is invalid.");
}, "Test that NDEFReader.scan rejects if NDEFScanOptions.url is invalid.");
promise_test(async t => {
await waitSyntaxErrorPromise(t, {url: "http://a.com"});
}, "Test that NDEFReader.scan fails if NDEFScanOptions.url has wrong \
}, "Test that NDEFReader.scan rejects if NDEFScanOptions.url has wrong \
protocol.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
reader.scan();
mockNFC.setHWStatus(NFCHWStatus.DISABLED);
const event = await readerWatcher.wait_for("error");
assert_equals(event.error.name, 'NotReadableError');
const reader = new NDEFReader();
await promise_rejects(t, 'NotReadableError', reader.scan());
}, "NDEFReader.scan should fail if NFC HW is disabled.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
reader.scan();
mockNFC.setHWStatus(NFCHWStatus.NOT_SUPPORTED);
const event = await readerWatcher.wait_for("error");
assert_equals(event.error.name, 'NotSupportedError');
const reader = new NDEFReader();
await promise_rejects(t, 'NotSupportedError', reader.scan());
}, "NDEFReader.scan should fail if NFC HW is not supported.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
const promise = readerWatcher.wait_for("reading").then(event => {
assert_true(event instanceof NDEFReadingEvent);
controller.abort();
});
// NDEFReader#scan() asynchronously dispatches the reading event.
reader.scan({signal : controller.signal});
await reader.scan({signal : controller.signal});
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
await promise;
}, "Test that nfc watch success if NFC HW is enabled.");
......@@ -82,14 +79,13 @@ nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
const promise = readerWatcher.wait_for("reading").then(event => {
assert_true(event instanceof NDEFReadingEvent);
controller.abort();
});
// NDEFReader#scan() asynchronously dispatches the reading event.
reader.scan({signal : controller.signal, url: "https://a.com"});
await reader.scan({signal : controller.signal, url: "https://a.com"});
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
await promise;
}, "Test that NDEFReader.scan succeeds if NDEFScanOptions.url is valid URL.");
......@@ -97,14 +93,13 @@ nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
const promise = readerWatcher.wait_for("reading").then(event => {
assert_true(event instanceof NDEFReadingEvent);
controller.abort();
});
// NDEFReader#scan() asynchronously dispatches the reading event.
reader.scan({signal : controller.signal, url: "https://a.com/*"});
await reader.scan({signal : controller.signal, url: "https://a.com/*"});
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
await promise;
}, "Test that NDEFReader.scan succeeds if NDEFScanOptions.url is valid URL \
with '*' wildcard character in path.");
......@@ -113,14 +108,13 @@ nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
const promise = readerWatcher.wait_for("reading").then(event => {
assert_true(event instanceof NDEFReadingEvent);
controller.abort();
});
// NDEFReader#scan() asynchronously dispatches the reading event.
reader.scan({signal : controller.signal, url: "https://a.com/*/bar"});
await reader.scan({signal : controller.signal, url: "https://a.com/*/bar"});
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
await promise;
}, "Test that NDEFReader.scan succeeds if NDEFScanOptions.url is valid URL \
with '*' wildcard character in the beginning of path component followed by \
......@@ -130,102 +124,90 @@ nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
const promise = readerWatcher.wait_for("reading").then(event => {
assert_true(event instanceof NDEFReadingEvent);
controller.abort();
});
// NDEFReader#scan() asynchronously dispatches the reading event.
reader.scan({signal : controller.signal, url: ""});
await reader.scan({signal : controller.signal, url: ""});
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
await promise;
}, "Test that NDEFReader.scan succeeds if NDEFScanOptions.url is empty.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
const controller = new AbortController();
controller.abort();
reader.scan({signal: controller.signal});
await new Promise((resolve, reject) => {
readerWatcher.wait_for("reading").then(event => {
reject("reading event should not be fired.");
});
t.step_timeout(resolve, 100);
});
await promise_rejects(t, 'AbortError', reader.scan({signal: controller.signal}));
}, "Test that NDEFReader.scan rejects if NDEFScanOptions.signal is already aborted.");
}, "Test that NDEFReader.onreading should not be fired if NDEFScanOptions.signal \
is aborted.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
const promise = reader.scan({signal: controller.signal});
controller.abort();
await promise_rejects(t, 'AbortError', promise);
}, "Test that NDEFReader.scan rejects if NDEFScanOptions.signal aborts right after \
the scan invocation.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
const message = createMessage([createTextRecord(test_text_data)]);
const promise = readerWatcher.wait_for("reading").then(event => {
assert_true(event instanceof NDEFReadingEvent);
});
await reader.scan({signal : controller.signal});
mockNFC.setReadingMessage(message);
await promise;
reader.scan({signal: controller.signal});
const event = await readerWatcher.wait_for("reading");
assert_true(event instanceof NDEFReadingEvent);
reader.onreading = t.unreached_func("reading event should not be fired.");
mockNFC.setReadingMessage(message);
controller.abort();
await new Promise((resolve, reject) => {
readerWatcher.wait_for("reading").then(event => {
reject("reading event should not be fired.");
});
t.step_timeout(resolve, 100);
});
}, "Synchronously signaled abort.");
}, "Test that NDEFReader can not get any reading events once the signal aborts.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
const promise = readerWatcher.wait_for("reading").then(event => {
controller.abort();
assert_true(event instanceof NDEFReadingEvent);
// The message contains only an external type record.
assert_equals(event.message.records.length, 1);
assert_equals(event.message.records[0].recordType, 'example.com:payloadIsMessage', 'recordType');
// The external type record's payload is a message, which contains only a text record.
const embeddedRecords = event.message.records[0].toRecords();
assert_equals(embeddedRecords.length, 1);
assert_equals(embeddedRecords[0].recordType, 'text', 'recordType');
assert_equals(embeddedRecords[0].mediaType, null, 'mediaType');
const decoder = new TextDecoder();
assert_equals(decoder.decode(embeddedRecords[0].data), test_text_data,
'data has the same content with the original dictionary');
});
await reader.scan({signal : controller.signal});
const payloadMessage = createMessage([createTextRecord(test_text_data)]);
const message = createMessage([createRecord('example.com:payloadIsMessage',
payloadMessage)]);
mockNFC.setReadingMessage(message);
reader.scan({signal : controller.signal});
const event = await readerWatcher.wait_for("reading");
controller.abort();
assert_true(event instanceof NDEFReadingEvent);
// The message contains only an external type record.
assert_equals(event.message.records.length, 1);
assert_equals(event.message.records[0].recordType, 'example.com:payloadIsMessage', 'recordType');
// The external type record's payload is a message, which contains only a text record.
const embeddedRecords = event.message.records[0].toRecords();
assert_equals(embeddedRecords.length, 1);
assert_equals(embeddedRecords[0].recordType, 'text', 'recordType');
assert_equals(embeddedRecords[0].mediaType, null, 'mediaType');
const decoder = new TextDecoder();
assert_equals(decoder.decode(embeddedRecords[0].data), test_text_data,
'data has the same content with the original dictionary');
await promise;
}, "NDEFRecord.toRecords returns its embedded records correctly.");
test(() => {
const reader = new NDEFReader();
invalid_signals.forEach(invalid_signal => {
assert_throws(new TypeError(),
() => { reader.scan({ signal: invalid_signal }); });
});
}, "NDEFReader.scan should fail if signal is not an AbortSignal.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
mockNFC.setIsNDEFTech(false);
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
const reader = new NDEFReader();
reader.onreading = t.unreached_func("reading event should not be fired.");
await reader.scan();
mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)]));
await new Promise((resolve, reject) => {
reader.onreading = () => reject("reading event should not be fired.");
reader.scan();
t.step_timeout(resolve, 100);
});
}, "Test that NDEFReader.onreading should not be fired if the NFC tag does not \
......@@ -234,16 +216,15 @@ expose NDEF technology.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
mockNFC.setReadingMessage({ records: [] });
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
const promise = readerWatcher.wait_for("reading").then(event => {
assert_equals(event.serialNumber, fake_tag_serial_number);
assert_equals(event.message.records.length, 0);
controller.abort();
});
// NDEFReader#scan() asynchronously dispatches the reading event.
reader.scan({signal : controller.signal});
await reader.scan({signal : controller.signal});
mockNFC.setReadingMessage({ records: [] });
await promise;
}, "Test that NDEFReader.onreading should be fired on an unformatted NFC tag \
with empty records array for NDEFMessage.");
......
......@@ -17,7 +17,7 @@ nfc_test(async (t, mockNFC) => {
assert_true(event instanceof NDEFReadingEvent);
controller.abort();
});
reader.scan({ signal: controller.signal });
await reader.scan({ signal: controller.signal });
const iframe = document.createElement('iframe');
iframe.src = 'resources/support-iframe.html';
......@@ -40,4 +40,4 @@ nfc_test(async (t, mockNFC) => {
}, 'Test that NDEFWriter.scan is not suspended if iframe gains focus.');
</script>
</body>
\ No newline at end of file
</body>
......@@ -316,18 +316,18 @@ nfc_test(async (t, mockNFC) => {
const message = createMessage([createTextRecord(test_text_data)]);
const controller = new AbortController();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
const promise = readerWatcher.wait_for("reading").then(event => {
controller.abort();
assertWebNDEFMessagesEqual(event.message, new NDEFMessage(message));
});
reader.scan({ signal: controller.signal });
await reader.scan({ signal: controller.signal });
const writer = new NDEFWriter();
await writer.push(test_text_data, { ignoreRead: false });
mockNFC.setReadingMessage(message);
assertNDEFMessagesEqual(test_text_data, mockNFC.pushedMessage());
await promise;
mockNFC.setReadingMessage(message);
await readerWatcher.wait_for("reading").then(event => {
controller.abort();
assertWebNDEFMessagesEqual(event.message, new NDEFMessage(message));
});
}, "NDEFWriter.push should read data when ignoreRead is false.");
nfc_test(async (t, mockNFC) => {
......@@ -335,7 +335,7 @@ nfc_test(async (t, mockNFC) => {
const message = createMessage([createTextRecord(test_text_data)]);
// Ignore reading if NDEFPushOptions.ignoreRead is true
reader.onreading = t.unreached_func("reading event should not be fired.");
reader.scan();
await reader.scan();
const writer = new NDEFWriter();
await writer.push(test_text_data, { ignoreRead: true });
......
This is a testharness.js-based test.
Found 72 tests; 70 PASS, 2 FAIL, 0 TIMEOUT, 0 NOTRUN.
Found 72 tests; 71 PASS, 1 FAIL, 0 TIMEOUT, 0 NOTRUN.
PASS idl_test setup
PASS idl_test validation
PASS NDEFMessage interface: existence and properties of interface object
......@@ -53,7 +53,7 @@ PASS NDEFReader interface: existence and properties of interface prototype objec
PASS NDEFReader interface: existence and properties of interface prototype object's @@unscopables property
PASS NDEFReader interface: attribute onerror
PASS NDEFReader interface: attribute onreading
FAIL NDEFReader interface: operation scan(NDEFScanOptions) assert_unreached: Throws "TypeError: Illegal invocation" instead of rejecting promise Reached unreachable code
PASS NDEFReader interface: operation scan(NDEFScanOptions)
PASS NDEFReader must be primary interface of new NDEFReader();
PASS Stringification of new NDEFReader();
PASS NDEFReader interface: new NDEFReader(); must inherit property "onerror" with the proper type
......
......@@ -174,22 +174,20 @@ function testNDEFScanOptions(message, scanOptions, unmatchedScanOptions, desc) {
const reader2 = new NDEFReader();
const controller = new AbortController();
mockNFC.setReadingMessage(message);
// Reading from unmatched reader will not be triggered
reader1.onreading = t.unreached_func("reading event should not be fired.");
unmatchedScanOptions.signal = controller.signal;
reader1.scan(unmatchedScanOptions);
await reader1.scan(unmatchedScanOptions);
const readerWatcher = new EventWatcher(t, reader2, ["reading", "error"]);
const promise = readerWatcher.wait_for("reading").then(event => {
controller.abort();
assertWebNDEFMessagesEqual(event.message, new NDEFMessage(message));
});
// NDEFReader#scan() asynchronously dispatches the onreading event.
scanOptions.signal = controller.signal;
reader2.scan(scanOptions);
await reader2.scan(scanOptions);
mockNFC.setReadingMessage(message);
await promise;
}, desc);
}
......@@ -200,19 +198,16 @@ function testReadingMultiMessages(
const reader = new NDEFReader();
const controller = new AbortController();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
const promise = readerWatcher.wait_for("reading").then(event => {
controller.abort();
assertWebNDEFMessagesEqual(event.message, new NDEFMessage(message));
});
// NDEFReader#scan() asynchronously dispatches the onreading event.
scanOptions.signal = controller.signal;
reader.scan(scanOptions);
await reader.scan(scanOptions);
// Unmatched message will not be read
mockNFC.setReadingMessage(unmatchedMessage);
mockNFC.setReadingMessage(message);
await promise;
}, desc);
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment