Skip to content

Commit 9d88d7b

Browse files
Get rid of in-parallel mutation of DOM and resolving of promise when not possible
Restructures the requestPictureInPicture() algorithm to handle parallel window creation more robustly. The algorithm now creates the Picture-in-Picture window in parallel, then queues subsequent DOM mutations and promise resolutions as a separate task. Key changes: - Steps 1-9 run synchronously with validation checks - Step 10 returns the promise and runs remaining steps in parallel - Within the parallel section, a global task is queued to handle DOM mutations, fire events, and resolve/reject the promise - Enforces one Picture-in-Picture element per top-level browsing context - Mandatorily exits fullscreen when entering PiP - Exits previous PiP session when a new one is requested Addresses #244, #233, and #229. Co-authored-by: Marcos Cáceres <marcos@marcosc.com>
1 parent fa78aaa commit 9d88d7b

1 file changed

Lines changed: 60 additions & 50 deletions

File tree

index.bs

Lines changed: 60 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ spec:dom; type:dfn; text:origin
3939
spec:dom; type:interface; text:Document
4040
spec:html; type:attribute; for:HTMLMediaElement; text:readyState
4141
spec:html; type:dfn; for:/; text:browsing context
42+
spec:html; type:dfn; text:allowed to use
4243
spec:infra; type:dfn; text:user agent
4344
spec:infra; type:dfn; for:list; text:item
4445
</pre>
@@ -167,53 +168,31 @@ to initiate and control this behavior by exposing the following sets of properti
167168

168169
# Concepts # {#concepts}
169170

170-
## Internal Slot Definitions ## {#defines}
171+
## Definitions ## {#defines}
172+
173+
A <dfn>Picture-in-Picture window</dfn> is a window displaying the <{video}> element.
174+
175+
A {{DocumentOrShadowRoot}} has:
176+
177+
1. A <dfn>Picture-in-Picture element</dfn>, which is an {{Element}} or `null`, initially `null`.
178+
179+
A <a for="/">traversable navigable</a> has:
180+
1. A <dfn>picture-in-picture parallel queue</dfn>, which is a [=parallel queue=] created by
181+
[=starting a new parallel queue=].
182+
171183

172184
A <a>user agent</a> has:
173185

174186
1. An <dfn>initiators of active Picture-in-Picture sessions</dfn>
175187
list of zero or more <a>origins</a>, which is initially empty.
176188

177-
Note: In case a <a>user agent</a> supports multiple Picture-in-Picture
178-
windows, the list allows duplicates.
189+
Note: In case a <a>user agent</a> supports multiple Picture-in-Picture windows, the list allows duplicates.
179190

180191
An origin is said to have an active Picture-in-Picture session if any
181192
of the origins in <a>initiators of active Picture-in-Picture sessions</a>
182193
are <a>same origin-domain</a> with origin.
183194

