summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/resources/chromium/nfc-mock.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/resources/chromium/nfc-mock.js')
-rw-r--r--testing/web-platform/tests/resources/chromium/nfc-mock.js437
1 files changed, 437 insertions, 0 deletions
diff --git a/testing/web-platform/tests/resources/chromium/nfc-mock.js b/testing/web-platform/tests/resources/chromium/nfc-mock.js
new file mode 100644
index 0000000000..4796735b13
--- /dev/null
+++ b/testing/web-platform/tests/resources/chromium/nfc-mock.js
@@ -0,0 +1,437 @@
+import {NDEFErrorType, NDEFRecordTypeCategory, NFC, NFCReceiver} from '/gen/services/device/public/mojom/nfc.mojom.m.js';
+
+// Converts between NDEFMessageInit https://w3c.github.io/web-nfc/#dom-ndefmessage
+// and mojom.NDEFMessage structure, so that watch function can be tested.
+function toMojoNDEFMessage(message) {
+ let ndefMessage = {data: []};
+ for (let record of message.records) {
+ ndefMessage.data.push(toMojoNDEFRecord(record));
+ }
+ return ndefMessage;
+}
+
+function toMojoNDEFRecord(record) {
+ let nfcRecord = {};
+ // Simply checks the existence of ':' to decide whether it's an external
+ // type or a local type. As a mock, no need to really implement the validation
+ // algorithms for them.
+ if (record.recordType.startsWith(':')) {
+ nfcRecord.category = NDEFRecordTypeCategory.kLocal;
+ } else if (record.recordType.search(':') != -1) {
+ nfcRecord.category = NDEFRecordTypeCategory.kExternal;
+ } else {
+ nfcRecord.category = NDEFRecordTypeCategory.kStandardized;
+ }
+ nfcRecord.recordType = record.recordType;
+ nfcRecord.mediaType = record.mediaType;
+ nfcRecord.id = record.id;
+ if (record.recordType == 'text') {
+ nfcRecord.encoding = record.encoding == null? 'utf-8': record.encoding;
+ nfcRecord.lang = record.lang == null? 'en': record.lang;
+ }
+ nfcRecord.data = toByteArray(record.data);
+ if (record.data != null && record.data.records !== undefined) {
+ // |record.data| may be an NDEFMessageInit, i.e. the payload is a message.
+ nfcRecord.payloadMessage = toMojoNDEFMessage(record.data);
+ }
+ return nfcRecord;
+}
+
+// Converts JS objects to byte array.
+function toByteArray(data) {
+ if (data instanceof ArrayBuffer)
+ return new Uint8Array(data);
+ else if (ArrayBuffer.isView(data))
+ return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
+
+ let byteArray = new Uint8Array(0);
+ let tmpData = data;
+ if (typeof tmpData === 'object' || typeof tmpData === 'number')
+ tmpData = JSON.stringify(tmpData);
+
+ if (typeof tmpData === 'string')
+ byteArray = new TextEncoder('utf-8').encode(tmpData);
+
+ return byteArray;
+}
+
+// Compares NDEFRecords that were provided / received by the mock service.
+// TODO: Use different getters to get received record data,
+// see spec changes at https://github.com/w3c/web-nfc/pull/243.
+self.compareNDEFRecords = function(providedRecord, receivedRecord) {
+ assert_equals(providedRecord.recordType, receivedRecord.recordType);
+
+ if (providedRecord.id === undefined) {
+ assert_equals(null, receivedRecord.id);
+ } else {
+ assert_equals(providedRecord.id, receivedRecord.id);
+ }
+
+ if (providedRecord.mediaType === undefined) {
+ assert_equals(null, receivedRecord.mediaType);
+ } else {
+ assert_equals(providedRecord.mediaType, receivedRecord.mediaType);
+ }
+
+ assert_not_equals(providedRecord.recordType, 'empty');
+
+ if (providedRecord.recordType == 'text') {
+ assert_equals(
+ providedRecord.encoding == null? 'utf-8': providedRecord.encoding,
+ receivedRecord.encoding);
+ assert_equals(providedRecord.lang == null? 'en': providedRecord.lang,
+ receivedRecord.lang);
+ } else {
+ assert_equals(null, receivedRecord.encoding);
+ assert_equals(null, receivedRecord.lang);
+ }
+
+ assert_array_equals(toByteArray(providedRecord.data),
+ new Uint8Array(receivedRecord.data));
+}
+
+// Compares NDEFWriteOptions structures that were provided to API and
+// received by the mock mojo service.
+self.assertNDEFWriteOptionsEqual = function(provided, received) {
+ if (provided.overwrite !== undefined)
+ assert_equals(provided.overwrite, !!received.overwrite);
+ else
+ assert_equals(!!received.overwrite, true);
+}
+
+// Compares NDEFReaderOptions structures that were provided to API and
+// received by the mock mojo service.
+self.assertNDEFReaderOptionsEqual = function(provided, received) {
+ if (provided.url !== undefined)
+ assert_equals(provided.url, received.url);
+ else
+ assert_equals(received.url, '');
+
+ if (provided.mediaType !== undefined)
+ assert_equals(provided.mediaType, received.mediaType);
+ else
+ assert_equals(received.mediaType, '');
+
+ if (provided.recordType !== undefined) {
+ assert_equals(provided.recordType, received.recordType);
+ }
+}
+
+function createNDEFError(type) {
+ return {error: (type != null ? {errorType: type, errorMessage: ''} : null)};
+}
+
+self.WebNFCTest = (() => {
+ class MockNFC {
+ constructor() {
+ this.receiver_ = new NFCReceiver(this);
+
+ this.interceptor_ = new MojoInterfaceInterceptor(NFC.$interfaceName);
+ this.interceptor_.oninterfacerequest = e => {
+ if (this.should_close_pipe_on_request_)
+ e.handle.close();
+ else
+ this.receiver_.$.bindHandle(e.handle);
+ }
+
+ this.interceptor_.start();
+
+ this.hw_status_ = NFCHWStatus.ENABLED;
+ this.pushed_message_ = null;
+ this.pending_write_options_ = null;
+ this.pending_push_promise_func_ = null;
+ this.push_completed_ = true;
+ this.pending_make_read_only_promise_func_ = null;
+ this.make_read_only_completed_ = true;
+ this.client_ = null;
+ this.watchers_ = [];
+ this.reading_messages_ = [];
+ this.operations_suspended_ = false;
+ this.is_formatted_tag_ = false;
+ this.data_transfer_failed_ = false;
+ this.should_close_pipe_on_request_ = false;
+ }
+
+ // NFC delegate functions.
+ async push(message, options) {
+ const error = this.getHWError();
+ if (error)
+ return error;
+ // Cancels previous pending push operation.
+ if (this.pending_push_promise_func_) {
+ this.cancelPendingPushOperation();
+ }
+
+ this.pushed_message_ = message;
+ this.pending_write_options_ = options;
+ return new Promise(resolve => {
+ if (this.operations_suspended_ || !this.push_completed_) {
+ // Leaves the push pending.
+ this.pending_push_promise_func_ = resolve;
+ } else if (this.is_formatted_tag_ && !options.overwrite) {
+ // Resolves with NotAllowedError if there are NDEF records on the device
+ // and overwrite is false.
+ resolve(createNDEFError(NDEFErrorType.NOT_ALLOWED));
+ } else if (this.data_transfer_failed_) {
+ // Resolves with NetworkError if data transfer fails.
+ resolve(createNDEFError(NDEFErrorType.IO_ERROR));
+ } else {
+ resolve(createNDEFError(null));
+ }
+ });
+ }
+
+ async cancelPush() {
+ this.cancelPendingPushOperation();
+ return createNDEFError(null);
+ }
+
+ async makeReadOnly(options) {
+ const error = this.getHWError();
+ if (error)
+ return error;
+ // Cancels previous pending makeReadOnly operation.
+ if (this.pending_make_read_only_promise_func_) {
+ this.cancelPendingMakeReadOnlyOperation();
+ }
+
+ if (this.operations_suspended_ || !this.make_read_only_completed_) {
+ // Leaves the makeReadOnly pending.
+ return new Promise(resolve => {
+ this.pending_make_read_only_promise_func_ = resolve;
+ });
+ } else if (this.data_transfer_failed_) {
+ // Resolves with NetworkError if data transfer fails.
+ return createNDEFError(NDEFErrorType.IO_ERROR);
+ } else {
+ return createNDEFError(null);
+ }
+ }
+
+ async cancelMakeReadOnly() {
+ this.cancelPendingMakeReadOnlyOperation();
+ return createNDEFError(null);
+ }
+
+ setClient(client) {
+ this.client_ = client;
+ }
+
+ async watch(id) {
+ assert_true(id > 0);
+ const error = this.getHWError();
+ if (error) {
+ return error;
+ }
+
+ this.watchers_.push({id: id});
+ // Ignores reading if NFC operation is suspended
+ // or the NFC tag does not expose NDEF technology.
+ if (!this.operations_suspended_) {
+ // Triggers onWatch if the new watcher matches existing messages.
+ for (let message of this.reading_messages_) {
+ this.client_.onWatch(
+ [id], fake_tag_serial_number, toMojoNDEFMessage(message));
+ }
+ }
+
+ return createNDEFError(null);
+ }
+
+ cancelWatch(id) {
+ let index = this.watchers_.findIndex(value => value.id === id);
+ if (index !== -1) {
+ this.watchers_.splice(index, 1);
+ }
+ }
+
+ getHWError() {
+ if (this.hw_status_ === NFCHWStatus.DISABLED)
+ return createNDEFError(NDEFErrorType.NOT_READABLE);
+ if (this.hw_status_ === NFCHWStatus.NOT_SUPPORTED)
+ return createNDEFError(NDEFErrorType.NOT_SUPPORTED);
+ return null;
+ }
+
+ setHWStatus(status) {
+ this.hw_status_ = status;
+ }
+
+ pushedMessage() {
+ return this.pushed_message_;
+ }
+
+ writeOptions() {
+ return this.pending_write_options_;
+ }
+
+ watchOptions() {
+ assert_not_equals(this.watchers_.length, 0);
+ return this.watchers_[this.watchers_.length - 1].options;
+ }
+
+ setPendingPushCompleted(result) {
+ this.push_completed_ = result;
+ }
+
+ setPendingMakeReadOnlyCompleted(result) {
+ this.make_read_only_completed_ = result;
+ }
+
+ reset() {
+ this.hw_status_ = NFCHWStatus.ENABLED;
+ this.watchers_ = [];
+ this.reading_messages_ = [];
+ this.operations_suspended_ = false;
+ this.cancelPendingPushOperation();
+ this.cancelPendingMakeReadOnlyOperation();
+ this.is_formatted_tag_ = false;
+ this.data_transfer_failed_ = false;
+ this.should_close_pipe_on_request_ = false;
+ }
+
+ cancelPendingPushOperation() {
+ if (this.pending_push_promise_func_) {
+ this.pending_push_promise_func_(
+ createNDEFError(NDEFErrorType.OPERATION_CANCELLED));
+ this.pending_push_promise_func_ = null;
+ }
+
+ this.pushed_message_ = null;
+ this.pending_write_options_ = null;
+ this.push_completed_ = true;
+ }
+
+ cancelPendingMakeReadOnlyOperation() {
+ if (this.pending_make_read_only_promise_func_) {
+ this.pending_make_read_only_promise_func_(
+ createNDEFError(NDEFErrorType.OPERATION_CANCELLED));
+ this.pending_make_read_only_promise_func_ = null;
+ }
+
+ this.make_read_only_completed_ = true;
+ }
+
+ // Sets message that is used to deliver NFC reading updates.
+ setReadingMessage(message) {
+ this.reading_messages_.push(message);
+ // Ignores reading if NFC operation is suspended.
+ if(this.operations_suspended_) return;
+ // when overwrite is false, the write algorithm will read the NFC tag
+ // to determine if it has NDEF records on it.
+ if (this.pending_write_options_ && this.pending_write_options_.overwrite)
+ return;
+ // Triggers onWatch if the new message matches existing watchers.
+ for (let watcher of this.watchers_) {
+ this.client_.onWatch(
+ [watcher.id], fake_tag_serial_number,
+ toMojoNDEFMessage(message));
+ }
+ }
+
+ // Suspends all pending NFC operations. Could be used when web page
+ // visibility is lost.
+ suspendNFCOperations() {
+ this.operations_suspended_ = true;
+ }
+
+ // Resumes all suspended NFC operations.
+ resumeNFCOperations() {
+ this.operations_suspended_ = false;
+ // Resumes pending NFC reading.
+ for (let watcher of this.watchers_) {
+ for (let message of this.reading_messages_) {
+ this.client_.onWatch(
+ [watcher.id], fake_tag_serial_number,
+ toMojoNDEFMessage(message));
+ }
+ }
+ // Resumes pending push operation.
+ if (this.pending_push_promise_func_ && this.push_completed_) {
+ this.pending_push_promise_func_(createNDEFError(null));
+ this.pending_push_promise_func_ = null;
+ }
+ // Resumes pending makeReadOnly operation.
+ if (this.pending_make_read_only_promise_func_ &&
+ this.make_read_only_completed_) {
+ this.pending_make_read_only_promise_func_(createNDEFError(null));
+ this.pending_make_read_only_promise_func_ = null;
+ }
+ }
+
+ // Simulates the device coming in proximity does not expose NDEF technology.
+ simulateNonNDEFTagDiscovered() {
+ // Notify NotSupportedError to all active readers.
+ if (this.watchers_.length != 0) {
+ this.client_.onError({
+ errorType: NDEFErrorType.NOT_SUPPORTED,
+ errorMessage: ''
+ });
+ }
+ // Reject the pending push with NotSupportedError.
+ if (this.pending_push_promise_func_) {
+ this.pending_push_promise_func_(
+ createNDEFError(NDEFErrorType.NOT_SUPPORTED));
+ this.pending_push_promise_func_ = null;
+ }
+ // Reject the pending makeReadOnly with NotSupportedError.
+ if (this.pending_make_read_only_promise_func_) {
+ this.pending_make_read_only_promise_func_(
+ createNDEFError(NDEFErrorType.NOT_SUPPORTED));
+ this.pending_make_read_only_promise_func_ = null;
+ }
+ }
+
+ setIsFormattedTag(isFormatted) {
+ this.is_formatted_tag_ = isFormatted;
+ }
+
+ simulateDataTransferFails() {
+ this.data_transfer_failed_ = true;
+ }
+
+ simulateClosedPipe() {
+ this.should_close_pipe_on_request_ = true;
+ }
+ }
+
+ let testInternal = {
+ initialized: false,
+ mockNFC: null
+ }
+
+ class NFCTestChromium {
+ constructor() {
+ Object.freeze(this); // Makes it immutable.
+ }
+
+ async initialize() {
+ if (testInternal.initialized)
+ throw new Error('Call reset() before initialize().');
+
+ // Grant nfc permissions for Chromium testdriver.
+ await test_driver.set_permission({ name: 'nfc' }, 'granted');
+
+ if (testInternal.mockNFC == null) {
+ testInternal.mockNFC = new MockNFC();
+ }
+ testInternal.initialized = true;
+ }
+
+ // Reuses the nfc mock but resets its state between test runs.
+ async reset() {
+ if (!testInternal.initialized)
+ throw new Error('Call initialize() before reset().');
+ testInternal.mockNFC.reset();
+ testInternal.initialized = false;
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ }
+
+ getMockNFC() {
+ return testInternal.mockNFC;
+ }
+ }
+
+ return NFCTestChromium;
+})();