Bug 1609065 [wpt PR 21157] - [webnfc] Support writing/reading local type records, a=testonly

Automatic update from web-platform-tests
[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/+/1996946
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Commit-Queue: Leon Han <leon.han@intel.com>
Cr-Commit-Position: refs/heads/master@{#737290}

--

wpt-commits: a1652e2faab5c92db0d3a2523ef9a94003e1e8f5
wpt-pr: 21157
This commit is contained in:
Leon Han 2020-02-04 13:19:26 +00:00 коммит произвёл moz-wptsync-bot
Родитель 2050493dae
Коммит 6cca16717b
4 изменённых файлов: 182 добавлений и 36 удалений

Просмотреть файл

@ -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) => {
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.");

Просмотреть файл

@ -83,7 +83,7 @@
assert_equals(record.lang, null, 'lang'); assert_equals(record.lang, null, 'lang');
assert_equals(record.data, null, 'data'); assert_equals(record.data, null, 'data');
assert_throws_dom('NotSupportedError', () => record.toRecords(), assert_throws_dom('NotSupportedError', () => record.toRecords(),
'Only smart-poster records and external type records could have embedded records.'); 'Only smart-poster, external type and local type records could have embedded records.');
}, 'NDEFRecord constructor with empty record type'); }, 'NDEFRecord constructor with empty record type');
test(() => { test(() => {
@ -97,7 +97,7 @@
assert_equals(decoder.decode(record.data), test_text_data, assert_equals(decoder.decode(record.data), test_text_data,
'data has the same content with the original dictionary'); 'data has the same content with the original dictionary');
assert_throws_dom('NotSupportedError', () => record.toRecords(), assert_throws_dom('NotSupportedError', () => record.toRecords(),
'Only smart-poster records and external type records could have embedded records.'); 'Only smart-poster, external type and local type records could have embedded records.');
}, 'NDEFRecord constructor with text record type and string data'); }, 'NDEFRecord constructor with text record type and string data');
test(() => { test(() => {
@ -114,7 +114,7 @@
assert_equals(decoder.decode(record.data), test_text_data, assert_equals(decoder.decode(record.data), test_text_data,
'data has the same content with the original dictionary'); 'data has the same content with the original dictionary');
assert_throws_dom('NotSupportedError', () => record.toRecords(), assert_throws_dom('NotSupportedError', () => record.toRecords(),
'Only smart-poster records and external type records could have embedded records.'); 'Only smart-poster, external type and local type records could have embedded records.');
}, 'NDEFRecord constructor with text record type and arrayBuffer data'); }, 'NDEFRecord constructor with text record type and arrayBuffer data');
test(() => { test(() => {
@ -131,7 +131,7 @@
assert_equals(decoder.decode(record.data), test_text_data, assert_equals(decoder.decode(record.data), test_text_data,
'data has the same content with the original dictionary'); 'data has the same content with the original dictionary');
assert_throws_dom('NotSupportedError', () => record.toRecords(), assert_throws_dom('NotSupportedError', () => record.toRecords(),
'Only smart-poster records and external type records could have embedded records.'); 'Only smart-poster, external type and local type records could have embedded records.');
}, 'NDEFRecord constructor with text record type and arrayBufferView data'); }, 'NDEFRecord constructor with text record type and arrayBufferView data');
test(() => { test(() => {
@ -191,7 +191,7 @@
assert_equals(decoder.decode(record.data), test_url_data, assert_equals(decoder.decode(record.data), test_url_data,
'data has the same content with the original dictionary'); 'data has the same content with the original dictionary');
assert_throws_dom('NotSupportedError', () => record.toRecords(), assert_throws_dom('NotSupportedError', () => record.toRecords(),
'Only smart-poster records and external type records could have embedded records.'); 'Only smart-poster, external type and local type records could have embedded records.');
}, 'NDEFRecord constructor with url record type'); }, 'NDEFRecord constructor with url record type');
test(() => { test(() => {
@ -203,7 +203,7 @@
assert_equals(decoder.decode(record.data), test_url_data, assert_equals(decoder.decode(record.data), test_url_data,
'data has the same content with the original dictionary'); 'data has the same content with the original dictionary');
assert_throws_dom('NotSupportedError', () => record.toRecords(), assert_throws_dom('NotSupportedError', () => record.toRecords(),
'Only smart-poster records and external type records could have embedded records.'); 'Only smart-poster, external type and local type records could have embedded records.');
}, 'NDEFRecord constructor with absolute-url record type'); }, 'NDEFRecord constructor with absolute-url record type');
test(() => { test(() => {
@ -222,7 +222,7 @@
assert_array_equals(new Uint8Array(record.data.buffer), [1, 2, 3, 4], assert_array_equals(new Uint8Array(record.data.buffer), [1, 2, 3, 4],
'data has the same content with the original buffer'); 'data has the same content with the original buffer');
assert_throws_dom('NotSupportedError', () => record.toRecords(), assert_throws_dom('NotSupportedError', () => record.toRecords(),
'Only smart-poster records and external type records could have embedded records.'); 'Only smart-poster, external type and local type records could have embedded records.');
} }
// Feed ArrayBufferView. // Feed ArrayBufferView.
{ {
@ -233,7 +233,7 @@
assert_array_equals(new Uint8Array(record.data.buffer), [2, 3, 4], assert_array_equals(new Uint8Array(record.data.buffer), [2, 3, 4],
'data has the same content with the original buffer view'); 'data has the same content with the original buffer view');
assert_throws_dom('NotSupportedError', () => record.toRecords(), assert_throws_dom('NotSupportedError', () => record.toRecords(),
'Only smart-poster records and external type records could have embedded records.'); 'Only smart-poster, external type and local type records could have embedded records.');
} }
}, 'NDEFRecord constructor with mime record type and stream data'); }, 'NDEFRecord constructor with mime record type and stream data');
@ -246,7 +246,7 @@
test_json_data, test_json_data,
'data has the same content with the original json'); 'data has the same content with the original json');
assert_throws_dom('NotSupportedError', () => record.toRecords(), assert_throws_dom('NotSupportedError', () => record.toRecords(),
'Only smart-poster records and external type records could have embedded records.'); 'Only smart-poster, external type and local type records could have embedded records.');
}, 'NDEFRecord constructor with mime record type and json data'); }, 'NDEFRecord constructor with mime record type and json data');
test(() => { test(() => {
@ -264,7 +264,7 @@
assert_array_equals(new Uint8Array(record.data.buffer), [1, 2, 3, 4], assert_array_equals(new Uint8Array(record.data.buffer), [1, 2, 3, 4],
'data has the same content with the original buffer'); 'data has the same content with the original buffer');
assert_throws_dom('NotSupportedError', () => record.toRecords(), assert_throws_dom('NotSupportedError', () => record.toRecords(),
'Only smart-poster records and external type records could have embedded records.'); 'Only smart-poster, external type and local type records could have embedded records.');
} }
// Feed ArrayBufferView. // Feed ArrayBufferView.
{ {
@ -275,7 +275,7 @@
assert_array_equals(new Uint8Array(record.data.buffer), [2, 3, 4], assert_array_equals(new Uint8Array(record.data.buffer), [2, 3, 4],
'data has the same content with the original buffer view'); 'data has the same content with the original buffer view');
assert_throws_dom('NotSupportedError', () => record.toRecords(), assert_throws_dom('NotSupportedError', () => record.toRecords(),
'Only smart-poster records and external type records could have embedded records.'); 'Only smart-poster, external type and local type records could have embedded records.');
} }
}, 'NDEFRecord constructor with unknown record type'); }, 'NDEFRecord constructor with unknown record type');
@ -330,6 +330,77 @@
} }
}, 'NDEFRecord constructor with external record type'); }, 'NDEFRecord constructor with external record type');
test(() => {
assert_throws_js(TypeError, () => new NDEFRecord(createRecord(':xyz', test_buffer_data)),
'The local type record must be embedded in the payload of another record (smart-poster, external, or local)');
// The following test cases use an external type record embedding our target local type record.
const local_record= createRecord(':xyz', undefined /* data */, 'dummy_id_for_local_type');
const payload_message = createMessage([local_record]);
const external_record_embedding_local_record = createRecord('example.com:foo', payload_message);
local_record.data = "A string is not a BufferSource or NDEFMessageInit";
assert_throws_js(TypeError, () => new NDEFRecord(external_record_embedding_local_record),
'Only BufferSource and NDEFMessageInit are allowed to be the record data.');
let buffer = new ArrayBuffer(4);
new Uint8Array(buffer).set([1, 2, 3, 4]);
// Feed ArrayBuffer.
{
local_record.data = buffer;
const record = new NDEFRecord(external_record_embedding_local_record);
const embedded_records = record.toRecords();
assert_equals(embedded_records.length, 1, 'Only the one embedded local record.');
// The embedded local record is actually from |local_record|.
assert_equals(embedded_records[0].recordType, ':xyz', 'recordType');
assert_equals(embedded_records[0].mediaType, null, 'mediaType');
assert_equals(embedded_records[0].id, 'dummy_id_for_local_type', 'id');
assert_array_equals(new Uint8Array(embedded_records[0].data.buffer), [1, 2, 3, 4],
'data has the same content with the original buffer');
assert_equals(embedded_records[0].toRecords(), null,
'toRecords() returns null if the payload is not an NDEF message.');
}
// Feed ArrayBufferView.
{
let buffer_view = new Uint8Array(buffer, 1);
local_record.data = buffer_view;
const record = new NDEFRecord(external_record_embedding_local_record);
const embedded_records = record.toRecords();
assert_equals(embedded_records.length, 1, 'Only the one embedded local record.');
// The embedded local record is actually from |local_record|.
assert_equals(embedded_records[0].recordType, ':xyz', 'recordType');
assert_equals(embedded_records[0].mediaType, null, 'mediaType');
assert_equals(embedded_records[0].id, 'dummy_id_for_local_type', 'id');
assert_array_equals(new Uint8Array(embedded_records[0].data.buffer), [2, 3, 4],
'data has the same content with the original buffer view');
assert_equals(embedded_records[0].toRecords(), null,
'toRecords() returns null if the payload is not an NDEF message.');
}
// Feed NDEFMessageInit.
{
const payload_message = createMessage([createTextRecord(test_text_data)]);
local_record.data = payload_message;
const record = new NDEFRecord(external_record_embedding_local_record);
const embedded_records = record.toRecords();
assert_equals(embedded_records.length, 1, 'Only the one embedded local record.');
// The embedded local record is actually from |local_record|.
assert_equals(embedded_records[0].recordType, ':xyz', 'recordType');
assert_equals(embedded_records[0].mediaType, null, 'mediaType');
assert_equals(embedded_records[0].id, 'dummy_id_for_local_type', 'id');
// The embedded local record embeds another text record that's from |payload_message|.
const embedded_records_in_local_record = embedded_records[0].toRecords();
assert_equals(embedded_records_in_local_record.length, 1, 'Only one embedded record.');
// The only one embedded record has correct content.
assert_equals(embedded_records_in_local_record[0].recordType, 'text', 'recordType');
assert_equals(embedded_records_in_local_record[0].mediaType, null, 'mediaType');
assert_equals(embedded_records_in_local_record[0].id, test_record_id, 'id');
const decoder = new TextDecoder();
assert_equals(decoder.decode(embedded_records_in_local_record[0].data), test_text_data,
'data has the same content with the original dictionary');
}
}, 'NDEFRecord constructor with local record type');
test(() => { test(() => {
assert_throws_js(TypeError, () => new NDEFRecord(createRecord('EMptY')), assert_throws_js(TypeError, () => new NDEFRecord(createRecord('EMptY')),
'Unknown record type.'); 'Unknown record type.');
@ -370,4 +441,47 @@
'example.com:xyz/', test_buffer_data)), 'The type should not contain \'/\'.'); 'example.com:xyz/', test_buffer_data)), 'The type should not contain \'/\'.');
}, 'NDEFRecord constructor with invalid external record type'); }, 'NDEFRecord constructor with invalid external record type');
test(() => {
assert_throws_js(TypeError, () => new NDEFRecord(createRecord(':xyz', test_buffer_data)),
'The local type record must be embedded in the payload of another record (smart-poster, external, or local)');
// The following test cases use an external type record embedding our target local type record.
const local_record= createRecord(':xyz', test_buffer_data);
const payload_message = createMessage([local_record]);
const external_record_embedding_local_record = createRecord('example.com:foo', payload_message);
// OK.
new NDEFRecord(external_record_embedding_local_record);
local_record.recordType = ':xyZ123';
new NDEFRecord(external_record_embedding_local_record);
local_record.recordType = ':123XYz';
new NDEFRecord(external_record_embedding_local_record);
local_record.recordType = ':hellö';
assert_throws_js(TypeError, () => new NDEFRecord(external_record_embedding_local_record),
'The local type must be an ASCII string.');
// Length of the local type excluding the prefix ':' is 255, OK.
local_record.recordType = ':' + [...Array(255)].map(_ => 'a').join('');
const record_255 = new NDEFRecord(external_record_embedding_local_record);
// Exceeding 255, Throws.
local_record.recordType = ':' + [...Array(256)].map(_ => 'a').join('');
assert_throws_js(TypeError, () => new NDEFRecord(external_record_embedding_local_record),
'The local type excluding the prefix \':\' should not be longer than 255.');
local_record.recordType = 'xyz';
assert_throws_js(TypeError, () => new NDEFRecord(external_record_embedding_local_record),
'The local type must start with a \':\'.');
local_record.recordType = ':Xyz';
assert_throws_js(TypeError, () => new NDEFRecord(external_record_embedding_local_record),
'The local type must have a lower case letter or digit following the prefix \':\'.');
local_record.recordType = ':-xyz';
assert_throws_js(TypeError, () => new NDEFRecord(external_record_embedding_local_record),
'The local type must have a lower case letter or digit following the prefix \':\'.');
}, 'NDEFRecord constructor with various local record types');
</script> </script>

Просмотреть файл

@ -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) => {
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) => {
// 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) => {