summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/web-nfc
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/web-nfc')
-rw-r--r--testing/web-platform/tests/web-nfc/META.yml4
-rw-r--r--testing/web-platform/tests/web-nfc/NDEFMessage_constructor.https.window.js42
-rw-r--r--testing/web-platform/tests/web-nfc/NDEFMessage_recursion-limit.https.window.js156
-rw-r--r--testing/web-platform/tests/web-nfc/NDEFReader-make-read-only-document-hidden-manual.https.html27
-rw-r--r--testing/web-platform/tests/web-nfc/NDEFReader-read-document-hidden-manual.https.html27
-rw-r--r--testing/web-platform/tests/web-nfc/NDEFReader-write-document-hidden-manual.https.html28
-rw-r--r--testing/web-platform/tests/web-nfc/NDEFReader_make-read-only.https.window.js174
-rw-r--r--testing/web-platform/tests/web-nfc/NDEFReader_scan.https.html323
-rw-r--r--testing/web-platform/tests/web-nfc/NDEFReader_scan_iframe.https.html43
-rw-r--r--testing/web-platform/tests/web-nfc/NDEFReader_write.https.html480
-rw-r--r--testing/web-platform/tests/web-nfc/NDEFReadingEvent_constructor.https.window.js57
-rw-r--r--testing/web-platform/tests/web-nfc/NDEFRecord_constructor.https.window.js769
-rw-r--r--testing/web-platform/tests/web-nfc/README.md29
-rw-r--r--testing/web-platform/tests/web-nfc/idlharness.https.window.js30
-rw-r--r--testing/web-platform/tests/web-nfc/nfc-prompt-manual.https.html56
-rw-r--r--testing/web-platform/tests/web-nfc/nfc_insecure_context.html23
-rw-r--r--testing/web-platform/tests/web-nfc/nfc_permission.https.window.js20
-rw-r--r--testing/web-platform/tests/web-nfc/resources/nfc-helpers.js232
-rw-r--r--testing/web-platform/tests/web-nfc/resources/support-iframe.html3
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