Add new -[WKWebView _closeAllMediaPresentations] SPI
authorjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 28 Jun 2019 17:39:53 +0000 (17:39 +0000)
committerjer.noble@apple.com <jer.noble@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 28 Jun 2019 17:39:53 +0000 (17:39 +0000)
https://bugs.webkit.org/show_bug.cgi?id=199294
<rdar://problem/51965958>

Reviewed by Alex Christensen.

Source/WebKit:

Add a new SPI that will close all out-of-window media presentations, including
picture-in-picture, video fullscreen, and element fullscreen.

Drive-by fixes:

+ -[WKApplicationStateTrackingView didMoveToWindow] incorrectly assumes that a WKWebView will
  never be moved frome one window to another, and asserts.

+ -[WKFullScreenWindowController close] doesn't fire the correct 'webkitfullscreenchange' event
  when called in the middle of animating into fullscreen.

* UIProcess/API/Cocoa/WKWebView.mm:
(-[WKWebView _closeAllMediaPresentations]):
* UIProcess/API/Cocoa/WKWebViewPrivate.h:
* UIProcess/Cocoa/VideoFullscreenManagerProxy.h:
* UIProcess/Cocoa/VideoFullscreenManagerProxy.mm:
(WebKit::VideoFullscreenManagerProxy::forEachSession):
* UIProcess/ios/WKApplicationStateTrackingView.mm:
(-[WKApplicationStateTrackingView didMoveToWindow]):
* UIProcess/mac/WKFullScreenWindowController.h:
* UIProcess/mac/WKFullScreenWindowController.mm:
(-[WKFullScreenWindowController exitFullScreenImmediately]):
(-[WKFullScreenWindowController close]):

Tools:

* TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
* TestWebKitAPI/Tests/WebKitCocoa/WKWebViewCloseAllMediaPresentations.mm: Added.
(TEST):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@246928 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit/ChangeLog
Source/WebKit/UIProcess/API/Cocoa/WKWebView.mm
Source/WebKit/UIProcess/API/Cocoa/WKWebViewPrivate.h
Source/WebKit/UIProcess/Cocoa/VideoFullscreenManagerProxy.h
Source/WebKit/UIProcess/Cocoa/VideoFullscreenManagerProxy.mm
Source/WebKit/UIProcess/ios/WKApplicationStateTrackingView.mm
Source/WebKit/UIProcess/mac/WKFullScreenWindowController.h
Source/WebKit/UIProcess/mac/WKFullScreenWindowController.mm
Tools/ChangeLog
Tools/TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj
Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebViewCloseAllMediaPresentations.mm [new file with mode: 0644]

index 52ce4c1..f03dec1 100644 (file)
@@ -1,3 +1,35 @@
+2019-06-28  Jer Noble  <jer.noble@apple.com>
+
+        Add new -[WKWebView _closeAllMediaPresentations] SPI
+        https://bugs.webkit.org/show_bug.cgi?id=199294
+        <rdar://problem/51965958>
+
+        Reviewed by Alex Christensen.
+
+        Add a new SPI that will close all out-of-window media presentations, including
+        picture-in-picture, video fullscreen, and element fullscreen.
+
+        Drive-by fixes:
+
+        + -[WKApplicationStateTrackingView didMoveToWindow] incorrectly assumes that a WKWebView will
+          never be moved frome one window to another, and asserts.
+
+        + -[WKFullScreenWindowController close] doesn't fire the correct 'webkitfullscreenchange' event
+          when called in the middle of animating into fullscreen.
+
+        * UIProcess/API/Cocoa/WKWebView.mm:
+        (-[WKWebView _closeAllMediaPresentations]):
+        * UIProcess/API/Cocoa/WKWebViewPrivate.h:
+        * UIProcess/Cocoa/VideoFullscreenManagerProxy.h:
+        * UIProcess/Cocoa/VideoFullscreenManagerProxy.mm:
+        (WebKit::VideoFullscreenManagerProxy::forEachSession):
+        * UIProcess/ios/WKApplicationStateTrackingView.mm:
+        (-[WKApplicationStateTrackingView didMoveToWindow]):
+        * UIProcess/mac/WKFullScreenWindowController.h:
+        * UIProcess/mac/WKFullScreenWindowController.mm:
+        (-[WKFullScreenWindowController exitFullScreenImmediately]):
+        (-[WKFullScreenWindowController close]):
+
 2019-06-28  Antti Koivisto  <antti@apple.com>
 
         [iOS Scrolling] Propagate scrolls to non-nested UIScrollViews
