1. Introduction
This section is non-normative.
There currently exists a Web API for putting an HTMLVideoElement
into a
Picture-in-Picture window (requestPictureInPicture()
). 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 open() 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).
-
The PiP window cannot open more windows.
2. Dependencies
The IDL fragments in this specification must be interpreted as required for conforming IDL fragments, as described in the Web IDL specification. [WEBIDL]
3. Security Considerations
3.1. Secure Context
The API is limited to [SECURE-CONTEXTS].
3.2. Spoofing
It is recommended 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. The website’s inability to directly set the location or size of the PiP
window helps alleviate some avenues of abuse.
It is recommended that the user agent makes it clear to the user which origin is
controlling the DocumentPictureInPicture
window at all times.
3.3. IFrames
This API is only available on a top-level browsing context. However, the DocumentPictureInPicture
Window
itself may contain HTMLIFrameElement
s, even cross-origin HTMLIFrameElement
s.
4. Privacy Considerations
4.1. 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 (modulo aspect-ratio constraints) 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.
5. API
[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 = 0; [
width EnforceRange ]unsigned long long = 0;
height double = 0.0;
initialAspectRatio boolean =
copyStyleSheets false ; }; [Exposed =Window ]interface :
DocumentPictureInPictureEvent Event {(
constructor DOMString ,
type DocumentPictureInPictureEventInit ); [
eventInitDict SameObject ]readonly attribute Window ; };
window dictionary :
DocumentPictureInPictureEventInit EventInit {required Window ; };
window
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 documentPictureInPicture API,
which is a new DocumentPictureInPicture
instance created alongside the Window
.
documentPictureInPicture
getter steps are:
-
Return this’s documentPictureInPicture API.
window
getter steps are:
requestWindow(options)
method steps are:
-
If Document Picture-in-Picture support is
false
, throw a "NotSupportedError
"DOMException
and abort these steps. -
If the relevant global object of this is not a top-level browsing context, throw a "
NotAllowedError
"DOMException
and abort these steps. -
If the relevant global object of this is a DocumentPictureInPicture
Window
, throw a "NotAllowedError
"DOMException
and abort these steps. -
If the relevant global object of this does not have transient activation, throw a "
NotAllowedError
"DOMException
and abort these steps. -
The user agent may choose to close any existing DocumentPictureInPicture
Window
s or PictureInPictureWindows. -
Let target browsing context be a new browsing context navigated to the
about:blank
URL. -
If options["
width
"] exists and is greater than zero: -
If options["
height
"] exists and is greater than zero: -
If options["
initialAspectRatio
"] exists and is greater than zero:-
If options["
width
"] and options["height
"] have been specified and don’t match options["initialAspectRatio
"], the user agent may ignore options["initialAspectRatio
"]. -
Optionally, clamp or ignore options["
initialAspectRatio
"] if it is too large or too small in order to fit a user-friendly window size. -
Set the window size for the target browsing context to a width and height such that width divided by height is approximately options["
initialAspectRatio
"].
-
-
Configure the window containing target browsing context to float on top of other windows.
-
If options["
copyStyleSheets
"] exists and istrue
, then the CSS style sheets applied the current associated Document should be copied and applied to the target browsing context’s associated Document. This is a one-time copy, and any further changes to the current associated Document’s CSS style sheets will not be copied. -
Queue a global task on the DOM manipulation task source given this’s relevant global object to fire an event named
enter
usingDocumentPictureInPictureEvent
on this with itsbubbles
attribute initialized totrue
and itswindow
attribute initialized to target browsing context. -
Return target browsing context.
While the aspect ratio or size of the window can be configured by the website, the initial position is left to the discretion of the user agent.
enter
-
Fired on
DocumentPictureInPicture
when a PiP window is opened.
6. Concepts
6.1. Document Picture-in-Picture Support
Document Picture-in-Picture Support is false
if there’s
a user preference that disables it or a platform limitation. It is true
otherwise.
6.2. One PiP Window
Whether only one window is allowed in Picture-in-Picture mode is left to the
implementation and the platform. As such, what happens when there is a
Picture-in-Picture request while a DocumentPictureInPicture
Window
or PictureInPictureWindow is already open will be left as an implementation detail: the current window
could be closed, the Picture-in-Picture request could be rejected, or multiple
Picture-in-Picture windows could be created. Regardless, the user agent must
fire the appropriate events in order to notify the websites of the
Picture-in-Picture status changes.
6.3. Relative URLs
A primary use case of DocumentPictureInPicture is to put existing elements (e.g.
an HTMLVideoElement
) into an always-on-top window so the user can continue
to see them while multitasking. However, sometimes these elements have
attributes that use a relative-URL string (e.g. src). Since the Document
in a DocumentPictureInPicture
Window
is always navigated to the about:blank
URL, these relative-URL strings would
break. To prevent this, the user agent must parse relative-URL strings as
if they were being parsed on the Document
that opened the DocumentPictureInPicture
Window
.
7. Examples
This section is non-normative
7.1. Extracting a video player into PiP
7.1.1. 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 >
7.1.2. JavaScript
// Handle to the picture-in-picture window. let pipWindow= null ; function enterPiP() { const player= document. querySelector( '#player' ); // Set the aspect ratio so the window is properly sized to the video. const pipOptions= { initialAspectRatio: player. clientWidth/ player. clientHeight, copyStyleSheets: true }; 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( 'unload' , 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 ; }
7.2. Accessing elements on the PiP Window
const video= pipWindow. document. querySelector( '#video' ); video. loop= true ;
7.3. Listening to events on the PiP Window
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.
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);
7.4. 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 close() method on the Window
object:
// This will close the PiP window and trigger our existing onLeavePiP() // listener. pipWindow. close();
7.5. Getting elements out of the PiP window when it closes
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 unload
event on the Window
object. This is shown in the onLeavePiP()
handler in video player example above and is copied
below:
// 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 ; }
8. 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.