diff --git a/accelerometer/Accelerometer-iframe-access.https.html b/accelerometer/Accelerometer-iframe-access.https.html index bec1705780f53e..56005696a7f5fb 100644 --- a/accelerometer/Accelerometer-iframe-access.https.html +++ b/accelerometer/Accelerometer-iframe-access.https.html @@ -6,12 +6,15 @@ + + +
diff --git a/accelerometer/Accelerometer.https.html b/accelerometer/Accelerometer.https.html index 0db87d2efc3dd1..d422fef7264a6e 100644 --- a/accelerometer/Accelerometer.https.html +++ b/accelerometer/Accelerometer.https.html @@ -6,28 +6,12 @@ + + + + diff --git a/accelerometer/GravitySensor.https.html b/accelerometer/GravitySensor.https.html index 6fb21e5572813e..0f98f3e00dec40 100644 --- a/accelerometer/GravitySensor.https.html +++ b/accelerometer/GravitySensor.https.html @@ -6,28 +6,16 @@ + + + + diff --git a/accelerometer/LinearAccelerationSensor.https.html b/accelerometer/LinearAccelerationSensor.https.html index 8dd3446f409169..91035cc9628bad 100644 --- a/accelerometer/LinearAccelerationSensor.https.html +++ b/accelerometer/LinearAccelerationSensor.https.html @@ -6,28 +6,16 @@ + + + + diff --git a/accelerometer/resources/sensor-data.js b/accelerometer/resources/sensor-data.js new file mode 100644 index 00000000000000..6f56cfdbeb6dff --- /dev/null +++ b/accelerometer/resources/sensor-data.js @@ -0,0 +1,34 @@ +'use strict'; + +const kAccelerometerSensorData = { + sensorName: 'Accelerometer', + permissionName: 'accelerometer', + testDriverName: 'accelerometer', + featurePolicyNames: ['accelerometer'] +}; + +const kGravitySensorData = { + sensorName: 'GravitySensor', + permissionName: 'accelerometer', + testDriverName: 'gravity', + featurePolicyNames: ['accelerometer'] +}; + +const kLinearAccelerationSensorData = { + sensorName: 'LinearAccelerationSensor', + permissionName: 'accelerometer', + testDriverName: 'linear-acceleration', + featurePolicyNames: ['accelerometer'] +}; + +const kAccelerometerReadings = { + readings: [ + { x: 1.12345, y: 2.12345, z: 3.12345 } + ], + expectedReadings: [ + { x: 1.1, y: 2.1, z: 3.1 } + ], + expectedRemappedReadings: [ + { x: -2.1, y: 1.1, z: 3.1 } + ] +}; diff --git a/ambient-light/AmbientLightSensor-iframe-access.https.html b/ambient-light/AmbientLightSensor-iframe-access.https.html index 5fedd5fb7a9b18..765c1bee1f8189 100644 --- a/ambient-light/AmbientLightSensor-iframe-access.https.html +++ b/ambient-light/AmbientLightSensor-iframe-access.https.html @@ -5,10 +5,13 @@ + + +
diff --git a/ambient-light/AmbientLightSensor.https.html b/ambient-light/AmbientLightSensor.https.html index a8c65e4d3faf71..f2ed655082ed38 100644 --- a/ambient-light/AmbientLightSensor.https.html +++ b/ambient-light/AmbientLightSensor.https.html @@ -6,58 +6,12 @@ + + + + diff --git a/ambient-light/resources/sensor-data.js b/ambient-light/resources/sensor-data.js new file mode 100644 index 00000000000000..c1f7bd5ca0f7fb --- /dev/null +++ b/ambient-light/resources/sensor-data.js @@ -0,0 +1,47 @@ +'use strict'; + +const kSensorData = { + sensorName: 'AmbientLightSensor', + permissionName: 'ambient-light-sensor', + testDriverName: 'ambient-light', + featurePolicyNames: ['ambient-light-sensor'] +}; + +const kReadings = { + readings: [ + // Readings are selected so that illuminance significance check causes + // the following to happen: + // 1. First two values test situation when two values would be rounded + // to same value. As the second value would be rounded to same value + // as first it won't trigger reading event. + // 2. New value is set to 24. And test checks it is correctly rounded to + // 0. + // 3. New reading is attempted to set to 35. + // 4. Value is read from sensor and compared new reading. But as new + // reading was not significantly different compared to initial, for + // privacy reasons, service returns the initial value. + // 5. New value is set to 49. And test checks it is correctly rounded to + // 50. New value is allowed as it is significantly different compared + // to old value (24). + // 6. New reading is attempted to set to 35. + // 7. Value is read from sensor and compared new reading. But as new + // reading was not significantly different compared to initial, for + // privacy reasons, service returns the initial value. + // 8. New value is set to 23. And test checks it is correctly rounded to + // 0. New value is allowed as it is significantly different compared + // to old value (49). + // + // Note: Readings and expectedReadings wraps around correctly as next + // value would be 150 (output from 127). + { illuminance: 127 }, { illuminance: 165 }, { illuminance: 24 }, { + illuminance: + 35 + }, { illuminance: 49 }, { illuminance: 35 }, { illuminance: 23 } + ], + expectedReadings: [ + { illuminance: 150 }, // output from 127 + { illuminance: 0 }, // output from 24 + { illuminance: 50 }, // output from 49 + { illuminance: 0 } // output from 23 + ] +}; diff --git a/generic-sensor/generic-sensor-iframe-tests.sub.js b/generic-sensor/generic-sensor-iframe-tests.sub.js index 7b816c01c745ab..486971db87f802 100644 --- a/generic-sensor/generic-sensor-iframe-tests.sub.js +++ b/generic-sensor/generic-sensor-iframe-tests.sub.js @@ -1,152 +1,316 @@ -function send_message_to_iframe(iframe, message, reply) { - if (reply === undefined) { - reply = 'success'; - } - +function send_message_to_iframe(iframe, message) { return new Promise((resolve, reject) => { window.addEventListener('message', (e) => { + // The usage of test_driver.set_test_context() in + // iframe_sensor_handler.html causes unrelated messages to be sent as + // well. We just need to ignore them here. + if (!e.data.command) { + return; + } + if (e.data.command !== message.command) { - reject(`Expected reply with command '${message.command}', got '${e.data.command}' instead`); + reject(`Expected reply with command '${message.command}', got '${ + e.data.command}' instead`); return; } - if (e.data.result === reply) { - resolve(); - } else { - reject(`Got unexpected reply '${e.data.result}' to command '${message.command}', expected '${reply}'`); + if (e.data.error) { + reject(e.data.error); + return; } - }, { once: true }); + resolve(e.data.result); + }); iframe.contentWindow.postMessage(message, '*'); }); } -function run_generic_sensor_iframe_tests(sensorName) { +function run_generic_sensor_iframe_tests(sensorData, readingData) { + validate_sensor_data(sensorData); + validate_reading_data(readingData); + + const {sensorName, permissionName, testDriverName} = sensorData; const sensorType = self[sensorName]; const featurePolicies = get_feature_policies_for_sensor(sensorName); - sensor_test(async t => { - assert_implements(sensorName in self, `${sensorName} is not supported.`); + // When comparing timestamps in the tests below, we need to account for small + // deviations coming from the way time is coarsened according to the High + // Resolution Time specification, even more so when we need to translate + // timestamps from different documents with different time origins. + // 0.5 is 500 microseconds, which is acceptable enough given that even a high + // sensor frequency beyond what is usually allowed like 100Hz has a period + // much larger than 0.5ms. + const ALLOWED_JITTER_IN_MS = 0.5; + + function sensor_test(func, name, properties) { + promise_test(async t => { + assert_implements(sensorName in self, `${sensorName} is not supported.`); + const readings = new RingBuffer(readingData.readings); + return func(t, readings); + }, name, properties); + } + + sensor_test(async (t, readings) => { + // This is a specialized EventWatcher that works with a sensor inside a + // cross-origin iframe. We cannot manipulate the sensor object there + // directly from this frame, so we need the iframe to send us a message + // when the "reading" event is fired, and we decide whether we were + // expecting for it or not. This should be instantiated early in the test + // to catch as many unexpected events as possible. + class IframeSensorReadingEventWatcher { + constructor(test_obj) { + this.resolve_ = null; + + window.onmessage = test_obj.step_func((ev) => { + // Unrelated message, ignore. + if (!ev.data.eventName) { + return; + } + + assert_equals( + ev.data.eventName, 'reading', 'Expecting a "reading" event'); + assert_true( + !!this.resolve_, + 'Received "reading" event from iframe but was not expecting one'); + const resolveFunc = this.resolve_; + this.resolve_ = null; + resolveFunc(ev.data.sensorData); + }); + } + + wait_for_reading() { + return new Promise(resolve => { + this.resolve_ = resolve; + }); + } + }; + + // Create main frame sensor. + await test_driver.set_permission({name: permissionName}, 'granted'); + await test_driver.create_virtual_sensor(testDriverName); + const sensor = new sensorType(); + t.add_cleanup(async () => { + sensor.stop(); + await test_driver.remove_virtual_sensor(testDriverName); + }); + const sensorWatcher = + new EventWatcher(t, sensor, ['activate', 'reading', 'error']); + + // Create cross-origin iframe and a sensor inside it. const iframe = document.createElement('iframe'); iframe.allow = featurePolicies.join(';') + ';'; - iframe.src = 'https://{{domains[www1]}}:{{ports[https][0]}}/generic-sensor/resources/iframe_sensor_handler.html'; - - // Create sensor inside cross-origin nested browsing context. + iframe.src = + 'https://{{domains[www1]}}:{{ports[https][0]}}/generic-sensor/resources/iframe_sensor_handler.html'; const iframeLoadWatcher = new EventWatcher(t, iframe, 'load'); document.body.appendChild(iframe); t.add_cleanup(async () => { - await send_message_to_iframe(iframe, { command: 'reset_sensor_backend' }); + await send_message_to_iframe(iframe, {command: 'stop_sensor'}); iframe.parentNode.removeChild(iframe); }); await iframeLoadWatcher.wait_for('load'); - await send_message_to_iframe(iframe, {command: 'create_sensor', - type: sensorName}); + const iframeSensorWatcher = new IframeSensorReadingEventWatcher(t); + await send_message_to_iframe( + iframe, {command: 'create_sensor', sensorData}); - // Focus on the main frame and test that sensor receives readings. + // Start the test by focusing the main frame. It is already focused by + // default, but this makes the test easier to follow. + // When the main frame is focused, it sensor is expected to fire "reading" + // events and provide access to new reading values while the sensor in the + // cross-origin iframe is not. window.focus(); - const sensor = new sensorType(); - const sensorWatcher = new EventWatcher(t, sensor, ['reading', 'error']); + + // Start both sensors. They should both have the same state: active, but no + // readings have been provided to them yet. + await send_message_to_iframe(iframe, {command: 'start_sensor'}); sensor.start(); + await sensorWatcher.wait_for('activate'); + assert_false( + await send_message_to_iframe(iframe, {command: 'has_reading'})); + assert_false(sensor.hasReading); + + // We store `reading` here because we want to make sure the very same + // value is accepted later. + const reading = readings.next().value; + const [readingEvent] = await Promise.all([ + sensorWatcher.wait_for('reading'), + test_driver.update_virtual_sensor(testDriverName, reading), + // Since we do not wait for the iframe sensor's "reading" event, it could + // arguably be delivered later. There are enough async calls happening + // that IframeSensorReadingEventWatcher would end up catching it and + // throwing an error. + ]); + assert_true(sensor.hasReading); + assert_false( + await send_message_to_iframe(iframe, {command: 'has_reading'})); - await sensorWatcher.wait_for('reading'); - const cachedTimeStamp = sensor.timestamp; + // Save sensor data for later before the sensor is stopped. + const savedMainFrameSensorReadings = serialize_sensor_data(sensor); + const mainFrameSensorReadingEvent = readingEvent; - // Focus on the cross-origin frame and verify that sensor reading updates in - // the top level browsing context are suspended. + sensor.stop(); + await send_message_to_iframe(iframe, {command: 'stop_sensor'}); + + // Now focus the cross-origin iframe. The situation should be the opposite: + // the sensor in the main frame should not fire any "reading" events or + // provide access to updated readings, but the sensor in the iframe should. iframe.contentWindow.focus(); + + // Start both sensors. They should both have the same state: active, but no + // readings have been provided to them yet. await send_message_to_iframe(iframe, {command: 'start_sensor'}); + sensor.start(); + await sensorWatcher.wait_for('activate'); + assert_false( + await send_message_to_iframe(iframe, {command: 'has_reading'})); + assert_false(sensor.hasReading); - // Focus on the main frame, verify that sensor reading updates are resumed. - window.focus(); - await sensorWatcher.wait_for('reading'); - assert_greater_than(sensor.timestamp, cachedTimeStamp); - sensor.stop(); + const timestampBeforeUpdatingIframeSensor = performance.now(); - // Verify that sensor in cross-origin frame is suspended. - await send_message_to_iframe(iframe, {command: 'is_sensor_suspended'}, true); - }, `${sensorName}: sensor is suspended and resumed when focus traverses from\ - to cross-origin frame`); + const [iframeSensorData] = await Promise.all([ + iframeSensorWatcher.wait_for_reading(), + test_driver.update_virtual_sensor(testDriverName, reading), + ]); + assert_true(await send_message_to_iframe(iframe, {command: 'has_reading'})); + assert_false(sensor.hasReading); - sensor_test(async t => { - assert_implements(sensorName in self, `${sensorName} is not supported.`); + assert_sensor_reading_is_null(sensor); + + assert_sensor_reading_equals( + savedMainFrameSensorReadings, iframeSensorData.serializedSensor, + {ignoreTimestamps: true}); + + assert_less_than( + savedMainFrameSensorReadings.timestamp, + timestampBeforeUpdatingIframeSensor, + 'Sensor in main frame\'s timestamp is less than timestampBeforeUpdatingIframeSensor'); + assert_less_than( + mainFrameSensorReadingEvent.timeStamp, + timestampBeforeUpdatingIframeSensor, + 'Sensor in main frame\'s event\'s timestamp is less than timestampBeforeUpdatingIframeSensor'); + assert_greater_than( + iframeSensorData.timeOrigin, performance.timeOrigin, + 'timeOrigin in iframe is greater than main frame\'s'); + const translatedIframeSensorTimestamp = + iframeSensorData.serializedSensor.timestamp + + iframeSensorData.timeOrigin - performance.timeOrigin; + console.log(`iframeSensorData.timeStamp: ${iframeSensorData.serializedSensor.timestamp}`); + console.log(`timeOrigin delta: ${iframeSensorData.timeOrigin - performance.timeOrigin}`); + console.log(`timestampBeforeUpdatingIframeSensor: ${timestampBeforeUpdatingIframeSensor}`); + console.log(`translatedIframeSensorTimestamp: ${translatedIframeSensorTimestamp}`); + assert_greater_than( + translatedIframeSensorTimestamp, savedMainFrameSensorReadings.timestamp, + 'iframe sensor\'s timestamp is greater than main frame\'s sensor\'s'); + assert_greater_than( + translatedIframeSensorTimestamp, mainFrameSensorReadingEvent.timeStamp, + 'iframe sensor\'s timestamp is greater than main frame\'s event\'s'); + // We need to allow for some jitter because of time coarsening that affects + // the math operations above. + assert_less_than( + timestampBeforeUpdatingIframeSensor, + translatedIframeSensorTimestamp + ALLOWED_JITTER_IN_MS, + 'timestampBeforeUpdatingIframeSensor is less than iframe sensor\'s timestamp'); + }, `${sensorName}: unfocused sensors in cross-origin frames are not updated`); + + sensor_test(async (t, readings) => { + // Create main frame sensor. + await test_driver.set_permission({name: permissionName}, 'granted'); + await test_driver.create_virtual_sensor(testDriverName); + const sensor = new sensorType(); + t.add_cleanup(async () => { + sensor.stop(); + await test_driver.remove_virtual_sensor(testDriverName); + }); + const sensorWatcher = + new EventWatcher(t, sensor, ['activate', 'reading', 'error']); + + // Create same-origin iframe and a sensor inside it. const iframe = document.createElement('iframe'); iframe.allow = featurePolicies.join(';') + ';'; - iframe.src = 'https://{{host}}:{{ports[https][0]}}/generic-sensor/resources/iframe_sensor_handler.html'; - + iframe.src = 'https://{{host}}:{{ports[https][0]}}/resources/blank.html'; // Create sensor inside same-origin nested browsing context. const iframeLoadWatcher = new EventWatcher(t, iframe, 'load'); document.body.appendChild(iframe); - t.add_cleanup(async () => { - await send_message_to_iframe(iframe, { command: 'reset_sensor_backend' }); + t.add_cleanup(() => { + if (iframeSensor) { + iframeSensor.stop(); + } iframe.parentNode.removeChild(iframe); }); await iframeLoadWatcher.wait_for('load'); - await send_message_to_iframe(iframe, {command: 'create_sensor', - type: sensorName}); + // We deliberately create the sensor here instead of using + // send_messge_to_iframe() because this is a same-origin iframe, and we can + // therefore use EventWatcher() to wait for "reading" events a lot more + // easily. + const iframeSensor = new iframe.contentWindow[sensorName](); + const iframeSensorWatcher = + new EventWatcher(t, iframeSensor, ['activate', 'error', 'reading']); - // Focus on main frame and test that sensor receives readings. - window.focus(); - const sensor = new sensorType({ - // generic_sensor_mocks.js uses a default frequency of 5Hz for sensors. - // We deliberately use a higher frequency here to make it easier to spot - // spurious, unexpected 'reading' events caused by the main frame's - // sensor not stopping early enough. - // TODO(rakuco): Create a constant with the 5Hz default frequency instead - // of using magic numbers. - frequency: 15 - }); - const sensorWatcher = new EventWatcher(t, sensor, ['reading', 'error']); - sensor.start(); - await sensorWatcher.wait_for('reading'); - let cachedTimeStamp = sensor.timestamp; - - // Stop sensor in main frame, so that sensorWatcher would not receive - // readings while sensor in iframe is started. Sensors that are active and - // belong to the same-origin context are not suspended automatically when - // focus changes to another same-origin iframe, so if we do not explicitly - // stop them we may receive extra 'reading' events that cause the test to - // fail (see e.g. https://crbug.com/857520). - sensor.stop(); + // Focus a different same-origin window each time and check that everything + // works the same. + for (const windowObject of [window, iframe.contentWindow]) { + windowObject.focus(); - iframe.contentWindow.focus(); - await send_message_to_iframe(iframe, {command: 'start_sensor'}); + iframeSensor.start(); + sensor.start(); + await Promise.all([ + iframeSensorWatcher.wait_for('activate'), + sensorWatcher.wait_for('activate') + ]); - // Start sensor on main frame, verify that readings are updated. - window.focus(); - sensor.start(); - await sensorWatcher.wait_for('reading'); - assert_greater_than(sensor.timestamp, cachedTimeStamp); - cachedTimeStamp = sensor.timestamp; - sensor.stop(); + assert_false(sensor.hasReading); + assert_false(iframeSensor.hasReading); - // Verify that sensor in nested browsing context is not suspended. - await send_message_to_iframe(iframe, {command: 'is_sensor_suspended'}, false); + // We store `reading` here because we want to make sure the very same + // value is accepted later. + const reading = readings.next().value; + await Promise.all([ + test_driver.update_virtual_sensor(testDriverName, reading), + iframeSensorWatcher.wait_for('reading'), + sensorWatcher.wait_for('reading') + ]); - // Verify that sensor in top level browsing context is receiving readings. - iframe.contentWindow.focus(); - sensor.start(); - await sensorWatcher.wait_for('reading'); - assert_greater_than(sensor.timestamp, cachedTimeStamp); - sensor.stop(); - }, `${sensorName}: sensor is not suspended when focus traverses from\ - to same-origin frame`); + assert_greater_than( + iframe.contentWindow.performance.timeOrigin, performance.timeOrigin, + 'iframe\'s time origin must be higher than the main window\'s'); + + // Check that the timestamps are similar enough to indicate that this is + // the same reading that was delivered to both sensors. + // The values are not identical due to how high resolution time is + // coarsened. + const translatedIframeSensorTimestamp = iframeSensor.timestamp + + iframe.contentWindow.performance.timeOrigin - performance.timeOrigin; + assert_approx_equals( + translatedIframeSensorTimestamp, sensor.timestamp, + ALLOWED_JITTER_IN_MS); - sensor_test(async t => { + // Do not compare timestamps here because of the reasons above. + assert_sensor_reading_equals( + sensor, iframeSensor, {ignoreTimestamps: true}); + + // Stop all sensors so we can use the same value in `reading` on every + // loop iteration. + iframeSensor.stop(); + sensor.stop(); + } + }, `${sensorName}: sensors in same-origin frames are updated if one of the frames is focused`); + + promise_test(async t => { assert_implements(sensorName in self, `${sensorName} is not supported.`); const iframe = document.createElement('iframe'); iframe.allow = featurePolicies.join(';') + ';'; - iframe.src = 'https://{{host}}:{{ports[https][0]}}/generic-sensor/resources/iframe_sensor_handler.html'; + iframe.src = + 'https://{{host}}:{{ports[https][0]}}/generic-sensor/resources/iframe_sensor_handler.html'; - // Create sensor in the iframe. const iframeLoadWatcher = new EventWatcher(t, iframe, 'load'); document.body.appendChild(iframe); await iframeLoadWatcher.wait_for('load'); - // This is required for the JS Mojo backend to be initialized in the - // iframe. - await send_message_to_iframe(iframe, {command: 'create_sensor', - type: sensorName}); + + // Create sensor in the iframe. + await test_driver.set_permission({name: permissionName}, 'granted'); + await test_driver.create_virtual_sensor(testDriverName); iframe.contentWindow.focus(); const iframeSensor = new iframe.contentWindow[sensorName](); - t.add_cleanup(() => { + t.add_cleanup(async () => { iframeSensor.stop(); + await test_driver.remove_virtual_sensor(testDriverName); }); const sensorWatcher = new EventWatcher(t, iframeSensor, ['activate']); iframeSensor.start(); @@ -161,24 +325,25 @@ function run_generic_sensor_iframe_tests(sensorName) { window.focus(); }, `${sensorName}: losing a document's frame with an active sensor does not crash`); - sensor_test(async t => { + promise_test(async t => { assert_implements(sensorName in self, `${sensorName} is not supported.`); const iframe = document.createElement('iframe'); iframe.allow = featurePolicies.join(';') + ';'; - iframe.src = 'https://{{host}}:{{ports[https][0]}}/generic-sensor/resources/iframe_sensor_handler.html'; + iframe.src = + 'https://{{host}}:{{ports[https][0]}}/generic-sensor/resources/iframe_sensor_handler.html'; - // Create sensor in the iframe (we do not care whether this is a - // cross-origin nested context in this test). const iframeLoadWatcher = new EventWatcher(t, iframe, 'load'); document.body.appendChild(iframe); await iframeLoadWatcher.wait_for('load'); - // The purpose of this message is to initialize the mock backend in the - // iframe. We are not going to use the sensor created there. - await send_message_to_iframe(iframe, {command: 'create_sensor', - type: sensorName}); - + // Create sensor in the iframe. + await test_driver.set_permission({name: permissionName}, 'granted'); + await test_driver.create_virtual_sensor(testDriverName); const iframeSensor = new iframe.contentWindow[sensorName](); + t.add_cleanup(async () => { + iframeSensor.stop(); + await test_driver.remove_virtual_sensor(testDriverName); + }); assert_not_equals(iframeSensor, null); // Remove iframe from main document. |iframeSensor| no longer has a diff --git a/generic-sensor/generic-sensor-tests.js b/generic-sensor/generic-sensor-tests.js index 3c8f478c541dcd..47ea9e9c4093e9 100644 --- a/generic-sensor/generic-sensor-tests.js +++ b/generic-sensor/generic-sensor-tests.js @@ -14,289 +14,344 @@ // |verificationFunction| is called to verify that a given reading matches a // value in |expectedReadings|. // |featurePolicies| represents |sensorName|'s associated sensor feature name. +function runGenericSensorTests(sensorData, readingData) { + validate_sensor_data(sensorData); + validate_reading_data(readingData); -function runGenericSensorTests(sensorName, - readingData, - verificationFunction, - featurePolicies) { + const {sensorName, permissionName, testDriverName, featurePolicyNames} = + sensorData; const sensorType = self[sensorName]; - function validateReadingFormat(data) { - return Array.isArray(data) && data.every(element => Array.isArray(element)); - } - - const { readings, expectedReadings, expectedRemappedReadings } = readingData; - if (!validateReadingFormat(readings)) { - throw new TypeError('readingData.readings must be an array of arrays.'); - } - if (!validateReadingFormat(expectedReadings)) { - throw new TypeError('readingData.expectedReadings must be an array of ' + - 'arrays.'); - } - if (readings.length < expectedReadings.length) { - throw new TypeError('readingData.readings\' length must be bigger than ' + - 'or equal to readingData.expectedReadings\' length.'); - } - if (expectedRemappedReadings && - !validateReadingFormat(expectedRemappedReadings)) { - throw new TypeError('readingData.expectedRemappedReadings must be an ' + - 'array of arrays.'); - } - if (expectedRemappedReadings && - expectedReadings.length != expectedRemappedReadings.length) { - throw new TypeError('readingData.expectedReadings and ' + - 'readingData.expectedRemappedReadings must have the same ' + - 'length.'); - } + function sensor_test(func, name, properties) { + promise_test(async t => { + assert_implements(sensorName in self, `${sensorName} is not supported.`); - sensor_test(async (t, sensorProvider) => { - assert_implements(sensorName in self, `${sensorName} is not supported.`); - sensorProvider.setGetSensorShouldFail(sensorName, true); - const sensor = new sensorType; - const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]); - sensor.start(); + const readings = new RingBuffer(readingData.readings); + const expectedReadings = new RingBuffer(readingData.expectedReadings); + const expectedRemappedReadings = readingData.expectedRemappedReadings ? + new RingBuffer(readingData.expectedRemappedReadings) : + undefined; - const event = await sensorWatcher.wait_for("error"); + return func(t, readings, expectedReadings, expectedRemappedReadings); + }, name, properties); + } - assert_false(sensor.activated); - assert_equals(event.error.name, 'NotReadableError'); - }, `${sensorName}: Test that onerror is sent when sensor is not supported.`); + sensor_test(async t => { + await test_driver.set_permission({name: permissionName}, 'denied'); - sensor_test(async (t, sensorProvider) => { - assert_implements(sensorName in self, `${sensorName} is not supported.`); - sensorProvider.setPermissionsDenied(sensorName, true); + await test_driver.create_virtual_sensor(testDriverName); const sensor = new sensorType; - const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]); + t.add_cleanup(async () => { + sensor.stop(); + await test_driver.remove_virtual_sensor(testDriverName); + }); + const sensorWatcher = new EventWatcher(t, sensor, ['reading', 'error']); sensor.start(); - const event = await sensorWatcher.wait_for("error"); + const event = await sensorWatcher.wait_for('error'); assert_false(sensor.activated); assert_equals(event.error.name, 'NotAllowedError'); }, `${sensorName}: Test that onerror is sent when permissions are not\ granted.`); - sensor_test(async (t, sensorProvider) => { - assert_implements(sensorName in self, `${sensorName} is not supported.`); - const sensor = new sensorType({frequency: 560}); - const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]); - sensor.start(); + sensor_test(async t => { + await test_driver.set_permission({name: permissionName}, 'granted'); - const mockSensor = await sensorProvider.getCreatedSensor(sensorName); - mockSensor.setStartShouldFail(true); + await test_driver.create_virtual_sensor(testDriverName, {connected: false}); + const sensor = new sensorType; + t.add_cleanup(async () => { + sensor.stop(); + await test_driver.remove_virtual_sensor(testDriverName); + }); + const sensorWatcher = new EventWatcher(t, sensor, ['reading', 'error']); + + sensor.start(); - const event = await sensorWatcher.wait_for("error"); + const event = await sensorWatcher.wait_for('error'); assert_false(sensor.activated); assert_equals(event.error.name, 'NotReadableError'); }, `${sensorName}: Test that onerror is send when start() call has failed.`); - sensor_test(async (t, sensorProvider) => { - assert_implements(sensorName in self, `${sensorName} is not supported.`); + sensor_test(async t => { + await test_driver.set_permission({name: permissionName}, 'granted'); + + await test_driver.create_virtual_sensor(testDriverName); + const sensor = new sensorType({frequency: 560}); - const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]); + t.add_cleanup(async () => { + sensor.stop(); + await test_driver.remove_virtual_sensor(testDriverName); + }); + const sensorWatcher = new EventWatcher(t, sensor, ['activate', 'error']); sensor.start(); - const mockSensor = await sensorProvider.getCreatedSensor(sensorName); - - await sensorWatcher.wait_for("activate"); + await sensorWatcher.wait_for('activate'); + const mockSensorInfo = + await test_driver.get_virtual_sensor_information(testDriverName); - assert_less_than_equal(mockSensor.getSamplingFrequency(), 60); - sensor.stop(); - assert_false(sensor.activated); + assert_less_than_equal(mockSensorInfo.requestedSamplingFrequency, 60); }, `${sensorName}: Test that frequency is capped to allowed maximum.`); - sensor_test(async (t, sensorProvider) => { - assert_implements(sensorName in self, `${sensorName} is not supported.`); + sensor_test(async t => { + await test_driver.set_permission({name: permissionName}, 'granted'); + const maxSupportedFrequency = 5; - sensorProvider.setMaximumSupportedFrequency(maxSupportedFrequency); + await test_driver.create_virtual_sensor( + testDriverName, {maxSamplingFrequency: maxSupportedFrequency}); + const sensor = new sensorType({frequency: 50}); - const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]); + t.add_cleanup(async () => { + sensor.stop(); + await test_driver.remove_virtual_sensor(testDriverName); + }); + const sensorWatcher = new EventWatcher(t, sensor, ['activate', 'error']); sensor.start(); - const mockSensor = await sensorProvider.getCreatedSensor(sensorName); + await sensorWatcher.wait_for('activate'); + const mockSensorInfo = + await test_driver.get_virtual_sensor_information(testDriverName); - await sensorWatcher.wait_for("activate"); - - assert_equals(mockSensor.getSamplingFrequency(), maxSupportedFrequency); - sensor.stop(); - assert_false(sensor.activated); + assert_equals( + mockSensorInfo.requestedSamplingFrequency, maxSupportedFrequency); }, `${sensorName}: Test that frequency is capped to the maximum supported\ frequency.`); - sensor_test(async (t, sensorProvider) => { - assert_implements(sensorName in self, `${sensorName} is not supported.`); + sensor_test(async t => { + await test_driver.set_permission({name: permissionName}, 'granted'); + const minSupportedFrequency = 2; - sensorProvider.setMinimumSupportedFrequency(minSupportedFrequency); + await test_driver.create_virtual_sensor( + testDriverName, {minSamplingFrequency: minSupportedFrequency}); + const sensor = new sensorType({frequency: -1}); - const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]); + t.add_cleanup(async () => { + sensor.stop(); + await test_driver.remove_virtual_sensor(testDriverName); + }); + const sensorWatcher = new EventWatcher(t, sensor, ['activate', 'error']); sensor.start(); - const mockSensor = await sensorProvider.getCreatedSensor(sensorName); + await sensorWatcher.wait_for('activate'); + const mockSensorInfo = + await test_driver.get_virtual_sensor_information(testDriverName); - await sensorWatcher.wait_for("activate"); - - assert_equals(mockSensor.getSamplingFrequency(), minSupportedFrequency); - sensor.stop(); - assert_false(sensor.activated); + assert_equals( + mockSensorInfo.requestedSamplingFrequency, minSupportedFrequency); }, `${sensorName}: Test that frequency is limited to the minimum supported\ frequency.`); - promise_test(async t => { - assert_implements(sensorName in self, `${sensorName} is not supported.`); + sensor_test(async t => { const iframe = document.createElement('iframe'); - iframe.allow = featurePolicies.join(' \'none\'; ') + ' \'none\';'; + iframe.allow = featurePolicyNames.join(' \'none\'; ') + ' \'none\';'; iframe.srcdoc = ' + diff --git a/geolocation-sensor/GeolocationSensor-iframe-access.https.html b/geolocation-sensor/GeolocationSensor-iframe-access.https.html index bb0541de32c374..a3d39e3606f907 100644 --- a/geolocation-sensor/GeolocationSensor-iframe-access.https.html +++ b/geolocation-sensor/GeolocationSensor-iframe-access.https.html @@ -5,10 +5,13 @@ + + +
diff --git a/geolocation-sensor/GeolocationSensor.https.html b/geolocation-sensor/GeolocationSensor.https.html index b71f964b1297f3..3b4b94e5bbbcc7 100644 --- a/geolocation-sensor/GeolocationSensor.https.html +++ b/geolocation-sensor/GeolocationSensor.https.html @@ -5,25 +5,12 @@ + + + + diff --git a/geolocation-sensor/resources/sensor-data.js b/geolocation-sensor/resources/sensor-data.js new file mode 100644 index 00000000000000..5bbad442929bf5 --- /dev/null +++ b/geolocation-sensor/resources/sensor-data.js @@ -0,0 +1,17 @@ +'use strict'; + +const kGeolocationSensorData = { + sensorName: 'GeolocationSensor', + permissionName: 'geolocation', + testDriverName: 'geolocation', + featurePolicyNames: ['geolocation'] +}; + +const kGeolocationReadings = { + readings: [ + [1.12345, 2.12345, 3.12345, 0.95, 0.96, 4.12345, 5.123] + ], + expectedReadings: [ + [1.12345, 2.12345, 3.12345, 0.95, 0.96, 4.12345, 5.123] + ] +}; diff --git a/gyroscope/Gyroscope-iframe-access.https.html b/gyroscope/Gyroscope-iframe-access.https.html index ed0183bef9ad41..c94016764d5176 100644 --- a/gyroscope/Gyroscope-iframe-access.https.html +++ b/gyroscope/Gyroscope-iframe-access.https.html @@ -5,11 +5,14 @@ + + +
diff --git a/gyroscope/Gyroscope.https.html b/gyroscope/Gyroscope.https.html index f85349f5315a27..8aab94693abea6 100644 --- a/gyroscope/Gyroscope.https.html +++ b/gyroscope/Gyroscope.https.html @@ -6,28 +6,16 @@ + + + + diff --git a/gyroscope/resources/sensor-data.js b/gyroscope/resources/sensor-data.js new file mode 100644 index 00000000000000..409ffef9a1750c --- /dev/null +++ b/gyroscope/resources/sensor-data.js @@ -0,0 +1,23 @@ +'use strict'; + +const kGyroscopeSensorData = { + sensorName: 'Gyroscope', + permissionName: 'gyroscope', + testDriverName: 'gyroscope', + featurePolicyNames: ['gyroscope'] +}; + +// Due to the gyroscope input values being rounded using a precision of +// 0.1 deg/sec, the expectedReadings and expectedRemappedReadings contain +// a significant number of decimal places. +const kGyroscopeReadings = { + readings: [ + { x: 1, y: 2, z: 3 } + ], + expectedReadings: [ + { x: 1.00007366, y: 2.00014732, z: 3.00022098 } + ], + expectedRemappedReadings: [ + { x: -2.00014732, y: 1.00007366, z: 3.00022098 } + ] +}; diff --git a/magnetometer/Magnetometer-iframe-access.https.html b/magnetometer/Magnetometer-iframe-access.https.html index 3dc90e3dd7f5ac..7aabd0eb61b3c7 100644 --- a/magnetometer/Magnetometer-iframe-access.https.html +++ b/magnetometer/Magnetometer-iframe-access.https.html @@ -5,12 +5,15 @@ + + +
diff --git a/magnetometer/Magnetometer.https.html b/magnetometer/Magnetometer.https.html index 0cc443784bebb3..6beb534509d79f 100644 --- a/magnetometer/Magnetometer.https.html +++ b/magnetometer/Magnetometer.https.html @@ -6,34 +6,17 @@ + + + + diff --git a/magnetometer/resources/sensor-data.js b/magnetometer/resources/sensor-data.js new file mode 100644 index 00000000000000..32c2b1e2895b48 --- /dev/null +++ b/magnetometer/resources/sensor-data.js @@ -0,0 +1,21 @@ +'use strict'; + +const kMagnetometerSensorData = { + sensorName: 'Magnetometer', + permissionName: 'magnetometer', + testDriverName: 'magnetometer', + featurePolicyNames: ['magnetometer'] +}; + +const kUncalibratedMagnetometerSensorData = { + sensorName: 'UncalibratedMagnetometer', + permissionName: 'magnetometer', + testDriverName: 'magnetometer', + featurePolicyNames: ['magnetometer'] +}; + +const kMagnetometerReadings = { + readings: [{x: -19.2, y: 12.1, z: -44.3}], + expectedReadings: [{x: -19.2, y: 12.1, z: -44.3}], + expectedRemappedReadings: [{x: -12.1, y: -19.2, z: -44.3}] +}; diff --git a/orientation-sensor/AbsoluteOrientationSensor-iframe-access.https.html b/orientation-sensor/AbsoluteOrientationSensor-iframe-access.https.html index e99b5c6365d303..4831b5e719200d 100644 --- a/orientation-sensor/AbsoluteOrientationSensor-iframe-access.https.html +++ b/orientation-sensor/AbsoluteOrientationSensor-iframe-access.https.html @@ -5,11 +5,14 @@ + + +
diff --git a/orientation-sensor/AbsoluteOrientationSensor.https.html b/orientation-sensor/AbsoluteOrientationSensor.https.html index e6f5dd913835a9..4c516da145169e 100644 --- a/orientation-sensor/AbsoluteOrientationSensor.https.html +++ b/orientation-sensor/AbsoluteOrientationSensor.https.html @@ -7,17 +7,19 @@ + + + + diff --git a/orientation-sensor/RelativeOrientationSensor-iframe-access.https.html b/orientation-sensor/RelativeOrientationSensor-iframe-access.https.html index 5d30534841bfc4..fe995e2d1b982d 100644 --- a/orientation-sensor/RelativeOrientationSensor-iframe-access.https.html +++ b/orientation-sensor/RelativeOrientationSensor-iframe-access.https.html @@ -5,11 +5,14 @@ + + +
diff --git a/orientation-sensor/RelativeOrientationSensor.https.html b/orientation-sensor/RelativeOrientationSensor.https.html index 499e7df8ceac24..35dc8c31113637 100644 --- a/orientation-sensor/RelativeOrientationSensor.https.html +++ b/orientation-sensor/RelativeOrientationSensor.https.html @@ -7,17 +7,19 @@ + + + + diff --git a/orientation-sensor/orientation-sensor-tests.js b/orientation-sensor/orientation-sensor-tests.js index 88fd432d470d02..d69fa3e54cdc70 100644 --- a/orientation-sensor/orientation-sensor-tests.js +++ b/orientation-sensor/orientation-sensor-tests.js @@ -1,89 +1,104 @@ 'use strict'; -const kDefaultReading = [ - [ 1, 0, 0, 0 ] // 180 degrees around X axis. -]; -const kRotationMatrix = [1, 0, 0, 0, - 0, -1, 0, 0, - 0, 0, -1, 0, - 0, 0, 0, 1]; -const kReadings = { - readings: kDefaultReading, - expectedReadings: kDefaultReading, - expectedRemappedReadings: [ - // For 'orientation.angle == 270', which is set for tests at - // at SensorProxy::GetScreenOrientationAngle(). - [-0.707107, 0.707107, 0, 0] - ] -}; - -async function checkQuaternion(t, sensorType) { +async function checkQuaternion( + t, sensorType, testDriverName, permissionName, readings) { + await test_driver.set_permission({name: permissionName}, 'granted'); + await test_driver.create_virtual_sensor(testDriverName); const sensor = new sensorType(); - const eventWatcher = new EventWatcher(t, sensor, ["reading", "error"]); + t.add_cleanup(async () => { + sensor.stop(); + await test_driver.remove_virtual_sensor(testDriverName); + }); + const sensorWatcher = + new EventWatcher(t, sensor, ['activate', 'reading', 'error']); sensor.start(); - await eventWatcher.wait_for("reading"); - assert_equals(sensor.quaternion.length, 4); - assert_true(sensor.quaternion instanceof Array); - sensor.stop(); + await sensorWatcher.wait_for('activate'); + await Promise.all([ + test_driver.update_virtual_sensor(testDriverName, readings.next().value), + sensorWatcher.wait_for('reading') + ]); + assert_equals(sensor.quaternion.length, 4, 'Quaternion length must be 4'); + assert_true( + sensor.quaternion instanceof Array, 'Quaternion is must be array'); }; -async function checkPopulateMatrix(t, sensorProvider, sensorType) { +async function checkPopulateMatrix( + t, sensorProvider, sensorType, testDriverName, permissionName, readings) { + await test_driver.set_permission({name: permissionName}, 'granted'); + await test_driver.create_virtual_sensor(testDriverName); const sensor = new sensorType(); - const eventWatcher = new EventWatcher(t, sensor, ["reading", "error"]); + t.add_cleanup(async () => { + sensor.stop(); + await test_driver.remove_virtual_sensor(testDriverName); + }); + const sensorWatcher = + new EventWatcher(t, sensor, ['activate', 'reading', 'error']); // Throws with insufficient buffer space. - assert_throws_js(TypeError, - () => sensor.populateMatrix(new Float32Array(15))); + assert_throws_js( + TypeError, () => sensor.populateMatrix(new Float32Array(15))); // Throws if no orientation data available. - assert_throws_dom('NotReadableError', - () => sensor.populateMatrix(new Float32Array(16))); + assert_throws_dom( + 'NotReadableError', () => sensor.populateMatrix(new Float32Array(16))); // Throws if passed SharedArrayBuffer view. - assert_throws_js(TypeError, - // See https://github.com/whatwg/html/issues/5380 for why not `new SharedArrayBuffer()` - // WebAssembly.Memory's size is in multiples of 64 KiB - () => sensor.populateMatrix(new Float32Array(new WebAssembly.Memory({ shared:true, initial:1, maximum:1 }).buffer))); + assert_throws_js( + TypeError, + // See https://github.com/whatwg/html/issues/5380 for why not `new + // SharedArrayBuffer()` WebAssembly.Memory's size is in multiples of 64KiB + () => sensor.populateMatrix(new Float32Array( + new WebAssembly.Memory({shared: true, initial: 1, maximum: 1}) + .buffer))); sensor.start(); + await sensorWatcher.wait_for('activate'); - const mockSensor = await sensorProvider.getCreatedSensor(sensorType.name); - await mockSensor.setSensorReading(kDefaultReading); - - await eventWatcher.wait_for("reading"); + await Promise.all([ + test_driver.update_virtual_sensor(testDriverName, readings.next().value), + sensorWatcher.wait_for('reading') + ]); // Works for all supported types. const rotationMatrix32 = new Float32Array(16); sensor.populateMatrix(rotationMatrix32); - assert_array_equals(rotationMatrix32, kRotationMatrix); + assert_array_approx_equals(rotationMatrix32, kRotationMatrix, kEpsilon); let rotationMatrix64 = new Float64Array(16); sensor.populateMatrix(rotationMatrix64); - assert_array_equals(rotationMatrix64, kRotationMatrix); + assert_array_approx_equals(rotationMatrix64, kRotationMatrix, kEpsilon); let rotationDOMMatrix = new DOMMatrix(); sensor.populateMatrix(rotationDOMMatrix); - assert_array_equals(rotationDOMMatrix.toFloat64Array(), kRotationMatrix); + assert_array_approx_equals( + rotationDOMMatrix.toFloat64Array(), kRotationMatrix, kEpsilon); // Sets every matrix element. rotationMatrix64.fill(123); sensor.populateMatrix(rotationMatrix64); - assert_array_equals(rotationMatrix64, kRotationMatrix); - - sensor.stop(); + assert_array_approx_equals(rotationMatrix64, kRotationMatrix, kEpsilon); } -function runOrientationSensorTests(sensorName) { +function runOrientationSensorTests(sensorData, readingData) { + validate_sensor_data(sensorData); + validate_reading_data(readingData); + + const {sensorName, permissionName, testDriverName} = sensorData; const sensorType = self[sensorName]; - sensor_test(async t => { - assert_true(sensorName in self); - return checkQuaternion(t, sensorType); + const readings = new RingBuffer(readingData.readings); + + promise_test(async t => { + assert_implements(sensorName in self, `${sensorName} is not supported.`); + return checkQuaternion( + t, sensorType, testDriverName, permissionName, readings); }, `${sensorName}.quaternion return a four-element FrozenArray.`); - sensor_test(async (t, sensorProvider) => { - assert_true(sensorName in self); - return checkPopulateMatrix(t, sensorProvider, sensorType); + promise_test(async (t, sensorProvider) => { + assert_implements(sensorName in self, `${sensorName} is not supported.`); + return checkPopulateMatrix( + t, sensorProvider, sensorType, testDriverName, permissionName, + readings); }, `${sensorName}.populateMatrix() method works correctly.`); } diff --git a/orientation-sensor/resources/sensor-data.js b/orientation-sensor/resources/sensor-data.js new file mode 100644 index 00000000000000..d0b280fa6c4371 --- /dev/null +++ b/orientation-sensor/resources/sensor-data.js @@ -0,0 +1,26 @@ +'use strict'; + +const kAbsoluteOrientationSensorData = { + sensorName: 'AbsoluteOrientationSensor', + permissionName: 'accelerometer', + testDriverName: 'absolute-orientation', + featurePolicyNames: ['accelerometer', 'gyroscope', 'magnetometer'] +}; + +const kRelativeOrientationSensorData = { + sensorName: 'RelativeOrientationSensor', + permissionName: 'accelerometer', + testDriverName: 'relative-orientation', + featurePolicyNames: ['accelerometer', 'gyroscope'] +}; + +const kOrientationReadings = { + readings: [{quaternion: [1, 0, 0, 0]}], + expectedReadings: [{quaternion: [1, 0, 0, 0]}], + expectedRemappedReadings: [{quaternion: [-0.70710678, 0.70710678, 0, 0]}] +}; + +const kRotationMatrix = [1, 0, 0, 0, + 0, -1, 0, 0, + 0, 0, -1, 0, + 0, 0, 0, 1];