diff options
Diffstat (limited to 'testing/web-platform/tests/generic-sensor/resources')
-rw-r--r-- | testing/web-platform/tests/generic-sensor/resources/generic-sensor-helpers.js | 179 | ||||
-rw-r--r-- | testing/web-platform/tests/generic-sensor/resources/iframe_sensor_handler.html | 91 |
2 files changed, 270 insertions, 0 deletions
diff --git a/testing/web-platform/tests/generic-sensor/resources/generic-sensor-helpers.js b/testing/web-platform/tests/generic-sensor/resources/generic-sensor-helpers.js new file mode 100644 index 0000000000..146f4292ad --- /dev/null +++ b/testing/web-platform/tests/generic-sensor/resources/generic-sensor-helpers.js @@ -0,0 +1,179 @@ +'use strict'; + +// If two doubles differ by less than this amount, we can consider them +// to be effectively equal. +const kEpsilon = 1e-8; + +class RingBuffer { + constructor(data) { + if (!Array.isArray(data)) { + throw new TypeError('`data` must be an array.'); + } + + this.bufferPosition_ = 0; + this.data_ = Array.from(data); + } + + get data() { + return Array.from(this.data_); + } + + next() { + const value = this.data_[this.bufferPosition_]; + this.bufferPosition_ = (this.bufferPosition_ + 1) % this.data_.length; + return {done: false, value: value}; + } + + value() { + return this.data_[this.bufferPosition_]; + } + + [Symbol.iterator]() { + return this; + } + + reset() { + this.bufferPosition_ = 0; + } +}; + +// Calls test_driver.update_virtual_sensor() until it results in a "reading" +// event. It waits |timeoutInMs| before considering that an event has not been +// delivered. +async function update_virtual_sensor_until_reading( + t, readings, readingPromise, testDriverName, timeoutInMs) { + while (true) { + await test_driver.update_virtual_sensor( + testDriverName, readings.next().value); + const value = await Promise.race([ + new Promise( + resolve => {t.step_timeout(() => resolve('TIMEOUT'), timeoutInMs)}), + readingPromise, + ]); + if (value !== 'TIMEOUT') { + break; + } + } +} + +// This could be turned into a t.step_wait() call once +// https://github.com/web-platform-tests/wpt/pull/34289 is merged. +async function wait_for_virtual_sensor_state(testDriverName, predicate) { + const result = + await test_driver.get_virtual_sensor_information(testDriverName); + if (!predicate(result)) { + await wait_for_virtual_sensor_state(testDriverName, predicate); + } +} + +function validate_sensor_data(sensorData) { + if (!('sensorName' in sensorData)) { + throw new TypeError('sensorData.sensorName is missing'); + } + if (!('permissionName' in sensorData)) { + throw new TypeError('sensorData.permissionName is missing'); + } + if (!('testDriverName' in sensorData)) { + throw new TypeError('sensorData.testDriverName is missing'); + } + if (sensorData.featurePolicyNames !== undefined && + !Array.isArray(sensorData.featurePolicyNames)) { + throw new TypeError('sensorData.featurePolicyNames must be an array'); + } +} + +function validate_reading_data(readingData) { + if (!Array.isArray(readingData.readings)) { + throw new TypeError('readingData.readings must be an array.'); + } + if (!Array.isArray(readingData.expectedReadings)) { + throw new TypeError('readingData.expectedReadings must be an array.'); + } + if (readingData.readings.length < readingData.expectedReadings.length) { + throw new TypeError( + 'readingData.readings\' length must be bigger than ' + + 'or equal to readingData.expectedReadings\' length.'); + } + if (readingData.expectedRemappedReadings && + !Array.isArray(readingData.expectedRemappedReadings)) { + throw new TypeError( + 'readingData.expectedRemappedReadings must be an ' + + 'array.'); + } + if (readingData.expectedRemappedReadings && + readingData.expectedReadings.length != + readingData.expectedRemappedReadings.length) { + throw new TypeError( + 'readingData.expectedReadings and ' + + 'readingData.expectedRemappedReadings must have the same ' + + 'length.'); + } +} + +function get_sensor_reading_properties(sensor) { + const className = sensor[Symbol.toStringTag]; + if ([ + 'Accelerometer', 'GravitySensor', 'Gyroscope', + 'LinearAccelerationSensor', 'Magnetometer', 'ProximitySensor' + ].includes(className)) { + return ['x', 'y', 'z']; + } else if (className == 'AmbientLightSensor') { + return ['illuminance']; + } else if ([ + 'AbsoluteOrientationSensor', 'RelativeOrientationSensor' + ].includes(className)) { + return ['quaternion']; + } else { + throw new TypeError(`Unexpected sensor '${className}'`); + } +} + +// Checks that `sensor` and `expectedSensorLike` have the same properties +// (except for timestamp) and they have the same values. +// +// Options allows configuring some aspects of the comparison: +// - ignoreTimestamps (boolean): If true, `sensor` and `expectedSensorLike`'s +// "timestamp" attribute will not be compared. If `expectedSensorLike` does +// not have a "timestamp" attribute, the values will not be compared either. +// This is particularly useful when comparing sensor objects from different +// origins (and consequently different time origins). +function assert_sensor_reading_equals( + sensor, expectedSensorLike, options = {}) { + for (const prop of get_sensor_reading_properties(sensor)) { + assert_true( + prop in expectedSensorLike, + `expectedSensorLike must have a property called '${prop}'`); + if (Array.isArray(sensor[prop])) + assert_array_approx_equals( + sensor[prop], expectedSensorLike[prop], kEpsilon); + else + assert_approx_equals(sensor[prop], expectedSensorLike[prop], kEpsilon); + } + assert_not_equals(sensor.timestamp, null); + + if ('timestamp' in expectedSensorLike && !options.ignoreTimestamps) { + assert_equals( + sensor.timestamp, expectedSensorLike.timestamp, + 'Sensor timestamps must be equal'); + } +} + +function assert_sensor_reading_is_null(sensor) { + for (const prop of get_sensor_reading_properties(sensor)) { + assert_equals(sensor[prop], null); + } + assert_equals(sensor.timestamp, null); +} + +function serialize_sensor_data(sensor) { + const sensorData = {}; + for (const property of get_sensor_reading_properties(sensor)) { + sensorData[property] = sensor[property]; + } + sensorData['timestamp'] = sensor.timestamp; + + // Note that this is not serialized by postMessage(). + sensorData[Symbol.toStringTag] = sensor[Symbol.toStringTag]; + + return sensorData; +} diff --git a/testing/web-platform/tests/generic-sensor/resources/iframe_sensor_handler.html b/testing/web-platform/tests/generic-sensor/resources/iframe_sensor_handler.html new file mode 100644 index 0000000000..80cdcf7c0d --- /dev/null +++ b/testing/web-platform/tests/generic-sensor/resources/iframe_sensor_handler.html @@ -0,0 +1,91 @@ +<!DOCTYPE html> +<meta charset="utf-8"> +<title>cross-origin iframe sensor tester</title> +<script src="/resources/testdriver.js"></script> +<script src="/resources/testdriver-vendor.js"></script> +<script src="/generic-sensor/resources/generic-sensor-helpers.js"></script> +<script> + let sensor = null; + + test_driver.set_test_context(window.parent); + + // This function is defined separately so that it is added only once + // regardless of how many times the 'start_sensor' command is received. + function sensorReadingEventHandler() { + window.parent.postMessage( + { + eventName: 'reading', + serializedSensor: serialize_sensor_data(sensor), + }, '*'); + } + + async function messageHandler(e) { + switch (e.data.command) { + case 'create_sensor': + if (!sensor) { + const { sensorName, permissionName } = e.data.sensorData; + // TODO(https://github.com/w3c/permissions/issues/419): This does not + // work as expected: due to the set_test_context() call above, this + // call goes through the top-level frame, which has a different + // origin in cross-origin tests, meaning that cross-origin tests only + // really work when permissions are granted by default. This can only + // be fixed by testdriver.js allowing set_permission() to specify a + // different origin. + await test_driver.set_permission({ name: permissionName }, 'granted'); + sensor = new self[sensorName](); + } + return Promise.resolve(); + + case 'start_sensor': + return new Promise((resolve, reject) => { + // This event listener is different from the ones below, as it is + // supposed to be used together with IframeSensorReadingEventWatcher. + // It sends a message whenever there is an event, and window.parent + // decides whether it was expected or not. It is the only way to have + // something akin to EventWatcher in a cross-origin iframe. + sensor.addEventListener('reading', sensorReadingEventHandler); + + sensor.addEventListener('activate', () => { + resolve(); + }, { once: true }); + sensor.addEventListener('error', e => { + reject(`${e.error.name}: ${e.error.message}`); + }, { once: true }); + sensor.start(); + }); + + case 'has_reading': + return Promise.resolve(sensor.hasReading); + + case 'stop_sensor': + if (sensor) { + sensor.stop(); + } + return Promise.resolve(); + + default: + return Promise.reject(`unknown command "${e.data.command}"`); + } + } + + window.onmessage = async (e) => { + // The call to test_driver.set_context() above makes messages other than + // those we are specifically waiting for to be delivered too. Ignore those + // here. + if (!e.data.command) { + return; + } + + try { + test_driver.message_test({ + command: e.data.command, + result: await messageHandler(e), + }); + } catch (error) { + test_driver.message_test({ + command: e.data.command, + error, + }); + } + } +</script> |