184-
## Request Picture-in-Picture ## {#request-pip}
185-
186-
When the <dfn>request Picture-in-Picture algorithm</dfn> with |video| is invoked,
187-
the user agent MUST run the following steps:
188-
189-
1. If <a>Picture-in-Picture support</a> is `false`, throw a
190-
{{NotSupportedError}} and abort these steps.
191-
2. If the document is not <a>allowed to use</a> the <a>policy-controlled feature</a>
192-
named `"picture-in-picture"`, throw a {{SecurityError}} and abort these
193-
steps.
194-
3. If |video|'s {{readyState}} attribute is {{HAVE_NOTHING}}, throw a
195-
{{InvalidStateError}} and abort these steps.
196-
4. If |video| has no video track, throw a {{InvalidStateError}} and abort
197-
these steps.
198-
5. If |video|'s {{HTMLVideoElement/disablePictureInPicture}} is true,
199-
the user agent MAY throw an {{InvalidStateError}} and abort these steps.
200-
6. If {{pictureInPictureElement}} is `null`:
201-
1. If <a>this</a>'s <a>relevant global object</a> does not have
202-
<a>transient activation</a>, throw a {{NotAllowedError}} and abort these steps.
203-
2. [=Consume user activation=] given <a>this</a>'s <a>relevant global object</a>.
204-
7. If |video| is {{pictureInPictureElement}}, abort these steps.
205-
8. Set {{pictureInPictureElement}} to |video|.
206-
9. Let <dfn>Picture-in-Picture window</dfn> be a new instance of
207-
{{PictureInPictureWindow}} associated with {{pictureInPictureElement}}.
208-
10. Append <a>relevant settings object</a>'s <a>origin</a> to
209-
<a>initiators of active Picture-in-Picture sessions</a>.
210-
11. <a>Queue a task</a> to <a>fire an event</a> named
211-
{{enterpictureinpicture}} using {{PictureInPictureEvent}} at the
212-
|video| with its {{bubbles}} attribute initialized to `true` and its
213-
{{PictureInPictureEvent/pictureInPictureWindow}} attribute initialized to
214-
<a>Picture-in-Picture window</a>.
215-
12. If {{pictureInPictureElement}} is <a>fullscreenElement</a>, it is
216-
RECOMMENDED to <a>exit fullscreen</a>.
195+
## Picture-in-Picture ## {#pip}
217196

218197
It is RECOMMENDED that video frames are not rendered in the page and in the
219198
Picture-in-Picture window at the same time but if they are, they MUST be kept
@@ -233,7 +212,7 @@ It is also RECOMMENDED that the Picture-in-Picture window has a maximum and
233212
minimum size. For example, it could be restricted to be between a quarter and
234213
a half of one dimension of the screen.
235214

236-
When a {{DocumentOrShadowRoot}}'s {{pictureInPictureElement}} attribute is set,
215+
When a {{DocumentOrShadowRoot}}'s <a>Picture-in-Picture element</a> is set,
237216
the [=Picture-in-Picture window=] MUST be visible, even when the
238217
{{DocumentOrShadowRoot}}'s [=relevant global object=]'s [=associated Document=]'s
239218
[=Document/visibility state=] is "hidden".
@@ -332,16 +311,48 @@ partial interface HTMLVideoElement {
332311
};
333312
</pre>
334313

