tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

nfc-mock.js (14131B)


      1 import {NDEFErrorType, NDEFRecordTypeCategory, NFC, NFCReceiver} from '/gen/services/device/public/mojom/nfc.mojom.m.js';
      2 
      3 // Converts between NDEFMessageInit https://w3c.github.io/web-nfc/#dom-ndefmessage
      4 // and mojom.NDEFMessage structure, so that watch function can be tested.
      5 function toMojoNDEFMessage(message) {
      6  let ndefMessage = {data: []};
      7  for (let record of message.records) {
      8    ndefMessage.data.push(toMojoNDEFRecord(record));
      9  }
     10  return ndefMessage;
     11 }
     12 
     13 function toMojoNDEFRecord(record) {
     14  let nfcRecord = {};
     15  // Simply checks the existence of ':' to decide whether it's an external
     16  // type or a local type. As a mock, no need to really implement the validation
     17  // algorithms for them.
     18  if (record.recordType.startsWith(':')) {
     19    nfcRecord.category = NDEFRecordTypeCategory.kLocal;
     20  } else if (record.recordType.search(':') != -1) {
     21    nfcRecord.category = NDEFRecordTypeCategory.kExternal;
     22  } else {
     23    nfcRecord.category = NDEFRecordTypeCategory.kStandardized;
     24  }
     25  nfcRecord.recordType = record.recordType;
     26  nfcRecord.mediaType = record.mediaType;
     27  nfcRecord.id = record.id;
     28  if (record.recordType == 'text') {
     29    nfcRecord.encoding = record.encoding == null? 'utf-8': record.encoding;
     30    nfcRecord.lang = record.lang == null? 'en': record.lang;
     31  }
     32  nfcRecord.data = toByteArray(record.data);
     33  if (record.data != null && record.data.records !== undefined) {
     34    // |record.data| may be an NDEFMessageInit, i.e. the payload is a message.
     35    nfcRecord.payloadMessage = toMojoNDEFMessage(record.data);
     36  }
     37  return nfcRecord;
     38 }
     39 
     40 // Converts JS objects to byte array.
     41 function toByteArray(data) {
     42  if (data instanceof ArrayBuffer)
     43    return new Uint8Array(data);
     44  else if (ArrayBuffer.isView(data))
     45    return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
     46 
     47  let byteArray = new Uint8Array(0);
     48  let tmpData = data;
     49  if (typeof tmpData === 'object' || typeof tmpData === 'number')
     50    tmpData = JSON.stringify(tmpData);
     51 
     52  if (typeof tmpData === 'string')
     53    byteArray = new TextEncoder().encode(tmpData);
     54 
     55  return byteArray;
     56 }
     57 
     58 // Compares NDEFRecords that were provided / received by the mock service.
     59 // TODO: Use different getters to get received record data,
     60 // see spec changes at https://github.com/w3c/web-nfc/pull/243.
     61 self.compareNDEFRecords = function(providedRecord, receivedRecord) {
     62  assert_equals(providedRecord.recordType, receivedRecord.recordType);
     63 
     64  if (providedRecord.id === undefined) {
     65    assert_equals(null, receivedRecord.id);
     66  } else {
     67    assert_equals(providedRecord.id, receivedRecord.id);
     68  }
     69 
     70  if (providedRecord.mediaType === undefined) {
     71    assert_equals(null, receivedRecord.mediaType);
     72  } else {
     73    assert_equals(providedRecord.mediaType, receivedRecord.mediaType);
     74  }
     75 
     76  assert_not_equals(providedRecord.recordType, 'empty');
     77 
     78  if (providedRecord.recordType == 'text') {
     79    assert_equals(
     80        providedRecord.encoding == null? 'utf-8': providedRecord.encoding,
     81        receivedRecord.encoding);
     82    assert_equals(providedRecord.lang == null? 'en': providedRecord.lang,
     83                  receivedRecord.lang);
     84  } else {
     85    assert_equals(null, receivedRecord.encoding);
     86    assert_equals(null, receivedRecord.lang);
     87  }
     88 
     89  assert_array_equals(toByteArray(providedRecord.data),
     90                      new Uint8Array(receivedRecord.data));
     91 }
     92 
     93 // Compares NDEFWriteOptions structures that were provided to API and
     94 // received by the mock mojo service.
     95 self.assertNDEFWriteOptionsEqual = function(provided, received) {
     96  if (provided.overwrite !== undefined)
     97    assert_equals(provided.overwrite, !!received.overwrite);
     98  else
     99    assert_equals(!!received.overwrite, true);
    100 }
    101 
    102 // Compares NDEFReaderOptions structures that were provided to API and
    103 // received by the mock mojo service.
    104 self.assertNDEFReaderOptionsEqual = function(provided, received) {
    105  if (provided.url !== undefined)
    106    assert_equals(provided.url, received.url);
    107  else
    108    assert_equals(received.url, '');
    109 
    110  if (provided.mediaType !== undefined)
    111    assert_equals(provided.mediaType, received.mediaType);
    112  else
    113    assert_equals(received.mediaType, '');
    114 
    115  if (provided.recordType !== undefined) {
    116    assert_equals(provided.recordType, received.recordType);
    117  }
    118 }
    119 
    120 function createNDEFError(type) {
    121  return {error: (type != null ? {errorType: type, errorMessage: ''} : null)};
    122 }
    123 
    124 self.WebNFCTest = (() => {
    125  class MockNFC {
    126    constructor() {
    127      this.receiver_ = new NFCReceiver(this);
    128 
    129      this.interceptor_ = new MojoInterfaceInterceptor(NFC.$interfaceName);
    130      this.interceptor_.oninterfacerequest = e => {
    131        if (this.should_close_pipe_on_request_)
    132          e.handle.close();
    133        else
    134          this.receiver_.$.bindHandle(e.handle);
    135      }
    136 
    137      this.interceptor_.start();
    138 
    139      this.hw_status_ = NFCHWStatus.ENABLED;
    140      this.pushed_message_ = null;
    141      this.pending_write_options_ = null;
    142      this.pending_push_promise_func_ = null;
    143      this.push_completed_ = true;
    144      this.pending_make_read_only_promise_func_ = null;
    145      this.make_read_only_completed_ = true;
    146      this.client_ = null;
    147      this.watchers_ = [];
    148      this.reading_messages_ = [];
    149      this.operations_suspended_ = false;
    150      this.is_formatted_tag_ = false;
    151      this.data_transfer_failed_ = false;
    152      this.should_close_pipe_on_request_ = false;
    153    }
    154 
    155    // NFC delegate functions.
    156    async push(message, options) {
    157      const error = this.getHWError();
    158      if (error)
    159        return error;
    160      // Cancels previous pending push operation.
    161      if (this.pending_push_promise_func_) {
    162        this.cancelPendingPushOperation();
    163      }
    164 
    165      this.pushed_message_ = message;
    166      this.pending_write_options_ = options;
    167      return new Promise(resolve => {
    168        if (this.operations_suspended_ || !this.push_completed_) {
    169          // Leaves the push pending.
    170          this.pending_push_promise_func_ = resolve;
    171        } else if (this.is_formatted_tag_ && !options.overwrite) {
    172          // Resolves with NotAllowedError if there are NDEF records on the device
    173          // and overwrite is false.
    174          resolve(createNDEFError(NDEFErrorType.NOT_ALLOWED));
    175        } else if (this.data_transfer_failed_) {
    176          // Resolves with NetworkError if data transfer fails.
    177          resolve(createNDEFError(NDEFErrorType.IO_ERROR));
    178        } else {
    179          resolve(createNDEFError(null));
    180        }
    181      });
    182    }
    183 
    184    async cancelPush() {
    185      this.cancelPendingPushOperation();
    186      return createNDEFError(null);
    187    }
    188 
    189    async makeReadOnly(options) {
    190      const error = this.getHWError();
    191      if (error)
    192        return error;
    193      // Cancels previous pending makeReadOnly operation.
    194      if (this.pending_make_read_only_promise_func_) {
    195        this.cancelPendingMakeReadOnlyOperation();
    196      }
    197 
    198      if (this.operations_suspended_ || !this.make_read_only_completed_) {
    199        // Leaves the makeReadOnly pending.
    200        return new Promise(resolve => {
    201          this.pending_make_read_only_promise_func_ = resolve;
    202        });
    203      } else if (this.data_transfer_failed_) {
    204        // Resolves with NetworkError if data transfer fails.
    205        return createNDEFError(NDEFErrorType.IO_ERROR);
    206      } else {
    207        return createNDEFError(null);
    208      }
    209    }
    210 
    211    async cancelMakeReadOnly() {
    212      this.cancelPendingMakeReadOnlyOperation();
    213      return createNDEFError(null);
    214    }
    215 
    216    setClient(client) {
    217      this.client_ = client;
    218    }
    219 
    220    async watch(id) {
    221      assert_true(id > 0);
    222      const error = this.getHWError();
    223      if (error) {
    224        return error;
    225      }
    226 
    227      this.watchers_.push({id: id});
    228      // Ignores reading if NFC operation is suspended
    229      // or the NFC tag does not expose NDEF technology.
    230      if (!this.operations_suspended_) {
    231        // Triggers onWatch if the new watcher matches existing messages.
    232        for (let message of this.reading_messages_) {
    233          this.client_.onWatch(
    234              [id], fake_tag_serial_number, toMojoNDEFMessage(message));
    235        }
    236      }
    237 
    238      return createNDEFError(null);
    239    }
    240 
    241    cancelWatch(id) {
    242      let index = this.watchers_.findIndex(value => value.id === id);
    243      if (index !== -1) {
    244        this.watchers_.splice(index, 1);
    245      }
    246    }
    247 
    248    getHWError() {
    249      if (this.hw_status_ === NFCHWStatus.DISABLED)
    250        return createNDEFError(NDEFErrorType.NOT_READABLE);
    251      if (this.hw_status_ === NFCHWStatus.NOT_SUPPORTED)
    252        return createNDEFError(NDEFErrorType.NOT_SUPPORTED);
    253      return null;
    254    }
    255 
    256    setHWStatus(status) {
    257      this.hw_status_ = status;
    258    }
    259 
    260    pushedMessage() {
    261      return this.pushed_message_;
    262    }
    263 
    264    writeOptions() {
    265      return this.pending_write_options_;
    266    }
    267 
    268    watchOptions() {
    269      assert_not_equals(this.watchers_.length, 0);
    270      return this.watchers_[this.watchers_.length - 1].options;
    271    }
    272 
    273    setPendingPushCompleted(result) {
    274      this.push_completed_ = result;
    275    }
    276 
    277    setPendingMakeReadOnlyCompleted(result) {
    278      this.make_read_only_completed_ = result;
    279    }
    280 
    281    reset() {
    282      this.hw_status_ = NFCHWStatus.ENABLED;
    283      this.watchers_ = [];
    284      this.reading_messages_ = [];
    285      this.operations_suspended_ = false;
    286      this.cancelPendingPushOperation();
    287      this.cancelPendingMakeReadOnlyOperation();
    288      this.is_formatted_tag_ = false;
    289      this.data_transfer_failed_ = false;
    290      this.should_close_pipe_on_request_ = false;
    291    }
    292 
    293    cancelPendingPushOperation() {
    294      if (this.pending_push_promise_func_) {
    295        this.pending_push_promise_func_(
    296            createNDEFError(NDEFErrorType.OPERATION_CANCELLED));
    297        this.pending_push_promise_func_ = null;
    298      }
    299 
    300      this.pushed_message_ = null;
    301      this.pending_write_options_ = null;
    302      this.push_completed_ = true;
    303    }
    304 
    305    cancelPendingMakeReadOnlyOperation() {
    306      if (this.pending_make_read_only_promise_func_) {
    307        this.pending_make_read_only_promise_func_(
    308            createNDEFError(NDEFErrorType.OPERATION_CANCELLED));
    309        this.pending_make_read_only_promise_func_ = null;
    310      }
    311 
    312      this.make_read_only_completed_ = true;
    313    }
    314 
    315    // Sets message that is used to deliver NFC reading updates.
    316    setReadingMessage(message) {
    317      this.reading_messages_.push(message);
    318      // Ignores reading if NFC operation is suspended.
    319      if(this.operations_suspended_) return;
    320      // when overwrite is false, the write algorithm will read the NFC tag
    321      // to determine if it has NDEF records on it.
    322      if (this.pending_write_options_ && this.pending_write_options_.overwrite)
    323        return;
    324      // Triggers onWatch if the new message matches existing watchers.
    325      for (let watcher of this.watchers_) {
    326        this.client_.onWatch(
    327            [watcher.id], fake_tag_serial_number,
    328            toMojoNDEFMessage(message));
    329      }
    330    }
    331 
    332    // Suspends all pending NFC operations. Could be used when web page
    333    // visibility is lost.
    334    suspendNFCOperations() {
    335      this.operations_suspended_ = true;
    336    }
    337 
    338    // Resumes all suspended NFC operations.
    339    resumeNFCOperations() {
    340      this.operations_suspended_ = false;
    341      // Resumes pending NFC reading.
    342      for (let watcher of this.watchers_) {
    343        for (let message of this.reading_messages_) {
    344          this.client_.onWatch(
    345              [watcher.id], fake_tag_serial_number,
    346              toMojoNDEFMessage(message));
    347        }
    348      }
    349      // Resumes pending push operation.
    350      if (this.pending_push_promise_func_ && this.push_completed_) {
    351        this.pending_push_promise_func_(createNDEFError(null));
    352        this.pending_push_promise_func_ = null;
    353      }
    354      // Resumes pending makeReadOnly operation.
    355      if (this.pending_make_read_only_promise_func_ &&
    356          this.make_read_only_completed_) {
    357        this.pending_make_read_only_promise_func_(createNDEFError(null));
    358        this.pending_make_read_only_promise_func_ = null;
    359      }
    360    }
    361 
    362    // Simulates the device coming in proximity does not expose NDEF technology.
    363    simulateNonNDEFTagDiscovered() {
    364      // Notify NotSupportedError to all active readers.
    365      if (this.watchers_.length != 0) {
    366        this.client_.onError({
    367          errorType: NDEFErrorType.NOT_SUPPORTED,
    368          errorMessage: ''
    369        });
    370      }
    371      // Reject the pending push with NotSupportedError.
    372      if (this.pending_push_promise_func_) {
    373        this.pending_push_promise_func_(
    374            createNDEFError(NDEFErrorType.NOT_SUPPORTED));
    375        this.pending_push_promise_func_ = null;
    376      }
    377      // Reject the pending makeReadOnly with NotSupportedError.
    378      if (this.pending_make_read_only_promise_func_) {
    379        this.pending_make_read_only_promise_func_(
    380            createNDEFError(NDEFErrorType.NOT_SUPPORTED));
    381        this.pending_make_read_only_promise_func_ = null;
    382      }
    383    }
    384 
    385    setIsFormattedTag(isFormatted) {
    386      this.is_formatted_tag_ = isFormatted;
    387    }
    388 
    389    simulateDataTransferFails() {
    390      this.data_transfer_failed_ = true;
    391    }
    392 
    393    simulateClosedPipe() {
    394      this.should_close_pipe_on_request_ = true;
    395    }
    396  }
    397 
    398  let testInternal = {
    399    initialized: false,
    400    mockNFC: null
    401  }
    402 
    403  class NFCTestChromium {
    404    constructor() {
    405      Object.freeze(this); // Makes it immutable.
    406    }
    407 
    408    async initialize() {
    409      if (testInternal.initialized)
    410        throw new Error('Call reset() before initialize().');
    411 
    412      // Grant nfc permissions for Chromium testdriver.
    413      await test_driver.set_permission({ name: 'nfc' }, 'granted');
    414 
    415      if (testInternal.mockNFC == null) {
    416        testInternal.mockNFC = new MockNFC();
    417      }
    418      testInternal.initialized = true;
    419    }
    420 
    421    // Reuses the nfc mock but resets its state between test runs.
    422    async reset() {
    423      if (!testInternal.initialized)
    424        throw new Error('Call initialize() before reset().');
    425      testInternal.mockNFC.reset();
    426      testInternal.initialized = false;
    427 
    428      await new Promise(resolve => setTimeout(resolve, 0));
    429    }
    430 
    431    getMockNFC() {
    432      return testInternal.mockNFC;
    433    }
    434  }
    435 
    436  return NFCTestChromium;
    437 })();