1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
|
function send_message_to_iframe(iframe, message, reply) {
if (reply === undefined) {
reply = 'success';
}
return new Promise((resolve, reject) => {
window.addEventListener('message', (e) => {
if (e.data.command !== message.command) {
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}'`);
}
}, { once: true });
iframe.contentWindow.postMessage(message, '*');
});
}
function run_generic_sensor_iframe_tests(sensorName) {
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.`);
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.
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' });
iframe.parentNode.removeChild(iframe);
});
await iframeLoadWatcher.wait_for('load');
await send_message_to_iframe(iframe, {command: 'create_sensor',
type: sensorName});
// Focus on the main frame and test that sensor receives readings.
window.focus();
const sensor = new sensorType();
const sensorWatcher = new EventWatcher(t, sensor, ['reading', 'error']);
sensor.start();
await sensorWatcher.wait_for('reading');
const cachedTimeStamp = sensor.timestamp;
// Focus on the cross-origin frame and verify that sensor reading updates in
// the top level browsing context are suspended.
iframe.contentWindow.focus();
await send_message_to_iframe(iframe, {command: 'start_sensor'});
// 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();
// 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`);
sensor_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';
// 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' });
iframe.parentNode.removeChild(iframe);
});
await iframeLoadWatcher.wait_for('load');
await send_message_to_iframe(iframe, {command: 'create_sensor',
type: sensorName});
// 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();
iframe.contentWindow.focus();
await send_message_to_iframe(iframe, {command: 'start_sensor'});
// 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();
// Verify that sensor in nested browsing context is not suspended.
await send_message_to_iframe(iframe, {command: 'is_sensor_suspended'}, false);
// 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`);
sensor_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';
// 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');
await send_message_to_iframe(iframe, {command: 'create_sensor',
type: sensorName});
iframe.contentWindow.focus();
await send_message_to_iframe(iframe, {command: 'start_sensor'});
// 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.
// Note that we cannot send the "reset_sensor_backend" command because the
// iframe is discarded with the removeChild call.
iframe.parentNode.removeChild(iframe);
window.focus();
}, `${sensorName}: losing a document's frame with an active sensor does not crash`);
sensor_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';
// 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});
const iframeSensor = new iframe.contentWindow[sensorName]();
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`);
}
|