diff options
Diffstat (limited to 'testing/web-platform/tests/web-nfc')
19 files changed, 2523 insertions, 0 deletions
diff --git a/testing/web-platform/tests/web-nfc/META.yml b/testing/web-platform/tests/web-nfc/META.yml new file mode 100644 index 0000000000..32c24570db --- /dev/null +++ b/testing/web-platform/tests/web-nfc/META.yml @@ -0,0 +1,4 @@ +spec: https://w3c.github.io/web-nfc/ +suggested_reviewers: + - Honry + - kenchris diff --git a/testing/web-platform/tests/web-nfc/NDEFMessage_constructor.https.window.js b/testing/web-platform/tests/web-nfc/NDEFMessage_constructor.https.window.js new file mode 100644 index 0000000000..f4d9a441b4 --- /dev/null +++ b/testing/web-platform/tests/web-nfc/NDEFMessage_constructor.https.window.js @@ -0,0 +1,42 @@ +// META: script=resources/nfc-helpers.js + +'use strict'; + +test(() => { + assert_equals(NDEFMessage.length, 1); + assert_throws_js(TypeError, () => new NDEFMessage()); +}, 'NDEFMessage constructor without init dict'); + +test(() => { + assert_throws_js( + TypeError, () => new NDEFMessage(null), + 'NDEFMessageInit#records is a required field.'); +}, 'NDEFMessage constructor with null init dict'); + +test(() => { + assert_throws_js( + TypeError, () => new NDEFMessage({dummy_key: 'dummy_value'}), + 'NDEFMessageInit#records is a required field.'); +}, 'NDEFMessage constructor without NDEFMessageInit#records field'); + +test(() => { + assert_throws_js( + TypeError, () => new NDEFMessage({records: []}), + 'NDEFMessageInit#records should not be empty.'); +}, 'NDEFMessage constructor with NDEFMessageInit#records being empty'); + +test(() => { + const message = + new NDEFMessage(createMessage([createTextRecord(test_text_data)])); + assert_equals(message.records.length, 1, 'one text record'); + assert_equals(message.records[0].recordType, 'text', 'messageType'); + assert_equals(message.records[0].mediaType, null, 'mediaType'); + assert_equals(message.records[0].encoding, 'utf-8', 'encoding'); + assert_equals(message.records[0].lang, 'en', 'lang'); + assert_true( + message.records[0].data instanceof DataView, 'data returns a DataView'); + const decoder = new TextDecoder(); + assert_equals( + decoder.decode(message.records[0].data), test_text_data, + 'data contains the same text content'); +}, 'NDEFMessage constructor with a text record'); diff --git a/testing/web-platform/tests/web-nfc/NDEFMessage_recursion-limit.https.window.js b/testing/web-platform/tests/web-nfc/NDEFMessage_recursion-limit.https.window.js new file mode 100644 index 0000000000..49a69524d9 --- /dev/null +++ b/testing/web-platform/tests/web-nfc/NDEFMessage_recursion-limit.https.window.js @@ -0,0 +1,156 @@ +'use strict'; + +test(() => { + let recordInit = {recordType: 'w3.org:ExternalRecord'}; + const messageInit = {records: [recordInit]}; + recordInit.data = messageInit; + + assert_throws_js(TypeError, () => { + new NDEFMessage(messageInit); + }, 'Creating a recursive NDEFMessage throws a TypeError'); + assert_throws_js(TypeError, () => { + new NDEFRecord(recordInit); + }, 'Creating a recursive NDEFRecord throws a TypeError'); + assert_throws_js(TypeError, () => { + new NDEFReadingEvent('message', {message: messageInit}); + }, 'Creating a recursive NDEFReadingEvent throws a TypeError'); +}, 'NDEFRecord and NDEFMessage cycle in external records'); + +test(() => { + let recordInit = {recordType: ':local'}; + const messageInit = {records: [recordInit]}; + recordInit.data = messageInit; + + const externalRecordMessageInit = { + records: [{recordType: 'w3.org:ExternalRecord', data: messageInit}] + }; + + assert_throws_js(TypeError, () => { + new NDEFMessage(externalRecordMessageInit); + }, 'Creating a recursive NDEFMessage throws a TypeError'); + assert_throws_js(TypeError, () => { + new NDEFRecord(externalRecordMessageInit.records[0]); + }, 'Creating a recursive NDEFRecord throws a TypeError'); + assert_throws_js(TypeError, () => { + new NDEFReadingEvent('message', {message: externalRecordMessageInit}); + }, 'Creating a recursive NDEFReadingEvent throws a TypeError'); +}, 'NDEFRecord and NDEFMessage cycle in local records'); + +test(() => { + let recordInit = {recordType: 'smart-poster'}; + const messageInit = { + records: [ + // Smart poster records require an URL record. Add it here so we can be + // sure a TypeError is being thrown because of the recursion limit, not + // the lack of a mandatory record. + {recordType: 'url', data: 'https://w3.org'}, recordInit + ] + }; + recordInit.data = messageInit; + + assert_throws_js(TypeError, () => { + new NDEFMessage(messageInit); + }, 'Creating a recursive NDEFMessage throws a TypeError'); + assert_throws_js(TypeError, () => { + new NDEFRecord(recordInit); + }, 'Creating a recursive NDEFRecord throws a TypeError'); + assert_throws_js(TypeError, () => { + new NDEFReadingEvent('message', {message: messageInit}); + }, 'Creating a recursive NDEFReadingEvent throws a TypeError'); +}, 'NDEFRecord and NDEFMessage cycle in smart poster records'); + +function makeSmartPosterMessageInit(innerMessageInit) { + const innerRecords = innerMessageInit.records; + return { + records: [{ + recordType: 'smart-poster', + data: { + records: + [{recordType: 'url', data: 'https://w3.org'}].concat(innerRecords) + } + }] + }; +} + +// Creates an NDEFMessageInit with nested records except for the innermost +// one, which is an empty record. +function makeRecursiveMessageInit(innerRecordType, maxDepth) { + function innerHelper(value) { + if (++value > maxDepth) { + return {records: [{recordType: 'empty'}]}; + } + + return {records: [{recordType: innerRecordType, data: innerHelper(value)}]}; + } + + return innerHelper(0); +} + +// Maximum number of chained NDEFMessages according to the spec. +const MAX_NESTING_LEVEL = 32; + +test(() => { + // makeRecursiveMessageInit(..., N) will cause N NDEFMessages to be created + // when it is parsed. The calls are passed to an outer NDEFMessage + // constructor, so we end up with N+1 NDEFMessage objects. The spec allows + // up to 32 NDEFMessages in the same chain, and we have 33 here. + assert_throws_js(TypeError, () => { + new NDEFMessage( + makeRecursiveMessageInit('w3.org:ExternalRecord', MAX_NESTING_LEVEL)); + }, 'Creating a recursive NDEFMessage throws a TypeError'); + assert_throws_js(TypeError, () => { + new NDEFReadingEvent('message', { + message: + makeRecursiveMessageInit('w3.org:ExternalRecord', MAX_NESTING_LEVEL) + }); + }, 'Creating a recursive NDEFReadingEvent throws a TypeError'); + + // Here we call makeRecursiveMessageInit() with a smaller number than above + // because there is a smart poster wrapping everything that also creates an + // NDEFMessage. + assert_throws_js(TypeError, () => { + const innerMessageInit = makeRecursiveMessageInit( + 'w3.org:ExternalRecord', MAX_NESTING_LEVEL - 1); + new NDEFMessage(makeSmartPosterMessageInit(innerMessageInit)); + }, 'Creating a recursive NDEFMessage throws a TypeError'); + assert_throws_js(TypeError, () => { + const innerMessageInit = + makeRecursiveMessageInit(':local', MAX_NESTING_LEVEL - 1); + new NDEFMessage(makeSmartPosterMessageInit(innerMessageInit)); + }, 'Creating a recursive NDEFMessage throws a TypeError'); + assert_throws_js(TypeError, () => { + const innerMessageInit = makeRecursiveMessageInit( + 'w3.org:ExternalRecord', MAX_NESTING_LEVEL - 1); + new NDEFReadingEvent( + 'message', {message: makeSmartPosterMessageInit(innerMessageInit)}); + }, 'Creating a recursive NDEFMessage throws a TypeError'); + assert_throws_js(TypeError, () => { + const innerMessageInit = + makeRecursiveMessageInit(':local', MAX_NESTING_LEVEL - 1); + new NDEFReadingEvent( + 'message', {message: makeSmartPosterMessageInit(innerMessageInit)}); + }, 'Creating a recursive NDEFMessage throws a TypeError'); +}, 'Create too many nested NDEFMessages'); + +// See above for explanations about the counts passed to +// makeRecursiveMessageInit(). +test(() => { + new NDEFMessage( + makeRecursiveMessageInit('w3.org:ExternalRecord', MAX_NESTING_LEVEL - 1)); + new NDEFReadingEvent('message', { + message: + makeRecursiveMessageInit('w3.org:ExternalRecord', MAX_NESTING_LEVEL - 1) + }); + + let innerMessageInit; + + innerMessageInit = + makeRecursiveMessageInit('w3.org:ExternalRecord', MAX_NESTING_LEVEL - 2); + new NDEFMessage(makeSmartPosterMessageInit(innerMessageInit)); + new NDEFReadingEvent( + 'message', {message: makeSmartPosterMessageInit(innerMessageInit)}); + innerMessageInit = makeRecursiveMessageInit(':local', MAX_NESTING_LEVEL - 2); + new NDEFMessage(makeSmartPosterMessageInit(innerMessageInit)); + new NDEFReadingEvent( + 'message', {message: makeSmartPosterMessageInit(innerMessageInit)}); +}, 'Nest maximum number of NDEFMessages') diff --git a/testing/web-platform/tests/web-nfc/NDEFReader-make-read-only-document-hidden-manual.https.html b/testing/web-platform/tests/web-nfc/NDEFReader-make-read-only-document-hidden-manual.https.html new file mode 100644 index 0000000000..25cefcbca7 --- /dev/null +++ b/testing/web-platform/tests/web-nfc/NDEFReader-make-read-only-document-hidden-manual.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<title>NDEFReader.makeReadOnly respect page visibility changes</title> +<link rel="help" href="https://w3c.github.io/web-nfc/#visible-document"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script> + +promise_test(async t => { + const ndef = new NDEFReader(); + const p1 = ndef.makeReadOnly(); + + return await new Promise((resolve, reject) => { + p1.then(() => { + assert_false(document.hidden); + resolve(); + }).catch(e => { + reject(); + }); + }); +}, "Test NDEFReader.makeReadOnly operation should be suspended when document is not visible"); + +</script> + +<p>Step1: switch the page to the background, then tap an NFC tag.</p> +<p>Step2: switch back to the page, then tap the tag again.</p> diff --git a/testing/web-platform/tests/web-nfc/NDEFReader-read-document-hidden-manual.https.html b/testing/web-platform/tests/web-nfc/NDEFReader-read-document-hidden-manual.https.html new file mode 100644 index 0000000000..47c1332713 --- /dev/null +++ b/testing/web-platform/tests/web-nfc/NDEFReader-read-document-hidden-manual.https.html @@ -0,0 +1,27 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<title>NDEFReader.onreading respect page visibility changes</title> +<link rel="help" href="https://w3c.github.io/web-nfc/#visible-document"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/nfc-helpers.js"></script> +<script> + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const ndefWatcher = new EventWatcher(t, reader, ["reading", "readingerror"]); + const promise = new Promise((resolve, reject) => { + ndefWatcher.wait_for("reading").then(event => { + if (document.hidden) reject(); + else resolve(); + }); + }); + await ndef.scan(); + await promise; +}, "Test NDEFReader.onreading is not fired when document is hidden"); + +</script> + +<p>Step1: switch the page to the background, then tap a formatted NFC tag.</p> +<p>Step2: switch back to the page, then tap the tag again.</p> diff --git a/testing/web-platform/tests/web-nfc/NDEFReader-write-document-hidden-manual.https.html b/testing/web-platform/tests/web-nfc/NDEFReader-write-document-hidden-manual.https.html new file mode 100644 index 0000000000..150e5a239c --- /dev/null +++ b/testing/web-platform/tests/web-nfc/NDEFReader-write-document-hidden-manual.https.html @@ -0,0 +1,28 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<meta name="timeout" content="long"> +<title>NDEFReader.write respect page visibility changes</title> +<link rel="help" href="https://w3c.github.io/web-nfc/#visible-document"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/nfc-helpers.js"></script> +<script> + +promise_test(async t => { + const writer = new NDEFReader(); + const p1 = writer.write(test_text_data); + + return await new Promise((resolve, reject) => { + p1.then(() => { + assert_false(document.hidden); + resolve(); + }).catch(e => { + reject(); + }); + }); +}, "Test NDEFReader.write operation should be suspended when document is not visible"); + +</script> + +<p>Step1: switch the page to the background, then tap an NFC tag.</p> +<p>Step2: switch back to the page, then tap the tag again.</p> diff --git a/testing/web-platform/tests/web-nfc/NDEFReader_make-read-only.https.window.js b/testing/web-platform/tests/web-nfc/NDEFReader_make-read-only.https.window.js new file mode 100644 index 0000000000..f5872088ca --- /dev/null +++ b/testing/web-platform/tests/web-nfc/NDEFReader_make-read-only.https.window.js @@ -0,0 +1,174 @@ +// META: script=resources/nfc-helpers.js + +// NDEFReader.makeReadOnly method +// https://w3c.github.io/web-nfc/#dom-ndefreader-makereadonly + +'use strict'; + +const invalid_signals = ['string', 123, {}, true, Symbol(), () => {}, self]; + +nfc_test(async t => { + await test_driver.set_permission({name: 'nfc'}, 'denied'); + const ndef = new NDEFReader(); + await promise_rejects_dom(t, 'NotAllowedError', ndef.makeReadOnly()); +}, 'NDEFReader.makeReadOnly should fail if user permission is not granted.'); + +// We do not provide NFC mock here to simulate that there has no available +// implementation for NFC Mojo interface. +nfc_test(async (t, mockNFC) => { + mockNFC.simulateClosedPipe(); + const ndef = new NDEFReader(); + await promise_rejects_dom(t, 'NotSupportedError', ndef.makeReadOnly()); +}, 'NDEFReader.makeReadOnly should fail if no implementation for NFC Mojo interface is available.'); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + + // Make sure makeReadOnly is pending + mockNFC.setPendingMakeReadOnlyCompleted(false); + const p = ndef.makeReadOnly({signal: controller.signal}); + const rejected = promise_rejects_dom(t, 'AbortError', p); + let callback_called = false; + await new Promise(resolve => { + t.step_timeout(() => { + callback_called = true; + controller.abort(); + resolve(); + }, 10); + }); + await rejected; + assert_true(callback_called, 'timeout should have caused the abort'); +}, 'NDEFReader.makeReadOnly should fail if request is aborted before makeReadOnly happends.'); + +nfc_test(async t => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + assert_false(controller.signal.aborted); + controller.abort(); + assert_true(controller.signal.aborted); + await promise_rejects_dom( + t, 'AbortError', ndef.makeReadOnly({signal: controller.signal})); +}, 'NDEFReader.makeReadOnly should fail if signal is already aborted.'); + +nfc_test(async t => { + const ndef = new NDEFReader(); + const promises = []; + invalid_signals.forEach(invalid_signal => { + promises.push(promise_rejects_js( + t, TypeError, ndef.makeReadOnly({signal: invalid_signal}))); + }); + await Promise.all(promises); +}, 'NDEFReader.write should fail if signal is not an AbortSignal.'); + +nfc_test(async (t, mockNFC) => { + const ndef1 = new NDEFReader(); + const ndef2 = new NDEFReader(); + const controller = new AbortController(); + const p1 = ndef1.makeReadOnly({signal: controller.signal}); + + // Even though makeReadOnly request is grantable, + // this abort should be processed synchronously. + controller.abort(); + await promise_rejects_dom(t, 'AbortError', p1); + + await ndef2.makeReadOnly(); +}, 'Synchronously signaled abort.'); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + mockNFC.setHWStatus(NFCHWStatus.DISABLED); + await promise_rejects_dom(t, 'NotReadableError', ndef.makeReadOnly()); +}, 'NDEFReader.makeReadOnly should fail when NFC HW is disabled.'); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + mockNFC.setHWStatus(NFCHWStatus.NOT_SUPPORTED); + await promise_rejects_dom(t, 'NotSupportedError', ndef.makeReadOnly()); +}, 'NDEFReader.makeReadOnly should fail when NFC HW is not supported.'); + +nfc_test(async () => { + await new Promise((resolve, reject) => { + const iframe = document.createElement('iframe'); + iframe.srcdoc = `<script> + window.onmessage = async (message) => { + if (message.data === "Ready") { + try { + const ndef = new NDEFReader(); + await ndef.makeReadOnly(); + parent.postMessage("Failure", "*"); + } catch (error) { + if (error.name == "InvalidStateError") { + parent.postMessage("Success", "*"); + } else { + parent.postMessage("Failure", "*"); + } + } + } + }; + </script>`; + iframe.onload = () => iframe.contentWindow.postMessage('Ready', '*'); + document.body.appendChild(iframe); + window.onmessage = message => { + if (message.data == 'Success') { + resolve(); + } else if (message.data == 'Failure') { + reject(); + } + } + }); +}, 'Test that WebNFC API is not accessible from iframe context.'); + +nfc_test(async () => { + const ndef = new NDEFReader(); + await ndef.makeReadOnly(); +}, 'NDEFReader.makeReadOnly should succeed when NFC HW is enabled'); + +nfc_test(async (t, mockNFC) => { + const ndef1 = new NDEFReader(); + const ndef2 = new NDEFReader(); + + // Make sure the first makeReadOnly will be pending. + mockNFC.setPendingMakeReadOnlyCompleted(false); + + const p1 = ndef1.makeReadOnly(); + const p2 = ndef2.makeReadOnly(); + + await promise_rejects_dom(t, 'AbortError', p1); + await p2; +}, 'NDEFReader.makeReadOnly should replace all previously configured makeReadOnly operations.'); + +nfc_test(async () => { + const ndef = new NDEFReader(); + + const controller1 = new AbortController(); + await ndef.makeReadOnly({signal: controller1.signal}); + + const controller2 = new AbortController(); + const promise = ndef.makeReadOnly({signal: controller2.signal}); + controller1.abort(); + await promise; +}, 'NDEFReader.makeReadOnly signals are independent.'); + +nfc_test(async (t, mockNFC) => { + // Make sure the makeReadOnly will be pending in the mock. + mockNFC.setPendingMakeReadOnlyCompleted(false); + + const ndef1 = new NDEFReader(); + const promise = ndef1.makeReadOnly(); + + // Just to make sure the makeReadOnly() request has already reached to the + // mock. + const ndef2 = new NDEFReader(); + await ndef2.scan(); + + mockNFC.simulateNonNDEFTagDiscovered(); + await promise_rejects_dom(t, 'NotSupportedError', promise); +}, 'NDEFReader.makeReadOnly should fail when the NFC device coming up does not expose \ +NDEF technology.'); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + mockNFC.simulateDataTransferFails(); + await promise_rejects_dom(t, 'NetworkError', ndef.makeReadOnly()); +}, 'NDEFReader.makeReadOnly should fail with NetworkError when NFC data transfer fails.'); diff --git a/testing/web-platform/tests/web-nfc/NDEFReader_scan.https.html b/testing/web-platform/tests/web-nfc/NDEFReader_scan.https.html new file mode 100644 index 0000000000..a1a4a04db7 --- /dev/null +++ b/testing/web-platform/tests/web-nfc/NDEFReader_scan.https.html @@ -0,0 +1,323 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Web NFC: NDEFReader.scan tests</title> +<link rel="author" title="Intel" href="http://www.intel.com"/> +<link rel="help" href="https://w3c.github.io/web-nfc/"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/nfc-helpers.js"></script> +<script> + +"use strict"; + +const invalid_signals = [ + "string", + 123, + {}, + true, + Symbol(), + () => {}, + self +]; + +function waitSyntaxErrorPromise(t, scan_options) { + const ndef = new NDEFReader(); + return promise_rejects_dom(t, 'SyntaxError', ndef.scan(scan_options)); +} + +nfc_test(async t => { + const ndef = new NDEFReader(); + const promises = []; + invalid_signals.forEach(invalid_signal => { + promises.push(promise_rejects_js(t, TypeError, + ndef.scan({ signal: invalid_signal }))); + }); + await Promise.all(promises); +}, "Test that NDEFReader.scan rejects if signal is not an AbortSignal."); + +nfc_test(async t => { + await test_driver.set_permission({ name: 'nfc' }, 'denied'); + const ndef = new NDEFReader(); + await promise_rejects_dom(t, 'NotAllowedError', ndef.scan()); +}, "NDEFReader.scan should fail if user permission is not granted."); + +// We do not provide NFC mock here to simulate that there has no available +// implementation for NFC Mojo interface. +nfc_test(async (t, mockNFC) => { + mockNFC.simulateClosedPipe(); + const ndef = new NDEFReader(); + await promise_rejects_dom(t, 'NotSupportedError', ndef.scan()); +}, "NDEFReader.scan should fail if no implementation for NFC Mojo interface."); + +nfc_test(async (t, mockNFC) => { + mockNFC.setHWStatus(NFCHWStatus.DISABLED); + const ndef = new NDEFReader(); + await promise_rejects_dom(t, 'NotReadableError', ndef.scan()); +}, "NDEFReader.scan should fail if NFC HW is disabled."); + +nfc_test(async (t, mockNFC) => { + mockNFC.setHWStatus(NFCHWStatus.NOT_SUPPORTED); + const ndef = new NDEFReader(); + await promise_rejects_dom(t, 'NotSupportedError', ndef.scan()); +}, "NDEFReader.scan should fail if NFC HW is not supported."); + +nfc_test(async () => { + await new Promise((resolve,reject) => { + const iframe = document.createElement('iframe'); + iframe.srcdoc = `<script> + window.onmessage = async (message) => { + if (message.data === "Ready") { + try { + const ndef = new NDEFReader(); + await ndef.scan(); + parent.postMessage("Failure", "*"); + } catch (error) { + if (error.name == "InvalidStateError") { + parent.postMessage("Success", "*"); + } else { + parent.postMessage("Failure", "*"); + } + } + } + }; + <\/script>`; + iframe.onload = () => iframe.contentWindow.postMessage('Ready', '*'); + document.body.appendChild(iframe); + window.onmessage = message => { + if (message.data == 'Success') { + resolve(); + } else if (message.data == 'Failure') { + reject(); + } + } + }); +}, 'Test that WebNFC API is not accessible from iframe context.'); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + const ndefWatcher = new EventWatcher(t, ndef, ["reading", "readingerror"]); + const promise = ndefWatcher.wait_for("reading").then(event => { + assert_true(event instanceof NDEFReadingEvent); + controller.abort(); + }); + await ndef.scan({signal : controller.signal}); + + mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)])); + await promise; +}, "Test that nfc watch success if NFC HW is enabled."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + controller.abort(); + await promise_rejects_dom(t, 'AbortError', ndef.scan({signal: controller.signal})); +}, "Test that NDEFReader.scan rejects if NDEFScanOptions.signal is already aborted."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + const promise = ndef.scan({signal: controller.signal}); + controller.abort(); + await promise_rejects_dom(t, 'AbortError', promise); +}, "Test that NDEFReader.scan rejects if NDEFScanOptions.signal aborts right after \ +the scan invocation."); + +nfc_test(async () => { + const ndef = new NDEFReader(); + + const controller1 = new AbortController(); + await ndef.scan({signal: controller1.signal}); + + controller1.abort(); + + const controller2 = new AbortController(); + await ndef.scan({signal: controller2.signal}); +}, "Test that NDEFReader.scan signals are independant."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + const ndefWatcher = new EventWatcher(t, ndef, ["reading", "readingerror"]); + const message = createMessage([createTextRecord(test_text_data)]); + const promise = ndefWatcher.wait_for("reading").then(event => { + assert_true(event instanceof NDEFReadingEvent); + }); + await ndef.scan({signal : controller.signal}); + + mockNFC.setReadingMessage(message); + await promise; + + ndef.onreading = t.unreached_func("reading event should not be fired."); + mockNFC.setReadingMessage(message); + controller.abort(); + await new Promise((resolve, reject) => { + t.step_timeout(resolve, 100); + }); +}, "Test that NDEFReader can not get any reading events once the signal aborts."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + const ndefWatcher = new EventWatcher(t, ndef, ["reading", "readingerror"]); + const promise = ndefWatcher.wait_for("reading").then(event => { + controller.abort(); + assert_true(event instanceof NDEFReadingEvent); + + // The message in the event contains only the external type record. + assert_equals(event.message.records.length, 1); + assert_equals(event.message.records[0].recordType, 'example.com:containsLocalRecord', + 'recordType'); + + // The external type record contains only the local type record. + assert_equals(event.message.records[0].toRecords().length, 1); + 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(); + assert_equals(decoder.decode(event.message.records[0].toRecords()[0].toRecords()[0].data), + test_text_data, 'data has the same content with the original dictionary'); + }); + await ndef.scan({signal : controller.signal}); + + // An external type record --contains-> a local type record --contains-> a text record. + const messageContainText = createMessage([createTextRecord(test_text_data)]); + const messageContainLocal= createMessage([createRecord(':containsTextRecord', + messageContainText)]); + const message = createMessage([createRecord('example.com:containsLocalRecord', + messageContainLocal)]); + mockNFC.setReadingMessage(message); + await promise; +}, "NDEFRecord.toRecords returns its embedded records correctly."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + const ndefWatcher = new EventWatcher(t, ndef, ["reading", "readingerror"]); + const promise = ndefWatcher.wait_for("reading").then(event => { + controller.abort(); + assert_true(event instanceof NDEFReadingEvent); + + // The message in the event contains only the smart-poster record. + assert_equals(event.message.records.length, 1); + assert_equals(event.message.records[0].recordType, 'smart-poster', 'recordType'); + assert_equals(event.message.records[0].mediaType, null, 'mediaType'); + assert_equals(event.message.records[0].id, 'dummy_record_id', 'id'); + + // The smart-poster record contains one uri record and one text record. + const embedded_records = event.message.records[0].toRecords(); + assert_equals(embedded_records.length, 2); + + const decoder = new TextDecoder(); + let embedded_record_types = []; + for(let record of embedded_records) { + embedded_record_types.push(record.recordType); + switch(record.recordType) { + case 'url': + assert_equals(record.mediaType, null, 'url record\'s mediaType'); + assert_equals(record.id, test_record_id, 'url record\'s id'); + assert_equals(decoder.decode(record.data), test_url_data, 'url record\'s data'); + break; + case 'text': + assert_equals(record.mediaType, null, 'text record\'s mediaType'); + assert_equals(record.id, test_record_id, 'text record\'s id'); + assert_equals(decoder.decode(record.data), test_text_data, 'text record\'s data'); + break; + default: + assert_unreached("Unknown recordType"); + } + } + assert_array_equals(embedded_record_types.sort(), ['text', 'url'], + 'smart-poster record\'s contained record types'); + }); + await ndef.scan({signal : controller.signal}); + + // A smart-poster record contains a uri record, text record. + const uri_record = createUrlRecord(test_url_data); + const text_record = createTextRecord(test_text_data); + const payload_message = createMessage([uri_record, text_record]); + const message = createMessage([createRecord( + 'smart-poster', payload_message, "dummy_record_id")]); + mockNFC.setReadingMessage(message); + await promise; +}, "NDEFReader.scan returns smart-poster record correctly."); + +nfc_test(async (t, mockNFC) => { + const promises = []; + + const ndef1 = new NDEFReader(); + const ndefWatcher1 = new EventWatcher(t, ndef1, ["reading", "readingerror"]); + const promise1 = ndefWatcher1.wait_for("readingerror"); + promises.push(promise1); + await ndef1.scan(); + + const ndef2 = new NDEFReader(); + const ndefWatcher2 = new EventWatcher(t, ndef2, ["reading", "readingerror"]); + const promise2 = ndefWatcher2.wait_for("readingerror"); + promises.push(promise2); + await ndef2.scan(); + + mockNFC.simulateNonNDEFTagDiscovered(); + await Promise.all(promises); +}, "Test that NDEFReader.onreadingerror should be fired if the NFC tag does not \ +expose NDEF technology."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + const ndefWatcher = new EventWatcher(t, ndef, ["reading", "readingerror"]); + const promise = ndefWatcher.wait_for("reading").then(event => { + assert_equals(event.serialNumber, fake_tag_serial_number); + assert_equals(event.message.records.length, 0); + controller.abort(); + }); + await ndef.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."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + const message = createMessage([createTextRecord(test_text_data), + createMimeRecordFromJson(test_json_data), + createMimeRecord(test_buffer_data), + createUnknownRecord(test_buffer_data), + createUrlRecord(test_url_data), + createUrlRecord(test_url_data, true), + createRecord('w3.org:xyz', test_buffer_data)], + test_message_origin); + const ndefWatcher = new EventWatcher(t, ndef, ["reading", "readingerror"]); + const promise = ndefWatcher.wait_for("reading").then(event => { + assert_equals(event.serialNumber, fake_tag_serial_number); + assertWebNDEFMessagesEqual(event.message, new NDEFMessage(message)); + controller.abort(); + }); + await ndef.scan({signal : controller.signal}); + + mockNFC.setReadingMessage(message); + await promise; +}, "Test that reading message with multiple records should succeed."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const promise1 = ndef.scan(); + const promise2 = promise_rejects_dom(t, 'InvalidStateError', ndef.scan()); + await promise1; + await promise2; + await promise_rejects_dom(t, 'InvalidStateError', ndef.scan()); +}, "Test that NDEFReader.scan rejects if there is already an ongoing scan."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + await ndef.scan({signal : controller.signal}); + controller.abort(); + await ndef.scan(); +}, "Test that NDEFReader.scan can be started after the previous scan is aborted."); +</script> diff --git a/testing/web-platform/tests/web-nfc/NDEFReader_scan_iframe.https.html b/testing/web-platform/tests/web-nfc/NDEFReader_scan_iframe.https.html new file mode 100644 index 0000000000..6967c76ecd --- /dev/null +++ b/testing/web-platform/tests/web-nfc/NDEFReader_scan_iframe.https.html @@ -0,0 +1,43 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>NDEFReader.scan with an focused iframe</title> +<link rel="help" href="https://w3c.github.io/web-nfc/"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/nfc-helpers.js"></script> +<body> +<script> + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + const ndefWatcher = new EventWatcher(t, ndef, ["reading", "readingerror"]); + + const promise = ndefWatcher.wait_for("reading").then(event => { + assert_true(event instanceof NDEFReadingEvent); + controller.abort(); + }); + await ndef.scan({ signal: controller.signal }); + + const iframe = document.createElement('iframe'); + iframe.src = 'resources/support-iframe.html'; + + const iframeLoadWatcher = new EventWatcher(t, iframe, 'load'); + document.body.appendChild(iframe); + await iframeLoadWatcher.wait_for('load'); + // Focus on iframe. + iframe.contentWindow.document.getElementById('foo').focus(); + assert_true(iframe.contentDocument.hasFocus(), 'iframe gains the focus'); + // Suspend NFC operations is async in blink, use setTimeout as a workaround. + // TODO(wanming.lin@intel.com): find a good way to eliminate this race + // condition. + await new Promise(resolve => t.step_timeout(resolve, 0)); + mockNFC.setReadingMessage(createMessage([createTextRecord(test_text_data)])); + await promise; + + // Remove iframe from main document. + iframe.parentNode.removeChild(iframe); +}, 'Test that NDEFReader.scan is not suspended if iframe gains focus.'); + +</script> +</body> diff --git a/testing/web-platform/tests/web-nfc/NDEFReader_write.https.html b/testing/web-platform/tests/web-nfc/NDEFReader_write.https.html new file mode 100644 index 0000000000..0463b779de --- /dev/null +++ b/testing/web-platform/tests/web-nfc/NDEFReader_write.https.html @@ -0,0 +1,480 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Web NFC: NDEFReader.write Tests</title> +<link rel="author" title="Intel" href="http://www.intel.com"/> +<link rel="help" href="https://w3c.github.io/web-nfc/"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<script src="resources/nfc-helpers.js"></script> +<script> + +"use strict"; + +const invalid_type_messages = + [ + // Invalid NDEFMessageSource type + undefined, + + // NDEFMessage.records: should have at least 1 valid record. + // https://w3c.github.io/web-nfc/#the-write-method - Step 8. + createMessage([{}]), + + // NDEFMessageSource: not NDEF-formatable. + // https://w3c.github.io/web-nfc/#the-write-method - Step 8. + createMessage([]), + + // https://w3c.github.io/web-nfc/#dfn-map-text-to-ndef + // NDEFRecord must have data. + createMessage([createTextRecord()]), + + // NDEFRecord.data for 'text' record must be either a string, + // an arrayBuffer, or an arrayBufferView. + createMessage([createTextRecord(test_json_data)]), + + // NDEFRecord.encoding for 'text' record must be either "utf-8", + // "utf-16", "utf-16le" or "utf-16be". + createMessage([createTextRecord(test_buffer_data, "chinese")]), + + // https://w3c.github.io/web-nfc/#dfn-map-a-url-to-ndef + // NDEFRecord must have data. + createMessage([createUrlRecord()]), + + // https://w3c.github.io/web-nfc/#dfn-map-a-url-to-ndef + // NDEFRecord must have data. + createMessage([createUrlRecord(undefined, true)]), + + // NDEFRecord.data for 'url' record must be string. + createMessage([createUrlRecord(test_buffer_data)]), + createMessage([createUrlRecord(test_json_data)]), + + // NDEFRecord.data for 'absolute-url' record must be string. + createMessage([createUrlRecord(test_buffer_data, true)]), + createMessage([createUrlRecord(test_json_data, true)]), + + // https://w3c.github.io/web-nfc/#dfn-map-binary-data-to-ndef + // NDEFRecord must have data. + createMessage([createMimeRecord()]), + + // NDEFRecord.data for 'mime' record must be BufferSource. + createMessage([createMimeRecord(test_text_data)]), + createMessage([createMimeRecord(test_number_data)]), + createMessage([createMimeRecord(test_json_data)]), + + // NDEFRecord must have data. + createMessage([createUnknownRecord()]), + + // NDEFRecord.data for 'unknown' record must be BufferSource. + createMessage([createUnknownRecord(test_text_data)]), + createMessage([createUnknownRecord(test_number_data)]), + createMessage([createUnknownRecord(test_json_data)]), + + // https://w3c.github.io/web-nfc/#dfn-map-external-data-to-ndef + // NDEFRecord must have data. + createMessage([createRecord('w3.org:xyz')]), + + // 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_number_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/#dfn-map-smart-poster-to-ndef + // NDEFRecord must have data. + createMessage([createRecord('smart-poster')]), + + // NDEFRecord.data for smart-poster record must be a NDEFMessageInit. + createMessage([createRecord('smart-poster', test_text_data)]), + createMessage([createRecord('smart-poster', test_number_data)]), + createMessage([createRecord('smart-poster', test_json_data)]), + createMessage([createRecord('smart-poster', test_buffer_data)]), + + // https://w3c.github.io/web-nfc/#ndef-record-types + // The record type is neither a known type ('text', 'mime' etc.) nor a + // valid external/local type. + createMessage([createRecord('unmatched_type', test_buffer_data)]) + ]; + +const invalid_syntax_messages = + [ + // Data for 'url' or 'absolute-url' record, must be a valid URL. + createMessage([createUrlRecord('Invalid URL:// Data')]), + createMessage([createUrlRecord('Invalid URL:// Data', true)]), + + // NDEFRecord.lang length for 'text' record must be lower than 64. + createMessage([createTextRecord(test_text_data, undefined /* encoding */, + [...Array(64)].map(_ => 'a'))]), + ]; + +const invalid_signals = [ + "string", + 123, + {}, + true, + Symbol(), + () => {}, + self +]; + +nfc_test(async t => { + const ndef = new NDEFReader(); + const promises = []; + invalid_type_messages.forEach(message => { + promises.push( + promise_rejects_js(t, TypeError, ndef.write(message))); + }); + await Promise.all(promises); +}, "Test that promise is rejected with TypeError if NDEFMessageSource is invalid."); + +nfc_test(async t => { + const ndef = new NDEFReader(); + const promises = []; + invalid_syntax_messages.forEach(message => { + promises.push( + promise_rejects_dom(t, 'SyntaxError', ndef.write(message))); + }); + await Promise.all(promises); +}, "Test that promise is rejected with SyntaxError if NDEFMessageSource contains\ + invalid records."); + +nfc_test(async t => { + await test_driver.set_permission({ name: 'nfc' }, 'denied'); + const ndef = new NDEFReader(); + await promise_rejects_dom(t, 'NotAllowedError', ndef.write(test_text_data)); +}, 'NDEFReader.write should fail if user permission is not granted.'); + +// We do not provide NFC mock here to simulate that there has no available +// implementation for NFC Mojo interface. +nfc_test(async (t, mockNFC) => { + mockNFC.simulateClosedPipe(); + const ndef = new NDEFReader(); + await promise_rejects_dom(t, 'NotSupportedError', ndef.write(test_text_data)); +}, 'NDEFReader.write should fail if no implementation for NFC Mojo interface is available.'); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + + //Make sure push is pending + mockNFC.setPendingPushCompleted(false); + const p = ndef.write(test_text_data, { signal: controller.signal }); + const rejected = promise_rejects_dom(t, 'AbortError', p); + let callback_called = false; + await new Promise(resolve => { + t.step_timeout(() => { + callback_called = true; + controller.abort(); + resolve(); + }, 10); + }); + await rejected; + assert_true(callback_called, 'timeout should have caused the abort'); +}, "NDEFReader.write should fail if abort write request before write happends."); + +nfc_test(async t => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + assert_false(controller.signal.aborted); + controller.abort(); + assert_true(controller.signal.aborted); + await promise_rejects_dom(t, 'AbortError', + ndef.write(test_text_data, { signal: controller.signal })); +}, "NDEFReader.write should fail if signal's aborted flag is set."); + +nfc_test(async t => { + const ndef = new NDEFReader(); + const promises = []; + invalid_signals.forEach(invalid_signal => { + promises.push(promise_rejects_js(t, TypeError, + ndef.write(test_text_data, { signal: invalid_signal }))); + }); + await Promise.all(promises); +}, "NDEFReader.write should fail if signal is not an AbortSignal."); + +nfc_test(async (t, mockNFC) => { + const ndef1 = new NDEFReader(); + const ndef2 = new NDEFReader(); + const controller = new AbortController(); + const p1 = ndef1.write(test_text_data, { signal: controller.signal }); + + // Even though write request is grantable, + // this abort should be processed synchronously. + controller.abort(); + await promise_rejects_dom(t, 'AbortError', p1); + + await ndef2.write(test_text_data); + assertNDEFMessagesEqual(test_text_data, mockNFC.pushedMessage()); +}, "Synchronously signaled abort."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + mockNFC.setHWStatus(NFCHWStatus.DISABLED); + await promise_rejects_dom(t, 'NotReadableError', ndef.write(test_text_data)); +}, "NDEFReader.write should fail when NFC HW is disabled."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + mockNFC.setHWStatus(NFCHWStatus.NOT_SUPPORTED); + await promise_rejects_dom(t, 'NotSupportedError', ndef.write(test_text_data)); +}, "NDEFReader.write should fail when NFC HW is not supported."); + +nfc_test(async () => { + await new Promise((resolve,reject) => { + const iframe = document.createElement('iframe'); + iframe.srcdoc = `<script> + window.onmessage = async (message) => { + if (message.data === "Ready") { + try { + const ndef = new NDEFReader(); + await ndef.write("Test"); + parent.postMessage("Failure", "*"); + } catch (error) { + if (error.name == "InvalidStateError") { + parent.postMessage("Success", "*"); + } else { + parent.postMessage("Failure", "*"); + } + } + } + }; + <\/script>`; + iframe.onload = () => iframe.contentWindow.postMessage('Ready', '*'); + document.body.appendChild(iframe); + window.onmessage = message => { + if (message.data == 'Success') { + resolve(); + } else if (message.data == 'Failure') { + reject(); + } + } + }); +}, 'Test that WebNFC API is not accessible from iframe context.'); + +nfc_test(async () => { + const ndef = new NDEFReader(); + await ndef.write(test_text_data); +}, 'NDEFReader.write should succeed when NFC HW is enabled'); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const message = createMessage([createTextRecord(test_text_data), + createMimeRecordFromJson(test_json_data), + createMimeRecord(test_buffer_data), + createUnknownRecord(test_buffer_data), + createUrlRecord(test_url_data), + createUrlRecord(test_url_data, true), + createRecord('w3.org:xyz', test_buffer_data)], + test_message_origin); + await ndef.write(message); + assertNDEFMessagesEqual(message, mockNFC.pushedMessage()); +}, "NDEFReader.write NDEFMessage containing text, mime, unknown, url, absolute-url \ +and external records with default NDEFWriteOptions."); + +nfc_test(async (t, mockNFC) => { + const messageContainText = createMessage([createTextRecord(test_text_data)]); + + // Prepare a local type record that uses |messageContainText| as its payload. + 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 ndef = new NDEFReader(); + await ndef.write(message); + const pushed_message = mockNFC.pushedMessage(); + + // 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[0].recordType, 'example.com:containsLocalRecord', 'recordType'); + + // 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), + 'payloadMessage is used instead'); + 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); +}, "NDEFReader.write NDEFMessage containing embedded records."); + +nfc_test(async (t, mockNFC) => { + // A smart-poster record contains a uri record, text record. + const uri_record = createUrlRecord(test_url_data); + const text_record = createTextRecord(test_text_data); + const payload_message = createMessage([uri_record, text_record]); + const message = createMessage([createRecord( + 'smart-poster', payload_message, "dummy_record_id")]); + + const ndef = new NDEFReader(); + await ndef.write(message); + const pushed_message = mockNFC.pushedMessage(); + + // The mojom message received by mock nfc contains only the smart-poster record. + assert_equals(pushed_message.data.length, 1); + assert_equals(pushed_message.data[0].recordType, 'smart-poster', 'recordType'); + assert_equals(pushed_message.data[0].mediaType, null, 'mediaType'); + assert_equals(pushed_message.data[0].id, 'dummy_record_id', 'id'); + + const embedded_records = pushed_message.data[0].payloadMessage.data; + assert_equals(embedded_records.length, 2); + + let embedded_record_types = []; + for(let record of embedded_records) { + embedded_record_types.push(record.recordType); + switch(record.recordType) { + case 'url': + compareNDEFRecords(uri_record, record); + break; + case 'text': + compareNDEFRecords(text_record, record); + break; + default: + assert_unreached("Unknown recordType"); + } + } + assert_array_equals(embedded_record_types.sort(), ['text', 'url'], + 'smart-poster record\'s contained record types'); +}, "NDEFReader.write NDEFMessage containing smart-poster record."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + await ndef.write(test_text_data); + assertNDEFMessagesEqual(test_text_data, mockNFC.pushedMessage()); +}, "Test that NDEFReader.write succeeds when message is DOMString."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + await ndef.write(test_buffer_data); + assertNDEFMessagesEqual(test_buffer_data, mockNFC.pushedMessage()); +}, "Test that NDEFReader.write succeeds when message is ArrayBuffer."); + +nfc_test(async (t, mockNFC) => { + let buffer_view = new Uint8Array(test_buffer_data, 2, 5); + const ndef = new NDEFReader(); + await ndef.write(buffer_view); + assertNDEFMessagesEqual(buffer_view, mockNFC.pushedMessage()); +}, "Test that NDEFReader.write succeeds when message is ArrayBufferView."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + await ndef.write(createMessage([createRecord('empty')])); + const receivedMessage = mockNFC.pushedMessage(); + assert_equals(receivedMessage.data.length, 1); + assert_equals(receivedMessage.data[0].recordType, 'empty', 'recordType'); +}, "NDEFReader.write with 'empty' record should succeed."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + await ndef.write(test_text_data); + assertNDEFWriteOptionsEqual({overwrite: true}, mockNFC.writeOptions()); +}, "Check that default NDEFWriteOptions values are correctly set."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + await ndef.write(test_text_data, {overwrite: false}); + assertNDEFWriteOptionsEqual({overwrite: false}, mockNFC.writeOptions()); +}, "Check that provided NDEFWriteOptions values are correctly converted."); + +nfc_test(async (t, mockNFC) => { + const ndef1 = new NDEFReader(); + const ndef2 = new NDEFReader(); + + const p1 = ndef1.write(test_text_data, {overwrite: false}); + const p2 = ndef2.write(test_url_data, {overwrite: true}); + + await new Promise((resolve, reject) => { + // Make first push pending + mockNFC.setPendingPushCompleted(false); + let err = ""; + p1.then(() => { + reject("pending push should not be fulfilled"); + }).catch(e => { + err = e.name; + }); + p2.then(() => { + assertNDEFMessagesEqual(test_url_data, mockNFC.pushedMessage()); + assertNDEFWriteOptionsEqual( {overwrite: true}, mockNFC.writeOptions()); + assert_equals(err, "AbortError", "the pending push should be aborted"); + resolve(); + }); + }); +}, "NDEFReader.write should replace all previously configured write operations."); + +nfc_test(async () => { + const ndef = new NDEFReader(); + + const controller1 = new AbortController(); + await ndef.write(test_text_data, {signal: controller1.signal}); + + const controller2 = new AbortController(); + const promise = ndef.write(test_text_data, {signal: controller2.signal}); + controller1.abort(); + await promise; +}, 'NDEFReader.write signals are independent.'); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + await ndef.write({ records: [{ recordType: "mime", data: test_buffer_data }] }); + assertNDEFMessagesEqual(test_buffer_data, mockNFC.pushedMessage()); +}, "Test that mediaType should be set to 'application/octet-stream' if \ +NDEFRecordInit.record's recordType is 'mime' and NDEFRecordInit.record's \ +mediaType is undefined."); + +nfc_test(async (t, mockNFC) => { + // Make sure the push will be pending in the mock. + mockNFC.setPendingPushCompleted(false); + + const ndef1 = new NDEFReader(); + const promise = ndef1.write(test_text_data); + + // Just to make sure the write() request has already reached to the mock. + const ndef2 = new NDEFReader(); + await ndef2.scan(); + + mockNFC.simulateNonNDEFTagDiscovered(); + await promise_rejects_dom(t, 'NotSupportedError', promise); +}, "NDEFReader.write should fail when the NFC device coming up does not expose \ +NDEF technology."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + await ndef.write(test_text_data, {overwrite: false}); + assertNDEFMessagesEqual(test_text_data, mockNFC.pushedMessage()); +}, "NDEFReader.write should succeed to write data to an unformatted NFC device \ +when the NDEFWriteOptions.overwrite is false."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + await ndef.write(test_buffer_data); + assertNDEFMessagesEqual(test_buffer_data, mockNFC.pushedMessage()); + await ndef.write(test_text_data, {overwrite: true}); + assertNDEFMessagesEqual(test_text_data, mockNFC.pushedMessage()); +}, "NDEFReader.write should succeed to overwrite the existing data \ +when the NDEFWriteOptions.overwrite is true."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const p = ndef.write(test_text_data, {overwrite: false}); + mockNFC.setIsFormattedTag(true); + await promise_rejects_dom(t, 'NotAllowedError', p); +}, "NDEFReader.write should fail when there are NDEF records on the NFC device \ +and NDEFWriteOptions.overwrite is false."); + +nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + mockNFC.simulateDataTransferFails(); + await promise_rejects_dom(t, 'NetworkError', ndef.write(test_text_data)); +}, "NDEFReader.write should fail with NetworkError when NFC data transfer fails."); + +</script> diff --git a/testing/web-platform/tests/web-nfc/NDEFReadingEvent_constructor.https.window.js b/testing/web-platform/tests/web-nfc/NDEFReadingEvent_constructor.https.window.js new file mode 100644 index 0000000000..d812dbaf18 --- /dev/null +++ b/testing/web-platform/tests/web-nfc/NDEFReadingEvent_constructor.https.window.js @@ -0,0 +1,57 @@ +// META: script=resources/nfc-helpers.js + +'use strict'; + +test(() => { + assert_equals(NDEFReadingEvent.length, 2); + assert_throws_js(TypeError, () => new NDEFReadingEvent('message')); +}, 'NDEFReadingEvent constructor without init dict'); + +test(() => { + assert_throws_js( + TypeError, + () => new NDEFReadingEvent('type', {serialNumber: '', message: null}), + 'NDEFMessageInit#records is a required field.'); +}, 'NDEFReadingEvent constructor failed to construct its NDEFMessage'); + +test(() => { + const message = createMessage([createMimeRecordFromJson(test_buffer_data)]); + const event = + new NDEFReadingEvent('type', {serialNumber: null, message: message}); + assert_equals(event.serialNumber, '', 'serialNumber'); +}, 'NDEFReadingEvent constructor with null serialNumber'); + +test(() => { + const message = createMessage([createMimeRecordFromJson(test_buffer_data)]); + const event = new NDEFReadingEvent('type', {message: message}); + assert_equals(event.serialNumber, '', 'serialNumber'); +}, 'NDEFReadingEvent constructor with serialNumber not present'); + +test(() => { + const message = createMessage([createMimeRecord(test_buffer_data)]); + const event = + new NDEFReadingEvent('type', {serialNumber: '', message: message}); + assert_equals(event.type, 'type', 'type'); + assert_equals(event.serialNumber, '', 'serialNumber'); + assertWebNDEFMessagesEqual(event.message, new NDEFMessage(message)); +}, 'NDEFReadingEvent constructor with valid parameters'); + +test(() => { + const record_init = createTextRecord(test_text_data); + const event = new NDEFReadingEvent( + 'type', {serialNumber: '', message: createMessage([record_init])}); + assert_equals(event.type, 'type', 'type'); + assert_equals(event.serialNumber, '', 'serialNumber'); + assert_equals(1, event.message.records.length, 'only 1 record'); + + const record = new NDEFRecord(record_init); + assert_equals(record.recordType, 'text', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.encoding, 'utf-8', 'encoding'); + assert_equals(record.lang, 'en', 'lang'); + + assert_equals(event.message.records[0].recordType, 'text', 'recordType'); + assert_equals(event.message.records[0].mediaType, null, 'mediaType'); + assert_equals(event.message.records[0].encoding, 'utf-8', 'encoding'); + assert_equals(event.message.records[0].lang, 'en', 'lang'); +}, 'NDEFReadingEvent constructor sets NDEFRecord#lang for the text records it embeds'); diff --git a/testing/web-platform/tests/web-nfc/NDEFRecord_constructor.https.window.js b/testing/web-platform/tests/web-nfc/NDEFRecord_constructor.https.window.js new file mode 100644 index 0000000000..57c5bf0212 --- /dev/null +++ b/testing/web-platform/tests/web-nfc/NDEFRecord_constructor.https.window.js @@ -0,0 +1,769 @@ +// META: script=resources/nfc-helpers.js + +// NDEFRecord constructor +// https://w3c.github.io/web-nfc/#dom-ndefrecord + +test(() => { + assert_equals(NDEFRecord.length, 1); + assert_throws_js(TypeError, () => new NDEFRecord()); +}, 'NDEFRecord constructor without init dict'); + +test(() => { + assert_throws_js( + TypeError, () => new NDEFRecord(null), + 'NDEFRecordInit#recordType is a required field.'); +}, 'NDEFRecord constructor with null init dict'); + +test(() => { + assert_throws_js( + TypeError, + () => new NDEFRecord({id: test_record_id, data: test_text_data}), + 'NDEFRecordInit#recordType is a required field.'); +}, 'NDEFRecord constructor without NDEFRecordInit#recordType field'); + +test(() => { + assert_throws_js( + TypeError, + () => + new NDEFRecord(createRecord('empty', test_text_data, test_record_id)), + 'id does not apply for empty record type.'); +}, 'NDEFRecord constructor with empty record type and id'); + +test(() => { + assert_throws_js( + TypeError, + () => new NDEFRecord( + createRecord('empty', test_text_data, test_record_id, 'text/plain')), + 'mediaType does not apply for empty record type.'); + assert_throws_js( + TypeError, + () => new NDEFRecord( + createRecord('text', test_text_data, test_record_id, 'text/plain')), + 'mediaType does not apply for text record type.'); + assert_throws_js( + TypeError, + () => new NDEFRecord( + createRecord('url', test_url_data, test_record_id, 'text/plain')), + 'mediaType does not apply for url record type.'); + assert_throws_js( + TypeError, + () => new NDEFRecord(createRecord( + 'absolute-url', test_url_data, test_record_id, 'text/plain')), + 'mediaType does not apply for absolute-url record type.'); + assert_throws_js( + TypeError, + () => new NDEFRecord(createRecord( + 'unknown', test_buffer_data, test_record_id, + 'application/octet-stream')), + 'mediaType does not apply for unknown record type.'); + assert_throws_js( + TypeError, + () => new NDEFRecord(createRecord( + 'foo.example.com:bar', test_buffer_data, test_record_id, + 'application/octet-stream')), + 'mediaType does not apply for external record type.'); +}, 'NDEFRecord constructor should only accept mediaType for mime record type'); + +test( + () => { + { + const record = new NDEFRecord(createRecord('text', test_text_data)); + assert_equals(record.id, null, 'id'); + } {const record = + new NDEFRecord(createRecord('text', test_text_data, '')); + assert_equals(record.id, '', 'id');} { + const dummy_id = 'https://dummy_host/mypath/myid'; + const record = + new NDEFRecord(createRecord('text', test_text_data, dummy_id)); + assert_equals(record.id, dummy_id, 'id'); + } {const dummy_id = 'http://dummy_host/mypath/myid'; + const record = + new NDEFRecord(createRecord('text', test_text_data, dummy_id)); + assert_equals(record.id, dummy_id, 'id');} { + const dummy_id = 'mypath/myid'; + const record = + new NDEFRecord(createRecord('text', test_text_data, dummy_id)); + assert_equals(record.id, dummy_id, 'id'); + } + }, + 'NDEFRecord constructor with custom record ids'); + +test(() => { + const record = new NDEFRecord(createRecord('empty')); + assert_equals(record.recordType, 'empty', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.id, null, 'id'); + assert_equals(record.encoding, null, 'encoding'); + assert_equals(record.lang, null, 'lang'); + assert_equals(record.data, null, 'data'); + assert_throws_dom( + 'NotSupportedError', () => record.toRecords(), + 'Only smart-poster, external type and local type records could have embedded records.'); +}, 'NDEFRecord constructor with empty record type'); + +test(() => { + const record = new NDEFRecord(createTextRecord(test_text_data)); + assert_equals(record.recordType, 'text', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.id, test_record_id, 'id'); + assert_equals(record.encoding, 'utf-8', 'encoding'); + assert_equals(record.lang, 'en', 'lang'); + const decoder = new TextDecoder(); + assert_equals( + decoder.decode(record.data), test_text_data, + 'data has the same content with the original dictionary'); + assert_throws_dom( + 'NotSupportedError', () => record.toRecords(), + 'Only smart-poster, external type and local type records could have embedded records.'); +}, 'NDEFRecord constructor with text record type and string data'); + +test(() => { + const encoder = new TextEncoder(); + const uint8Array = encoder.encode(test_text_data); + const record = new NDEFRecord(createTextRecord(uint8Array.buffer)); + assert_equals(record.recordType, 'text', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.id, test_record_id, 'id'); + // By default, 'utf-8'. + assert_equals(record.encoding, 'utf-8', 'encoding'); + assert_equals(record.lang, 'en', 'lang'); + const decoder = new TextDecoder(); + assert_equals( + decoder.decode(record.data), test_text_data, + 'data has the same content with the original dictionary'); + assert_throws_dom( + 'NotSupportedError', () => record.toRecords(), + 'Only smart-poster, external type and local type records could have embedded records.'); +}, 'NDEFRecord constructor with text record type and arrayBuffer data'); + +test(() => { + const encoder = new TextEncoder(); + const uint8Array = encoder.encode(test_text_data); + const record = new NDEFRecord(createTextRecord(uint8Array)); + assert_equals(record.recordType, 'text', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.id, test_record_id, 'id'); + // By default, 'utf-8'. + assert_equals(record.encoding, 'utf-8', 'encoding'); + assert_equals(record.lang, 'en', 'lang'); + const decoder = new TextDecoder(); + assert_equals( + decoder.decode(record.data), test_text_data, + 'data has the same content with the original dictionary'); + assert_throws_dom( + 'NotSupportedError', () => record.toRecords(), + 'Only smart-poster, external type and local type records could have embedded records.'); +}, 'NDEFRecord constructor with text record type and arrayBufferView data'); + +test(() => { + assert_throws_js( + TypeError, + () => + new NDEFRecord(createTextRecord(test_text_data, 'random-encoding'))); + assert_throws_js( + TypeError, + () => new NDEFRecord(createTextRecord(test_text_data, 'utf-16'))); + // Only 'utf-8' is OK for a DOMString data source. + const record = + new NDEFRecord(createTextRecord(test_text_data, 'utf-8', 'fr')); + assert_equals(record.recordType, 'text', 'recordType'); + assert_equals(record.encoding, 'utf-8', 'encoding'); + assert_equals(record.lang, 'fr', 'lang'); + const decoder = new TextDecoder(); + assert_equals( + decoder.decode(record.data), test_text_data, + 'data has the same content with the original text'); + + assert_throws_js( + TypeError, + () => new NDEFRecord(createTextRecord( + encodeTextToArrayBuffer(test_text_data, 'utf-8'), + 'random-encoding'))); + // The encoding list valid for a BufferSource data source. + const encodings = ['utf-8', 'utf-16', 'utf-16be', 'utf-16le']; + for (const encoding of encodings) { + const record = new NDEFRecord(createTextRecord( + encodeTextToArrayBuffer(test_text_data, encoding), encoding, 'fr')); + assert_equals(record.recordType, 'text', 'recordType'); + assert_equals(record.encoding, encoding, 'encoding'); + assert_equals(record.lang, 'fr', 'lang'); + const decoder = new TextDecoder(record.encoding); + assert_equals( + decoder.decode(record.data), test_text_data, + 'data has the same content with the original text. encoding: ' + + encoding); + } +}, 'NDEFRecord constructor with text record type, encoding, and lang'); + +test(t => { + const previous_lang = document.querySelector('html').getAttribute('lang'); + const test_lang = 'fr'; + document.querySelector('html').setAttribute('lang', test_lang); + t.add_cleanup(() => { + document.querySelector('html').setAttribute('lang', previous_lang); + }); + const record = new NDEFRecord(createTextRecord(test_text_data)); + assert_equals(record.recordType, 'text', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.id, test_record_id, 'id'); + assert_equals(record.encoding, 'utf-8', 'encoding'); + assert_equals(record.lang, test_lang, 'lang'); + const decoder = new TextDecoder(); + assert_equals( + decoder.decode(record.data), test_text_data, + 'data has the same content with the original dictionary'); +}, 'NDEFRecord constructor with text record type and custom document language'); + +test(() => { + const record = new NDEFRecord(createUrlRecord(test_url_data)); + assert_equals(record.recordType, 'url', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.id, test_record_id, 'id'); + const decoder = new TextDecoder(); + assert_equals( + decoder.decode(record.data), test_url_data, + 'data has the same content with the original dictionary'); + assert_throws_dom( + 'NotSupportedError', () => record.toRecords(), + 'Only smart-poster, external type and local type records could have embedded records.'); +}, 'NDEFRecord constructor with url record type'); + +test(() => { + const record = new NDEFRecord(createUrlRecord(test_url_data, true)); + assert_equals(record.recordType, 'absolute-url', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.id, test_record_id, 'id'); + const decoder = new TextDecoder(); + assert_equals( + decoder.decode(record.data), test_url_data, + 'data has the same content with the original dictionary'); + assert_throws_dom( + 'NotSupportedError', () => record.toRecords(), + 'Only smart-poster, external type and local type records could have embedded records.'); +}, 'NDEFRecord constructor with absolute-url record type'); + +test(() => { + assert_throws_js( + TypeError, + () => new NDEFRecord(createMimeRecord('A string is not a BufferSource')), + 'Only BufferSource is allowed to be the record data.'); + + let buffer = new ArrayBuffer(4); + new Uint8Array(buffer).set([1, 2, 3, 4]); + // Feed ArrayBuffer. + { + const record = new NDEFRecord(createMimeRecord(buffer)); + assert_equals(record.recordType, 'mime', 'recordType'); + assert_equals(record.mediaType, 'application/octet-stream', 'mediaType'); + assert_equals(record.id, test_record_id, 'id'); + assert_array_equals( + new Uint8Array(record.data.buffer), [1, 2, 3, 4], + 'data has the same content with the original buffer'); + assert_throws_dom( + 'NotSupportedError', () => record.toRecords(), + 'Only smart-poster, external type and local type records could have embedded records.'); + } + // Feed ArrayBufferView. + { + let buffer_view = new Uint8Array(buffer, 1); + const record = new NDEFRecord(createMimeRecord(buffer_view)); + assert_equals(record.recordType, 'mime', 'recordType'); + assert_equals(record.id, test_record_id, 'id'); + assert_array_equals( + new Uint8Array(record.data.buffer), [2, 3, 4], + 'data has the same content with the original buffer view'); + assert_throws_dom( + 'NotSupportedError', () => record.toRecords(), + 'Only smart-poster, external type and local type records could have embedded records.'); + } +}, 'NDEFRecord constructor with mime record type and stream data'); + +test(() => { + const record = new NDEFRecord(createMimeRecordFromJson(test_json_data)); + assert_equals(record.recordType, 'mime', 'recordType'); + assert_equals(record.mediaType, 'application/json', 'mediaType'); + assert_equals(record.id, test_record_id, 'id'); + assert_object_equals( + JSON.parse(new TextDecoder().decode(record.data)), test_json_data, + 'data has the same content with the original json'); + assert_throws_dom( + 'NotSupportedError', () => record.toRecords(), + 'Only smart-poster, external type and local type records could have embedded records.'); +}, 'NDEFRecord constructor with mime record type and json data'); + +test(() => { + assert_throws_js( + TypeError, + () => + new NDEFRecord(createUnknownRecord('A string is not a BufferSource')), + 'Only BufferSource is allowed to be the record data.'); + + let buffer = new ArrayBuffer(4); + new Uint8Array(buffer).set([1, 2, 3, 4]); + // Feed ArrayBuffer. + { + const record = new NDEFRecord(createUnknownRecord(buffer)); + assert_equals(record.recordType, 'unknown', 'recordType'); + assert_equals(record.id, test_record_id, 'id'); + assert_array_equals( + new Uint8Array(record.data.buffer), [1, 2, 3, 4], + 'data has the same content with the original buffer'); + assert_throws_dom( + 'NotSupportedError', () => record.toRecords(), + 'Only smart-poster, external type and local type records could have embedded records.'); + } + // Feed ArrayBufferView. + { + let buffer_view = new Uint8Array(buffer, 1); + const record = new NDEFRecord(createUnknownRecord(buffer_view)); + assert_equals(record.recordType, 'unknown', 'recordType'); + assert_equals(record.id, test_record_id, 'id'); + assert_array_equals( + new Uint8Array(record.data.buffer), [2, 3, 4], + 'data has the same content with the original buffer view'); + assert_throws_dom( + 'NotSupportedError', () => record.toRecords(), + 'Only smart-poster, external type and local type records could have embedded records.'); + } +}, 'NDEFRecord constructor with unknown record type'); + +test(() => { + assert_throws_js( + TypeError, + () => new NDEFRecord(createRecord( + 'foo.eXamPle.com:bAr*-', + 'A string is not a BufferSource or NDEFMessageInit')), + '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. + { + const record = new NDEFRecord( + createRecord('foo.eXamPle.com:bAr*-', buffer, test_record_id)); + assert_equals(record.recordType, 'foo.eXamPle.com:bAr*-', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.id, test_record_id, 'id'); + assert_array_equals( + new Uint8Array(record.data.buffer), [1, 2, 3, 4], + 'data has the same content with the original buffer'); + assert_equals( + record.toRecords(), null, + 'toRecords() returns null if the payload is not an NDEF message.'); + } + // Feed ArrayBufferView. + { + let buffer_view = new Uint8Array(buffer, 1); + const record = new NDEFRecord( + createRecord('foo.eXamPle.com:bAr*-', buffer_view, test_record_id)); + assert_equals(record.recordType, 'foo.eXamPle.com:bAr*-', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.id, test_record_id, 'id'); + assert_array_equals( + new Uint8Array(record.data.buffer), [2, 3, 4], + 'data has the same content with the original buffer view'); + assert_equals( + record.toRecords(), null, + 'toRecords() returns null if the payload is not an NDEF message.'); + } + // Feed NDEFMessageInit. + { + const payload_message = createMessage([createTextRecord(test_text_data)]); + const record = new NDEFRecord(createRecord( + 'foo.eXamPle.com:bAr*-', payload_message, 'dummy_record_id')); + assert_equals(record.recordType, 'foo.eXamPle.com:bAr*-', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.id, 'dummy_record_id', 'id'); + const embedded_records = record.toRecords(); + assert_equals(embedded_records.length, 1, 'Only one embedded record.'); + // The only one embedded record has correct content. + assert_equals(embedded_records[0].recordType, 'text', 'recordType'); + assert_equals(embedded_records[0].mediaType, null, 'mediaType'); + assert_equals(embedded_records[0].id, test_record_id, 'id'); + const decoder = new TextDecoder(); + assert_equals( + decoder.decode(embedded_records[0].data), test_text_data, + 'data has the same content with the original dictionary'); + } +}, '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(() => { + let buffer = new ArrayBuffer(4); + new Uint8Array(buffer).set([1, 2, 3, 4]); + const encoder = new TextEncoder(); + const uri_record = createUrlRecord(test_url_data); + const title_record = createTextRecord(test_text_data, 'utf-8', 'en'); + const type_record = createRecord(':t', encoder.encode('image/gif')); + const size_record = createRecord(':s', new Uint32Array([4096])); + const action_record = createRecord(':act', new Uint8Array([3])); + const icon_record = createRecord('mime', buffer, test_record_id, 'image/gif'); + const payload_message = createMessage([ + uri_record, title_record, type_record, size_record, action_record, + icon_record + ]); + const smart_poster_record = + createRecord('smart-poster', payload_message, 'dummy_record_id'); + + const record = new NDEFRecord(smart_poster_record); + assert_equals(record.recordType, 'smart-poster', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.id, 'dummy_record_id', 'id'); + const embedded_records = record.toRecords(); + assert_equals(embedded_records.length, 6, 'length'); + + const decoder = new TextDecoder(); + let embedded_record_types = []; + for (let record of embedded_records) { + embedded_record_types.push(record.recordType); + switch (record.recordType) { + case 'url': + assert_equals(record.mediaType, null, 'uri record\'s mediaType'); + assert_equals(record.id, test_record_id, 'uri record\'s id'); + assert_equals( + decoder.decode(record.data), test_url_data, 'uri record\'s data'); + break; + case 'text': + assert_equals(record.mediaType, null, 'title record\'s mediaType'); + assert_equals(record.id, test_record_id, 'title record\'s id'); + assert_equals(record.encoding, 'utf-8', 'title record\'s encoding'); + assert_equals(record.lang, 'en', 'title record\'s lang'); + assert_equals( + decoder.decode(record.data), test_text_data, + 'title record\'s data'); + break; + case ':t': + assert_equals(record.mediaType, null, 'type record\'s mediaType'); + assert_equals(record.id, null, 'type record\'s id'); + assert_equals( + decoder.decode(record.data), 'image/gif', 'type record\'s data'); + break; + case ':s': + assert_equals(record.mediaType, null, 'size record\'s mediaType'); + assert_equals(record.id, null, 'size record\'s id'); + assert_equals( + record.data.byteLength, 4, 'byteLength of size record\'s data'); + assert_equals( + new Uint32Array(record.data.buffer)[0], 4096, + 'value of size record\'s data'); + break; + case ':act': + assert_equals(record.mediaType, null, 'action record\'s mediaType'); + assert_equals(record.id, null, 'action record\'s id'); + assert_equals( + record.data.byteLength, 1, 'byteLength of action record\'s data'); + assert_equals( + new Uint8Array(record.data.buffer)[0], 3, + 'value of action record\'s data'); + break; + case 'mime': + assert_equals( + record.mediaType, 'image/gif', 'icon record\'s mediaType'); + assert_equals(record.id, test_record_id, 'icon record\'s id'); + assert_array_equals( + new Uint8Array(record.data.buffer), [1, 2, 3, 4], + 'icon record\'s mediaType'); + break; + default: + assert_unreached('Unknown recordType'); + } + } + assert_array_equals( + embedded_record_types.sort(), [':act', ':s', ':t', 'mime', 'text', 'url'], + 'smart-poster record\'s contained record types'); +}, 'NDEFRecord constructor with smart-poster record type'); + +test(() => { + const uri_record = createUrlRecord(test_url_data); + const smart_poster_record = createRecord( + 'smart-poster', createMessage([uri_record]), 'dummy_record_id'); + const record = new NDEFRecord(smart_poster_record); + assert_equals(record.recordType, 'smart-poster', 'recordType'); + assert_equals(record.mediaType, null, 'mediaType'); + assert_equals(record.id, 'dummy_record_id', 'id'); + const embedded_records = record.toRecords(); + + // smart-poster record only contains a uri record. + assert_equals(embedded_records.length, 1, 'length'); + const decoder = new TextDecoder(); + assert_equals( + embedded_records[0].recordType, 'url', 'uri record\'s recordType'); + assert_equals(embedded_records[0].mediaType, null, 'uri record\'s mediaType'); + assert_equals(embedded_records[0].id, test_record_id, 'uri record\'s id'); + assert_equals( + decoder.decode(embedded_records[0].data), test_url_data, + 'uri record\'s data'); +}, 'NDEFRecord constructor with smart-poster record type that contains only a mandatory uri record'); + +test(() => { + assert_throws_js( + TypeError, () => new NDEFRecord(createRecord('EMptY')), + 'Unknown record type.'); + assert_throws_js( + TypeError, () => new NDEFRecord(createRecord('TeXt', test_text_data)), + 'Unknown record type.'); + assert_throws_js( + TypeError, () => new NDEFRecord(createRecord('uRL', test_url_data)), + 'Unknown record type.'); + assert_throws_js( + TypeError, () => new NDEFRecord(createRecord('Mime', test_buffer_data)), + 'Unknown record type.'); + assert_throws_js( + TypeError, + () => new NDEFRecord(createRecord('sMart-PosTER', test_url_data)), + 'Unknown record type.'); +}, 'NDEFRecord constructor with record type string being treated as case sensitive'); + +test(() => { + assert_throws_js( + TypeError, + () => new NDEFRecord(createRecord('example.com:hellö', test_buffer_data)), + 'The external type must be an ASCII string.'); + + // Length of the external type is 255, OK. + const record = new NDEFRecord(createRecord( + [...Array(251)].map(_ => 'a').join('') + ':xyz', test_buffer_data)); + // Exceeding 255, Throws. + assert_throws_js( + TypeError, + () => new NDEFRecord(createRecord( + [...Array(252)].map(_ => 'a').join('') + ':xyz', test_buffer_data)), + 'The external type should not be longer than 255.'); + + assert_throws_js( + TypeError, () => new NDEFRecord(createRecord('xyz', test_buffer_data)), + 'The external type must have a \':\'.'); + assert_throws_js( + TypeError, () => new NDEFRecord(createRecord(':xyz', test_buffer_data)), + 'The domain should not be empty.'); + assert_throws_js( + TypeError, + () => new NDEFRecord(createRecord('example.com:', test_buffer_data)), + 'The type should not be empty.'); + assert_throws_js( + TypeError, + () => new NDEFRecord(createRecord('example.com:xyz[', test_buffer_data)), + 'The type should not contain \'[\'.'); + assert_throws_js( + TypeError, + () => new NDEFRecord(createRecord('example.com:xyz~', test_buffer_data)), + 'The type should not contain \'~\'.'); + assert_throws_js( + TypeError, + () => new NDEFRecord(createRecord('example.com:xyz/', test_buffer_data)), + 'The type should not contain \'/\'.'); +}, 'NDEFRecord constructor with invalid external record type'); + +test(() => { + const encoder = new TextEncoder(); + const uri_record = createUrlRecord(test_url_data); + const title_record = createTextRecord(test_text_data, 'utf-8', 'en'); + const type_record = createRecord(':t', encoder.encode('image/gif')); + const size_record = createRecord(':s', new Uint32Array([4096])); + const action_record = createRecord(':act', new Uint8Array([0])); + const icon_record = + createRecord('mime', test_buffer_data, test_record_id, 'image/gif'); + + const invalid_data_list = [ + { + data: 'A string is not a NDEFMessageInit', + message: 'A string is not allowed.' + }, + {data: test_buffer_data, message: 'An ArrayBuffer is not allowed.'}, { + data: createMessage( + [title_record, type_record, size_record, action_record, icon_record]), + message: 'Must contain a URI record.' + }, + { + data: createMessage([ + uri_record, title_record, type_record, size_record, action_record, + icon_record, uri_record + ]), + message: 'Must not contain more than one uri record.' + }, + { + data: createMessage([ + uri_record, title_record, type_record, size_record, action_record, + icon_record, type_record + ]), + message: 'Must not contain more than one type record.' + }, + { + data: createMessage([ + uri_record, title_record, type_record, size_record, action_record, + icon_record, size_record + ]), + message: 'Must not contain more than one size record.' + }, + { + data: createMessage([ + uri_record, title_record, type_record, size_record, action_record, + icon_record, action_record + ]), + message: 'Must not contain more than one action record.' + }, + { + data: createMessage([ + uri_record, title_record, type_record, action_record, icon_record, + createRecord(':s', new Uint8Array([1])) + ]), + message: + 'Size record must have payload as 4-byte 32 bit unsigned integer.' + }, + { + data: createMessage([ + uri_record, title_record, type_record, size_record, icon_record, + createRecord(':act', new Uint32Array([0])) + ]), + message: + 'Action record must have payload as 1-byte 8 bit unsigned integer.' + } + ]; + + invalid_data_list.forEach(entry => { + assert_throws_js( + TypeError, + () => new NDEFRecord(createRecord('smart-poster', entry.data)), + entry.message); + }); +}, 'NDEFRecord constructor for smart-poster record with invalid embedded records.'); + +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'); diff --git a/testing/web-platform/tests/web-nfc/README.md b/testing/web-platform/tests/web-nfc/README.md new file mode 100644 index 0000000000..b51018d68f --- /dev/null +++ b/testing/web-platform/tests/web-nfc/README.md @@ -0,0 +1,29 @@ +The `nfc-helpers.js` requires an implementation of +the `WebNFCTest` interfaces, which should emulate platform Web NFC backends. + +The `WebNFCTest` interface is defined as: + +``` + class NFCTestChromium { + initialize(); // Sets up the testing environment. + async reset(); // Frees the resources. + getMockNFC(); // Returns `MockNFC` interface. + }; + + class MockNFC { + setHWStatus(number status); // Sets the hardware status. + setReadingMessage(NDEFMessageInit message); // Sets message that is used to deliver NFC reading updates. + setPendingPushCompleted(boolean result); // Sets if the pending push is completed. + setPendingMakeReadOnlyCompleted(boolean result); // Sets if the pending makeReadOnly is completed. + pushedMessage(); // Gets the pushed `NDEFMessageSource`. + writeOptions(); // Gets the pushed `NDEFWriteOptions`. + simulateNonNDEFTagDiscovered(); // Simulates that the NFC device discovered does not expose NDEF technology. + setIsFormattedTag(boolean isFormatted); // Sets if the NFC tag has formatted NDEF message. + }; +``` + +The Chromium implementation of the `WebNFCTest` interface is located in +[nfc-mock.js](../resources/chromium/nfc-mock.js). + +Other browser vendors should provide their own implementations of +the `WebNFCTest` interfaces. diff --git a/testing/web-platform/tests/web-nfc/idlharness.https.window.js b/testing/web-platform/tests/web-nfc/idlharness.https.window.js new file mode 100644 index 0000000000..72b5793f48 --- /dev/null +++ b/testing/web-platform/tests/web-nfc/idlharness.https.window.js @@ -0,0 +1,30 @@ +// META: script=/resources/WebIDLParser.js +// META: script=/resources/idlharness.js +// META: timeout=long + +'use strict'; + +// https://w3c.github.io/web-nfc/ + +const record = { + recordType: "text", + data: "Hello World", + id: "/custom/path" +}; +const message = { + url: "/custom/path", + records: [record] +}; + +idl_test( + ['web-nfc'], + ['html', 'dom', 'webidl'], + idl_array => { + idl_array.add_objects({ + NDEFReader: ['new NDEFReader();'], + NDEFRecord: [`new NDEFRecord(${JSON.stringify(record)});`], + NDEFMessage: [`new NDEFMessage(${JSON.stringify(message)});`], + NDEFReadingEvent: [`new NDEFReadingEvent("reading", { message: ${JSON.stringify(message)} })`], + }); + } +); diff --git a/testing/web-platform/tests/web-nfc/nfc-prompt-manual.https.html b/testing/web-platform/tests/web-nfc/nfc-prompt-manual.https.html new file mode 100644 index 0000000000..a8b0ce636c --- /dev/null +++ b/testing/web-platform/tests/web-nfc/nfc-prompt-manual.https.html @@ -0,0 +1,56 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Web NFC: prompt closes when page reloads</title> +<link rel="help" href="https://w3c.github.io/web-nfc/"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<p> + This manual test checks that Web NFC prompt closes when page reloads. +</p> +<ol> + <li>Make sure NFC is disabled at system level</li> + <li>Click button to open Web NFC prompt</li> + <li>Accept first Web NFC prompt if needed</li> + <li>Wait 3 seconds when prompted about turning on the NFC adapter on the system level</li> + <li>Page reloads, click button if Web NFC prompt closed</li> +</ol> +<script> + +promise_test(async (t) => { + if (window.location.search === "?didPromptClose") { + const button = document.createElement("button"); + button.textContent = "Did Web NFC prompt close?"; + + await new Promise((resolve) => { + button.onclick = () => { + document.body.removeChild(button); + resolve(); + }; + document.body.appendChild(button); + }); + return; + } + + const button = document.createElement("button"); + button.textContent = "Click to open Web NFC prompt"; + + await new Promise((resolve) => { + button.onclick = () => { + document.body.removeChild(button); + resolve(); + }; + document.body.appendChild(button); + }); + + // Web NFC prompt shows up. + const ndef = new NDEFReader(); + ndef.scan(); + + // Wait 3 seconds to give time to user to accept first Web NFC prompt. + await new Promise((resolve) => setTimeout(resolve, 3000)); + + // Reload page and manually check that Web NFC prompt closes. + window.location.search = "?didPromptClose"; +}, "Web NFC prompt should close when page reloads."); + +</script> diff --git a/testing/web-platform/tests/web-nfc/nfc_insecure_context.html b/testing/web-platform/tests/web-nfc/nfc_insecure_context.html new file mode 100644 index 0000000000..8c2eb5a142 --- /dev/null +++ b/testing/web-platform/tests/web-nfc/nfc_insecure_context.html @@ -0,0 +1,23 @@ +<!DOCTYPE html> +<meta charset=utf-8> +<title>Web NFC Test: insecure context</title> +<link rel="author" title="Intel" href="http://www.intel.com"/> +<link rel="help" href="https://w3c.github.io/web-nfc/"/> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<h2>Note</h2> +<ol> + <li> + Run test is an insecure context, e.g. http://example.com/ + </li> +</ol> +<script> + +"use strict"; + +test(t => { + assert_false(isSecureContext); + assert_false('NDEFReader' in window); +}, 'NDEFReader requires a secure context'); + +</script> diff --git a/testing/web-platform/tests/web-nfc/nfc_permission.https.window.js b/testing/web-platform/tests/web-nfc/nfc_permission.https.window.js new file mode 100644 index 0000000000..9cb4209d2f --- /dev/null +++ b/testing/web-platform/tests/web-nfc/nfc_permission.https.window.js @@ -0,0 +1,20 @@ +// META: script=/resources/testdriver.js +// META: script=/resources/testdriver-vendor.js + +'use strict'; + +promise_test(async t => { + await test_driver.set_permission({name: 'nfc'}, 'denied'); + + const status = await navigator.permissions.query({name: 'nfc'}); + assert_class_string(status, 'PermissionStatus'); + assert_equals(status.state, 'denied'); +}, 'Deny nfc permission should work.'); + +promise_test(async t => { + await test_driver.set_permission({name: 'nfc'}, 'granted'); + + const status = await navigator.permissions.query({name: 'nfc'}); + assert_class_string(status, 'PermissionStatus'); + assert_equals(status.state, 'granted'); +}, 'Grant nfc permission should work.'); diff --git a/testing/web-platform/tests/web-nfc/resources/nfc-helpers.js b/testing/web-platform/tests/web-nfc/resources/nfc-helpers.js new file mode 100644 index 0000000000..10229f0b06 --- /dev/null +++ b/testing/web-platform/tests/web-nfc/resources/nfc-helpers.js @@ -0,0 +1,232 @@ +'use strict'; +// These tests rely on the User Agent providing an implementation of +// platform nfc backends. +// +// In Chromium-based browsers this implementation is provided by a polyfill +// in order to reduce the amount of test-only code shipped to users. To enable +// these tests the browser must be run with these options: +// +// --enable-blink-features=MojoJS,MojoJSTest + +async function loadChromiumResources() { + await loadScript('/resources/testdriver.js'); + await loadScript('/resources/testdriver-vendor.js'); + await import('/resources/chromium/nfc-mock.js'); +} + +async function initialize_nfc_tests() { + if (typeof WebNFCTest === 'undefined') { + const script = document.createElement('script'); + script.src = '/resources/test-only-api.js'; + script.async = false; + const p = new Promise((resolve, reject) => { + script.onload = () => { resolve(); }; + script.onerror = e => { reject(e); }; + }) + document.head.appendChild(script); + await p; + + if (isChromiumBased) { + await loadChromiumResources(); + } + } + assert_implements( WebNFCTest, 'WebNFC testing interface is unavailable.'); + let NFCTest = new WebNFCTest(); + await NFCTest.initialize(); + return NFCTest; +} + +function nfc_test(func, name, properties) { + promise_test(async t => { + let NFCTest = await initialize_nfc_tests(); + t.add_cleanup(async () => { + await NFCTest.reset(); + }); + await func(t, NFCTest.getMockNFC()); + }, name, properties); +} + +const test_text_data = 'Test text data.'; +const test_text_byte_array = new TextEncoder().encode(test_text_data); +const test_number_data = 42; +const test_json_data = {level: 1, score: 100, label: 'Game'}; +const test_url_data = 'https://w3c.github.io/web-nfc/'; +const test_message_origin = 'https://127.0.0.1:8443'; +const test_buffer_data = new ArrayBuffer(test_text_byte_array.length); +const test_buffer_view = new Uint8Array(test_buffer_data); +test_buffer_view.set(test_text_byte_array); +const fake_tag_serial_number = 'c0:45:00:02'; +const test_record_id = '/test_path/test_id'; + +const NFCHWStatus = {}; +// OS-level NFC setting is ON +NFCHWStatus.ENABLED = 1; +// no NFC chip +NFCHWStatus.NOT_SUPPORTED = NFCHWStatus.ENABLED + 1; +// OS-level NFC setting OFF +NFCHWStatus.DISABLED = NFCHWStatus.NOT_SUPPORTED + 1; + +function encodeTextToArrayBuffer(string, encoding) { + // Only support 'utf-8', 'utf-16', 'utf-16be', and 'utf-16le'. + assert_true( + encoding === 'utf-8' || encoding === 'utf-16' || + encoding === 'utf-16be' || encoding === 'utf-16le'); + + if (encoding === 'utf-8') { + return new TextEncoder().encode(string).buffer; + } + + if (encoding === 'utf-16') { + let uint16array = new Uint16Array(string.length); + for (let i = 0; i < string.length; i++) { + uint16array[i] = string.codePointAt(i); + } + return uint16array.buffer; + } + + const littleEndian = encoding === 'utf-16le'; + const buffer = new ArrayBuffer(string.length * 2); + const view = new DataView(buffer); + for (let i = 0; i < string.length; i++) { + view.setUint16(i * 2, string.codePointAt(i), littleEndian); + } + return buffer; +} + +function createMessage(records) { + if (records !== undefined) { + let message = {}; + message.records = records; + return message; + } +} + +function createRecord(recordType, data, id, mediaType, encoding, lang) { + let record = {}; + if (recordType !== undefined) + record.recordType = recordType; + if (id !== undefined) + record.id = id; + if (mediaType !== undefined) + record.mediaType = mediaType; + if (encoding !== undefined) + record.encoding = encoding; + if (lang !== undefined) + record.lang = lang; + if (data !== undefined) + record.data = data; + return record; +} + +function createTextRecord(data, encoding, lang) { + return createRecord('text', data, test_record_id, undefined, encoding, lang); +} + +function createMimeRecordFromJson(json) { + return createRecord( + 'mime', new TextEncoder().encode(JSON.stringify(json)), + test_record_id, 'application/json'); +} + +function createMimeRecord(buffer) { + return createRecord( + 'mime', buffer, test_record_id, 'application/octet-stream'); +} + +function createUnknownRecord(buffer) { + return createRecord('unknown', buffer, test_record_id); +} + +function createUrlRecord(url, isAbsUrl) { + if (isAbsUrl) { + return createRecord('absolute-url', url, test_record_id); + } + return createRecord('url', url, test_record_id); +} + +// Compares NDEFMessageSource that was provided to the API +// (e.g. NDEFReader.write), and NDEFMessage that was received by the +// mock NFC service. +function assertNDEFMessagesEqual(providedMessage, receivedMessage) { + // If simple data type is passed, e.g. String or ArrayBuffer or + // ArrayBufferView, convert it to NDEFMessage before comparing. + // https://w3c.github.io/web-nfc/#dom-ndefmessagesource + let provided = providedMessage; + if (providedMessage instanceof ArrayBuffer || + ArrayBuffer.isView(providedMessage)) + provided = createMessage([createRecord( + 'mime', providedMessage, undefined /* id */, + 'application/octet-stream')]); + else if (typeof providedMessage === 'string') + provided = createMessage([createRecord('text', providedMessage)]); + + assert_equals(provided.records.length, receivedMessage.data.length, + 'NDEFMessages must have same number of NDEFRecords'); + + // Compare contents of each individual NDEFRecord + for (let i = 0; i < provided.records.length; ++i) + compareNDEFRecords(provided.records[i], receivedMessage.data[i]); +} + +// Used to compare two NDEFMessage, one that is received from +// NDEFReader.onreading() EventHandler and another that is provided to mock NFC +// service. +function assertWebNDEFMessagesEqual(message, expectedMessage) { + assert_equals(message.records.length, expectedMessage.records.length); + + for(let i in message.records) { + let record = message.records[i]; + let expectedRecord = expectedMessage.records[i]; + assert_equals(record.recordType, expectedRecord.recordType); + assert_equals(record.mediaType, expectedRecord.mediaType); + assert_equals(record.id, expectedRecord.id); + assert_equals(record.encoding, expectedRecord.encoding); + assert_equals(record.lang, expectedRecord.lang); + // Compares record data + assert_array_equals(new Uint8Array(record.data), + new Uint8Array(expectedRecord.data)); + } +} + +function testMultiScanOptions(message, scanOptions, unmatchedScanOptions, desc) { + nfc_test(async (t, mockNFC) => { + const ndef1 = new NDEFReader(); + const ndef2 = new NDEFReader(); + const controller = new AbortController(); + + // Reading from unmatched ndef will not be triggered + ndef1.onreading = t.unreached_func("reading event should not be fired."); + unmatchedScanOptions.signal = controller.signal; + await ndef1.scan(unmatchedScanOptions); + + const ndefWatcher = new EventWatcher(t, ndef2, ["reading", "readingerror"]); + const promise = ndefWatcher.wait_for("reading").then(event => { + controller.abort(); + assertWebNDEFMessagesEqual(event.message, new NDEFMessage(message)); + }); + scanOptions.signal = controller.signal; + await ndef2.scan(scanOptions); + + mockNFC.setReadingMessage(message); + await promise; + }, desc); +} + +function testMultiMessages(message, scanOptions, unmatchedMessage, desc) { + nfc_test(async (t, mockNFC) => { + const ndef = new NDEFReader(); + const controller = new AbortController(); + const ndefWatcher = new EventWatcher(t, ndef, ["reading", "readingerror"]); + const promise = ndefWatcher.wait_for("reading").then(event => { + controller.abort(); + assertWebNDEFMessagesEqual(event.message, new NDEFMessage(message)); + }); + scanOptions.signal = controller.signal; + await ndef.scan(scanOptions); + + // Unmatched message will not be read + mockNFC.setReadingMessage(unmatchedMessage); + mockNFC.setReadingMessage(message); + await promise; + }, desc); +} diff --git a/testing/web-platform/tests/web-nfc/resources/support-iframe.html b/testing/web-platform/tests/web-nfc/resources/support-iframe.html new file mode 100644 index 0000000000..540eab108b --- /dev/null +++ b/testing/web-platform/tests/web-nfc/resources/support-iframe.html @@ -0,0 +1,3 @@ +<!DOCTYPE HTML> +<meta charset="utf-8"> +<input type="text" id="foo" value="click me!"></input>
\ No newline at end of file |