Title: [262038] trunk
Revision
262038
Author
peng.l...@apple.com
Date
2020-05-21 17:20:29 -0700 (Thu, 21 May 2020)

Log Message

Fix issues of the Picture-in-Picture API under stress tests
https://bugs.webkit.org/show_bug.cgi?id=212191

Reviewed by Eric Carlson.

Source/WebCore:

The current implementation of the Picture-in-Picture API is not robust under stress tests.
Changing the video presentation mode of a video element between inline and picture-in-picture
continuously may corrupt the internal states of the video element.

This patch refactors the approach to tracking the progress of video presentation mode changes
and make sure no new requestPictureInPicture() or exitPictureInPicture() will trigger
a presentation mode change unless the previous operations are completed.

This patch also removes the code for testing purposes in the HTMLVideoElement class.

Covered by existing tests.

* html/HTMLMediaElement.h:
* html/HTMLVideoElement.cpp:
(WebCore::toPresentationMode):
(WebCore::HTMLVideoElement::setFullscreenMode):
(WebCore::HTMLVideoElement::fullscreenModeChanged):
(WebCore::HTMLVideoElement::didEnterFullscreen):
(WebCore::HTMLVideoElement::didExitFullscreen):
(WebCore::HTMLVideoElement::setPictureInPictureObserver):
(WebCore::HTMLVideoElement::setVideoFullscreenFrame):
(WebCore::HTMLVideoElement::didBecomeFullscreenElement): Deleted.
(WebCore::HTMLVideoElement::setPictureInPictureAPITestEnabled): Deleted.
* html/HTMLVideoElement.h:

* testing/Internals.cpp:
(WebCore::Internals::setPictureInPictureAPITestEnabled): Deleted.
* testing/Internals.h:
* testing/Internals.idl:
Remove setPictureInPictureAPITestEnabled().

Source/WebKit:

* UIProcess/Cocoa/VideoFullscreenManagerProxy.mm:
(WebKit::VideoFullscreenManagerProxy::enterFullscreen):
Mock the behavior of the VideoFullscreenInterface[AVKit|Mac] regarding
the Picture-in-Picture mode support.

* WebProcess/cocoa/VideoFullscreenManager.mm:
(WebKit::VideoFullscreenManager::didEnterFullscreen):
(WebKit::VideoFullscreenManager::didCleanupFullscreen):
Add callbacks for the end of entering/exiting fullscreen.

LayoutTests:

Refactor the layout tests for the Picture-in-Picture API with the Mock VideoPresentation Mode.

* media/picture-in-picture/picture-in-picture-api-css-selector-expected.txt:
* media/picture-in-picture/picture-in-picture-api-css-selector.html:
* media/picture-in-picture/picture-in-picture-api-enter-pip-1-expected.txt:
* media/picture-in-picture/picture-in-picture-api-enter-pip-1.html:
* media/picture-in-picture/picture-in-picture-api-enter-pip-2-expected.txt:
* media/picture-in-picture/picture-in-picture-api-enter-pip-2.html:
* media/picture-in-picture/picture-in-picture-api-enter-pip-3-expected.txt:
* media/picture-in-picture/picture-in-picture-api-enter-pip-3.html:
* media/picture-in-picture/picture-in-picture-api-enter-pip-4-expected.txt:
* media/picture-in-picture/picture-in-picture-api-enter-pip-4.html:
* media/picture-in-picture/picture-in-picture-api-events-expected.txt:
* media/picture-in-picture/picture-in-picture-api-events.html:
* media/picture-in-picture/picture-in-picture-api-exit-pip-1-expected.txt:
* media/picture-in-picture/picture-in-picture-api-exit-pip-1.html:
* media/picture-in-picture/picture-in-picture-api-exit-pip-2-expected.txt:
* media/picture-in-picture/picture-in-picture-api-exit-pip-2.html:
* media/picture-in-picture/picture-in-picture-api-pip-window-expected.txt:
* media/picture-in-picture/picture-in-picture-api-pip-window.html:
* media/picture-in-picture/picture-in-picture-events-expected.txt:
* media/picture-in-picture/picture-in-picture-events.html:
* media/picture-in-picture/picture-in-picture-interruption-expected.txt:
* media/picture-in-picture/picture-in-picture-interruption.html:

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (262037 => 262038)


