blob: f0e5b5da998efacc194ed9664cabedf50526285f [file]
/*
* Copyright (C) 2025 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"
#if PLATFORM(IOS)
#import "PlatformUtilities.h"
#import "TestWKWebView.h"
#import <AVKit/AVKit.h>
#import <WebKit/WKUIDelegatePrivate.h>
#import <WebKit/WebKit.h>
#if HAVE(AVKIT)
#import <WebCore/WebAVPlayerLayer.h>
#endif
@interface VideoPresentationModeUIDelegate : NSObject <WKUIDelegate>
- (void)waitForDidEnterFullscreen;
- (void)waitForDidExitFullscreen;
- (void)waitForDidEnterStandby;
- (void)waitForDidExitStandby;
@end
@implementation VideoPresentationModeUIDelegate {
bool _willEnterFullscreen;
bool _didEnterFullscreen;
bool _didExitFullscreen;
bool _didEnterStandby;
bool _didExitStandby;
}
- (void)waitForDidEnterFullscreen
{
_willEnterFullscreen = false;
_didEnterFullscreen = false;
TestWebKitAPI::Util::run(&_willEnterFullscreen);
TestWebKitAPI::Util::run(&_didEnterFullscreen);
}
- (void)waitForDidExitFullscreen
{
_didExitFullscreen = false;
TestWebKitAPI::Util::run(&_didExitFullscreen);
}
- (void)waitForDidEnterStandby
{
_didEnterStandby = false;
TestWebKitAPI::Util::run(&_didEnterStandby);
}
- (void)waitForDidExitStandby
{
_didExitStandby = false;
TestWebKitAPI::Util::run(&_didExitStandby);
}
#pragma mark WKUIDelegate
- (void)_webViewWillEnterFullscreen:(WKWebView *)webView
{
_willEnterFullscreen = true;
}
- (void)_webViewDidEnterFullscreen:(WKWebView *)webView
{
_didEnterFullscreen = true;
}
- (void)_webViewDidExitFullscreen:(WKWebView *)webView
{
_didExitFullscreen = true;
}
- (void)_webViewDidEnterStandbyForTesting:(WKWebView *)webView
{
_didEnterStandby = true;
}
- (void)_webViewDidExitStandbyForTesting:(WKWebView *)webView
{
_didExitStandby = true;
}
@end
#pragma mark -
TEST(VideoPresentationMode, Fullscreen)
{
// This test must run in TestWebKitAPI.app
if (![NSBundle.mainBundle.bundleIdentifier isEqualToString:@"org.webkit.TestWebKitAPI"])
return;
RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration setAllowsInlineMediaPlayback:YES];
RetainPtr uiDelegate = adoptNS([[VideoPresentationModeUIDelegate alloc] init]);
RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView setUIDelegate:uiDelegate.get()];
[webView synchronouslyLoadHTMLString:@"<video src=video-with-audio.mp4 playsinline loop controls></video>"];
[webView evaluateJavaScript:@"document.querySelector('video').play()" completionHandler:nil];
[webView evaluateJavaScript:@"document.querySelector('video').webkitSetPresentationMode('fullscreen')" completionHandler:nil];
[uiDelegate waitForDidEnterFullscreen];
UIApplication *application = UIApplication.sharedApplication;
EXPECT_EQ(application.connectedScenes.count, 1U);
UIScene *scene = application.connectedScenes.anyObject;
RELEASE_ASSERT([scene isKindOfClass:UIWindowScene.class]);
UIViewController *fullScreenViewController = nil;
for (UIWindow *window in [(UIWindowScene *)scene windows]) {
UIViewController *presentedViewController = window.rootViewController.presentedViewController;
if ([presentedViewController isKindOfClass:NSClassFromString(@"AVFullScreenViewController")]) {
fullScreenViewController = presentedViewController;
break;
}
}
EXPECT_TRUE(!!fullScreenViewController);
UIView *fullScreenView = fullScreenViewController.viewIfLoaded;
EXPECT_TRUE(!!fullScreenView);
RetainPtr<UIView> playerLayerView;
RetainPtr viewStack = adoptNS([[NSMutableArray alloc] initWithObjects:fullScreenView, nil]);
while ([viewStack count]) {
RetainPtr view = (UIView *)[viewStack lastObject];
[viewStack removeLastObject];
if ([view isKindOfClass:NSClassFromString(@"WebAVPlayerLayerView")]) {
playerLayerView = WTF::move(view);
break;
}
[viewStack addObjectsFromArray:[view subviews]];
}
EXPECT_TRUE(!!playerLayerView);
[webView evaluateJavaScript:@"document.querySelector('video').webkitSetPresentationMode('inline')" completionHandler:nil];
[uiDelegate waitForDidExitFullscreen];
}
TEST(VideoPresentationMode, Inline)
{
// This test must run in TestWebKitAPI.app
if (![NSBundle.mainBundle.bundleIdentifier isEqualToString:@"org.webkit.TestWebKitAPI"])
return;
RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration setAllowsInlineMediaPlayback:YES];
RetainPtr uiDelegate = adoptNS([[VideoPresentationModeUIDelegate alloc] init]);
RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView setUIDelegate:uiDelegate.get()];
[webView synchronouslyLoadHTMLString:@"<video src=video-with-audio.mp4 playsinline loop controls></video>"];
[webView evaluateJavaScript:@"document.querySelector('video').play()" completionHandler:nil];
[webView evaluateJavaScript:@"document.querySelector('video').webkitSetPresentationMode('fullscreen')" completionHandler:nil];
[uiDelegate waitForDidEnterFullscreen];
[webView evaluateJavaScript:@"document.querySelector('video').webkitSetPresentationMode('inline')" completionHandler:nil];
[uiDelegate waitForDidExitFullscreen];
UIApplication *application = UIApplication.sharedApplication;
EXPECT_EQ(application.connectedScenes.count, 1U);
UIScene *scene = application.connectedScenes.anyObject;
RELEASE_ASSERT([scene isKindOfClass:UIWindowScene.class]);
UIViewController *fullScreenViewController = nil;
UIWindow *hostWindow = nil;
for (UIWindow *window in [(UIWindowScene *)scene windows]) {
UIViewController *presentedViewController = window.rootViewController.presentedViewController;
if ([presentedViewController isKindOfClass:NSClassFromString(@"AVFullScreenViewController")])
fullScreenViewController = presentedViewController;
if ([window isKindOfClass:NSClassFromString(@"TestWKWebViewHostWindow")])
hostWindow = window;
}
EXPECT_TRUE(!fullScreenViewController);
EXPECT_TRUE(!!hostWindow);
RetainPtr<UIView> playerLayerView;
RetainPtr viewStack = adoptNS([[NSMutableArray alloc] initWithObjects:hostWindow, nil]);
while ([viewStack count]) {
RetainPtr view = (UIView *)[viewStack lastObject];
[viewStack removeLastObject];
if ([view isKindOfClass:NSClassFromString(@"WebAVPlayerLayerView")]) {
playerLayerView = WTF::move(view);
break;
}
[viewStack addObjectsFromArray:[view subviews]];
}
EXPECT_TRUE(!!playerLayerView);
}
TEST(VideoPresentationMode, Standby)
{
// This test must run in TestWebKitAPI.app
if (![NSBundle.mainBundle.bundleIdentifier isEqualToString:@"org.webkit.TestWebKitAPI"])
return;
RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration setAllowsInlineMediaPlayback:YES];
[configuration preferences].elementFullscreenEnabled = YES;
RetainPtr uiDelegate = adoptNS([[VideoPresentationModeUIDelegate alloc] init]);
RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView setUIDelegate:uiDelegate.get()];
[webView synchronouslyLoadHTMLString:@"<video src=video-with-audio.mp4 playsinline loop controls></video>"];
[webView evaluateJavaScript:@"document.querySelector('video').play()" completionHandler:nil];
[webView evaluateJavaScript:@"document.querySelector('body').requestFullscreen()" completionHandler:nil];
[uiDelegate waitForDidEnterStandby];
UIApplication *application = UIApplication.sharedApplication;
EXPECT_EQ(application.connectedScenes.count, 1U);
UIScene *scene = application.connectedScenes.anyObject;
RELEASE_ASSERT([scene isKindOfClass:UIWindowScene.class]);
RetainPtr<UIViewController> playerViewController;
for (UIWindow *window in [(UIWindowScene *)scene windows]) {
UIViewController *rootViewController = window.rootViewController;
RetainPtr viewControllerStack = adoptNS([[NSMutableArray alloc] initWithObjects:rootViewController, nil]);
while ([viewControllerStack count]) {
RetainPtr viewController = (UIViewController *)[viewControllerStack lastObject];
[viewControllerStack removeLastObject];
if ([viewController isKindOfClass:AVPlayerViewController.class]) {
playerViewController = WTF::move(viewController);
EXPECT_TRUE(window.isHidden);
break;
}
[viewControllerStack addObjectsFromArray:[viewController childViewControllers]];
}
if (!!playerViewController)
break;
}
EXPECT_TRUE(!!playerViewController);
UIView *playerView = [playerViewController viewIfLoaded];
EXPECT_TRUE(playerView.isHidden);
[webView evaluateJavaScript:@"document.exitFullscreen()" completionHandler:nil];
[uiDelegate waitForDidExitStandby];
}
#if !PLATFORM(IOS_SIMULATOR)
TEST(VideoPresentationMode, PictureInPicture)
{
// This test must run in TestWebKitAPI.app
if (![NSBundle.mainBundle.bundleIdentifier isEqualToString:@"org.webkit.TestWebKitAPI"])
return;
RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration setAllowsInlineMediaPlayback:YES];
RetainPtr uiDelegate = adoptNS([[VideoPresentationModeUIDelegate alloc] init]);
RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView setUIDelegate:uiDelegate.get()];
[webView synchronouslyLoadHTMLString:@"<video src=video-with-audio.mp4 playsinline loop controls></video>"];
[webView evaluateJavaScript:@"document.querySelector('video').play()" completionHandler:nil];
[webView evaluateJavaScript:@"document.querySelector('video').webkitSetPresentationMode('picture-in-picture')" completionHandler:nil];
[uiDelegate waitForDidEnterFullscreen];
UIApplication *application = UIApplication.sharedApplication;
EXPECT_EQ(application.connectedScenes.count, 1U);
UIScene *scene = application.connectedScenes.anyObject;
RELEASE_ASSERT([scene isKindOfClass:UIWindowScene.class]);
RetainPtr<UIViewController> playerViewController;
for (UIWindow *window in [(UIWindowScene *)scene windows]) {
UIViewController *rootViewController = window.rootViewController;
RetainPtr viewControllerStack = adoptNS([[NSMutableArray alloc] initWithObjects:rootViewController, nil]);
while ([viewControllerStack count]) {
RetainPtr viewController = (UIViewController *)[viewControllerStack lastObject];
[viewControllerStack removeLastObject];
if ([viewController isKindOfClass:AVPlayerViewController.class]) {
playerViewController = WTF::move(viewController);
break;
}
[viewControllerStack addObjectsFromArray:[viewController childViewControllers]];
}
if (!!playerViewController)
break;
}
EXPECT_TRUE(!!playerViewController);
UIView *playerView = [playerViewController viewIfLoaded];
EXPECT_TRUE(!!playerView);
RetainPtr<UIView> playerLayerView;
RetainPtr viewStack = adoptNS([[NSMutableArray alloc] initWithObjects:playerView, nil]);
while ([viewStack count]) {
RetainPtr view = (UIView *)[viewStack lastObject];
[viewStack removeLastObject];
if ([view isKindOfClass:NSClassFromString(@"WebAVPlayerLayerView")]) {
playerLayerView = WTF::move(view);
break;
}
[viewStack addObjectsFromArray:[view subviews]];
}
EXPECT_TRUE(!!playerLayerView);
[webView evaluateJavaScript:@"document.querySelector('video').webkitSetPresentationMode('inline')" completionHandler:nil];
[uiDelegate waitForDidExitFullscreen];
}
#if HAVE(AVKIT)
TEST(VideoPresentationMode, CaptionPreview)
{
if (![NSBundle.mainBundle.bundleIdentifier isEqualToString:@"org.webkit.TestWebKitAPI"])
return;
RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration setAllowsInlineMediaPlayback:YES];
RetainPtr uiDelegate = adoptNS([[VideoPresentationModeUIDelegate alloc] init]);
RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 800, 600) configuration:configuration.get()]);
[webView setUIDelegate:uiDelegate.get()];
[webView synchronouslyLoadHTMLString:@"<video src=video-with-audio.mp4 playsinline loop controls></video>"];
[webView evaluateJavaScript:@"document.querySelector('video').play()" completionHandler:nil];
[webView evaluateJavaScript:@"document.querySelector('video').webkitSetPresentationMode('fullscreen')" completionHandler:nil];
[uiDelegate waitForDidEnterFullscreen];
RetainPtr<UIView> playerLayerView;
UIApplication *application = UIApplication.sharedApplication;
UIScene *scene = application.connectedScenes.anyObject;
RELEASE_ASSERT([scene isKindOfClass:UIWindowScene.class]);
for (UIWindow *window in [(UIWindowScene *)scene windows]) {
RetainPtr viewStack = adoptNS([[NSMutableArray alloc] initWithObjects:window, nil]);
while ([viewStack count]) {
RetainPtr view = (UIView *)[viewStack lastObject];
[viewStack removeLastObject];
if ([view isKindOfClass:NSClassFromString(@"WebAVPlayerLayerView")]) {
playerLayerView = WTF::move(view);
break;
}
[viewStack addObjectsFromArray:[view subviews]];
}
if (playerLayerView)
break;
}
EXPECT_TRUE(!!playerLayerView);
WebAVPlayerLayer *webAVPlayerLayer = (WebAVPlayerLayer *)[playerLayerView layer];
EXPECT_TRUE([webAVPlayerLayer isKindOfClass:[WebAVPlayerLayer class]]);
EXPECT_FALSE([webAVPlayerLayer showingCaptionPreview]);
[webAVPlayerLayer setCaptionPreviewProfileID:@"test-profile" position:CGPointMake(10, 20) text:@"Test caption text"];
EXPECT_TRUE([webAVPlayerLayer showingCaptionPreview]);
[webAVPlayerLayer stopShowingCaptionPreview];
EXPECT_FALSE([webAVPlayerLayer showingCaptionPreview]);
[webAVPlayerLayer setCaptionPreviewProfileID:@"another-profile" position:CGPointMake(50, 100) text:@"Another test"];
EXPECT_TRUE([webAVPlayerLayer showingCaptionPreview]);
[webAVPlayerLayer setCaptionPreviewProfileID:@"third-profile" position:CGPointMake(0, 0) text:nil];
EXPECT_TRUE([webAVPlayerLayer showingCaptionPreview]);
[webAVPlayerLayer stopShowingCaptionPreview];
EXPECT_FALSE([webAVPlayerLayer showingCaptionPreview]);
[webAVPlayerLayer stopShowingCaptionPreview];
EXPECT_FALSE([webAVPlayerLayer showingCaptionPreview]);
[webView evaluateJavaScript:@"document.querySelector('video').webkitSetPresentationMode('inline')" completionHandler:nil];
[uiDelegate waitForDidExitFullscreen];
}
#endif // HAVE(AVKIT)
#endif // !PLATFORM(IOS_SIMULATOR)
#endif // PLATFORM(IOS)