| 'use strict'; |
| |
| function toMojoNDEFPushTarget(target) { |
| switch (target) { |
| case 'peer': |
| return device.mojom.NDEFPushTarget.PEER; |
| case 'tag': |
| return device.mojom.NDEFPushTarget.TAG; |
| } |
| |
| return device.mojom.NDEFPushTarget.ANY; |
| } |
| |
| // 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 = new device.mojom.NDEFMessage(); |
| ndefMessage.data = []; |
| for (let record of message.records) { |
| ndefMessage.data.push(toMojoNDEFRecord(record)); |
| } |
| return ndefMessage; |
| } |
| |
| function toMojoNDEFRecord(record) { |
| let nfcRecord = new device.mojom.NDEFRecord(); |
| if (record.recordType.search(':') != -1) { |
| // Simply checks the existence of ':' to decide whether it's an external |
| // type. As a mock, no need to really implement the validation algo at |
| // https://w3c.github.io/web-nfc/#dfn-validate-external-type. |
| nfcRecord.category = device.mojom.NDEFRecordTypeCategory.kExternal; |
| } else { |
| nfcRecord.category = device.mojom.NDEFRecordTypeCategory.kStandardized; |
| } |
| nfcRecord.recordType = record.recordType; |
| nfcRecord.mediaType = record.mediaType; |
| nfcRecord.id = record.id; |
| 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. |
| function compareNDEFRecords(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'); |
| |
| assert_array_equals(toByteArray(providedRecord.data), |
| new Uint8Array(receivedRecord.data)); |
| } |
| |
| // Compares NDEFPushOptions structures that were provided to API and |
| // received by the mock mojo service. |
| function assertNDEFPushOptionsEqual(provided, received) { |
| if (provided.ignoreRead !== undefined) |
| assert_equals(provided.ignoreRead, !!received.ignoreRead); |
| else |
| assert_equals(!!received.ignore_read, true); |
| |
| if (provided.target !== undefined) |
| assert_equals(toMojoNDEFPushTarget(provided.target), received.target); |
| else |
| assert_equals(received.target, device.mojom.NDEFPushTarget.ANY); |
| } |
| |
| // Compares NDEFReaderOptions structures that were provided to API and |
| // received by the mock mojo service. |
| function assertNDEFReaderOptionsEqual(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); |
| } |
| } |
| |
| // Checks whether NDEFReaderOptions are matched with given message. |
| function matchesWatchOptions(message, options) { |
| // A message with no records is to notify that the tag is already formatted to |
| // support NDEF but does not contain a message yet. We always dispatch it for |
| // all options. |
| if (message.records.length == 0) |
| return true; |
| |
| for (let record of message.records) { |
| if (options.id != null && options.id !== record.id) { |
| continue; |
| } |
| if (options.recordType != null && |
| options.recordType !== record.recordType) { |
| continue; |
| } |
| if (options.mediaType !== '' && options.mediaType !== record.mediaType) { |
| continue; |
| } |
| |
| // Found one record matches, means the message matches. |
| return true; |
| } |
| |
| return false; |
| } |
| |
| function createNDEFError(type) { |
| return { error: type ? |
| new device.mojom.NDEFError({ errorType: type }) : null }; |
| } |
| |
| var WebNFCTest = (() => { |
| class MockNFC { |
| constructor() { |
| this.bindingSet_ = new mojo.BindingSet(device.mojom.NFC); |
| |
| this.interceptor_ = new MojoInterfaceInterceptor( |
| device.mojom.NFC.name, "context", true); |
| this.interceptor_.oninterfacerequest = |
| e => this.bindingSet_.addBinding(this, e.handle); |
| this.interceptor_.start(); |
| |
| this.hw_status_ = NFCHWStatus.ENABLED; |
| this.pushed_message_ = null; |
| this.pending_push_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; |
| } |
| |
| // 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_push_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(device.mojom.NDEFErrorType.NOT_ALLOWED)); |
| } else { |
| resolve(createNDEFError(null)); |
| } |
| }); |
| } |
| |
| async cancelPush(target) { |
| if (this.pending_push_options_ && |
| ((target === device.mojom.NDEFPushTarget.ANY) || |
| (this.pending_push_options_.target === target))) { |
| this.cancelPendingPushOperation(); |
| } |
| |
| return createNDEFError(null); |
| } |
| |
| setClient(client) { |
| this.client_ = client; |
| } |
| |
| async watch(options, id) { |
| assert_true(id > 0); |
| let error = this.getHWError(); |
| if (error) { |
| return error; |
| } |
| |
| this.watchers_.push({id: id, options: options}); |
| // 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_) { |
| if (matchesWatchOptions(message, options)) { |
| this.client_.onWatch( |
| [id], fake_tag_serial_number, toMojoNDEFMessage(message)); |
| } |
| } |
| } |
| |
| return createNDEFError(null); |
| } |
| |
| async cancelWatch(id) { |
| let index = this.watchers_.findIndex(value => value.id === id); |
| if (index === -1) { |
| return createNDEFError(device.mojom.NDEFErrorType.NOT_FOUND); |
| } |
| |
| this.watchers_.splice(index, 1); |
| return createNDEFError(null); |
| } |
| |
| async cancelAllWatches() { |
| if (this.watchers_.length === 0) { |
| return createNDEFError(device.mojom.NDEFErrorType.NOT_FOUND); |
| } |
| |
| this.watchers_.splice(0, this.watchers_.length); |
| return createNDEFError(null); |
| } |
| |
| getHWError() { |
| if (this.hw_status_ === NFCHWStatus.DISABLED) |
| return createNDEFError(device.mojom.NDEFErrorType.NOT_READABLE); |
| if (this.hw_status_ === NFCHWStatus.NOT_SUPPORTED) |
| return createNDEFError(device.mojom.NDEFErrorType.NOT_SUPPORTED); |
| return null; |
| } |
| |
| setHWStatus(status) { |
| this.hw_status_ = status; |
| } |
| |
| pushedMessage() { |
| return this.pushed_message_; |
| } |
| |
| pushOptions() { |
| return this.pending_push_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; |
| } |
| |
| cancelPendingPushOperation() { |
| if (this.pending_promise_func_) { |
| this.pending_promise_func_( |
| createNDEFError(device.mojom.NDEFErrorType.OPERATION_CANCELLED)); |
| this.pending_promise_func_ = null; |
| } |
| |
| this.pushed_message_ = null; |
| this.pending_push_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; |
| // Ignores reading if NDEFPushOptions.ignoreRead is true. |
| if (this.pending_push_options_ && this.pending_push_options_.ignoreRead) |
| return; |
| // Triggers onWatch if the new message matches existing watchers. |
| for (let watcher of this.watchers_) { |
| if (matchesWatchOptions(message, watcher.options)) { |
| 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_) { |
| if (matchesWatchOptions(message, watcher.options)) { |
| 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(device.mojom.NDEFErrorType.NOT_SUPPORTED); |
| } |
| // Reject the pending push with NotSupportedError. |
| if (this.pending_promise_func_) { |
| this.pending_promise_func_( |
| createNDEFError(device.mojom.NDEFErrorType.NOT_SUPPORTED)); |
| this.pending_promise_func_ = null; |
| } |
| } |
| |
| setIsFormattedTag(isFormatted) { |
| this.is_formatted_tag_ = isFormatted; |
| } |
| } |
| |
| let testInternal = { |
| initialized: false, |
| mockNFC: null |
| } |
| |
| class NFCTestChromium { |
| constructor() { |
| Object.freeze(this); // Makes it immutable. |
| } |
| |
| initialize() { |
| if (testInternal.initialized) |
| throw new Error('Call reset() before initialize().'); |
| |
| if (window.testRunner) { |
| // Grant nfc permissions for Chromium testrunner. |
| window.testRunner.setPermission('nfc', 'granted', |
| location.origin, location.origin); |
| } |
| |
| 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; |
| })(); |