Commit 202a08e1 authored by Leon Han's avatar Leon Han Committed by Commit Bot

[webnfc] toRecords() implementation

This CL adds support for reading sub records of a NDEFRecord,
i.e. NDEFRecord#toRecords().

Note: No support for writing yet.

The spec changes:
https://github.com/w3c/web-nfc/pull/333
https://github.com/w3c/web-nfc/pull/334
https://github.com/w3c/web-nfc/pull/359

BUG=520391

Change-Id: Ic7ac74416afa8e43f5293e4ca85e538fc700f3c6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1824395
Commit-Queue: Leon Han <leon.han@intel.com>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Cr-Commit-Position: refs/heads/master@{#712452}
parent 588ad09c
......@@ -5,6 +5,7 @@
package org.chromium.device.nfc;
import android.net.Uri;
import android.nfc.FormatException;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.device.mojom.NdefMessage;
......@@ -130,6 +131,8 @@ public final class NdefMessageUtils {
// TODO(https://crbug.com/520391): Support 'smart-poster' type records.
throw new InvalidNdefMessageException();
}
// TODO(https://crbug.com/520391): Need to create an external record for either a custom
// type name or a local type name (for an embedded record).
PairOfDomainAndType pair = parseDomainAndType(record.recordType);
if (pair != null) {
return android.nfc.NdefRecord.createExternal(pair.mDomain, pair.mType, record.data);
......@@ -276,11 +279,13 @@ public final class NdefMessageUtils {
/**
* Constructs External type NdefRecord
*/
private static NdefRecord createExternalTypeRecord(String customType, byte[] payload) {
private static NdefRecord createExternalTypeRecord(String type, byte[] payload) {
// |type| may be a custom type name or a local type name (for an embedded record).
NdefRecord nfcRecord = new NdefRecord();
nfcRecord.recordType = customType;
nfcRecord.recordType = type;
nfcRecord.mediaType = OCTET_STREAM_MIME;
nfcRecord.data = payload;
nfcRecord.payloadMessage = getNdefMessageFromPayload(payload);
return nfcRecord;
}
......@@ -340,4 +345,17 @@ public final class NdefMessageUtils {
buffer.put(record.data);
return buffer.array();
}
/**
* Tries to construct a android.nfc.NdefMessage from the raw bytes |payload| then converts it to
* a Mojo NdefMessage and returns. Returns null for anything wrong.
*/
private static NdefMessage getNdefMessageFromPayload(byte[] payload) {
try {
android.nfc.NdefMessage payloadMessage = new android.nfc.NdefMessage(payload);
return toNdefMessage(payloadMessage);
} catch (FormatException | UnsupportedEncodingException e) {
}
return null;
}
}
......@@ -352,6 +352,27 @@ public class NFCTest {
assertNull(extMojoNdefMessage.data[0].lang);
assertEquals(TEST_TEXT, new String(extMojoNdefMessage.data[0].data));
// Test conversion for external records with the payload being a ndef message.
android.nfc.NdefMessage payloadMessage = new android.nfc.NdefMessage(
android.nfc.NdefRecord.createTextRecord(LANG_EN_US, TEST_TEXT));
byte[] payloadBytes = payloadMessage.toByteArray();
// Put |payloadBytes| as payload of an external record.
android.nfc.NdefMessage extNdefMessage1 =
new android.nfc.NdefMessage(android.nfc.NdefRecord.createExternal(
DUMMY_EXTERNAL_RECORD_DOMAIN, DUMMY_EXTERNAL_RECORD_TYPE, payloadBytes));
NdefMessage extMojoNdefMessage1 = NdefMessageUtils.toNdefMessage(extNdefMessage1);
assertEquals(1, extMojoNdefMessage1.data.length);
assertEquals(DUMMY_EXTERNAL_RECORD_DOMAIN + ':' + DUMMY_EXTERNAL_RECORD_TYPE,
extMojoNdefMessage1.data[0].recordType);
assertEquals(OCTET_STREAM_MIME, extMojoNdefMessage1.data[0].mediaType);
// The embedded ndef message should have content corresponding with the original
// |payloadMessage|.
NdefMessage payloadMojoMessage = extMojoNdefMessage1.data[0].payloadMessage;
assertEquals(1, payloadMojoMessage.data.length);
assertEquals(NdefMessageUtils.RECORD_TYPE_TEXT, payloadMojoMessage.data[0].recordType);
assertEquals(TEXT_MIME, payloadMojoMessage.data[0].mediaType);
assertEquals(TEST_TEXT, new String(payloadMojoMessage.data[0].data));
// Test NdefMessage with an additional WebNFC author record.
android.nfc.NdefRecord jsonNdefRecord = android.nfc.NdefRecord.createMime(
JSON_MIME, ApiCompatibilityUtils.getBytesUtf8(TEST_JSON));
......
......@@ -55,6 +55,11 @@ struct NDEFRecord {
// Payload of the NDEFRecord.
array<uint8> data;
// |data| parsed as an NDEFMessage. This field may be set for some
// "smart-poster" or external type records. This field may be null even if
// |data| is valid.
NDEFMessage? payload_message;
};
struct NDEFMessage {
......
......@@ -12,6 +12,7 @@
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/typed_arrays/dom_data_view.h"
#include "third_party/blink/renderer/modules/nfc/ndef_message.h"
#include "third_party/blink/renderer/modules/nfc/ndef_record_init.h"
#include "third_party/blink/renderer/modules/nfc/nfc_utils.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
......@@ -229,6 +230,8 @@ static NDEFRecord* CreateUnknownRecord(const String& media_type,
static NDEFRecord* CreateExternalRecord(const String& custom_type,
const ScriptValue& data,
ExceptionState& exception_state) {
// TODO(https://crbug.com/520391): Add support in case of |data| being an
// NDEFMessageInit.
// https://w3c.github.io/web-nfc/#dfn-map-external-data-to-ndef
if (data.IsEmpty() || !data.V8Value()->IsArrayBuffer()) {
exception_state.ThrowTypeError(
......@@ -290,6 +293,11 @@ NDEFRecord* NDEFRecord::Create(const ExecutionContext* execution_context,
exception_state.ThrowTypeError("smart-poster type is not supported yet");
return nullptr;
} else {
// TODO(https://crbug.com/520391): Here |record_type| may be a custom type
// name for an external type record, or a local type name for an external
// type record embedded within a ndef message as payload of a parent record,
// in either case we should try to create an external type record from
// |data|.
String formated_type = ValidateCustomRecordType(record_type);
if (!formated_type.IsNull())
return CreateExternalRecord(formated_type, init->data(), exception_state);
......@@ -336,7 +344,12 @@ NDEFRecord::NDEFRecord(const device::mojom::blink::NDEFRecord& record)
id_(record.id),
encoding_(record.encoding),
lang_(record.lang),
payload_data_(record.data) {}
payload_data_(record.data) {
if (record.payload_message) {
payload_message_ =
MakeGarbageCollected<NDEFMessage>(*record.payload_message);
}
}
const String& NDEFRecord::recordType() const {
return record_type_;
......@@ -368,7 +381,26 @@ const WTF::Vector<uint8_t>& NDEFRecord::payloadData() const {
return payload_data_;
}
// https://w3c.github.io/web-nfc/#dfn-convert-ndefrecord-payloaddata-bytes
base::Optional<HeapVector<Member<NDEFRecord>>> NDEFRecord::toRecords(
ExceptionState& exception_state) const {
if (record_type_ != "smart-poster" &&
ValidateCustomRecordType(record_type_).IsNull()) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotSupportedError,
"Only smart-poster records and external type records could have a ndef "
"message as payload.");
return base::nullopt;
}
return payload_message_->records();
}
const NDEFMessage* NDEFRecord::payload_message() const {
return payload_message_.Get();
}
void NDEFRecord::Trace(blink::Visitor* visitor) {
visitor->Trace(payload_message_);
ScriptWrappable::Trace(visitor);
}
......
......@@ -18,6 +18,7 @@ namespace blink {
class DOMDataView;
class ExceptionState;
class ExecutionContext;
class NDEFMessage;
class NDEFRecordInit;
class MODULES_EXPORT NDEFRecord final : public ScriptWrappable {
......@@ -48,8 +49,11 @@ class MODULES_EXPORT NDEFRecord final : public ScriptWrappable {
const String& encoding() const;
const String& lang() const;
DOMDataView* data() const;
base::Optional<HeapVector<Member<NDEFRecord>>> toRecords(
ExceptionState& exception_state) const;
const WTF::Vector<uint8_t>& payloadData() const;
const NDEFMessage* payload_message() const;
void Trace(blink::Visitor*) override;
......@@ -62,6 +66,9 @@ class MODULES_EXPORT NDEFRecord final : public ScriptWrappable {
// Holds the NDEFRecord.[[PayloadData]] bytes defined at
// https://w3c.github.io/web-nfc/#the-ndefrecord-interface.
WTF::Vector<uint8_t> payload_data_;
// |payload_data_| parsed as an NDEFMessage. This field will be set for some
// "smart-poster" and external type records.
Member<NDEFMessage> payload_message_;
};
} // namespace blink
......
......@@ -18,4 +18,5 @@
readonly attribute USVString? encoding;
readonly attribute USVString? lang;
readonly attribute DataView? data;
[RaisesException] sequence<NDEFRecord>? toRecords();
};
......@@ -28,15 +28,21 @@ using device::mojom::blink::NDEFScanOptionsPtr;
// Mojo type converters
namespace mojo {
NDEFRecordPtr TypeConverter<NDEFRecordPtr, ::blink::NDEFRecord*>::Convert(
const ::blink::NDEFRecord* record) {
return NDEFRecord::New(record->recordType(), record->mediaType(),
record->id(), record->encoding(), record->lang(),
record->payloadData());
NDEFRecordPtr TypeConverter<NDEFRecordPtr, blink::NDEFRecord*>::Convert(
const blink::NDEFRecord* record) {
return NDEFRecord::New(
record->recordType(), record->mediaType(), record->id(),
record->encoding(), record->lang(), record->payloadData(),
TypeConverter<NDEFMessagePtr, blink::NDEFMessage*>::Convert(
record->payload_message()));
}
NDEFMessagePtr TypeConverter<NDEFMessagePtr, ::blink::NDEFMessage*>::Convert(
const ::blink::NDEFMessage* message) {
NDEFMessagePtr TypeConverter<NDEFMessagePtr, blink::NDEFMessage*>::Convert(
const blink::NDEFMessage* message) {
// |message| may come from blink::NDEFRecord::payload_message() which is
// possible to be null for some "smart-poster" and external type records.
if (!message)
return nullptr;
NDEFMessagePtr messagePtr = NDEFMessage::New();
messagePtr->url = message->url();
messagePtr->data.resize(message->records().size());
......
......@@ -22,17 +22,16 @@ class NDEFPushOptions;
namespace mojo {
template <>
struct TypeConverter<device::mojom::blink::NDEFRecordPtr,
::blink::NDEFRecord*> {
struct TypeConverter<device::mojom::blink::NDEFRecordPtr, blink::NDEFRecord*> {
static device::mojom::blink::NDEFRecordPtr Convert(
const ::blink::NDEFRecord* record);
const blink::NDEFRecord* record);
};
template <>
struct TypeConverter<device::mojom::blink::NDEFMessagePtr,
::blink::NDEFMessage*> {
blink::NDEFMessage*> {
static device::mojom::blink::NDEFMessagePtr Convert(
const ::blink::NDEFMessage* message);
const blink::NDEFMessage* message);
};
template <>
......
......@@ -28,6 +28,10 @@ function toMojoNDEFRecord(record) {
nfcRecord.recordType = record.recordType;
nfcRecord.mediaType = record.mediaType;
nfcRecord.data = toByteArray(record.data);
if (record.data != null && record.data.records !== undefined) {
// |record.data| may be an NDEFMessageInit, i.e. the payload is a message.
nfcRecord.payloadMessage = toMojoNDEFMessage(record.data);
}
return nfcRecord;
}
......
......@@ -183,6 +183,33 @@ nfc_test(async (t, mockNFC) => {
}, "Synchronously signaled abort.");
nfc_test(async (t, mockNFC) => {
const reader = new NDEFReader();
const controller = new AbortController();
const readerWatcher = new EventWatcher(t, reader, ["reading", "error"]);
const payloadMessage = createMessage([createTextRecord(test_text_data)]);
const message = createMessage([createRecord('example.com:payloadIsMessage',
undefined, 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, 'text/plain', 'mediaType');
const decoder = new TextDecoder();
assert_equals(decoder.decode(embeddedRecords[0].data), test_text_data,
'data has the same content with the original dictionary');
}, "NDEFRecord.toRecords returns its embedded records correctly.");
test(() => {
const reader = new NDEFReader();
invalid_signals.forEach(invalid_signal => {
......
This is a testharness.js-based test.
Found 70 tests; 66 PASS, 4 FAIL, 0 TIMEOUT, 0 NOTRUN.
Found 70 tests; 68 PASS, 2 FAIL, 0 TIMEOUT, 0 NOTRUN.
PASS idl_test setup
PASS idl_test validation
PASS NDEFMessage interface: existence and properties of interface object
......@@ -24,7 +24,7 @@ PASS NDEFRecord interface: attribute id
PASS NDEFRecord interface: attribute data
PASS NDEFRecord interface: attribute encoding
PASS NDEFRecord interface: attribute lang
FAIL NDEFRecord interface: operation toRecords() assert_own_property: interface prototype object missing non-static operation expected property "toRecords" missing
PASS NDEFRecord interface: operation toRecords()
PASS NDEFRecord must be primary interface of new NDEFRecord({"recordType":"text","mediaType":"text/plain","data":"Hello World","id":"/custom/path"});
PASS Stringification of new NDEFRecord({"recordType":"text","mediaType":"text/plain","data":"Hello World","id":"/custom/path"});
PASS NDEFRecord interface: new NDEFRecord({"recordType":"text","mediaType":"text/plain","data":"Hello World","id":"/custom/path"}); must inherit property "recordType" with the proper type
......@@ -33,7 +33,7 @@ PASS NDEFRecord interface: new NDEFRecord({"recordType":"text","mediaType":"text
FAIL NDEFRecord interface: new NDEFRecord({"recordType":"text","mediaType":"text/plain","data":"Hello World","id":"/custom/path"}); must inherit property "data" with the proper type Unrecognized type DataView
PASS NDEFRecord interface: new NDEFRecord({"recordType":"text","mediaType":"text/plain","data":"Hello World","id":"/custom/path"}); must inherit property "encoding" with the proper type
PASS NDEFRecord interface: new NDEFRecord({"recordType":"text","mediaType":"text/plain","data":"Hello World","id":"/custom/path"}); must inherit property "lang" with the proper type
FAIL NDEFRecord interface: new NDEFRecord({"recordType":"text","mediaType":"text/plain","data":"Hello World","id":"/custom/path"}); must inherit property "toRecords()" with the proper type assert_inherits: property "toRecords" not found in prototype chain
PASS NDEFRecord interface: new NDEFRecord({"recordType":"text","mediaType":"text/plain","data":"Hello World","id":"/custom/path"}); must inherit property "toRecords()" with the proper type
PASS NDEFWriter interface: existence and properties of interface object
PASS NDEFWriter interface object length
PASS NDEFWriter interface object name
......
......@@ -5099,6 +5099,7 @@ interface NDEFRecord
getter mediaType
getter recordType
method constructor
method toRecords
interface NDEFWriter
attribute @@toStringTag
method constructor
......
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