blob: 6ef03b390d8df7df8abd7662e037db6bdf5febce [file] [log] [blame]
'use strict';
function toMojoNFCPushTarget(target) {
switch (target) {
case 'peer':
return device.mojom.NFCPushTarget.PEER;
case 'tag':
return device.mojom.NFCPushTarget.TAG;
}
return device.mojom.NFCPushTarget.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.url = message.url;
ndefMessage.data = [];
for (let record of message.records) {
ndefMessage.data.push(toMojoNDEFRecord(record));
}
return ndefMessage;
}
function toMojoNDEFRecord(record) {
let nfcRecord = new device.mojom.NDEFRecord();
nfcRecord.recordType = record.recordType;
nfcRecord.mediaType = record.mediaType;
nfcRecord.data = toByteArray(record.data);
return nfcRecord;
}
function toByteArray(data) {
// Convert JS objects to byte array.
let byteArray = new Uint8Array(0);
let tmpData = data;
if (tmpData instanceof ArrayBuffer)
byteArray = new Uint8Array(tmpData);
else 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);
// Compare media types without charset.
// Charset should be compared when watch method is implemented, in order
// to check that written and read strings are equal.
assert_equals(providedRecord.mediaType,
receivedRecord.mediaType.substring(0, providedRecord.mediaType.length));
assert_not_equals(providedRecord.recordType, 'empty');
assert_array_equals(toByteArray(providedRecord.data),
new Uint8Array(receivedRecord.data));
}
// Compares NFCPushOptions structures that were provided to API and
// received by the mock mojo service.
function assertNFCPushOptionsEqual(provided, received) {
if (provided.ignoreRead !== undefined)
assert_equals(provided.ignoreRead, !!received.ignoreRead);
else
assert_equals(!!received.ignore_read, true);
if (provided.timeout !== undefined)
assert_equals(provided.timeout, received.timeout);
else
assert_equals(received.timeout, Infinity);
if (provided.target !== undefined)
assert_equals(toMojoNFCPushTarget(provided.target), received.target);
else
assert_equals(received.target, device.mojom.NFCPushTarget.ANY);
}
// Compares NFCReaderOptions structures that were provided to API and
// received by the mock mojo service.
function assertNFCReaderOptionsEqual(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(!+received.record_filter, true);
assert_equals(provided.recordType, received.recordFilter.recordType);
}
}
// Checks whether NFCReaderOptions are matched with given message.
function matchesWatchOptions(message, options) {
// Filter by Web NFC id.
if (!matchesWebNfcId(message.url, options.url)) return false;
// Matches any record / media type.
if ((options.mediaType == null || options.mediaType === "")
&& options.recordFilter == null) {
return true;
}
// Filter by mediaType and recordType.
for (let record of message.records) {
if (options.mediaType != null && options.mediaType !== ""
&& options.mediaType !== record.mediaType) {
return false;
}
if (options.recordFilter != null &&
options.recordFilter.recordType !== record.recordType) {
return false;
}
}
return true;
}
// Web NFC id match algorithm.
// https://w3c.github.io/web-nfc/#url-pattern-match-algorithm
function matchesWebNfcId(id, pattern) {
if (id != null && id !== "" && pattern != null && pattern !== "") {
const id_url = new URL(id);
const pattern_url = new URL(pattern);
if (id_url.protocol !== pattern_url.protocol) return false;
if (!id_url.host.endsWith("." + pattern_url.host)
&& id_url.host !== pattern_url.host) {
return false;
}
if (pattern_url.pathname === "/*") return true;
if (id_url.pathname.startsWith(pattern_url.pathname)) return true;
return false;
}
return true;
}
function createNFCError(type) {
return { error: type ?
new device.mojom.NFCError({ 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.push_options_ = null;
this.pending_promise_func_ = null;
this.push_completed_ = true;
this.push_should_timeout_ = false;
this.client_ = null;
this.watchers_ = [];
this.reading_messages_ = [];
this.operations_suspended_ = false;
}
// NFC delegate functions.
async push(message, options) {
let error = this.getHWError();
if (error)
return error;
// Cancel previous pending push operation.
if (this.pending_promise_func_) {
this.cancelPendingPushOperation();
}
this.pushed_message_ = message;
this.push_options_ = options;
return new Promise(resolve => {
this.pending_promise_func_ = resolve;
// Pend push operation if NFC operation is suspended.
if (this.operations_suspended_) {
// Do nothing, pends push operation.
} else if (options.timeout && options.timeout !== Infinity &&
!this.push_completed_) {
// Resolve with TimeoutError, else pend push operation.
if (this.push_should_timeout_) {
resolve(
createNFCError(device.mojom.NFCErrorType.TIMER_EXPIRED));
}
} else {
resolve(createNFCError(null));
}
});
}
async cancelPush(target) {
if (this.push_options_ && ((target === device.mojom.NFCPushTarget.ANY) ||
(this.push_options_.target === target))) {
this.cancelPendingPushOperation();
}
return createNFCError(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});
// Ignore reading if NFC operation is suspended.
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 createNFCError(null);
}
async cancelWatch(id) {
let index = this.watchers_.findIndex(value => value.id === id);
if (index === -1) {
return createNFCError(device.mojom.NFCErrorType.NOT_FOUND);
}
this.watchers_.splice(index, 1);
return createNFCError(null);
}
async cancelAllWatches() {
if (this.watchers_.length === 0) {
return createNFCError(device.mojom.NFCErrorType.NOT_FOUND);
}
this.watchers_.splice(0, this.watchers_.length);
return createNFCError(null);
}
getHWError() {
if (this.hw_status_ === NFCHWStatus.DISABLED)
return createNFCError(device.mojom.NFCErrorType.NOT_READABLE);
if (this.hw_status_ === NFCHWStatus.NOT_SUPPORTED)
return createNFCError(device.mojom.NFCErrorType.NOT_SUPPORTED);
return null;
}
setHWStatus(status) {
this.hw_status_ = status;
}
pushedMessage() {
return this.pushed_message_;
}
pushOptions() {
return this.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.push_completed_ = true;
this.watchers_ = [];
this.reading_messages_ = [];
this.operations_suspended_ = false;
this.cancelPendingPushOperation();
this.bindingSet_.closeAllBindings();
this.interceptor_.stop();
}
cancelPendingPushOperation() {
if (this.pending_promise_func_) {
this.pending_promise_func_(
createNFCError(device.mojom.NFCErrorType.OPERATION_CANCELLED));
}
this.pushed_message_ = null;
this.push_options_ = null;
this.pending_promise_func_ = null;
this.push_should_timeout_ = false;
this.push_completed_ = true;
}
// Sets message that is used to deliver NFC reading updates.
setReadingMessage(message) {
this.reading_messages_.push(message);
// Ignore reading if NFC operation is suspended.
if(this.operations_suspended_) return;
// Ignore reading if NFCPushOptions.ignoreRead is true.
if(this.push_options_ && this.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));
}
}
}
setPushShouldTimeout(result) {
this.push_should_timeout_ = result;
}
// 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.pending_promise_func_(createNFCError(null));
}
}
}
let testInternal = {
initialized: false,
mockNFC: null
}
class NFCTestChromium {
constructor() {
Object.freeze(this); // Make it immutable.
}
initialize() {
if (testInternal.initialized)
throw new Error('Call reset() before initialize().');
testInternal.mockNFC = new MockNFC;
testInternal.initialized = true;
}
// Resets state of nfc mocks between test runs.
async reset() {
if (!testInternal.initialized)
throw new Error('Call initialize() before reset().');
testInternal.mockNFC.reset();
testInternal.mockNFC = null;
testInternal.initialized = false;
await new Promise(resolve => setTimeout(resolve, 0));
}
getMockNFC() {
return testInternal.mockNFC;
}
}
return NFCTestChromium;
})();