333 lines
14 KiB
JavaScript
333 lines
14 KiB
JavaScript
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`);
|
|
return;
|
|
}
|
|
if (e.data.error) {
|
|
reject(e.data.error);
|
|
return;
|
|
}
|
|
resolve(e.data.result);
|
|
});
|
|
iframe.contentWindow.postMessage(message, '*');
|
|
});
|
|
}
|
|
|
|
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);
|
|
|
|
// 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);
|
|
}
|
|
|
|
promise_setup(async () => {
|
|
// Ensure window's document starts with focus so that it can receive data.
|
|
await test_driver.click(document.documentElement);
|
|
});
|
|
|
|
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.serializedSensor);
|
|
});
|
|
}
|
|
|
|
wait_for_reading() {
|
|
return new Promise(resolve => {
|
|
this.resolve_ = resolve;
|
|
});
|
|
}
|
|
};
|
|
|
|
// Create main frame sensor.
|
|
await test_driver.bidi.permissions.set_permission(
|
|
{descriptor: {name: permissionName}, state: '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(';') + '; focus-without-user-activation;';
|
|
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: 'stop_sensor'});
|
|
iframe.parentNode.removeChild(iframe);
|
|
});
|
|
await iframeLoadWatcher.wait_for('load');
|
|
const iframeSensorWatcher = new IframeSensorReadingEventWatcher(t);
|
|
await send_message_to_iframe(
|
|
iframe, {command: 'create_sensor', sensorData});
|
|
|
|
// 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();
|
|
|
|
// 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;
|
|
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'}));
|
|
|
|
// Save sensor data for later before the sensor is stopped.
|
|
const savedMainFrameSensorReadings = serialize_sensor_data(sensor);
|
|
|
|
sensor.stop();
|
|
await send_message_to_iframe(iframe, {command: 'stop_sensor'});
|
|
|
|
// The sensors are stopped; queue the same reading. The virtual sensor
|
|
// would send it anyway, but this update changes its timestamp.
|
|
await test_driver.update_virtual_sensor(testDriverName, reading);
|
|
|
|
// 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. Only the iframe sensor should receive a reading
|
|
// event and contain readings.
|
|
sensor.start();
|
|
await sensorWatcher.wait_for('activate');
|
|
await send_message_to_iframe(iframe, {command: 'start_sensor'});
|
|
const serializedIframeSensor = await iframeSensorWatcher.wait_for_reading();
|
|
assert_true(await send_message_to_iframe(iframe, {command: 'has_reading'}));
|
|
assert_false(sensor.hasReading);
|
|
|
|
assert_sensor_reading_is_null(sensor);
|
|
|
|
assert_sensor_reading_equals(
|
|
savedMainFrameSensorReadings, serializedIframeSensor,
|
|
{ignoreTimestamps: true});
|
|
|
|
// We could check that serializedIframeSensor.timestamp (adjusted to this
|
|
// frame by adding the iframe's timeOrigin and substracting
|
|
// performance.timeOrigin) is greater than
|
|
// savedMainFrameSensorReadings.timestamp (or other timestamps prior to the
|
|
// last test_driver.update_virtual_sensor() call), but this is surprisingly
|
|
// tricky and flaky due to the fact that we are using timestamps from
|
|
// cross-origin frames.
|
|
//
|
|
// On Chrome on Windows (M120 at the time of writing), for example, the
|
|
// difference between timeOrigin values is sometimes off by more than 10ms
|
|
// from the real difference, and allowing for this much jitter makes the
|
|
// test not test something meaningful.
|
|
}, `${sensorName}: unfocused sensors in cross-origin frames are not updated`);
|
|
|
|
sensor_test(async (t, readings) => {
|
|
// Create main frame sensor.
|
|
await test_driver.bidi.permissions.set_permission(
|
|
{descriptor: {name: permissionName}, state: '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]}}/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(() => {
|
|
if (iframeSensor) {
|
|
iframeSensor.stop();
|
|
}
|
|
iframe.parentNode.removeChild(iframe);
|
|
});
|
|
await iframeLoadWatcher.wait_for('load');
|
|
// 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 a different same-origin window each time and check that everything
|
|
// works the same.
|
|
for (const windowObject of [window, iframe.contentWindow]) {
|
|
await test_driver.update_virtual_sensor(
|
|
testDriverName, readings.next().value);
|
|
|
|
windowObject.focus();
|
|
|
|
iframeSensor.start();
|
|
sensor.start();
|
|
|
|
await Promise.all([
|
|
iframeSensorWatcher.wait_for(['activate', 'reading']),
|
|
sensorWatcher.wait_for(['activate', 'reading'])
|
|
]);
|
|
|
|
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);
|
|
|
|
// 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';
|
|
|
|
const iframeLoadWatcher = new EventWatcher(t, iframe, 'load');
|
|
document.body.appendChild(iframe);
|
|
await iframeLoadWatcher.wait_for('load');
|
|
|
|
// Create sensor in the iframe.
|
|
await test_driver.bidi.permissions.set_permission(
|
|
{descriptor: {name: permissionName}, state: 'granted'});
|
|
await test_driver.create_virtual_sensor(testDriverName);
|
|
iframe.contentWindow.focus();
|
|
const iframeSensor = new iframe.contentWindow[sensorName]();
|
|
t.add_cleanup(async () => {
|
|
iframeSensor.stop();
|
|
await test_driver.remove_virtual_sensor(testDriverName);
|
|
});
|
|
const sensorWatcher = new EventWatcher(t, iframeSensor, ['activate']);
|
|
iframeSensor.start();
|
|
await sensorWatcher.wait_for('activate');
|
|
|
|
// Remove iframe from main document and change focus. When focus changes,
|
|
// we need to determine whether a sensor must have its execution suspended
|
|
// or resumed (section 4.2.3, "Focused Area" of the Generic Sensor API
|
|
// spec). In Blink, this involves querying a frame, which might no longer
|
|
// exist at the time of the check.
|
|
iframe.parentNode.removeChild(iframe);
|
|
window.focus();
|
|
}, `${sensorName}: losing a document's frame with an active sensor does not crash`);
|
|
|
|
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';
|
|
|
|
const iframeLoadWatcher = new EventWatcher(t, iframe, 'load');
|
|
document.body.appendChild(iframe);
|
|
await iframeLoadWatcher.wait_for('load');
|
|
|
|
// Create sensor in the iframe.
|
|
await test_driver.bidi.permissions.set_permission(
|
|
{descriptor: {name: permissionName}, state: '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
|
|
// non-null browsing context. Calling start() should probably throw an
|
|
// error when called from a non-fully active document, but that depends on
|
|
// https://github.com/w3c/sensors/issues/415
|
|
iframe.parentNode.removeChild(iframe);
|
|
iframeSensor.start();
|
|
}, `${sensorName}: calling start() in a non-fully active document does not crash`);
|
|
}
|