tor-browser

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

bluetooth-test.js (15318B)


      1 'use strict';
      2 
      3 // A flag indicating whether to use Web Bluetooth BiDi commands for Bluetooth
      4 // emulation.
      5 let useBidi = false;
      6 
      7 /**
      8 * Test Setup Helpers
      9 */
     10 
     11 /**
     12 * Loads a script by creating a <script> element pointing to |path|.
     13 * @param {string} path The path of the script to load.
     14 * @returns {Promise<void>} Resolves when the script has finished loading.
     15 */
     16 function loadScript(path) {
     17  let script = document.createElement('script');
     18  let promise = new Promise(resolve => script.onload = resolve);
     19  script.src = path;
     20  script.async = false;
     21  document.head.appendChild(script);
     22  return promise;
     23 }
     24 
     25 /**
     26 * Performs the Chromium specific setup necessary to run the tests in the
     27 * Chromium browser. This test file is shared between Web Platform Tests and
     28 * Blink Web Tests, so this method figures out the correct paths to use for
     29 * loading scripts.
     30 *
     31 * TODO(https://crbug.com/569709): Update this description when all Web
     32 * Bluetooth Blink Web Tests have been migrated into this repository.
     33 * @returns {Promise<void>} Resolves when Chromium specific setup is complete.
     34 */
     35 async function performChromiumSetup() {
     36  // Determine path prefixes.
     37  let resPrefix = '/resources';
     38  const chromiumResources = ['/resources/chromium/web-bluetooth-test.js'];
     39  const pathname = window.location.pathname;
     40  if (pathname.includes('/wpt_internal/')) {
     41    chromiumResources.push(
     42        '/wpt_internal/bluetooth/resources/bluetooth-fake-adapter.js');
     43  }
     44 
     45  await loadScript(`${resPrefix}/test-only-api.js`);
     46  if (!isChromiumBased) {
     47    return;
     48  }
     49 
     50  for (const path of chromiumResources) {
     51    await loadScript(path);
     52  }
     53 
     54  await initializeChromiumResources();
     55 
     56  // Call setBluetoothFakeAdapter() to clean up any fake adapters left over by
     57  // legacy tests. Legacy tests that use setBluetoothFakeAdapter() sometimes
     58  // fail to clean their fake adapter. This is not a problem for these tests
     59  // because the next setBluetoothFakeAdapter() will clean it up anyway but it
     60  // is a problem for the new tests that do not use setBluetoothFakeAdapter().
     61  // TODO(https://crbug.com/569709): Remove once setBluetoothFakeAdapter is no
     62  // longer used.
     63  if (typeof setBluetoothFakeAdapter !== 'undefined') {
     64    setBluetoothFakeAdapter('');
     65  }
     66 }
     67 
     68 /**
     69 * These tests rely on the User Agent providing an implementation of the Web
     70 * Bluetooth Testing API.
     71 * https://docs.google.com/document/d/1Nhv_oVDCodd1pEH_jj9k8gF4rPGb_84VYaZ9IG8M_WY/edit?ts=59b6d823#heading=h.7nki9mck5t64
     72 * @param {function{*}: Promise<*>} test_function The Web Bluetooth test to run.
     73 * @param {string} name The name or description of the test.
     74 * @param {object} properties An object containing extra options for the test.
     75 * @param {Boolean} validate_response_consumed Whether to validate all response
     76 *     consumed or not.
     77 * @returns {Promise<void>} Resolves if Web Bluetooth test ran successfully, or
     78 *     rejects if the test failed.
     79 */
     80 function bluetooth_test(
     81    test_function, name, properties, validate_response_consumed = true) {
     82  return promise_test(async (t) => {
     83    assert_implements(navigator.bluetooth, 'missing navigator.bluetooth');
     84    // Trigger Chromium-specific setup.
     85    await performChromiumSetup();
     86    assert_implements(
     87        navigator.bluetooth.test, 'missing navigator.bluetooth.test');
     88    await test_function(t);
     89    if (validate_response_consumed) {
     90      let consumed = await navigator.bluetooth.test.allResponsesConsumed();
     91      assert_true(consumed);
     92    }
     93  }, name, properties);
     94 }
     95 
     96 /**
     97 * These tests rely on the User Agent providing an implementation of the
     98 * WebDriver-Bidi for testing Web Bluetooth
     99 * https://webbluetoothcg.github.io/web-bluetooth/#automated-testing
    100 * @param {function{*}: Promise<*>} test_function The Web Bluetooth test to run.
    101 * @param {string} name The name or description of the test.
    102 * @param {object} properties An object containing extra options for the test.
    103 * @param {Boolean} validate_response_consumed Whether to validate all response
    104 *     consumed or not.
    105 * @returns {Promise<void>} Resolves if Web Bluetooth test ran successfully, or
    106 *     rejects if the test failed.
    107 */
    108 function bluetooth_bidi_test(
    109  test_function, name, properties, validate_response_consumed = true) {
    110 return promise_test(async (t) => {
    111  assert_implements(navigator.bluetooth, 'missing navigator.bluetooth');
    112 
    113  // Necessary setup for Bluetooth emulation using WebDriver Bidi commands.
    114  useBidi = true;
    115  await loadScript('/resources/web-bluetooth-bidi-test.js');
    116  await initializeBluetoothBidiResources();
    117  assert_implements(
    118      navigator.bluetooth.test, 'missing navigator.bluetooth.test');
    119  await test_driver.bidi.bluetooth.request_device_prompt_updated.subscribe();
    120  await test_driver.bidi.bluetooth.gatt_connection_attempted.subscribe();
    121  await test_driver.bidi.bluetooth.characteristic_event_generated.subscribe();
    122  await test_driver.bidi.bluetooth.descriptor_event_generated.subscribe();
    123  try {
    124    await test_function(t);
    125  } finally {
    126    await test_driver.bidi.bluetooth.disable_simulation();
    127  }
    128 }, name, properties);
    129 }
    130 
    131 /**
    132 * Test Helpers
    133 */
    134 
    135 /**
    136 * Waits until the document has finished loading.
    137 * @returns {Promise<void>} Resolves if the document is already completely
    138 *     loaded or when the 'onload' event is fired.
    139 */
    140 function waitForDocumentReady() {
    141  return new Promise(resolve => {
    142    if (document.readyState === 'complete') {
    143      resolve();
    144    }
    145 
    146    window.addEventListener('load', () => {
    147      resolve();
    148    }, {once: true});
    149  });
    150 }
    151 
    152 /**
    153 * Simulates a user activation prior to running |callback|.
    154 * @param {Function} callback The function to run after the user activation.
    155 * @returns {Promise<*>} Resolves when the user activation has been simulated
    156 *     with the result of |callback|.
    157 */
    158 async function callWithTrustedClick(callback) {
    159  await waitForDocumentReady();
    160  return new Promise(resolve => {
    161    let button = document.createElement('button');
    162    button.textContent = 'click to continue test';
    163    button.style.display = 'block';
    164    button.style.fontSize = '20px';
    165    button.style.padding = '10px';
    166    button.onclick = () => {
    167      document.body.removeChild(button);
    168      resolve(callback());
    169    };
    170    document.body.appendChild(button);
    171    test_driver.click(button);
    172  });
    173 }
    174 
    175 /**
    176 * Registers a one-time handler that selects the first device in the device
    177 * prompt upon a device prompt updated event.
    178 * @returns {Promise<void>} Fulfilled after the Bluetooth device prompt
    179 * is handled, or rejected if the operation fails.
    180 */
    181 function selectFirstDeviceOnDevicePromptUpdated() {
    182  if (!useBidi) {
    183    // Return a resolved promise when there is no bidi support.
    184    return Promise.resolve();
    185  }
    186  test_driver.bidi.bluetooth.request_device_prompt_updated.once().then(
    187      (promptEvent) => {
    188        assert_greater_than(promptEvent.devices.length, 0);
    189        return test_driver.bidi.bluetooth.handle_request_device_prompt({
    190          prompt: promptEvent.prompt,
    191          accept: true,
    192          device: promptEvent.devices[0].id
    193        });
    194      });
    195 }
    196 
    197 /**
    198 * Calls requestDevice() in a context that's 'allowed to show a popup'.
    199 * @returns {Promise<BluetoothDevice>} Resolves with a Bluetooth device if
    200 *     successful or rejects with an error.
    201 */
    202 function requestDeviceWithTrustedClick() {
    203  selectFirstDeviceOnDevicePromptUpdated();
    204  let args = arguments;
    205  return callWithTrustedClick(
    206      () => navigator.bluetooth.requestDevice.apply(navigator.bluetooth, args));
    207 }
    208 
    209 /**
    210 * Calls requestLEScan() in a context that's 'allowed to show a popup'.
    211 * @returns {Promise<BluetoothLEScan>} Resolves with the properties of the scan
    212 *     if successful or rejects with an error.
    213 */
    214 function requestLEScanWithTrustedClick() {
    215  let args = arguments;
    216  return callWithTrustedClick(
    217      () => navigator.bluetooth.requestLEScan.apply(navigator.bluetooth, args));
    218 }
    219 
    220 /**
    221 * Function to test that a promise rejects with the expected error type and
    222 * message.
    223 * @param {Promise} promise
    224 * @param {object} expected
    225 * @param {string} description
    226 * @returns {Promise<void>} Resolves if |promise| rejected with |expected|
    227 *     error.
    228 */
    229 function assert_promise_rejects_with_message(promise, expected, description) {
    230  return promise.then(
    231      () => {
    232        assert_unreached('Promise should have rejected: ' + description);
    233      },
    234      error => {
    235        assert_equals(error.name, expected.name, 'Unexpected Error Name:');
    236        if (expected.message) {
    237          assert_true(
    238              error.message.includes(expected.message),
    239              'Unexpected Error Message:');
    240        }
    241      });
    242 }
    243 
    244 /**
    245 * Helper class that can be created to check that an event has fired.
    246 */
    247 class EventCatcher {
    248  /**
    249   * @param {EventTarget} object The object to listen for events on.
    250   * @param {string} event The type of event to listen for.
    251   */
    252  constructor(object, event) {
    253    /** @type {boolean} */
    254    this.eventFired = false;
    255 
    256    /** @type {function()} */
    257    let event_listener = () => {
    258      object.removeEventListener(event, event_listener);
    259      this.eventFired = true;
    260    };
    261    object.addEventListener(event, event_listener);
    262  }
    263 }
    264 
    265 /**
    266 * Notifies when the event |type| has fired.
    267 * @param {EventTarget} target The object to listen for the event.
    268 * @param {string} type The type of event to listen for.
    269 * @param {object} options Characteristics about the event listener.
    270 * @returns {Promise<Event>} Resolves when an event of |type| has fired.
    271 */
    272 function eventPromise(target, type, options) {
    273  return new Promise(resolve => {
    274    let wrapper = function(event) {
    275      target.removeEventListener(type, wrapper);
    276      resolve(event);
    277    };
    278    target.addEventListener(type, wrapper, options);
    279  });
    280 }
    281 
    282 /**
    283 * The action that should occur first in assert_promise_event_order_().
    284 * @enum {string}
    285 */
    286 const ShouldBeFirst = {
    287  EVENT: 'event',
    288  PROMISE_RESOLUTION: 'promiseresolved',
    289 };
    290 
    291 /**
    292 * Helper function to assert that events are fired and a promise resolved
    293 * in the correct order.
    294 * 'event' should be passed as |should_be_first| to indicate that the events
    295 * should be fired first, otherwise 'promiseresolved' should be passed.
    296 * Attaches |num_listeners| |event| listeners to |object|. If all events have
    297 * been fired and the promise resolved in the correct order, returns a promise
    298 * that fulfills with the result of |object|.|func()| and |event.target.value|
    299 * of each of event listeners. Otherwise throws an error.
    300 * @param {ShouldBeFirst} should_be_first Indicates whether |func| should
    301 *     resolve before |event| is fired.
    302 * @param {EventTarget} object The target object to add event listeners to.
    303 * @param {function(*): Promise<*>} func The function to test the resolution
    304 *     order for.
    305 * @param {string} event The event type to listen for.
    306 * @param {number} num_listeners The number of events to listen for.
    307 * @returns {Promise<*>} The return value of |func|.
    308 */
    309 function assert_promise_event_order_(
    310    should_be_first, object, func, event, num_listeners) {
    311  let order = [];
    312  let event_promises = [];
    313  for (let i = 0; i < num_listeners; i++) {
    314    event_promises.push(new Promise(resolve => {
    315      let event_listener = (e) => {
    316        object.removeEventListener(event, event_listener);
    317        order.push(ShouldBeFirst.EVENT);
    318        resolve(e.target.value);
    319      };
    320      object.addEventListener(event, event_listener);
    321    }));
    322  }
    323 
    324  let func_promise = object[func]().then(result => {
    325    order.push(ShouldBeFirst.PROMISE_RESOLUTION);
    326    return result;
    327  });
    328 
    329  return Promise.all([func_promise, ...event_promises]).then((result) => {
    330    if (should_be_first !== order[0]) {
    331      throw should_be_first === ShouldBeFirst.PROMISE_RESOLUTION ?
    332          `'${event}' was fired before promise resolved.` :
    333          `Promise resolved before '${event}' was fired.`;
    334    }
    335 
    336    if (order[0] !== ShouldBeFirst.PROMISE_RESOLUTION &&
    337        order[order.length - 1] !== ShouldBeFirst.PROMISE_RESOLUTION) {
    338      throw 'Promise resolved in between event listeners.';
    339    }
    340 
    341    return result;
    342  });
    343 }
    344 
    345 /**
    346 * Asserts that the promise returned by |func| resolves before events of type
    347 * |event| are fired |num_listeners| times on |object|. See
    348 * assert_promise_event_order_ above for more details.
    349 * @param {EventTarget} object  The target object to add event listeners to.
    350 * @param {function(*): Promise<*>} func The function whose promise should
    351 *     resolve first.
    352 * @param {string} event The event type to listen for.
    353 * @param {number} num_listeners The number of events to listen for.
    354 * @returns {Promise<*>} The return value of |func|.
    355 */
    356 function assert_promise_resolves_before_event(
    357    object, func, event, num_listeners = 1) {
    358  return assert_promise_event_order_(
    359      ShouldBeFirst.PROMISE_RESOLUTION, object, func, event, num_listeners);
    360 }
    361 
    362 /**
    363 * Asserts that the promise returned by |func| resolves after events of type
    364 * |event| are fired |num_listeners| times on |object|. See
    365 * assert_promise_event_order_ above for more details.
    366 * @param {EventTarget} object  The target object to add event listeners to.
    367 * @param {function(*): Promise<*>} func The function whose promise should
    368 *     resolve first.
    369 * @param {string} event The event type to listen for.
    370 * @param {number} num_listeners The number of events to listen for.
    371 * @returns {Promise<*>} The return value of |func|.
    372 */
    373 function assert_promise_resolves_after_event(
    374    object, func, event, num_listeners = 1) {
    375  return assert_promise_event_order_(
    376      ShouldBeFirst.EVENT, object, func, event, num_listeners);
    377 }
    378 
    379 /**
    380 * Returns a promise that resolves after 100ms unless the event is fired on
    381 * the object in which case the promise rejects.
    382 * @param {EventTarget} object The target object to listen for events.
    383 * @param {string} event_name The event type to listen for.
    384 * @returns {Promise<void>} Resolves if no events were fired.
    385 */
    386 function assert_no_events(object, event_name) {
    387  return new Promise((resolve) => {
    388    let event_listener = (e) => {
    389      object.removeEventListener(event_name, event_listener);
    390      assert_unreached('Object should not fire an event.');
    391    };
    392    object.addEventListener(event_name, event_listener);
    393    // TODO: Remove timeout.
    394    // http://crbug.com/543884
    395    step_timeout(() => {
    396      object.removeEventListener(event_name, event_listener);
    397      resolve();
    398    }, 100);
    399  });
    400 }
    401 
    402 /**
    403 * Asserts that |properties| contains the same properties in
    404 * |expected_properties| with equivalent values.
    405 * @param {object} properties Actual object to compare.
    406 * @param {object} expected_properties Expected object to compare with.
    407 */
    408 function assert_properties_equal(properties, expected_properties) {
    409  for (let key in expected_properties) {
    410    assert_equals(properties[key], expected_properties[key]);
    411  }
    412 }
    413 
    414 /**
    415 * Asserts that |data_map| contains |expected_key|, and that the uint8 values
    416 * for |expected_key| matches |expected_value|.
    417 */
    418 function assert_data_maps_equal(data_map, expected_key, expected_value) {
    419  assert_true(data_map.has(expected_key));
    420 
    421  const value = new Uint8Array(data_map.get(expected_key).buffer);
    422  assert_equals(value.length, expected_value.length);
    423  for (let i = 0; i < value.length; ++i) {
    424    assert_equals(value[i], expected_value[i]);
    425  }
    426 }