| /* |
| * 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 |