-
Notifications
You must be signed in to change notification settings - Fork 22
/
index.bs
582 lines (466 loc) · 21.7 KB
/
index.bs
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
<pre class="metadata">
Title: Idle Detection API
Status: w3c/CG-DRAFT
ED: https://wicg.github.io/idle-detection/
Shortname: idle-detection
Level: 1
Editor: Reilly Grant 83788, Google LLC https://www.google.com, [email protected]
Abstract: This document defines a web platform API for observing system-wide user presence signals.
Group: wicg
Repository: https://github.com/WICG/idle-detection/
Favicon: logo-idle.png
Markup Shorthands: css no, markdown yes
WPT Display: inline
WPT Path Prefix: /idle-detection/
</pre>
<pre class="anchors">
spec: ECMAScript; urlPrefix: https://tc39.github.io/ecma262/#
type: dfn
text: internal slot; url: sec-object-internal-methods-and-internal-slots
spec: page-visibility-2; urlPrefix: https://www.w3.org/TR/page-visibility-2/#
for: Document
type: attribute
text: hidden; url: dom-document-hidden
text: onvisibilitychange; url:dom-document-onvisibilitychange
</pre>
<pre class="link-defaults">
spec:html; type:event; for:HTMLElement; text:change
spec:infra; type:dfn; text:user agent
spec:service-workers; type:dfn; for:/; text:service worker
spec:webidl; type:dfn; text:resolve
</pre>
<style>
table {
border-collapse: collapse;
border-left-style: hidden;
border-right-style: hidden;
text-align: left;
}
table td, table th {
border: 1px solid black;
padding: 3px;
}
</style>
# Introduction # {#introduction}
*This section is non-normative.*
Using existing capabilities a page is able to determine when it is currently
visible to the user (using the {{Document/hidden}} property and
{{Document/onvisibilitychange}} event). It is also possible to know when the
user has recently interacted with the page by observing
{{GlobalEventHandlers/onmousemove}}, {{GlobalEventHandlers/onkeypress}}, and
other events triggered by user input. While sufficiently reflecting user
engagement with a particular page these events give an incomplete picture of
whether the user is still present at their device. For example, if
{{Document/hidden}} is `true`, then the device screensaver may have activated,
or the user could have switched to a different application. If it is `false` but
there have been no recent input events, then the user could have left their
computer to grab a cup of coffee, or they could be editing a document in another
window side-by-side with the page.
Making these distinctions is important for applications which have the option of
delivering notifications across multiple devices, such as a desktop and
smartphone. Users may find it frustrating when notifications are delivered to
the wrong device or are disruptive. For example, if they switch from a tab
containing a messaging application to one for a document they are editing, the
messaging application, not being able to observe that the user is still
interacting with their device, may assume that they have left to grab a coffee
and start delivering notifications to their phone, causing it to buzz
distractingly, instead of displaying notifications on their desktop or
incrementing a badge count.
## Alternatives Considered ## {#alternatives-considered}
An alternative design would protect this information by allowing a notification
to be marked as "hide on active" or "hide on idle" and not allowing the page to
observe whether or not the notification was actually shown. The problem with
this approach is that the intelligent notification routing described previously
requires observing these signals of user presence and making centralized
decisions based on the state of all of the user's devices.
For example, to route notifications to a user's mobile device when they get up
to grab a coffee the messaging application could detect that it is no longer
visible and start sending push messages to the mobile device while marking the
desktop notifications as "hide on active". If the user were still at their desk
but using a different application then they would start getting the distracting
notifications from their mobile device this proposal is attempting to avoid
whether or not the desktop is able to successfully suppress them. Successful
suppression of duplicate and disruptive notification requires multi-device
coordination.
Allowing notifications to be hidden also breaks implementor mitigations for the
[[PUSH-API]] being used to run silent background tasks.
# Observing User Presence # {#api}
## Model ## {#api-model}
This specification defines a model for user presence on two dimensions: idle
state and screen lock.
### The {{UserIdleState}} enum ### {#api-useridlestate}
<xmp class="idl">
enum UserIdleState {
"active",
"idle"
};
</xmp>
: {{"active"}}
:: Indicates that the user has interacted with the device in the last
{{IdleOptions/threshold}} milliseconds.
: {{"idle"}}
:: Indicates that the user has not interacted with the device in at least
{{IdleOptions/threshold}} milliseconds.
### The {{ScreenIdleState}} enum ### {#api-screenidlestate}
<xmp class="idl">
enum ScreenIdleState {
"locked",
"unlocked"
};
</xmp>
: {{"locked"}}
:: Indicates that the device has engaged a screensaver or lock screen which
prevents content from being seen or interacted with.
: {{"unlocked"}}
:: Indicates that the device is able to display content and be interacted with.
## Permissions ## {#api-permissions}
The <dfn permission export>"idle-detection"</dfn> permission is a [=default powerful feature=].
## Permissions policy ## {#api-permissions-policy}
This specification defines a [=policy-controlled feature=] identified by the
string `"idle-detection"`. Its [=default allowlist=] is `'self'`.
<div class="note">
The [=default allowlist=] of `'self'` allows usage of this feature on
same-origin nested frames by default but prevents access by third-party content.
Third-party usage can be selectively enabled by adding the
`allow="idle-detection"` attribute to an {{iframe}} element:
<div class="example">
```html
<iframe src="https://example.com" allow="idle-detection"></iframe>
```
</div>
Alternatively, this feature can be disabled completely in first-party contexts
by specifying the permissions policy in an HTTP response header:
<div class="example">
```http
Permissions-Policy: idle-detection 'none'
```
</div>
See [[PERMISSIONS-POLICY]] for more details.
</div>
## The {{IdleDetector}} interface ## {#api-idledetector}
<xmp class="idl">
dictionary IdleOptions {
[EnforceRange] unsigned long long threshold;
AbortSignal signal;
};
[
SecureContext,
Exposed=(Window,DedicatedWorker)
] interface IdleDetector : EventTarget {
constructor();
readonly attribute UserIdleState? userState;
readonly attribute ScreenIdleState? screenState;
attribute EventHandler onchange;
[Exposed=Window] static Promise<PermissionState> requestPermission();
Promise<undefined> start(optional IdleOptions options = {});
};
</xmp>
Instances of {{IdleDetector}} are created with the [=internal slots=] described
in the following table:
<table dfn-for=IdleDetector dfn-type=attribute>
<tr>
<th>[=Internal slot=]</th>
<th>Initial value</th>
<th>Description (non-normative)</th>
</tr>
<tr>
<td><dfn>\[[state]]</dfn></th>
<td>`"stopped"`</td>
<td>Tracks the active state of the {{IdleDetector}}</td>
</tr>
<tr>
<td><dfn>\[[threshold]]</dfn></th>
<td>`undefined`</td>
<td>The configured idle detection threshold</td>
</tr>
<tr>
<td><dfn>\[[userState]]</dfn></td>
<td>`null`</td>
<td>The last known user idle state</td>
</tr>
<tr>
<td><dfn>\[[screenState]]</dfn></td>
<td>`null`</td>
<td>The last known screen idle state</td>
</tr>
</table>
<wpt>
idlharness-worker.https.window.js
idlharness.https.window.js
</wpt>
Methods on this interface typically complete asynchronously, queuing work on the
<dfn>idle detection task source</dfn>.
### {{IdleDetector/userState}} attribute ### {#api-idledetector-userstate}
<div algorithm>
The <dfn attribute for=IdleDetector>userState</dfn> getter steps are:
1. Return [=this=].{{[[userState]]}}.
</div>
### {{IdleDetector/screenState}} attribute ### {#api-idledetector-screenstate}
<div algorithm>
The <dfn attribute for=IdleDetector>screenState</dfn> getter steps are:
1. Return [=this=].{{[[screenState]]}}.
</div>
### {{IdleDetector/onchange}} attribute ### {#api-idledetector-onchange}
<dfn attribute for="IdleDetector">onchange</dfn> is an <a>Event handler IDL
attribute</a> for the {{change}} event type.
### {{IdleDetector/requestPermission()}} method ### {#api-idledetector-requestpermission}
<div algorithm>
The <dfn method for=IdleDetector>requestPermission()</dfn> method steps are:
1. If [=this=]'s [=relevant global object=]'s [=associated Document=] is not
[=fully active=], return [=a promise rejected with=] a
"{{InvalidStateError}}" {{DOMException}}.
<wpt>
idle-detection-detached-frame.https.html
</wpt>
1. If the [=relevant global object=] of [=this=] does not have [=transient
activation=], return [=a promise rejected with=] a "{{NotAllowedError}}"
{{DOMException}}.
1. Let |result| be [=a new promise=].
1. [=In parallel=]:
1. Let |permissionState| be the result of [=requesting permission to use=]
<a permission>"idle-detection"</a>.
1. [=Queue a global task=] on the [=relevant global object=] of [=this=]
using the [=idle detection task source=] to [=resolve=] |result| with
|permissionState|.
1. Return |result|.
</div>
### {{IdleDetector/start()}} method ### {#api-idledetector-start}
<div algorithm>
The <dfn method for=IdleDetector>start(|options|)</dfn> method steps are:
1. Let |document| be [=this=]'s [=relevant global object=]'s [=associated
Document=].
1. If |document| is not [=fully active=], return [=a promise rejected with=] a
"{{InvalidStateError}}" {{DOMException}}.
<wpt>
idle-detection-detached-frame.https.html
</wpt>
1. If |document| is not [=allowed to use=] <a permission>"idle-detection"</a>,
return [=a promise rejected with=] a "{{NotAllowedError}}" {{DOMException}}.
<wpt>
idle-detection-allowed-by-permissions-policy-attribute-redirect-on-load.https.sub.html
idle-detection-allowed-by-permissions-policy-attribute.https.sub.html
idle-detection-allowed-by-permissions-policy.https.sub.html
idle-detection-default-permissions-policy.https.sub.html
idle-detection-disabled-by-permissions-policy.https.sub.html
</wpt>
1. If |this|.{{IdleDetector/[[state]]}} is not `"stopped"`,
return [=a promise rejected with=] an "{{InvalidStateError}}" {{DOMException}}.
<wpt>
interceptor.https.html
</wpt>
1. Set |this|.{{IdleDetector/[[state]]}} to `"starting"`.
1. If |options|[{{"threshold"}}] is less than 60,000, return [=a promise rejected with=]
a {{TypeError}}.
<wpt>
basics.tentative.https.window.js
</wpt>
1. Let |result| be [=a new promise=].
1. If |options|["{{signal}}"] is present, then perform the following sub-steps:
1. If |options|["{{signal}}"] is [=AbortSignal/aborted=], then
[=reject=] |result| with |options|["{{signal}}"]'s [=AbortSignal/abort reason=]
and return |result|.
1. [=AbortSignal/add|Add the following abort steps=] to
|options|["{{signal}}"]:
1. Set |this|.{{IdleDetector/[[state]]}} to `"stopped"`.
1. [=Reject=] |result| with |options|["{{signal}}"]'s [=AbortSignal/abort reason=].
<wpt>
interceptor.https.html
</wpt>
1. [=In parallel=]:
1. Let |permissionState| be the [=permission state=] of `"idle-detection"`.
<wpt>idle-permission.tentative.https.window.js</wpt>
1. [=Queue a global task=] on the [=relevant global object=] of [=this=]
using the [=idle detection task source=] to perform the following steps:
1. If |permissionState| is `"denied"`,
1. Set |this|.{{IdleDetector/[[state]]}} to `"stopped"`.
1. [=Reject=] |result| with a "{{NotAllowedError}}" {{DOMException}}.
1. Otherwise,
1. If |this|.{{IdleDetector/[[state]]}} is `"stopped"`, abort these
steps.
1. Set |this|.{{IdleDetector/[[state]]}} to `"started"`.
1. Set |this|.{{IdleDetector/[[threshold]]}} to |options|["{{threshold}}"].
1. [=Resolve=] |result|.
1. Return |result|.
Note: The steps above are performed [=in parallel=] to enable implementations
to check the permission state asynchronously.
</div>
<div class="example">
The availability of this API can be detected by looking for the {{IdleDetector}}
constructor in the {{Window}} object.
```js
if (!('IdleDetector' in window)) {
console.log('Idle detection is not available.');
return;
}
```
Calling {{start()}} will fail if the <a permission>"idle-detection"</a> permission
has not been [=permission/granted=].
```js
if ((await IdleDetector.requestPermission()) !== 'granted') {
console.log('Idle detection permission not granted.');
return;
}
```
A set of options can be configured to control the threshold the [=user agent=]
uses to decide when the user has become idle.
```js
const controller = new AbortController();
const signal = controller.signal;
const options = {
threshold: 60_000,
signal,
};
```
The {{IdleDetector}} can now be created and started. An listener for the
{{"change"}} event is added and will be fired if the {{userState}} or
{{screenState}} attributes change.
```js
try {
const idleDetector = new IdleDetector();
idleDetector.addEventListener('change', () => {
console.log(`Idle change: ${idleDetector.userState}, ${idleDetector.screenState}.`);
});
await idleDetector.start(options);
console.log('IdleDetector is active.');
} catch (err) {
// Deal with initialization errors like permission denied,
// running outside of top-level frame, etc.
console.error(err.name, err.message);
}
```
At a later time the page can cancel its interest in state change events by
removing its event listeners or using the {{AbortSignal}} that was passed to
{{start()}}.
```js
controller.abort();
console.log('IdleDetector is stopped.');
```
</div>
### Reacting to state changes ### {#state-changes}
<div algorithm="state changes">
For each {{IdleDetector}} instance |detector| where |detector|.{{[[state]]}} is
`"started"` the [=user agent=] MUST continuously monitor the following
conditions:
* If |detector|.{{[[userState]]}} is {{"active"}} and the user has not
interacted with the device within the last |detector|.{{[[threshold]]}}
milliseconds, it MUST [=queue a global task=] on the [=relevant global
object=] of |detector| using the [=idle detection task source=] to run the
following steps:
1. Set |detector|.{{[[userState]]}} to {{"idle"}}.
1. [=Fire an event=] named {{"change"}} at |detector|.
* If |detector|.{{[[userState]]}} is {{"idle"}} and the user interacts with
the device, it MUST [=queue a global task=] on the [=relevant global
object=] of |detector| using the [=idle detection task source=] to run the
following steps:
1. Set |detector|.{{[[userState]]}} to {{"active"}}.
1. [=Fire an event=] named {{"change"}} at |detector|.
* If |detector|.{{[[screenState]]}} is {{"unlocked"}} and the screen is
locked, it MUST [=queue a global task=] on the [=relevant global object=] of
|detector| using the [=idle detection task source=] to run the following
steps:
1. Set |detector|.{{[[screenState]]}} to {{"locked"}}.
1. [=Fire an event=] named {{"change"}} at |detector|.
* If |detector|.{{[[screenState]]}} is {{"locked"}} and the screen is
unlocked, it MUST [=queue a global task=] on the [=relevant global object=]
of |detector| using the [=idle detection task source=] to run the following
steps:
1. Set |detector|.{{[[screenState]]}} to {{"unlocked"}}.
1. [=Fire an event=] named {{"change"}} at |detector|.
</div>
<wpt>
interceptor.https.html
page-visibility.https.html
</wpt>
# Security and privacy considerations # {#security-and-privacy}
*This section is non-normative.*
## Cross-origin information leakage ## {#privacy-cross-origin-leakage}
This interface exposes the state of global system properties and so care must be
taken to prevent them from being used as cross-origin communication or
identification channels. Similar concerns are present in specifications such as
[[DEVICE-ORIENTATION]] and [[GEOLOCATION]], which mitigate them by requiring
a visible or focused context. This prevents multiple origins from observing the
global state at the same time. These mitigations are unfortunately inappropriate
here because the intent of this specification is precisely to allow a limited
form of tracking in blurred and hidden contexts. A malicious page could notify a
tracking server whenever the user is detected as idle or active. If multiple
pages the user was visiting notified the same server it could use the timing of
the events to guess which sessions corresponded to a single user as they would
arrive roughly simultaneously.
To reduce the number of independent contexts with access to this interface this
specification restricts it to top-level and same-origin contexts. Access can be
delegated to a cross-origin context through [[PERMISSIONS-POLICY]].
To further reduce the number of contexts this specification requires
a page to obtain the <a permission>"idle-detection"</a> permission. User agents should inform
the user of the capability that this permission grants and encourage them to
only [=permission/grant=] it to trusted sites which have a legitimate purpose for this data.
Implementations that provide a "private browsing" mode should not allow this
capability in contexts where this mode is enabled. Implementations should be
careful however to avoid the lack of this capability from being used as a signal
that this mode is enabled. This can be accomplished by refusing to allow the
<a permission>"idle-detection"</a> permission to be [=permission/granted=] but delaying the automatic
dismissal of the permission request by a random interval so that it appears to
have been a user action.
## Behavior tracking ## {#privacy-behavior-tracking}
While this interface does not provide details of the user interaction which
triggered an {{"idle"}} to {{"active"}} transition, with a sufficiently short
threshold these events could be used to detect behavior such as typing. This
specification therefore restricts the requested threshold to a minimum of at
least 60 seconds.
The permission requirement described previously also helps to mitigate the
general concern that this interface can be used to build a profile of when and
for how long the user typically interacts with their device.
## User coercion ## {#privacy-user-coercion}
Sites may require the user to [=permission/grant=] them the <a permission>"idle-detection"</a> [=permission=]
before unlocking some functionality. For example, a testing site could require
this permission as part of an anti-cheating mechanism to detect the user
consulting forbidden reference materials in another window. This type of
"Contract of Adhesion" has been observed with other permissions such as
notifications, FIDO attestation and DRM identifiers.
A potential mitigation for this concern is to design that interface so that it
is not possible for a site to determine whether the user has
[=permission/granted=] or [=permission/denied=] the permission.
An implementation could refuse to acknowledge that the user is
idle, reducing a site to only the signals currently available. This mitigation
could be detectable as it is unlikely that a user who has not interacted with a
page for hours has nevertheless still been continuously interacting with
something else. Implementations could instead insert fake idle transition events
which correspond to plausible behavior given the other signals available to the
page.
This specification does not mandate this type of mitigation as it could create a
poor user experience when sites take action based on this false data. For
example, the message application mentioned previously would not deliver
notifications to the user's mobile device because it believes the signals it has
been given indicate that they are still at their desktop. As the site cannot
detect that it is in this state it cannot directly recommend an action for the
user to take to get themselves out of it.
The harm done by such a site is limited as tracking is only possible while the
user is visiting that page. Tracking across multiple origins requires permission
to be requested on each participating site.
# Accessibility considerations # {#accessibility}
*This section is non-normative.*
Users with physical or cognitive impairments may require more time to interact
with user agents and content. Implementations should not allow distinguishing
such users, or limiting their ability to interact with content any more than
existing observation of UI events. For example, implementation should ensure
that interactions from assistive technologies count towards considering the user
active.
The use of a permission also requires that user agents provide a user interface
element to support requesting and managing that permission. Any such user
interface elements must be designed with accessibility tools in mind. For
example, a user interface describing the capability being requested should
provide the same description to tools such as screen readers.
# Internationalization considerations # {#internationalization}
*This section is non-normative.*
The interface described by this specification has limited internationalization
considerations, however the use of a permission does require that user agents
provide a user interface element to support requesting and managing that
permission. Any content displayed by the user agent in this context should be
translated into the user's native language.
# Acknowledgements # {#acknowledgements}
*This section is non-normative.*
Many thanks to
Kenneth Christiansen,
Samuel Goto,
Ayu Ishii and
Thomas Steiner
for their help in crafting this proposal.