blob: 8280f539354e7185e9ebe4044361786363f8b330 [file] [log] [blame]
/*
* 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 "Test.h"
#import "TestNavigationDelegate.h"
#import "TestWKWebView.h"
#import <WebKit/WKPreferences.h>
#import <WebKit/WKPreferencesPrivate.h>
#import <WebKit/WKProcessPoolPrivate.h>
#import <WebKit/WKWebView.h>
#import <WebKit/WKWebViewConfiguration.h>
#import <WebKit/WKWebViewPrivateForTesting.h>
#import <WebKit/_WKProcessPoolConfiguration.h>
#import <wtf/RetainPtr.h>
#if PLATFORM(IOS_FAMILY)
static bool didLayout;
static bool didEndAnimatedResize;
static bool didChangeSafeAreaShouldAffectObscuredInsets;
@interface AnimatedResizeWebView : WKWebView <WKUIDelegate>
@end
@implementation AnimatedResizeWebView
- (void)_endAnimatedResize
{
didEndAnimatedResize = true;
[super _endAnimatedResize];
}
- (void)_webView:(WKWebView *)webView didChangeSafeAreaShouldAffectObscuredInsets:(BOOL)safeAreaShouldAffectObscuredInsets
{
didChangeSafeAreaShouldAffectObscuredInsets = true;
}
@end
static RetainPtr<AnimatedResizeWebView> createAnimatedResizeWebView()
{
auto processPoolConfiguration = adoptNS([[_WKProcessPoolConfiguration alloc] init]);
[processPoolConfiguration setIgnoreSynchronousMessagingTimeoutsForTesting:YES];
auto processPool = adoptNS([[WKProcessPool alloc] _initWithConfiguration:processPoolConfiguration.get()]);
auto webViewConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]);
[webViewConfiguration setProcessPool:processPool.get()];
auto webView = adoptNS([[AnimatedResizeWebView alloc] initWithFrame:CGRectMake(0, 0, 800, 600) configuration:webViewConfiguration.get()]);
return webView;
}
static RetainPtr<TestNavigationDelegate> createFirstVisuallyNonEmptyWatchingNavigationDelegate()
{
auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
[navigationDelegate setRenderingProgressDidChange:^(WKWebView *, _WKRenderingProgressEvents progressEvents) {
if (progressEvents & _WKRenderingProgressEventFirstVisuallyNonEmptyLayout)
didLayout = true;
}];
return navigationDelegate;
}
TEST(WebKit, DISABLED_ResizeWithHiddenContentDoesNotHang)
{
auto webView = createAnimatedResizeWebView();
[webView loadRequest:[NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"blinking-div" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]]];
auto navigationDelegate = createFirstVisuallyNonEmptyWatchingNavigationDelegate();
[webView setNavigationDelegate:navigationDelegate.get()];
auto window = adoptNS([[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
[window addSubview:webView.get()];
[window setHidden:NO];
TestWebKitAPI::Util::run(&didLayout);
didLayout = false;
for (unsigned i = 0; i < 50; i++) {
[webView _resizeWhileHidingContentWithUpdates:^{
[webView setFrame:CGRectMake(0, 0, [webView frame].size.width + 100, 400)];
}];
TestWebKitAPI::Util::run(&didEndAnimatedResize);
didEndAnimatedResize = false;
}
}
TEST(WebKit, AnimatedResizeDoesNotHang)
{
auto webView = createAnimatedResizeWebView();
[webView loadRequest:[NSURLRequest requestWithURL:[[NSBundle mainBundle] URLForResource:@"blinking-div" withExtension:@"html" subdirectory:@"TestWebKitAPI.resources"]]];
auto navigationDelegate = createFirstVisuallyNonEmptyWatchingNavigationDelegate();
[webView setNavigationDelegate:navigationDelegate.get()];
auto window = adoptNS([[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
[window addSubview:webView.get()];
[window setHidden:NO];
TestWebKitAPI::Util::run(&didLayout);
didLayout = false;
for (unsigned i = 0; i < 50; i++) {
[webView _beginAnimatedResizeWithUpdates:^{
[webView setFrame:CGRectMake(0, 0, [webView frame].size.width + 100, 400)];
}];
dispatch_async(dispatch_get_main_queue(), ^{
[webView _endAnimatedResize];
});
TestWebKitAPI::Util::run(&didEndAnimatedResize);
didEndAnimatedResize = false;
}
}
TEST(WebKit, AnimatedResizeBlocksViewportFitChanges)
{
auto webView = createAnimatedResizeWebView();
[webView setUIDelegate:webView.get()];
// We need to have something loaded before beginning the animated
// resize, or it will bail.
[webView loadHTMLString:@"<head></head>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
auto window = adoptNS([[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
[window addSubview:webView.get()];
[window setHidden:NO];
[webView _beginAnimatedResizeWithUpdates:^{
[webView setFrame:CGRectMake(0, 0, [webView frame].size.width + 100, 400)];
}];
// Load a page that will change the state of viewport-fit,
// in the middle of the resize.
[webView loadHTMLString:@"<head><meta name='viewport' content='viewport-fit=cover'></head>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
didChangeSafeAreaShouldAffectObscuredInsets = false;
// Wait for a commit to come in /after/ loading the viewport-fit=cover
// page, and ensure that we didn't call the UIDelegate callback,
// because we're still in the resize. Then, end the resize.
[webView _doAfterNextPresentationUpdateWithoutWaitingForAnimatedResizeForTesting:^{
EXPECT_FALSE(didChangeSafeAreaShouldAffectObscuredInsets);
[webView _endAnimatedResize];
}];
TestWebKitAPI::Util::run(&didEndAnimatedResize);
didEndAnimatedResize = false;
// Wait for one more commit so that we see the viewport-fit state
// change actually take place (post-resize), and ensure that it does.
__block bool didGetCommitAfterEndAnimatedResize = false;
[webView _doAfterNextPresentationUpdate:^{
didGetCommitAfterEndAnimatedResize = true;
}];
TestWebKitAPI::Util::run(&didGetCommitAfterEndAnimatedResize);
EXPECT_TRUE(didChangeSafeAreaShouldAffectObscuredInsets);
}
TEST(WebKit, OverrideLayoutSizeChangesDuringAnimatedResizeSucceed)
{
auto webView = createAnimatedResizeWebView();
[webView setUIDelegate:webView.get()];
[webView _overrideLayoutParametersWithMinimumLayoutSize:CGSizeMake(200, 50) maximumUnobscuredSizeOverride:CGSizeMake(200, 50)];
[webView loadHTMLString:@"<head><meta name='viewport' content='initial-scale=1'></head>" baseURL:nil];
auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
webView.get().navigationDelegate = navigationDelegate.get();
[navigationDelegate waitForDidFinishNavigation];
auto window = adoptNS([[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
[window addSubview:webView.get()];
[window setHidden:NO];
[webView _beginAnimatedResizeWithUpdates:^{
[webView setFrame:CGRectMake(0, 0, [webView frame].size.width + 100, 400)];
}];
[webView _overrideLayoutParametersWithMinimumLayoutSize:CGSizeMake(100, 200) maximumUnobscuredSizeOverride:CGSizeMake(100, 200)];
[webView _endAnimatedResize];
__block bool didReadLayoutSize = false;
[webView _doAfterNextPresentationUpdate:^{
[webView evaluateJavaScript:@"[window.innerWidth, window.innerHeight]" completionHandler:^(id value, NSError *error) {
CGFloat innerWidth = [[value objectAtIndex:0] floatValue];
CGFloat innerHeight = [[value objectAtIndex:1] floatValue];
EXPECT_EQ(innerWidth, 100);
EXPECT_EQ(innerHeight, 200);
didReadLayoutSize = true;
}];
}];
TestWebKitAPI::Util::run(&didReadLayoutSize);
}
TEST(WebKit, OverrideLayoutSizeIsRestoredAfterProcessRelaunch)
{
auto webView = createAnimatedResizeWebView();
[webView setUIDelegate:webView.get()];
[webView _overrideLayoutParametersWithMinimumLayoutSize:CGSizeMake(200, 50) maximumUnobscuredSizeOverride:CGSizeMake(200, 50)];
[webView loadHTMLString:@"<head><meta name='viewport' content='initial-scale=1'></head>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
auto window = adoptNS([[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
[window addSubview:webView.get()];
[window setHidden:NO];
[webView _killWebContentProcessAndResetState];
[webView loadHTMLString:@"<head><meta name='viewport' content='initial-scale=1'></head>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
__block bool didReadLayoutSize = false;
[webView evaluateJavaScript:@"[window.innerWidth, window.innerHeight]" completionHandler:^(id value, NSError *error) {
CGFloat innerWidth = [[value objectAtIndex:0] floatValue];
CGFloat innerHeight = [[value objectAtIndex:1] floatValue];
EXPECT_EQ(innerWidth, 200);
EXPECT_EQ(innerHeight, 50);
didReadLayoutSize = true;
}];
TestWebKitAPI::Util::run(&didReadLayoutSize);
}
TEST(WebKit, OverrideLayoutSizeIsRestoredAfterChangingDuringProcessRelaunch)
{
auto webView = createAnimatedResizeWebView();
[webView setUIDelegate:webView.get()];
[webView _overrideLayoutParametersWithMinimumLayoutSize:CGSizeMake(100, 100) maximumUnobscuredSizeOverride:CGSizeMake(100, 100)];
[webView loadHTMLString:@"<head><meta name='viewport' content='initial-scale=1'></head>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
auto window = adoptNS([[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
[window addSubview:webView.get()];
[window setHidden:NO];
[webView _killWebContentProcessAndResetState];
[webView _overrideLayoutParametersWithMinimumLayoutSize:CGSizeMake(200, 50) maximumUnobscuredSizeOverride:CGSizeMake(200, 50)];
[webView loadHTMLString:@"<head><meta name='viewport' content='initial-scale=1'></head>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
__block bool didReadLayoutSize = false;
[webView evaluateJavaScript:@"[window.innerWidth, window.innerHeight]" completionHandler:^(id value, NSError *error) {
CGFloat innerWidth = [[value objectAtIndex:0] floatValue];
CGFloat innerHeight = [[value objectAtIndex:1] floatValue];
EXPECT_EQ(innerWidth, 200);
EXPECT_EQ(innerHeight, 50);
didReadLayoutSize = true;
}];
TestWebKitAPI::Util::run(&didReadLayoutSize);
}
TEST(WebKit, ChangeFrameAndMinimumEffectiveDeviceWidthDuringAnimatedResize)
{
auto webView = createAnimatedResizeWebView();
[[webView configuration] preferences]._shouldIgnoreMetaViewport = YES;
[webView setUIDelegate:webView.get()];
[webView loadHTMLString:@"<body>Hello world</body>" baseURL:nil];
auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
[webView setNavigationDelegate:navigationDelegate.get()];
[navigationDelegate waitForDidFinishNavigation];
auto window = adoptNS([[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
[window addSubview:webView.get()];
[window setHidden:NO];
[webView _beginAnimatedResizeWithUpdates:^{
[webView _setMinimumEffectiveDeviceWidth:1000];
[webView setFrame:CGRectMake(0, 0, 500, 375)];
}];
[webView _endAnimatedResize];
__block bool didReadLayoutSize = false;
[webView _doAfterNextPresentationUpdate:^{
[webView evaluateJavaScript:@"[window.innerWidth, window.innerHeight, visualViewport.scale]" completionHandler:^(NSArray<NSNumber *> *value, NSError *error) {
CGFloat innerWidth = value[0].floatValue;
CGFloat innerHeight = value[1].floatValue;
CGFloat scale = value[2].floatValue;
EXPECT_EQ(innerWidth, 1000);
EXPECT_EQ(innerHeight, 750);
EXPECT_EQ(scale, 0.5);
didReadLayoutSize = true;
}];
}];
TestWebKitAPI::Util::run(&didReadLayoutSize);
}
static UIView *immediateSubviewOfClass(UIView *view, Class cls)
{
UIView *foundSubview = nil;
for (UIView *subview in view.subviews) {
if ([subview isKindOfClass:cls]) {
// Make it harder to write a bad test; if there's more than one subview
// of the given class, fail the test!
ASSERT(!foundSubview);
foundSubview = subview;
}
}
return foundSubview;
}
TEST(WebKit, ResizeWithContentHiddenCompletes)
{
auto webView = createAnimatedResizeWebView();
[webView setUIDelegate:webView.get()];
[webView loadHTMLString:@"<head><meta name='viewport' content='initial-scale=1'></head>" baseURL:nil];
auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
webView.get().navigationDelegate = navigationDelegate.get();
[navigationDelegate waitForDidFinishNavigation];
auto window = adoptNS([[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
[window addSubview:webView.get()];
[window setHidden:NO];
[webView _resizeWhileHidingContentWithUpdates:^{
[webView setFrame:CGRectMake(0, 0, 100, 200)];
}];
__block bool didReadLayoutSize = false;
[webView _doAfterNextPresentationUpdate:^{
[webView evaluateJavaScript:@"[window.innerWidth, window.innerHeight]" completionHandler:^(id value, NSError *error) {
CGFloat innerWidth = [[value objectAtIndex:0] floatValue];
CGFloat innerHeight = [[value objectAtIndex:1] floatValue];
EXPECT_EQ(innerWidth, 100);
EXPECT_EQ(innerHeight, 200);
didReadLayoutSize = true;
}];
}];
TestWebKitAPI::Util::run(&didReadLayoutSize);
UIView *scrollView = immediateSubviewOfClass(webView.get(), NSClassFromString(@"WKScrollView"));
UIView *contentView = immediateSubviewOfClass(scrollView, NSClassFromString(@"WKContentView"));
// Make sure that we've put the view hierarchy back together after the resize completed.
EXPECT_NOT_NULL(scrollView);
EXPECT_NOT_NULL(contentView);
EXPECT_FALSE(contentView.hidden);
}
TEST(WebKit, ResizeWithContentHiddenWithSubsequentNoOpResizeCompletes)
{
auto webView = createAnimatedResizeWebView();
[webView setUIDelegate:webView.get()];
[webView loadHTMLString:@"<head><meta name='viewport' content='initial-scale=1'></head>" baseURL:nil];
auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
webView.get().navigationDelegate = navigationDelegate.get();
[navigationDelegate waitForDidFinishNavigation];
auto window = adoptNS([[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
[window addSubview:webView.get()];
[window setHidden:NO];
[webView _resizeWhileHidingContentWithUpdates:^{
[webView setFrame:CGRectMake(0, 0, 100, 200)];
}];
[webView _beginAnimatedResizeWithUpdates:^{
[webView setFrame:CGRectMake(0, 0, 100, 200)];
}];
__block bool didReadLayoutSize = false;
[webView _doAfterNextPresentationUpdate:^{
[webView evaluateJavaScript:@"[window.innerWidth, window.innerHeight]" completionHandler:^(id value, NSError *error) {
CGFloat innerWidth = [[value objectAtIndex:0] floatValue];
CGFloat innerHeight = [[value objectAtIndex:1] floatValue];
EXPECT_EQ(innerWidth, 100);
EXPECT_EQ(innerHeight, 200);
didReadLayoutSize = true;
}];
}];
TestWebKitAPI::Util::run(&didReadLayoutSize);
UIView *scrollView = immediateSubviewOfClass(webView.get(), NSClassFromString(@"WKScrollView"));
UIView *contentView = immediateSubviewOfClass(scrollView, NSClassFromString(@"WKContentView"));
// Make sure that we've put the view hierarchy back together after the resize completed.
EXPECT_NOT_NULL(scrollView);
EXPECT_NOT_NULL(contentView);
EXPECT_FALSE(contentView.hidden);
}
TEST(WebKit, AnimatedResizeBlocksDoAfterNextPresentationUpdate)
{
auto webView = createAnimatedResizeWebView();
[webView setUIDelegate:webView.get()];
// We need to have something loaded before beginning the animated
// resize, or it will bail.
[webView loadHTMLString:@"<head></head>" baseURL:nil];
[webView _test_waitForDidFinishNavigation];
auto window = adoptNS([[UIWindow alloc] initWithFrame:CGRectMake(0, 0, 800, 600)]);
[window addSubview:webView.get()];
[window setHidden:NO];
[webView _beginAnimatedResizeWithUpdates:^{
[webView setFrame:CGRectMake(0, 0, [webView frame].size.width + 100, 400)];
}];
__block bool didGetCommitAfterEndAnimatedResize = false;
// Despite being invoked first, this doAfterNextPresentationUpdate
// should be deferred until after we call endAnimatedResize, inside
// the below _doAfterNextPresentationUpdateWithoutWaitingForAnimatedResize.
[webView _doAfterNextPresentationUpdate:^{
EXPECT_TRUE(didEndAnimatedResize);
didGetCommitAfterEndAnimatedResize = true;
}];
[webView _doAfterNextPresentationUpdateWithoutWaitingForAnimatedResizeForTesting:^{
[webView _endAnimatedResize];
}];
TestWebKitAPI::Util::run(&didEndAnimatedResize);
TestWebKitAPI::Util::run(&didGetCommitAfterEndAnimatedResize);
}
TEST(WebKit, CreateWebPageAfterAnimatedResize)
{
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 1024, 768)]);
[webView synchronouslyLoadTestPageNamed:@"large-red-square-image"];
[webView _beginAnimatedResizeWithUpdates:^{
[webView setFrame:CGRectMake(0, 0, 768, 1024)];
}];
__block bool doneWaitingForPresentationUpdate = false;
[webView _doAfterNextPresentationUpdate:^{
doneWaitingForPresentationUpdate = true;
}];
[webView _doAfterNextPresentationUpdateWithoutWaitingForAnimatedResizeForTesting:^{
[webView _endAnimatedResize];
}];
TestWebKitAPI::Util::run(&doneWaitingForPresentationUpdate);
[webView _killWebContentProcessAndResetState];
[webView synchronouslyLoadTestPageNamed:@"large-red-square-image"];
NSArray<NSNumber *> *dimensions = [webView objectByEvaluatingJavaScript:@"[innerWidth, innerHeight]"];
EXPECT_EQ(768, dimensions.firstObject.intValue);
EXPECT_EQ(1024, dimensions.lastObject.intValue);
}
#endif