forked from WICG/document-picture-in-picture
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathspec.bs
793 lines (635 loc) · 33.1 KB
/
spec.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
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
<pre class='metadata'>
Title: Document Picture-in-Picture Specification
Repository: WICG/document-picture-in-picture
Shortname: document-pip-spec
Level: 1
Status: CG-DRAFT
Group: WICG
URL: https://wicg.github.io/document-picture-in-picture/
Editor: Tommy Steimel, Google Inc., [email protected]
Abstract: This specification enables web developers to populate an HTMLDocument
Abstract: in an always-on-top window.
</pre>
<pre class="link-defaults">
spec:dom; type:dfn; text:origin
spec:html; type:dfn; for:navigable; text:top-level traversable
spec:html; type:dfn; for:Window; text:navigable
spec:url; type:dfn; for:/; text:url
</pre>
# Introduction # {#intro}
<em>This section is non-normative.</em>
There currently exists a Web API for putting an {{HTMLVideoElement}} into a
Picture-in-Picture window (<code>requestPictureInPicture()</code>). This limits
a website's ability to provide a custom picture-in-picture experience (PiP). We
want to expand upon that functionality by providing the website with a full
{{Document}} on an always-on-top window.
This new window will be much like a blank same-origin window opened via the
existing <a method for="Window">open()</a> method on {{Window}}, with some minor
differences:
- The PiP window will float on top of other windows.
- The PiP window will never outlive the opening window.
- The website cannot set the position of the PiP window.
- The PiP window cannot be navigated (any `window.history` or `window.location`
calls that change to a new document will close the PiP window).
# Dependencies # {#dependencies}
The IDL fragments in this specification must be interpreted as required for
conforming IDL fragments, as described in the Web IDL specification. [[!WEBIDL]]
# Security Considerations # {#security-considerations}
## Secure Context ## {#secure-context}
The API is limited to [[SECURE-CONTEXTS]].
## Spoofing ## {#spoofing}
It is required that the user agent provides enough UI on the
{{DocumentPictureInPicture}} window to prevent malicious websites from abusing
the ability to float on top of other windows to spoof other websites or system
UI.
### Positioning ### {#positioning}
The user agent must prevent the website from setting the position of the window
in order to prevent the website from purposefully positioning the window in a
location that may trick a user into thinking it is part of another page's UI. In
particular, this means the {{Window/moveTo()}} and {{Window/moveBy()}} APIs must
be disabled for document picture-in-picture windows.
### Origin Visibility ### {#origin-visibility}
It is required that the user agent makes it clear to the user which origin is
controlling the {{DocumentPictureInPicture}} window at all times to ensure that
the user is aware of where the content is coming from. For example, the user
agent may display the origin of the website in a titlebar on the window.
### Maximum size ### {#maximum-size}
The user agent should restrict the maximum size of the document
picture-in-picture window to prevent the website from covering the screen with
an always-on-top window and locking the user in the picture-in-picture window.
This also helps prevent spoofing the user's desktop.
## IFrames ## {#iframes}
This API is only available on a <a>top-level traversable</a>. However, the
{{DocumentPictureInPicture}} {{Window}} itself may contain {{HTMLIFrameElement}}s, even
<a href="https://html.spec.whatwg.org/multipage/origin.html#concept-origin">cross-origin</a>
{{HTMLIFrameElement}}s.
# Privacy Considerations # {#privacy-considerations}
## Fingerprinting ## {#fingerprinting}
When a PiP window is closed and then later re-opened, it can be useful for the
user agent to re-use size and location of the previous PiP window to provide a
smoother user experience. However, it is recommended that the user agent does
not re-use size/location across different origins as this may provide malicious
websites an avenue for fingerprinting a user.
# API # {#api}
<pre class="idl">
[Exposed=Window]
partial interface Window {
[SameObject, SecureContext] readonly attribute DocumentPictureInPicture
documentPictureInPicture;
};
[Exposed=Window, SecureContext]
interface DocumentPictureInPicture : EventTarget {
[NewObject] Promise<Window> requestWindow(
optional DocumentPictureInPictureOptions options = {});
readonly attribute Window window;
attribute EventHandler onenter;
};
dictionary DocumentPictureInPictureOptions {
[EnforceRange] unsigned long long width = 0;
[EnforceRange] unsigned long long height = 0;
boolean disallowReturnToOpener = false;
boolean preferInitialWindowPlacement = false;
};
[Exposed=Window, SecureContext]
interface DocumentPictureInPictureEvent : Event {
constructor(DOMString type, DocumentPictureInPictureEventInit eventInitDict);
[SameObject] readonly attribute Window window;
};
dictionary DocumentPictureInPictureEventInit : EventInit {
required Window window;
};
</pre>
<p>
A {{DocumentPictureInPicture}} object allows websites to create and open a new
always-on-top {{Window}} as well as listen for events related to opening and
closing that {{Window}}.
Each {{Window}} object has an associated <dfn for="Window">documentPictureInPicture API</dfn>,
which is a new {{DocumentPictureInPicture}} instance created alongside the {{Window}}.
<div algorithm>
The <dfn attribute for="Window">documentPictureInPicture</dfn> getter steps are:
1. Return <a>this</a>'s <a>documentPictureInPicture API</a>.
</div>
Each {{DocumentPictureInPicture}} object has an associated
<dfn for="DocumentPictureInPicture">last-opened window</dfn> which is a
{{Window}} object that is initially <code>null</code> and is set as part of the
<a>requestWindow()</a> method steps.
<div algorithm>
The <dfn attribute for="DocumentPictureInPicture">window</dfn> getter steps are:
1. Let |win| be <a>this</a>'s <a>last-opened window</a>.
2. If |win| is not <code>null</code> and |win|'s <a for="Window" idl>closed</a>
attribute is <code>false</code>, return |win|.
3. Return <code>null</code>.
</div>
<div algorithm>
The <dfn method for="DocumentPictureInPicture">requestWindow(options)</dfn> method steps are:
1. If <a>Document Picture-in-Picture support</a> is <code>false</code>, throw a
"{{NotSupportedError}}" {{DOMException}}.
2. If <a>this</a>'s <a>relevant global object</a>'s <a>navigable</a> is not a
<a>top-level traversable</a>, throw a "{{NotAllowedError}}"
{{DOMException}}.
3. If <a>this</a>'s <a>relevant global object</a>'s <a>navigable</a>'s
<a>Is Document Picture-in-Picture</a> boolean is <code>true</code>, throw a
"{{NotAllowedError}}" {{DOMException}}.
4. If <a>this</a>'s <a>relevant global object</a> does not have
<a>transient activation</a>, throw a "{{NotAllowedError}}"
{{DOMException}}.
5. If |options|["{{DocumentPictureInPictureOptions/width}}"] exists and is greater than zero, but
|options|["{{DocumentPictureInPictureOptions/height}}"] does not exist or is zero, throw a
{{RangeError}}.
6. If |options|["{{DocumentPictureInPictureOptions/height}}"] exists and is greater than zero, but
|options|["{{DocumentPictureInPictureOptions/width}}"] does not exist or is zero, throw a
{{RangeError}}.
7. <a>Consume user activation</a> given <a>this</a>'s <a>relevant global object</a>.
8. Let |win| be <a>this</a>'s <a>last-opened window</a>. If |win| is not
<code>null</code> and |win|'s <a for="Window" idl>closed</a> attribute is
<code>false</code>, then
<a data-link-type="dfn" href="https://html.spec.whatwg.org/#close-a-top-level-traversable">close</a>
|win|'s <a for="Window" dfn>navigable</a>.
9. Optionally, the user agent can <a>close any existing picture-in-picture windows</a>.
10. Set |pip traversable| to be the result of
<a data-link-type="dfn" href="https://html.spec.whatwg.org/#creating-a-new-top-level-traversable">creating a new top-level traversable</a>
given <a>this</a>'s <a>relevant global object</a>'s <a>navigable</a>'s
<a>active browsing context</a> and "<code>_blank</code>".
<p class="note">
The resulting {{Document}}'s [=Document/URL=] will be `about:blank`, but its
[=document base URL=] will fall back to be that of the initiator that called
{{DocumentPictureInPicture/requestWindow()}}. Some browsers do not implement
this fallback behavior for normal `about:blank` popups; see
<a href="https://github.com/whatwg/html/issues/421">whatwg/html#421</a> for
discussion. Implementers are advised to make sure this inheritance happens as
specified for document picture-in-picture windows, to avoid further interop
problems.
</p>
11. Set |pip traversable|'s <a>Is Document Picture-in-Picture</a> boolean to
<code>true</code>.
12. If |options|["{{DocumentPictureInPictureOptions/width}}"] exists and is
greater than zero:
1. Optionally, clamp or ignore |options|["{{DocumentPictureInPictureOptions/width}}"] if it is too large or too
small in order to fit a user-friendly window size.
2. Optionally, size |pip traversable|'s <a>active browsing context</a>'s
window such that the distance between the left and right edges of the
viewport are |options|["{{DocumentPictureInPictureOptions/width}}"]
pixels.
13. If |options|["{{DocumentPictureInPictureOptions/height}}"] exists and is
greater than zero:
1. Optionally, clamp or ignore |options|["{{DocumentPictureInPictureOptions/height}}"] if it is too large or too
small in order to fit a user-friendly window size.
2. Optionally, size |pip traversable|'s <a>active browsing context</a>'s
window such that the distance between the top and bottom edges of the
viewport are |options|["{{DocumentPictureInPictureOptions/height}}"]
pixels.
If |options|["{{DocumentPictureInPictureOptions/preferInitialWindowPlacement}}"]
exists and is true, then the user agent may use this hint to prefer behavior
that is similar that is similar to steps 12 and 13, rather than considering any
previous position or size of any previously closed |pip traversable| window.
14. If |options|["{{DocumentPictureInPictureOptions/disallowReturnToOpener}}"] exists
and is <code>true</code>, the user agent should not display UI affordances
on the picture-in-picture window that allow the user to return to the
opener window.
<p class="note">
For both video and document picture-in-picture, user agents often display a
button for the user to return to the original page and close the
picture-in-picture window. While this action makes sense in most cases
(especially for a video picture-in-picture window that returns the video to the
main document), it does not always make sense for document picture-in-picture
windows. {{DocumentPictureInPictureOptions/disallowReturnToOpener}} is a hint to
the user agent from the website as to whether that action makes sense for their
particular document picture-in-picture experience.
</p>
15. Configure |pip traversable|'s <a>active browsing context</a>'s window to
float on top of other windows.
16. Set <a>this</a>'s <a>last-opened window</a> to |pip traversable|'s <a>active window</a>.
17. <a>Queue a global task</a> on the
<a data-link-type="idl" href="https://html.spec.whatwg.org/multipage/webappapis.html#dom-manipulation-task-source">DOM manipulation task source</a>
given <a>this</a>'s <a>relevant global object</a> to <a>fire an event</a>
named {{enter}} using {{DocumentPictureInPictureEvent}} on
<a>this</a> with its {{DocumentPictureInPictureEvent/window}} attribute
initialized to |pip traversable|'s <a>active window</a>.
18. Return |pip traversable|'s <a>active window</a>.
</div>
<p class="note">
While the size of the window can be configured by the website, the initial
position is left to the discretion of the user agent.
</p>
</p>
: <dfn event for="DocumentPictureInPicture">enter</dfn>
:: Fired on {{DocumentPictureInPicture}} when a PiP window is opened.
# Concepts # {#concepts}
## Document Picture-in-Picture Support ## {#pip-support}
Each user agent has a <dfn>Document Picture-in-Picture Support</dfn> boolean,
whose value is <a>implementation-defined</a> (and might vary according to user
preferences).
## DocumentPictureInPicture Window ## {#is-document-picture-in-picture-window}
Each <a>top-level traversable</a> has an <dfn>Is Document Picture-in-Picture</dfn>
boolean, whose value defaults to <code>false</code>, but can be set to
<code>true</code> in the <a>requestWindow()</a> method steps.
## Closing a Document Picture-in-Picture window ## {#close-document-pip-window}
<p class="issue">
Merge this into
<a dfn href="https://html.spec.whatwg.org/#close-a-top-level-traversable">close</a>
once it has enough consensus.
</p>
Modify step 2 of
<a dfn href="https://html.spec.whatwg.org/#close-a-top-level-traversable">close</a>,
"If the result of checking if unloading is user-canceled for toUnload is true,
then return." to be:
2. If <var ignore>traversable</var>'s <a>Is Document Picture-in-Picture</a> boolean is
true, then skip this step. Otherwise, if the result of
<a href="https://html.spec.whatwg.org/#checking-if-unloading-is-user-canceled" dfn>checking if unloading is user-canceled</a>
for toUnload is true, then return.
## Close any existing PiP windows ## {#close-existing-pip-windows}
To <dfn>close any existing picture-in-picture windows</dfn>:
1. For each |top-level traversable| of the user agent's
<a data-link-type="dfn" href="https://html.spec.whatwg.org/#top-level-traversable-set">top-level traversable set</a>:
1. If |top-level traversable|'s <a>Is Document Picture-in-Picture</a>
boolean is <code>true</code>, then
<a data-link-type="dfn" href="https://html.spec.whatwg.org/#close-a-top-level-traversable">close</a>
|top-level traversable|.
2. If |top-level traversable|'s <a>active document</a>'s
<a data-link-type="idl" href="https://w3c.github.io/picture-in-picture/#dom-documentorshadowroot-pictureinpictureelement">pictureInPictureElement</a>
is not <code>null</code>, run the
<a data-link-type="dfn" href="https://w3c.github.io/picture-in-picture/#exit-picture-in-picture-algorithm">exit Picture-in-Picture algorithm</a>
with |top-level traversable|'s <a>active document</a>.
3. For each |navigable| of |top-level traversable|'s <a>active document</a>'s
<a>descendant navigables</a>:
1. If |navigable|'s <a>active document</a>'s
<a data-link-type="idl" href="https://w3c.github.io/picture-in-picture/#dom-documentorshadowroot-pictureinpictureelement">pictureInPictureElement</a>
is not <code>null</code>, run the
<a data-link-type="dfn" href="https://w3c.github.io/picture-in-picture/#exit-picture-in-picture-algorithm">exit Picture-in-Picture algorithm</a>
with |navigable|'s <a>active document</a>.
## One PiP Window ## {#one-pip-window}
Any <a>top-level traversable</a> must have at most one document
picture-in-picture window open at a time. If a <a>top-level traversable</a>
whose <a>active window</a>'s <a>documentPictureInPicture API</a>'s
<a>last-opened window</a> is not <code>null</code> tries to open another
document picture-in-picture window, the user agent must close the existing
<a>last-opened window</a> as described in the <a>requestWindow()</a> method
steps.
However, whether only one window is allowed in Picture-in-Picture mode across
all <a>top-level traversables</a> is left to the implementation and the platform.
As such, what happens when there is a Picture-in-Picture request while there is
a <a>top-level traversable</a> whose <a>Is Document Picture-in-Picture</a>
boolean is <code>true</code> or whose <a>active document</a>'s
<a data-link-type="idl" href="https://w3c.github.io/picture-in-picture/#dom-documentorshadowroot-pictureinpictureelement">pictureInPictureElement</a>
is not <code>null</code> will be left as an implementation detail: the user
agent could <a>close any existing picture-in-picture windows</a> or multiple
Picture-in-Picture windows could be created.
## Closing the PiP window when either the original or PiP document is destroyed ## {#close-on-destroy}
To <dfn>close any associated Document Picture-in-Picture windows</dfn> given a
{{Document}} |document|:
1. Let |navigable| be |document|'s <a>node navigable</a>.
2. If |navigable| is not a <a>top-level traversable</a>, abort these steps.
3. If |navigable|'s <a>Is Document Picture-in-Picture</a> boolean is
<code>true</code>, then
<a dfn href="https://html.spec.whatwg.org/#close-a-top-level-traversable">close</a>
|navigable| and abort these steps.
4. Let |win| be |navigable|'s <a>active window</a>'s
<a>documentPictureInPicture API</a>'s <a>last-opened window</a>.
5. If |win| is not <code>null</code> and |win|'s <a for="Window" idl>closed</a>
attribute is <code>false</code>, then
<a dfn href="https://html.spec.whatwg.org/#close-a-top-level-traversable">close</a>
|win|'s <a for="Window" dfn>navigable</a>.
<p class="issue">
Merge this into
<a data-link-type="dfn" href="https://html.spec.whatwg.org/#destroying-documents">destroy</a>
once it has enough consensus.
</p>
Add a step 10 to the end of
<a data-link-type="dfn" href="https://html.spec.whatwg.org/#destroying-documents">destroy</a>:
5. <a>Close any associated Document Picture-in-Picture windows</a> given |document|.
<p class="note">
This ensures that when a page with an open Document Picture-in-Picture window is
closed, then its PiP window is closed as well.
</p>
## Closing the PiP window when either the original or PiP document is navigated ## {#close-on-navigate}
<p class="issue">
Merge this into
<a data-link-type="dfn" href="https://html.spec.whatwg.org/#navigate">navigate</a>
once it has enough consensus.
</p>
Modify step 16.3 of
<a data-link-type="dfn" href="https://html.spec.whatwg.org/#navigate">navigate</a>,
"Queue a global task on the navigation and traversal task source given navigable's active window to abort navigable's active document.",
and also insert a step 16.4 immediately after it:
3. <a>Queue a global task</a> on the
<a dfn href="https://html.spec.whatwg.org/#navigation-and-traversal-task-source">navigation and traversal task source</a>
given |navigable|'s <a>active window</a> to
<a dfn href="https://html.spec.whatwg.org/#abort-a-document">abort</a>
|navigable|'s <a>active document</a> and <a>close any associated Document Picture-in-Picture windows</a>
given |navigable|'s <a>active document</a>.
4. If |navigable| is a <a>top-level traversable</a> whose
<a>Is Document Picture-in-Picture</a> boolean is <code>true</code>, then
abort these steps.
<p class="note">
This ensures that when a page with an open Document Picture-in-Picture window is
navigated, then its PiP window is closed as well. It also ensures that when the
document in a Document Picture-in-Picture window is navigated, the Document
Picture-in-Picture window is closed.
</p>
## Resizing the PiP window ## {#resizing-the-pip-window}
<p>
While programmatically resizing a document picture-in-picture window can be
useful, the always-on-top nature of the window means an unrestricted ability
to resize the window could be abused in annoying or intrusive way. To mitigate
these concerns without completely preventing the use of window resize APIs, we
will have those APIs consume a user gesture for document picture-in-picture
windows.
</p>
<p class="issue">
Merge this into {{Window/resizeTo()}} once it has enough consensus.
</p>
Add a new step to {{Window/resizeTo()}} after step 3, "If |target| is not an
[=auxiliary browsing context=] that was created by a script (as opposed to by an
action of the user), then return.":
4. If |target|'s <a>top-level traversable</a>'s
<a>Is Document Picture-in-Picture</a> boolean is <code>true</code>, then:
1. If <a>this</a>'s <a>relevant global object</a> does not have
<a>transient activation</a>, throw a "{{NotAllowedError}}"
{{DOMException}}.
2. <a>Consume user activation</a> given <a>this</a>'s <a>relevant global object</a>.
<p class="issue">
Merge this into {{Window/resizeBy()}} once it has enough consensus.
</p>
Add a new step to {{Window/resizeBy()}} after step 3, "If |target| is not an
[=auxiliary browsing context=] that was created by a script (as opposed to by an
action of the user), then return.":
4. If |target|'s <a>top-level traversable</a>'s
<a>Is Document Picture-in-Picture</a> boolean is <code>true</code>, then:
1. If <a>this</a>'s <a>relevant global object</a> does not have
<a>transient activation</a>, throw a "{{NotAllowedError}}"
{{DOMException}}.
2. <a>Consume user activation</a> given <a>this</a>'s <a>relevant global object</a>.
## Focusing the opener window ## {#focusing-the-opener-window}
<p>
It can often be useful for the picture-in-picture window to be able to re-focus
its opener tab, e.g. when the smaller form-factor of the window doesn't fit the
experience the user needs. We modify the {{Window/focus()}} API to allow it to
take system-level focus when a picture-in-picture window is focusing its
opener.
</p>
<p class="issue">
Merge this into {{Window/focus()}} once it has enough consensus.
</p>
Add a new step to {{Window/focus()}} after step 3, "Run the <a>focusing steps</a> with |current|.":
4. If |current| is a <a>top-level traversable</a>, then:
1. Let |pipWindow| be |current|'s <a>active window</a>'s
<a>documentPictureInPicture API</a>'s <a>last-opened window</a>.
2. If |pipWindow| is not <code>null</code> and |pipWindow|'s <a>relevant global object</a>
has <a>transient activation</a>, then:
1. <a>Consume user activation</a> given |pipWindow|'s <a>relevant global object</a>.
2. Give |current| <a>system focus</a>.
<p class="note">
Giving system focus to the opener does not necessarily need to close the
document picture-in-picture window. If the website wants to close the document
picture-in-picture window after focusing, they can always do so using
{{Window/close()}} on the document picture-in-picture window itself.
</p>
## CSS display-mode ## {#css-display-mode}
<p>
The CSS display mode media feature ''@media/display-mode/picture-in-picture'' lets web developers
write specific CSS rules that are only applied when (part of the) the web app is shown in
picture-in-picture mode.
</p>
## User activation propagation ## {#user-activation-propagation}
<p>
Due to the nature of document picture-in-picture windows, event handlers on
buttons within the window often end up actually running in the opener's context.
This can make it unergonomic for websites to call
<a href="https://html.spec.whatwg.org/multipage/interaction.html#activation-consuming-api">activation consuming APIs</a>,
since sometimes the document
picture-in-picture window has <a>transient activation</a> while the opener does
not.
To make this easier, we will update the
<a href="https://html.spec.whatwg.org/multipage/interaction.html#activation-notification">activation notification</a>
steps to also trigger user activation in the opener when triggering user
activation in a document picture-in-picture window. Additionally, when user
activation is triggered in the opener, we will activate same-origin frames
insides the document picture-in-picture window, similar to how same-origin
descendant frames are activated.
</p>
<p class="issue">
Merge this into
<a href="https://html.spec.whatwg.org/multipage/interaction.html#activation-notification">activation notification</a>
steps once it has enough consensus.
</p>
Add three new steps to
<a href="https://html.spec.whatwg.org/multipage/interaction.html#activation-notification">activation notification</a>
after step 4, "<a>Extend</a>
|windows| with the <a>active window</a> of each of |document|'s
<a>descendant navigables</a>, filtered to include only those <a>navigables</a>
whose <a>active document</a>'s <a>origin</a> is <a>same origin</a> with
|document|'s <a>origin</a>":
5. If |document|'s <a>node navigable</a>'s <a>top-level traversable</a>'s
<a>Is Document Picture-in-Picture</a> boolean is <code>true</code>, then
<a>extend</a> |windows| with |document|'s <a>node navigable</a>'s
<a>top-level traversable</a>'s <a>active browsing context</a>'s
<a>opener browsing context</a>'s <a>active window</a>.
6. Let |document picture-in-picture window| be |document|'s <a>node navigable</a>'s
<a>top-level traversable</a>'s <a>active window</a>'s
<a>documentPictureInPicture API</a>'s <a>last-opened window</a>.
7. If |document picture-in-picture window| is not <code>null</code> then
<a>extend</a> |windows| with the <a>active window</a> of each of
|document picture-in-picture window|'s <a>associated document</a>'s
<a>descendant navigables</a>, filtered to include only those
<a>navigables</a> whose <a>active document</a>'s <a>origin</a> is
<a>same origin</a> with |document picture-in-picture window|'s
<a>associated document</a>'s <a>origin</a>.
<p>
Additionally, we need to make sure that this activation is properly consumed so
it can't be used twice (once in the opener and once in the picture-in-picture
window). We do this by adding steps to <a>consume user activation</a> which
consume user activation from the opener when consuming a picture-in-picture
window's user activation, and consuming an associated picture-in-picture
window's user activation when consuming an opener's user activation.
</p>
<p class="issue">
Merge this into <a>consume user activation</a> steps once it has enough
consensus.
</p>
Add three new steps to <a>consume user activation</a> after step 3, "Let
|navigables| be the <a>inclusive descendant navigables</a> of |top|'s
<a>active document</a>.":
4. If |top|'s <a>Is Document Picture-in-Picture</a> boolean is
<code>true</code>, then <a>extend</a> |navigables| with the
<a>inclusive descendant navigables</a> of |top|'s
<a>active browsing context</a>'s <a>opener browsing context</a>'s
<a>active document</a>.
5. Let |document picture-in-picture window| be |top|'s <a>active window</a>'s
<a>documentPictureInPicture API</a>'s <a>last-opened window</a>.
6. If |document picture-in-picture window| is not <code>null</code> then
<a>extend</a> |navigables| with the <a>inclusive descendant navigables</a>
of |document picture-in-picture window|'s <a>associated document</a>.
# Examples # {#examples}
<em>This section is non-normative</em>
## Extracting a video player into PiP ## {#example-video-player}
### HTML ### {#example-video-player-html}
<pre class="lang-html">
<body>
<div id="player-container">
<div id="player">
<video id="video" src="foo.webm"></video>
<!-- More player elements here. -->
</div>
</div>
<input type="button" onclick="enterPiP();" value="Enter PiP" />
</body>
</pre>
### JavaScript ### {#example-video-player-js}
<pre class="lang-javascript">
// Handle to the picture-in-picture window.
let pipWindow = null;
function enterPiP() {
const player = document.querySelector('#player');
// Set the width/height so the window is properly sized to the video.
const pipOptions = {
width: player.clientWidth,
height: player.clientHeight,
};
documentPictureInPicture.requestWindow(pipOptions).then((pipWin) => {
pipWindow = pipWin;
// Style remaining container to imply the player is in PiP.
playerContainer.classList.add('pip-mode');
// Add player to the PiP window.
pipWindow.document.body.append(player);
// Listen for the PiP closing event to put the video back.
pipWindow.addEventListener('pagehide', onLeavePiP.bind(pipWindow), { once: true });
});
}
// Called when the PiP window has closed.
function onLeavePiP() {
if (this !== pipWindow) {
return;
}
// Remove PiP styling from the container.
const playerContainer = document.querySelector('#player-container');
playerContainer.classList.remove('pip-mode');
// Add the player back to the main window.
const player = pipWindow.document.querySelector('#player');
playerContainer.append(player);
pipWindow = null;
}
</pre>
## Accessing elements on the PiP Window ## {#example-access-elements}
<pre class="lang-javascript">
const video = pipWindow.document.querySelector('#video');
video.loop = true;
</pre>
## Listening to events on the PiP Window ## {#example-listen-events}
As part of creating an improved picture-in-picture experience, websites will often want
customize buttons and controls that need to respond to user input events such as clicks.
<pre class="lang-javascript">
const pipDocument = pipWindow.document;
const video = pipDocument.querySelector('#video');
const muteButton = pipDocument.document.createElement('button');
muteButton.textContent = 'Toggle mute';
muteButton.addEventListener('click', () => {
video.muted = !video.muted;
});
pipDocument.body.append(muteButton);
</pre>
## Exiting PiP ## {#example-exiting-pip}
The website may want to close the {{DocumentPictureInPicture}} {{Window}}
without the user explicitly clicking on the window's close button. They can do
this by using the <a method for="Window">close()</a> method on the {{Window}}
object:
<pre class="lang-javascript">
// This will close the PiP window and trigger our existing onLeavePiP()
// listener.
pipWindow.close();
</pre>
## Getting elements out of the PiP window when it closes ## {#example-elements-out-on-close}
When the PiP window is closed for any reason (either because the website
initiated it or the user closed it), the website will often want to get the
elements back out of the PiP window. The website can perform this in an event
handler for the {{Window/pagehide}} event on the
{{Window}} object. This is shown in the
<code>onLeavePiP()</code> handler in
<a href="#example-video-player">video player example</a> above and is copied
below:
<pre class="lang-javascript">
// Called when the PiP window has closed.
function onLeavePiP() {
if (this !== pipWindow) {
return;
}
// Remove PiP styling from the container.
const playerContainer = document.querySelector('#player-container');
playerContainer.classList.remove('pip-mode');
// Add the player back to the main window.
const player = pipWindow.document.querySelector('#player');
playerContainer.append(player);
pipWindow = null;
}
</pre>
## Programatically resize the PiP window ## {#example-programmatic-resize}
The document picture-in-picture window supports the {{Window/resizeTo()}} and
{{Window/resizeBy()}} APIs, but only with a user gesture on the PiP window:
<pre class="lang-javascript">
const expandButton = pipWindow.document.createElement('button');
expandButton.textContent = 'Expand PiP Window';
expandButton.addEventListener('click', () => {
// Expand the PiP window's width by 20px and height by 30px.
pipWindow.resizeBy(20, 30);
});
pipWindow.document.body.append(expandButton);
</pre>
## Return to the opener tab ## {#example-return-to-tab}
The {{Window/focus()}} API can be used to focus the opener tab from a
picture-in-picture window (requiring a user gesture):
<pre class="lang-javascript">
const returnToTabButton = pipWindow.document.createElement('button');
returnToTabButton.textContent = 'Return to opener tab';
returnToTabButton.addEventListener('click', () => {
window.focus();
});
pipWindow.document.body.append(returnToTabButton);
</pre>
## CSS picture-in-picture display mode usage ## {#example-display-mode}
The following example shows how to remove margins on the body element
and reduce the font size of titles in PiP window to better fit the
content in question inside the PiP window:
<pre class="lang-css">
@media all and (display-mode: picture-in-picture) {
body {
margin: 0;
}
h1 {
font-size: 0.8em;
}
}
</pre>
## Hide return-to-opener button ## {#example-hide-return-to-opener}
While user agents often display a button on their video and document
picture-in-picture windows to return to the opener and close the window,
this button doesn't always make sense for some websites' document
picture-in-picture experience. Use the
{{DocumentPictureInPictureOptions/disallowReturnToOpener}} option to hide the
button.
<pre class="lang-javascript">
await documentPictureInPicture.requestWindow({
disallowReturnToOpener: true
});
</pre>
## Prefer initial window placement ## {#example-prefer-initial-window-placement}
While a document picture-in-picture window is open, the user may manually
resize or reposition it. If the document picture-in-picture window is closed,
then reopened later, the user agent may use the previous position and size as
a hint for where to place the new window rather than opening it in is original,
default position.
The site can provide a hint to the user agent that reusing the previous
document picture-in-picture window position and size is not desirable
by setting the {{DocumentPictureInPictureOptions/preferInitialWindowPlacement}}
value to true. For example, if the site is requesting the new document
picture-in-picture window for an unrelated activity from the previous one, then
the site might provide this hint to the user agent. In response, the user
agent may choose to use the default position, the default size, or the size
hint provided by the site instead.
<pre class="lang-javascript">
await documentPictureInPicture.requestWindow({
preferInitialWindowPlacement: true
});
</pre>
# Acknowledgments # {#acknowledgments}
Many thanks to Frank Liberato, Mark Foltz, Klaus Weidner, François Beaufort,
Charlie Reis, Joe DeBlasio, Domenic Denicola, and Yiren Wang for their comments
and contributions to this document and to the discussions that have informed it.