/* * Copyright (C) 2016 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 #import #import #import #if PLATFORM(MAC) @interface WKWebView (WKWebViewAdditions) - (void)_interactWithMediaControlsForTesting; @end @interface VideoControlsManagerTestWebView : TestWKWebView @end @implementation VideoControlsManagerTestWebView { bool _isDoneQueryingControlledElementID; NSString *_controlledElementID; } - (void)expectControlsManager:(BOOL)expectControlsManager afterReceivingMessage:(NSString *)message { __block bool doneWaiting = false; [self performAfterReceivingMessage:message action:^ { BOOL hasVideoForControlsManager = [self _hasActiveVideoForControlsManager]; if (expectControlsManager) EXPECT_TRUE(hasVideoForControlsManager); else EXPECT_FALSE(hasVideoForControlsManager); doneWaiting = true; }]; TestWebKitAPI::Util::run(&doneWaiting); } - (void)waitForMediaControlsToShow { while (![self _hasActiveVideoForControlsManager]) [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]; } - (void)waitForMediaControlsToHide { while ([self _hasActiveVideoForControlsManager]) [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]; } - (void)waitForPageToLoadWithAutoplayingVideos:(int)numberOfAutoplayingVideos { __block int remainingAutoplayedCount = numberOfAutoplayingVideos; __block bool autoplayingIsFinished = !numberOfAutoplayingVideos; __block bool pageHasLoaded = false; [self performAfterLoading:^() { pageHasLoaded = true; }]; if (numberOfAutoplayingVideos) { [self performAfterReceivingMessage:@"autoplayed" action:^() { remainingAutoplayedCount--; if (remainingAutoplayedCount <= 0) autoplayingIsFinished = true; }]; } TestWebKitAPI::Util::run(&pageHasLoaded); TestWebKitAPI::Util::run(&autoplayingIsFinished); } - (NSString *)controlledElementID { _isDoneQueryingControlledElementID = false; [self _requestControlledElementID]; TestWebKitAPI::Util::run(&_isDoneQueryingControlledElementID); return _controlledElementID; } - (void)_handleControlledElementIDResponse:(NSString *)identifier { _controlledElementID = [identifier copy]; _isDoneQueryingControlledElementID = true; } @end namespace TestWebKitAPI { RetainPtr setUpWebViewForTestingVideoControlsManager(NSRect frame) { RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]); configuration.get().mediaTypesRequiringUserActionForPlayback = WKAudiovisualMediaTypeNone; return adoptNS([[VideoControlsManagerTestWebView alloc] initWithFrame:frame configuration:configuration.get()]); } TEST(VideoControlsManager, VideoControlsManagerSingleLargeVideo) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 100, 100)); // A large video with audio should have a controls manager even if it is played via script like this video. // So the expectation is YES. [webView loadTestPageNamed:@"large-video-with-audio"]; [webView waitForMediaControlsToShow]; } TEST(VideoControlsManager, VideoControlsManagerSingleSmallVideo) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 100, 100)); // A small video will not have a controls manager unless it started playing because of a user gesture. Since this // video is started with a script, the expectation is NO. [webView loadTestPageNamed:@"video-with-audio"]; [webView expectControlsManager:NO afterReceivingMessage:@"playing"]; } TEST(VideoControlsManager, VideoControlsManagerMultipleVideosWithAudio) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 100, 100)); [webView loadTestPageNamed:@"large-videos-with-audio"]; [webView waitForPageToLoadWithAutoplayingVideos:0]; EXPECT_FALSE([webView _hasActiveVideoForControlsManager]); } // FIXME: Re-enable this test once is resolved. TEST(VideoControlsManager, DISABLED_VideoControlsManagerMultipleVideosWithAudioAndAutoplay) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 100, 100)); [webView loadTestPageNamed:@"large-videos-with-audio-autoplay"]; [webView waitForPageToLoadWithAutoplayingVideos:1]; EXPECT_TRUE([webView _hasActiveVideoForControlsManager]); EXPECT_TRUE([[webView controlledElementID] isEqualToString:@"bar"]); } TEST(VideoControlsManager, VideoControlsManagerMultipleVideosScrollPausedVideoOutOfView) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 500, 500)); [webView loadTestPageNamed:@"large-videos-paused-video-hides-controls"]; [webView waitForPageToLoadWithAutoplayingVideos:1]; [webView stringByEvaluatingJavaScript:@"pauseFirstVideoAndScrollToSecondVideo()"]; [webView expectControlsManager:NO afterReceivingMessage:@"paused"]; } TEST(VideoControlsManager, VideoControlsManagerMultipleVideosScrollPlayingVideoWithSoundOutOfView) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 500, 500)); [webView loadTestPageNamed:@"large-videos-playing-video-keeps-controls"]; [webView waitForPageToLoadWithAutoplayingVideos:1]; [webView stringByEvaluatingJavaScript:@"scrollToSecondVideo()"]; [webView expectControlsManager:YES afterReceivingMessage:@"scrolled"]; } TEST(VideoControlsManager, VideoControlsManagerMultipleVideosScrollPlayingMutedVideoOutOfView) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 500, 500)); [webView loadTestPageNamed:@"large-videos-playing-muted-video-hides-controls"]; [webView waitForPageToLoadWithAutoplayingVideos:1]; [webView stringByEvaluatingJavaScript:@"muteFirstVideoAndScrollToSecondVideo()"]; [webView expectControlsManager:NO afterReceivingMessage:@"playing"]; } TEST(VideoControlsManager, VideoControlsManagerMultipleVideosShowControlsForLastInteractedVideo) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 800, 600)); NSPoint clickPoint = NSMakePoint(400, 300); [webView loadTestPageNamed:@"large-videos-autoplaying-click-to-pause"]; [webView waitForPageToLoadWithAutoplayingVideos:2]; [webView mouseDownAtPoint:clickPoint simulatePressure:YES]; __block bool firstVideoPaused = false; __block bool secondVideoPaused = false; [webView performAfterReceivingMessage:@"paused" action:^ { NSString *controlledElementID = [webView controlledElementID]; if (firstVideoPaused) { EXPECT_TRUE([controlledElementID isEqualToString:@"second"]); secondVideoPaused = true; } else { EXPECT_TRUE([controlledElementID isEqualToString:@"first"]); [webView mouseDownAtPoint:clickPoint simulatePressure:YES]; } firstVideoPaused = true; }]; TestWebKitAPI::Util::run(&secondVideoPaused); } // FIXME: Re-enable this test once is resolved. TEST(VideoControlsManager, DISABLED_VideoControlsManagerMultipleVideosSwitchControlledVideoWhenScrolling) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 800, 600)); [webView loadTestPageNamed:@"large-videos-autoplaying-scroll-to-video"]; [webView waitForPageToLoadWithAutoplayingVideos:2]; [webView stringByEvaluatingJavaScript:@"scrollToSecondView()"]; [webView expectControlsManager:YES afterReceivingMessage:@"scrolled"]; EXPECT_TRUE([[webView controlledElementID] isEqualToString:@"second"]); } TEST(VideoControlsManager, VideoControlsManagerMultipleVideosScrollOnlyLargeVideoOutOfView) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 500, 500)); [webView loadTestPageNamed:@"large-video-playing-scroll-away"]; [webView waitForPageToLoadWithAutoplayingVideos:1]; [webView stringByEvaluatingJavaScript:@"scrollVideoOutOfView()"]; [webView expectControlsManager:YES afterReceivingMessage:@"scrolled"]; } TEST(VideoControlsManager, VideoControlsManagerSingleSmallAutoplayingVideo) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 100, 100)); [webView loadTestPageNamed:@"autoplaying-video-with-audio"]; [webView waitForPageToLoadWithAutoplayingVideos:1]; [webView mouseDownAtPoint:NSMakePoint(50, 50) simulatePressure:YES]; [webView expectControlsManager:YES afterReceivingMessage:@"paused"]; } TEST(VideoControlsManager, VideoControlsManagerLargeAutoplayingVideoSeeksAfterEnding) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 100, 100)); [webView loadTestPageNamed:@"large-video-seek-after-ending"]; // Immediately after ending, the controls should still be present. [webView expectControlsManager:YES afterReceivingMessage:@"ended"]; // At some point in the future, they should automatically hide. [webView waitForMediaControlsToHide]; } TEST(VideoControlsManager, VideoControlsManagerLargeAutoplayingVideoSeeksAndPlaysAfterEnding) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 100, 100)); // Since the video is still playing, the expectation is YES even if the video has ended once. [webView loadTestPageNamed:@"large-video-seek-to-beginning-and-play-after-ending"]; [webView expectControlsManager:YES afterReceivingMessage:@"replaying"]; } TEST(VideoControlsManager, VideoControlsManagerLargeAutoplayingVideoAfterSeekingToEnd) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 100, 100)); [webView loadTestPageNamed:@"large-video-hides-controls-after-seek-to-end"]; [webView waitForPageToLoadWithAutoplayingVideos:1]; [webView mouseDownAtPoint:NSMakePoint(50, 50) simulatePressure:YES]; // We expect there to be media controls, since this is a user gestured seek to the end. // This is akin to seeking to the end by scrubbing in the controls. [webView expectControlsManager:YES afterReceivingMessage:@"ended"]; } TEST(VideoControlsManager, VideoControlsManagerSingleLargeVideoWithoutAudio) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 100, 100)); // A large video with no audio will not have a controls manager unless it started playing because of a user gesture. Since this // video is started with a script, the expectation is NO. [webView loadTestPageNamed:@"large-video-without-audio"]; [webView expectControlsManager:NO afterReceivingMessage:@"playing"]; } TEST(VideoControlsManager, VideoControlsManagerAudioElementStartedWithScript) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 100, 100)); // An audio element MUST be started with a user gesture in order to have a controls manager, so the expectation is NO. [webView loadTestPageNamed:@"audio-only"]; [webView expectControlsManager:NO afterReceivingMessage:@"playing"]; } TEST(VideoControlsManager, VideoControlsManagerAudioElementStartedByInteraction) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 400, 400)); [webView loadTestPageNamed:@"play-audio-on-click"]; [webView waitForPageToLoadWithAutoplayingVideos:0]; [webView mouseDownAtPoint:NSMakePoint(200, 200) simulatePressure:YES]; // An audio element MUST be started with a user gesture in order to have a controls manager, so the expectation is YES. [webView expectControlsManager:YES afterReceivingMessage:@"playing-first"]; EXPECT_TRUE([[webView controlledElementID] isEqualToString:@"first"]); } TEST(VideoControlsManager, DISABLED_VideoControlsManagerAudioElementFollowingUserInteraction) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 400, 400)); [webView loadTestPageNamed:@"play-audio-on-click"]; [webView waitForPageToLoadWithAutoplayingVideos:0]; [webView performAfterReceivingMessage:@"playing-first" action:^ { [webView evaluateJavaScript:@"seekToEnd()" completionHandler:nil]; }]; __block bool secondAudioPlaying = false; [webView performAfterReceivingMessage:@"playing-second" action:^ { secondAudioPlaying = true; }]; [webView mouseDownAtPoint:NSMakePoint(200, 200) simulatePressure:YES]; TestWebKitAPI::Util::run(&secondAudioPlaying); while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) { if ([webView _hasActiveVideoForControlsManager] && [[webView controlledElementID] isEqualToString:@"second"]) break; } } TEST(VideoControlsManager, VideoControlsManagerTearsDownMediaControlsOnDealloc) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 100, 100)); NSURL *urlOfVideo = [[NSBundle mainBundle] URLForResource:@"video-with-audio" withExtension:@"mp4" subdirectory:@"TestWebKitAPI.resources"]; [webView loadFileURL:urlOfVideo allowingReadAccessToURL:[urlOfVideo URLByDeletingLastPathComponent]]; __block bool finishedTest = false; [webView performAfterLoading:^ { // Verify that we tear down the media controls properly, such that we don't crash when the web view is released. if ([webView respondsToSelector:@selector(_interactWithMediaControlsForTesting)]) [webView _interactWithMediaControlsForTesting]; finishedTest = true; }]; TestWebKitAPI::Util::run(&finishedTest); } TEST(VideoControlsManager, VideoControlsManagerDoesNotShowMediaControlsForOffscreenVideo) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 1024, 768)); [webView loadTestPageNamed:@"large-video-offscreen"]; [webView waitForMediaControlsToHide]; } TEST(VideoControlsManager, VideoControlsManagerKeepsControlsStableDuringSrcChangeOnClick) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 800, 600)); [webView loadTestPageNamed:@"change-video-source-on-click"]; [webView waitForPageToLoadWithAutoplayingVideos:1]; [webView mouseDownAtPoint:NSMakePoint(400, 300) simulatePressure:YES]; [webView expectControlsManager:YES afterReceivingMessage:@"changed"]; } TEST(VideoControlsManager, VideoControlsManagerKeepsControlsStableDuringSrcChangeOnEnd) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 800, 600)); [webView loadTestPageNamed:@"change-video-source-on-end"]; [webView expectControlsManager:YES afterReceivingMessage:@"changed"]; } TEST(VideoControlsManager, VideoControlsManagerSmallVideoInMediaDocument) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 100, 100)); __block bool finishedLoad = false; [webView performAfterLoading:^ { finishedLoad = true; }]; NSURL *urlOfVideo = [[NSBundle mainBundle] URLForResource:@"video-with-audio" withExtension:@"mp4" subdirectory:@"TestWebKitAPI.resources"]; [webView loadFileURL:urlOfVideo allowingReadAccessToURL:[urlOfVideo URLByDeletingLastPathComponent]]; TestWebKitAPI::Util::run(&finishedLoad); // We expect the media controller to be present because this is a media document. EXPECT_TRUE([webView _hasActiveVideoForControlsManager]); } TEST(VideoControlsManager, VideoControlsManagerOffscreenIframeMediaDocument) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 800, 600)); [webView loadTestPageNamed:@"offscreen-iframe-of-media-document"]; // We do not expect a controls manager becuase the media document is in an iframe. [webView expectControlsManager:NO afterReceivingMessage:@"loaded"]; } TEST(VideoControlsManager, VideoControlsManagerLongSkinnyVideoInWideMainFrame) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 1600, 800)); [webView loadTestPageNamed:@"skinny-autoplaying-video-with-audio"]; [webView expectControlsManager:NO afterReceivingMessage:@"playing"]; } TEST(VideoControlsManager, VideoControlsManagerWideMediumSizedVideoInWideMainFrame) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 1600, 800)); [webView loadTestPageNamed:@"wide-autoplaying-video-with-audio"]; [webView expectControlsManager:YES afterReceivingMessage:@"playing"]; } TEST(VideoControlsManager, VideoControlsManagerFullSizeVideoInWideMainFrame) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 1600, 800)); [webView loadTestPageNamed:@"full-size-autoplaying-video-with-audio"]; [webView expectControlsManager:YES afterReceivingMessage:@"playing"]; } TEST(VideoControlsManager, VideoControlsManagerVideoMutesOnPlaying) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 500, 500)); [webView loadTestPageNamed:@"large-video-mutes-onplaying"]; [webView expectControlsManager:NO afterReceivingMessage:@"playing"]; } TEST(VideoControlsManager, VideoControlsManagerPageWithEnormousVideo) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 500, 500)); [webView loadTestPageNamed:@"enormous-video-with-sound"]; [webView expectControlsManager:NO afterReceivingMessage:@"playing"]; } TEST(VideoControlsManager, VideoControlsManagerDoesNotChangeValuesExposedToJavaScript) { RetainPtr webView = setUpWebViewForTestingVideoControlsManager(NSMakeRect(0, 0, 500, 500)); // A large video with audio should have a controls manager even if it is played via script like this video. // So the expectation is YES. [webView loadTestPageNamed:@"large-video-with-audio"]; [webView waitForMediaControlsToShow]; [webView _updateMediaPlaybackControlsManager]; EXPECT_EQ(1.0, [[webView objectByEvaluatingJavaScript:@"document.getElementsByTagName('video')[0].playbackRate"] doubleValue]); EXPECT_EQ(1.0, [[webView objectByEvaluatingJavaScript:@"document.getElementsByTagName('video')[0].defaultPlaybackRate"] doubleValue]); [webView objectByEvaluatingJavaScript:@"document.getElementsByTagName('video')[0].playbackRate = 2.0;"]; EXPECT_EQ(2.0, [[webView objectByEvaluatingJavaScript:@"document.getElementsByTagName('video')[0].playbackRate"] doubleValue]); EXPECT_EQ(1.0, [[webView objectByEvaluatingJavaScript:@"document.getElementsByTagName('video')[0].defaultPlaybackRate"] doubleValue]); } } // namespace TestWebKitAPI #endif // PLATFORM(MAC)