blob: b6fcd935850ebd67b2b18610e48890665e3192ab [file] [log] [blame]
/*
* 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(MAC) || PLATFORM(IOS)
#import "DragAndDropSimulator.h"
#import "NSItemProviderAdditions.h"
#import "PencilKitTestSPI.h"
#import "PlatformUtilities.h"
#import "TestNavigationDelegate.h"
#import "TestWKWebView.h"
#import "WKWebViewConfigurationExtras.h"
#import <Contacts/Contacts.h>
#import <MapKit/MapKit.h>
#import <WebKit/WKPreferencesRefPrivate.h>
#import <WebKit/WKWebViewPrivate.h>
#import <WebKit/WebArchive.h>
#import <WebKit/WebKitPrivate.h>
#import <wtf/RetainPtr.h>
#import <wtf/SoftLinking.h>
#if PLATFORM(IOS_FAMILY)
#import <MobileCoreServices/MobileCoreServices.h>
#endif
SOFT_LINK_FRAMEWORK(Contacts)
SOFT_LINK_CLASS(Contacts, CNMutableContact)
SOFT_LINK_FRAMEWORK(MapKit)
SOFT_LINK_CLASS(MapKit, MKMapItem)
SOFT_LINK_CLASS(MapKit, MKPlacemark)
@interface NSArray (AttachmentTestingHelpers)
- (_WKAttachment *)_attachmentWithName:(NSString *)name;
@end
@implementation NSArray (AttachmentTestingHelpers)
- (_WKAttachment *)_attachmentWithName:(NSString *)name
{
for (_WKAttachment *attachment in self) {
if ([attachment.info.name isEqualToString:name])
return attachment;
}
return nil;
}
@end
#define USES_MODERN_ATTRIBUTED_STRING_CONVERSION ((PLATFORM(IOS_FAMILY) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000) || PLATFORM(MAC))
@interface AttachmentUpdateObserver : NSObject <WKUIDelegatePrivate>
@property (nonatomic, readonly) NSArray *inserted;
@property (nonatomic, readonly) NSArray *removed;
@property (nonatomic, readonly) NSArray *dataInvalidated;
@end
@implementation AttachmentUpdateObserver {
RetainPtr<NSMutableArray<_WKAttachment *>> _inserted;
RetainPtr<NSMutableArray<_WKAttachment *>> _removed;
RetainPtr<NSMutableArray<_WKAttachment *>> _dataInvalidated;
RetainPtr<NSMutableDictionary<NSString *, NSString *>> _identifierToSourceMap;
}
- (instancetype)init
{
if (self = [super init]) {
_inserted = adoptNS([[NSMutableArray alloc] init]);
_removed = adoptNS([[NSMutableArray alloc] init]);
_dataInvalidated = adoptNS([[NSMutableArray alloc] init]);
_identifierToSourceMap = adoptNS([[NSMutableDictionary alloc] init]);
}
return self;
}
- (NSArray<_WKAttachment *> *)inserted
{
return _inserted.get();
}
- (NSArray<_WKAttachment *> *)removed
{
return _removed.get();
}
- (NSArray<_WKAttachment *> *)dataInvalidated
{
return _dataInvalidated.get();
}
- (NSString *)sourceForIdentifier:(NSString *)identifier
{
return [_identifierToSourceMap objectForKey:identifier];
}
- (void)_webView:(WKWebView *)webView didInsertAttachment:(_WKAttachment *)attachment withSource:(NSString *)source
{
[_inserted addObject:attachment];
if (source)
[_identifierToSourceMap setObject:source forKey:attachment.uniqueIdentifier];
}
- (void)_webView:(WKWebView *)webView didRemoveAttachment:(_WKAttachment *)attachment
{
[_removed addObject:attachment];
}
- (void)_webView:(WKWebView *)webView didInvalidateDataForAttachment:(_WKAttachment *)attachment
{
[_dataInvalidated addObject:attachment];
}
@end
namespace TestWebKitAPI {
class ObserveAttachmentUpdatesForScope {
public:
ObserveAttachmentUpdatesForScope(TestWKWebView *webView)
: m_webView(webView)
{
m_previousDelegate = webView.UIDelegate;
m_observer = adoptNS([[AttachmentUpdateObserver alloc] init]);
webView.UIDelegate = m_observer.get();
}
~ObserveAttachmentUpdatesForScope()
{
[m_webView setUIDelegate:m_previousDelegate.get()];
}
AttachmentUpdateObserver *observer() const { return m_observer.get(); }
void expectAttachmentUpdates(NSArray<_WKAttachment *> *removed, NSArray<_WKAttachment *> *inserted)
{
BOOL removedAttachmentsMatch = [[NSSet setWithArray:observer().removed] isEqual:[NSSet setWithArray:removed]];
if (!removedAttachmentsMatch)
NSLog(@"Expected removed attachments: %@ to match %@.", observer().removed, removed);
EXPECT_TRUE(removedAttachmentsMatch);
BOOL insertedAttachmentsMatch = [[NSSet setWithArray:observer().inserted] isEqual:[NSSet setWithArray:inserted]];
if (!insertedAttachmentsMatch)
NSLog(@"Expected inserted attachments: %@ to match %@.", observer().inserted, inserted);
EXPECT_TRUE(insertedAttachmentsMatch);
}
void expectAttachmentInvalidation(NSArray<_WKAttachment *> *dataInvalidated)
{
BOOL invalidatedAttachmentsMatch = [[NSSet setWithArray:observer().dataInvalidated] isEqual:[NSSet setWithArray:dataInvalidated]];
if (!invalidatedAttachmentsMatch)
NSLog(@"Expected attachments with invalidated data: %@ to match %@.", observer().dataInvalidated, dataInvalidated);
EXPECT_TRUE(invalidatedAttachmentsMatch);
}
void expectSourceForIdentifier(NSString *expectedSource, NSString *identifier)
{
NSString *observedSource = [observer() sourceForIdentifier:identifier];
BOOL success = observedSource == expectedSource || [observedSource isEqualToString:expectedSource];
EXPECT_TRUE(success);
if (!success)
NSLog(@"Expected source: %@ but observed: %@", expectedSource, observedSource);
}
private:
RetainPtr<AttachmentUpdateObserver> m_observer;
RetainPtr<TestWKWebView> m_webView;
RetainPtr<id> m_previousDelegate;
};
}
@interface TestWKWebView (AttachmentTesting)
@end
static NSString *attachmentEditingTestMarkup = @"<meta name='viewport' content='width=device-width, initial-scale=1'>"
"<script>focus = () => document.body.focus()</script>"
"<body onload=focus() contenteditable></body>";
static RetainPtr<TestWKWebView> webViewForTestingAttachments(CGSize webViewSize, WKWebViewConfiguration *configuration)
{
configuration._attachmentElementEnabled = YES;
configuration._editableImagesEnabled = YES;
WKPreferencesSetCustomPasteboardDataEnabled((__bridge WKPreferencesRef)[configuration preferences], YES);
auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, webViewSize.width, webViewSize.height) configuration:configuration]);
[webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
return webView;
}
static RetainPtr<TestWKWebView> webViewForTestingAttachments(CGSize webViewSize)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
return webViewForTestingAttachments(webViewSize, configuration.get());
}
static RetainPtr<TestWKWebView> webViewForTestingAttachments()
{
return webViewForTestingAttachments(CGSizeMake(500, 500));
}
static NSData *testZIPData()
{
NSURL *zipFileURL = [[NSBundle mainBundle] URLForResource:@"compressed-files" withExtension:@"zip" subdirectory:@"TestWebKitAPI.resources"];
return [NSData dataWithContentsOfURL:zipFileURL];
}
static NSData *testHTMLData()
{
return [@"<a href='#'>This is some HTML data</a>" dataUsingEncoding:NSUTF8StringEncoding];
}
static NSURL *testImageFileURL()
{
return [[NSBundle mainBundle] URLForResource:@"icon" withExtension:@"png" subdirectory:@"TestWebKitAPI.resources"];
}
static NSData *testImageData()
{
return [NSData dataWithContentsOfURL:testImageFileURL()];
}
static NSURL *testGIFFileURL()
{
return [[NSBundle mainBundle] URLForResource:@"apple" withExtension:@"gif" subdirectory:@"TestWebKitAPI.resources"];
}
static NSData *testGIFData()
{
return [NSData dataWithContentsOfURL:testGIFFileURL()];
}
static NSURL *testPDFFileURL()
{
return [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"pdf" subdirectory:@"TestWebKitAPI.resources"];
}
static NSData *testPDFData()
{
return [NSData dataWithContentsOfURL:testPDFFileURL()];
}
@implementation TestWKWebView (AttachmentTesting)
- (_WKAttachment *)synchronouslyInsertAttachmentWithFileWrapper:(NSFileWrapper *)fileWrapper contentType:(NSString *)contentType
{
__block bool done = false;
RetainPtr<_WKAttachment> attachment = [self _insertAttachmentWithFileWrapper:fileWrapper contentType:contentType completion:^(BOOL) {
done = true;
}];
TestWebKitAPI::Util::run(&done);
return attachment.autorelease();
}
- (_WKAttachment *)synchronouslyInsertAttachmentWithFilename:(NSString *)filename contentType:(NSString *)contentType data:(NSData *)data
{
__block bool done = false;
auto fileWrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:data]);
if (filename)
[fileWrapper setPreferredFilename:filename];
RetainPtr<_WKAttachment> attachment = [self _insertAttachmentWithFileWrapper:fileWrapper.get() contentType:contentType completion:^(BOOL) {
done = true;
}];
TestWebKitAPI::Util::run(&done);
return attachment.autorelease();
}
- (NSArray<NSValue *> *)allBoundingClientRects:(NSString *)querySelector
{
auto rects = adoptNS([[NSMutableArray alloc] init]);
bool doneEvaluatingScript = false;
NSString *script = [NSString stringWithFormat:@"Array.from(document.querySelectorAll('%@')).map(e => { const r = e.getBoundingClientRect(); return [r.left, r.top, r.width, r.height]; })", querySelector];
[self evaluateJavaScript:script completionHandler:[rects, &doneEvaluatingScript] (NSArray<NSArray<NSNumber *> *> *result, NSError *) {
for (NSArray<NSNumber *> *rectInfo in result) {
#if PLATFORM(IOS_FAMILY)
[rects addObject:[NSValue valueWithCGRect:CGRectMake(rectInfo[0].floatValue, rectInfo[1].floatValue, rectInfo[2].floatValue, rectInfo[3].floatValue)]];
#else
[rects addObject:[NSValue valueWithRect:NSMakeRect(rectInfo[0].floatValue, rectInfo[1].floatValue, rectInfo[2].floatValue, rectInfo[3].floatValue)]];
#endif
}
doneEvaluatingScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingScript);
return rects.autorelease();
}
- (CGPoint)attachmentElementMidPoint
{
__block CGPoint midPoint;
__block bool doneEvaluatingScript = false;
[self evaluateJavaScript:@"r = document.querySelector('attachment').getBoundingClientRect(); [r.left + r.width / 2, r.top + r.height / 2]" completionHandler:^(NSArray<NSNumber *> *result, NSError *) {
midPoint = CGPointMake(result.firstObject.floatValue, result.lastObject.floatValue);
doneEvaluatingScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingScript);
return midPoint;
}
- (CGSize)attachmentElementSize
{
__block CGSize size;
__block bool doneEvaluatingScript = false;
[self evaluateJavaScript:@"r = document.querySelector('attachment').getBoundingClientRect(); [r.width, r.height]" completionHandler:^(NSArray<NSNumber *> *sizeResult, NSError *) {
size = CGSizeMake(sizeResult.firstObject.floatValue, sizeResult.lastObject.floatValue);
doneEvaluatingScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingScript);
return size;
}
- (CGSize)imageElementSize
{
__block CGSize size;
__block bool doneEvaluatingScript = false;
[self evaluateJavaScript:@"r = document.querySelector('img').getBoundingClientRect(); [r.width, r.height]" completionHandler:^(NSArray<NSNumber *> *sizeResult, NSError *) {
size = CGSizeMake(sizeResult.firstObject.floatValue, sizeResult.lastObject.floatValue);
doneEvaluatingScript = true;
}];
TestWebKitAPI::Util::run(&doneEvaluatingScript);
return size;
}
- (void)waitForImageElementSizeToBecome:(CGSize)expectedSize
{
while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) {
if (CGSizeEqualToSize(self.imageElementSize, expectedSize))
break;
}
}
- (BOOL)hasAttribute:(NSString *)attributeName forQuerySelector:(NSString *)querySelector
{
return [self stringByEvaluatingJavaScript:[NSString stringWithFormat:@"document.querySelector('%@').hasAttribute('%@')", querySelector, attributeName]].boolValue;
}
- (NSString *)valueOfAttribute:(NSString *)attributeName forQuerySelector:(NSString *)querySelector
{
return [self stringByEvaluatingJavaScript:[NSString stringWithFormat:@"document.querySelector('%@').getAttribute('%@')", querySelector, attributeName]];
}
- (void)expectUpdatesAfterCommand:(NSString *)command withArgument:(NSString *)argument expectedRemovals:(NSArray<_WKAttachment *> *)removed expectedInsertions:(NSArray<_WKAttachment *> *)inserted
{
TestWebKitAPI::ObserveAttachmentUpdatesForScope observer(self);
EXPECT_TRUE([self _synchronouslyExecuteEditCommand:command argument:argument]);
observer.expectAttachmentUpdates(removed, inserted);
}
@end
@implementation NSData (AttachmentTesting)
- (NSString *)shortDescription
{
return [NSString stringWithFormat:@"<%tu bytes>", self.length];
}
@end
@implementation _WKAttachment (AttachmentTesting)
- (void)synchronouslySetFileWrapper:(NSFileWrapper *)fileWrapper newContentType:(NSString *)newContentType error:(NSError **)error
{
__block RetainPtr<NSError> resultError;
__block bool done = false;
[self setFileWrapper:fileWrapper contentType:newContentType completion:^(NSError *error) {
resultError = error;
done = true;
}];
TestWebKitAPI::Util::run(&done);
if (error)
*error = resultError.autorelease();
}
- (void)synchronouslySetData:(NSData *)data newContentType:(NSString *)newContentType newFilename:(NSString *)newFilename error:(NSError **)error
{
__block RetainPtr<NSError> resultError;
__block bool done = false;
auto fileWrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:data]);
if (newFilename)
[fileWrapper setPreferredFilename:newFilename];
[self setFileWrapper:fileWrapper.get() contentType:newContentType completion:^(NSError *error) {
resultError = error;
done = true;
}];
TestWebKitAPI::Util::run(&done);
if (error)
*error = resultError.autorelease();
}
- (void)expectRequestedDataToBe:(NSData *)expectedData
{
NSError *requestError = nil;
_WKAttachmentInfo *info = self.info;
BOOL observedDataIsEqualToExpectedData = info.data == expectedData || [info.data isEqualToData:expectedData];
EXPECT_TRUE(observedDataIsEqualToExpectedData);
if (!observedDataIsEqualToExpectedData) {
NSLog(@"Expected data: %@ but observed: %@ for %@", [expectedData shortDescription], [info.data shortDescription], self);
NSLog(@"Observed error: %@ while reading data for %@", requestError, self);
}
}
@end
static void runTestWithTemporaryFolder(void(^runTest)(NSURL *folderURL))
{
NSFileManager *defaultManager = [NSFileManager defaultManager];
auto temporaryFolder = retainPtr([NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"folder-%@", NSUUID.UUID]] isDirectory:YES]);
[defaultManager removeItemAtURL:temporaryFolder.get() error:nil];
[defaultManager createDirectoryAtURL:temporaryFolder.get() withIntermediateDirectories:NO attributes:nil error:nil];
[testImageData() writeToURL:[temporaryFolder.get() URLByAppendingPathComponent:@"image.png" isDirectory:NO] atomically:YES];
[testZIPData() writeToURL:[temporaryFolder.get() URLByAppendingPathComponent:@"archive.zip" isDirectory:NO] atomically:YES];
@try {
runTest(temporaryFolder.get());
} @finally {
[[NSFileManager defaultManager] removeItemAtURL:temporaryFolder.get() error:nil];
}
}
#if PLATFORM(IOS_FAMILY)
static void runTestWithTemporaryImageFile(NSString *fileName, void(^runTest)(NSURL *fileURL))
{
NSFileManager *defaultManager = [NSFileManager defaultManager];
auto temporaryFilePath = retainPtr([NSTemporaryDirectory() stringByAppendingPathComponent:fileName]);
auto temporaryFileURL = retainPtr([NSURL fileURLWithPath:temporaryFilePath.get()]);
[defaultManager removeItemAtURL:temporaryFileURL.get() error:nil];
[testImageData() writeToFile:temporaryFilePath.get() atomically:YES];
@try {
runTest(temporaryFileURL.get());
} @finally {
[defaultManager removeItemAtURL:temporaryFileURL.get() error:nil];
}
}
#endif // PLATFORM(IOS_FAMILY)
static void simulateFolderDragWithURL(DragAndDropSimulator *simulator, NSURL *folderURL)
{
#if PLATFORM(MAC)
[simulator writePromisedFiles:@[ folderURL ]];
#else
auto folderProvider = adoptNS([[NSItemProvider alloc] init]);
[folderProvider setSuggestedName:folderURL.lastPathComponent];
[folderProvider setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
[folderProvider registerFileRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypeFolder fileOptions:0 visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[protectedFolderURL = retainPtr(folderURL)] (void(^completion)(NSURL *, BOOL, NSError *)) -> NSProgress * {
completion(protectedFolderURL.get(), NO, nil);
return nil;
}];
simulator.externalItemProviders = @[ folderProvider.get() ];
#endif
}
#pragma mark - Platform testing helper functions
#if PLATFORM(MAC)
BOOL isCompletelyTransparent(NSImage *image)
{
auto representation = adoptNS([[NSBitmapImageRep alloc] initWithData:image.TIFFRepresentation]);
for (int row = 0; row < image.size.height; ++row) {
for (int column = 0; column < image.size.width; ++column) {
if ([representation colorAtX:column y:row].alphaComponent)
return false;
}
}
return true;
}
#endif
#if PLATFORM(IOS_FAMILY)
typedef void(^ItemProviderDataLoadHandler)(NSData *, NSError *);
@implementation NSItemProvider (AttachmentTesting)
- (void)registerData:(NSData *)data type:(NSString *)type
{
[self registerDataRepresentationForTypeIdentifier:type visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[protectedData = retainPtr(data)] (ItemProviderDataLoadHandler completionHandler) -> NSProgress * {
completionHandler(protectedData.get(), nil);
return nil;
}];
}
- (void)expectType:(NSString *)type withData:(NSData *)expectedData
{
BOOL containsType = [self.registeredTypeIdentifiers containsObject:type];
EXPECT_TRUE(containsType);
if (!containsType) {
NSLog(@"Expected: %@ to contain %@", self, type);
return;
}
__block bool done = false;
[self loadDataRepresentationForTypeIdentifier:type completionHandler:^(NSData *observedData, NSError *error) {
EXPECT_TRUE([observedData isEqualToData:expectedData]);
if (![observedData isEqualToData:expectedData])
NSLog(@"Expected data: <%tu bytes> to be equal to data: <%tu bytes>", observedData.length, expectedData.length);
EXPECT_TRUE(!error);
if (error)
NSLog(@"Encountered error when loading data: %@", error);
done = true;
}];
TestWebKitAPI::Util::run(&done);
}
@end
#endif // PLATFORM(IOS_FAMILY)
void platformCopyRichTextWithMultipleAttachments()
{
auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(__bridge NSString *)kUTTypePNG]);
auto pdf = adoptNS([[NSTextAttachment alloc] initWithData:testPDFData() ofType:(__bridge NSString *)kUTTypePDF]);
auto zip = adoptNS([[NSTextAttachment alloc] initWithData:testZIPData() ofType:(__bridge NSString *)kUTTypeZipArchive]);
auto richText = adoptNS([[NSMutableAttributedString alloc] init]);
[richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:image.get()]];
[richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:pdf.get()]];
[richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:zip.get()]];
#if PLATFORM(MAC)
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
[pasteboard clearContents];
[pasteboard writeObjects:@[ richText.get() ]];
#elif PLATFORM(IOS_FAMILY)
auto item = adoptNS([[NSItemProvider alloc] init]);
[item registerObject:richText.get() visibility:NSItemProviderRepresentationVisibilityAll];
[UIPasteboard generalPasteboard].itemProviders = @[ item.get() ];
#endif
}
void platformCopyRichTextWithImage()
{
auto richText = adoptNS([[NSMutableAttributedString alloc] init]);
auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(__bridge NSString *)kUTTypePNG]);
[richText appendAttributedString:[[[NSAttributedString alloc] initWithString:@"Lorem ipsum "] autorelease]];
[richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:image.get()]];
[richText appendAttributedString:[[[NSAttributedString alloc] initWithString:@" dolor sit amet."] autorelease]];
#if PLATFORM(MAC)
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
[pasteboard clearContents];
[pasteboard writeObjects:@[ richText.get() ]];
#elif PLATFORM(IOS_FAMILY)
auto item = adoptNS([[NSItemProvider alloc] init]);
[item registerObject:richText.get() visibility:NSItemProviderRepresentationVisibilityAll];
[UIPasteboard generalPasteboard].itemProviders = @[ item.get() ];
#endif
}
void platformCopyPNG()
{
#if PLATFORM(MAC)
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
[pasteboard declareTypes:@[NSPasteboardTypePNG] owner:nil];
[pasteboard setData:testImageData() forType:NSPasteboardTypePNG];
#elif PLATFORM(IOS_FAMILY)
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
auto item = adoptNS([[NSItemProvider alloc] init]);
[item setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
[item registerData:testImageData() type:(__bridge NSString *)kUTTypePNG];
pasteboard.itemProviders = @[ item.get() ];
#endif
}
#if PLATFORM(MAC)
typedef NSImage PlatformImage;
#else
typedef UIImage PlatformImage;
#endif
PlatformImage *platformImageWithData(NSData *data)
{
#if PLATFORM(MAC)
return [[[NSImage alloc] initWithData:data] autorelease];
#else
return [UIImage imageWithData:data];
#endif
}
@interface FileWrapper : NSFileWrapper
@end
@implementation FileWrapper
@end
namespace TestWebKitAPI {
#pragma mark - Platform-agnostic tests
TEST(WKAttachmentTests, AttachmentElementInsertion)
{
auto webView = webViewForTestingAttachments();
RetainPtr<_WKAttachment> firstAttachment;
RetainPtr<_WKAttachment> secondAttachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
// Use the given content type for the attachment element's type.
firstAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo" contentType:@"text/html" data:testHTMLData()];
EXPECT_WK_STREQ(@"foo", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ(@"text/html", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ(@"38 bytes", [webView valueOfAttribute:@"subtitle" forQuerySelector:@"attachment"]);
observer.expectAttachmentUpdates(@[ ], @[ firstAttachment.get() ]);
}
_WKAttachmentInfo *info = [firstAttachment info];
EXPECT_TRUE([info.data isEqualToData:testHTMLData()]);
EXPECT_TRUE([info.contentType isEqualToString:@"text/html"]);
EXPECT_TRUE([info.name isEqualToString:@"foo"]);
EXPECT_EQ(info.filePath.length, 0U);
{
ObserveAttachmentUpdatesForScope scope(webView.get());
// Since no content type is explicitly specified, compute it from the file extension.
[webView _executeEditCommand:@"DeleteBackward" argument:nil completion:nil];
secondAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"bar.png" contentType:nil data:testImageData()];
EXPECT_WK_STREQ(@"bar.png", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ(@"image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ(@"37 KB", [webView valueOfAttribute:@"subtitle" forQuerySelector:@"attachment"]);
scope.expectAttachmentUpdates(@[ firstAttachment.get() ], @[ secondAttachment.get() ]);
}
[firstAttachment expectRequestedDataToBe:testHTMLData()];
[secondAttachment expectRequestedDataToBe:testImageData()];
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentid" forQuerySelector:@"attachment"]);
}
TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingAndDeletingNewline)
{
auto webView = webViewForTestingAttachments();
RetainPtr<_WKAttachment> attachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData()];
observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
}
[webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
[webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
[webView stringByEvaluatingJavaScript:@"getSelection().collapse(document.body)"];
[webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
[webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
_WKAttachmentInfo *info = [attachment info];
EXPECT_TRUE([info.data isEqualToData:testHTMLData()]);
EXPECT_TRUE([info.contentType isEqualToString:@"text/plain"]);
EXPECT_TRUE([info.name isEqualToString:@"foo.txt"]);
EXPECT_EQ(info.filePath.length, 0U);
[webView expectUpdatesAfterCommand:@"DeleteForward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
[attachment expectRequestedDataToBe:testHTMLData()];
}
TEST(WKAttachmentTests, AttachmentUpdatesWhenUndoingAndRedoing)
{
auto webView = webViewForTestingAttachments();
RetainPtr<NSData> htmlData = testHTMLData();
RetainPtr<_WKAttachment> attachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData()];
observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
}
[webView expectUpdatesAfterCommand:@"Undo" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
[attachment expectRequestedDataToBe:htmlData.get()];
[webView expectUpdatesAfterCommand:@"Redo" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
[attachment expectRequestedDataToBe:htmlData.get()];
[webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
[attachment expectRequestedDataToBe:htmlData.get()];
[webView expectUpdatesAfterCommand:@"Undo" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
[attachment expectRequestedDataToBe:htmlData.get()];
[webView expectUpdatesAfterCommand:@"Redo" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
[attachment expectRequestedDataToBe:htmlData.get()];
}
TEST(WKAttachmentTests, AttachmentUpdatesWhenChangingFontStyles)
{
auto webView = webViewForTestingAttachments();
RetainPtr<_WKAttachment> attachment;
[webView _synchronouslyExecuteEditCommand:@"InsertText" argument:@"Hello"];
{
ObserveAttachmentUpdatesForScope observer(webView.get());
attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData()];
observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
}
[webView expectUpdatesAfterCommand:@"InsertText" withArgument:@"World" expectedRemovals:@[] expectedInsertions:@[]];
[webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
[webView expectUpdatesAfterCommand:@"ToggleBold" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
[webView expectUpdatesAfterCommand:@"ToggleItalic" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
[webView expectUpdatesAfterCommand:@"ToggleUnderline" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
[attachment expectRequestedDataToBe:testHTMLData()];
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentid" forQuerySelector:@"attachment"]);
// Inserting text should delete the current selection, removing the attachment in the process.
[webView expectUpdatesAfterCommand:@"InsertText" withArgument:@"foo" expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
[attachment expectRequestedDataToBe:testHTMLData()];
}
TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingLists)
{
auto webView = webViewForTestingAttachments();
RetainPtr<_WKAttachment> attachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData()];
observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
}
[webView expectUpdatesAfterCommand:@"InsertOrderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
// This edit command behaves more like a "toggle", and will actually break us out of the list we just inserted.
[webView expectUpdatesAfterCommand:@"InsertOrderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
[webView expectUpdatesAfterCommand:@"InsertUnorderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
[webView expectUpdatesAfterCommand:@"InsertUnorderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
[attachment expectRequestedDataToBe:testHTMLData()];
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentid" forQuerySelector:@"attachment"]);
}
TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingRichMarkup)
{
auto webView = webViewForTestingAttachments();
RetainPtr<_WKAttachment> attachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"InsertHTML" argument:@"<div><strong><attachment src='cid:123-4567' title='a'></attachment></strong></div>"];
attachment = observer.observer().inserted[0];
observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
observer.expectSourceForIdentifier(@"cid:123-4567", [attachment uniqueIdentifier]);
}
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentid" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').uniqueIdentifier"]);
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').remove()"];
[webView waitForNextPresentationUpdate];
observer.expectAttachmentUpdates(@[ attachment.get() ], @[ ]);
}
[attachment expectRequestedDataToBe:nil];
}
TEST(WKAttachmentTests, AttachmentUpdatesWhenCuttingAndPasting)
{
auto webView = webViewForTestingAttachments();
RetainPtr<_WKAttachment> attachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData()];
observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
}
[attachment expectRequestedDataToBe:testHTMLData()];
EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').uniqueIdentifier"]);
[webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Cut" argument:nil];
observer.expectAttachmentUpdates(@[ attachment.get() ], @[ ]);
}
[attachment expectRequestedDataToBe:testHTMLData()];
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
}
[attachment expectRequestedDataToBe:testHTMLData()];
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
EXPECT_FALSE([webView hasAttribute:@"webkitattachmentid" forQuerySelector:@"attachment"]);
}
TEST(WKAttachmentTests, AttachmentDataForEmptyFile)
{
auto webView = webViewForTestingAttachments();
RetainPtr<_WKAttachment> attachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
attachment = [webView synchronouslyInsertAttachmentWithFilename:@"empty.txt" contentType:@"text/plain" data:[NSData data]];
observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
}
[attachment expectRequestedDataToBe:[NSData data]];
EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').uniqueIdentifier"]);
{
ObserveAttachmentUpdatesForScope scope(webView.get());
[webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
scope.expectAttachmentUpdates(@[ attachment.get() ], @[ ]);
}
[attachment expectRequestedDataToBe:[NSData data]];
}
TEST(WKAttachmentTests, DropFolderAsAttachmentAndMoveByDragging)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration _setAttachmentElementEnabled:YES];
auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:NSMakeRect(0, 0, 400, 400) configuration:configuration.get()]);
[[simulator webView] synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
runTestWithTemporaryFolder([simulator] (NSURL *folderURL) {
simulateFolderDragWithURL(simulator.get(), folderURL);
[simulator runFrom:CGPointMake(0, 0) to:CGPointMake(50, 50)];
TestWKWebView *webView = [simulator webView];
auto attachment = retainPtr([simulator insertedAttachments].firstObject);
#if PLATFORM(IOS_FAMILY)
NSString *expectedType = (__bridge NSString *)kUTTypeFolder;
#else
NSString *expectedType = (__bridge NSString *)kUTTypeDirectory;
#endif
EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').uniqueIdentifier"]);
EXPECT_WK_STREQ(expectedType, [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ(folderURL.lastPathComponent, [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
NSFileWrapper *image = [attachment info].fileWrapper.fileWrappers[@"image.png"];
NSFileWrapper *archive = [attachment info].fileWrapper.fileWrappers[@"archive.zip"];
EXPECT_TRUE([image.regularFileContents isEqualToData:testImageData()]);
EXPECT_TRUE([archive.regularFileContents isEqualToData:testZIPData()]);
[webView evaluateJavaScript:@"getSelection().collapseToEnd()" completionHandler:nil];
[webView _executeEditCommand:@"InsertParagraph" argument:nil completion:nil];
[webView _executeEditCommand:@"InsertHTML" argument:@"<em>foo</em>" completion:nil];
[webView _executeEditCommand:@"InsertParagraph" argument:nil completion:nil];
[webView expectElementTag:@"ATTACHMENT" toComeBefore:@"EM"];
[simulator clearExternalDragInformation];
[simulator runFrom:webView.attachmentElementMidPoint to:CGPointMake(300, 300)];
[webView expectElementTag:@"EM" toComeBefore:@"ATTACHMENT"];
});
}
TEST(WKAttachmentTests, InsertFolderAndFileWithUnknownExtension)
{
auto webView = webViewForTestingAttachments();
auto file = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testHTMLData()]);
[file setPreferredFilename:@"test.foobar"];
auto image = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testImageData()]);
auto document = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testPDFData()]);
auto folder = adoptNS([[NSFileWrapper alloc] initDirectoryWithFileWrappers:@{ @"image.png": image.get(), @"document.pdf": document.get() }]);
[folder setPreferredFilename:@"folder"];
RetainPtr<_WKAttachment> firstAttachment;
RetainPtr<_WKAttachment> secondAttachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
firstAttachment = [webView synchronouslyInsertAttachmentWithFileWrapper:file.get() contentType:nil];
observer.expectAttachmentUpdates(@[ ], @[ firstAttachment.get() ]);
}
{
ObserveAttachmentUpdatesForScope observer(webView.get());
secondAttachment = [webView synchronouslyInsertAttachmentWithFileWrapper:folder.get() contentType:nil];
observer.expectAttachmentUpdates(@[ ], @[ secondAttachment.get() ]);
}
auto checkAttachmentConsistency = [webView, file, folder] (_WKAttachment *expectedFileAttachment, _WKAttachment *expectedFolderAttachment) {
[webView expectElementCount:2 querySelector:@"ATTACHMENT"];
EXPECT_TRUE(UTTypeConformsTo((__bridge CFStringRef)[webView valueOfAttribute:@"type" forQuerySelector:@"attachment[title=folder]"], kUTTypeDirectory));
EXPECT_TRUE(UTTypeConformsTo((__bridge CFStringRef)[webView valueOfAttribute:@"type" forQuerySelector:@"attachment[title^=test]"], kUTTypeData));
EXPECT_WK_STREQ(expectedFileAttachment.uniqueIdentifier, [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment[title^=test]').uniqueIdentifier"]);
EXPECT_WK_STREQ(expectedFolderAttachment.uniqueIdentifier, [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment[title=folder]').uniqueIdentifier"]);
EXPECT_TRUE([expectedFileAttachment.info.fileWrapper isEqual:file.get()]);
EXPECT_TRUE([expectedFolderAttachment.info.fileWrapper isEqual:folder.get()]);
};
checkAttachmentConsistency(firstAttachment.get(), secondAttachment.get());
{
// Swap the two attachments' file wrappers without creating or destroying attachment elements.
ObserveAttachmentUpdatesForScope observer(webView.get());
[firstAttachment synchronouslySetFileWrapper:folder.get() newContentType:nil error:nil];
[secondAttachment synchronouslySetFileWrapper:file.get() newContentType:nil error:nil];
observer.expectAttachmentUpdates(@[ ], @[ ]);
}
checkAttachmentConsistency(secondAttachment.get(), firstAttachment.get());
}
TEST(WKAttachmentTests, ChangeAttachmentDataAndFileInformation)
{
auto webView = webViewForTestingAttachments();
RetainPtr<_WKAttachment> attachment;
{
RetainPtr<NSData> pdfData = testPDFData();
ObserveAttachmentUpdatesForScope observer(webView.get());
attachment = [webView synchronouslyInsertAttachmentWithFilename:@"test.pdf" contentType:@"application/pdf" data:pdfData.get()];
[attachment expectRequestedDataToBe:pdfData.get()];
EXPECT_WK_STREQ(@"test.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ(@"application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
}
{
RetainPtr<NSData> imageData = testImageData();
ObserveAttachmentUpdatesForScope observer(webView.get());
[attachment synchronouslySetData:imageData.get() newContentType:@"image/png" newFilename:@"icon.png" error:nil];
[attachment expectRequestedDataToBe:imageData.get()];
EXPECT_WK_STREQ(@"icon.png", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ(@"image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
observer.expectAttachmentUpdates(@[ ], @[ ]);
}
{
RetainPtr<NSData> textData = [@"Hello world" dataUsingEncoding:NSUTF8StringEncoding];
ObserveAttachmentUpdatesForScope observer(webView.get());
// The new content type should be inferred from the file name.
[attachment synchronouslySetData:textData.get() newContentType:nil newFilename:@"foo.txt" error:nil];
[attachment expectRequestedDataToBe:textData.get()];
EXPECT_WK_STREQ(@"foo.txt", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ(@"text/plain", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
observer.expectAttachmentUpdates(@[ ], @[ ]);
}
{
RetainPtr<NSData> htmlData = testHTMLData();
ObserveAttachmentUpdatesForScope observer(webView.get());
[attachment synchronouslySetData:htmlData.get() newContentType:@"text/html" newFilename:@"bar" error:nil];
[attachment expectRequestedDataToBe:htmlData.get()];
EXPECT_WK_STREQ(@"bar", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ(@"text/html", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
observer.expectAttachmentUpdates(@[ ], @[ ]);
}
[webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[ ]];
}
TEST(WKAttachmentTests, RemoveNewlinesBeforePastedImage)
{
platformCopyPNG();
RetainPtr<_WKAttachment> attachment;
auto webView = webViewForTestingAttachments();
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
EXPECT_EQ(1U, observer.observer().inserted.count);
attachment = observer.observer().inserted[0];
}
auto size = platformImageWithData([attachment info].data).size;
EXPECT_EQ(215., size.width);
EXPECT_EQ(174., size.height);
EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
[webView stringByEvaluatingJavaScript:@"getSelection().collapse(document.body, 0)"];
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"InsertParagraph" argument:nil];
[webView _synchronouslyExecuteEditCommand:@"InsertParagraph" argument:nil];
observer.expectAttachmentUpdates(@[ ], @[ ]);
[webView expectElementTagsInOrder:@[ @"BR", @"BR", @"IMG" ]];
}
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
[webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
observer.expectAttachmentUpdates(@[ ], @[ ]);
[webView expectElementCount:0 querySelector:@"BR"];
}
}
TEST(WKAttachmentTests, CutAndPastePastedImage)
{
platformCopyPNG();
RetainPtr<_WKAttachment> attachment;
auto webView = webViewForTestingAttachments();
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
EXPECT_EQ(1U, observer.observer().inserted.count);
attachment = observer.observer().inserted[0];
}
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
[webView _synchronouslyExecuteEditCommand:@"Cut" argument:nil];
observer.expectAttachmentUpdates(@[ attachment.get() ], @[ ]);
}
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
}
}
TEST(WKAttachmentTests, MovePastedImageByDragging)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration _setAttachmentElementEnabled:YES];
auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:NSMakeRect(0, 0, 400, 400) configuration:configuration.get()]);
TestWKWebView *webView = [simulator webView];
[webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
platformCopyPNG();
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
[webView _executeEditCommand:@"InsertParagraph" argument:nil completion:nil];
[webView _executeEditCommand:@"InsertHTML" argument:@"<strong>text</strong>" completion:nil];
[webView _synchronouslyExecuteEditCommand:@"InsertParagraph" argument:nil];
[webView expectElementTag:@"IMG" toComeBefore:@"STRONG"];
[webView expectElementCount:1 querySelector:@"IMG"];
// Drag the attachment element to somewhere below the strong text.
[simulator runFrom:CGPointMake(50, 50) to:CGPointMake(50, 350)];
[webView expectElementTag:@"STRONG" toComeBefore:@"IMG"];
[webView expectElementCount:1 querySelector:@"IMG"];
EXPECT_EQ([simulator insertedAttachments].count, [simulator removedAttachments].count);
[simulator endDataTransfer];
}
TEST(WKAttachmentTests, InsertPastedAttributedStringContainingImage)
{
auto webView = webViewForTestingAttachments();
platformCopyRichTextWithImage();
RetainPtr<_WKAttachment> attachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
EXPECT_EQ(0U, observer.observer().removed.count);
EXPECT_EQ(1U, observer.observer().inserted.count);
attachment = observer.observer().inserted[0];
}
[attachment expectRequestedDataToBe:testImageData()];
EXPECT_WK_STREQ("Lorem ipsum dolor sit amet.", [webView stringByEvaluatingJavaScript:@"document.body.textContent"]);
EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
[webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
observer.expectAttachmentUpdates(@[ attachment.get() ], @[ ]);
}
}
TEST(WKAttachmentTests, InsertPastedAttributedStringContainingMultipleAttachments)
{
auto webView = webViewForTestingAttachments();
platformCopyRichTextWithMultipleAttachments();
RetainPtr<_WKAttachment> imageAttachment;
RetainPtr<_WKAttachment> zipAttachment;
RetainPtr<_WKAttachment> pdfAttachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
EXPECT_EQ(0U, observer.observer().removed.count);
EXPECT_EQ(3U, observer.observer().inserted.count);
for (_WKAttachment *attachment in observer.observer().inserted) {
NSData *data = attachment.info.data;
if ([data isEqualToData:testZIPData()])
zipAttachment = attachment;
else if ([data isEqualToData:testPDFData()])
pdfAttachment = attachment;
else if ([data isEqualToData:testImageData()])
imageAttachment = attachment;
}
}
EXPECT_TRUE(zipAttachment && imageAttachment && pdfAttachment);
[webView expectElementCount:2 querySelector:@"ATTACHMENT"];
[webView expectElementCount:1 querySelector:@"IMG"];
EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
NSString *zipAttachmentType = [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"];
#if USES_MODERN_ATTRIBUTED_STRING_CONVERSION
EXPECT_WK_STREQ("application/zip", zipAttachmentType);
#else
EXPECT_WK_STREQ("application/octet-stream", zipAttachmentType);
#endif
EXPECT_WK_STREQ([imageAttachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
EXPECT_WK_STREQ([pdfAttachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].uniqueIdentifier"]);
EXPECT_WK_STREQ([zipAttachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].uniqueIdentifier"]);
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
[webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
NSArray<_WKAttachment *> *removedAttachments = [observer.observer() removed];
EXPECT_EQ(3U, removedAttachments.count);
EXPECT_TRUE([removedAttachments containsObject:zipAttachment.get()]);
EXPECT_TRUE([removedAttachments containsObject:imageAttachment.get()]);
EXPECT_TRUE([removedAttachments containsObject:pdfAttachment.get()]);
}
}
TEST(WKAttachmentTests, DoNotInsertDataURLImagesAsAttachments)
{
auto webContentSourceView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]);
[webContentSourceView synchronouslyLoadTestPageNamed:@"apple-data-url"];
[webContentSourceView selectAll:nil];
[webContentSourceView _synchronouslyExecuteEditCommand:@"Copy" argument:nil];
auto webView = webViewForTestingAttachments();
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
EXPECT_EQ(0U, observer.observer().inserted.count);
}
EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"Boolean(document.querySelector('attachment'))"].boolValue);
EXPECT_EQ(1990, [webView stringByEvaluatingJavaScript:@"document.querySelector('img').src.length"].integerValue);
EXPECT_WK_STREQ("This is an apple", [webView stringByEvaluatingJavaScript:@"document.body.textContent"]);
}
TEST(WKAttachmentTests, InsertAndRemoveDuplicateAttachment)
{
auto webView = webViewForTestingAttachments();
RetainPtr<NSData> data = testHTMLData();
auto fileWrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:data.get()]);
RetainPtr<_WKAttachment> originalAttachment;
RetainPtr<_WKAttachment> pastedAttachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
originalAttachment = [webView synchronouslyInsertAttachmentWithFileWrapper:fileWrapper.get() contentType:@"text/plain"];
EXPECT_EQ(0U, observer.observer().removed.count);
observer.expectAttachmentUpdates(@[ ], @[ originalAttachment.get() ]);
}
[webView selectAll:nil];
[webView _executeEditCommand:@"Copy" argument:nil completion:nil];
[webView evaluateJavaScript:@"getSelection().collapseToEnd()" completionHandler:nil];
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
EXPECT_EQ(0U, observer.observer().removed.count);
EXPECT_EQ(1U, observer.observer().inserted.count);
pastedAttachment = observer.observer().inserted.firstObject;
EXPECT_FALSE([pastedAttachment isEqual:originalAttachment.get()]);
}
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
observer.expectAttachmentUpdates(@[ pastedAttachment.get() ], @[ ]);
[originalAttachment expectRequestedDataToBe:data.get()];
}
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView evaluateJavaScript:@"getSelection().setPosition(document.body)" completionHandler:nil];
[webView _synchronouslyExecuteEditCommand:@"DeleteForward" argument:nil];
observer.expectAttachmentUpdates(@[ originalAttachment.get() ], @[ ]);
}
EXPECT_FALSE([originalAttachment isEqual:pastedAttachment.get()]);
EXPECT_TRUE([[originalAttachment info].fileWrapper isEqual:[pastedAttachment info].fileWrapper]);
EXPECT_TRUE([[originalAttachment info].fileWrapper isEqual:fileWrapper.get()]);
}
TEST(WKAttachmentTests, InsertDuplicateAttachmentAndUpdateData)
{
auto webView = webViewForTestingAttachments();
auto originalData = retainPtr(testHTMLData());
auto fileWrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:originalData.get()]);
RetainPtr<_WKAttachment> originalAttachment;
RetainPtr<_WKAttachment> pastedAttachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
originalAttachment = [webView synchronouslyInsertAttachmentWithFileWrapper:fileWrapper.get() contentType:@"text/plain"];
EXPECT_EQ(0U, observer.observer().removed.count);
observer.expectAttachmentUpdates(@[ ], @[ originalAttachment.get() ]);
}
[webView selectAll:nil];
[webView _executeEditCommand:@"Copy" argument:nil completion:nil];
[webView evaluateJavaScript:@"getSelection().collapseToEnd()" completionHandler:nil];
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
EXPECT_EQ(0U, observer.observer().removed.count);
EXPECT_EQ(1U, observer.observer().inserted.count);
pastedAttachment = observer.observer().inserted.firstObject;
EXPECT_FALSE([pastedAttachment isEqual:originalAttachment.get()]);
}
auto updatedData = retainPtr([@"HELLO WORLD" dataUsingEncoding:NSUTF8StringEncoding]);
[originalAttachment synchronouslySetData:updatedData.get() newContentType:nil newFilename:nil error:nil];
[originalAttachment expectRequestedDataToBe:updatedData.get()];
[pastedAttachment expectRequestedDataToBe:originalData.get()];
EXPECT_FALSE([originalAttachment isEqual:pastedAttachment.get()]);
EXPECT_FALSE([[originalAttachment info].fileWrapper isEqual:[pastedAttachment info].fileWrapper]);
EXPECT_FALSE([[originalAttachment info].fileWrapper isEqual:fileWrapper.get()]);
}
TEST(WKAttachmentTests, InsertAttachmentUsingFileWrapperWithFilePath)
{
auto webView = webViewForTestingAttachments();
auto originalFileWrapper = adoptNS([[NSFileWrapper alloc] initWithURL:testImageFileURL() options:0 error:nil]);
RetainPtr<_WKAttachment> attachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
attachment = [webView synchronouslyInsertAttachmentWithFileWrapper:originalFileWrapper.get() contentType:nil];
observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
}
_WKAttachmentInfo *infoBeforeUpdate = [attachment info];
EXPECT_WK_STREQ("image/png", infoBeforeUpdate.contentType);
EXPECT_WK_STREQ("icon.png", infoBeforeUpdate.name);
EXPECT_TRUE([originalFileWrapper isEqual:infoBeforeUpdate.fileWrapper]);
[attachment expectRequestedDataToBe:testImageData()];
auto newFileWrapper = adoptNS([[NSFileWrapper alloc] initWithURL:testPDFFileURL() options:0 error:nil]);
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[attachment synchronouslySetFileWrapper:newFileWrapper.get() newContentType:nil error:nil];
observer.expectAttachmentUpdates(@[ ], @[ ]);
}
_WKAttachmentInfo *infoAfterUpdate = [attachment info];
EXPECT_WK_STREQ("application/pdf", infoAfterUpdate.contentType);
EXPECT_WK_STREQ("test.pdf", infoAfterUpdate.name);
EXPECT_TRUE([newFileWrapper isEqual:infoAfterUpdate.fileWrapper]);
[attachment expectRequestedDataToBe:testPDFData()];
}
TEST(WKAttachmentTests, InvalidateAttachmentsAfterMainFrameNavigation)
{
auto webView = webViewForTestingAttachments();
RetainPtr<_WKAttachment> pdfAttachment;
RetainPtr<_WKAttachment> htmlAttachment;
{
ObserveAttachmentUpdatesForScope insertionObserver(webView.get());
pdfAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"attachment.pdf" contentType:@"application/pdf" data:testPDFData()];
htmlAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"index.html" contentType:@"text/html" data:testHTMLData()];
insertionObserver.expectAttachmentUpdates(@[ ], @[ pdfAttachment.get(), htmlAttachment.get() ]);
EXPECT_TRUE([pdfAttachment isConnected]);
EXPECT_TRUE([htmlAttachment isConnected]);
}
ObserveAttachmentUpdatesForScope removalObserver(webView.get());
[webView synchronouslyLoadTestPageNamed:@"simple"];
removalObserver.expectAttachmentUpdates(@[ pdfAttachment.get(), htmlAttachment.get() ], @[ ]);
EXPECT_FALSE([pdfAttachment isConnected]);
EXPECT_FALSE([htmlAttachment isConnected]);
[pdfAttachment expectRequestedDataToBe:nil];
[htmlAttachment expectRequestedDataToBe:nil];
}
TEST(WKAttachmentTests, InvalidateAttachmentsAfterWebProcessTermination)
{
auto webView = webViewForTestingAttachments();
RetainPtr<_WKAttachment> pdfAttachment;
RetainPtr<_WKAttachment> htmlAttachment;
{
ObserveAttachmentUpdatesForScope insertionObserver(webView.get());
pdfAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"attachment.pdf" contentType:@"application/pdf" data:testPDFData()];
htmlAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"index.html" contentType:@"text/html" data:testHTMLData()];
insertionObserver.expectAttachmentUpdates(@[ ], @[ pdfAttachment.get(), htmlAttachment.get() ]);
EXPECT_TRUE([pdfAttachment isConnected]);
EXPECT_TRUE([htmlAttachment isConnected]);
}
{
ObserveAttachmentUpdatesForScope removalObserver(webView.get());
[webView stringByEvaluatingJavaScript:@"getSelection().collapseToEnd()"];
[webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
removalObserver.expectAttachmentUpdates(@[ htmlAttachment.get() ], @[ ]);
EXPECT_TRUE([pdfAttachment isConnected]);
EXPECT_FALSE([htmlAttachment isConnected]);
[htmlAttachment expectRequestedDataToBe:testHTMLData()];
}
__block bool webProcessTerminated = false;
auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
[webView setNavigationDelegate:navigationDelegate.get()];
[navigationDelegate setWebContentProcessDidTerminate:^(WKWebView *) {
webProcessTerminated = true;
}];
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _killWebContentProcess];
TestWebKitAPI::Util::run(&webProcessTerminated);
observer.expectAttachmentUpdates(@[ pdfAttachment.get() ], @[ ]);
EXPECT_FALSE([pdfAttachment isConnected]);
EXPECT_FALSE([htmlAttachment isConnected]);
[pdfAttachment expectRequestedDataToBe:nil];
[htmlAttachment expectRequestedDataToBe:nil];
}
TEST(WKAttachmentTests, MoveAttachmentElementAsIconByDragging)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration _setAttachmentElementEnabled:YES];
auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:NSMakeRect(0, 0, 400, 400) configuration:configuration.get()]);
TestWKWebView *webView = [simulator webView];
[webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
auto data = retainPtr(testPDFData());
auto attachment = retainPtr([webView synchronouslyInsertAttachmentWithFilename:@"document.pdf" contentType:@"application/pdf" data:data.get()]);
[webView _executeEditCommand:@"InsertParagraph" argument:nil completion:nil];
[webView _executeEditCommand:@"InsertHTML" argument:@"<strong>text</strong>" completion:nil];
[webView _synchronouslyExecuteEditCommand:@"InsertParagraph" argument:nil];
[webView expectElementTag:@"ATTACHMENT" toComeBefore:@"STRONG"];
// Drag the attachment element to somewhere below the strong text.
[simulator runFrom:[webView attachmentElementMidPoint] to:CGPointMake(50, 300)];
EXPECT_WK_STREQ("document.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ("application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
[attachment expectRequestedDataToBe:data.get()];
EXPECT_EQ([simulator insertedAttachments].count, [simulator removedAttachments].count);
#if PLATFORM(MAC)
EXPECT_FALSE(isCompletelyTransparent([simulator draggingInfo].draggedImage));
#endif
[webView expectElementTag:@"STRONG" toComeBefore:@"ATTACHMENT"];
[simulator endDataTransfer];
}
TEST(WKAttachmentTests, PasteWebArchiveContainingImages)
{
NSData *markupData = [@"<img src='1.png' alt='foo'><div><br></div><img src='2.gif' alt='bar'>" dataUsingEncoding:NSUTF8StringEncoding];
auto mainResource = adoptNS([[WebResource alloc] initWithData:markupData URL:[NSURL URLWithString:@"foo.html"] MIMEType:@"text/html" textEncodingName:@"utf-8" frameName:nil]);
auto pngResource = adoptNS([[WebResource alloc] initWithData:testImageData() URL:[NSURL URLWithString:@"1.png"] MIMEType:@"image/png" textEncodingName:nil frameName:nil]);
auto gifResource = adoptNS([[WebResource alloc] initWithData:testGIFData() URL:[NSURL URLWithString:@"2.gif"] MIMEType:@"image/gif" textEncodingName:nil frameName:nil]);
auto archive = adoptNS([[WebArchive alloc] initWithMainResource:mainResource.get() subresources:@[ pngResource.get(), gifResource.get() ] subframeArchives:@[ ]]);
#if PLATFORM(MAC)
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
[pasteboard declareTypes:@[WebArchivePboardType] owner:nil];
[pasteboard setData:[archive data] forType:WebArchivePboardType];
#else
UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
[pasteboard setData:[archive data] forPasteboardType:WebArchivePboardType];
#endif
RetainPtr<_WKAttachment> gifAttachment;
RetainPtr<_WKAttachment> pngAttachment;
auto webView = webViewForTestingAttachments();
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
[webView expectElementCount:2 querySelector:@"IMG"];
for (_WKAttachment *attachment in observer.observer().inserted) {
if ([attachment.info.contentType isEqualToString:@"image/png"])
pngAttachment = attachment;
else if ([attachment.info.contentType isEqualToString:@"image/gif"])
gifAttachment = attachment;
}
EXPECT_WK_STREQ("foo", [pngAttachment info].name);
EXPECT_WK_STREQ("bar", [gifAttachment info].name);
[pngAttachment expectRequestedDataToBe:testImageData()];
[gifAttachment expectRequestedDataToBe:testGIFData()];
observer.expectAttachmentUpdates(@[ ], @[ pngAttachment.get(), gifAttachment.get() ]);
}
TEST(WKAttachmentTests, ChangeFileWrapperForPastedImage)
{
platformCopyPNG();
auto webView = webViewForTestingAttachments();
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView paste:nil];
[webView waitForImageElementSizeToBecome:CGSizeMake(215, 174)];
auto attachment = retainPtr(observer.observer().inserted.firstObject);
auto originalImageData = retainPtr([attachment info].fileWrapper);
EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"HTMLAttachmentElement.getAttachmentIdentifier(document.querySelector('img'))"]);
auto newImage = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testGIFData()]);
[newImage setPreferredFilename:@"foo.gif"];
[attachment synchronouslySetFileWrapper:newImage.get() newContentType:nil error:nil];
[webView waitForImageElementSizeToBecome:CGSizeMake(52, 64)];
[attachment synchronouslySetFileWrapper:originalImageData.get() newContentType:@"image/png" error:nil];
[webView waitForImageElementSizeToBecome:CGSizeMake(215, 174)];
}
TEST(WKAttachmentTests, AddAttachmentToConnectedImageElement)
{
auto webView = webViewForTestingAttachments();
[webView _synchronouslyExecuteEditCommand:@"InsertHTML" argument:@"<img></img>"];
__block bool doneWaitingForAttachmentInsertion = false;
[webView performAfterReceivingMessage:@"inserted" action:^{
doneWaitingForAttachmentInsertion = true;
}];
const char *scriptForEnsuringAttachmentIdentifier = \
"const identifier = HTMLAttachmentElement.getAttachmentIdentifier(document.querySelector('img'));"
"setTimeout(() => webkit.messageHandlers.testHandler.postMessage('inserted'), 0);"
"identifier";
ObserveAttachmentUpdatesForScope observer(webView.get());
NSString *attachmentIdentifier = [webView stringByEvaluatingJavaScript:@(scriptForEnsuringAttachmentIdentifier)];
auto attachment = retainPtr([webView _attachmentForIdentifier:attachmentIdentifier]);
EXPECT_WK_STREQ(attachmentIdentifier, [attachment uniqueIdentifier]);
EXPECT_WK_STREQ(attachmentIdentifier, [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
Util::run(&doneWaitingForAttachmentInsertion);
observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
auto firstImage = adoptNS([[NSFileWrapper alloc] initWithURL:testImageFileURL() options:0 error:nil]);
auto secondImage = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testGIFData()]);
[secondImage setPreferredFilename:@"foo.gif"];
[attachment synchronouslySetFileWrapper:firstImage.get() newContentType:@"image/png" error:nil];
[webView waitForImageElementSizeToBecome:CGSizeMake(215, 174)];
[attachment synchronouslySetFileWrapper:secondImage.get() newContentType:(__bridge NSString *)kUTTypeGIF error:nil];
[webView waitForImageElementSizeToBecome:CGSizeMake(52, 64)];
[attachment synchronouslySetFileWrapper:firstImage.get() newContentType:nil error:nil];
[webView waitForImageElementSizeToBecome:CGSizeMake(215, 174)];
}
TEST(WKAttachmentTests, ConnectImageWithAttachmentToDocument)
{
auto webView = webViewForTestingAttachments();
ObserveAttachmentUpdatesForScope observer(webView.get());
NSString *identifier = [webView stringByEvaluatingJavaScript:@"image = document.createElement('img'); HTMLAttachmentElement.getAttachmentIdentifier(image)"];
auto image = adoptNS([[NSFileWrapper alloc] initWithURL:testImageFileURL() options:0 error:nil]);
auto attachment = retainPtr([webView _attachmentForIdentifier:identifier]);
[attachment synchronouslySetFileWrapper:image.get() newContentType:nil error:nil];
EXPECT_FALSE([attachment isConnected]);
observer.expectAttachmentUpdates(@[ ], @[ ]);
[webView evaluateJavaScript:@"document.body.appendChild(image)" completionHandler:nil];
[webView waitForImageElementSizeToBecome:CGSizeMake(215, 174)];
EXPECT_TRUE([attachment isConnected]);
observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
auto newImage = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testGIFData()]);
[newImage setPreferredFilename:@"test.gif"];
[attachment synchronouslySetFileWrapper:newImage.get() newContentType:nil error:nil];
[webView waitForImageElementSizeToBecome:CGSizeMake(52, 64)];
}
TEST(WKAttachmentTests, CustomFileWrapperSubclass)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration _setAttachmentElementEnabled:YES];
RetainPtr<NSException> exception;
@try {
[configuration _setAttachmentFileWrapperClass:[NSArray self]];
} @catch(NSException *caught) {
exception = caught;
}
EXPECT_TRUE(exception);
[configuration _setAttachmentFileWrapperClass:[FileWrapper self]];
auto webView = webViewForTestingAttachments(CGSizeZero, configuration.get());
ObserveAttachmentUpdatesForScope observer(webView.get());
platformCopyPNG();
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
NSArray<_WKAttachment *> * insertedAttachments = observer.observer().inserted;
EXPECT_EQ(1U, insertedAttachments.count);
EXPECT_EQ([FileWrapper self], [insertedAttachments.firstObject.info.fileWrapper class]);
}
// FIXME: Remove this version guard once rdar://51752593 is resolved.
#if PLATFORM(IOS) && __IPHONE_OS_VERSION_MAX_ALLOWED < 130000
TEST(WKAttachmentTests, CopyAndPasteBetweenWebViews)
{
auto file = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testHTMLData()]);
[file setPreferredFilename:@"test.foobar"];
auto image = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testImageData()]);
auto document = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testPDFData()]);
auto folder = adoptNS([[NSFileWrapper alloc] initDirectoryWithFileWrappers:@{ @"image.png": image.get(), @"document.pdf": document.get() }]);
[folder setPreferredFilename:@"folder"];
auto archive = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testZIPData()]);
[archive setPreferredFilename:@"archive"];
@autoreleasepool {
auto firstWebView = webViewForTestingAttachments();
[firstWebView synchronouslyInsertAttachmentWithFileWrapper:file.get() contentType:@"application/octet-stream"];
[firstWebView synchronouslyInsertAttachmentWithFileWrapper:folder.get() contentType:(__bridge NSString *)kUTTypeFolder];
[firstWebView synchronouslyInsertAttachmentWithFileWrapper:archive.get() contentType:@"application/zip"];
[firstWebView selectAll:nil];
[firstWebView _executeEditCommand:@"Copy" argument:nil completion:nil];
}
auto secondWebView = webViewForTestingAttachments();
ObserveAttachmentUpdatesForScope observer(secondWebView.get());
[secondWebView paste:nil];
[secondWebView expectElementCount:3 querySelector:@"attachment"];
EXPECT_EQ(3U, observer.observer().inserted.count);
NSString *plainFileIdentifier = [secondWebView stringByEvaluatingJavaScript:@"document.querySelector('attachment[title^=test]').uniqueIdentifier"];
NSString *folderIdentifier = [secondWebView stringByEvaluatingJavaScript:@"document.querySelector('attachment[title=folder]').uniqueIdentifier"];
NSString *archiveIdentifier = [secondWebView stringByEvaluatingJavaScript:@"document.querySelector('attachment[title=archive]').uniqueIdentifier"];
_WKAttachmentInfo *pastedFileInfo = [secondWebView _attachmentForIdentifier:plainFileIdentifier].info;
_WKAttachmentInfo *pastedFolderInfo = [secondWebView _attachmentForIdentifier:folderIdentifier].info;
_WKAttachmentInfo *pastedArchiveInfo = [secondWebView _attachmentForIdentifier:archiveIdentifier].info;
NSDictionary<NSString *, NSFileWrapper *> *pastedFolderContents = pastedFolderInfo.fileWrapper.fileWrappers;
NSFileWrapper *documentFromFolder = [pastedFolderContents objectForKey:@"document.pdf"];
NSFileWrapper *imageFromFolder = [pastedFolderContents objectForKey:@"image.png"];
EXPECT_TRUE([[document regularFileContents] isEqualToData:documentFromFolder.regularFileContents]);
EXPECT_TRUE([[image regularFileContents] isEqualToData:imageFromFolder.regularFileContents]);
EXPECT_TRUE([[file regularFileContents] isEqualToData:pastedFileInfo.fileWrapper.regularFileContents]);
EXPECT_TRUE([[archive regularFileContents] isEqualToData:pastedArchiveInfo.fileWrapper.regularFileContents]);
EXPECT_WK_STREQ("application/octet-stream", pastedFileInfo.contentType);
EXPECT_WK_STREQ("public.directory", pastedFolderInfo.contentType);
EXPECT_WK_STREQ("application/zip", pastedArchiveInfo.contentType);
}
#endif // PLATFORM(IOS) && __IPHONE_OS_VERSION_MAX_ALLOWED < 130000
TEST(WKAttachmentTests, AttachmentIdentifierOfClonedAttachment)
{
auto webView = webViewForTestingAttachments();
auto attachment = retainPtr([webView synchronouslyInsertAttachmentWithFilename:@"attachment.pdf" contentType:@"application/pdf" data:testPDFData()]);
EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.body.cloneNode(true).querySelector('attachment').uniqueIdentifier"]);
}
TEST(WKAttachmentTests, SetFileWrapperForPDFImageAttachment)
{
auto webView = webViewForTestingAttachments();
[webView evaluateJavaScript:@"document.body.appendChild()" completionHandler:nil];
NSString *identifier = [webView stringByEvaluatingJavaScript:@"const i = document.createElement('img'); document.body.appendChild(i); HTMLAttachmentElement.getAttachmentIdentifier(i)"];
auto attachment = retainPtr([webView _attachmentForIdentifier:identifier]);
auto pdfFile = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testPDFData()]);
[attachment setFileWrapper:pdfFile.get() contentType:(__bridge NSString *)kUTTypePDF completion:nil];
[webView waitForImageElementSizeToBecome:CGSizeMake(130, 29)];
[webView synchronouslyLoadTestPageNamed:@"simple"];
auto zipFile = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:testZIPData()]);
[attachment setFileWrapper:zipFile.get() contentType:(__bridge NSString *)kUTTypeZipArchive completion:nil];
EXPECT_FALSE([attachment isConnected]);
}
#pragma mark - Platform-specific tests
#if PLATFORM(MAC)
TEST(WKAttachmentTestsMac, InsertPastedFileURLsAsAttachments)
{
NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
[pasteboard clearContents];
[pasteboard declareTypes:@[NSFilenamesPboardType] owner:nil];
[pasteboard setPropertyList:@[testPDFFileURL().path, testImageFileURL().path] forType:NSFilenamesPboardType];
RetainPtr<NSArray<_WKAttachment *>> insertedAttachments;
auto webView = webViewForTestingAttachments();
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
insertedAttachments = [observer.observer() inserted];
EXPECT_EQ(2U, [insertedAttachments count]);
}
[webView expectElementCount:1 querySelector:@"ATTACHMENT"];
[webView expectElementCount:1 querySelector:@"IMG"];
EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').getAttribute('type')"]);
EXPECT_WK_STREQ("test.pdf", [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').getAttribute('title')"]);
NSString *imageAttachmentIdentifier = [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"];
if ([testImageData() isEqualToData:[insertedAttachments firstObject].info.data])
EXPECT_WK_STREQ([insertedAttachments firstObject].uniqueIdentifier, imageAttachmentIdentifier);
else
EXPECT_WK_STREQ([insertedAttachments lastObject].uniqueIdentifier, imageAttachmentIdentifier);
for (_WKAttachment *attachment in insertedAttachments.get())
EXPECT_GT(attachment.info.filePath.length, 0U);
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
[webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
NSArray<_WKAttachment *> *removedAttachments = [observer.observer() removed];
EXPECT_EQ(2U, removedAttachments.count);
EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments firstObject]]);
EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments lastObject]]);
}
}
TEST(WKAttachmentTestsMac, DISABLED_InsertDroppedFilePromisesAsAttachments)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration _setAttachmentElementEnabled:YES];
auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:NSMakeRect(0, 0, 400, 400) configuration:configuration.get()]);
TestWKWebView *webView = [simulator webView];
[webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
[simulator writePromisedFiles:@[ testPDFFileURL(), testImageFileURL() ]];
[simulator runFrom:CGPointMake(0, 0) to:CGPointMake(50, 50)];
[webView expectElementCount:1 querySelector:@"ATTACHMENT"];
[webView expectElementCount:1 querySelector:@"IMG"];
EXPECT_EQ(2U, [simulator insertedAttachments].count);
auto insertedAttachments = retainPtr([simulator insertedAttachments]);
NSArray<NSData *> *expectedData = @[ testPDFData(), testImageData() ];
for (_WKAttachment *attachment in insertedAttachments.get()) {
EXPECT_GT(attachment.info.filePath.length, 0U);
EXPECT_TRUE([expectedData containsObject:attachment.info.data]);
if ([testPDFData() isEqualToData:attachment.info.data])
EXPECT_WK_STREQ("application/pdf", attachment.info.contentType);
else if ([testImageData() isEqualToData:attachment.info.data]) {
EXPECT_WK_STREQ("image/png", attachment.info.contentType);
EXPECT_WK_STREQ(attachment.uniqueIdentifier, [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
}
}
[webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
[webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
auto removedAttachments = retainPtr([simulator removedAttachments]);
EXPECT_EQ(2U, [removedAttachments count]);
[webView expectElementCount:0 querySelector:@"ATTACHMENT"];
[webView expectElementCount:0 querySelector:@"IMG"];
EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments firstObject]]);
EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments lastObject]]);
}
TEST(WKAttachmentTestsMac, DragAttachmentAsFilePromise)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration _setAttachmentElementEnabled:YES];
auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:NSMakeRect(0, 0, 400, 400) configuration:configuration.get()]);
TestWKWebView *webView = [simulator webView];
[webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
auto fileWrapper = adoptNS([[NSFileWrapper alloc] initWithURL:testPDFFileURL() options:0 error:nil]);
auto attachment = retainPtr([webView synchronouslyInsertAttachmentWithFileWrapper:fileWrapper.get() contentType:nil]);
[simulator runFrom:[webView attachmentElementMidPoint] to:CGPointMake(300, 300)];
NSArray<NSURL *> *urls = [simulator receivePromisedFiles];
EXPECT_EQ(1U, urls.count);
EXPECT_WK_STREQ("test.pdf", urls.lastObject.lastPathComponent);
EXPECT_TRUE([[NSData dataWithContentsOfURL:urls.firstObject] isEqualToData:testPDFData()]);
EXPECT_FALSE(isCompletelyTransparent([simulator draggingInfo].draggedImage));
}
TEST(WKAttachmentTestsMac, DragAttachmentWithNoTypeShouldNotCrash)
{
auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
[configuration _setAttachmentElementEnabled:YES];
auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:NSMakeRect(0, 0, 400, 400) configuration:configuration.get()]);
TestWKWebView *webView = [simulator webView];
[webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
[webView stringByEvaluatingJavaScript:@"document.body.appendChild(document.createElement('attachment')); 0"];
[simulator runFrom:[webView attachmentElementMidPoint] to:CGPointMake(300, 300)];
NSArray<NSURL *> *urls = [simulator receivePromisedFiles];
EXPECT_EQ(0U, urls.count);
}
#endif // PLATFORM(MAC)
#if PLATFORM(IOS_FAMILY)
static RetainPtr<UITargetedDragPreview> targetedImageDragPreview(WKWebView *webView, NSData *imageData, CGSize size)
{
auto imageView = adoptNS([[UIImageView alloc] initWithImage:[UIImage imageWithData:imageData]]);
[imageView setBounds:CGRectMake(0, 0, size.width, size.height)];
auto defaultDropTarget = adoptNS([[UIDragPreviewTarget alloc] initWithContainer:webView center:CGPointMake(450, 450)]);
auto parameters = adoptNS([[UIDragPreviewParameters alloc] init]);
return adoptNS([[UITargetedDragPreview alloc] initWithView:imageView.get() parameters:parameters.get() target:defaultDropTarget.get()]);
}
TEST(WKAttachmentTestsIOS, TargetedPreviewsWhenDroppingImages)
{
auto webView = webViewForTestingAttachments();
[webView _setEditable:YES];
auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
// The first item preview should be scaled down by a factor of 2.
auto firstPreview = targetedImageDragPreview(webView.get(), testImageData(), CGSizeMake(430, 348));
auto firstItem = adoptNS([[NSItemProvider alloc] init]);
[firstItem registerDataRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypePNG withData:testImageData() loadingDelay:0.5];
[firstItem setPreferredPresentationSize:CGSizeMake(215, 174)];
[firstItem setSuggestedName:@"icon"];
// The second item preview should be scaled up by a factor of 2.
auto secondPreview = targetedImageDragPreview(webView.get(), testGIFData(), CGSizeMake(26, 32));
auto secondItem = adoptNS([[NSItemProvider alloc] init]);
[secondItem registerDataRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypeGIF withData:testGIFData() loadingDelay:0.5];
[secondItem setPreferredPresentationSize:CGSizeMake(52, 64)];
[secondItem setSuggestedName:@"apple"];
[simulator setDropAnimationTiming:DropAnimationShouldFinishBeforeHandlingDrop];
[simulator setExternalItemProviders:[NSArray arrayWithObjects:firstItem.get(), secondItem.get(), nil] defaultDropPreviews:[NSArray arrayWithObjects:firstPreview.get(), secondPreview.get(), nil]];
[simulator runFrom:CGPointMake(0, 0) to:CGPointMake(450, 450)];
EXPECT_EQ([simulator delayedDropPreviews].count, 2U);
UITargetedDragPreview *firstDelayedPreview = [simulator delayedDropPreviews].firstObject;
UITargetedDragPreview *secondDelayedPreview = [simulator delayedDropPreviews].lastObject;
auto imageElementBounds = retainPtr([webView allBoundingClientRects:@"IMG"]);
EXPECT_TRUE(CGAffineTransformEqualToTransform(CGAffineTransformMakeScale(0.5, 0.5), firstDelayedPreview.target.transform));
EXPECT_TRUE(CGRectEqualToRect(CGRectMake(0, 0, 430, 348), firstDelayedPreview.parameters.visiblePath.bounds));
EXPECT_TRUE(CGRectContainsPoint([imageElementBounds firstObject].CGRectValue, firstDelayedPreview.target.center));
EXPECT_TRUE(CGAffineTransformEqualToTransform(CGAffineTransformMakeScale(2, 2), secondDelayedPreview.target.transform));
EXPECT_TRUE(CGRectEqualToRect(CGRectMake(0, 0, 26, 32), secondDelayedPreview.parameters.visiblePath.bounds));
EXPECT_TRUE(CGRectContainsPoint([imageElementBounds lastObject].CGRectValue, secondDelayedPreview.target.center));
[webView expectElementCount:2 querySelector:@"IMG"];
_WKAttachment *pngAttachment = [[simulator insertedAttachments] _attachmentWithName:@"icon.png"];
_WKAttachment *gifAttachment = [[simulator insertedAttachments] _attachmentWithName:@"apple.gif"];
EXPECT_WK_STREQ(pngAttachment.info.contentType, @"image/png");
EXPECT_WK_STREQ(gifAttachment.info.contentType, @"image/gif");
}
TEST(WKAttachmentTestsIOS, TargetedPreviewIsClippedWhenDroppingTallImage)
{
auto webView = webViewForTestingAttachments(CGSizeMake(800, 200));
[webView stringByEvaluatingJavaScript:@"document.body.style.margin = '0'"];
[webView _setEditable:YES];
auto imageData = retainPtr([NSData dataWithContentsOfURL:[[NSBundle mainBundle] URLForResource:@"400x400-green" withExtension:@"png" subdirectory:@"TestWebKitAPI.resources"]]);
auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
auto preview = targetedImageDragPreview(webView.get(), imageData.get(), CGSizeMake(100, 100));
auto item = adoptNS([[NSItemProvider alloc] init]);
[item registerDataRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypePNG withData:imageData.get() loadingDelay:0.5];
[item setPreferredPresentationSize:CGSizeMake(400, 400)];
[item setSuggestedName:@"green"];
[simulator setDropAnimationTiming:DropAnimationShouldFinishBeforeHandlingDrop];
[simulator setExternalItemProviders:[NSArray arrayWithObject:item.get()] defaultDropPreviews:[NSArray arrayWithObject:preview.get()]];
[simulator runFrom:CGPointMake(0, 0) to:CGPointMake(350, 350)];
EXPECT_EQ([simulator delayedDropPreviews].count, 1U);
UITargetedDragPreview *delayedPreview = [simulator delayedDropPreviews].firstObject;
EXPECT_TRUE(CGAffineTransformEqualToTransform(CGAffineTransformMakeScale(4, 4), delayedPreview.target.transform));
EXPECT_TRUE(CGRectEqualToRect(CGRectMake(0, 0, 100, 50), delayedPreview.parameters.visiblePath.bounds));
EXPECT_TRUE(CGPointEqualToPoint(CGPointMake(200, 100), delayedPreview.target.center));
[webView expectElementCount:1 querySelector:@"IMG"];
_WKAttachment *attachment = [[simulator insertedAttachments] _attachmentWithName:@"green.png"];
EXPECT_WK_STREQ(attachment.info.contentType, @"image/png");
}
TEST(WKAttachmentTestsIOS, InsertDroppedImageAsAttachment)
{
auto webView = webViewForTestingAttachments();
auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
auto item = adoptNS([[NSItemProvider alloc] init]);
[item registerData:testImageData() type:(__bridge NSString *)kUTTypePNG];
[dragAndDropSimulator setExternalItemProviders:@[ item.get() ]];
[dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
EXPECT_EQ(1U, [dragAndDropSimulator insertedAttachments].count);
EXPECT_EQ(0U, [dragAndDropSimulator removedAttachments].count);
auto attachment = retainPtr([dragAndDropSimulator insertedAttachments].firstObject);
[attachment expectRequestedDataToBe:testImageData()];
EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
[webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
observer.expectAttachmentUpdates(@[ attachment.get() ], @[ ]);
}
}
TEST(WKAttachmentTestsIOS, InsertDroppedImageWithPreferredPresentationSize)
{
auto webView = webViewForTestingAttachments();
auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
auto item = adoptNS([[NSItemProvider alloc] init]);
[item registerData:testImageData() type:(__bridge NSString *)kUTTypePNG];
[item setPreferredPresentationSize:CGSizeMake(200, 100)];
[dragAndDropSimulator setExternalItemProviders:@[ item.get() ]];
[dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
CGSize imageElementSize = [webView imageElementSize];
EXPECT_EQ(200, imageElementSize.width);
EXPECT_EQ(100, imageElementSize.height);
}
TEST(WKAttachmentTestsIOS, InsertDroppedAttributedStringContainingAttachment)
{
auto webView = webViewForTestingAttachments();
auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(__bridge NSString *)kUTTypePNG]);
auto item = adoptNS([[NSItemProvider alloc] init]);
[item registerObject:[NSAttributedString attributedStringWithAttachment:image.get()] visibility:NSItemProviderRepresentationVisibilityAll];
[dragAndDropSimulator setExternalItemProviders:@[ item.get() ]];
[dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
EXPECT_EQ(1U, [dragAndDropSimulator insertedAttachments].count);
EXPECT_EQ(0U, [dragAndDropSimulator removedAttachments].count);
auto attachment = retainPtr([dragAndDropSimulator insertedAttachments].firstObject);
auto size = platformImageWithData([attachment info].data).size;
EXPECT_EQ(215., size.width);
EXPECT_EQ(174., size.height);
EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('img').attachmentIdentifier"]);
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
[webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
observer.expectAttachmentUpdates(@[ attachment.get() ], @[ ]);
}
}
TEST(WKAttachmentTestsIOS, InsertDroppedRichAndPlainTextFilesAsAttachments)
{
// Here, both rich text and plain text are content types that WebKit already understands how to insert in editable
// areas in the absence of attachment elements. However, due to the explicitly set attachment presentation style
// on the item providers, we should instead treat them as dropped files and insert attachment elements.
// This exercises the scenario of dragging rich and plain text files from Files to Mail.
auto richTextItem = adoptNS([[NSItemProvider alloc] init]);
auto richText = adoptNS([[NSAttributedString alloc] initWithString:@"Hello world" attributes:@{ NSFontAttributeName: [UIFont boldSystemFontOfSize:12] }]);
[richTextItem setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
[richTextItem registerObject:richText.get() visibility:NSItemProviderRepresentationVisibilityAll];
[richTextItem setSuggestedName:@"hello.rtf"];
auto plainTextItem = adoptNS([[NSItemProvider alloc] init]);
[plainTextItem setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
[plainTextItem registerObject:@"Hello world" visibility:NSItemProviderRepresentationVisibilityAll];
[plainTextItem setSuggestedName:@"world.txt"];
auto webView = webViewForTestingAttachments();
auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
[dragAndDropSimulator setExternalItemProviders:@[ richTextItem.get(), plainTextItem.get() ]];
[dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
EXPECT_EQ(2U, [dragAndDropSimulator insertedAttachments].count);
EXPECT_EQ(0U, [dragAndDropSimulator removedAttachments].count);
for (_WKAttachment *attachment in [dragAndDropSimulator insertedAttachments])
EXPECT_GT([attachment info].data.length, 0U);
[webView expectElementCount:2 querySelector:@"ATTACHMENT"];
EXPECT_WK_STREQ("hello.rtf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('title')"]);
EXPECT_WK_STREQ((__bridge NSString *)kUTTypeFlatRTFD, [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
EXPECT_WK_STREQ("world.txt", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('title')"]);
EXPECT_WK_STREQ((__bridge NSString *)kUTTypeUTF8PlainText, [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]);
}
TEST(WKAttachmentTestsIOS, InsertDroppedZipArchiveAsAttachment)
{
// Since WebKit doesn't have any default DOM representation for ZIP archives, we should fall back to inserting
// attachment elements. This exercises the flow of dragging a ZIP file from an app that doesn't specify a preferred
// presentation style (e.g. Notes) into Mail.
auto item = adoptNS([[NSItemProvider alloc] init]);
NSData *data = testZIPData();
[item registerData:data type:(__bridge NSString *)kUTTypeZipArchive];
[item setSuggestedName:@"archive.zip"];
auto webView = webViewForTestingAttachments();
auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
[dragAndDropSimulator setExternalItemProviders:@[ item.get() ]];
[dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
EXPECT_EQ(1U, [dragAndDropSimulator insertedAttachments].count);
EXPECT_EQ(0U, [dragAndDropSimulator removedAttachments].count);
[[dragAndDropSimulator insertedAttachments].firstObject expectRequestedDataToBe:data];
[webView expectElementCount:1 querySelector:@"ATTACHMENT"];
EXPECT_WK_STREQ("archive.zip", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ("application/zip", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
}
TEST(WKAttachmentTestsIOS, InsertDroppedItemProvidersInOrder)
{
// Tests that item providers are inserted in the order they are specified. In this case, the two inserted attachments
// should be separated by a link.
auto firstAttachmentItem = adoptNS([[NSItemProvider alloc] init]);
[firstAttachmentItem setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
[firstAttachmentItem registerObject:@"FIRST" visibility:NSItemProviderRepresentationVisibilityAll];
[firstAttachmentItem setSuggestedName:@"first.txt"];
auto inlineTextItem = adoptNS([[NSItemProvider alloc] init]);
auto appleURL = retainPtr([NSURL URLWithString:@"https://www.apple.com/"]);
[inlineTextItem registerObject:appleURL.get() visibility:NSItemProviderRepresentationVisibilityAll];
auto secondAttachmentItem = adoptNS([[NSItemProvider alloc] init]);
[secondAttachmentItem registerData:testPDFData() type:(__bridge NSString *)kUTTypePDF];
[secondAttachmentItem setSuggestedName:@"second.pdf"];
auto webView = webViewForTestingAttachments();
auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
[dragAndDropSimulator setExternalItemProviders:@[ firstAttachmentItem.get(), inlineTextItem.get(), secondAttachmentItem.get() ]];
[dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
EXPECT_EQ(2U, [dragAndDropSimulator insertedAttachments].count);
EXPECT_EQ(0U, [dragAndDropSimulator removedAttachments].count);
for (_WKAttachment *attachment in [dragAndDropSimulator insertedAttachments])
EXPECT_GT([attachment info].data.length, 0U);
[webView expectElementTagsInOrder:@[ @"ATTACHMENT", @"A", @"ATTACHMENT" ]];
EXPECT_WK_STREQ("first.txt", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('title')"]);
EXPECT_WK_STREQ((__bridge NSString *)kUTTypeUTF8PlainText, [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
EXPECT_WK_STREQ([appleURL absoluteString], [webView valueOfAttribute:@"href" forQuerySelector:@"a"]);
EXPECT_WK_STREQ("second.pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('title')"]);
EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]);
}
TEST(WKAttachmentTestsIOS, DragAttachmentInsertedAsFile)
{
auto item = adoptNS([[NSItemProvider alloc] init]);
auto data = retainPtr(testPDFData());
[item registerData:data.get() type:(__bridge NSString *)kUTTypePDF];
[item setSuggestedName:@"document.pdf"];
auto webView = webViewForTestingAttachments();
auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
[dragAndDropSimulator setExternalItemProviders:@[ item.get() ]];
[dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
// First, verify that the attachment was successfully dropped.
EXPECT_EQ(1U, [dragAndDropSimulator insertedAttachments].count);
_WKAttachment *attachment = [dragAndDropSimulator insertedAttachments].firstObject;
[attachment expectRequestedDataToBe:data.get()];
EXPECT_WK_STREQ("document.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ("application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
[webView evaluateJavaScript:@"getSelection().removeAllRanges()" completionHandler:nil];
[dragAndDropSimulator setExternalItemProviders:@[ ]];
[dragAndDropSimulator runFrom:CGPointMake(25, 25) to:CGPointMake(-100, -100)];
// Next, verify that dragging the attachment produces an item provider with a PDF attachment.
EXPECT_EQ(1U, [dragAndDropSimulator sourceItemProviders].count);
NSItemProvider *itemProvider = [dragAndDropSimulator sourceItemProviders].firstObject;
EXPECT_EQ(UIPreferredPresentationStyleAttachment, itemProvider.preferredPresentationStyle);
[itemProvider expectType:(__bridge NSString *)kUTTypePDF withData:data.get()];
EXPECT_WK_STREQ("document.pdf", [itemProvider suggestedName]);
[dragAndDropSimulator endDataTransfer];
}
TEST(WKAttachmentTestsIOS, DragAttachmentInsertedAsData)
{
auto webView = webViewForTestingAttachments();
auto data = retainPtr(testPDFData());
RetainPtr<_WKAttachment> attachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
attachment = [webView synchronouslyInsertAttachmentWithFilename:@"document.pdf" contentType:@"application/pdf" data:data.get()];
observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
}
// First, verify that the attachment was successfully inserted from raw data.
[attachment expectRequestedDataToBe:data.get()];
EXPECT_WK_STREQ("document.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ("application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
[webView evaluateJavaScript:@"getSelection().removeAllRanges()" completionHandler:nil];
auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
[dragAndDropSimulator runFrom:CGPointMake(25, 25) to:CGPointMake(-100, -100)];
// Next, verify that dragging the attachment produces an item provider with a PDF attachment.
EXPECT_EQ(1U, [dragAndDropSimulator sourceItemProviders].count);
NSItemProvider *itemProvider = [dragAndDropSimulator sourceItemProviders].firstObject;
EXPECT_EQ(UIPreferredPresentationStyleAttachment, itemProvider.preferredPresentationStyle);
[itemProvider expectType:(__bridge NSString *)kUTTypePDF withData:data.get()];
EXPECT_WK_STREQ("document.pdf", [itemProvider suggestedName]);
[dragAndDropSimulator endDataTransfer];
}
static RetainPtr<NSItemProvider> mapItemForTesting()
{
auto placemark = adoptNS([allocMKPlacemarkInstance() initWithCoordinate:CLLocationCoordinate2DMake(37.3327, -122.0053)]);
auto mapItem = adoptNS([allocMKMapItemInstance() initWithPlacemark:placemark.get()]);
[mapItem setName:@"Apple Park.vcf"];
auto itemProvider = adoptNS([[NSItemProvider alloc] init]);
[itemProvider registerObject:mapItem.get() visibility:NSItemProviderRepresentationVisibilityAll];
[itemProvider setSuggestedName:[mapItem name]];
return itemProvider;
}
static RetainPtr<NSItemProvider> contactItemForTesting()
{
auto contact = adoptNS([allocCNMutableContactInstance() init]);
[contact setGivenName:@"Foo"];
[contact setFamilyName:@"Bar"];
auto itemProvider = adoptNS([[NSItemProvider alloc] init]);
[itemProvider registerObject:contact.get() visibility:NSItemProviderRepresentationVisibilityAll];
[itemProvider setSuggestedName:@"Foo Bar.vcf"];
return itemProvider;
}
TEST(WKAttachmentTestsIOS, InsertDroppedMapItemAsAttachment)
{
auto itemProvider = mapItemForTesting();
auto webView = webViewForTestingAttachments();
auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
[simulator setExternalItemProviders:@[ itemProvider.get() ]];
[simulator runFrom:CGPointMake(25, 25) to:CGPointMake(100, 100)];
NSURL *droppedLinkURL = [NSURL URLWithString:[webView valueOfAttribute:@"href" forQuerySelector:@"a"]];
[webView expectElementTag:@"A" toComeBefore:@"ATTACHMENT"];
EXPECT_WK_STREQ("maps.apple.com", droppedLinkURL.host);
EXPECT_WK_STREQ("Apple Park.vcf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ("text/vcard", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
EXPECT_EQ(1U, [simulator insertedAttachments].count);
_WKAttachmentInfo *info = [simulator insertedAttachments].firstObject.info;
EXPECT_WK_STREQ("Apple Park.vcf", info.name);
EXPECT_WK_STREQ("text/vcard", info.contentType);
}
TEST(WKAttachmentTestsIOS, InsertDroppedContactAsAttachment)
{
auto itemProvider = contactItemForTesting();
auto webView = webViewForTestingAttachments();
auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
[simulator setExternalItemProviders:@[ itemProvider.get() ]];
[simulator runFrom:CGPointMake(25, 25) to:CGPointMake(100, 100)];
[webView expectElementCount:0 querySelector:@"a"];
EXPECT_WK_STREQ("Foo Bar.vcf", [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').title"]);
EXPECT_WK_STREQ("text/vcard", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
EXPECT_EQ(1U, [simulator insertedAttachments].count);
_WKAttachmentInfo *info = [simulator insertedAttachments].firstObject.info;
EXPECT_WK_STREQ("Foo Bar.vcf", info.name);
EXPECT_WK_STREQ("text/vcard", info.contentType);
}
TEST(WKAttachmentTestsIOS, InsertPastedContactAsAttachment)
{
UIPasteboard.generalPasteboard.itemProviders = @[ contactItemForTesting().autorelease() ];
auto webView = webViewForTestingAttachments();
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView paste:nil];
[webView expectElementCount:0 querySelector:@"a"];
EXPECT_WK_STREQ("Foo Bar.vcf", [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').title"]);
EXPECT_WK_STREQ("text/vcard", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
EXPECT_EQ(1U, observer.observer().inserted.count);
_WKAttachment *attachment = observer.observer().inserted.firstObject;
EXPECT_WK_STREQ("Foo Bar.vcf", attachment.info.name);
EXPECT_WK_STREQ("text/vcard", attachment.info.contentType);
}
TEST(WKAttachmentTestsIOS, InsertPastedMapItemAsAttachment)
{
UIApplicationInitialize();
UIPasteboard.generalPasteboard.itemProviders = @[ mapItemForTesting().autorelease() ];
auto webView = webViewForTestingAttachments();
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView paste:nil];
NSURL *pastedLinkURL = [NSURL URLWithString:[webView valueOfAttribute:@"href" forQuerySelector:@"a"]];
[webView expectElementTag:@"A" toComeBefore:@"ATTACHMENT"];
EXPECT_WK_STREQ("maps.apple.com", pastedLinkURL.host);
EXPECT_WK_STREQ("Apple Park.vcf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
EXPECT_WK_STREQ("text/vcard", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
EXPECT_EQ(1U, observer.observer().inserted.count);
_WKAttachment *attachment = observer.observer().inserted.firstObject;
EXPECT_WK_STREQ("Apple Park.vcf", attachment.info.name);
EXPECT_WK_STREQ("text/vcard", attachment.info.contentType);
}
TEST(WKAttachmentTestsIOS, InsertPastedFilesAsAttachments)
{
auto pdfItem = adoptNS([[NSItemProvider alloc] init]);
[pdfItem setSuggestedName:@"doc"];
[pdfItem registerData:testPDFData() type:(__bridge NSString *)kUTTypePDF];
auto textItem = adoptNS([[NSItemProvider alloc] init]);
[textItem setSuggestedName:@"hello"];
[textItem registerData:[@"helloworld" dataUsingEncoding:NSUTF8StringEncoding] type:(__bridge NSString *)kUTTypePlainText];
UIPasteboard.generalPasteboard.itemProviders = @[ pdfItem.get(), textItem.get() ];
RetainPtr<_WKAttachment> textAttachment;
RetainPtr<_WKAttachment> pdfAttachment;
auto webView = webViewForTestingAttachments();
{
ObserveAttachmentUpdatesForScope observer(webView.get());
[webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
EXPECT_EQ(2U, observer.observer().inserted.count);
_WKAttachment *firstAttachment = observer.observer().inserted.firstObject;
if ([firstAttachment.info.contentType isEqualToString:@"text/plain"]) {
textAttachment = firstAttachment;
pdfAttachment = observer.observer().inserted.lastObject;
} else {
EXPECT_WK_STREQ(firstAttachment.info.contentType, @"application/pdf");
textAttachment = observer.observer().inserted.lastObject;
pdfAttachment = firstAttachment;
}
observer.expectAttachmentUpdates(@[ ], @[ pdfAttachment.get(), textAttachment.get() ]);
}
[webView expectElementCount:2 querySelector:@"attachment"];
EXPECT_WK_STREQ("doc", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment[type^=application]"]);
EXPECT_WK_STREQ("doc", [pdfAttachment info].name);
EXPECT_WK_STREQ("hello", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment[type^=text]"]);
EXPECT_WK_STREQ("hello", [textAttachment info].name);
[pdfAttachment expectRequestedDataToBe:testPDFData()];
[textAttachment expectRequestedDataToBe:[@"helloworld" dataUsingEncoding:NSUTF8StringEncoding]];
EXPECT_TRUE([webView canPerformAction:@selector(paste:) withSender:nil]);
}
TEST(WKAttachmentTestsIOS, InsertDroppedImageWithNonImageFileExtension)
{
runTestWithTemporaryImageFile(@"image.hello", ^(NSURL *fileURL) {
auto item = adoptNS([[NSItemProvider alloc] init]);
[item setSuggestedName:@"image.hello"];
[item registerFileRepresentationForTypeIdentifier:(__bridge NSString *)kUTTypePNG fileOptions:NSItemProviderFileOptionOpenInPlace visibility:NSItemProviderRepresentationVisibilityAll loadHandler:^NSProgress *(void (^callback)(NSURL *, BOOL, NSError *))
{
callback(fileURL, YES, nil);
return nil;
}];
auto webView = webViewForTestingAttachments();
auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
[dragAndDropSimulator setExternalItemProviders:@[ item.get() ]];
[dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
EXPECT_EQ(1U, [dragAndDropSimulator insertedAttachments].count);
_WKAttachment *attachment = [dragAndDropSimulator insertedAttachments].firstObject;
_WKAttachmentInfo *info = attachment.info;
EXPECT_WK_STREQ("image/png", info.contentType);
EXPECT_WK_STREQ("image.hello", info.filePath.lastPathComponent);
EXPECT_WK_STREQ("image.hello", info.name);
[webView expectElementCount:1 querySelector:@"IMG"];
});
}
#if HAVE(PENCILKIT)
static BOOL forEachViewInHierarchy(UIView *view, void(^mapFunction)(UIView *subview, BOOL *stop))
{
BOOL stop = NO;
mapFunction(view, &stop);
if (stop)
return YES;
for (UIView *subview in view.subviews) {
stop = forEachViewInHierarchy(subview, mapFunction);
if (stop)
break;
}
return stop;
}
static PKCanvasView *findEditableImageCanvas(WKWebView *webView)
{
Class pkCanvasViewClass = NSClassFromString(@"PKCanvasView");
__block PKCanvasView *canvasView = nil;
forEachViewInHierarchy(webView.window, ^(UIView *subview, BOOL *stop) {
if (![subview isKindOfClass:pkCanvasViewClass])
return;
canvasView = (PKCanvasView *)subview;
*stop = YES;
});
return canvasView;
}
static void drawSquareInEditableImage(WKWebView *webView)
{
Class pkDrawingClass = NSClassFromString(@"PKDrawing");
Class pkInkClass = NSClassFromString(@"PKInk");
Class pkStrokeClass = NSClassFromString(@"PKStroke");
PKCanvasView *canvasView = findEditableImageCanvas(webView);
RetainPtr<PKDrawing> drawing = canvasView.drawing ?: adoptNS([[pkDrawingClass alloc] init]);
RetainPtr<CGPathRef> path = adoptCF(CGPathCreateWithRect(CGRectMake(0, 0, 50, 50), NULL));
RetainPtr<PKInk> ink = [pkInkClass inkWithIdentifier:@"com.apple.ink.pen" color:UIColor.greenColor weight:100.0];
RetainPtr<PKStroke> stroke = adoptNS([[pkStrokeClass alloc] _initWithPath:path.get() ink:ink.get() inputScale:1]);
[drawing _addStroke:stroke.get()];
[canvasView setDrawing:drawing.get()];
}
TEST(WKAttachmentTestsIOS, EditableImageAttachmentDataInvalidation)
{
auto webView = webViewForTestingAttachments();
RetainPtr<_WKAttachment> attachment;
{
ObserveAttachmentUpdatesForScope observer(webView.get());
EXPECT_TRUE([webView _synchronouslyExecuteEditCommand:@"InsertEditableImage" argument:nil]);
EXPECT_EQ(observer.observer().inserted.count, 1LU);
attachment = observer.observer().inserted.firstObject;
}
[webView waitForNextPresentationUpdate];
{
ObserveAttachmentUpdatesForScope observer(webView.get());
drawSquareInEditableImage(webView.get());
observer.expectAttachmentInvalidation(@[ attachment.get() ]);
}
}
#endif // HAVE(PENCILKIT)
#endif // PLATFORM(IOS_FAMILY)
} // namespace TestWebKitAPI
#endif // PLATFORM(MAC) || PLATFORM(IOS)