Commit ed4717cb authored by Rijubrata Bhaumik's avatar Rijubrata Bhaumik Committed by Commit Bot

[webnfc] Do not support multiple scans on the same reader.

We added support for multiple scan invocations on the same Reader in
this CL https://chromium-review.googlesource.com/c/chromium/src/+/2145263

Since Origin Trials feedback was to remove Filtering,
spec : https://github.com/w3c/web-nfc/pull/565
WIP CL : https://chromium-review.googlesource.com/c/chromium/src/+/2225770/
we tried to reason if in a world without filtering, multiple scans on
the same Reader is needed at all. The present thinking is to simply
reject scan promise when there's already an ongoing scan.

Spec discussions: https://github.com/w3c/web-nfc/issues/592

Bug: 520391
Change-Id: Ic50873ddc30351cec6656ac7b030394dc9fa18ea
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2297559
Commit-Queue: Rijubrata Bhaumik <rijubrata.bhaumik@intel.com>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#790438}
parent d16f904f
...@@ -37,6 +37,7 @@ namespace { ...@@ -37,6 +37,7 @@ namespace {
constexpr char kNotSupportedOrPermissionDenied[] = constexpr char kNotSupportedOrPermissionDenied[] =
"WebNFC feature is unavailable or permission denied."; "WebNFC feature is unavailable or permission denied.";
constexpr char kInvalidStateErrorMessage[] = "A scan() operation is ongoing.";
} // namespace } // namespace
// static // static
...@@ -93,21 +94,27 @@ ScriptPromise NDEFReader::scan(ScriptState* script_state, ...@@ -93,21 +94,27 @@ ScriptPromise NDEFReader::scan(ScriptState* script_state,
return ScriptPromise(); return ScriptPromise();
} }
// With the note in if (has_pending_scan_request_) {
// https://w3c.github.io/web-nfc/#the-ndefreader-and-ndefwriter-objects, exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
// successive invocations of NDEFReader.scan() with new options should replace kInvalidStateErrorMessage);
// existing filters. So stop current reading for this case. return ScriptPromise();
}
has_pending_scan_request_ = true;
// https://github.com/w3c/web-nfc/issues/592
// reject scan promise when there's already an ongoing scan.
if (GetNfcProxy()->IsReading(this)) { if (GetNfcProxy()->IsReading(this)) {
Abort(signal_.Get()); exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
kInvalidStateErrorMessage);
return ScriptPromise();
} }
resolver_ = MakeGarbageCollected<ScriptPromiseResolver>(script_state); resolver_ = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
// 8. If reader.[[Signal]] is not null, then add the following abort steps to // 8. If reader.[[Signal]] is not null, then add the following abort steps
// reader.[[Signal]]: // to reader.[[Signal]]:
if (options->hasSignal()) { if (options->hasSignal()) {
signal_ = options->signal(); options->signal()->AddAlgorithm(
signal_->AddAlgorithm(WTF::Bind(&NDEFReader::Abort, WrapPersistent(this), WTF::Bind(&NDEFReader::Abort, WrapPersistent(this)));
WrapPersistent(options->signal())));
} }
GetPermissionService()->RequestPermission( GetPermissionService()->RequestPermission(
...@@ -130,16 +137,20 @@ PermissionService* NDEFReader::GetPermissionService() { ...@@ -130,16 +137,20 @@ PermissionService* NDEFReader::GetPermissionService() {
void NDEFReader::OnRequestPermission(const NDEFScanOptions* options, void NDEFReader::OnRequestPermission(const NDEFScanOptions* options,
PermissionStatus status) { PermissionStatus status) {
if (!resolver_) if (!resolver_) {
has_pending_scan_request_ = false;
return; return;
}
if (status != PermissionStatus::GRANTED) { if (status != PermissionStatus::GRANTED) {
has_pending_scan_request_ = false;
resolver_->Reject(MakeGarbageCollected<DOMException>( resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kNotAllowedError, "NFC permission request denied.")); DOMExceptionCode::kNotAllowedError, "NFC permission request denied."));
resolver_.Clear(); resolver_.Clear();
return; return;
} }
if (options->hasSignal() && options->signal()->aborted()) { if (options->hasSignal() && options->signal()->aborted()) {
has_pending_scan_request_ = false;
resolver_->Reject(MakeGarbageCollected<DOMException>( resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kAbortError, "The NFC operation was cancelled.")); DOMExceptionCode::kAbortError, "The NFC operation was cancelled."));
resolver_.Clear(); resolver_.Clear();
...@@ -157,6 +168,7 @@ void NDEFReader::OnRequestPermission(const NDEFScanOptions* options, ...@@ -157,6 +168,7 @@ void NDEFReader::OnRequestPermission(const NDEFScanOptions* options,
void NDEFReader::OnScanRequestCompleted( void NDEFReader::OnScanRequestCompleted(
device::mojom::blink::NDEFErrorPtr error) { device::mojom::blink::NDEFErrorPtr error) {
has_pending_scan_request_ = false;
if (!resolver_) if (!resolver_)
return; return;
...@@ -173,7 +185,6 @@ void NDEFReader::OnScanRequestCompleted( ...@@ -173,7 +185,6 @@ void NDEFReader::OnScanRequestCompleted(
void NDEFReader::Trace(Visitor* visitor) const { void NDEFReader::Trace(Visitor* visitor) const {
visitor->Trace(permission_service_); visitor->Trace(permission_service_);
visitor->Trace(resolver_); visitor->Trace(resolver_);
visitor->Trace(signal_);
EventTargetWithInlineData::Trace(visitor); EventTargetWithInlineData::Trace(visitor);
ActiveScriptWrappable::Trace(visitor); ActiveScriptWrappable::Trace(visitor);
ExecutionContextLifecycleObserver::Trace(visitor); ExecutionContextLifecycleObserver::Trace(visitor);
...@@ -217,14 +228,7 @@ void NDEFReader::ContextDestroyed() { ...@@ -217,14 +228,7 @@ void NDEFReader::ContextDestroyed() {
GetNfcProxy()->StopReading(this); GetNfcProxy()->StopReading(this);
} }
void NDEFReader::Abort(AbortSignal* signal) { void NDEFReader::Abort() {
// In the case of successive invocations of NDEFReader.scan() with
// different signals, we should make sure aborting on previous signal
// won't abort current reading.
// If this is not triggered by the current signal, just ignore it.
if (signal && signal != signal_)
return;
if (resolver_) { if (resolver_) {
resolver_->Reject(MakeGarbageCollected<DOMException>( resolver_->Reject(MakeGarbageCollected<DOMException>(
DOMExceptionCode::kAbortError, "The NFC operation was cancelled.")); DOMExceptionCode::kAbortError, "The NFC operation was cancelled."));
......
...@@ -18,7 +18,6 @@ ...@@ -18,7 +18,6 @@
namespace blink { namespace blink {
class AbortSignal;
class ExecutionContext; class ExecutionContext;
class NFCProxy; class NFCProxy;
class NDEFScanOptions; class NDEFScanOptions;
...@@ -61,7 +60,7 @@ class MODULES_EXPORT NDEFReader : public EventTargetWithInlineData, ...@@ -61,7 +60,7 @@ class MODULES_EXPORT NDEFReader : public EventTargetWithInlineData,
// ExecutionContextLifecycleObserver overrides. // ExecutionContextLifecycleObserver overrides.
void ContextDestroyed() override; void ContextDestroyed() override;
void Abort(AbortSignal* signal); void Abort();
NFCProxy* GetNfcProxy() const; NFCProxy* GetNfcProxy() const;
...@@ -79,11 +78,8 @@ class MODULES_EXPORT NDEFReader : public EventTargetWithInlineData, ...@@ -79,11 +78,8 @@ class MODULES_EXPORT NDEFReader : public EventTargetWithInlineData,
// case the callback passed to Watch() won't be called and // case the callback passed to Watch() won't be called and
// mojo::WrapCallbackWithDefaultInvokeIfNotRun() is forbidden in Blink. // mojo::WrapCallbackWithDefaultInvokeIfNotRun() is forbidden in Blink.
Member<ScriptPromiseResolver> resolver_; Member<ScriptPromiseResolver> resolver_;
// To reject if there is already an ongoing scan.
// Currently AbortSignal has no method to remove an algorithm so this bool has_pending_scan_request_ = false;
// field tracks the most recently configured AbortSignal so that others
// can be ignored.
Member<AbortSignal> signal_;
}; };
} // namespace blink } // namespace blink
......
...@@ -284,4 +284,19 @@ nfc_test(async (t, mockNFC) => { ...@@ -284,4 +284,19 @@ nfc_test(async (t, mockNFC) => {
await promise; await promise;
}, "Test that reading message with multiple records should succeed."); }, "Test that reading message with multiple records should succeed.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const promise1 = reader.scan();
const promise2 = promise_rejects_dom(t, 'InvalidStateError', reader.scan());
await promise1;
await promise2;
}, "Test that NDEFReader.scan rejects if there is already an ongoing scan.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
await reader.scan({signal : controller.signal});
controller.abort();
await reader.scan();
}, "Test that NDEFReader.scan can be started after the previous scan is aborted.");
</script> </script>
...@@ -162,79 +162,4 @@ for (let multiMessagesTest of multiMessagesTests) { ...@@ -162,79 +162,4 @@ for (let multiMessagesTest of multiMessagesTests) {
); );
} }
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
const signal = controller.signal;
const textMsg = createMessage([createTextRecord(test_text_data)]);
const urlMsg = createMessage([createUrlRecord(test_url_data)]);
const mimeMsg = createMessage([createMimeRecord(test_buffer_data)]);
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
const promise = readerWatcher.wait_for("reading").then(event => {
controller.abort();
assertWebNDEFMessagesEqual(event.message, new NDEFMessage(mimeMsg));
});
const scanOptions1 = {recordType: "text", signal: signal};
const scanOptions2 = {recordType: "url", signal: signal};
const scanOptions3 = {recordType: "mime", signal: signal};
await reader.scan(scanOptions1);
await reader.scan(scanOptions2);
// There is maximum one filter for an NDEFReader object,
// last filter will replace all previous ones.
await reader.scan(scanOptions3);
mockNFC.setReadingMessage(textMsg);
mockNFC.setReadingMessage(urlMsg);
mockNFC.setReadingMessage(mimeMsg);
await promise;
}, "Multiple scan() from the same NDEFReader object with new options should \
replace existing filters.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller1 = new AbortController();
const controller2 = new AbortController();
const urlMsg = createMessage([createUrlRecord(test_url_data)]);
const mimeMsg = createMessage([createMimeRecord(test_buffer_data)]);
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
const promise = readerWatcher.wait_for("reading").then(event => {
assertWebNDEFMessagesEqual(event.message, new NDEFMessage(mimeMsg));
controller2.abort();
});
const scanOptions1 = {recordType: "url", signal: controller1.signal};
const scanOptions2 = {recordType: "mime", signal: controller2.signal};
// There is maximum one filter for an NDEFReader object,
// last filter will replace all previous ones.
await reader.scan(scanOptions1);
await reader.scan(scanOptions2);
mockNFC.setReadingMessage(urlMsg);
controller1.abort();
mockNFC.setReadingMessage(mimeMsg);
await promise;
}, "Aborting on previous signal should not stop current reading for multiple \
scan() with different signals.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
const scanOptions1 = {recordType: "url", signal: controller.signal };
const scanOptions2 = {recordType: "mime"};
await reader.scan(scanOptions1);
const promise = reader.scan(scanOptions2);
controller.abort();
await promise_rejects_dom(t, 'AbortError', promise);
}, "Aborting on previous signal can stop current reading if no new signals \
passed to successive scan().");
</script> </script>
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