/* * Copyright (C) 2021 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 ENABLE(APP_HIGHLIGHTS) #import "PlatformUtilities.h" #import "Test.h" #import "TestNavigationDelegate.h" #import "TestWKWebView.h" #import "WKWebViewConfigurationExtras.h" #import #import #import #import #import #import #if PLATFORM(IOS_FAMILY) #import "UIKitSPI.h" #endif @interface AppHighlightDelegate : NSObject <_WKAppHighlightDelegate> @property (nonatomic, copy) void (^storeAppHighlightCallback)(WKWebView *, _WKAppHighlight *, BOOL, BOOL); @end @implementation AppHighlightDelegate - (void)_webView:(WKWebView *)webView storeAppHighlight:(_WKAppHighlight *)highlight inNewGroup:(BOOL)inNewGroup requestOriginatedInApp:(BOOL)requestOriginatedInApp { if (_storeAppHighlightCallback) _storeAppHighlightCallback(webView, highlight, inNewGroup, requestOriginatedInApp); } @end namespace TestWebKitAPI { RetainPtr<_WKAppHighlight> createAppHighlightWithHTML(NSString *HTMLString, NSString *javaScript, NSString *highlightText) { WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES]; auto delegate = adoptNS([[AppHighlightDelegate alloc] init]); auto webViewCreate = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500) configuration:configuration]); [webViewCreate _setAppHighlightDelegate:delegate.get()]; [webViewCreate synchronouslyLoadHTMLString:HTMLString]; [webViewCreate stringByEvaluatingJavaScript:javaScript]; __block RetainPtr<_WKAppHighlight> resultHighlight; __block bool done = false; [delegate setStoreAppHighlightCallback:^(WKWebView *delegateWebView, _WKAppHighlight *highlight, BOOL inNewGroup, BOOL requestOriginatedInApp) { EXPECT_EQ(delegateWebView, webViewCreate.get()); EXPECT_NOT_NULL(highlight); EXPECT_WK_STREQ(highlight.text, highlightText); EXPECT_NOT_NULL(highlight.highlight); resultHighlight = highlight; done = true; }]; [webViewCreate _addAppHighlight]; TestWebKitAPI::Util::run(&done); return resultHighlight; } RetainPtr createWebViewForAppHighlightsWithHTML(NSString *HTMLString) { WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES]; auto webViewRestore = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500) configuration:configuration]); [webViewRestore synchronouslyLoadHTMLString:HTMLString]; return webViewRestore; } TEST(AppHighlights, AppHighlightCreateAndRestore) { auto highlight = createAppHighlightWithHTML(@"Test", @"document.execCommand('SelectAll')", @"Test"); auto webViewRestore = createWebViewForAppHighlightsWithHTML(@"Test"); [webViewRestore _restoreAppHighlights:@[[highlight highlight]]]; TestWebKitAPI::Util::waitForConditionWithLogging([&] () -> bool { return [webViewRestore stringByEvaluatingJavaScript:@"internals.numberOfAppHighlights()"].intValue == 1; }, 2, @"Expected Highlights to be populated."); } TEST(AppHighlights, AppHighlightCreateAndRestoreAndScroll) { auto highlight = createAppHighlightWithHTML(@"
Test", @"document.execCommand('SelectAll')", @"Test"); auto webViewRestore = createWebViewForAppHighlightsWithHTML(@"
Test"); [webViewRestore _restoreAndScrollToAppHighlight:[highlight highlight]]; TestWebKitAPI::Util::waitForConditionWithLogging([&] () -> bool { return [webViewRestore stringByEvaluatingJavaScript:@"internals.numberOfAppHighlights()"].intValue == 1 && [[webViewRestore objectByEvaluatingJavaScript:@"pageYOffset"] floatValue] > 0; }, 2, @"Expected Highlights to be populated and the page to scroll."); } TEST(AppHighlights, AppHighlightRestoreFailure) { auto highlight = createAppHighlightWithHTML(@"Test", @"document.execCommand('SelectAll')", @"Test"); auto webViewRestore = createWebViewForAppHighlightsWithHTML(@"Not The Same"); [webViewRestore _restoreAppHighlights:@[[highlight highlight]]]; TestWebKitAPI::Util::waitForConditionWithLogging([&] () -> bool { return ![webViewRestore stringByEvaluatingJavaScript:@"internals.numberOfAppHighlights()"].intValue; }, 2, @"Expected Highlights not to be populated."); } // Ensure that future versions of the blob format can add additional data and still be decoded successfully by version of WebKit that only know about the current format. TEST(AppHighlights, AppHighlightCreateAndRestoreWithExtraBytes) { auto highlight = createAppHighlightWithHTML(@"Test", @"document.execCommand('SelectAll')", @"Test"); auto webViewRestore = createWebViewForAppHighlightsWithHTML(@"Test"); NSMutableData *modifiedHighlightData = [NSMutableData dataWithData: [highlight highlight] ]; [modifiedHighlightData appendData:[@"TestV2Data" dataUsingEncoding:NSUTF8StringEncoding]]; [webViewRestore synchronouslyLoadHTMLString:@"Test"]; [webViewRestore _restoreAppHighlights:@[ modifiedHighlightData ]]; TestWebKitAPI::Util::waitForConditionWithLogging([&] () -> bool { return [webViewRestore stringByEvaluatingJavaScript:@"internals.numberOfAppHighlights()"].intValue == 1; }, 2, @"Expected Highlights to be populated."); } // Older versions of WebKit need to be able to decode blobs encoded on newer versions of WebKit, so ensure that is possible. TEST(AppHighlights, AppHighlightCreateAndRestoreWithLaterVersion) { auto highlight = createAppHighlightWithHTML(@"Test", @"document.execCommand('SelectAll')", @"Test"); auto webViewRestore = createWebViewForAppHighlightsWithHTML(@"Test"); uint64_t maximumVersion = std::numeric_limits::max(); NSData *versionData = [NSData dataWithBytes:&maximumVersion length:sizeof(maximumVersion)]; NSMutableData *modifiedHighlightData = [NSMutableData dataWithData:[highlight highlight]]; [modifiedHighlightData replaceBytesInRange:NSMakeRange(sizeof(uint64_t), sizeof(maximumVersion)) withBytes:versionData]; [webViewRestore synchronouslyLoadHTMLString:@"Test"]; [webViewRestore _restoreAppHighlights:@[ modifiedHighlightData ]]; TestWebKitAPI::Util::waitForConditionWithLogging([&] () -> bool { return [webViewRestore stringByEvaluatingJavaScript:@"internals.numberOfAppHighlights()"].intValue == 1; }, 2, @"Expected Highlights to be populated."); } // Ensure that shorter data blobs will correctly fail to decode. TEST(AppHighlights, AppHighlightCreateAndRestoreAndDropBytes) { auto highlight = createAppHighlightWithHTML(@"Test", @"document.execCommand('SelectAll')", @"Test"); auto webViewRestore = createWebViewForAppHighlightsWithHTML(@"Test"); NSMutableData *modifiedHighlightData = [NSMutableData dataWithBytes:[highlight highlight].bytes length:[highlight highlight].length / 2]; [webViewRestore synchronouslyLoadHTMLString:@"Test"]; [webViewRestore _restoreAppHighlights:@[ modifiedHighlightData ]]; TestWebKitAPI::Util::waitForConditionWithLogging([&] () -> bool { return ![webViewRestore stringByEvaluatingJavaScript:@"internals.numberOfAppHighlights()"].intValue; }, 2, @"Expected Highlights not to be populated."); } // Ensure that the original data format will always be able to be decoded in the future. TEST(AppHighlights, AppHighlightRestoreFromStorageV0) { WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES]; const unsigned char bytes[] = {0x04, 0x00, 0x00, 0x00, 0x01, 0x54, 0x65, 0x73, 0x74, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x01, 0x23, 0x74, 0x65, 0x78, 0x74, 0x04, 0x00, 0x00, 0x00, 0x01, 0x54, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x01, 0x23, 0x74, 0x65, 0x78, 0x74, 0x04, 0x00, 0x00, 0x00, 0x01, 0x54, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00}; NSData *storedData = [NSData dataWithBytes:&bytes length:87]; auto webViewRestore = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500) configuration:configuration]); [webViewRestore synchronouslyLoadHTMLString:@"Test"]; [webViewRestore _restoreAppHighlights:@[ storedData ]]; TestWebKitAPI::Util::waitForConditionWithLogging([&] () -> bool { return [webViewRestore stringByEvaluatingJavaScript:@"internals.numberOfAppHighlights()"].intValue == 1; }, 2, @"Expected Highlights to be populated."); } // Ensure that the V1 data format will always be able to be decoded in the future. TEST(AppHighlights, AppHighlightRestoreFromStorageV1) { WKWebViewConfiguration *configuration = [WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"WebProcessPlugInWithInternals" configureJSCForTesting:YES]; const unsigned char bytes[] = {0x31, 0x32, 0x30, 0x32, 0x48, 0x50, 0x41, 0x41, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x01, 0x31, 0x36, 0x30, 0x61, 0x36, 0x30, 0x31, 0x62, 0x2d, 0x31, 0x62, 0x37, 0x32, 0x2d, 0x34, 0x31, 0x31, 0x38, 0x2d, 0x62, 0x36, 0x64, 0x31, 0x2d, 0x39, 0x30, 0x32, 0x62, 0x37, 0x34, 0x66, 0x65, 0x61, 0x36, 0x36, 0x31, 0x04, 0x00, 0x00, 0x00, 0x01, 0x54, 0x65, 0x73, 0x74, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x01, 0x23, 0x74, 0x65, 0x78, 0x74, 0x04, 0x00, 0x00, 0x00, 0x01, 0x54, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x05, 0x00, 0x00, 0x00, 0x01, 0x23, 0x74, 0x65, 0x78, 0x74, 0x04, 0x00, 0x00, 0x00, 0x01, 0x54, 0x65, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00}; NSData *storedData = [NSData dataWithBytes:&bytes length:144]; auto webViewRestore = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500) configuration:configuration]); [webViewRestore synchronouslyLoadHTMLString:@"Test"]; [webViewRestore _restoreAppHighlights:@[ storedData ]]; TestWebKitAPI::Util::waitForConditionWithLogging([&] () -> bool { return [webViewRestore stringByEvaluatingJavaScript:@"internals.numberOfAppHighlights()"].intValue == 1; }, 2, @"Expected Highlights to be populated."); } #if PLATFORM(IOS_FAMILY) TEST(AppHighlights, AvoidForcingCalloutBarInitialization) { auto defaultConfiguration = adoptNS([[WKWebViewConfiguration alloc] init]); auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500) configuration:defaultConfiguration.get() addToWindow:NO]); [webView synchronouslyLoadTestPageNamed:@"simple"]; [webView stringByEvaluatingJavaScript:@"getSelection().setPosition(document.body, 1)"]; [webView waitForNextPresentationUpdate]; EXPECT_NULL(UICalloutBar.activeCalloutBar); } #endif // PLATFORM(IOS_FAMILY) } // namespace TestWebKitAPI #endif // ENABLE(APP_HIGHLIGHTS)