tor-browser

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

generic-sensor-iframe-tests.sub.js (14198B)


      1 function send_message_to_iframe(iframe, message) {
      2  return new Promise((resolve, reject) => {
      3    window.addEventListener('message', (e) => {
      4      // The usage of test_driver.set_test_context() in
      5      // iframe_sensor_handler.html causes unrelated messages to be sent as
      6      // well. We just need to ignore them here.
      7      if (!e.data.command) {
      8        return;
      9      }
     10 
     11      if (e.data.command !== message.command) {
     12        reject(`Expected reply with command '${message.command}', got '${
     13            e.data.command}' instead`);
     14        return;
     15      }
     16      if (e.data.error) {
     17        reject(e.data.error);
     18        return;
     19      }
     20      resolve(e.data.result);
     21    });
     22    iframe.contentWindow.postMessage(message, '*');
     23  });
     24 }
     25 
     26 function run_generic_sensor_iframe_tests(sensorData, readingData) {
     27  validate_sensor_data(sensorData);
     28  validate_reading_data(readingData);
     29 
     30  const {sensorName, permissionName, testDriverName} = sensorData;
     31  const sensorType = self[sensorName];
     32  const featurePolicies = get_feature_policies_for_sensor(sensorName);
     33 
     34  // When comparing timestamps in the tests below, we need to account for small
     35  // deviations coming from the way time is coarsened according to the High
     36  // Resolution Time specification, even more so when we need to translate
     37  // timestamps from different documents with different time origins.
     38  // 0.5 is 500 microseconds, which is acceptable enough given that even a high
     39  // sensor frequency beyond what is usually allowed like 100Hz has a period
     40  // much larger than 0.5ms.
     41  const ALLOWED_JITTER_IN_MS = 0.5;
     42 
     43  function sensor_test(func, name, properties) {
     44    promise_test(async t => {
     45      assert_implements(sensorName in self, `${sensorName} is not supported.`);
     46      const readings = new RingBuffer(readingData.readings);
     47      return func(t, readings);
     48    }, name, properties);
     49  }
     50 
     51  promise_setup(async () => {
     52    // Ensure window's document starts with focus so that it can receive data.
     53    await test_driver.click(document.documentElement);
     54  });
     55 
     56  sensor_test(async (t, readings) => {
     57    // This is a specialized EventWatcher that works with a sensor inside a
     58    // cross-origin iframe. We cannot manipulate the sensor object there
     59    // directly from this frame, so we need the iframe to send us a message
     60    // when the "reading" event is fired, and we decide whether we were
     61    // expecting for it or not. This should be instantiated early in the test
     62    // to catch as many unexpected events as possible.
     63    class IframeSensorReadingEventWatcher {
     64      constructor(test_obj) {
     65        this.resolve_ = null;
     66 
     67        window.onmessage = test_obj.step_func((ev) => {
     68          // Unrelated message, ignore.
     69          if (!ev.data.eventName) {
     70            return;
     71          }
     72 
     73          assert_equals(
     74              ev.data.eventName, 'reading', 'Expecting a "reading" event');
     75          assert_true(
     76              !!this.resolve_,
     77              'Received "reading" event from iframe but was not expecting one');
     78          const resolveFunc = this.resolve_;
     79          this.resolve_ = null;
     80          resolveFunc(ev.data.serializedSensor);
     81        });
     82      }
     83 
     84      wait_for_reading() {
     85        return new Promise(resolve => {
     86          this.resolve_ = resolve;
     87        });
     88      }
     89    };
     90 
     91    // Create main frame sensor.
     92    await test_driver.bidi.permissions.set_permission(
     93        {descriptor: {name: permissionName}, state: 'granted'});
     94    await test_driver.create_virtual_sensor(testDriverName);
     95    const sensor = new sensorType();
     96    t.add_cleanup(async () => {
     97      sensor.stop();
     98      await test_driver.remove_virtual_sensor(testDriverName);
     99    });
    100    const sensorWatcher =
    101        new EventWatcher(t, sensor, ['activate', 'reading', 'error']);
    102 
    103    // Create cross-origin iframe and a sensor inside it.
    104    const iframe = document.createElement('iframe');
    105    iframe.allow = featurePolicies.join(';') + '; focus-without-user-activation;';
    106    iframe.src =
    107        'https://{{domains[www1]}}:{{ports[https][0]}}/generic-sensor/resources/iframe_sensor_handler.html';
    108    const iframeLoadWatcher = new EventWatcher(t, iframe, 'load');
    109    document.body.appendChild(iframe);
    110    t.add_cleanup(async () => {
    111      await send_message_to_iframe(iframe, {command: 'stop_sensor'});
    112      iframe.parentNode.removeChild(iframe);
    113    });
    114    await iframeLoadWatcher.wait_for('load');
    115    const iframeSensorWatcher = new IframeSensorReadingEventWatcher(t);
    116    await send_message_to_iframe(
    117        iframe, {command: 'create_sensor', sensorData});
    118 
    119    // Start the test by focusing the main frame. It is already focused by
    120    // default, but this makes the test easier to follow.
    121    // When the main frame is focused, it sensor is expected to fire "reading"
    122    // events and provide access to new reading values while the sensor in the
    123    // cross-origin iframe is not.
    124    window.focus();
    125 
    126    // Start both sensors. They should both have the same state: active, but no
    127    // readings have been provided to them yet.
    128    await send_message_to_iframe(iframe, {command: 'start_sensor'});
    129    sensor.start();
    130    await sensorWatcher.wait_for('activate');
    131    assert_false(
    132        await send_message_to_iframe(iframe, {command: 'has_reading'}));
    133    assert_false(sensor.hasReading);
    134 
    135    // We store `reading` here because we want to make sure the very same
    136    // value is accepted later.
    137    const reading = readings.next().value;
    138    await Promise.all([
    139      sensorWatcher.wait_for('reading'),
    140      test_driver.update_virtual_sensor(testDriverName, reading),
    141      // Since we do not wait for the iframe sensor's "reading" event, it could
    142      // arguably be delivered later. There are enough async calls happening
    143      // that IframeSensorReadingEventWatcher would end up catching it and
    144      // throwing an error.
    145    ]);
    146    assert_true(sensor.hasReading);
    147    assert_false(
    148        await send_message_to_iframe(iframe, {command: 'has_reading'}));
    149 
    150    // Save sensor data for later before the sensor is stopped.
    151    const savedMainFrameSensorReadings = serialize_sensor_data(sensor);
    152 
    153    sensor.stop();
    154    await send_message_to_iframe(iframe, {command: 'stop_sensor'});
    155 
    156    // The sensors are stopped; queue the same reading. The virtual sensor
    157    // would send it anyway, but this update changes its timestamp.
    158    await test_driver.update_virtual_sensor(testDriverName, reading);
    159 
    160    // Now focus the cross-origin iframe. The situation should be the opposite:
    161    // the sensor in the main frame should not fire any "reading" events or
    162    // provide access to updated readings, but the sensor in the iframe should.
    163    iframe.contentWindow.focus();
    164 
    165    // Start both sensors. Only the iframe sensor should receive a reading
    166    // event and contain readings.
    167    sensor.start();
    168    await sensorWatcher.wait_for('activate');
    169    await send_message_to_iframe(iframe, {command: 'start_sensor'});
    170    const serializedIframeSensor = await iframeSensorWatcher.wait_for_reading();
    171    assert_true(await send_message_to_iframe(iframe, {command: 'has_reading'}));
    172    assert_false(sensor.hasReading);
    173 
    174    assert_sensor_reading_is_null(sensor);
    175 
    176    assert_sensor_reading_equals(
    177        savedMainFrameSensorReadings, serializedIframeSensor,
    178        {ignoreTimestamps: true});
    179 
    180    // We could check that serializedIframeSensor.timestamp (adjusted to this
    181    // frame by adding the iframe's timeOrigin and substracting
    182    // performance.timeOrigin) is greater than
    183    // savedMainFrameSensorReadings.timestamp (or other timestamps prior to the
    184    // last test_driver.update_virtual_sensor() call), but this is surprisingly
    185    // tricky and flaky due to the fact that we are using timestamps from
    186    // cross-origin frames.
    187    //
    188    // On Chrome on Windows (M120 at the time of writing), for example, the
    189    // difference between timeOrigin values is sometimes off by more than 10ms
    190    // from the real difference, and allowing for this much jitter makes the
    191    // test not test something meaningful.
    192  }, `${sensorName}: unfocused sensors in cross-origin frames are not updated`);
    193 
    194  sensor_test(async (t, readings) => {
    195    // Create main frame sensor.
    196    await test_driver.bidi.permissions.set_permission(
    197        {descriptor: {name: permissionName}, state: 'granted'});
    198    await test_driver.create_virtual_sensor(testDriverName);
    199    const sensor = new sensorType();
    200    t.add_cleanup(async () => {
    201      sensor.stop();
    202      await test_driver.remove_virtual_sensor(testDriverName);
    203    });
    204    const sensorWatcher =
    205        new EventWatcher(t, sensor, ['activate', 'reading', 'error']);
    206 
    207    // Create same-origin iframe and a sensor inside it.
    208    const iframe = document.createElement('iframe');
    209    iframe.allow = featurePolicies.join(';') + ';';
    210    iframe.src = 'https://{{host}}:{{ports[https][0]}}/resources/blank.html';
    211    // Create sensor inside same-origin nested browsing context.
    212    const iframeLoadWatcher = new EventWatcher(t, iframe, 'load');
    213    document.body.appendChild(iframe);
    214    t.add_cleanup(() => {
    215      if (iframeSensor) {
    216        iframeSensor.stop();
    217      }
    218      iframe.parentNode.removeChild(iframe);
    219    });
    220    await iframeLoadWatcher.wait_for('load');
    221    // We deliberately create the sensor here instead of using
    222    // send_messge_to_iframe() because this is a same-origin iframe, and we can
    223    // therefore use EventWatcher() to wait for "reading" events a lot more
    224    // easily.
    225    const iframeSensor = new iframe.contentWindow[sensorName]();
    226    const iframeSensorWatcher =
    227        new EventWatcher(t, iframeSensor, ['activate', 'error', 'reading']);
    228 
    229    // Focus a different same-origin window each time and check that everything
    230    // works the same.
    231    for (const windowObject of [window, iframe.contentWindow]) {
    232      await test_driver.update_virtual_sensor(
    233          testDriverName, readings.next().value);
    234 
    235      windowObject.focus();
    236 
    237      iframeSensor.start();
    238      sensor.start();
    239 
    240      await Promise.all([
    241        iframeSensorWatcher.wait_for(['activate', 'reading']),
    242        sensorWatcher.wait_for(['activate', 'reading'])
    243      ]);
    244 
    245      assert_greater_than(
    246          iframe.contentWindow.performance.timeOrigin, performance.timeOrigin,
    247          'iframe\'s time origin must be higher than the main window\'s');
    248 
    249      // Check that the timestamps are similar enough to indicate that this is
    250      // the same reading that was delivered to both sensors.
    251      // The values are not identical due to how high resolution time is
    252      // coarsened.
    253      const translatedIframeSensorTimestamp = iframeSensor.timestamp +
    254          iframe.contentWindow.performance.timeOrigin - performance.timeOrigin;
    255      assert_approx_equals(
    256          translatedIframeSensorTimestamp, sensor.timestamp,
    257          ALLOWED_JITTER_IN_MS);
    258 
    259      // Do not compare timestamps here because of the reasons above.
    260      assert_sensor_reading_equals(
    261          sensor, iframeSensor, {ignoreTimestamps: true});
    262 
    263      // Stop all sensors so we can use the same value in `reading` on every
    264      // loop iteration.
    265      iframeSensor.stop();
    266      sensor.stop();
    267    }
    268  }, `${sensorName}: sensors in same-origin frames are updated if one of the frames is focused`);
    269 
    270  promise_test(async t => {
    271    assert_implements(sensorName in self, `${sensorName} is not supported.`);
    272    const iframe = document.createElement('iframe');
    273    iframe.allow = featurePolicies.join(';') + ';';
    274    iframe.src =
    275        'https://{{host}}:{{ports[https][0]}}/generic-sensor/resources/iframe_sensor_handler.html';
    276 
    277    const iframeLoadWatcher = new EventWatcher(t, iframe, 'load');
    278    document.body.appendChild(iframe);
    279    await iframeLoadWatcher.wait_for('load');
    280 
    281    // Create sensor in the iframe.
    282    await test_driver.bidi.permissions.set_permission(
    283        {descriptor: {name: permissionName}, state: 'granted'});
    284    await test_driver.create_virtual_sensor(testDriverName);
    285    iframe.contentWindow.focus();
    286    const iframeSensor = new iframe.contentWindow[sensorName]();
    287    t.add_cleanup(async () => {
    288      iframeSensor.stop();
    289      await test_driver.remove_virtual_sensor(testDriverName);
    290    });
    291    const sensorWatcher = new EventWatcher(t, iframeSensor, ['activate']);
    292    iframeSensor.start();
    293    await sensorWatcher.wait_for('activate');
    294 
    295    // Remove iframe from main document and change focus. When focus changes,
    296    // we need to determine whether a sensor must have its execution suspended
    297    // or resumed (section 4.2.3, "Focused Area" of the Generic Sensor API
    298    // spec). In Blink, this involves querying a frame, which might no longer
    299    // exist at the time of the check.
    300    iframe.parentNode.removeChild(iframe);
    301    window.focus();
    302  }, `${sensorName}: losing a document's frame with an active sensor does not crash`);
    303 
    304  promise_test(async t => {
    305    assert_implements(sensorName in self, `${sensorName} is not supported.`);
    306    const iframe = document.createElement('iframe');
    307    iframe.allow = featurePolicies.join(';') + ';';
    308    iframe.src =
    309        'https://{{host}}:{{ports[https][0]}}/generic-sensor/resources/iframe_sensor_handler.html';
    310 
    311    const iframeLoadWatcher = new EventWatcher(t, iframe, 'load');
    312    document.body.appendChild(iframe);
    313    await iframeLoadWatcher.wait_for('load');
    314 
    315    // Create sensor in the iframe.
    316    await test_driver.bidi.permissions.set_permission(
    317        {descriptor: {name: permissionName}, state: 'granted'});
    318    await test_driver.create_virtual_sensor(testDriverName);
    319    const iframeSensor = new iframe.contentWindow[sensorName]();
    320    t.add_cleanup(async () => {
    321      iframeSensor.stop();
    322      await test_driver.remove_virtual_sensor(testDriverName);
    323    });
    324    assert_not_equals(iframeSensor, null);
    325 
    326    // Remove iframe from main document. |iframeSensor| no longer has a
    327    // non-null browsing context. Calling start() should probably throw an
    328    // error when called from a non-fully active document, but that depends on
    329    // https://github.com/w3c/sensors/issues/415
    330    iframe.parentNode.removeChild(iframe);
    331    iframeSensor.start();
    332  }, `${sensorName}: calling start() in a non-fully active document does not crash`);
    333 }