summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/generic-sensor/resources
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/generic-sensor/resources')
-rw-r--r--testing/web-platform/tests/generic-sensor/resources/generic-sensor-helpers.js179
-rw-r--r--testing/web-platform/tests/generic-sensor/resources/iframe_sensor_handler.html91
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>