/* * Copyright (C) 2017 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_FAMILY) #import "ClassMethodSwizzler.h" #import "InstanceMethodSwizzler.h" #import "PlatformUtilities.h" #import "TestWKWebView.h" #import "UIKitSPI.h" #import #import #import #import #import typedef void (^DataLoadCompletionBlock)(NSData *, NSError *); #if PLATFORM(IOS) static void checkJSONWithLogging(NSString *jsonString, NSDictionary *expected) { BOOL success = TestWebKitAPI::Util::jsonMatchesExpectedValues(jsonString, expected); EXPECT_TRUE(success); if (!success) NSLog(@"Expected JSON: %@ to match values: %@", jsonString, expected); } #if HAVE(PASTEBOARD_DATA_OWNER) static _UIDataOwner gLastKnownDataOwner = _UIDataOwnerUndefined; @interface TestUIPasteboard : NSObject + (void)_performAsDataOwner:(_UIDataOwner)owner block:(dispatch_block_t)block; @end @implementation TestUIPasteboard + (void)_performAsDataOwner:(_UIDataOwner)owner block:(dispatch_block_t)block { block(); gLastKnownDataOwner = owner; } @end @interface TestMCProfileConnection : NSObject @end @implementation TestMCProfileConnection - (BOOL)isURLManaged:(NSURL *)url { return [url.lastPathComponent isEqualToString:@"simple.html"]; } @end #endif // HAVE(PASTEBOARD_DATA_OWNER) #endif // PLATFORM(IOS) namespace TestWebKitAPI { NSData *dataForPasteboardType(CFStringRef type) { return [[UIPasteboard generalPasteboard] dataForPasteboardType:(NSString *)type inItemSet:[NSIndexSet indexSetWithIndex:0]].firstObject; } RetainPtr setUpWebViewForPasteboardTests(NSString *pageName) { // UIPasteboard's type coercion codepaths only take effect when the UIApplication has been initialized. UIApplicationInitialize(); [UIPasteboard generalPasteboard].items = @[]; EXPECT_TRUE(!dataForPasteboardType(kUTTypeUTF8PlainText).length); EXPECT_TRUE(!dataForPasteboardType(kUTTypeUTF16PlainText).length); auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500)]); WKPreferences *preferences = [webView configuration].preferences; preferences._javaScriptCanAccessClipboard = YES; preferences._domPasteAllowed = YES; [webView synchronouslyLoadTestPageNamed:pageName]; return webView; } TEST(UIPasteboardTests, CopyPlainTextWritesConcreteTypes) { auto webView = setUpWebViewForPasteboardTests(@"rich-and-plain-text"); [webView stringByEvaluatingJavaScript:@"selectPlainText()"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('copy')"]; auto utf8Result = adoptNS([[NSString alloc] initWithData:dataForPasteboardType(kUTTypeUTF8PlainText) encoding:NSUTF8StringEncoding]); EXPECT_WK_STREQ("Hello world", [utf8Result UTF8String]); } TEST(UIPasteboardTests, CopyRichTextWritesConcreteTypes) { auto webView = setUpWebViewForPasteboardTests(@"rich-and-plain-text"); [webView stringByEvaluatingJavaScript:@"selectRichText()"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('copy')"]; auto utf8Result = adoptNS([[NSString alloc] initWithData:dataForPasteboardType(kUTTypeUTF8PlainText) encoding:NSUTF8StringEncoding]); EXPECT_WK_STREQ("Hello world", [utf8Result UTF8String]); } TEST(UIPasteboardTests, DoNotPastePlainTextAsURL) { auto webView = setUpWebViewForPasteboardTests(@"rich-and-plain-text"); NSString *testString = @"[helloworld]"; [UIPasteboard generalPasteboard].string = testString; [webView stringByEvaluatingJavaScript:@"selectPlainText()"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"]; EXPECT_WK_STREQ(testString, [webView stringByEvaluatingJavaScript:@"plain.value"]); [webView stringByEvaluatingJavaScript:@"selectRichText()"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"]; EXPECT_WK_STREQ(testString, [webView stringByEvaluatingJavaScript:@"rich.textContent"]); EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"!!rich.querySelector('a')"].boolValue); } TEST(UIPasteboardTests, PastePlainTextAsURL) { auto webView = setUpWebViewForPasteboardTests(@"rich-and-plain-text"); NSString *testString = @"https://www.apple.com/iphone"; [UIPasteboard generalPasteboard].string = testString; [webView stringByEvaluatingJavaScript:@"selectPlainText()"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"]; EXPECT_WK_STREQ(testString, [webView stringByEvaluatingJavaScript:@"plain.value"]); [webView stringByEvaluatingJavaScript:@"selectRichText()"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"]; EXPECT_WK_STREQ(testString, [webView stringByEvaluatingJavaScript:@"rich.textContent"]); EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"!!rich.querySelector('a')"].boolValue); } TEST(UIPasteboardTests, PasteURLWithPlainTextAsURL) { auto webView = setUpWebViewForPasteboardTests(@"rich-and-plain-text"); NSString *testString = @"thisisdefinitelyaurl"; [UIPasteboard generalPasteboard].URL = [NSURL URLWithString:testString]; [webView stringByEvaluatingJavaScript:@"selectPlainText()"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"]; EXPECT_WK_STREQ(testString, [webView stringByEvaluatingJavaScript:@"plain.value"]); [webView stringByEvaluatingJavaScript:@"selectRichText()"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"]; EXPECT_WK_STREQ(testString, [webView stringByEvaluatingJavaScript:@"rich.textContent"]); EXPECT_TRUE([webView stringByEvaluatingJavaScript:@"!!rich.querySelector('a')"].boolValue); } TEST(UIPasteboardTests, PasteWithCompletionHandler) { auto webView = setUpWebViewForPasteboardTests(@"DataTransfer"); [UIPasteboard generalPasteboard].URL = [NSURL URLWithString:@"https://www.apple.com/"]; bool done = false; [(id )[webView textInputContentView] pasteWithCompletionHandler:[webView, &done] { [UIPasteboard generalPasteboard].items = @[ ]; done = true; }]; Util::run(&done); EXPECT_WK_STREQ("text/uri-list, text/plain", [webView stringByEvaluatingJavaScript:@"types.textContent"]); EXPECT_WK_STREQ("(STRING, text/uri-list), (STRING, text/plain)", [webView stringByEvaluatingJavaScript:@"items.textContent"]); EXPECT_WK_STREQ("https://www.apple.com/", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]); EXPECT_WK_STREQ("https://www.apple.com/", [webView stringByEvaluatingJavaScript:@"textData.textContent"]); } #if PLATFORM(IOS) TEST(UIPasteboardTests, DataTransferGetDataWhenPastingURL) { auto webView = setUpWebViewForPasteboardTests(@"dump-datatransfer-types"); NSString *testURLString = @"https://www.apple.com/"; [UIPasteboard generalPasteboard].URL = [NSURL URLWithString:testURLString]; [webView stringByEvaluatingJavaScript:@"destination.focus()"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"]; checkJSONWithLogging([webView stringByEvaluatingJavaScript:@"output.value"], @{ @"paste" : @{ @"text/plain" : testURLString, @"text/uri-list" : testURLString } }); } TEST(UIPasteboardTests, DataTransferGetDataWhenPastingPlatformRepresentations) { auto webView = setUpWebViewForPasteboardTests(@"dump-datatransfer-types"); [webView stringByEvaluatingJavaScript:@"ignoreInlineStyles = true"]; // This simulates how a native app on iOS might write to the pasteboard when copying. RetainPtr testURL = [NSURL URLWithString:@"https://www.apple.com/"]; RetainPtr testPlainTextString = @"WebKit"; RetainPtr testMarkupString = @"The WebKit Project"; auto itemProvider = adoptNS([[NSItemProvider alloc] init]); [itemProvider registerDataRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypeHTML visibility:NSItemProviderRepresentationVisibilityAll loadHandler:^NSProgress *(DataLoadCompletionBlock completionHandler) { completionHandler([testMarkupString dataUsingEncoding:NSUTF8StringEncoding], nil); return nil; }]; [itemProvider registerObject:testURL.get() visibility:NSItemProviderRepresentationVisibilityAll]; [itemProvider registerObject:testPlainTextString.get() visibility:NSItemProviderRepresentationVisibilityAll]; [UIPasteboard generalPasteboard].itemProviders = @[ itemProvider.get() ]; [webView stringByEvaluatingJavaScript:@"destination.focus()"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"]; checkJSONWithLogging([webView stringByEvaluatingJavaScript:@"output.value"], @{ @"paste" : @{ @"text/plain" : testPlainTextString.get(), @"text/uri-list" : [testURL absoluteString], @"text/html" : testMarkupString.get() } }); } TEST(UIPasteboardTests, DataTransferGetDataWhenPastingImageAndText) { auto webView = setUpWebViewForPasteboardTests(@"DataTransfer"); auto copiedText = retainPtr(@"Apple Inc."); auto itemProvider = adoptNS([[NSItemProvider alloc] init]); [itemProvider registerDataRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypePNG visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[] (DataLoadCompletionBlock completionHandler) -> NSProgress * { completionHandler([NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"icon" withExtension:@"png" subdirectory:@"TestWebKitAPI.resources"]], nil); return nil; }]; [itemProvider registerDataRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypeUTF8PlainText visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[copiedText] (DataLoadCompletionBlock completionHandler) -> NSProgress * { completionHandler([copiedText dataUsingEncoding:NSUTF8StringEncoding], nil); return nil; }]; [UIPasteboard generalPasteboard].itemProviders = @[ itemProvider.get() ]; [webView paste:nil]; EXPECT_WK_STREQ("Files, text/plain", [webView stringByEvaluatingJavaScript:@"types.textContent"]); EXPECT_WK_STREQ("(STRING, text/plain), (FILE, image/png)", [webView stringByEvaluatingJavaScript:@"items.textContent"]); EXPECT_WK_STREQ("('image.png', image/png)", [webView stringByEvaluatingJavaScript:@"files.textContent"]); EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]); EXPECT_WK_STREQ("Apple Inc.", [webView stringByEvaluatingJavaScript:@"textData.textContent"]); EXPECT_WK_STREQ("", [webView stringByEvaluatingJavaScript:@"htmlData.textContent"]); } TEST(UIPasteboardTests, DataTransferSetDataCannotWritePlatformTypes) { auto webView = setUpWebViewForPasteboardTests(@"dump-datatransfer-types"); [webView stringByEvaluatingJavaScript:@"customData = { 'text/plain' : '年年年', '年年年年年年' : 'test', 'com.adobe.pdf' : '🔥🔥🔥', 'text/rtf' : 'not actually rich text!' }"]; [webView stringByEvaluatingJavaScript:@"writeCustomData = true"]; [webView stringByEvaluatingJavaScript:@"select(rich)"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('copy')"]; [webView stringByEvaluatingJavaScript:@"destination.focus()"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"]; checkJSONWithLogging([webView stringByEvaluatingJavaScript:@"output.value"], @{ @"paste": @{ @"text/plain": @"年年年", @"年年年年年年": @"test", @"com.adobe.pdf": @"🔥🔥🔥", @"text/rtf": @"not actually rich text!" } }); // Most importantly, the system pasteboard should not contain the PDF UTI. NSData *pdfData = [[UIPasteboard generalPasteboard] dataForPasteboardType:(__bridge NSString *)kUTTypePDF]; EXPECT_EQ(0UL, pdfData.length); // However, the system pasteboard should contain a plain text string. EXPECT_WK_STREQ("年年年", [UIPasteboard generalPasteboard].string); } TEST(UIPasteboardTests, DataTransferGetDataCannotReadArbitraryPlatformTypes) { auto webView = setUpWebViewForPasteboardTests(@"dump-datatransfer-types"); auto itemProvider = adoptNS([[NSItemProvider alloc] init]); [itemProvider registerDataRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypeMP3 visibility:NSItemProviderRepresentationVisibilityAll loadHandler:^NSProgress *(DataLoadCompletionBlock completionHandler) { completionHandler([@"this is a test" dataUsingEncoding:NSUTF8StringEncoding], nil); return nil; }]; [itemProvider registerDataRepresentationForTypeIdentifier:@"org.WebKit.TestWebKitAPI.custom-pasteboard-type" visibility:NSItemProviderRepresentationVisibilityAll loadHandler:^NSProgress *(DataLoadCompletionBlock completionHandler) { completionHandler([@"this is another test" dataUsingEncoding:NSUTF8StringEncoding], nil); return nil; }]; [webView stringByEvaluatingJavaScript:@"destination.focus()"]; [webView stringByEvaluatingJavaScript:@"document.execCommand('paste')"]; checkJSONWithLogging([webView stringByEvaluatingJavaScript:@"output.value"], @{ @"paste": @{ } }); } TEST(UIPasteboardTests, DataTransferURIListContainsMultipleURLs) { auto webView = setUpWebViewForPasteboardTests(@"DataTransfer"); NSURL *firstURL = [NSURL URLWithString:@"https://www.apple.com/"]; NSURL *secondURL = [NSURL URLWithString:@"https://webkit.org/"]; [UIPasteboard generalPasteboard].URLs = @[ firstURL, secondURL ]; [webView paste:nil]; EXPECT_WK_STREQ("text/uri-list, text/plain", [webView stringByEvaluatingJavaScript:@"types.textContent"]); EXPECT_WK_STREQ("(STRING, text/uri-list), (STRING, text/plain)", [webView stringByEvaluatingJavaScript:@"items.textContent"]); EXPECT_WK_STREQ("https://www.apple.com/\nhttps://webkit.org/", [webView stringByEvaluatingJavaScript:@"urlData.textContent"]); EXPECT_WK_STREQ("https://www.apple.com/", [webView stringByEvaluatingJavaScript:@"textData.textContent"]); } TEST(UIPasteboardTests, ValidPreferredPresentationSizeForImage) { auto webView = setUpWebViewForPasteboardTests(@"autofocus-contenteditable"); auto itemProvider = adoptNS([[NSItemProvider alloc] init]); [itemProvider setPreferredPresentationSize:CGSizeMake(10, 20)]; [itemProvider registerDataRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypePNG visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[] (DataLoadCompletionBlock completionHandler) -> NSProgress * { completionHandler([NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"icon" withExtension:@"png" subdirectory:@"TestWebKitAPI.resources"]], nil); return nil; }]; [UIPasteboard generalPasteboard].itemProviders = @[ itemProvider.get() ]; [webView paste:nil]; EXPECT_WK_STREQ("10", [webView stringByEvaluatingJavaScript:@"document.querySelector('img').width"]); EXPECT_WK_STREQ("20", [webView stringByEvaluatingJavaScript:@"document.querySelector('img').height"]); } TEST(UIPasteboardTests, InvalidPreferredPresentationSizeForImage) { auto webView = setUpWebViewForPasteboardTests(@"autofocus-contenteditable"); auto itemProvider = adoptNS([[NSItemProvider alloc] init]); [itemProvider setPreferredPresentationSize:CGSizeMake(-10, -20)]; [itemProvider registerDataRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypePNG visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[] (DataLoadCompletionBlock completionHandler) -> NSProgress * { completionHandler([NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"icon" withExtension:@"png" subdirectory:@"TestWebKitAPI.resources"]], nil); return nil; }]; [UIPasteboard generalPasteboard].itemProviders = @[ itemProvider.get() ]; [webView paste:nil]; EXPECT_WK_STREQ("0", [webView stringByEvaluatingJavaScript:@"document.querySelector('img').width"]); EXPECT_WK_STREQ("174", [webView stringByEvaluatingJavaScript:@"document.querySelector('img').height"]); } TEST(UIPasteboardTests, MissingPreferredPresentationSizeForImage) { auto webView = setUpWebViewForPasteboardTests(@"autofocus-contenteditable"); auto itemProvider = adoptNS([[NSItemProvider alloc] init]); [itemProvider registerDataRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypePNG visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[] (DataLoadCompletionBlock completionHandler) -> NSProgress * { completionHandler([NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"icon" withExtension:@"png" subdirectory:@"TestWebKitAPI.resources"]], nil); return nil; }]; [UIPasteboard generalPasteboard].itemProviders = @[ itemProvider.get() ]; [webView paste:nil]; EXPECT_WK_STREQ("0", [webView stringByEvaluatingJavaScript:@"document.querySelector('img').width"]); EXPECT_WK_STREQ("174", [webView stringByEvaluatingJavaScript:@"document.querySelector('img').height"]); } #if HAVE(PASTEBOARD_DATA_OWNER) TEST(UIPasteboardTests, PerformAsDataOwnerWhenCopying) { auto swizzler = ClassMethodSwizzler { UIPasteboard.class, @selector(_performAsDataOwner:block:), [TestUIPasteboard methodForSelector:@selector(_performAsDataOwner:block:)] }; auto webView = setUpWebViewForPasteboardTests(@"simple"); [webView _setDataOwnerForCopy:_UIDataOwnerShared]; [webView _setDataOwnerForPaste:_UIDataOwnerEnterprise]; [webView selectAll:nil]; [webView copy:nil]; [webView waitForNextPresentationUpdate]; EXPECT_EQ(gLastKnownDataOwner, _UIDataOwnerShared); EXPECT_WK_STREQ(UIPasteboard.generalPasteboard.string, "Simple HTML file."); } TEST(UIPasteboardTests, PerformAsDataOwnerWhenPasting) { auto swizzler = ClassMethodSwizzler { UIPasteboard.class, @selector(_performAsDataOwner:block:), [TestUIPasteboard methodForSelector:@selector(_performAsDataOwner:block:)] }; auto webView = setUpWebViewForPasteboardTests(@"autofocus-contenteditable"); [webView _setDataOwnerForCopy:_UIDataOwnerShared]; [webView _setDataOwnerForPaste:_UIDataOwnerEnterprise]; UIPasteboard.generalPasteboard.string = @"Foo bar"; [webView paste:nil]; [webView waitForNextPresentationUpdate]; EXPECT_EQ(gLastKnownDataOwner, _UIDataOwnerEnterprise); EXPECT_WK_STREQ([webView contentsAsString], "Foo bar\n"); } TEST(UIPasteboardTests, PerformAsDataOwnerWithManagedURL) { auto pasteboardSwizzler = ClassMethodSwizzler { UIPasteboard.class, @selector(_performAsDataOwner:block:), [TestUIPasteboard methodForSelector:@selector(_performAsDataOwner:block:)] }; auto managedConfigurationSwizzler = InstanceMethodSwizzler { PAL::getMCProfileConnectionClass(), @selector(isURLManaged:), [TestMCProfileConnection instanceMethodForSelector:@selector(isURLManaged:)] }; { auto source = setUpWebViewForPasteboardTests(@"simple"); [source selectAll:nil]; [source copy:nil]; [source waitForNextPresentationUpdate]; EXPECT_EQ(gLastKnownDataOwner, _UIDataOwnerEnterprise); } { auto destination = setUpWebViewForPasteboardTests(@"autofocus-contenteditable"); [destination _setDataOwnerForPaste:_UIDataOwnerUser]; [destination paste:nil]; [destination waitForNextPresentationUpdate]; EXPECT_EQ(gLastKnownDataOwner, _UIDataOwnerUser); } } #endif // HAVE(PASTEBOARD_DATA_OWNER) #endif // PLATFORM(IOS) } // namespace TestWebKitAPI #endif