Commit acff81de authored by Leon Han's avatar Leon Han Committed by Commit Bot

[webnfc] Support writing/reading local type records

Some notable points:

1) Local type in WebNFC APIs is always prefixed by ':', but, the ':'
   will be omitted when it's actually written into the nfc tag.
     ":act"  --> "act" to be written as the TYPE field into the nfc tag.
     ":text" --> "text"
   The reading direction is vice versa.
     "act"  --> ":act" to be exposed as NDEFRecord#recordType.
     "text" --> ":text"

2) Only "smart-poster", external, and local type records are supposed to
   be able to carry a ndef message as payload.

3) Local type is only expected to exist inside a ndef message that is
   another ndef record's payload. Top level ndef message is not allowed
   to have a local type record.

The spec changes:
https://github.com/w3c/web-nfc/pull/491
https://github.com/w3c/web-nfc/pull/493
https://github.com/w3c/web-nfc/pull/495
https://github.com/w3c/web-nfc/pull/502
https://github.com/w3c/web-nfc/pull/506

BUG=520391

Change-Id: Ic2890c031109aa583437ac93a8901ff71992af78
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1996946Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Commit-Queue: Leon Han <leon.han@intel.com>
Cr-Commit-Position: refs/heads/master@{#737290}
parent 4a9e20a2
...@@ -167,7 +167,17 @@ public final class NdefMessageUtils { ...@@ -167,7 +167,17 @@ public final class NdefMessageUtils {
} }
if (record.category == NdefRecordTypeCategory.LOCAL) { if (record.category == NdefRecordTypeCategory.LOCAL) {
// TODO(https://crbug.com/520391): Support local type records. // It's impossible for a local type record to have non-empty |data| and non-null
// |payloadMessage| at the same time.
// TODO(https://crbug.com/520391): Validate the containing ndef message is the payload
// of another ndef record.
if (isValidLocalType(record.recordType)
&& (record.data.length == 0 || record.payloadMessage == null)) {
// The prefix ':' in |record.recordType| is only used to differentiate local type
// from other type names, remove it before writing.
return createPlatformLocalRecord(record.recordType.substring(1), record.id,
record.data, record.payloadMessage);
}
throw new InvalidNdefMessageException(); throw new InvalidNdefMessageException();
} }
...@@ -285,7 +295,19 @@ public final class NdefMessageUtils { ...@@ -285,7 +295,19 @@ public final class NdefMessageUtils {
} }
/** /**
* Constructs well known type (TEXT or URI) NdefRecord * Constructs local type NdefRecord
*/
private static NdefRecord createLocalRecord(String localType, byte[] payload) {
NdefRecord nfcRecord = new NdefRecord();
nfcRecord.category = NdefRecordTypeCategory.LOCAL;
nfcRecord.recordType = localType;
nfcRecord.data = payload;
nfcRecord.payloadMessage = getNdefMessageFromPayloadBytes(payload);
return nfcRecord;
}
/**
* Constructs well known type (TEXT, URI or local type) NdefRecord
*/ */
private static NdefRecord createWellKnownRecord(android.nfc.NdefRecord record) private static NdefRecord createWellKnownRecord(android.nfc.NdefRecord record)
throws UnsupportedEncodingException { throws UnsupportedEncodingException {
...@@ -299,7 +321,15 @@ public final class NdefMessageUtils { ...@@ -299,7 +321,15 @@ public final class NdefMessageUtils {
// TODO(https://crbug.com/520391): Support RTD_SMART_POSTER type records. // TODO(https://crbug.com/520391): Support RTD_SMART_POSTER type records.
// TODO(https://crbug.com/520391): Support local type records. // Prefix the raw local type with ':' to differentiate from other type names in WebNFC APIs,
// e.g. |localType| being "text" will become ":text" to differentiate from the standardized
// "text" record.
String recordType = ':' + new String(record.getType(), "UTF-8");
// We do not validate if we're in the context of a parent record but just expose to JS as is
// what has been read from the nfc tag.
if (isValidLocalType(recordType)) {
return createLocalRecord(recordType, record.getPayload());
}
return null; return null;
} }
...@@ -444,6 +474,26 @@ public final class NdefMessageUtils { ...@@ -444,6 +474,26 @@ public final class NdefMessageUtils {
id == null ? null : ApiCompatibilityUtils.getBytesUtf8(id), payload); id == null ? null : ApiCompatibilityUtils.getBytesUtf8(id), payload);
} }
/**
* Creates a TNF_WELL_KNOWN + |recordType| android.nfc.NdefRecord.
*/
public static android.nfc.NdefRecord createPlatformLocalRecord(
String recordType, String id, byte[] payload, NdefMessage payloadMessage) {
// Already guaranteed by the caller.
assert recordType != null && !recordType.isEmpty();
// |payloadMessage| being non-null means this record has an NDEF message as its payload.
if (payloadMessage != null) {
// Should be guaranteed by the caller that |payload| is an empty byte array.
assert payload.length == 0;
payload = getBytesFromPayloadNdefMessage(payloadMessage);
}
return new android.nfc.NdefRecord(android.nfc.NdefRecord.TNF_WELL_KNOWN,
ApiCompatibilityUtils.getBytesUtf8(recordType),
id == null ? null : ApiCompatibilityUtils.getBytesUtf8(id), payload);
}
/** /**
* Validates external types. * Validates external types.
* https://w3c.github.io/web-nfc/#dfn-validate-external-type * https://w3c.github.io/web-nfc/#dfn-validate-external-type
...@@ -468,6 +518,29 @@ public final class NdefMessageUtils { ...@@ -468,6 +518,29 @@ public final class NdefMessageUtils {
return true; return true;
} }
/**
* Validates local types.
* https://w3c.github.io/web-nfc/#dfn-validate-local-type
*/
private static boolean isValidLocalType(String input) {
// Must be an ASCII string first.
if (!Charset.forName("US-ASCII").newEncoder().canEncode(input)) return false;
// The prefix ':' will be omitted when we actually write the record type into the nfc tag.
// We're taking it into consideration for validating the length here.
if (input.length() < 2 || input.length() > 256) return false;
if (input.charAt(0) != ':') return false;
if (!Character.isLowerCase(input.charAt(1)) && !Character.isDigit(input.charAt(1))) {
return false;
}
// TODO(https://crbug.com/520391): Validate |input| is not equal to the record type of any
// NDEF record defined in its containing NDEF message.
return true;
}
/** /**
* Tries to construct a android.nfc.NdefMessage from the raw bytes |payload| then converts it to * 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. * a Mojo NdefMessage and returns. Returns null for anything wrong.
......
...@@ -367,6 +367,45 @@ public class NFCTest { ...@@ -367,6 +367,45 @@ public class NFCTest {
assertEquals(NdefMessageUtils.RECORD_TYPE_TEXT, payloadMojoMessage.data[0].recordType); assertEquals(NdefMessageUtils.RECORD_TYPE_TEXT, payloadMojoMessage.data[0].recordType);
assertEquals(null, payloadMojoMessage.data[0].mediaType); assertEquals(null, payloadMojoMessage.data[0].mediaType);
assertEquals(TEST_TEXT, new String(payloadMojoMessage.data[0].data)); assertEquals(TEST_TEXT, new String(payloadMojoMessage.data[0].data));
// Test local record conversion.
android.nfc.NdefMessage localNdefMessage = new android.nfc.NdefMessage(
NdefMessageUtils.createPlatformLocalRecord("xyz", DUMMY_RECORD_ID,
ApiCompatibilityUtils.getBytesUtf8(TEST_TEXT), null /* payloadMessage */));
NdefMessage localMojoNdefMessage = NdefMessageUtils.toNdefMessage(localNdefMessage);
assertEquals(1, localMojoNdefMessage.data.length);
assertEquals(NdefRecordTypeCategory.LOCAL, localMojoNdefMessage.data[0].category);
// Is already prefixed with ':'.
assertEquals(":xyz", localMojoNdefMessage.data[0].recordType);
assertEquals(null, localMojoNdefMessage.data[0].mediaType);
assertEquals(DUMMY_RECORD_ID, localMojoNdefMessage.data[0].id);
assertNull(localMojoNdefMessage.data[0].encoding);
assertNull(localMojoNdefMessage.data[0].lang);
assertEquals(TEST_TEXT, new String(localMojoNdefMessage.data[0].data));
// Test conversion for local records with the payload being a ndef message.
payloadMessage = new android.nfc.NdefMessage(
android.nfc.NdefRecord.createTextRecord(LANG_EN_US, TEST_TEXT));
payloadBytes = payloadMessage.toByteArray();
// Put |payloadBytes| as payload of a local record.
android.nfc.NdefMessage localNdefMessage1 =
new android.nfc.NdefMessage(NdefMessageUtils.createPlatformLocalRecord(
"xyz", DUMMY_RECORD_ID, payloadBytes, null /* payloadMessage */));
NdefMessage localMojoNdefMessage1 = NdefMessageUtils.toNdefMessage(localNdefMessage1);
assertEquals(1, localMojoNdefMessage1.data.length);
assertEquals(NdefRecordTypeCategory.LOCAL, localMojoNdefMessage1.data[0].category);
// Is already prefixed with ':'.
assertEquals(":xyz", localMojoNdefMessage1.data[0].recordType);
assertEquals(null, localMojoNdefMessage1.data[0].mediaType);
assertEquals(DUMMY_RECORD_ID, localMojoNdefMessage1.data[0].id);
// The embedded ndef message should have content corresponding with the original
// |payloadMessage|.
payloadMojoMessage = localMojoNdefMessage1.data[0].payloadMessage;
assertEquals(1, payloadMojoMessage.data.length);
assertEquals(NdefRecordTypeCategory.STANDARDIZED, payloadMojoMessage.data[0].category);
assertEquals(NdefMessageUtils.RECORD_TYPE_TEXT, payloadMojoMessage.data[0].recordType);
assertEquals(null, payloadMojoMessage.data[0].mediaType);
assertEquals(TEST_TEXT, new String(payloadMojoMessage.data[0].data));
} }
/** /**
...@@ -561,6 +600,56 @@ public class NFCTest { ...@@ -561,6 +600,56 @@ public class NFCTest {
assertEquals(DUMMY_RECORD_ID, new String(payloadMessage.getRecords()[0].getId())); assertEquals(DUMMY_RECORD_ID, new String(payloadMessage.getRecords()[0].getId()));
assertEquals(TEST_URL, payloadMessage.getRecords()[0].toUri().toString()); assertEquals(TEST_URL, payloadMessage.getRecords()[0].toUri().toString());
// Test local record conversion.
NdefRecord localMojoNdefRecord = new NdefRecord();
localMojoNdefRecord.category = NdefRecordTypeCategory.LOCAL;
localMojoNdefRecord.recordType = ":xyz";
localMojoNdefRecord.id = DUMMY_RECORD_ID;
localMojoNdefRecord.data = ApiCompatibilityUtils.getBytesUtf8(TEST_TEXT);
NdefMessage localMojoNdefMessage = createMojoNdefMessage(localMojoNdefRecord);
android.nfc.NdefMessage localNdefMessage =
NdefMessageUtils.toNdefMessage(localMojoNdefMessage);
assertEquals(1, localNdefMessage.getRecords().length);
assertEquals(
android.nfc.NdefRecord.TNF_WELL_KNOWN, localNdefMessage.getRecords()[0].getTnf());
// The ':' prefix is already omitted.
assertEquals("xyz", new String(localNdefMessage.getRecords()[0].getType()));
assertEquals(DUMMY_RECORD_ID, new String(localNdefMessage.getRecords()[0].getId()));
assertEquals(TEST_TEXT, new String(localNdefMessage.getRecords()[0].getPayload()));
// Test conversion for local records with the payload being a ndef message.
//
// Prepare a local record that embeds |payloadMojoRecord| in its payload.
NdefRecord localMojoNdefRecord1 = new NdefRecord();
localMojoNdefRecord1.category = NdefRecordTypeCategory.LOCAL;
localMojoNdefRecord1.recordType = ":xyz";
localMojoNdefRecord1.id = DUMMY_RECORD_ID;
// device.mojom.NDEFRecord.data is not allowed to be null, instead, empty byte array is just
// what's passed from Blink.
localMojoNdefRecord1.data = new byte[0];
localMojoNdefRecord1.payloadMessage = createMojoNdefMessage(payloadMojoRecord);
// Do the conversion.
android.nfc.NdefMessage localNdefMessage1 =
NdefMessageUtils.toNdefMessage(createMojoNdefMessage(localMojoNdefRecord1));
assertEquals(1, localNdefMessage1.getRecords().length);
assertEquals(
android.nfc.NdefRecord.TNF_WELL_KNOWN, localNdefMessage1.getRecords()[0].getTnf());
// The ':' prefix is already omitted.
assertEquals("xyz", new String(localNdefMessage1.getRecords()[0].getType()));
assertEquals(DUMMY_RECORD_ID, new String(localNdefMessage1.getRecords()[0].getId()));
// The payload raw bytes should be able to construct an ndef message containing an ndef
// record that has content corresponding with the original |payloadMojoRecord|.
payloadMessage =
new android.nfc.NdefMessage(localNdefMessage1.getRecords()[0].getPayload());
assertNotNull(payloadMessage);
assertEquals(1, payloadMessage.getRecords().length);
assertEquals(
android.nfc.NdefRecord.TNF_WELL_KNOWN, payloadMessage.getRecords()[0].getTnf());
assertEquals(new String(android.nfc.NdefRecord.RTD_URI),
new String(payloadMessage.getRecords()[0].getType()));
assertEquals(DUMMY_RECORD_ID, new String(payloadMessage.getRecords()[0].getId()));
assertEquals(TEST_URL, payloadMessage.getRecords()[0].toUri().toString());
// Test EMPTY record conversion. // Test EMPTY record conversion.
NdefRecord emptyMojoNdefRecord = new NdefRecord(); NdefRecord emptyMojoNdefRecord = new NdefRecord();
emptyMojoNdefRecord.category = NdefRecordTypeCategory.STANDARDIZED; emptyMojoNdefRecord.category = NdefRecordTypeCategory.STANDARDIZED;
...@@ -655,6 +744,49 @@ public class NFCTest { ...@@ -655,6 +744,49 @@ public class NFCTest {
} }
} }
/**
* Test local type record conversion with invalid local type.
*/
@Test(expected = InvalidNdefMessageException.class)
@Feature({"NFCTest"})
public void testInvalidLocalRecordType() throws InvalidNdefMessageException {
NdefRecord localMojoNdefRecord = new NdefRecord();
localMojoNdefRecord.category = NdefRecordTypeCategory.LOCAL;
localMojoNdefRecord.data = ApiCompatibilityUtils.getBytesUtf8(TEST_TEXT);
{
// Must start with ':'.
localMojoNdefRecord.recordType = "dummyLocalTypeNotStartingwith:";
localMojoNdefRecord.data = ApiCompatibilityUtils.getBytesUtf8(TEST_TEXT);
NdefMessage localMojoNdefMessage = createMojoNdefMessage(localMojoNdefRecord);
android.nfc.NdefMessage localNdefMessage =
NdefMessageUtils.toNdefMessage(localMojoNdefMessage);
assertNull(localNdefMessage);
}
{
// |recordType| is a string mixed with ASCII/non-ASCII, FAIL.
localMojoNdefRecord.recordType = ":hellö";
android.nfc.NdefMessage localNdefMessage_nonASCII =
NdefMessageUtils.toNdefMessage(createMojoNdefMessage(localMojoNdefRecord));
assertNull(localNdefMessage_nonASCII);
char[] chars = new char[255];
Arrays.fill(chars, 'a');
String chars_255 = new String(chars);
// The length of the real local type is 255, OK.
localMojoNdefRecord.recordType = ":" + chars_255;
android.nfc.NdefMessage localNdefMessage_255 =
NdefMessageUtils.toNdefMessage(createMojoNdefMessage(localMojoNdefRecord));
assertNotNull(localNdefMessage_255);
// Exceeding the maximum length 255, FAIL.
localMojoNdefRecord.recordType = ":a" + chars_255;
android.nfc.NdefMessage localNdefMessage_256 =
NdefMessageUtils.toNdefMessage(createMojoNdefMessage(localMojoNdefRecord));
assertNull(localNdefMessage_256);
}
}
/** /**
* Test that invalid NdefMessage is rejected with INVALID_MESSAGE error code. * Test that invalid NdefMessage is rejected with INVALID_MESSAGE error code.
*/ */
......
...@@ -47,9 +47,7 @@ struct NDEFError { ...@@ -47,9 +47,7 @@ struct NDEFError {
// https://w3c.github.io/web-nfc/#dom-ndefrecord // https://w3c.github.io/web-nfc/#dom-ndefrecord
struct NDEFRecord { struct NDEFRecord {
// The category |record_type| belongs to. This field is not exposed to JS, but // The category |record_type| belongs to. This field is not exposed to JS, but
// is used to transfer information internally and make code more readable, // is used to transfer information internally and make code more readable.
// given that there're some complex logic around external types and local
// types (will be supported in the future).
NDEFRecordTypeCategory category; NDEFRecordTypeCategory category;
// The type of NDEFRecord. // The type of NDEFRecord.
...@@ -73,9 +71,14 @@ struct NDEFRecord { ...@@ -73,9 +71,14 @@ struct NDEFRecord {
// Payload of the NDEFRecord. // Payload of the NDEFRecord.
array<uint8> data; array<uint8> data;
// |data| parsed as an NDEFMessage. This field may be set for some // This field may be set for some "smart-poster", external, or local type
// "smart-poster" or external type records. This field may be null even if // records.
// |data| is valid. // On the writing direction (passed from inside Blink), if this field is
// non-null, |data| will be empty and the receiver is expected to make out the
// payload raw bytes from this field.
// On the reading direction (passed towards Blink), |data| always carries the
// payload raw bytes, this field is non-null only if |data| can be parsed as
// an NDEFMessage.
NDEFMessage? payload_message; NDEFMessage? payload_message;
}; };
......
...@@ -16,7 +16,8 @@ namespace blink { ...@@ -16,7 +16,8 @@ namespace blink {
// static // static
NDEFMessage* NDEFMessage::Create(const ExecutionContext* execution_context, NDEFMessage* NDEFMessage::Create(const ExecutionContext* execution_context,
const NDEFMessageInit* init, const NDEFMessageInit* init,
ExceptionState& exception_state) { ExceptionState& exception_state,
bool is_embedded) {
// https://w3c.github.io/web-nfc/#creating-ndef-message // https://w3c.github.io/web-nfc/#creating-ndef-message
// NDEFMessageInit#records is a required field. // NDEFMessageInit#records is a required field.
...@@ -29,8 +30,8 @@ NDEFMessage* NDEFMessage::Create(const ExecutionContext* execution_context, ...@@ -29,8 +30,8 @@ NDEFMessage* NDEFMessage::Create(const ExecutionContext* execution_context,
NDEFMessage* message = MakeGarbageCollected<NDEFMessage>(); NDEFMessage* message = MakeGarbageCollected<NDEFMessage>();
for (const NDEFRecordInit* record_init : init->records()) { for (const NDEFRecordInit* record_init : init->records()) {
NDEFRecord* record = NDEFRecord* record = NDEFRecord::Create(execution_context, record_init,
NDEFRecord::Create(execution_context, record_init, exception_state); exception_state, is_embedded);
if (exception_state.HadException()) if (exception_state.HadException())
return nullptr; return nullptr;
DCHECK(record); DCHECK(record);
......
...@@ -24,9 +24,12 @@ class MODULES_EXPORT NDEFMessage final : public ScriptWrappable { ...@@ -24,9 +24,12 @@ class MODULES_EXPORT NDEFMessage final : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
public: public:
// |is_embedded| indicates if this message serves as payload for a parent
// record.
static NDEFMessage* Create(const ExecutionContext*, static NDEFMessage* Create(const ExecutionContext*,
const NDEFMessageInit*, const NDEFMessageInit*,
ExceptionState&); ExceptionState&,
bool is_embedded = false);
static NDEFMessage* Create(const ExecutionContext*, static NDEFMessage* Create(const ExecutionContext*,
const NDEFMessageSource&, const NDEFMessageSource&,
ExceptionState&); ExceptionState&);
......
...@@ -24,17 +24,20 @@ class MODULES_EXPORT NDEFRecord final : public ScriptWrappable { ...@@ -24,17 +24,20 @@ class MODULES_EXPORT NDEFRecord final : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
public: public:
// |is_embedded| indicates if this record is within the context of a parent
// record.
static NDEFRecord* Create(const ExecutionContext*, static NDEFRecord* Create(const ExecutionContext*,
const NDEFRecordInit*, const NDEFRecordInit*,
ExceptionState&); ExceptionState&,
bool is_embedded = false);
explicit NDEFRecord(device::mojom::NDEFRecordTypeCategory, explicit NDEFRecord(device::mojom::NDEFRecordTypeCategory,
const String& record_type, const String& record_type,
const String& id, const String& id,
WTF::Vector<uint8_t>); WTF::Vector<uint8_t>);
// For constructing an external type record or a "smart-poster" record whose // For constructing a "smart-poster", an external type or a local type record
// payload is an NDEF message. // whose payload is an NDEF message.
explicit NDEFRecord(device::mojom::NDEFRecordTypeCategory, explicit NDEFRecord(device::mojom::NDEFRecordTypeCategory,
const String& record_type, const String& record_type,
const String& id, const String& id,
...@@ -86,7 +89,7 @@ class MODULES_EXPORT NDEFRecord final : public ScriptWrappable { ...@@ -86,7 +89,7 @@ class MODULES_EXPORT NDEFRecord final : public ScriptWrappable {
// https://w3c.github.io/web-nfc/#the-ndefrecord-interface. // https://w3c.github.io/web-nfc/#the-ndefrecord-interface.
const WTF::Vector<uint8_t> payload_data_; const WTF::Vector<uint8_t> payload_data_;
// |payload_data_| parsed as an NDEFMessage. This field will be set for some // |payload_data_| parsed as an NDEFMessage. This field will be set for some
// "smart-poster" and external type records. // "smart-poster", external, and local type records.
const Member<NDEFMessage> payload_message_; const Member<NDEFMessage> payload_message_;
}; };
......
...@@ -13,10 +13,12 @@ function toMojoNDEFMessage(message) { ...@@ -13,10 +13,12 @@ function toMojoNDEFMessage(message) {
function toMojoNDEFRecord(record) { function toMojoNDEFRecord(record) {
let nfcRecord = new device.mojom.NDEFRecord(); let nfcRecord = new device.mojom.NDEFRecord();
if (record.recordType.search(':') != -1) { // Simply checks the existence of ':' to decide whether it's an external
// Simply checks the existence of ':' to decide whether it's an external // type or a local type. As a mock, no need to really implement the validation
// type. As a mock, no need to really implement the validation algo at // algorithms for them.
// https://w3c.github.io/web-nfc/#dfn-validate-external-type. if (record.recordType.startsWith(':')) {
nfcRecord.category = device.mojom.NDEFRecordTypeCategory.kLocal;
} else if (record.recordType.search(':') != -1) {
nfcRecord.category = device.mojom.NDEFRecordTypeCategory.kExternal; nfcRecord.category = device.mojom.NDEFRecordTypeCategory.kExternal;
} else { } else {
nfcRecord.category = device.mojom.NDEFRecordTypeCategory.kStandardized; nfcRecord.category = device.mojom.NDEFRecordTypeCategory.kStandardized;
......
...@@ -146,23 +146,32 @@ nfc_test(async (t, mockNFC) => { ...@@ -146,23 +146,32 @@ nfc_test(async (t, mockNFC) => {
const promise = readerWatcher.wait_for("reading").then(event => { const promise = readerWatcher.wait_for("reading").then(event => {
controller.abort(); controller.abort();
assert_true(event instanceof NDEFReadingEvent); assert_true(event instanceof NDEFReadingEvent);
// The message contains only an external type record.
// The message in the event contains only the external type record.
assert_equals(event.message.records.length, 1); assert_equals(event.message.records.length, 1);
assert_equals(event.message.records[0].recordType, 'example.com:payloadIsMessage', 'recordType'); assert_equals(event.message.records[0].recordType, 'example.com:containsLocalRecord',
// The external type record's payload is a message, which contains only a text record. 'recordType');
const embeddedRecords = event.message.records[0].toRecords();
assert_equals(embeddedRecords.length, 1); // The external type record contains only the local type record.
assert_equals(embeddedRecords[0].recordType, 'text', 'recordType'); assert_equals(event.message.records[0].toRecords().length, 1);
assert_equals(embeddedRecords[0].mediaType, null, 'mediaType'); assert_equals(event.message.records[0].toRecords()[0].recordType, ':containsTextRecord',
'recordType');
// The local type record contains only the text record.
assert_equals(event.message.records[0].toRecords()[0].toRecords()[0].recordType, 'text',
'recordType');
const decoder = new TextDecoder(); const decoder = new TextDecoder();
assert_equals(decoder.decode(embeddedRecords[0].data), test_text_data, assert_equals(decoder.decode(event.message.records[0].toRecords()[0].toRecords()[0].data),
'data has the same content with the original dictionary'); test_text_data, 'data has the same content with the original dictionary');
}); });
await reader.scan({signal : controller.signal}); await reader.scan({signal : controller.signal});
const payloadMessage = createMessage([createTextRecord(test_text_data)]); // An external type record --contains-> a local type record --contains-> a text record.
const message = createMessage([createRecord('example.com:payloadIsMessage', const messageContainText = createMessage([createTextRecord(test_text_data)]);
payloadMessage)]); const messageContainLocal= createMessage([createRecord(':containsTextRecord',
messageContainText)]);
const message = createMessage([createRecord('example.com:containsLocalRecord',
messageContainLocal)]);
mockNFC.setReadingMessage(message); mockNFC.setReadingMessage(message);
await promise; await promise;
}, "NDEFRecord.toRecords returns its embedded records correctly."); }, "NDEFRecord.toRecords returns its embedded records correctly.");
......
...@@ -72,14 +72,23 @@ const invalid_type_messages = ...@@ -72,14 +72,23 @@ const invalid_type_messages =
// NDEFRecord must have data. // NDEFRecord must have data.
createMessage([createRecord('w3.org:xyz')]), createMessage([createRecord('w3.org:xyz')]),
// NDEFRecord.data for external record must be ArrayBuffer. // NDEFRecord.data for external record must be a BufferSource or NDEFMessageInit.
createMessage([createRecord('w3.org:xyz', test_text_data)]), createMessage([createRecord('w3.org:xyz', test_text_data)]),
createMessage([createRecord('w3.org:xyz', test_number_data)]), createMessage([createRecord('w3.org:xyz', test_number_data)]),
createMessage([createRecord('w3.org:xyz', test_json_data)]), createMessage([createRecord('w3.org:xyz', test_json_data)]),
// https://w3c.github.io/web-nfc/#dfn-map-local-type-to-ndef
// NDEFRecord must have data.
createMessage([createRecord(':xyz')]),
// NDEFRecord.data for local type record must be a BufferSource or NDEFMessageInit.
createMessage([createRecord(':xyz', test_text_data)]),
createMessage([createRecord(':xyz', test_number_data)]),
createMessage([createRecord(':xyz', test_json_data)]),
// https://w3c.github.io/web-nfc/#ndef-record-types // https://w3c.github.io/web-nfc/#ndef-record-types
// The record type is neither a known type ('text', 'mime' etc.) nor a // The record type is neither a known type ('text', 'mime' etc.) nor a
// valid custom type for an external type record. // valid external/local type.
createMessage([createRecord('unmatched_type', test_buffer_data)]) createMessage([createRecord('unmatched_type', test_buffer_data)])
]; ];
...@@ -283,10 +292,13 @@ nfc_test(async (t, mockNFC) => { ...@@ -283,10 +292,13 @@ nfc_test(async (t, mockNFC) => {
and external records with default NDEFWriteOptions."); and external records with default NDEFWriteOptions.");
nfc_test(async (t, mockNFC) => { nfc_test(async (t, mockNFC) => {
const payloadMessage = createMessage([createTextRecord(test_text_data)]); const messageContainText = createMessage([createTextRecord(test_text_data)]);
// Prepare a message containing an external record that uses |payloadMessage| as its payload.
const message = createMessage([createRecord('example.com:payloadIsMessage', // Prepare a local type record that uses |messageContainText| as its payload.
payloadMessage)]); const messageContainLocal = createMessage([createRecord(':containsTextRecord', messageContainText)]);
// Prepare an external type record that uses |messageContainLocal| as its payload.
const message = createMessage([createRecord('example.com:containsLocalRecord', messageContainLocal)]);
const writer = new NDEFWriter(); const writer = new NDEFWriter();
await writer.write(message); await writer.write(message);
...@@ -294,11 +306,20 @@ nfc_test(async (t, mockNFC) => { ...@@ -294,11 +306,20 @@ nfc_test(async (t, mockNFC) => {
// The mojom message received by mock nfc contains only the external type record. // The mojom message received by mock nfc contains only the external type record.
assert_equals(pushed_message.data.length, 1); assert_equals(pushed_message.data.length, 1);
assert_equals(pushed_message.data[0].recordType, 'example.com:payloadIsMessage', 'recordType'); assert_equals(pushed_message.data[0].recordType, 'example.com:containsLocalRecord', 'recordType');
// The external type record's payload is from the original |payloadMessage|.
// The external type record's payload is from the original |messageContainLocal|,
// containing only the local type record.
assert_array_equals(pushed_message.data[0].data, new Uint8Array(0), assert_array_equals(pushed_message.data[0].data, new Uint8Array(0),
'payloadMessage is used instead'); 'payloadMessage is used instead');
assertNDEFMessagesEqual(payloadMessage, pushed_message.data[0].payloadMessage); assert_equals(pushed_message.data[0].payloadMessage.data.length, 1);
assert_equals(pushed_message.data[0].payloadMessage.data[0].recordType, ':containsTextRecord', 'recordType');
// The local type record's payload is from the original |messageContainText|,
// containing only the text record.
assert_array_equals(pushed_message.data[0].payloadMessage.data[0].data, new Uint8Array(0),
'payloadMessage is used instead');
assertNDEFMessagesEqual(messageContainText, pushed_message.data[0].payloadMessage.data[0].payloadMessage);
}, "NDEFWriter.write NDEFMessage containing embedded records."); }, "NDEFWriter.write NDEFMessage containing embedded records.");
nfc_test(async (t, mockNFC) => { nfc_test(async (t, mockNFC) => {
......
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