index 857ef85..c11f7ef 100644 (file)
@@ -54,6 +54,7 @@
 #import "UIDelegate.h"
 #import "UserMediaProcessManager.h"
 #import "VersionChecks.h"
+#import "VideoFullscreenManagerProxy.h"
 #import "ViewGestureController.h"
 #import "ViewSnapshotStore.h"
 #import "WKBackForwardListInternal.h"
@@ -4735,6 +4736,18 @@ FOR_EACH_PRIVATE_WKCONTENTVIEW_ACTION(FORWARD_ACTION_TO_WKCONTENTVIEW)
 #endif
 }
 
+- (void)_closeAllMediaPresentations
+{
+    if (auto videoFullscreenManager = _page->videoFullscreenManager()) {
+        videoFullscreenManager->forEachSession([] (auto& model, auto& interface) {
+            model.requestFullscreenMode(WebCore::HTMLMediaElementEnums::VideoFullscreenModeNone);
+        });
+    }
+
+    if (auto fullScreenManager = _page->fullScreenManager(); fullScreenManager && fullScreenManager->isFullScreen())
+        fullScreenManager->close();
+}
+
 - (void)_stopAllMediaPlayback
 {
     _page->stopAllMediaPlayback();
index 9263301..95be80a 100644 (file)
@@ -423,6 +423,7 @@ typedef NS_OPTIONS(NSUInteger, _WKRectEdge) {
 - (void)_stopAllMediaPlayback;
 - (void)_suspendAllMediaPlayback;
 - (void)_resumeAllMediaPlayback;
+- (void)_closeAllMediaPresentations;
 
 - (void)_requestTextInputContextsInRect:(CGRect)rect completionHandler:(void(^)(NSArray<_WKTextInputContext *> *))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
 - (void)_focusTextInputContext:(_WKTextInputContext *)textInputElement completionHandler:(void(^)(BOOL))completionHandler WK_API_AVAILABLE(macos(WK_MAC_TBA), ios(WK_IOS_TBA));
index 3e268ce..237bde4 100644 (file)
@@ -138,6 +138,8 @@ public:
 
     PlatformVideoFullscreenInterface* controlsManagerInterface();
 
+    void forEachSession(Function<void(WebCore::VideoFullscreenModel&, PlatformVideoFullscreenInterface&)>&&);
+
 private:
     friend class VideoFullscreenModelContext;
 
index c8d65c3..4ea1b6d 100644 (file)
@@ -466,6 +466,30 @@ void VideoFullscreenManagerProxy::removeClientForContext(uint64_t contextId)
     m_clientCounts.set(contextId, clientCount);
 }
 
+void VideoFullscreenManagerProxy::forEachSession(Function<void(VideoFullscreenModel&, PlatformVideoFullscreenInterface&)>&& callback)
+{
+    if (m_contextMap.isEmpty())
+        return;
+
+    Vector<ModelInterfaceTuple> values;
+    values.reserveInitialCapacity(m_contextMap.size());
+    for (auto& value : m_contextMap.values())
+        values.uncheckedAppend(value);
+
+    for (auto& value : values) {
+        RefPtr<VideoFullscreenModelContext> model;
+        RefPtr<PlatformVideoFullscreenInterface> interface;
+        std::tie(model, interface) = value;
+
+        ASSERT(model);
+        ASSERT(interface);
+        if (!model || !interface)
+            continue;
+
+        callback(*model, *interface);
+    }
+}
+
 #pragma mark Messages from VideoFullscreenManager
 
 void VideoFullscreenManagerProxy::setupFullscreenWithID(uint64_t contextId, uint32_t videoLayerID, const WebCore::IntRect& initialRect, float hostingDeviceScaleFactor, HTMLMediaElementEnums::VideoFullscreenMode videoFullscreenMode, bool allowsPictureInPicture, bool standby)
index 12a2b3a..d03a172 100644 (file)
 
 - (void)didMoveToWindow
 {
-    if (!self._contentView.window)
+    if (!self._contentView.window || _applicationStateTracker)
         return;
 
-    ASSERT(!_applicationStateTracker);
     _applicationStateTracker = std::make_unique<WebKit::ApplicationStateTracker>(self, @selector(_applicationDidEnterBackground), @selector(_applicationDidFinishSnapshottingAfterEnteringBackground), @selector(_applicationWillEnterForeground));
     
     if (_lastObservedStateWasBackground && ![self isBackground])
index 4336c25..4dedc0a 100644 (file)
@@ -77,6 +77,7 @@ typedef enum FullScreenState : NSInteger FullScreenState;
 
 - (void)enterFullScreen:(NSScreen *)screen;
 - (void)exitFullScreen;
+- (void)exitFullScreenImmediately;
 - (void)requestExitFullScreen;
 - (void)close;
 - (void)beganEnterFullScreenWithInitialFrame:(NSRect)initialFrame finalFrame:(NSRect)finalFrame;
index 68b6cf6..43e9f96 100644 (file)
@@ -420,6 +420,18 @@ static const float minVideoWidth = 480 + 20 + 20; // Note: Keep in sync with med
     [self _manager]->willExitFullScreen();
 }
 
+- (void)exitFullScreenImmediately
+{
+    if (![self isFullScreen])
+        return;
+
+    [self _manager]->requestExitFullScreen();
+    [_webViewPlaceholder setExitWarningVisible:NO];
+    [self _manager]->willExitFullScreen();
+    _fullScreenState = ExitingFullScreen;
+    [self finishedExitFullScreenAnimation:YES];
+}
+
 - (void)requestExitFullScreen
 {
     [self _manager]->requestExitFullScreen();
@@ -581,7 +593,7 @@ static RetainPtr<CGImageRef> takeWindowSnapshot(CGSWindowID windowID, bool captu
     // normal exit full screen sequence, but don't wait to be called back
     // in response.
     if ([self isFullScreen])
-        [self exitFullScreen];
+        [self exitFullScreenImmediately];
     
     if (_fullScreenState == ExitingFullScreen)
         [self finishedExitFullScreenAnimation:YES];
index fe443b7..84cd264 100644 (file)
@@ -1,3 +1,15 @@
+2019-06-28  Jer Noble  <jer.noble@apple.com>
+
+        Add new -[WKWebView _closeAllMediaPresentations] SPI
+        https://bugs.webkit.org/show_bug.cgi?id=199294
+        <rdar://problem/51965958>
+
+        Reviewed by Alex Christensen.
+
+        * TestWebKitAPI/TestWebKitAPI.xcodeproj/project.pbxproj:
+        * TestWebKitAPI/Tests/WebKitCocoa/WKWebViewCloseAllMediaPresentations.mm: Added.
+        (TEST):
+
 2019-06-28  Sihui Liu  <sihui_liu@apple.com>
 
         Add a regression test for change r246901
index 7802aa3..a57f829 100644 (file)
                CDC9442E1EF1FC080059C3C4 /* MediaStreamTrackDetached.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDC9442C1EF1FC080059C3C4 /* MediaStreamTrackDetached.mm */; };
                CDC9442F1EF205D60059C3C4 /* mediastreamtrack-detached.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CDC9442B1EF1FBD20059C3C4 /* mediastreamtrack-detached.html */; };
                CDCFA7AA1E45183200C2433D /* SampleMap.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CDCFA7A91E45122F00C2433D /* SampleMap.cpp */; };