--- trunk/LayoutTests/ChangeLog	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/ChangeLog	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,3 +1,35 @@
+2020-05-21  Peng Liu  <peng.l...@apple.com>
+
+        Fix issues of the Picture-in-Picture API under stress tests
+        https://bugs.webkit.org/show_bug.cgi?id=212191
+
+        Reviewed by Eric Carlson.
+
+        Refactor the layout tests for the Picture-in-Picture API with the Mock VideoPresentation Mode.
+
+        * media/picture-in-picture/picture-in-picture-api-css-selector-expected.txt:
+        * media/picture-in-picture/picture-in-picture-api-css-selector.html:
+        * media/picture-in-picture/picture-in-picture-api-enter-pip-1-expected.txt:
+        * media/picture-in-picture/picture-in-picture-api-enter-pip-1.html:
+        * media/picture-in-picture/picture-in-picture-api-enter-pip-2-expected.txt:
+        * media/picture-in-picture/picture-in-picture-api-enter-pip-2.html:
+        * media/picture-in-picture/picture-in-picture-api-enter-pip-3-expected.txt:
+        * media/picture-in-picture/picture-in-picture-api-enter-pip-3.html:
+        * media/picture-in-picture/picture-in-picture-api-enter-pip-4-expected.txt:
+        * media/picture-in-picture/picture-in-picture-api-enter-pip-4.html:
+        * media/picture-in-picture/picture-in-picture-api-events-expected.txt:
+        * media/picture-in-picture/picture-in-picture-api-events.html:
+        * media/picture-in-picture/picture-in-picture-api-exit-pip-1-expected.txt:
+        * media/picture-in-picture/picture-in-picture-api-exit-pip-1.html:
+        * media/picture-in-picture/picture-in-picture-api-exit-pip-2-expected.txt:
+        * media/picture-in-picture/picture-in-picture-api-exit-pip-2.html:
+        * media/picture-in-picture/picture-in-picture-api-pip-window-expected.txt:
+        * media/picture-in-picture/picture-in-picture-api-pip-window.html:
+        * media/picture-in-picture/picture-in-picture-events-expected.txt:
+        * media/picture-in-picture/picture-in-picture-events.html:
+        * media/picture-in-picture/picture-in-picture-interruption-expected.txt:
+        * media/picture-in-picture/picture-in-picture-interruption.html:
+
 2020-05-21  Oriol Brufau  <obru...@igalia.com>
 
         [css-grid] Don't create renderers for whitespace nodes

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-css-selector-expected.txt (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-css-selector-expected.txt	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-css-selector-expected.txt	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,7 +1,7 @@
 This tests that entering and leaving Picture-in-Picture toggles CSS selector.
 
 RUN(internals.settings.setAllowsPictureInPictureMediaPlayback(true))
-RUN(internals.setPictureInPictureAPITestEnabled(video, true))
+RUN(internals.setMockVideoPresentationModeEnabled(true))
 RUN(video.src = "" "../content/test"))
 EVENT(canplaythrough)
 EXPECTED (getComputedStyle(video).color == 'rgb(0, 0, 255)') OK

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-css-selector.html (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-css-selector.html	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-css-selector.html	2020-05-22 00:20:29 UTC (rev 262038)
@@ -17,16 +17,24 @@
     </style>
     <script>
         window.addEventListener('load', async event => {
+            if (!window.internals) {
+                failTest('This test requires window.internals.');
+                return;
+            }
+
             findMediaElement();
 
             run('internals.settings.setAllowsPictureInPictureMediaPlayback(true)');
-            run('internals.setPictureInPictureAPITestEnabled(video, true)');
+            run('internals.setMockVideoPresentationModeEnabled(true)');
 
             run('video.src = "" "../content/test")');
             await waitFor(video, 'canplaythrough');
             testExpected('getComputedStyle(video).color', 'rgb(0, 0, 255)');
 
-            runWithKeyDown(function() { video.requestPictureInPicture() });
+            runWithKeyDown(() => {
+                video.requestPictureInPicture();
+            });
+
             await waitFor(video, 'enterpictureinpicture');
             testExpected('getComputedStyle(video).color', 'rgb(0, 255, 0)');
 

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-1-expected.txt (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-1-expected.txt	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-1-expected.txt	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,7 +1,7 @@
 This tests that request Picture-in-Picture requires a user gesture.
 
 RUN(internals.settings.setAllowsPictureInPictureMediaPlayback(true))
-RUN(internals.setPictureInPictureAPITestEnabled(video, true))
+RUN(internals.setMockVideoPresentationModeEnabled(true))
 RUN(video.src = "" "../content/test"))
 EVENT(canplaythrough)
 EXPECTED (error.name == 'NotAllowedError') OK

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-1.html (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-1.html	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-1.html	2020-05-22 00:20:29 UTC (rev 262038)
@@ -5,10 +5,16 @@
     <script src=""
     <script>
         window.addEventListener('load', async event => {
+            if (!window.internals) {
+                failTest('This test requires window.internals.');
+                return;
+            }
+
             findMediaElement();
 
             run('internals.settings.setAllowsPictureInPictureMediaPlayback(true)');
-            run('internals.setPictureInPictureAPITestEnabled(video, true)');
+            run('internals.setMockVideoPresentationModeEnabled(true)');
+
             run('video.src = "" "../content/test")');
             await waitFor(video, 'canplaythrough');
 

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-2-expected.txt (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-2-expected.txt	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-2-expected.txt	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,7 +1,7 @@
 This tests that request Picture-in-Picture requires loaded metadata for the video element.
 
 RUN(internals.settings.setAllowsPictureInPictureMediaPlayback(true))
-RUN(internals.setPictureInPictureAPITestEnabled(video, true))
+RUN(internals.setMockVideoPresentationModeEnabled(true))
 EXPECTED (error.name == 'InvalidStateError') OK
 END OF TEST
 

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-2.html (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-2.html	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-2.html	2020-05-22 00:20:29 UTC (rev 262038)
@@ -5,11 +5,17 @@
     <script src=""
     <script>
         window.addEventListener('load', async event => {
+            if (!window.internals) {
+                failTest('This test requires window.internals.');
+                return;
+            }
+
             findMediaElement();
 
             run('internals.settings.setAllowsPictureInPictureMediaPlayback(true)');
-            run('internals.setPictureInPictureAPITestEnabled(video, true)');
-            runWithKeyDown(function() {
+            run('internals.setMockVideoPresentationModeEnabled(true)');
+
+            runWithKeyDown(() => {
                 video.requestPictureInPicture()
                 .then(() => {
                     failTest('request Picture-in-Picture requires loaded metadata for the video element.')

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-3-expected.txt (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-3-expected.txt	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-3-expected.txt	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,7 +1,7 @@
 This tests that request Picture-in-Picture requires video track for the video element.
 
 RUN(internals.settings.setAllowsPictureInPictureMediaPlayback(true))
-RUN(internals.setPictureInPictureAPITestEnabled(video, true))
+RUN(internals.setMockVideoPresentationModeEnabled(true))
 RUN(video.src = "" "../content/test"))
 EVENT(canplaythrough)
 EXPECTED (error.name == 'InvalidStateError') OK

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-3.html (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-3.html	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-3.html	2020-05-22 00:20:29 UTC (rev 262038)
@@ -5,13 +5,19 @@
     <script src=""
     <script>
         window.addEventListener('load', async event => {
+            if (!window.internals) {
+                failTest('This test requires window.internals.');
+                return;
+            }
+
             findMediaElement();
 
             run('internals.settings.setAllowsPictureInPictureMediaPlayback(true)');
-            run('internals.setPictureInPictureAPITestEnabled(video, true)');
+            run('internals.setMockVideoPresentationModeEnabled(true)');
+
             run('video.src = "" "../content/test")');
             await waitFor(video, 'canplaythrough');
-            runWithKeyDown(function() {
+            runWithKeyDown(() => {
                 video.requestPictureInPicture()
                 .then(() => {
                     failTest('request Picture-in-Picture requires video track for the video element.')

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-4-expected.txt (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-4-expected.txt	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-4-expected.txt	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,7 +1,7 @@
 This tests that request Picture-in-Picture resolves on user click.
 
 RUN(internals.settings.setAllowsPictureInPictureMediaPlayback(true))
-RUN(internals.setPictureInPictureAPITestEnabled(video, true))
+RUN(internals.setMockVideoPresentationModeEnabled(true))
 RUN(video.src = "" "../content/test"))
 EVENT(canplaythrough)
 END OF TEST

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-4.html (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-4.html	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-enter-pip-4.html	2020-05-22 00:20:29 UTC (rev 262038)
@@ -5,14 +5,19 @@
     <script src=""
     <script>
         window.addEventListener('load', async event => {
+            if (!window.internals) {
+                failTest('This test requires window.internals.');
+                return;
+            }
+
             findMediaElement();
 
             run('internals.settings.setAllowsPictureInPictureMediaPlayback(true)');
-            run('internals.setPictureInPictureAPITestEnabled(video, true)');
+            run('internals.setMockVideoPresentationModeEnabled(true)');
 
             run('video.src = "" "../content/test")');
             await waitFor(video, 'canplaythrough');
-            runWithKeyDown(function() {
+            runWithKeyDown(() => {
                 video.requestPictureInPicture()
                 .then(() => {
                     document.exitPictureInPicture().then(endTest).catch(() => {

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-events-expected.txt (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-events-expected.txt	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-events-expected.txt	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,7 +1,7 @@
 This tests that events are fired correctly when a video element enters and exits the Picture-in-Picture mode.
 
 RUN(internals.settings.setAllowsPictureInPictureMediaPlayback(true))
-RUN(internals.setPictureInPictureAPITestEnabled(video, true))
+RUN(internals.setMockVideoPresentationModeEnabled(true))
 RUN(video.src = "" "../content/test"))
 EVENT(canplaythrough)
 EVENT(enterpictureinpicture)

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-events.html (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-events.html	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-events.html	2020-05-22 00:20:29 UTC (rev 262038)
@@ -5,15 +5,23 @@
     <script src=""
     <script>
         window.addEventListener('load', async event => {
+            if (!window.internals) {
+                failTest('This test requires window.internals.');
+                return;
+            }
+
             findMediaElement();
 
             run('internals.settings.setAllowsPictureInPictureMediaPlayback(true)');
-            run('internals.setPictureInPictureAPITestEnabled(video, true)');
+            run('internals.setMockVideoPresentationModeEnabled(true)');
 
             run('video.src = "" "../content/test")');
             await waitFor(video, 'canplaythrough');
 
-            runWithKeyDown(function() { video.requestPictureInPicture(); });
+            runWithKeyDown(() => {
+                video.requestPictureInPicture();
+            });
+
             waitForEventOnce('enterpictureinpicture', event => {
                 window.pipWindow = event.pictureInPictureWindow;
                 testExpected('pipWindow.width', 0, '>');

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-exit-pip-1-expected.txt (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-exit-pip-1-expected.txt	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-exit-pip-1-expected.txt	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,7 +1,7 @@
 This tests that exit Picture-in-Picture resolves when there is a Picture-in-Picture video.
 
 RUN(internals.settings.setAllowsPictureInPictureMediaPlayback(true))
-RUN(internals.setPictureInPictureAPITestEnabled(video, true))
+RUN(internals.setMockVideoPresentationModeEnabled(true))
 RUN(video.src = "" "../content/test"))
 EVENT(canplaythrough)
 EVENT(enterpictureinpicture)

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-exit-pip-1.html (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-exit-pip-1.html	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-exit-pip-1.html	2020-05-22 00:20:29 UTC (rev 262038)
@@ -5,15 +5,23 @@
     <script src=""
     <script>
         window.addEventListener('load', async event => {
+            if (!window.internals) {
+                failTest('This test requires window.internals.');
+                return;
+            }
+
             findMediaElement();
 
             run('internals.settings.setAllowsPictureInPictureMediaPlayback(true)');
-            run('internals.setPictureInPictureAPITestEnabled(video, true)');
+            run('internals.setMockVideoPresentationModeEnabled(true)');
 
             run('video.src = "" "../content/test")');
             await waitFor(video, 'canplaythrough');
 
-            runWithKeyDown(function() { video.requestPictureInPicture() });
+            runWithKeyDown(() => {
+                video.requestPictureInPicture();
+            });
+
             await waitFor(video, 'enterpictureinpicture');
             document.exitPictureInPicture().then(endTest).catch(() => {
                 failTest('Failed to exit the Picture-in-Picture mode.');

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-exit-pip-2-expected.txt (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-exit-pip-2-expected.txt	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-exit-pip-2-expected.txt	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,7 +1,7 @@
 This tests that exit Picture-in-Picture rejects when there is no Picture-in-Picture video.
 
 RUN(internals.settings.setAllowsPictureInPictureMediaPlayback(true))
-RUN(internals.setPictureInPictureAPITestEnabled(video, true))
+RUN(internals.setMockVideoPresentationModeEnabled(true))
 EXPECTED (error.name == 'InvalidStateError') OK
 END OF TEST
 

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-exit-pip-2.html (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-exit-pip-2.html	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-exit-pip-2.html	2020-05-22 00:20:29 UTC (rev 262038)
@@ -5,10 +5,15 @@
     <script src=""
     <script>
         window.addEventListener('load', async event => {
+            if (!window.internals) {
+                failTest('This test requires window.internals.');
+                return;
+            }
+
             findMediaElement();
 
             run('internals.settings.setAllowsPictureInPictureMediaPlayback(true)');
-            run('internals.setPictureInPictureAPITestEnabled(video, true)');
+            run('internals.setMockVideoPresentationModeEnabled(true)');
 
             document.exitPictureInPicture()
             .then(() => {

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-pip-window-expected.txt (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-pip-window-expected.txt	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-pip-window-expected.txt	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,7 +1,7 @@
 This tests that a pip window is returned correctly when a video element enters the Picture-in-Picture mode.
 
 RUN(internals.settings.setAllowsPictureInPictureMediaPlayback(true))
-RUN(internals.setPictureInPictureAPITestEnabled(video, true))
+RUN(internals.setMockVideoPresentationModeEnabled(true))
 RUN(video.src = "" "../content/test"))
 EVENT(canplaythrough)
 EXPECTED (pipWindow.width > '0') OK

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-pip-window.html (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-pip-window.html	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-api-pip-window.html	2020-05-22 00:20:29 UTC (rev 262038)
@@ -5,15 +5,20 @@
     <script src=""
     <script>
         window.addEventListener('load', async event => {
+            if (!window.internals) {
+                failTest('This test requires window.internals.');
+                return;
+            }
+
             findMediaElement();
 
             run('internals.settings.setAllowsPictureInPictureMediaPlayback(true)');
-            run('internals.setPictureInPictureAPITestEnabled(video, true)');
+            run('internals.setMockVideoPresentationModeEnabled(true)');
 
             run('video.src = "" "../content/test")');
             await waitFor(video, 'canplaythrough');
 
-            runWithKeyDown(function() {
+            runWithKeyDown(() => {
                 video.requestPictureInPicture()
                 .then(pipWindow => {
                     window.pipWindow = pipWindow;
@@ -28,7 +33,6 @@
                     failTest("Failed to enter the Picture-in-Picture mode.");
                 });
             });
-
         });
     </script>
 </head>

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-events-expected.txt (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-events-expected.txt	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-events-expected.txt	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,7 +1,7 @@
 This tests that events are fired correctly when we use the webkit prefixed API to enters and exits the Picture-in-Picture mode.
 
 RUN(internals.settings.setAllowsPictureInPictureMediaPlayback(true))
-RUN(internals.setPictureInPictureAPITestEnabled(video, true))
+RUN(internals.setMockVideoPresentationModeEnabled(true))
 RUN(video.src = "" "../content/test"))
 EVENT(canplaythrough)
 EVENT(enterpictureinpicture)

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-events.html (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-events.html	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-events.html	2020-05-22 00:20:29 UTC (rev 262038)
@@ -5,15 +5,23 @@
     <script src=""
     <script>
         window.addEventListener('load', async event => {
+            if (!window.internals) {
+                failTest('This test requires window.internals.');
+                return;
+            }
+
             findMediaElement();
 
             run('internals.settings.setAllowsPictureInPictureMediaPlayback(true)');
-            run('internals.setPictureInPictureAPITestEnabled(video, true)');
+            run('internals.setMockVideoPresentationModeEnabled(true)');
 
             run('video.src = "" "../content/test")');
             await waitFor(video, 'canplaythrough');
 
-            runWithKeyDown(function() { video.webkitSetPresentationMode('picture-in-picture'); });
+            runWithKeyDown(() => {
+                video.webkitSetPresentationMode('picture-in-picture');
+            });
+
             waitForEventOnce('enterpictureinpicture', event => {
                 window.pipWindow = event.pictureInPictureWindow;
                 testExpected('pipWindow.width', 0, '>');

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-interruption-expected.txt (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-interruption-expected.txt	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-interruption-expected.txt	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,6 +1,7 @@
 This tests that video is in the correct media session state after ending a background interruption while pipped.
 
 RUN(internals.settings.setAllowsPictureInPictureMediaPlayback(true))
+RUN(internals.setMockVideoPresentationModeEnabled(true))
 RUN(video.src = "" "../content/test"))
 EVENT(canplaythrough)
 RUN(video.play())

Modified: trunk/LayoutTests/media/picture-in-picture/picture-in-picture-interruption.html (262037 => 262038)


--- trunk/LayoutTests/media/picture-in-picture/picture-in-picture-interruption.html	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/LayoutTests/media/picture-in-picture/picture-in-picture-interruption.html	2020-05-22 00:20:29 UTC (rev 262038)
@@ -5,9 +5,15 @@
     <script src=""
     <script>
         window.addEventListener('load', async event => {
+            if (!window.internals) {
+                failTest('This test requires window.internals.');
+                return;
+            }
+
             findMediaElement();
 
             run('internals.settings.setAllowsPictureInPictureMediaPlayback(true)');
+            run('internals.setMockVideoPresentationModeEnabled(true)');
 
             run('video.src = "" "../content/test")');
             await waitFor(video, 'canplaythrough');
@@ -15,7 +21,10 @@
             run('video.play()');
             await waitFor(video, 'playing');
 
-            runWithKeyDown(function(){ video.webkitSetPresentationMode('picture-in-picture'); });
+            runWithKeyDown(() => {
+                video.webkitSetPresentationMode('picture-in-picture');
+            });
+
             await waitFor(video, 'webkitpresentationmodechanged');
 
             run('internals.beginMediaSessionInterruption("enteringbackground")');

Modified: trunk/Source/WebCore/ChangeLog (262037 => 262038)


--- trunk/Source/WebCore/ChangeLog	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/Source/WebCore/ChangeLog	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,3 +1,41 @@
+2020-05-21  Peng Liu  <peng.l...@apple.com>
+
+        Fix issues of the Picture-in-Picture API under stress tests
+        https://bugs.webkit.org/show_bug.cgi?id=212191
+
+        Reviewed by Eric Carlson.
+
+        The current implementation of the Picture-in-Picture API is not robust under stress tests.
+        Changing the video presentation mode of a video element between inline and picture-in-picture
+        continuously may corrupt the internal states of the video element.
+
+        This patch refactors the approach to tracking the progress of video presentation mode changes
+        and make sure no new requestPictureInPicture() or exitPictureInPicture() will trigger
+        a presentation mode change unless the previous operations are completed.
+
+        This patch also removes the code for testing purposes in the HTMLVideoElement class.
+
+        Covered by existing tests.
+
+        * html/HTMLMediaElement.h:
+        * html/HTMLVideoElement.cpp:
+        (WebCore::toPresentationMode):
+        (WebCore::HTMLVideoElement::setFullscreenMode):
+        (WebCore::HTMLVideoElement::fullscreenModeChanged):
+        (WebCore::HTMLVideoElement::didEnterFullscreen):
+        (WebCore::HTMLVideoElement::didExitFullscreen):
+        (WebCore::HTMLVideoElement::setPictureInPictureObserver):
+        (WebCore::HTMLVideoElement::setVideoFullscreenFrame):
+        (WebCore::HTMLVideoElement::didBecomeFullscreenElement): Deleted.
+        (WebCore::HTMLVideoElement::setPictureInPictureAPITestEnabled): Deleted.
+        * html/HTMLVideoElement.h:
+
+        * testing/Internals.cpp:
+        (WebCore::Internals::setPictureInPictureAPITestEnabled): Deleted.
+        * testing/Internals.h:
+        * testing/Internals.idl:
+        Remove setPictureInPictureAPITestEnabled().
+
 2020-05-21  Sam Weinig  <wei...@apple.com>
 
         Extended Color Cleanup: Remove trivial uses of Color::rgb()

Modified: trunk/Source/WebCore/html/HTMLMediaElement.h (262037 => 262038)


--- trunk/Source/WebCore/html/HTMLMediaElement.h	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/Source/WebCore/html/HTMLMediaElement.h	2020-05-22 00:20:29 UTC (rev 262038)
@@ -561,7 +561,7 @@
 
     bool isSuspended() const final;
 
-    WEBCORE_EXPORT void didBecomeFullscreenElement() override;
+    WEBCORE_EXPORT void didBecomeFullscreenElement() final;
     WEBCORE_EXPORT void willExitFullscreen();
 
 #if ENABLE(PICTURE_IN_PICTURE_API)

Modified: trunk/Source/WebCore/html/HTMLVideoElement.cpp (262037 => 262038)


--- trunk/Source/WebCore/html/HTMLVideoElement.cpp	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/Source/WebCore/html/HTMLVideoElement.cpp	2020-05-22 00:20:29 UTC (rev 262038)
@@ -442,6 +442,21 @@
     return HTMLMediaElementEnums::VideoFullscreenModeNone;
 }
 
+static inline HTMLVideoElement::VideoPresentationMode toPresentationMode(HTMLMediaElementEnums::VideoFullscreenMode mode)
+{
+    if (mode == HTMLMediaElementEnums::VideoFullscreenModeStandard)
+        return HTMLVideoElement::VideoPresentationMode::Fullscreen;
+
+    if (mode & HTMLMediaElementEnums::VideoFullscreenModePictureInPicture)
+        return HTMLVideoElement::VideoPresentationMode::PictureInPicture;
+
+    if (mode == HTMLMediaElementEnums::VideoFullscreenModeNone)
+        return HTMLVideoElement::VideoPresentationMode::Inline;
+
+    ASSERT_NOT_REACHED();
+    return HTMLVideoElement::VideoPresentationMode::Inline;
+}
+
 void HTMLVideoElement::webkitSetPresentationMode(VideoPresentationMode mode)
 {
     INFO_LOG(LOGIDENTIFIER, ", mode = ",  mode);
@@ -451,25 +466,13 @@
 void HTMLVideoElement::setFullscreenMode(HTMLMediaElementEnums::VideoFullscreenMode mode)
 {
     INFO_LOG(LOGIDENTIFIER, ", mode = ", mode);
-#if ENABLE(PICTURE_IN_PICTURE_API)
-    if (m_pictureInPictureAPITestEnabled) {
-        if (mode == VideoFullscreenModePictureInPicture) {
-            fullscreenModeChanged(mode);
-            didBecomeFullscreenElement();
-            setVideoFullscreenFrame({0, 0, 100, 100});
-            return;
-        }
 
-        if (mode == VideoFullscreenModeNone) {
-            fullscreenModeChanged(mode);
-            return;
-        }
-    }
-#endif
-
     if (mode == VideoFullscreenModeNone) {
-        if (isFullscreen())
+        if (isFullscreen()) {
+            if (toPresentationMode(fullscreenMode()) == VideoPresentationMode::PictureInPicture)
+                m_isEnteringOrExitingPictureInPicture = true;
             exitFullscreen();
+        }
 
         return;
     }
@@ -477,24 +480,12 @@
     if (!mediaSession().fullscreenPermitted() || !supportsFullscreen(mode))
         return;
 
+    if (mode == VideoFullscreenModePictureInPicture)
+        m_isEnteringOrExitingPictureInPicture = true;
+
     enterFullscreen(mode);
 }
 
-static HTMLVideoElement::VideoPresentationMode toPresentationMode(HTMLMediaElementEnums::VideoFullscreenMode mode)
-{
-    if (mode == HTMLMediaElementEnums::VideoFullscreenModeStandard)
-        return HTMLVideoElement::VideoPresentationMode::Fullscreen;
-
-    if (mode & HTMLMediaElementEnums::VideoFullscreenModePictureInPicture)
-        return HTMLVideoElement::VideoPresentationMode::PictureInPicture;
-
-    if (mode == HTMLMediaElementEnums::VideoFullscreenModeNone)
-        return HTMLVideoElement::VideoPresentationMode::Inline;
-
-    ASSERT_NOT_REACHED();
-    return HTMLVideoElement::VideoPresentationMode::Inline;
-}
-
 auto HTMLVideoElement::webkitPresentationMode() const -> VideoPresentationMode
 {
     return toPresentationMode(fullscreenMode());
@@ -505,18 +496,6 @@
     if (mode != fullscreenMode()) {
         INFO_LOG(LOGIDENTIFIER, "changed from ", fullscreenMode(), ", to ", mode);
         scheduleEvent(eventNames().webkitpresentationmodechangedEvent);
-
-#if ENABLE(PICTURE_IN_PICTURE_API)
-        if (m_pictureInPictureObserver) {
-            HTMLVideoElement::VideoPresentationMode targetVideoPresentationMode = toPresentationMode(mode);
-            HTMLVideoElement::VideoPresentationMode sourceVideoPresentationMode = toPresentationMode(fullscreenMode());
-
-            if (targetVideoPresentationMode != HTMLVideoElement::VideoPresentationMode::PictureInPicture && sourceVideoPresentationMode == HTMLVideoElement::VideoPresentationMode::PictureInPicture) {
-                m_pictureInPictureObserver->didExitPictureInPicture();
-                m_isFullscreen = false;
-            }
-        }
-#endif
     }
 
     if (player())
@@ -525,55 +504,55 @@
     HTMLMediaElement::fullscreenModeChanged(mode);
 }
 
-#if ENABLE(PICTURE_IN_PICTURE_API)
-void HTMLVideoElement::didBecomeFullscreenElement()
+void HTMLVideoElement::didEnterFullscreen()
 {
-    m_isFullscreen = true;
-    m_waitingForPictureInPictureWindowFrame = true;
-    HTMLMediaElement::didBecomeFullscreenElement();
+    if (m_isEnteringOrExitingPictureInPicture)
+        m_isWaitingForPictureInPictureWindowFrame = true;
 }
 
-void HTMLVideoElement::setPictureInPictureObserver(PictureInPictureObserver* observer)
+void HTMLVideoElement::didExitFullscreen()
 {
-    m_pictureInPictureObserver = observer;
+    if (m_isEnteringOrExitingPictureInPicture) {
+        m_isEnteringOrExitingPictureInPicture = false;
+#if ENABLE(PICTURE_IN_PICTURE_API)
+        if (m_pictureInPictureObserver)
+            m_pictureInPictureObserver->didExitPictureInPicture();
+#endif
+    }
 }
 
-void HTMLVideoElement::setPictureInPictureAPITestEnabled(bool enabled)
-{
-    m_pictureInPictureAPITestEnabled = enabled;
-}
-#endif
-
-#endif
-
-#if ENABLE(VIDEO_PRESENTATION_MODE)
 void HTMLVideoElement::setVideoFullscreenFrame(FloatRect frame)
 {
     HTMLMediaElement::setVideoFullscreenFrame(frame);
 
+    if (m_isWaitingForPictureInPictureWindowFrame) {
+        m_isWaitingForPictureInPictureWindowFrame = false;
+        m_isEnteringOrExitingPictureInPicture = false;
 #if ENABLE(PICTURE_IN_PICTURE_API)
-    // fullscreenMode() does not always provide the correct fullscreen mode
-    // when mode changing is happening (webkit.org/b/203443)
-    if (!m_isFullscreen)
+        if (m_pictureInPictureObserver)
+            m_pictureInPictureObserver->didEnterPictureInPicture(IntSize(frame.size()));
+#endif
         return;
+    }
 
     if (toPresentationMode(fullscreenMode()) != VideoPresentationMode::PictureInPicture)
         return;
 
-    if (m_waitingForPictureInPictureWindowFrame) {
-        m_waitingForPictureInPictureWindowFrame = false;
-        if (m_pictureInPictureObserver)
-            m_pictureInPictureObserver->didEnterPictureInPicture(IntSize(frame.size()));
-
-        return;
-    }
-
-    if (m_pictureInPictureObserver)
+#if ENABLE(PICTURE_IN_PICTURE_API)
+    if (!m_isEnteringOrExitingPictureInPicture && m_pictureInPictureObserver)
         m_pictureInPictureObserver->pictureInPictureWindowResized(IntSize(frame.size()));
 #endif
 }
+
+#if ENABLE(PICTURE_IN_PICTURE_API)
+void HTMLVideoElement::setPictureInPictureObserver(PictureInPictureObserver* observer)
+{
+    m_pictureInPictureObserver = observer;
+}
 #endif
 
+#endif // ENABLE(VIDEO_PRESENTATION_MODE)
+
 #if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
 void HTMLVideoElement::exitToFullscreenModeWithoutAnimationIfPossible(HTMLMediaElementEnums::VideoFullscreenMode fromMode, HTMLMediaElementEnums::VideoFullscreenMode toMode)
 {

Modified: trunk/Source/WebCore/html/HTMLVideoElement.h (262037 => 262038)


--- trunk/Source/WebCore/html/HTMLVideoElement.h	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/Source/WebCore/html/HTMLVideoElement.h	2020-05-22 00:20:29 UTC (rev 262038)
@@ -89,17 +89,15 @@
     void setFullscreenMode(VideoFullscreenMode);
     void fullscreenModeChanged(VideoFullscreenMode) final;
 
+    WEBCORE_EXPORT void didEnterFullscreen();
+    WEBCORE_EXPORT void didExitFullscreen();
+    void setVideoFullscreenFrame(FloatRect) final;
+
 #if ENABLE(PICTURE_IN_PICTURE_API)
-    WEBCORE_EXPORT void didBecomeFullscreenElement() final;
     void setPictureInPictureObserver(PictureInPictureObserver*);
-    WEBCORE_EXPORT void setPictureInPictureAPITestEnabled(bool);
 #endif
 #endif
 
-#if ENABLE(VIDEO_PRESENTATION_MODE)
-    void setVideoFullscreenFrame(FloatRect) final;
-#endif
-
 #if PLATFORM(MAC) && ENABLE(VIDEO_PRESENTATION_MODE)
     void exitToFullscreenModeWithoutAnimationIfPossible(HTMLMediaElementEnums::VideoFullscreenMode fromMode, HTMLMediaElementEnums::VideoFullscreenMode toMode);
 #endif
@@ -136,12 +134,10 @@
     unsigned m_lastReportedVideoWidth { 0 };
     unsigned m_lastReportedVideoHeight { 0 };
 
+    bool m_isEnteringOrExitingPictureInPicture { false };
+    bool m_isWaitingForPictureInPictureWindowFrame { false };
 #if ENABLE(PICTURE_IN_PICTURE_API)
-    bool m_waitingForPictureInPictureWindowFrame { false };
-    bool m_isFullscreen { false };
     PictureInPictureObserver* m_pictureInPictureObserver { nullptr };
-
-    bool m_pictureInPictureAPITestEnabled { false };
 #endif
 };
 

Modified: trunk/Source/WebCore/testing/Internals.cpp (262037 => 262038)


--- trunk/Source/WebCore/testing/Internals.cpp	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/Source/WebCore/testing/Internals.cpp	2020-05-22 00:20:29 UTC (rev 262038)
@@ -5604,13 +5604,6 @@
 }
 #endif
 
-#if ENABLE(PICTURE_IN_PICTURE_API)
-void Internals::setPictureInPictureAPITestEnabled(HTMLVideoElement& videoElement, bool enabled)
-{
-    videoElement.setPictureInPictureAPITestEnabled(enabled);
-}
-#endif
-
 void Internals::setMaxCanvasPixelMemory(unsigned size)
 {
     HTMLCanvasElement::setMaxPixelMemoryForTesting(size);

Modified: trunk/Source/WebCore/testing/Internals.h (262037 => 262038)


--- trunk/Source/WebCore/testing/Internals.h	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/Source/WebCore/testing/Internals.h	2020-05-22 00:20:29 UTC (rev 262038)
@@ -987,10 +987,6 @@
     void setMockWebAuthenticationConfiguration(const MockWebAuthenticationConfiguration&);
 #endif
 
-#if ENABLE(PICTURE_IN_PICTURE_API)
-    void setPictureInPictureAPITestEnabled(HTMLVideoElement&, bool);
-#endif
-
     int processIdentifier() const;
 
     Ref<InternalsSetLike> createInternalsSetLike();

Modified: trunk/Source/WebCore/testing/Internals.idl (262037 => 262038)


--- trunk/Source/WebCore/testing/Internals.idl	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/Source/WebCore/testing/Internals.idl	2020-05-22 00:20:29 UTC (rev 262038)
@@ -886,8 +886,6 @@
 
     [Conditional=WEB_AUTHN] void setMockWebAuthenticationConfiguration(MockWebAuthenticationConfiguration configuration);
 
-    [Conditional=PICTURE_IN_PICTURE_API] void setPictureInPictureAPITestEnabled(HTMLVideoElement videoElement, boolean enabled);
-
     InternalsMapLike createInternalsMapLike();
     InternalsSetLike createInternalsSetLike();
 

Modified: trunk/Source/WebKit/ChangeLog (262037 => 262038)


--- trunk/Source/WebKit/ChangeLog	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/Source/WebKit/ChangeLog	2020-05-22 00:20:29 UTC (rev 262038)
@@ -1,3 +1,20 @@
+2020-05-21  Peng Liu  <peng.l...@apple.com>
+
+        Fix issues of the Picture-in-Picture API under stress tests
+        https://bugs.webkit.org/show_bug.cgi?id=212191
+
+        Reviewed by Eric Carlson.
+
+        * UIProcess/Cocoa/VideoFullscreenManagerProxy.mm:
+        (WebKit::VideoFullscreenManagerProxy::enterFullscreen):
+        Mock the behavior of the VideoFullscreenInterface[AVKit|Mac] regarding
+        the Picture-in-Picture mode support.
+
+        * WebProcess/cocoa/VideoFullscreenManager.mm:
+        (WebKit::VideoFullscreenManager::didEnterFullscreen):
+        (WebKit::VideoFullscreenManager::didCleanupFullscreen):
+        Add callbacks for the end of entering/exiting fullscreen.
+
 2020-05-21  Tyler Wilcock  <twilc...@protonmail.com>
 
         Fix misspelling -- m_releaseNetwrokActivityTimer --> m_releaseNetworkActivityTimer

Modified: trunk/Source/WebKit/UIProcess/Cocoa/VideoFullscreenManagerProxy.mm (262037 => 262038)


--- trunk/Source/WebKit/UIProcess/Cocoa/VideoFullscreenManagerProxy.mm	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/Source/WebKit/UIProcess/Cocoa/VideoFullscreenManagerProxy.mm	2020-05-22 00:20:29 UTC (rev 262038)
@@ -554,6 +554,7 @@
     MESSAGE_CHECK_CONTEXTID(contextId);
     if (m_mockVideoPresentationModeEnabled) {
         didEnterFullscreen(contextId);
+        setVideoLayerFrame(contextId, {0, 0, 200, 150});
         return;
     }
 

Modified: trunk/Source/WebKit/WebProcess/cocoa/VideoFullscreenManager.mm (262037 => 262038)


--- trunk/Source/WebKit/WebProcess/cocoa/VideoFullscreenManager.mm	2020-05-22 00:13:11 UTC (rev 262037)
+++ trunk/Source/WebKit/WebProcess/cocoa/VideoFullscreenManager.mm	2020-05-22 00:20:29 UTC (rev 262038)
@@ -441,6 +441,8 @@
     if (!videoElement)
         return;
 
+    videoElement->didEnterFullscreen();
+
     dispatch_async(dispatch_get_main_queue(), [protectedThis = makeRefPtr(this), videoElement] {
         videoElement->didBecomeFullscreenElement();
     });
@@ -505,6 +507,8 @@
 
     model->setVideoFullscreenLayer(nil);
     RefPtr<HTMLVideoElement> videoElement = model->videoElement();
+    if (videoElement)
+        videoElement->didExitFullscreen();
 
     interface->setFullscreenMode(HTMLMediaElementEnums::VideoFullscreenModeNone);
     interface->setFullscreenStandby(false);
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to