// These tests rely on the User Agent providing an implementation of // platform sensor backends. // // In Chromium-based browsers this implementation is provided by a polyfill // in order to reduce the amount of test-only code shipped to users. To enable // these tests the browser must be run with these options: // // --enable-blink-features=MojoJS,MojoJSTest let loadChromiumResources = Promise.resolve().then(() => { if (!MojoInterfaceInterceptor) { // Do nothing on non-Chromium-based browsers or when the Mojo bindings are // not present in the global namespace. return; } let chain = Promise.resolve(); [ '/resources/chromium/mojo_bindings.js', '/resources/chromium/string16.mojom.js', '/resources/chromium/sensor.mojom.js', '/resources/chromium/sensor_provider.mojom.js', '/resources/chromium/generic_sensor_mocks.js', ].forEach(path => { let script = document.createElement('script'); script.src = path; script.async = false; chain = chain.then(() => new Promise(resolve => { script.onload = resolve; })); document.head.appendChild(script); }); return chain; }); async function initialize_generic_sensor_tests() { if (typeof GenericSensorTest === 'undefined') { await loadChromiumResources; } assert_true(typeof GenericSensorTest !== 'undefined'); let sensorTest = new GenericSensorTest(); await sensorTest.initialize(); return sensorTest; } function sensor_test(func, name, properties) { promise_test(async (t) => { let sensorTest = await initialize_generic_sensor_tests(); try { await func(t); } finally { await sensorTest.reset(); }; }, name, properties); } const properties = { 'AmbientLightSensor' : ['timestamp', 'illuminance'], 'Accelerometer' : ['timestamp', 'x', 'y', 'z'], 'LinearAccelerationSensor' : ['timestamp', 'x', 'y', 'z'], "GravitySensor" : ['timestamp', 'x', 'y', 'z'], 'Gyroscope' : ['timestamp', 'x', 'y', 'z'], 'Magnetometer' : ['timestamp', 'x', 'y', 'z'], "UncalibratedMagnetometer" : ['timestamp', 'x', 'y', 'z', 'xBias', 'yBias', 'zBias'], 'AbsoluteOrientationSensor' : ['timestamp', 'quaternion'], 'RelativeOrientationSensor' : ['timestamp', 'quaternion'], 'GeolocationSensor' : ['timestamp', 'latitude', 'longitude', 'altitude', 'accuracy', 'altitudeAccuracy', 'heading', 'speed'], 'ProximitySensor' : ['timestamp', 'max'] }; const spatialSensors = ['Accelerometer', 'LinearAccelerationSensor', 'GravitySensor', 'Gyroscope', 'Magnetometer', 'UncalibratedMagnetometer', 'AbsoluteOrientationSensor', 'RelativeOrientationSensor']; function assert_reading_not_null(sensor) { for (let property in properties[sensor.constructor.name]) { let propertyName = properties[sensor.constructor.name][property]; assert_not_equals(sensor[propertyName], null); } } function assert_reading_null(sensor) { for (let property in properties[sensor.constructor.name]) { let propertyName = properties[sensor.constructor.name][property]; assert_equals(sensor[propertyName], null); } } function reading_to_array(sensor) { const arr = new Array(); for (let property in properties[sensor.constructor.name]) { let propertyName = properties[sensor.constructor.name][property]; arr[property] = sensor[propertyName]; } return arr; } function runGenericSensorTests(sensorName) { const sensorType = self[sensorName]; sensor_test(async t => { assert_true(sensorName in self); const sensor = new sensorType(); const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]); sensor.start(); await sensorWatcher.wait_for("reading"); assert_reading_not_null(sensor); assert_true(sensor.hasReading); sensor.stop(); assert_reading_null(sensor); assert_false(sensor.hasReading); }, `${sensorName}: Test that 'onreading' is called and sensor reading is valid`); sensor_test(async t => { assert_true(sensorName in self); const sensor1 = new sensorType(); const sensor2 = new sensorType(); const sensorWatcher = new EventWatcher(t, sensor1, ["reading", "error"]); sensor2.start(); sensor1.start(); await sensorWatcher.wait_for("reading"); // Reading values are correct for both sensors. assert_reading_not_null(sensor1); assert_reading_not_null(sensor2); //After first sensor stops its reading values are null, //reading values for the second sensor remains sensor1.stop(); assert_reading_null(sensor1); assert_reading_not_null(sensor2); sensor2.stop(); assert_reading_null(sensor2); }, `${sensorName}: sensor reading is correct`); sensor_test(async t => { assert_true(sensorName in self); const sensor = new sensorType(); const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]); sensor.start(); await sensorWatcher.wait_for("reading"); const cachedTimeStamp1 = sensor.timestamp; await sensorWatcher.wait_for("reading"); const cachedTimeStamp2 = sensor.timestamp; assert_greater_than(cachedTimeStamp2, cachedTimeStamp1); sensor.stop(); }, `${sensorName}: sensor timestamp is updated when time passes`); sensor_test(async t => { assert_true(sensorName in self); const sensor = new sensorType(); const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]); assert_false(sensor.activated); sensor.start(); assert_false(sensor.activated); await sensorWatcher.wait_for("activate"); assert_true(sensor.activated); sensor.stop(); assert_false(sensor.activated); }, `${sensorName}: Test that sensor can be successfully created and its states are correct.`); sensor_test(async t => { assert_true(sensorName in self); const sensor = new sensorType(); const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]); const start_return = sensor.start(); await sensorWatcher.wait_for("activate"); assert_equals(start_return, undefined); sensor.stop(); }, `${sensorName}: sensor.start() returns undefined`); sensor_test(async t => { assert_true(sensorName in self); const sensor = new sensorType(); const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]); sensor.start(); sensor.start(); await sensorWatcher.wait_for("activate"); assert_true(sensor.activated); sensor.stop(); }, `${sensorName}: no exception is thrown when calling start() on already started sensor`); sensor_test(async t => { assert_true(sensorName in self); const sensor = new sensorType(); const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]); sensor.start(); await sensorWatcher.wait_for("activate"); const stop_return = sensor.stop(); assert_equals(stop_return, undefined); }, `${sensorName}: sensor.stop() returns undefined`); sensor_test(async t => { assert_true(sensorName in self); const sensor = new sensorType(); const sensorWatcher = new EventWatcher(t, sensor, ["activate", "error"]); sensor.start(); await sensorWatcher.wait_for("activate"); sensor.stop(); sensor.stop(); assert_false(sensor.activated); }, `${sensorName}: no exception is thrown when calling stop() on already stopped sensor`); sensor_test(async t => { assert_true(sensorName in self); const sensor = new sensorType(); const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]); sensor.start(); await sensorWatcher.wait_for("reading"); assert_true(sensor.hasReading); const timestamp = sensor.timestamp; sensor.stop(); assert_false(sensor.hasReading); sensor.start(); await sensorWatcher.wait_for("reading"); assert_true(sensor.hasReading); assert_greater_than(timestamp, 0); assert_greater_than(sensor.timestamp, timestamp); sensor.stop(); }, `${sensorName}: Test that fresh reading is fetched on start()`); // TBD file a WPT issue: visibilityChangeWatcher times out. // sensor_test(async t => { // const sensor = new sensorType(); // const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]); // const visibilityChangeWatcher = new EventWatcher(t, document, "visibilitychange"); // sensor.start(); // await sensorWatcher.wait_for("reading"); // assert_reading_not_null(sensor); // const cachedSensor1 = reading_to_array(sensor); // const win = window.open('', '_blank'); // await visibilityChangeWatcher.wait_for("visibilitychange"); // const cachedSensor2 = reading_to_array(sensor); // win.close(); // sensor.stop(); // assert_object_equals(cachedSensor1, cachedSensor2); // }, `${sensorName}: sensor readings can not be fired on the background tab`); sensor_test(async t => { assert_true(sensorName in self); const fastSensor = new sensorType({frequency: 30}); const slowSensor = new sensorType({frequency: 5}); slowSensor.start(); const fastCounter = await new Promise((resolve, reject) => { let fastCounter = 0; let slowCounter = 0; fastSensor.onreading = () => { fastCounter++; } slowSensor.onreading = () => { slowCounter++; if (slowCounter == 1) { fastSensor.start(); } else if (slowCounter == 3) { fastSensor.stop(); slowSensor.stop(); resolve(fastCounter); } } fastSensor.onerror = reject; slowSensor.onerror = reject; }); assert_greater_than(fastCounter, 2, "Fast sensor overtakes the slow one"); }, `${sensorName}: frequency hint works`); sensor_test(async t => { assert_true(sensorName in self); // Create a focused editbox inside a cross-origin iframe, // sensor notification must suspend. const iframeSrc = 'data:text/html;charset=utf-8,' + ''; const iframe = document.createElement('iframe'); iframe.src = encodeURI(iframeSrc); const sensor = new sensorType(); const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]); sensor.start(); await sensorWatcher.wait_for("reading"); assert_reading_not_null(sensor); const cachedTimestamp = sensor.timestamp; const cachedSensor1 = reading_to_array(sensor); const iframeWatcher = new EventWatcher(t, iframe, "load"); document.body.appendChild(iframe); await iframeWatcher.wait_for("load"); const cachedSensor2 = reading_to_array(sensor); assert_array_equals(cachedSensor1, cachedSensor2); iframe.remove(); await sensorWatcher.wait_for("reading"); const cachedSensor3 = reading_to_array(sensor); assert_greater_than(sensor.timestamp, cachedTimestamp); sensor.stop(); }, `${sensorName}: sensor receives suspend / resume notifications when\ cross-origin subframe is focused`); // Re-enable after https://github.com/w3c/sensors/issues/361 is fixed. // test(() => { // assert_throws("NotSupportedError", () => { new sensorType({invalid: 1}) }); // assert_throws("NotSupportedError", () => { new sensorType({frequency: 60, invalid: 1}) }); // if (spatialSensors.indexOf(sensorName) == -1) { // assert_throws("NotSupportedError", () => { new sensorType({referenceFrame: "screen"}) }); // } // }, `${sensorName}: throw 'NotSupportedError' for an unsupported sensor option`); test(() => { assert_true(sensorName in self); const invalidFreqs = [ "invalid", NaN, Infinity, -Infinity, {}, undefined ]; invalidFreqs.map(freq => { assert_throws(new TypeError(), () => { new sensorType({frequency: freq}) }, `when freq is ${freq}`); }); }, `${sensorName}: throw 'TypeError' if frequency is invalid`); if (spatialSensors.indexOf(sensorName) == -1) { // The sensorType does not represent a spatial sensor. return; } sensor_test(async t => { assert_true(sensorName in self); const sensor = new sensorType({referenceFrame: "screen"}); const sensorWatcher = new EventWatcher(t, sensor, ["reading", "error"]); sensor.start(); await sensorWatcher.wait_for("reading"); //TODO use mock data to verify sensor readings, blocked by issue: // https://github.com/web-platform-tests/wpt/issues/9686 assert_reading_not_null(sensor); sensor.stop(); }, `${sensorName}: sensor reading is correct when options.referenceFrame is 'screen'`); test(() => { assert_true(sensorName in self); const invalidRefFrames = [ "invalid", null, 123, {}, "", true ]; invalidRefFrames.map(refFrame => { assert_throws(new TypeError(), () => { new sensorType({referenceFrame: refFrame}) }, `when refFrame is ${refFrame}`); }); }, `${sensorName}: throw 'TypeError' if referenceFrame is not one of enumeration values`); } function runGenericSensorInsecureContext(sensorName) { test(() => { assert_false(sensorName in window, `${sensorName} must not be exposed`); }, `${sensorName} is not exposed in an insecure context`); } function runGenericSensorOnerror(sensorName) { const sensorType = self[sensorName]; promise_test(async t => { assert_true(sensorName in self); const sensor = new sensorType(); const sensorWatcher = new EventWatcher(t, sensor, ["error", "activate"]); sensor.start(); const event = await sensorWatcher.wait_for("error"); assert_false(sensor.activated); assert_true(event.error.name == 'NotReadableError' || event.error.name == 'NotAllowedError'); }, `${sensorName}: 'onerror' event is fired when sensor is not supported`); }