+               CDD68F0D22C18317000CF0AE /* WKWebViewCloseAllMediaPresentations.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDD68F0C22C18317000CF0AE /* WKWebViewCloseAllMediaPresentations.mm */; };
                CDE195B51CFE0B880053D256 /* FullscreenTopContentInset.html in Copy Resources */ = {isa = PBXBuildFile; fileRef = CDE195B21CFE0ADE0053D256 /* FullscreenTopContentInset.html */; };
                CDF0B78A216D48DC00421ECC /* CloseWebViewDuringEnterFullscreen.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDF0B789216D484300421ECC /* CloseWebViewDuringEnterFullscreen.mm */; };
                CDF92237216D186400647AA7 /* CloseWebViewAfterEnterFullscreen.mm in Sources */ = {isa = PBXBuildFile; fileRef = CDF92236216D186400647AA7 /* CloseWebViewAfterEnterFullscreen.mm */; };
                CDC9442B1EF1FBD20059C3C4 /* mediastreamtrack-detached.html */ = {isa = PBXFileReference; lastKnownFileType = text.html; path = "mediastreamtrack-detached.html"; sourceTree = "<group>"; };
                CDC9442C1EF1FC080059C3C4 /* MediaStreamTrackDetached.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = MediaStreamTrackDetached.mm; sourceTree = "<group>"; };
                CDCFA7A91E45122F00C2433D /* SampleMap.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SampleMap.cpp; sourceTree = "<group>"; };
+               CDD68F0C22C18317000CF0AE /* WKWebViewCloseAllMediaPresentations.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = WKWebViewCloseAllMediaPresentations.mm; sourceTree = "<group>"; };
                CDE195B21CFE0ADE0053D256 /* FullscreenTopContentInset.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = FullscreenTopContentInset.html; sourceTree = "<group>"; };
                CDE195B31CFE0ADE0053D256 /* FullscreenTopContentInset.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FullscreenTopContentInset.mm; sourceTree = "<group>"; };
                CDF0B789216D484300421ECC /* CloseWebViewDuringEnterFullscreen.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = CloseWebViewDuringEnterFullscreen.mm; sourceTree = "<group>"; };
                                93F56DA81E5F9181003EDE84 /* WKWebViewSnapshot.mm */,
                                CD7F89DB22A86CDA00D683AE /* WKWebViewSuspendAllMediaPlayback.mm */,
                                9984FACA1CFFAEEE008D198C /* WKWebViewTextInput.mm */,