335-
The {{requestPictureInPicture()}} method, when invoked, MUST
336-
return <a>a new promise</a> |promise| and run the following steps <a>in
337-
parallel</a>:
314+
The {{requestPictureInPicture()}} method steps <dfn export>request Picture-in-Picture</dfn>:
315+
316+
1. If [=Picture-in-Picture support=] is `false`, return [=a promise rejected with=] {{NotSupportedError}} {{DOMException}}.
317+
2. Let |doc| be [=this=]'s [=node document=].
318+
3. If |doc| is not [=allowed to use=] the [=policy-controlled feature=]
319+
named `"picture-in-picture"`, return [=a promise rejected with=] {{NotAllowedError}} {{DOMException}}.
320+
4. If [=this=]'s {{readyState}} attribute is {{HAVE_NOTHING}}, return [=a promise rejected with=] {{InvalidStateError}} {{DOMException}}.
321+
5. If [=this=] has no video track, return [=a promise rejected with=] {{InvalidStateError}} {{DOMException}}.
322+
6. If [=this=]'s {{HTMLVideoElement/disablePictureInPicture}} is true, user agent may return [=a promise rejected with=] {{InvalidStateError}} {{DOMException}}.
323+
7. If |doc|'s [=Picture-in-Picture element=] is `null`:
324+
1. If [=this=]'s [=relevant global object=] does not have a [=transient activation=], then return [=a
325+
promise rejected with=] {{NotAllowedError}} {{DOMException}}.
326+
2. [=Consume user activation=] given [=this=]'s [=relevant global object=].
327+
8. If [=this=] is |doc|'s [=Picture-in-Picture element=]:
328+
1. Return [=a promise resolved with=] the [=Picture-in-Picture window=] associated with |doc|'s [=Picture-in-Picture element=].
329+
9. Let |global| be [=this=]'s [=relevant global object=].
330+
10. Let |p| be [=a new promise=] created in [=this=]'s [=relevant realm=].
331+
11. Return |p|, and [=enqueue the following steps=] to |doc|'s [=picture-in-picture parallel queue=]:
332+
1. If [=this=] is |doc|'s [=Picture-in-Picture element=]:
333+
1. [=Queue a global task=] on the [=media element event task source=] given |global| to
334+
[=/resolve=] |p| with the [=Picture-in-Picture window=] associated with
335+
[=this=].
336+
2. Abort these steps.
337+
2. Attempt to associate a [=Picture-in-Picture window=] with [=this=].
338+
3. If the previous step failed:
339+
1. [=Queue a global task=] on the [=media element event task source=] given |global| to
340+
[=/reject=] |p| with {{InvalidStateError}} {{DOMException}}.
341+
2. Abort these steps.
342+
4. Let |pipWindow| be a new instance of {{PictureInPictureWindow}} that represents [=this=]'s associated [=Picture-in-Picture window=].
343+
5. [=Queue a global task=] on the [=media element event task source=] given |global|, to perform
344+
the following steps:
345+
1. If {{pictureInPictureElement}} is not `null`, run the [=exit Picture-in-Picture algorithm=].
346+
2. Set |doc|'s [=Picture-in-Picture element=] to [=this=].
347+
3. [=list/Append=] [=relevant settings object=]'s [=origin=] to [=initiators of active
348+
Picture-in-Picture sessions=].
349+
4. If [=this=] is [=fullscreenElement=], [=exit fullscreen=].
350+
5. [=Fire an event=] named {{enterpictureinpicture}} using {{PictureInPictureEvent}} at
351+
[=this=] with its {{bubbles}} attribute initialized to `true` and its
352+
{{PictureInPictureEvent/pictureInPictureWindow}} attribute initialized to
353+
[=Picture-in-Picture window=].
354+
6. [=/Resolve=] |p| with |pipWindow|.
338355

339-
1. Let |video| be the video element on which the method was invoked.
340-
2. Run the <a>request Picture-in-Picture algorithm</a> with |video|.
341-
3. If the previous step threw an exception, reject |promise| with that
342-
exception and abort these steps.
343-
4. [=/Resolve=] |promise| with the <a>Picture-in-Picture window</a> associated with
344-
{{pictureInPictureElement}}.
345356

346357
## Extensions to <code>Document</code> ## {#document-extensions}
347358

@@ -382,8 +393,7 @@ The {{pictureInPictureElement}} attribute's getter must run these steps:
382393

383394
1. If <a>this</a> is a <a for=/>shadow root</a> and its <a for=DocumentFragment>host</a>
384395
is not <a>connected</a>, return `null` and abort these steps.
385-
2. Let |candidate| be the result of <a>retargeting</a> Picture-in-Picture
386-
element against <a>this</a>.
396+
2. Let |candidate| be the result of [=retargeting=] [=Picture-in-Picture element=] against [=this=].
387397
3. If |candidate| and <a>this</a> are in the same <a>tree</a>,
388398
return |candidate| and abort these steps.
389399
4. Return `null`.
@@ -468,8 +478,8 @@ regardless of the <a>browsing context</a>.
468478
## Permissions Policy ## {#permissions-policy}
469479

470480
This specification defines a <a>policy-controlled feature</a> named
471-
`"picture-in-picture"` that controls whether the <a>request Picture-in-Picture
472-
algorithm</a> may return a {{SecurityError}} and whether
481+
`"picture-in-picture"` that controls whether [=request Picture-in-Picture=]
482+
returns a {{SecurityError}} and whether
473483
{{pictureInPictureEnabled}} is `true` or `false`.
474484

475485
The <a>default allowlist</a> for this feature is `*`.

0 commit comments

Comments
 (0)