| 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_promise_func_ = null; |
| this.push_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) { |
| let error = this.getHWError(); |
| if (error) |
| return error; |
| // Cancels previous pending push operation. |
| if (this.pending_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_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); |
| } |
| |
| setClient(client) { |
| this.client_ = client; |
| } |
| |
| async watch(id) { |
| assert_true(id > 0); |
| let 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; |
| } |
| |
| reset() { |
| this.hw_status_ = NFCHWStatus.ENABLED; |
| this.watchers_ = []; |
| this.reading_messages_ = []; |
| this.operations_suspended_ = false; |
| this.cancelPendingPushOperation(); |
| this.is_formatted_tag_ = false; |
| this.data_transfer_failed_ = false; |
| this.should_close_pipe_on_request_ = false; |
| } |
| |
| cancelPendingPushOperation() { |
| if (this.pending_promise_func_) { |
| this.pending_promise_func_( |
| createNDEFError(NDEFErrorType.OPERATION_CANCELLED)); |
| this.pending_promise_func_ = null; |
| } |
| |
| this.pushed_message_ = null; |
| this.pending_write_options_ = null; |
| this.push_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_promise_func_ && this.push_completed_) { |
| this.pending_promise_func_(createNDEFError(null)); |
| this.pending_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_promise_func_) { |
| this.pending_promise_func_( |
| createNDEFError(NDEFErrorType.NOT_SUPPORTED)); |
| this.pending_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', false); |
| |
| 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; |
| })(); |