+                               CDD68F0C22C18317000CF0AE /* WKWebViewCloseAllMediaPresentations.mm */,
                        );
                        name = "WebKit Cocoa";
                        path = WebKitCocoa;
                                CA38459620AE17A900990D3B /* LocalStorageDatabaseTracker.mm in Sources */,
                                46C519DA1D355AB200DAA51A /* LocalStorageNullEntries.mm in Sources */,
                                8C10AF99206467A90018FD90 /* LocalStoragePersistence.mm in Sources */,
+                               CDD68F0D22C18317000CF0AE /* WKWebViewCloseAllMediaPresentations.mm in Sources */,
                                7A6A2C701DCCFA8C00C0D085 /* LocalStorageQuirkTest.mm in Sources */,
                                076E507F1F4513D6006E9F5A /* Logging.cpp in Sources */,
                                CE1866491F72E8F100A0CAB6 /* MarkedText.cpp in Sources */,
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebViewCloseAllMediaPresentations.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKWebViewCloseAllMediaPresentations.mm
new file mode 100644 (file)
index 0000000..0043dbf
--- /dev/null
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#import "config.h"
+
+#import "PlatformUtilities.h"
+#import "TestWKWebView.h"
+#import <WebKit/WKPreferencesPrivate.h>
+#import <WebKit/WKWebViewPrivate.h>
+#import <wtf/RetainPtr.h>
+
+TEST(WKWebViewCloseAllMediaPresentations, PictureInPicture)
+{
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    configuration.get().preferences._allowsPictureInPictureMediaPlayback = YES;
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
+
+    [webView synchronouslyLoadHTMLString:@"<video src=video-with-audio.mp4 webkit-playsinline></video>"];
+    [webView objectByEvaluatingJavaScript:@"document.querySelector('video').addEventListener('webkitpresentationmodechanged', event => { window.webkit.messageHandlers.testHandler.postMessage('presentationmodechanged'); });"];
+
+    __block bool presentationModeChanged = false;
+    [webView performAfterReceivingMessage:@"presentationmodechanged" action:^{ presentationModeChanged = true; }];
+
+    [webView objectByEvaluatingJavaScriptWithUserGesture:@"document.querySelector('video').webkitSetPresentationMode('picture-in-picture')"];
+
+    TestWebKitAPI::Util::run(&presentationModeChanged);
+
+    presentationModeChanged = false;
+    [webView performAfterReceivingMessage:@"presentationmodechanged" action:^{ presentationModeChanged = true; }];
+
+    [webView _closeAllMediaPresentations];
+
+    TestWebKitAPI::Util::run(&presentationModeChanged);
+
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"document.querySelector('video').webkitPresentationMode"].UTF8String, "inline");
+}
+
+TEST(WKWebViewCloseAllMediaPresentations, VideoFullscreen)
+{
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    configuration.get().preferences._fullScreenEnabled = YES;
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
+
+    [webView synchronouslyLoadHTMLString:@"<video src=video-with-audio.mp4 webkit-playsinline></video>"];
+    [webView objectByEvaluatingJavaScript:@"document.querySelector('video').addEventListener('webkitpresentationmodechanged', event => { window.webkit.messageHandlers.testHandler.postMessage('presentationmodechanged'); });"];
+
+    __block bool presentationModeChanged = false;
+    [webView performAfterReceivingMessage:@"presentationmodechanged" action:^{ presentationModeChanged = true; }];
+
+    [webView objectByEvaluatingJavaScriptWithUserGesture:@"document.querySelector('video').webkitEnterFullscreen()"];
+
+    TestWebKitAPI::Util::run(&presentationModeChanged);
+
+    presentationModeChanged = false;
+    [webView performAfterReceivingMessage:@"presentationmodechanged" action:^{ presentationModeChanged = true; }];
+
+    [webView _closeAllMediaPresentations];
+
+    TestWebKitAPI::Util::run(&presentationModeChanged);
+
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"document.querySelector('video').webkitPresentationMode"].UTF8String, "inline");
+}
+
+TEST(WKWebViewCloseAllMediaPresentations, ElementFullscreen)
+{
+    auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+    configuration.get().preferences._fullScreenEnabled = YES;
+    auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get() addToWindow:YES]);
+
+    [webView synchronouslyLoadHTMLString:@"<div id=\"target\" style=\"width:100px;height:100px;background-color:red\"></div>"];
+    [webView objectByEvaluatingJavaScript:@"document.querySelector('#target').addEventListener('webkitfullscreenchange', event => { window.webkit.messageHandlers.testHandler.postMessage('fullscreenchange'); });"];
+
+    __block bool fullscreenModeChanged = false;
+    [webView performAfterReceivingMessage:@"fullscreenchange" action:^{ fullscreenModeChanged = true; }];
+
+    [webView objectByEvaluatingJavaScriptWithUserGesture:@"document.querySelector('#target').webkitRequestFullscreen()"];
+
+    TestWebKitAPI::Util::run(&fullscreenModeChanged);
+
+    fullscreenModeChanged = false;
+    [webView performAfterReceivingMessage:@"fullscreenchange" action:^{ fullscreenModeChanged = true; }];
+
+    [webView _closeAllMediaPresentations];
+
+    TestWebKitAPI::Util::run(&fullscreenModeChanged);
+
+    EXPECT_STREQ([webView stringByEvaluatingJavaScript:@"document.webkitFullscreenElement"].UTF8String, "<null>");
+}