-
Notifications
You must be signed in to change notification settings - Fork 149
/
ui-security.src.html
825 lines (701 loc) · 43.8 KB
/
ui-security.src.html
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
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
<pre class='metadata'>
Title: User Interface Security and the Visibility API
Status: ED
Group: WebAppSec
ED: https://w3c.github.io/webappsec/specs/uisecurity/
Shortname: UI Security
Level: 1
Editor: Brad Hill, Facebook, [email protected]
!Author: Dan Kaminsky, White Ops
!Author: David Lin-Shung Huang, Carnegie Mellon University
!Author: Giorgio Maone, Invited Expert
Abstract:
UI Security and the Visibility API defines both a
declarative and imperative means for resources
displayed in an embedded context to protect
themselves against having their content obscured,
moved, or otherwise displayed in a misleading
manner.
Indent: 4
Ignored Terms: long, boolean
</pre>
<pre class='anchors'>
urlPrefix: http://www.w3.org/TR/hr-time/; type: typedef; text: DOMHighResTimeStamp
urlPrefix: https://html.spec.whatwg.org/multipage/browsers.html; type: dfn; text: unit of related similar-origin browsing contexts
urlPrefix: https://html.spec.whatwg.org/multipage/browsers.html; type: dfn; text: parent browsing context
urlPrefix: https://html.spec.whatwg.org/multipage/webappapis.html; type: dfn; text: event loop
urlPrefix: https://html.spec.whatwg.org/multipage/webappapis.html; type: dfn; text: report the exception
urlPrefix: https://dom.spec.whatwg.org/; type: dfn; text: cancelled flag
urlPrefix: https://dom.spec.whatwg.org/; type: dfn; text: dispatching events
url: http://w3c.github.io/requestidlecallback/#dfn-list-of-idle-request-callbacks; type: dfn; text: list of idle request callbacks
url: https://html.spec.whatwg.org/multipage/infrastructure.html#dfn-callback-this-value; type: dfn; text: callback this value
url: https://drafts.csswg.org/css2/box.html#content-edge; type:dfn; text: content-box
spec: HTML; urlPrefix: https://html.spec.whatwg.org/multipage/
urlPrefix: wwebappapis.html
type: interface
text: Navigator; url: the-navigator-object
</pre>
<pre class='link-defaults'>
spec:dom; type:interface; text:Event
spec:dom; type:interface; text:Document
</pre>
<!--
████ ██ ██ ████████ ████████ ███████
██ ███ ██ ██ ██ ██ ██ ██
██ ████ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ████████ ██ ██
██ ██ ████ ██ ██ ██ ██ ██
██ ██ ███ ██ ██ ██ ██ ██
████ ██ ██ ██ ██ ██ ███████
-->
<section>
<h2 id='intro'>Introduction</h2>
<em>This section is not normative.</em>
Composite or "mash-up" web applications built using iframes
are ubiquitous because they allow users to interact seamlessly and
simultaneously with content from multiple origins while
maintaining isolation boundaries that are essential to
security and privacy for both users and applications.
However, those boundaries are not absolute. In particular,
the visual and temporal integrity of embedded content is
not protected from manipulation by the embedding resource.
An embedding resource might constrain the viewport,
draw over, transform, reposition, or resize the user's
view of a third-party resource.
Collectively known as User Interface Redressing, the goal
of such manipulations might be to entice the user to interact
with embedded content without knowing its context, (e.g. to
send a payment or share content) commonly known as "clickjacking",
or to convince paid content that it is being shown to the user
when it is actually obscured, commonly known in the advertising
business as "display fraud".
Existing anti-clickjacking measures such as frame-busting
scripts and headers granting origin-based embedding permissions have
shortcomings which prevent their application to important use-cases.
Frame-busting scripts, for example, rely on browser behavior that has not been
engineered to provide a security guarantee and as a consequence,
such scripts may be unreliable if loaded inside a sandbox
or otherwise disabled. The X-Frame-Options header and the frame-ancestors
Content Security Policy directive offer an all-or-none approach to
display of embedded content that is not appropriate for content
which may be embedded in arbitrary locations, or known locations
which might still be adversarial.
This document defines mechanisms to allow resources to
request to be displayed free of interference by their embedding context and
learn if the user agent was able to satisfy such a request, with
sufficient granularity to make decisions that can protect both users
and content purveyors from various types of fraud.
First, this document defines an imperative API, VisibilityObserver,
by which a resource can request that a conforming user agent guarantee
unmodified display of its viewport, and report events on the success or
failure of meeting such guarantees. This API should be suitable
for e.g. paid content such as advertising to receive trustworthy
signals about its viewability from a conforming user agent.
Secondly, this specification defines a declarative mechanism
(via a Content Security Poicy directive) to request visibility
protection and receive notification, via event properties or
out-of-band reporting, if certain events are delivered to
a resource while it does not meet its requested visibility
contract.
The declarative CSP interface does not offer the same fine-granularity control as
the JavaScript API. Its goal is to allow protection to be
retrofitted to legacy applications, with no or minimal code changes, as a replacement for
X-Frame-Options, or potentially for use with content that is sandboxed and cannot
execute JavaScript.
ISSUE: Do we need to deal with form submission / navigations that aren't JS-event-based?
ISSUE: how to interact with frame-ancestors and XFO?
A notable non-goal is pixel-accurate information about what was
actually displayed beyond its bounding rectangle, as this information
can be quite difficult to obtain in an efficient manner, and is extremely
difficult to accomplish without exposing timing side channels which leak
information across the Same Origin Policy security boundary.
<div class='note'>
NOTE:
Similar to, and modeled on the
<a href=http://rawgit.com/slightlyoff/IntersectionObserver/master/index.html>
Intersection Observer</a> draft, this specification shares a goal of allowing reliable and low-cost
calculation of element visibility for, e.g purposes of
<a href="http://www.iab.net/iablog/2014/03/viewability-has-arrived-what-you-need-to-know-to-see-through-this-sea-change.html">
reporting ad visibility for monetizing impressions</a>. The current specification
adds the goals of preventing clickjacking and other UI redressing attacks both by enforcing that
an iframe which has requested visibility be free of any transforms, movement or re-clipping
within a defined time threshold, and by allowing event delivery to be intercepted or
annotated when policies are not met.
Distinct from the <em>Intersection Observer</em> proposal, this specification operates internally on entire
documents, on a per-iframe basis (although it provides some syntatic sugar for the declarative,
event-driven API) rather than observing individual elements, and it affirmatively modifies the final
composited result in the global viewport by promoting the graphics layer of an iframe that has requested visibility.
</div>
</section>
<!--
██████ ███████ ██ ██ ████████ ███████ ████████ ██ ██ ███ ██ ██ ██████ ████████
██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ███ ███ ██ ██ ███ ██ ██ ██ ██
██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ████ ████ ██ ██ ████ ██ ██ ██
██ ██ ██ ██ ██ ██ ██████ ██ ██ ████████ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██████
██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ █████████ ██ ████ ██ ██
██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██
██████ ███████ ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ██ ██ ██████ ████████
-->
<section>
<h2 id='special-conformance'>Special Conformance Notes</h2>
<em>This section is not normative.</em>
UI Redressing attacks rely on fooling the subjective perceptions of
human actors to induce them to interact with a web application out of
its intended context. Because of this, the specific mechanisms which
may be used in attack and defense may vary greatly with the details of
a user agent implementation. For example, attacks which rely on
redressing the cursor may not apply in a touch environment, or entire
classes of attack may be impossible on a text-only browser or screen
reader.
Similarly, the implementation of the policies specified herein is highly
dependent on internal architecture and implementation strategies of
the user agent; such strategies may vary greatly between user agents
or even across versions or platforms for a single user agent.
This specification provides a normative means by which a resource
owner can communicate to a user agent its desire for additional
protective measures, actions to take if violations are detected,
and tuning hints which may be useful for certain means of
implementation. A user agent is conformant if it understands
these directives and makes a best effort to provide the desired
security properties, which might require no additional implementation
steps, e.g. in the case of a screen reader that does not support
embedded resources in a manner that is subject to any of the
attack classes of concern.
While the indeterminacy of the user agent implementation protects
applications from needing to constantly update their policies as
user agents make internal changes, application authors should
understand that even a conformant user agent cannot make
perfect security guarantees against UI Redressing.
These directives should be used as part of a comprehensive risk
mitigation strategy with an appropriate understanding of their
limitations.
</section>
<!--
██ ██████ ███ ████████ ████
██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██
██ ██████ ██ ██ ████████ ██
██ ██ ██ █████████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██ ██ ██ ████
-->
<section>
<h2 id='visibility-observer-api'>VisibilityObserver API</h2>
The VisibilityObserver API provides an imperative API for developers to
receive notification of visibility state changes for their document relative to
the global viewport.
<h3 id='visibility-observer-callback'>The VisibilityObserverCallback</h3>
<pre class='idl'>
callback VisibilityObserverCallback = void(sequence<VisibilityObserverEntry> entries, VisibilityObserver observer)
</pre>
This callback will be invoked when there are changes to the document's <em>visibility state</em>.
<h3 id='visibility-observer-entry'>The VisibilityObserverEntry interface</h3>
<pre class="idl">
[Constructor(VisibilityObserverCallback callback, optional VisibilityObserverEntryInit visibilityObserverEntryInit), Exposed=Window]
interface VisibilityObserverEntry {
readonly attribute DOMRectReadOnly globalVisibleBounds;
readonly attribute DOMRectReadOnly visibleBounds;
readonly attribute DOMHighResTimeStamp time;
};
dictionary VisibilityObserverEntryInit {
required DOMRectInit globalVisibleBounds;
required DOMRectInit visibleBounds;
required DOMHighResTimeStamp time;
};
</pre>
<div dfn-for='VisibilityObserverEntry'>
<dfn attribute lt='globalVisibleBounds'>globalVisibleBounds</dfn>
The {{DOMRect}} coresponding to the visible dimensions of the
top-level document in the global viewport's coordinate space.
<dfn attribute lt='visibleBounds'>visibleBounds</dfn>
The {{DOMRect}} corresponding to the document's <i>boundingClientRect</i>,
intersected by each of the document's ancestor's clipping rects,
intersected with {{VisibilityObserverEntry/globalVisibleBounds}}.
This value represents the portion of the document actually visible within
{{VisibilityObserverEntry/globalVisibleBounds}}.
<dfn attribute lt='time'>time</dfn>
A {{DOMHighResTimeStamp}} that corresponds to the time the visibility
state was recorded.
</div>
<h3 id='visibility-observer-interface'>The VisibilityObserver Interface</h3>
The VisibilityObserver interface can be used to observe changes in the
document's visibility state relative to the global viewport.
<pre class='idl'>
[Constructor(VisibilityObserverCallback callback), Exposed=Window]
interface VisibilityObserver {
void observe ();
void unobserve ();
sequence<VisibilityObserverEntry> takeRecords ();
};
</pre>
<div dfn-type='method' dfn-for='VisibilityObserver'>
: <dfn constructor lt='VisibilityObserver(callback, options)'>new VisibilityObserver(callback, options)</dfn>
::
<ol>
<li>Let |this| be a new {{VisibilityObserver}} object</li>
<li>Set |this|'s internal {{[[callback]]}} slot to |callback|.</li>
</ol>
: <dfn>observe()</dfn>
::
<ol>
<li>Add |this| to the document's {{[[RegisteredVisibilityObservers]]}} list</li>
</ol>
: <dfn>unobserve()</dfn>
::
<ol>
<li>Remove |this| from the document's {{[[RegisteredVisibilityObservers]]}} set.</li>
</ol>
: <dfn>takeRecords()</dfn>
::
<ol>
<li>Let |queue| be a copy of |this|'s internal {{[[QueuedEntries]]}} slot.</li>
<li>Clear |this|'s internal {{[[QueuedEntries]]}} slot.</li>
<li>Return |queue|.</li>
</ol>
</div>
<h3 id="visibility-observer-init">The VisibilityObserverInit dictionary</h3>
<pre class="idl">
dictionary VisibilityObserverInit {
(double or sequence<double>) areaThreshold = 0;
(boolean) displacementAware = false;
(DOMString) visibleMargin = "0px";
(Element)? observedElement;
};
</pre>
<div dfn-type="dict-member" dfn-for="VisibilityObserverInit">
: <dfn>areaThreshold</dfn>
::
List of threshold(s) at which to trigger callback.
callback will be invoked when visibleBounds area changes from
greater than or equal to any threshold to less than that threshold,
and vice versa.
Threshold values must be in the range of [0, 1.0] and represent a
percentage of the area as specified by
<em>target</em>.{{Element/getBoundingClientRect()}}.
Note: 0.0 is effectively "any non-zero number of pixels".
: <dfn>displacementAware</dfn>
::
If <em>true</em>, this observer should trigger the callback
when the position of the {{[[observedElement]]}} changes relative to the
global viewport.
: <dfn>visibleMargin</dfn>
::
Same as 'margin', extends the required visibility rectangle
behind the <a>protected-element</a>.{{Element/getBoundingClientRect()}}.
Can be 1, 2, 3 or 4 components, possibly negative lengths.
If there is only one component value, it applies to all sides.
If there are two values, the top and bottom margins are set to
the first value and the right and left margins are set to the
second. If there are three values, the top is set to the first
value, the left and right are set to the second, and the bottom
is set to the third. If there are four values, they apply to the
top, right, bottom, and left, respectively.e.g.
<pre class="example">
<code class="js">
"5px" // all margins set to 5px
"5px 10px" // top & bottom = 5px, right & left = 10px
"-10px 5px 8px" // top = -10px, right & left = 5px, bottom = 8px
"-10px -5px 5px 8px" // top = -10px, right = -5px, bottom = 5px, left = 8px
</code>
</pre>
: <dfn>observedElement</dfn>
::
The {{Element}} being observed. If unset, the internal slot will be
initialized to the {{Document}} element.
</div>
<section>
<!--
██████ ██████ ████████ ████████ ████ ████████ ████████ ██████ ████████ ████ ██ ██ ████████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██████ ████████ ██ ██ ██ ████████ ██████ ██ ██ ██ ██ ██ ██████
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ██████ ██ ████████ ████ ██ ██ ████████ ██████ ██ ████ ███ ████████
-->
<section>
<h2 id='csp-interface'>Content Security Policy Interface</h2>
This section describes the Content Security Policy
directive introduced in this specification to provide declarative
configuration of protection against input when an element does not meet it's
visibility requirements.
The optional directive-value allows configuration of conditions for which violations
will be triggered.
<h3 id='input-protection-interface'>The <dfn>input-protection Directive</dfn></h3>
<pre>
directive-name = 'input-protection'
directive-value = ['area-threshold=' num-val]
['protected-element=' id-selector]
['time-threshold=' num-val]
['visible-margin=' num-val 'px' *3(',' num-val 'px')]
</pre>
<h4 id='directive-value'>Directive Value</h4>
<dfn>area-threshold</dfn>
A violation will be triggered if an event is delivered to the
protected-element or one of its ancestors if the visibility of the
protected area is below this threshold.
Threshold values must be in the range [0, 1.0] and represent a
percentage of the area as specified by
<a>protected-element</a>.{{Element/getBoundingClientRect()}},
adjusted by <a>visible-margin</a>. Unlike the imperative API,
only a single value may be specified.
<dfn>protected-element</dfn>
A {{DOMString}} used as the argument to {{NonElementParentNode/getElementById()}}
to resolve the {{Element}} to which the policy applies.
If unspecified the policy is applied to the resource's {{Document}} node.
<dfn>time-threshold</dfn>
A numeric value in the range [0, 10000] that specifies how long,
in milliseconds, the screen area containing the protected-element
must have unmodified viewiability properties when an event is
delivered to it or one of its ancestors.
If not specified, it defaults to 800. If a value outside of the
range stated above is given, it defaults ot the nearest value
between the lower and higher bounds.
<dfn>visible-margin</dfn>
Same as {{VisibilityObserverInit/visibleMargin}}.
If unspecified, it defaults to "0px".
</section>
<!--
████████ ████████ ███████ ██████ ████████ ██████ ██████ ████ ██ ██ ██████ ██ ██ ███████ ████████ ████████ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ███ ███ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██
████████ ████████ ██ ██ ██ ██████ ██████ ██████ ██ ██ ██ ██ ██ ████ ██ ███ ██ ██ ██ ██ ██ ██████ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ███████ ██████ ████████ ██████ ██████ ████ ██ ██ ██████ ██ ██ ███████ ████████ ████████ ████████
-->
<section>
<h2 id='processing-model'>Processing Model</h2>
This section outlines the steps the user agent must take when implementing the
VisibilityObserver API.
<h3 id='defines'>Internal Slot Definitions</h3>
<h4 id='browsing-context-slots'>Browsing Contexts</h4>
Each <a>unit of related similar-origin browsing contexts</a> has an
<dfn for="browsing context">VisibilityObserverTaskQueued</dfn> flag which
is initialized to false.
<h4 id='element-private-slots'>Element</h4>
{{Element}} objects have an internal
<dfn attribute for=Element>\[[InputProtectionObservers]]</dfn> list,
which is initially empty.
<h4 id='document-private-slots'>Document</h4>
{{Document}} objects have an internal
<dfn attribute for=Document>\[[RegisteredVisibilityObservers]]</dfn> list,
which is initially empty, and an
<dfn attribute for=Document>\[[InputProtectionRequested]]</dfn> flag which is intitially <i>false</i>.
<h4 id='visibility-observer-private-slots'>VisibilityObserver</h4>
{{VisibilityObserver}} objects have the following internal slots:
<ul>
<li><dfn attribute for=VisibilityObserver>\[[QueuedEntries]]</dfn>
which is initialized to an empty list</li>
<li><dfn attribute for=VisibilityObserver>\[[previousVisibleRatio]]</dfn>
which is initialized to 0</li>
<li><dfn attribute for=VisibilityObserver>\[[previousGlobalViewportPosition]]</dfn>
</ul>
As well as internal slots initialized by <a>VisibilityObserver(callback,options)</a>:
<ul>
<li><dfn attribute for=VisibilityObserver>\[[callback]]</dfn></li>
<li><dfn attribute for=VisibilityObserver>\[[areaThreshold]]</dfn></li>
<li><dfn attribute for=VisibilityObserver>\[[displacementAware]]</dfn></li>
<li><dfn attribute for=VisibilityObserver>\[[visibleMargin]]</dfn></li>
<li><dfn attribute for=VisibilityObserver>\[[observedElement]]</dfn> which is
initialized to the {{Document}} Element if not set in the {{VisibilityObserverInit}}</a> dictionary</li>
</ul>
The following internal slots will be initialzed to <i>null</i> unless the
object was constructed to represent an <a>input-protection directive</a>.
<ul>
<li><dfn attribute for=VisibilityObserver>\[[timeThreshold]]</dfn></li>
<li><dfn attribute for=VisibilityObserver>\[[associatedContentSecurityPolicy]]</dfn></li>
</ul>
<h3 id='algorithms'>Algorithms</h3>
<h4 id='queue-visibility-observer-task-algo'>Queue a VisibilityObserver Task</h4>
To <dfn>queue a visibility observer task</dfn> for a
<a>unit of related similar-origin browsing contexts</a> |unit|,
run these steps:
<ol>
<li>If |unit|'s <a>VisibilityObserverTaskQueued</a> flag is set to
true, return.</li>
<li>Set |unit|'s <a>VisibilityObserverTaskQueued</a> flag to true.</li>
<li>Post a task to <a>notify visibility observers</a>, or enqueue a
task to <a>notify visibility observers</a> in the
<a>list of idle request callbacks</a> with an appropriate |timeout|.
Issue: Should we define an appropriate |timeout|?
</li>
</ol>
<h4 id='notify-visibility-observers-algo'>Notify VisibilityObservers</h4>
To <dfn>notify visibility observers</dfn> for a
<a>unit of related similar-origin browsing contexts</a> |unit|,
run these steps:
<ol>
<li>Set |unit|'s <a>VisibilityObserverTaskQueued</a> flag to false.</li>
<li>For each {{Document}} |document| in |unit|</li>
<ol>
<li>Let |notify list| be a copy of |document|'s {{[[RegisteredVisibilityObservers]]}}</a>
list.</li>
<li>For each {{VisibilityObserver}} object |observer| in
|notify list|, run these steps:</li>
<ol>
<li>If |observer|'s internal {{[[QueuedEntries]]}} slot is
empty, continue.</li>
<li>Let |queue| be a copy of |observer|'s internal
{{[[QueuedEntries]]}} slot.</li>
<li>Clear |observer|'s internal {{[[QueuedEntries]]}} slot.</li>
<li>Invoke |callback| with |queue| as the first argument and
|observer| as the second argument and
<a>callback this value</a>. If this throws an exception,
<a>report the exception</a>.</li>
</ol>
</ol>
</ol>
<h4 id='queue-visibility-observer-entry-algo'>Queue a VisibilityObserverEntry</h4>
To <dfn>queue a VisibilityObserverEntry</dfn> for |observer|, given a
<a>unit of related similar-origin browsing contexts</a> |unit|, VisibilityObserver
|observer|, and VisibilityObserverEntry |entry| run these steps:
<ol>
<li>Append |entry| to |observer|'s internal {{[[QueuedEntries]]}} slot.</li>
<li><a>Queue a visibility observer task</a> for |unit|.</li>
</ol>
<h4 id='promote-observed-graphicslayers-algo'>Promote Observed GraphicsLayers</h4>
<em>This section is non-normative.</em>
<div class="note">
NOTE: The full internal details of rendering a document to the pixels
actually displayed to the user is not standardized. UA implementations
may vary widely.
The implementation strategy detailed in this section is not normative. Any
strategy which produces correct outcomes for the normative algorithms is
conformant and implementers are encouraged to optimize whenever possible.
The possibility of variance among user agent implementations notwithstanding,
the normative algorithms of this specification are designed such that a highly performant
implementation should be possible on the most common internal software and hardware
architectures that are state-of-the-art for user agents and consumer computing
platforms as of the time of writing.
In particular, the approach here deliberately avoids auditing the correctness
of the representations displayed to users. In typical architectures, the pixel-level
rendering of the global viewport is delgated to a a Graphics Processing Unit (GPU)
using higher-level abstractions like surfaces, polygons, and vectors. As a consequence,
the main execution context of the user agent does not "know" what pixels actually result
without reading them back. System architectures are optimized for sending data
to a GPU, not returning data from it, therefore, approaches which rely on pixel comparisons
are likely to have an unacceptable performance cost. Instead, the approach detailed
here relies on correctness by design, by manipulating the order in which instructions
are sent to the GPU such that malicious interference is not possible.
</div>
Generally, at some point in the rendering of a set of documents in nested browsing
contexts into the fully composed graphical representation in the global viewport,
a user agent will arrive at a set of intermediate representations we will designate
as <dfn>GraphicsLayer</dfn>s, each of which represents a graphical surface to be
painted / clipped / scrolled.
A <a>GraphicsLayer</a> representing the contents of a document in an iframe will
be arranged in the layer stack such that at a later phase in the rendering
it is automatically clipped and positioned relative to the series of viewports
above it, and also subject to being drawn over or transformed by the layers above it.
To prevent potentially malicious composition, the user agent can
<dfn>promote observed graphicsLayers</dfn> by manipulating them such that
a document with {{[[RegisteredVisibilityObservers]]}}
<ul>
<li>Is clipped and positioned as-if-unmodified within the set of viewports of its ancestor
browsing contexts. A promoted document should not be able to occupy more
screen real estate than it is given by its embedding contexts.</li>
<li>Responds to hit testing and events as-if-unmodified. Implementation-specific modifications
to internal representations of the document should not change the behavior of the DOM.</li>
<li>Is not subject to being drawn over or transformed by any other <a>GraphicsLayers</a>,
except other promoted layers, which should be treated as fully opaque occlusions
when reporting the visibility state of the document.</li>
</ul>
To <a>promote observed graphicsLayers</a>, given a time |now|, and an initially empty list
|promotedLayers|, run these steps during the rendering
loop at the stage where the intermediate representation of a set of {{Document}}s is a set of
<a>GraphicsLayer</a>s |graphicsLayers|.
<ol>
<li>For each |graphicsLayer| in |graphicsLayers|</li>
<li>For each {{Document}} |document| with an intermediate representation in |graphicsLayer|</li>
<ol>
<li>If |document| has an empty list of {{[[RegisteredVisibilityObservers]]}}, continue.</li>
<li>If |document| has a non-empty list of {{[[RegisteredVisibilityObservers]]}}</li>
<ol>
<li>If |document| is not the only {{Document}} represented in |graphicsLayer|, apply
whatever implementation-specific steps are necessary to place it in its own layer.
(e.g. apply translatez(0) to the documentElement) Let |graphicsLayer| be that
new layer.</li>
</ol>
<li>Let |rectToRaise| be the value of |document|.{{Element/getBoundingClientRect()}}.</li>
<li>Intersect |rectToRaise| with |document|'s viewport clip rect.</li>
<li>For every <a>parent browsing context</a> |parent| between |document| and the top-level document,
intersect |rectToRaise| with |parent|'s viewport clip rect,
and finally with the global viewport clip rect.</li>
<li>Clip |graphicsLayer| to |rectToRaise|. (|graphicsLayer| may have zero width and height
if it is scrolled off screen by an ancestor browsing context)</li>
<li>Intersect |rectToRaise| with any items in the |promotedLayers| list.</li>
<li>Add |rectToRaise| to the |promotedLayers| list.</li>
<li>Without reordering prior intermediate representations in a manner which would
change event dispatching, hit testing, or the DOM as exposed to JavaScript, reorder
the <a>GraphicsLayer</a>s such that |rectToRaise| is on top of the root <a>GraphicsLayer</a>.
(e.g. by making it a direct child of the root layer) but beneath any layers in |promotedLayers|
that clipped it.</li>
<li>Let |protectedRect| be the value of |observer|'s {{[[observedElement]]}}.{{Element/getBoundingClientRect()}},
adjusted by {{[[visibleMargin]]}}.</li>
<li>Let |visibleRatio| be the intersection of |protectedRect| with |rectToRaise|, divided by |protectedRect|
if |protectedRect| is non-zero, and <i>0</i> otherwise.</li>
<li>For each of |document|'s {{[[RegisteredVisibilityObservers]]}} |observer|</li>
<ol>
<li>Let |threshold| be the index of the first entry in
|observer|'s internal {{[[areaThreshold]]}} slot whose value
is greater than or equal to |visibleRatio|. If
|visibleRatio| is equal to <i>0</i>, let |threshold| be
<i>-1</i>.</li>
<li>Let |oldVisibleRatio| be set to |observer|'s internal
{{[[previousVisibleRatio]]}} slot.</li>
<li>Let |oldThreshold| be the index of the first entry in
|observer|'s internal {{[[areaThreshold]]}} slot whose value
is greater than or equal to |oldVisibleRatio|. If
|oldVisibleRatio| is equal to <i>0</i>, let
|oldThreshold| be <i>-1</i>.</li>
<li>Let |oldPosition| be the value of the |observer|'s internal
{{[[previousGlobalViewportPosition]]}}.</li>
<li>If |threshold| does not equal |oldThreshold|, or if |observer|'s
internal {{[[displacementAware]]}} slot is <i>true</i> and
|oldPosition| is not equal to |protectedRect|,</li>
<ul>
<li><a>queue a VisibilityObserverEntry</a></li>
<li>Assign |visibleRatio| to |observer|'s internal
{{[[previousVisibleRatio]]}} slot.</li>
<li>Assign |protectedRect| to the value of the |observer|'s internal
{{[[previousGlobalViewportPosition]]}} slot.</li>
</ul>
</ol>
</ol>
</ol>
ISSUE: find exact terms to make sure that we have viewport definitions minus scrollbars
ISSUE: need to also clip to any other layers that were promoted ahead of us!
ISSUE: if a parent and child layer both request to be promoted, the parent's clipping window will have a complex geometry with holes in it that is not accounted for by this algorithm. Likely need to specify that graphics layers be processed by order of depth.
<h4 id='enforce-an-input-protection-directive-algo'>Enforce An input-protection Directive</h4>
To <dfn>enforce an input-protection directive</dfn> for a {{Document}} |document|,
run the following steps:
<ol>
<li>Parse the policy according to [[!CSP2]].</li>
<li>If a value is set for <a>protected-element</a>, let |protectedElement| be the
{{Element}} returned by invoking |document|.{{NonElementParentNode/getElementById()}} with
the value as the input, or |document| if <i>null</i> or unset.</li>
<li>If |document|'s {{[[InputProtectionRequested]]}} flag is <i>false</i>, set it
to <i>true</i>.</li>
<li>Construct a new {{VisibilityObserver}} |observer|, with {{[[areaThreshold]]}} set to the value of
<a>area-threshold</a>, {{[[visibleMargin]]}} set to the value of <a>visible-margin</a>,
{{[[observedElement]]}} set to |protectedElement|, {{[[displacementAware]]}} set to <i>true</i>,
and {{[[callback]]}} set to a new function with an empty function body.</li>
<li>Set the internal {{[[timeThreshold]]}} slot of |observer| to the value of <a>time-threshold</a></li>
<li>Set the internal {{[[associatedContentSecurityPolicy]]}} slot of |observer| to a reference to the
Content Security Policy which the <a>input-protection directive</a> is associated with.</li>
<li>When <a>dispatching events</a>, when an {{Element}} |element| will handle an {{Event}} |event|,
if |event| is of type Mouse Event, Pointer Event, Drag-and-Drop, or Clipboard Event, (TODO:linkify)
and if |element| has {{[[InputProtectionObservers]]}} |observers|:</li>
<ol>
<li>If applicable, check the computed style for the cursor. If a cursor is typically displayed but
has been hidden or changed to a non-standard bitmap, <a>handle a violation</a> for |event| and each
|observer| in |observers|.</li>
<li>Otherwise, for each |observer| in |observers|:</li>
<ol>
<li>If |observer|'s {{[[previousVisibleRatio]]}} is less than {{[[areaThreshold]]}},
<a>handle a violation</a> for |observer|.</li>
<li>If |observer|'s {{[[previousVisibleRatio]]}} is greater than {{[[areaThreshold]]}},
get the most recent {{VisibilityObserverEntry}} |entry| from |observer|'s
{{[[QueuedEntries]]}}. If the difference between |entry|.{{VisibilityObserverEntry/time}}
and |now| is less than {{[[timeThreshold]]}}, <a>handle a violation</a> for |observer|.
</ol>
</ol>
</ol>
<h4 id='handle-a-violation-algo'>Handle a Violation</h4>
To <dfn>handle a violation</dfn> of an <a>input-protection directive</a> for |observer| and |event|, run the following steps:
<ul>
<li>Follow the steps in [[!CSP2]] to report a violation for |observer|'s {{[[associatedContentSecurityPolicy]]}} |policy|.</li>
<li>Determine if |policy| is being <i>enforced</i> or <i>monitored</i>. [[!CSP2]]</li>
<li>If |policy| is being <i>enforced</i>, set |event|'s <a>cancelled flag</a> and <a>stop propagation flag</a>.</li>
<li>If |policy| is being <i>monitored</i>, set |event|.{{isUnsafe}} to <i>true</i>.</li>
</ul>
<h3 id='external-spec-integrations'>External Spec Integrations</h3>
<h4 id='event-loop'>HTML Processing Model: Event Loop</h4>
As part of substep 10 of the
<a href="https://html.spec.whatwg.org/multipage/webappapis.html#processing-model-8">
update the rendering</a> event loop in the HTML Processing Model,
<a>Promote Observed GraphicsLayers</a>, passing in <i>now</i> as the timestamp.
<h4 id='dispatching-events-algo'>DOM: Dispatching Events</h4>
As part of <a href="https://dom.spec.whatwg.org/#dispatching-events">dispatching events</a>
in the DOM Standard, add a substep to step 5, ("For each <i>object</i> in <i>event path</i>..."),
invoking step 7 of <a>enforce an input-protection directive</a> before proceeding to
"invoke <i>object</i> with <i>event</i>".
<h4 id='usafe-attribute'>isUnsafe Attribute</h4>
<pre class='idl'>
partial interface Event {
readonly attribute boolean isUnsafe;
};
</pre>
<dl dfn-for='Event'>
<dt><dfn attribute lt='isUnsafe'>isUnsafe</dfn></dt>
<dd>
Will be set to true if the event fired when the event did not
meet the document's <em>input-protection</em> requirements.
</dd>
</dl>
</section>
<!--
████████ ████████ ████ ██ ██ ███ ██████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████
████████ ████████ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ █████████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ████ ███ ██ ██ ██████ ██
-->
<section>
<h2 id='privacy-considerations'>Privacy Considerations</h2>
<em>This section is non-normative.</em>
The timing of visibilityEvents may leak some information across Origin boundaries. An embedded
document might have previously been unable to learn that it was obscured, or the timing and
nature of repositioning of ancestor frame's viewports. In some circumstances, this information
leak might have privacy implications, but the granularity and nature of the information is such
that it should not be of much value to attackers. Compared to anti-clickjacking strategies
which rely on pixel comparisions, the side channels exposed by comparing rectulangar masks are
very low bandwidth. The privacy gains from preventing clickjacking, considered in a holistic
system context, may be quite large.
</section>
<!--
██████ ████████ ██████ ██ ██ ████████ ████ ████████ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ████
██████ ██████ ██ ██ ██ ████████ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██
██████ ████████ ██████ ███████ ██ ██ ████ ██ ██
-->
<section>
<h2 id='security-considerations'>Security Considerations</h2>
<em>This section is non-normative.</em>
UI Redressing and Clickjacking attacks rely on violating the contextual and temporal
integrity of embedded content. Because these attacks target the subjective perception
of the user and not well-defined security boundaries, the heuristic protections
afforded by the input-protection directive can never be 100% effective for every
interface. It provides no protection against certain classes of attacks, such as
displaying content around an embedded resource that appears to extend a trusted
dialog but provides misleading information.
When used as a mechanism to report visibility for purposes of monetizing content,
operators should be aware that a malicious or modified user agent can always report
perfect visibility for content it colludes with. Determining, through remote measurement,
whether an ostensible viewer of monetizable content is using an agent which faithfully
implements and reports in conformance with this specification is out of scope for this
document.
</section>
<!--
███ ██ ██ ██ ██
██ ██ ████ ████ ██ ██
██ ██ ██ ██ ████
██ ██ ██ ██ ██
█████████ ██ ██ ██
██ ██ ██ ██ ██
██ ██ ██████ ██████ ██
-->
<section>
<h2 id='accessibility-considerations'>Accessibility Considerations</h2>
Users of accessibility tools MUST NOT be prevented from accessing content
because of input-protection or VisibilityEvents. If a user agent's interaction
modality is not subject to UI redressing attacks or definitions of "visibility"
do not apply, the user agent SHOULD report a VisibilityEvent indicating 100%
visibility, and SHOULD never fire a violation for any input-protection policy.
</section>