generic-sensor-helpers.js (5838B)
1 'use strict'; 2 3 // If two doubles differ by less than this amount, we can consider them 4 // to be effectively equal. 5 const kEpsilon = 1e-8; 6 7 class RingBuffer { 8 constructor(data) { 9 if (!Array.isArray(data)) { 10 throw new TypeError('`data` must be an array.'); 11 } 12 13 this.bufferPosition_ = 0; 14 this.data_ = Array.from(data); 15 } 16 17 get data() { 18 return Array.from(this.data_); 19 } 20 21 next() { 22 const value = this.data_[this.bufferPosition_]; 23 this.bufferPosition_ = (this.bufferPosition_ + 1) % this.data_.length; 24 return {done: false, value: value}; 25 } 26 27 value() { 28 return this.data_[this.bufferPosition_]; 29 } 30 31 [Symbol.iterator]() { 32 return this; 33 } 34 35 reset() { 36 this.bufferPosition_ = 0; 37 } 38 }; 39 40 // Calls test_driver.update_virtual_sensor() until it results in a "reading" 41 // event. It waits |timeoutInMs| before considering that an event has not been 42 // delivered. 43 async function update_virtual_sensor_until_reading( 44 t, readings, readingPromise, testDriverName, timeoutInMs) { 45 while (true) { 46 await test_driver.update_virtual_sensor( 47 testDriverName, readings.next().value); 48 const value = await Promise.race([ 49 new Promise( 50 resolve => {t.step_timeout(() => resolve('TIMEOUT'), timeoutInMs)}), 51 readingPromise, 52 ]); 53 if (value !== 'TIMEOUT') { 54 break; 55 } 56 } 57 } 58 59 // This could be turned into a t.step_wait() call once 60 // https://github.com/web-platform-tests/wpt/pull/34289 is merged. 61 async function wait_for_virtual_sensor_state(testDriverName, predicate) { 62 const result = 63 await test_driver.get_virtual_sensor_information(testDriverName); 64 if (!predicate(result)) { 65 await wait_for_virtual_sensor_state(testDriverName, predicate); 66 } 67 } 68 69 function validate_sensor_data(sensorData) { 70 if (!('sensorName' in sensorData)) { 71 throw new TypeError('sensorData.sensorName is missing'); 72 } 73 if (!('permissionName' in sensorData)) { 74 throw new TypeError('sensorData.permissionName is missing'); 75 } 76 if (!('testDriverName' in sensorData)) { 77 throw new TypeError('sensorData.testDriverName is missing'); 78 } 79 if (sensorData.featurePolicyNames !== undefined && 80 !Array.isArray(sensorData.featurePolicyNames)) { 81 throw new TypeError('sensorData.featurePolicyNames must be an array'); 82 } 83 } 84 85 function validate_reading_data(readingData) { 86 if (!Array.isArray(readingData.readings)) { 87 throw new TypeError('readingData.readings must be an array.'); 88 } 89 if (!Array.isArray(readingData.expectedReadings)) { 90 throw new TypeError('readingData.expectedReadings must be an array.'); 91 } 92 if (readingData.readings.length < readingData.expectedReadings.length) { 93 throw new TypeError( 94 'readingData.readings\' length must be bigger than ' + 95 'or equal to readingData.expectedReadings\' length.'); 96 } 97 if (readingData.expectedRemappedReadings && 98 !Array.isArray(readingData.expectedRemappedReadings)) { 99 throw new TypeError( 100 'readingData.expectedRemappedReadings must be an ' + 101 'array.'); 102 } 103 if (readingData.expectedRemappedReadings && 104 readingData.expectedReadings.length != 105 readingData.expectedRemappedReadings.length) { 106 throw new TypeError( 107 'readingData.expectedReadings and ' + 108 'readingData.expectedRemappedReadings must have the same ' + 109 'length.'); 110 } 111 } 112 113 function get_sensor_reading_properties(sensor) { 114 const className = sensor[Symbol.toStringTag]; 115 if ([ 116 'Accelerometer', 'GravitySensor', 'Gyroscope', 117 'LinearAccelerationSensor', 'Magnetometer', 'ProximitySensor' 118 ].includes(className)) { 119 return ['x', 'y', 'z']; 120 } else if (className == 'AmbientLightSensor') { 121 return ['illuminance']; 122 } else if ([ 123 'AbsoluteOrientationSensor', 'RelativeOrientationSensor' 124 ].includes(className)) { 125 return ['quaternion']; 126 } else { 127 throw new TypeError(`Unexpected sensor '${className}'`); 128 } 129 } 130 131 // Checks that `sensor` and `expectedSensorLike` have the same properties 132 // (except for timestamp) and they have the same values. 133 // 134 // Options allows configuring some aspects of the comparison: 135 // - ignoreTimestamps (boolean): If true, `sensor` and `expectedSensorLike`'s 136 // "timestamp" attribute will not be compared. If `expectedSensorLike` does 137 // not have a "timestamp" attribute, the values will not be compared either. 138 // This is particularly useful when comparing sensor objects from different 139 // origins (and consequently different time origins). 140 function assert_sensor_reading_equals( 141 sensor, expectedSensorLike, options = {}) { 142 for (const prop of get_sensor_reading_properties(sensor)) { 143 assert_true( 144 prop in expectedSensorLike, 145 `expectedSensorLike must have a property called '${prop}'`); 146 if (Array.isArray(sensor[prop])) 147 assert_array_approx_equals( 148 sensor[prop], expectedSensorLike[prop], kEpsilon); 149 else 150 assert_approx_equals(sensor[prop], expectedSensorLike[prop], kEpsilon); 151 } 152 assert_not_equals(sensor.timestamp, null); 153 154 if ('timestamp' in expectedSensorLike && !options.ignoreTimestamps) { 155 assert_equals( 156 sensor.timestamp, expectedSensorLike.timestamp, 157 'Sensor timestamps must be equal'); 158 } 159 } 160 161 function assert_sensor_reading_is_null(sensor) { 162 for (const prop of get_sensor_reading_properties(sensor)) { 163 assert_equals(sensor[prop], null); 164 } 165 assert_equals(sensor.timestamp, null); 166 } 167 168 function serialize_sensor_data(sensor) { 169 const sensorData = {}; 170 for (const property of get_sensor_reading_properties(sensor)) { 171 sensorData[property] = sensor[property]; 172 } 173 sensorData['timestamp'] = sensor.timestamp; 174 175 // Note that this is not serialized by postMessage(). 176 sensorData[Symbol.toStringTag] = sensor[Symbol.toStringTag]; 177 178 return sensorData; 179 }