[Attachment Support] Support dragging attachment elements out as files on macOS
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKitCocoa / WKAttachmentTests.mm
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27
28 #import "DragAndDropSimulator.h"
29 #import "PlatformUtilities.h"
30 #import "TestNavigationDelegate.h"
31 #import "TestWKWebView.h"
32 #import "WKWebViewConfigurationExtras.h"
33 #import <WebKit/WKPreferencesRefPrivate.h>
34 #import <WebKit/WKWebViewPrivate.h>
35 #import <WebKit/WebKit.h>
36 #import <WebKit/WebKitPrivate.h>
37 #import <wtf/RetainPtr.h>
38
39 #if PLATFORM(IOS)
40 #import <MobileCoreServices/MobileCoreServices.h>
41 #endif
42
43 #define USES_MODERN_ATTRIBUTED_STRING_CONVERSION ((PLATFORM(IOS) && __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000) || (PLATFORM(MAC) && __MAC_OS_X_VERSION_MIN_REQUIRED >= 101300))
44
45 #if WK_API_ENABLED && !PLATFORM(WATCHOS) && !PLATFORM(TVOS)
46
47 @interface AttachmentUpdateObserver : NSObject <WKUIDelegatePrivate>
48 @property (nonatomic, readonly) NSArray *inserted;
49 @property (nonatomic, readonly) NSArray *removed;
50 @end
51
52 @implementation AttachmentUpdateObserver {
53     RetainPtr<NSMutableArray<_WKAttachment *>> _inserted;
54     RetainPtr<NSMutableArray<_WKAttachment *>> _removed;
55     RetainPtr<NSMutableDictionary<NSString *, NSString *>> _identifierToSourceMap;
56 }
57
58 - (instancetype)init
59 {
60     if (self = [super init]) {
61         _inserted = adoptNS([[NSMutableArray alloc] init]);
62         _removed = adoptNS([[NSMutableArray alloc] init]);
63         _identifierToSourceMap = adoptNS([[NSMutableDictionary alloc] init]);
64     }
65     return self;
66 }
67
68 - (NSArray<_WKAttachment *> *)inserted
69 {
70     return _inserted.get();
71 }
72
73 - (NSArray<_WKAttachment *> *)removed
74 {
75     return _removed.get();
76 }
77
78 - (NSString *)sourceForIdentifier:(NSString *)identifier
79 {
80     return [_identifierToSourceMap objectForKey:identifier];
81 }
82
83 - (void)_webView:(WKWebView *)webView didInsertAttachment:(_WKAttachment *)attachment withSource:(NSString *)source
84 {
85     [_inserted addObject:attachment];
86     if (source)
87         [_identifierToSourceMap setObject:source forKey:attachment.uniqueIdentifier];
88 }
89
90 - (void)_webView:(WKWebView *)webView didRemoveAttachment:(_WKAttachment *)attachment
91 {
92     [_removed addObject:attachment];
93 }
94
95 @end
96
97 namespace TestWebKitAPI {
98
99 class ObserveAttachmentUpdatesForScope {
100 public:
101     ObserveAttachmentUpdatesForScope(TestWKWebView *webView)
102         : m_webView(webView)
103     {
104         m_previousDelegate = webView.UIDelegate;
105         m_observer = adoptNS([[AttachmentUpdateObserver alloc] init]);
106         webView.UIDelegate = m_observer.get();
107     }
108
109     ~ObserveAttachmentUpdatesForScope()
110     {
111         [m_webView setUIDelegate:m_previousDelegate.get()];
112     }
113
114     AttachmentUpdateObserver *observer() const { return m_observer.get(); }
115
116     void expectAttachmentUpdates(NSArray<_WKAttachment *> *removed, NSArray<_WKAttachment *> *inserted)
117     {
118         BOOL removedAttachmentsMatch = [[NSSet setWithArray:observer().removed] isEqual:[NSSet setWithArray:removed]];
119         if (!removedAttachmentsMatch)
120             NSLog(@"Expected removed attachments: %@ to match %@.", observer().removed, removed);
121         EXPECT_TRUE(removedAttachmentsMatch);
122
123         BOOL insertedAttachmentsMatch = [[NSSet setWithArray:observer().inserted] isEqual:[NSSet setWithArray:inserted]];
124         if (!insertedAttachmentsMatch)
125             NSLog(@"Expected inserted attachments: %@ to match %@.", observer().inserted, inserted);
126         EXPECT_TRUE(insertedAttachmentsMatch);
127     }
128
129     void expectSourceForIdentifier(NSString *expectedSource, NSString *identifier)
130     {
131         NSString *observedSource = [observer() sourceForIdentifier:identifier];
132         BOOL success = observedSource == expectedSource || [observedSource isEqualToString:expectedSource];
133         EXPECT_TRUE(success);
134         if (!success)
135             NSLog(@"Expected source: %@ but observed: %@", expectedSource, observedSource);
136     }
137
138 private:
139     RetainPtr<AttachmentUpdateObserver> m_observer;
140     RetainPtr<TestWKWebView> m_webView;
141     RetainPtr<id> m_previousDelegate;
142 };
143
144 }
145
146 @interface TestWKWebView (AttachmentTesting)
147 @end
148
149 static NSString *attachmentEditingTestMarkup = @"<meta name='viewport' content='width=device-width, initial-scale=1'>"
150     "<script>focus = () => document.body.focus()</script>"
151     "<body onload=focus() contenteditable></body>";
152
153 static RetainPtr<TestWKWebView> webViewForTestingAttachments(CGSize webViewSize, WKWebViewConfiguration *configuration)
154 {
155     configuration._attachmentElementEnabled = YES;
156     WKPreferencesSetCustomPasteboardDataEnabled((WKPreferencesRef)[configuration preferences], YES);
157
158     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, webViewSize.width, webViewSize.height) configuration:configuration]);
159     [webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
160
161     return webView;
162 }
163
164 static RetainPtr<TestWKWebView> webViewForTestingAttachments(CGSize webViewSize)
165 {
166     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
167     return webViewForTestingAttachments(webViewSize, configuration.get());
168 }
169
170 static RetainPtr<TestWKWebView> webViewForTestingAttachments()
171 {
172     return webViewForTestingAttachments(CGSizeMake(500, 500));
173 }
174
175 static NSData *testZIPData()
176 {
177     NSURL *zipFileURL = [[NSBundle mainBundle] URLForResource:@"compressed-files" withExtension:@"zip" subdirectory:@"TestWebKitAPI.resources"];
178     return [NSData dataWithContentsOfURL:zipFileURL];
179 }
180
181 static NSData *testHTMLData()
182 {
183     return [@"<a href='#'>This is some HTML data</a>" dataUsingEncoding:NSUTF8StringEncoding];
184 }
185
186 static NSURL *testImageFileURL()
187 {
188     return [[NSBundle mainBundle] URLForResource:@"icon" withExtension:@"png" subdirectory:@"TestWebKitAPI.resources"];
189 }
190
191 static NSData *testImageData()
192 {
193     return [NSData dataWithContentsOfURL:testImageFileURL()];
194 }
195
196 static NSURL *testPDFFileURL()
197 {
198     return [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"pdf" subdirectory:@"TestWebKitAPI.resources"];
199 }
200
201 static NSData *testPDFData()
202 {
203     return [NSData dataWithContentsOfURL:testPDFFileURL()];
204 }
205
206 @implementation TestWKWebView (AttachmentTesting)
207
208 - (NSArray<NSString *> *)tagsInBody
209 {
210     return [self objectByEvaluatingJavaScript:@"Array.from(document.body.getElementsByTagName('*')).map(e => e.tagName)"];
211 }
212
213 - (void)expectElementTagsInOrder:(NSArray<NSString *> *)tagNames
214 {
215     auto remainingTags = adoptNS([tagNames mutableCopy]);
216     NSArray<NSString *> *tagsInBody = self.tagsInBody;
217     for (NSString *tag in tagsInBody.reverseObjectEnumerator) {
218         if ([tag isEqualToString:[remainingTags lastObject]])
219             [remainingTags removeLastObject];
220         if (![remainingTags count])
221             break;
222     }
223     EXPECT_EQ([remainingTags count], 0U);
224     if ([remainingTags count])
225         NSLog(@"Expected to find ordered tags: %@ in: %@", tagNames, tagsInBody);
226 }
227
228 - (void)expectElementTag:(NSString *)tagName toComeBefore:(NSString *)otherTagName
229 {
230     [self expectElementTagsInOrder:@[tagName, otherTagName]];
231 }
232
233 - (BOOL)_synchronouslyExecuteEditCommand:(NSString *)command argument:(NSString *)argument
234 {
235     __block bool done = false;
236     __block bool success;
237     [self _executeEditCommand:command argument:argument completion:^(BOOL completionSuccess) {
238         done = true;
239         success = completionSuccess;
240     }];
241     TestWebKitAPI::Util::run(&done);
242     return success;
243 }
244
245 - (_WKAttachment *)synchronouslyInsertAttachmentWithFileWrapper:(NSFileWrapper *)fileWrapper contentType:(NSString *)contentType
246 {
247     __block bool done = false;
248     RetainPtr<_WKAttachment> attachment = [self _insertAttachmentWithFileWrapper:fileWrapper contentType:contentType options:nil completion:^(BOOL) {
249         done = true;
250     }];
251     TestWebKitAPI::Util::run(&done);
252     return attachment.autorelease();
253 }
254
255 - (_WKAttachment *)synchronouslyInsertAttachmentWithFilename:(NSString *)filename contentType:(NSString *)contentType data:(NSData *)data
256 {
257     __block bool done = false;
258     auto fileWrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:data]);
259     if (filename)
260         [fileWrapper setPreferredFilename:filename];
261     RetainPtr<_WKAttachment> attachment = [self _insertAttachmentWithFileWrapper:fileWrapper.get() contentType:contentType options:nil completion:^(BOOL) {
262         done = true;
263     }];
264     TestWebKitAPI::Util::run(&done);
265     return attachment.autorelease();
266 }
267
268 - (CGPoint)attachmentElementMidPoint
269 {
270     __block CGPoint midPoint;
271     __block bool doneEvaluatingScript = false;
272     [self evaluateJavaScript:@"r = document.querySelector('attachment').getBoundingClientRect(); [r.left + r.width / 2, r.top + r.height / 2]" completionHandler:^(NSArray<NSNumber *> *result, NSError *) {
273         midPoint = CGPointMake(result.firstObject.floatValue, result.lastObject.floatValue);
274         doneEvaluatingScript = true;
275     }];
276     TestWebKitAPI::Util::run(&doneEvaluatingScript);
277     return midPoint;
278 }
279
280 - (CGSize)attachmentElementSize
281 {
282     __block CGSize size;
283     __block bool doneEvaluatingScript = false;
284     [self evaluateJavaScript:@"r = document.querySelector('attachment').getBoundingClientRect(); [r.width, r.height]" completionHandler:^(NSArray<NSNumber *> *sizeResult, NSError *) {
285         size = CGSizeMake(sizeResult.firstObject.floatValue, sizeResult.lastObject.floatValue);
286         doneEvaluatingScript = true;
287     }];
288     TestWebKitAPI::Util::run(&doneEvaluatingScript);
289     return size;
290 }
291
292 - (void)waitForAttachmentElementSizeToBecome:(CGSize)expectedSize
293 {
294     while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) {
295         if (CGSizeEqualToSize(self.attachmentElementSize, expectedSize))
296             break;
297     }
298 }
299
300 - (BOOL)hasAttribute:(NSString *)attributeName forQuerySelector:(NSString *)querySelector
301 {
302     return [self stringByEvaluatingJavaScript:[NSString stringWithFormat:@"document.querySelector('%@').hasAttribute('%@')", querySelector, attributeName]].boolValue;
303 }
304
305 - (NSString *)valueOfAttribute:(NSString *)attributeName forQuerySelector:(NSString *)querySelector
306 {
307     return [self stringByEvaluatingJavaScript:[NSString stringWithFormat:@"document.querySelector('%@').getAttribute('%@')", querySelector, attributeName]];
308 }
309
310 - (void)expectUpdatesAfterCommand:(NSString *)command withArgument:(NSString *)argument expectedRemovals:(NSArray<_WKAttachment *> *)removed expectedInsertions:(NSArray<_WKAttachment *> *)inserted
311 {
312     TestWebKitAPI::ObserveAttachmentUpdatesForScope observer(self);
313     EXPECT_TRUE([self _synchronouslyExecuteEditCommand:command argument:argument]);
314     observer.expectAttachmentUpdates(removed, inserted);
315 }
316
317 @end
318
319 @implementation NSData (AttachmentTesting)
320
321 - (NSString *)shortDescription
322 {
323     return [NSString stringWithFormat:@"<%tu bytes>", self.length];
324 }
325
326 @end
327
328 @implementation _WKAttachment (AttachmentTesting)
329
330 - (void)synchronouslySetDisplayOptions:(_WKAttachmentDisplayOptions *)options error:(NSError **)error
331 {
332     __block RetainPtr<NSError> resultError;
333     __block bool done = false;
334     [self setDisplayOptions:options completion:^(NSError *error) {
335         resultError = error;
336         done = true;
337     }];
338
339     TestWebKitAPI::Util::run(&done);
340
341     if (error)
342         *error = resultError.autorelease();
343 }
344
345 - (void)synchronouslySetFileWrapper:(NSFileWrapper *)fileWrapper newContentType:(NSString *)newContentType error:(NSError **)error
346 {
347     __block RetainPtr<NSError> resultError;
348     __block bool done = false;
349
350     [self setFileWrapper:fileWrapper contentType:newContentType completion:^(NSError *error) {
351         resultError = error;
352         done = true;
353     }];
354
355     TestWebKitAPI::Util::run(&done);
356
357     if (error)
358         *error = resultError.autorelease();
359 }
360
361 - (void)synchronouslySetData:(NSData *)data newContentType:(NSString *)newContentType newFilename:(NSString *)newFilename error:(NSError **)error
362 {
363     __block RetainPtr<NSError> resultError;
364     __block bool done = false;
365     auto fileWrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:data]);
366     if (newFilename)
367         [fileWrapper setPreferredFilename:newFilename];
368
369     [self setFileWrapper:fileWrapper.get() contentType:newContentType completion:^(NSError *error) {
370         resultError = error;
371         done = true;
372     }];
373
374     TestWebKitAPI::Util::run(&done);
375
376     if (error)
377         *error = resultError.autorelease();
378 }
379
380 - (void)expectRequestedDataToBe:(NSData *)expectedData
381 {
382     NSError *requestError = nil;
383     _WKAttachmentInfo *info = self.info;
384
385     BOOL observedDataIsEqualToExpectedData = info.data == expectedData || [info.data isEqualToData:expectedData];
386     EXPECT_TRUE(observedDataIsEqualToExpectedData);
387     if (!observedDataIsEqualToExpectedData) {
388         NSLog(@"Expected data: %@ but observed: %@ for %@", [expectedData shortDescription], [info.data shortDescription], self);
389         NSLog(@"Observed error: %@ while reading data for %@", requestError, self);
390     }
391 }
392
393 @end
394
395 #pragma mark - Platform testing helper functions
396
397 #if PLATFORM(IOS)
398
399 typedef void(^ItemProviderDataLoadHandler)(NSData *, NSError *);
400
401 @implementation NSItemProvider (AttachmentTesting)
402
403 - (void)registerData:(NSData *)data type:(NSString *)type
404 {
405     [self registerDataRepresentationForTypeIdentifier:type visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[protectedData = retainPtr(data)] (ItemProviderDataLoadHandler completionHandler) -> NSProgress * {
406         completionHandler(protectedData.get(), nil);
407         return nil;
408     }];
409 }
410
411 - (void)expectType:(NSString *)type withData:(NSData *)expectedData
412 {
413     BOOL containsType = [self.registeredTypeIdentifiers containsObject:type];
414     EXPECT_TRUE(containsType);
415     if (!containsType) {
416         NSLog(@"Expected: %@ to contain %@", self, type);
417         return;
418     }
419
420     __block bool done = false;
421     [self loadDataRepresentationForTypeIdentifier:type completionHandler:^(NSData *observedData, NSError *error) {
422         EXPECT_TRUE([observedData isEqualToData:expectedData]);
423         if (![observedData isEqualToData:expectedData])
424             NSLog(@"Expected data: <%tu bytes> to be equal to data: <%tu bytes>", observedData.length, expectedData.length);
425         EXPECT_TRUE(!error);
426         if (error)
427             NSLog(@"Encountered error when loading data: %@", error);
428         done = true;
429     }];
430     TestWebKitAPI::Util::run(&done);
431 }
432
433 @end
434
435 #endif // PLATFORM(IOS)
436
437 void platformCopyRichTextWithMultipleAttachments()
438 {
439     auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(NSString *)kUTTypePNG]);
440     auto pdf = adoptNS([[NSTextAttachment alloc] initWithData:testPDFData() ofType:(NSString *)kUTTypePDF]);
441     auto zip = adoptNS([[NSTextAttachment alloc] initWithData:testZIPData() ofType:(NSString *)kUTTypeZipArchive]);
442
443     auto richText = adoptNS([[NSMutableAttributedString alloc] init]);
444     [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:image.get()]];
445     [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:pdf.get()]];
446     [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:zip.get()]];
447
448 #if PLATFORM(MAC)
449     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
450     [pasteboard clearContents];
451     [pasteboard writeObjects:@[ richText.get() ]];
452 #elif PLATFORM(IOS)
453     auto item = adoptNS([[NSItemProvider alloc] init]);
454     [item registerObject:richText.get() visibility:NSItemProviderRepresentationVisibilityAll];
455     [UIPasteboard generalPasteboard].itemProviders = @[ item.get() ];
456 #endif
457 }
458
459 void platformCopyRichTextWithImage()
460 {
461     auto richText = adoptNS([[NSMutableAttributedString alloc] init]);
462     auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(NSString *)kUTTypePNG]);
463
464     [richText appendAttributedString:[[[NSAttributedString alloc] initWithString:@"Lorem ipsum "] autorelease]];
465     [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:image.get()]];
466     [richText appendAttributedString:[[[NSAttributedString alloc] initWithString:@" dolor sit amet."] autorelease]];
467
468 #if PLATFORM(MAC)
469     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
470     [pasteboard clearContents];
471     [pasteboard writeObjects:@[ richText.get() ]];
472 #elif PLATFORM(IOS)
473     auto item = adoptNS([[NSItemProvider alloc] init]);
474     [item registerObject:richText.get() visibility:NSItemProviderRepresentationVisibilityAll];
475     [UIPasteboard generalPasteboard].itemProviders = @[ item.get() ];
476 #endif
477 }
478
479 void platformCopyPNG()
480 {
481 #if PLATFORM(MAC)
482     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
483     [pasteboard declareTypes:@[NSPasteboardTypePNG] owner:nil];
484     [pasteboard setData:testImageData() forType:NSPasteboardTypePNG];
485 #elif PLATFORM(IOS)
486     UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
487     auto item = adoptNS([[UIItemProvider alloc] init]);
488     [item setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
489     [item registerData:testImageData() type:(NSString *)kUTTypePNG];
490     pasteboard.itemProviders = @[ item.get() ];
491 #endif
492 }
493
494 #if PLATFORM(MAC)
495 typedef NSImage PlatformImage;
496 #else
497 typedef UIImage PlatformImage;
498 #endif
499
500 PlatformImage *platformImageWithData(NSData *data)
501 {
502 #if PLATFORM(MAC)
503     return [[[NSImage alloc] initWithData:data] autorelease];
504 #else
505     return [UIImage imageWithData:data];
506 #endif
507 }
508
509 namespace TestWebKitAPI {
510
511 #pragma mark - Platform-agnostic tests
512
513 TEST(WKAttachmentTests, AttachmentElementInsertion)
514 {
515     auto webView = webViewForTestingAttachments();
516     RetainPtr<_WKAttachment> firstAttachment;
517     RetainPtr<_WKAttachment> secondAttachment;
518     {
519         ObserveAttachmentUpdatesForScope observer(webView.get());
520         // Use the given content type for the attachment element's type.
521         firstAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo" contentType:@"text/html" data:testHTMLData()];
522         EXPECT_WK_STREQ(@"foo", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
523         EXPECT_WK_STREQ(@"text/html", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
524         EXPECT_WK_STREQ(@"38 bytes", [webView valueOfAttribute:@"subtitle" forQuerySelector:@"attachment"]);
525         observer.expectAttachmentUpdates(@[ ], @[ firstAttachment.get() ]);
526     }
527
528     _WKAttachmentInfo *info = [firstAttachment info];
529     EXPECT_TRUE([info.data isEqualToData:testHTMLData()]);
530     EXPECT_TRUE([info.contentType isEqualToString:@"text/html"]);
531     EXPECT_TRUE([info.name isEqualToString:@"foo"]);
532     EXPECT_EQ(info.filePath.length, 0U);
533
534     {
535         ObserveAttachmentUpdatesForScope scope(webView.get());
536         // Since no content type is explicitly specified, compute it from the file extension.
537         [webView _executeEditCommand:@"DeleteBackward" argument:nil completion:nil];
538         secondAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"bar.png" contentType:nil data:testImageData()];
539         EXPECT_WK_STREQ(@"bar.png", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
540         EXPECT_WK_STREQ(@"image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
541         EXPECT_WK_STREQ(@"37 KB", [webView valueOfAttribute:@"subtitle" forQuerySelector:@"attachment"]);
542         scope.expectAttachmentUpdates(@[ firstAttachment.get() ], @[ secondAttachment.get() ]);
543     }
544
545     [firstAttachment expectRequestedDataToBe:testHTMLData()];
546     [secondAttachment expectRequestedDataToBe:testImageData()];
547     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
548     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
549     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentid" forQuerySelector:@"attachment"]);
550 }
551
552 TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingAndDeletingNewline)
553 {
554     auto webView = webViewForTestingAttachments();
555     RetainPtr<_WKAttachment> attachment;
556     {
557         ObserveAttachmentUpdatesForScope observer(webView.get());
558         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData()];
559         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
560     }
561     [webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
562     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
563     [webView stringByEvaluatingJavaScript:@"getSelection().collapse(document.body)"];
564     [webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
565
566     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
567
568     _WKAttachmentInfo *info = [attachment info];
569     EXPECT_TRUE([info.data isEqualToData:testHTMLData()]);
570     EXPECT_TRUE([info.contentType isEqualToString:@"text/plain"]);
571     EXPECT_TRUE([info.name isEqualToString:@"foo.txt"]);
572     EXPECT_EQ(info.filePath.length, 0U);
573
574     [webView expectUpdatesAfterCommand:@"DeleteForward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
575     [attachment expectRequestedDataToBe:testHTMLData()];
576 }
577
578 TEST(WKAttachmentTests, AttachmentUpdatesWhenUndoingAndRedoing)
579 {
580     auto webView = webViewForTestingAttachments();
581     RetainPtr<NSData> htmlData = testHTMLData();
582     RetainPtr<_WKAttachment> attachment;
583     {
584         ObserveAttachmentUpdatesForScope observer(webView.get());
585         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData()];
586         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
587     }
588     [webView expectUpdatesAfterCommand:@"Undo" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
589     [attachment expectRequestedDataToBe:htmlData.get()];
590
591     [webView expectUpdatesAfterCommand:@"Redo" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
592     [attachment expectRequestedDataToBe:htmlData.get()];
593
594     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
595     [attachment expectRequestedDataToBe:htmlData.get()];
596
597     [webView expectUpdatesAfterCommand:@"Undo" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
598     [attachment expectRequestedDataToBe:htmlData.get()];
599
600     [webView expectUpdatesAfterCommand:@"Redo" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
601     [attachment expectRequestedDataToBe:htmlData.get()];
602 }
603
604 TEST(WKAttachmentTests, AttachmentUpdatesWhenChangingFontStyles)
605 {
606     auto webView = webViewForTestingAttachments();
607     RetainPtr<_WKAttachment> attachment;
608     [webView _synchronouslyExecuteEditCommand:@"InsertText" argument:@"Hello"];
609     {
610         ObserveAttachmentUpdatesForScope observer(webView.get());
611         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData()];
612         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
613     }
614     [webView expectUpdatesAfterCommand:@"InsertText" withArgument:@"World" expectedRemovals:@[] expectedInsertions:@[]];
615     [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
616     [webView expectUpdatesAfterCommand:@"ToggleBold" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
617     [webView expectUpdatesAfterCommand:@"ToggleItalic" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
618     [webView expectUpdatesAfterCommand:@"ToggleUnderline" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
619     [attachment expectRequestedDataToBe:testHTMLData()];
620     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
621     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
622     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentid" forQuerySelector:@"attachment"]);
623
624     // Inserting text should delete the current selection, removing the attachment in the process.
625     [webView expectUpdatesAfterCommand:@"InsertText" withArgument:@"foo" expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
626     [attachment expectRequestedDataToBe:testHTMLData()];
627 }
628
629 TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingLists)
630 {
631     auto webView = webViewForTestingAttachments();
632     RetainPtr<_WKAttachment> attachment;
633     {
634         ObserveAttachmentUpdatesForScope observer(webView.get());
635         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData()];
636         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
637     }
638     [webView expectUpdatesAfterCommand:@"InsertOrderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
639     // This edit command behaves more like a "toggle", and will actually break us out of the list we just inserted.
640     [webView expectUpdatesAfterCommand:@"InsertOrderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
641     [webView expectUpdatesAfterCommand:@"InsertUnorderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
642     [webView expectUpdatesAfterCommand:@"InsertUnorderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
643     [attachment expectRequestedDataToBe:testHTMLData()];
644     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
645     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
646     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentid" forQuerySelector:@"attachment"]);
647 }
648
649 TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingRichMarkup)
650 {
651     auto webView = webViewForTestingAttachments();
652     RetainPtr<_WKAttachment> attachment;
653     {
654         ObserveAttachmentUpdatesForScope observer(webView.get());
655         [webView _synchronouslyExecuteEditCommand:@"InsertHTML" argument:@"<div><strong><attachment src='cid:123-4567' title='a'></attachment></strong></div>"];
656         attachment = observer.observer().inserted[0];
657         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
658         observer.expectSourceForIdentifier(@"cid:123-4567", [attachment uniqueIdentifier]);
659     }
660     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
661     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
662     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentid" forQuerySelector:@"attachment"]);
663     EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').uniqueIdentifier"]);
664     {
665         ObserveAttachmentUpdatesForScope observer(webView.get());
666         [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').remove()"];
667         [webView waitForNextPresentationUpdate];
668         observer.expectAttachmentUpdates(@[attachment.get()], @[ ]);
669     }
670     [attachment expectRequestedDataToBe:nil];
671 }
672
673 TEST(WKAttachmentTests, AttachmentUpdatesWhenCuttingAndPasting)
674 {
675     auto webView = webViewForTestingAttachments();
676     RetainPtr<_WKAttachment> attachment;
677     {
678         ObserveAttachmentUpdatesForScope observer(webView.get());
679         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData()];
680         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
681     }
682     [attachment expectRequestedDataToBe:testHTMLData()];
683     EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').uniqueIdentifier"]);
684     [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
685     {
686         ObserveAttachmentUpdatesForScope observer(webView.get());
687         [webView _synchronouslyExecuteEditCommand:@"Cut" argument:nil];
688         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
689     }
690     [attachment expectRequestedDataToBe:testHTMLData()];
691     {
692         ObserveAttachmentUpdatesForScope observer(webView.get());
693         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
694         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
695     }
696     [attachment expectRequestedDataToBe:testHTMLData()];
697     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
698     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
699     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentid" forQuerySelector:@"attachment"]);
700 }
701
702 TEST(WKAttachmentTests, AttachmentDataForEmptyFile)
703 {
704     auto webView = webViewForTestingAttachments();
705     RetainPtr<_WKAttachment> attachment;
706     {
707         ObserveAttachmentUpdatesForScope observer(webView.get());
708         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"empty.txt" contentType:@"text/plain" data:[NSData data]];
709         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
710     }
711     [attachment expectRequestedDataToBe:[NSData data]];
712     EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').uniqueIdentifier"]);
713     {
714         ObserveAttachmentUpdatesForScope scope(webView.get());
715         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
716         scope.expectAttachmentUpdates(@[attachment.get()], @[]);
717     }
718     [attachment expectRequestedDataToBe:[NSData data]];
719 }
720
721 TEST(WKAttachmentTests, ChangeAttachmentDataAndFileInformation)
722 {
723     auto webView = webViewForTestingAttachments();
724     RetainPtr<_WKAttachment> attachment;
725     {
726         RetainPtr<NSData> pdfData = testPDFData();
727         ObserveAttachmentUpdatesForScope observer(webView.get());
728         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"test.pdf" contentType:@"application/pdf" data:pdfData.get()];
729         [attachment expectRequestedDataToBe:pdfData.get()];
730         EXPECT_WK_STREQ(@"test.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
731         EXPECT_WK_STREQ(@"application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
732         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
733     }
734     {
735         RetainPtr<NSData> imageData = testImageData();
736         ObserveAttachmentUpdatesForScope observer(webView.get());
737         [attachment synchronouslySetData:imageData.get() newContentType:@"image/png" newFilename:@"icon.png" error:nil];
738         [attachment expectRequestedDataToBe:imageData.get()];
739         EXPECT_WK_STREQ(@"icon.png", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
740         EXPECT_WK_STREQ(@"image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
741         observer.expectAttachmentUpdates(@[], @[]);
742     }
743     {
744         RetainPtr<NSData> textData = testHTMLData();
745         ObserveAttachmentUpdatesForScope observer(webView.get());
746         // The new content type should be inferred from the file name.
747         [attachment synchronouslySetData:textData.get() newContentType:nil newFilename:@"foo.txt" error:nil];
748         [attachment expectRequestedDataToBe:textData.get()];
749         EXPECT_WK_STREQ(@"foo.txt", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
750         EXPECT_WK_STREQ(@"text/plain", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
751         observer.expectAttachmentUpdates(@[], @[]);
752     }
753     {
754         RetainPtr<NSData> secondTextData = [@"Hello world" dataUsingEncoding:NSUTF8StringEncoding];
755         ObserveAttachmentUpdatesForScope observer(webView.get());
756         // Both the previous file name and type should be inferred.
757         [attachment synchronouslySetData:secondTextData.get() newContentType:nil newFilename:nil error:nil];
758         [attachment expectRequestedDataToBe:secondTextData.get()];
759         EXPECT_WK_STREQ(@"foo.txt", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
760         EXPECT_WK_STREQ(@"text/plain", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
761         observer.expectAttachmentUpdates(@[], @[]);
762     }
763     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
764 }
765
766 TEST(WKAttachmentTests, InsertPastedImageAsAttachment)
767 {
768     platformCopyPNG();
769
770     RetainPtr<_WKAttachment> attachment;
771     auto webView = webViewForTestingAttachments();
772     {
773         ObserveAttachmentUpdatesForScope observer(webView.get());
774         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
775         EXPECT_EQ(1U, observer.observer().inserted.count);
776         attachment = observer.observer().inserted[0];
777     }
778
779     auto size = platformImageWithData([attachment info].data).size;
780     EXPECT_EQ(215., size.width);
781     EXPECT_EQ(174., size.height);
782     EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').uniqueIdentifier"]);
783
784     {
785         ObserveAttachmentUpdatesForScope observer(webView.get());
786         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
787         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
788         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
789     }
790 }
791
792 TEST(WKAttachmentTests, InsertPastedAttributedStringContainingImage)
793 {
794     platformCopyRichTextWithImage();
795
796     RetainPtr<_WKAttachment> attachment;
797     auto webView = webViewForTestingAttachments();
798     {
799         ObserveAttachmentUpdatesForScope observer(webView.get());
800         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
801         EXPECT_EQ(0U, observer.observer().removed.count);
802         EXPECT_EQ(1U, observer.observer().inserted.count);
803         attachment = observer.observer().inserted[0];
804     }
805
806     [attachment expectRequestedDataToBe:testImageData()];
807     EXPECT_WK_STREQ("Lorem ipsum  dolor sit amet.", [webView stringByEvaluatingJavaScript:@"document.body.textContent"]);
808     EXPECT_WK_STREQ("image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
809     EXPECT_WK_STREQ([attachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').uniqueIdentifier"]);
810
811     {
812         ObserveAttachmentUpdatesForScope observer(webView.get());
813         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
814         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
815         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
816     }
817 }
818
819 TEST(WKAttachmentTests, InsertPastedAttributedStringContainingMultipleAttachments)
820 {
821     platformCopyRichTextWithMultipleAttachments();
822
823     RetainPtr<_WKAttachment> imageAttachment;
824     RetainPtr<_WKAttachment> zipAttachment;
825     RetainPtr<_WKAttachment> pdfAttachment;
826     auto webView = webViewForTestingAttachments();
827     {
828         ObserveAttachmentUpdatesForScope observer(webView.get());
829         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
830         EXPECT_EQ(0U, observer.observer().removed.count);
831         EXPECT_EQ(3U, observer.observer().inserted.count);
832         for (_WKAttachment *attachment in observer.observer().inserted) {
833             NSData *data = attachment.info.data;
834             if ([data isEqualToData:testZIPData()])
835                 zipAttachment = attachment;
836             else if ([data isEqualToData:testPDFData()])
837                 pdfAttachment = attachment;
838             else if ([data isEqualToData:testImageData()])
839                 imageAttachment = attachment;
840         }
841     }
842
843     EXPECT_TRUE(zipAttachment && imageAttachment && pdfAttachment);
844     EXPECT_EQ(3, [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment').length"].integerValue);
845     EXPECT_WK_STREQ("image/png", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
846     EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]);
847
848     NSString *zipAttachmentType = [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[2].getAttribute('type')"];
849 #if USES_MODERN_ATTRIBUTED_STRING_CONVERSION
850     EXPECT_WK_STREQ("application/zip", zipAttachmentType);
851 #else
852     EXPECT_WK_STREQ("application/octet-stream", zipAttachmentType);
853 #endif
854
855     EXPECT_WK_STREQ([imageAttachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].uniqueIdentifier"]);
856     EXPECT_WK_STREQ([pdfAttachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].uniqueIdentifier"]);
857     EXPECT_WK_STREQ([zipAttachment uniqueIdentifier], [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[2].uniqueIdentifier"]);
858
859     {
860         ObserveAttachmentUpdatesForScope observer(webView.get());
861         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
862         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
863         NSArray<_WKAttachment *> *removedAttachments = [observer.observer() removed];
864         EXPECT_EQ(3U, removedAttachments.count);
865         EXPECT_TRUE([removedAttachments containsObject:zipAttachment.get()]);
866         EXPECT_TRUE([removedAttachments containsObject:imageAttachment.get()]);
867         EXPECT_TRUE([removedAttachments containsObject:pdfAttachment.get()]);
868     }
869 }
870
871 TEST(WKAttachmentTests, DoNotInsertDataURLImagesAsAttachments)
872 {
873     auto webContentSourceView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]);
874     [webContentSourceView synchronouslyLoadTestPageNamed:@"apple-data-url"];
875     [webContentSourceView selectAll:nil];
876     [webContentSourceView _synchronouslyExecuteEditCommand:@"Copy" argument:nil];
877
878     auto webView = webViewForTestingAttachments();
879     {
880         ObserveAttachmentUpdatesForScope observer(webView.get());
881         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
882         EXPECT_EQ(0U, observer.observer().inserted.count);
883     }
884
885     EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"Boolean(document.querySelector('attachment'))"].boolValue);
886     EXPECT_EQ(1990, [webView stringByEvaluatingJavaScript:@"document.querySelector('img').src.length"].integerValue);
887     EXPECT_WK_STREQ("This is an apple", [webView stringByEvaluatingJavaScript:@"document.body.textContent"]);
888 }
889
890 TEST(WKAttachmentTests, InsertAndRemoveDuplicateAttachment)
891 {
892     auto webView = webViewForTestingAttachments();
893     RetainPtr<NSData> data = testHTMLData();
894     auto fileWrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:data.get()]);
895     RetainPtr<_WKAttachment> originalAttachment;
896     RetainPtr<_WKAttachment> pastedAttachment;
897     {
898         ObserveAttachmentUpdatesForScope observer(webView.get());
899         originalAttachment = [webView synchronouslyInsertAttachmentWithFileWrapper:fileWrapper.get() contentType:@"text/plain"];
900         EXPECT_EQ(0U, observer.observer().removed.count);
901         observer.expectAttachmentUpdates(@[], @[originalAttachment.get()]);
902     }
903     [webView selectAll:nil];
904     [webView _executeEditCommand:@"Copy" argument:nil completion:nil];
905     [webView evaluateJavaScript:@"getSelection().collapseToEnd()" completionHandler:nil];
906     {
907         ObserveAttachmentUpdatesForScope observer(webView.get());
908         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
909         EXPECT_EQ(0U, observer.observer().removed.count);
910         EXPECT_EQ(1U, observer.observer().inserted.count);
911         pastedAttachment = observer.observer().inserted.firstObject;
912         EXPECT_FALSE([pastedAttachment isEqual:originalAttachment.get()]);
913     }
914     {
915         ObserveAttachmentUpdatesForScope observer(webView.get());
916         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
917         observer.expectAttachmentUpdates(@[pastedAttachment.get()], @[]);
918         [originalAttachment expectRequestedDataToBe:data.get()];
919     }
920     {
921         ObserveAttachmentUpdatesForScope observer(webView.get());
922         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
923         observer.expectAttachmentUpdates(@[originalAttachment.get()], @[]);
924     }
925
926     EXPECT_FALSE([originalAttachment isEqual:pastedAttachment.get()]);
927     EXPECT_TRUE([[originalAttachment info].fileWrapper isEqual:[pastedAttachment info].fileWrapper]);
928     EXPECT_TRUE([[originalAttachment info].fileWrapper isEqual:fileWrapper.get()]);
929 }
930
931 TEST(WKAttachmentTests, InsertDuplicateAttachmentAndUpdateData)
932 {
933     auto webView = webViewForTestingAttachments();
934     auto originalData = retainPtr(testHTMLData());
935     auto fileWrapper = adoptNS([[NSFileWrapper alloc] initRegularFileWithContents:originalData.get()]);
936     RetainPtr<_WKAttachment> originalAttachment;
937     RetainPtr<_WKAttachment> pastedAttachment;
938     {
939         ObserveAttachmentUpdatesForScope observer(webView.get());
940         originalAttachment = [webView synchronouslyInsertAttachmentWithFileWrapper:fileWrapper.get() contentType:@"text/plain"];
941         EXPECT_EQ(0U, observer.observer().removed.count);
942         observer.expectAttachmentUpdates(@[], @[originalAttachment.get()]);
943     }
944     [webView selectAll:nil];
945     [webView _executeEditCommand:@"Copy" argument:nil completion:nil];
946     [webView evaluateJavaScript:@"getSelection().collapseToEnd()" completionHandler:nil];
947     {
948         ObserveAttachmentUpdatesForScope observer(webView.get());
949         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
950         EXPECT_EQ(0U, observer.observer().removed.count);
951         EXPECT_EQ(1U, observer.observer().inserted.count);
952         pastedAttachment = observer.observer().inserted.firstObject;
953         EXPECT_FALSE([pastedAttachment isEqual:originalAttachment.get()]);
954     }
955     auto updatedData = retainPtr([@"HELLO WORLD" dataUsingEncoding:NSUTF8StringEncoding]);
956     [originalAttachment synchronouslySetData:updatedData.get() newContentType:nil newFilename:nil error:nil];
957     [originalAttachment expectRequestedDataToBe:updatedData.get()];
958     [pastedAttachment expectRequestedDataToBe:originalData.get()];
959
960     EXPECT_FALSE([originalAttachment isEqual:pastedAttachment.get()]);
961     EXPECT_FALSE([[originalAttachment info].fileWrapper isEqual:[pastedAttachment info].fileWrapper]);
962     EXPECT_FALSE([[originalAttachment info].fileWrapper isEqual:fileWrapper.get()]);
963 }
964
965 TEST(WKAttachmentTests, InjectedBundleReplaceURLsWhenPastingAttributedString)
966 {
967     platformCopyRichTextWithMultipleAttachments();
968
969     auto configuration = retainPtr([WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"BundleEditingDelegatePlugIn"]);
970     [[configuration processPool] _setObject:@{ @"image/png" : @"cid:foo-bar" } forBundleParameter:@"MIMETypeToReplacementURLMap"];
971     auto webView = webViewForTestingAttachments(CGSizeMake(500, 500), configuration.get());
972     {
973         ObserveAttachmentUpdatesForScope observer(webView.get());
974         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
975         EXPECT_EQ(2U, observer.observer().inserted.count);
976     }
977     [webView expectElementTagsInOrder:@[ @"IMG", @"ATTACHMENT", @"ATTACHMENT" ]];
978     EXPECT_WK_STREQ("cid:foo-bar", [webView valueOfAttribute:@"src" forQuerySelector:@"img"]);
979 }
980
981 TEST(WKAttachmentTests, InjectedBundleReplaceURLWhenPastingImage)
982 {
983     platformCopyPNG();
984
985     NSString *replacementURL = @"cid:foo-bar";
986     auto configuration = retainPtr([WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"BundleEditingDelegatePlugIn"]);
987     [[configuration processPool] _setObject:@{ @"image/tiff" : replacementURL, @"image/png" : replacementURL } forBundleParameter:@"MIMETypeToReplacementURLMap"];
988     auto webView = webViewForTestingAttachments(CGSizeMake(500, 500), configuration.get());
989     {
990         ObserveAttachmentUpdatesForScope observer(webView.get());
991         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
992         EXPECT_EQ(0U, observer.observer().inserted.count);
993     }
994     EXPECT_WK_STREQ("cid:foo-bar", [webView valueOfAttribute:@"src" forQuerySelector:@"img"]);
995 }
996
997 TEST(WKAttachmentTests, InsertAttachmentUsingFileWrapperWithFilePath)
998 {
999     auto webView = webViewForTestingAttachments();
1000     auto originalFileWrapper = adoptNS([[NSFileWrapper alloc] initWithURL:testImageFileURL() options:0 error:nil]);
1001     RetainPtr<_WKAttachment> attachment;
1002     {
1003         ObserveAttachmentUpdatesForScope observer(webView.get());
1004         attachment = [webView synchronouslyInsertAttachmentWithFileWrapper:originalFileWrapper.get() contentType:nil];
1005         observer.expectAttachmentUpdates(@[ ], @[ attachment.get() ]);
1006     }
1007
1008     _WKAttachmentInfo *infoBeforeUpdate = [attachment info];
1009     EXPECT_WK_STREQ("image/png", infoBeforeUpdate.contentType);
1010     EXPECT_WK_STREQ("icon.png", infoBeforeUpdate.name);
1011     EXPECT_TRUE([originalFileWrapper isEqual:infoBeforeUpdate.fileWrapper]);
1012     [attachment expectRequestedDataToBe:testImageData()];
1013
1014     auto newFileWrapper = adoptNS([[NSFileWrapper alloc] initWithURL:testPDFFileURL() options:0 error:nil]);
1015     {
1016         ObserveAttachmentUpdatesForScope observer(webView.get());
1017         [attachment synchronouslySetFileWrapper:newFileWrapper.get() newContentType:nil error:nil];
1018         observer.expectAttachmentUpdates(@[ ], @[ ]);
1019     }
1020
1021     _WKAttachmentInfo *infoAfterUpdate = [attachment info];
1022     EXPECT_WK_STREQ("application/pdf", infoAfterUpdate.contentType);
1023     EXPECT_WK_STREQ("test.pdf", infoAfterUpdate.name);
1024     EXPECT_TRUE([newFileWrapper isEqual:infoAfterUpdate.fileWrapper]);
1025     [attachment expectRequestedDataToBe:testPDFData()];
1026 }
1027
1028 TEST(WKAttachmentTests, InvalidateAttachmentsAfterMainFrameNavigation)
1029 {
1030     auto webView = webViewForTestingAttachments();
1031     RetainPtr<_WKAttachment> pdfAttachment;
1032     RetainPtr<_WKAttachment> htmlAttachment;
1033     {
1034         ObserveAttachmentUpdatesForScope insertionObserver(webView.get());
1035         pdfAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"attachment.pdf" contentType:@"application/pdf" data:testPDFData()];
1036         htmlAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"index.html" contentType:@"text/html" data:testHTMLData()];
1037         insertionObserver.expectAttachmentUpdates(@[ ], @[ pdfAttachment.get(), htmlAttachment.get() ]);
1038         EXPECT_TRUE([pdfAttachment isConnected]);
1039         EXPECT_TRUE([htmlAttachment isConnected]);
1040     }
1041
1042     ObserveAttachmentUpdatesForScope removalObserver(webView.get());
1043     [webView synchronouslyLoadTestPageNamed:@"simple"];
1044     removalObserver.expectAttachmentUpdates(@[ pdfAttachment.get(), htmlAttachment.get() ], @[ ]);
1045     EXPECT_FALSE([pdfAttachment isConnected]);
1046     EXPECT_FALSE([htmlAttachment isConnected]);
1047     [pdfAttachment expectRequestedDataToBe:nil];
1048     [htmlAttachment expectRequestedDataToBe:nil];
1049 }
1050
1051 TEST(WKAttachmentTests, InvalidateAttachmentsAfterWebProcessTermination)
1052 {
1053     auto webView = webViewForTestingAttachments();
1054     RetainPtr<_WKAttachment> pdfAttachment;
1055     RetainPtr<_WKAttachment> htmlAttachment;
1056     {
1057         ObserveAttachmentUpdatesForScope insertionObserver(webView.get());
1058         pdfAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"attachment.pdf" contentType:@"application/pdf" data:testPDFData()];
1059         htmlAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"index.html" contentType:@"text/html" data:testHTMLData()];
1060         insertionObserver.expectAttachmentUpdates(@[ ], @[ pdfAttachment.get(), htmlAttachment.get() ]);
1061         EXPECT_TRUE([pdfAttachment isConnected]);
1062         EXPECT_TRUE([htmlAttachment isConnected]);
1063     }
1064     {
1065         ObserveAttachmentUpdatesForScope removalObserver(webView.get());
1066         [webView stringByEvaluatingJavaScript:@"getSelection().collapseToEnd()"];
1067         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
1068         removalObserver.expectAttachmentUpdates(@[ htmlAttachment.get() ], @[ ]);
1069         EXPECT_TRUE([pdfAttachment isConnected]);
1070         EXPECT_FALSE([htmlAttachment isConnected]);
1071         [htmlAttachment expectRequestedDataToBe:testHTMLData()];
1072     }
1073
1074     __block bool webProcessTerminated = false;
1075     auto navigationDelegate = adoptNS([[TestNavigationDelegate alloc] init]);
1076     [webView setNavigationDelegate:navigationDelegate.get()];
1077     [navigationDelegate setWebContentProcessDidTerminate:^(WKWebView *) {
1078         webProcessTerminated = true;
1079     }];
1080
1081     ObserveAttachmentUpdatesForScope observer(webView.get());
1082     [webView _killWebContentProcess];
1083     TestWebKitAPI::Util::run(&webProcessTerminated);
1084
1085     observer.expectAttachmentUpdates(@[ pdfAttachment.get() ], @[ ]);
1086     EXPECT_FALSE([pdfAttachment isConnected]);
1087     EXPECT_FALSE([htmlAttachment isConnected]);
1088     [pdfAttachment expectRequestedDataToBe:nil];
1089     [htmlAttachment expectRequestedDataToBe:nil];
1090 }
1091
1092 TEST(WKAttachmentTests, MoveAttachmentElementAsIconByDragging)
1093 {
1094     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
1095     [configuration _setAttachmentElementEnabled:YES];
1096     auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:NSMakeRect(0, 0, 400, 400) configuration:configuration.get()]);
1097     TestWKWebView *webView = [simulator webView];
1098     [webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
1099
1100     auto data = retainPtr(testPDFData());
1101     auto attachment = retainPtr([webView synchronouslyInsertAttachmentWithFilename:@"document.pdf" contentType:@"application/pdf" data:data.get()]);
1102
1103     [webView _executeEditCommand:@"InsertParagraph" argument:nil completion:nil];
1104     [webView _executeEditCommand:@"InsertHTML" argument:@"<strong>text</strong>" completion:nil];
1105     [webView _synchronouslyExecuteEditCommand:@"InsertParagraph" argument:nil];
1106     [webView expectElementTag:@"ATTACHMENT" toComeBefore:@"STRONG"];
1107
1108     // Drag the attachment element to somewhere below the strong text.
1109     [simulator runFrom:[webView attachmentElementMidPoint] to:CGPointMake(50, 300)];
1110
1111     EXPECT_EQ([simulator insertedAttachments].count, [simulator removedAttachments].count);
1112     [attachment expectRequestedDataToBe:data.get()];
1113     EXPECT_WK_STREQ("document.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
1114     EXPECT_WK_STREQ("application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
1115
1116     [webView expectElementTag:@"STRONG" toComeBefore:@"ATTACHMENT"];
1117     [simulator endDataTransfer];
1118 }
1119
1120 #pragma mark - Platform-specific tests
1121
1122 #if PLATFORM(MAC)
1123
1124 TEST(WKAttachmentTestsMac, InsertPastedFileURLsAsAttachments)
1125 {
1126     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
1127     [pasteboard clearContents];
1128     [pasteboard declareTypes:@[NSFilenamesPboardType] owner:nil];
1129     [pasteboard setPropertyList:@[testPDFFileURL().path, testImageFileURL().path] forType:NSFilenamesPboardType];
1130
1131     RetainPtr<NSArray<_WKAttachment *>> insertedAttachments;
1132     auto webView = webViewForTestingAttachments();
1133     {
1134         ObserveAttachmentUpdatesForScope observer(webView.get());
1135         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
1136         insertedAttachments = [observer.observer() inserted];
1137         EXPECT_EQ(2U, [insertedAttachments count]);
1138     }
1139
1140     NSArray<NSData *> *expectedAttachmentData = @[ testPDFData(), testImageData() ];
1141     EXPECT_TRUE([expectedAttachmentData containsObject:[insertedAttachments firstObject].info.data]);
1142     EXPECT_TRUE([expectedAttachmentData containsObject:[insertedAttachments lastObject].info.data]);
1143     EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
1144     EXPECT_WK_STREQ("test.pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('title')"]);
1145     EXPECT_WK_STREQ("image/png", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]);
1146     EXPECT_WK_STREQ("icon.png", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('title')"]);
1147
1148     for (_WKAttachment *attachment in insertedAttachments.get())
1149         EXPECT_GT(attachment.info.filePath.length, 0U);
1150
1151     {
1152         ObserveAttachmentUpdatesForScope observer(webView.get());
1153         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
1154         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
1155         NSArray<_WKAttachment *> *removedAttachments = [observer.observer() removed];
1156         EXPECT_EQ(2U, removedAttachments.count);
1157         EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments firstObject]]);
1158         EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments lastObject]]);
1159     }
1160 }
1161
1162 TEST(WKAttachmentTestsMac, InsertDroppedFilePromisesAsAttachments)
1163 {
1164     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
1165     [configuration _setAttachmentElementEnabled:YES];
1166     auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:NSMakeRect(0, 0, 400, 400) configuration:configuration.get()]);
1167     TestWKWebView *webView = [simulator webView];
1168     [webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
1169     [simulator writePromisedFiles:@[ testPDFFileURL(), testImageFileURL() ]];
1170
1171     [simulator runFrom:CGPointMake(0, 0) to:CGPointMake(50, 50)];
1172     while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) {
1173         if ([simulator insertedAttachments].count == 2)
1174             break;
1175     }
1176     EXPECT_EQ(2, [[webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment').length"] intValue]);
1177
1178     auto insertedAttachments = retainPtr([simulator insertedAttachments]);
1179     NSArray<NSData *> *expectedData = @[ testPDFData(), testImageData() ];
1180     for (_WKAttachment *attachment in insertedAttachments.get()) {
1181         EXPECT_GT(attachment.info.filePath.length, 0U);
1182         EXPECT_TRUE([expectedData containsObject:attachment.info.data]);
1183         if ([testPDFData() isEqualToData:attachment.info.data])
1184             EXPECT_WK_STREQ("application/pdf", attachment.info.contentType);
1185         else if ([testImageData() isEqualToData:attachment.info.data])
1186             EXPECT_WK_STREQ("image/png", attachment.info.contentType);
1187     }
1188
1189     [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
1190     [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
1191     auto removedAttachments = retainPtr([simulator removedAttachments]);
1192     EXPECT_EQ(2U, [removedAttachments count]);
1193     EXPECT_EQ(0, [[webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment').length"] intValue]);
1194     EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments firstObject]]);
1195     EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments lastObject]]);
1196 }
1197
1198 TEST(WKAttachmentTestsMac, DragAttachmentAsFilePromise)
1199 {
1200     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
1201     [configuration _setAttachmentElementEnabled:YES];
1202     auto simulator = adoptNS([[DragAndDropSimulator alloc] initWithWebViewFrame:NSMakeRect(0, 0, 400, 400) configuration:configuration.get()]);
1203     TestWKWebView *webView = [simulator webView];
1204     [webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
1205
1206     auto fileWrapper = adoptNS([[NSFileWrapper alloc] initWithURL:testPDFFileURL() options:0 error:nil]);
1207     auto attachment = retainPtr([webView synchronouslyInsertAttachmentWithFileWrapper:fileWrapper.get() contentType:nil]);
1208     [simulator runFrom:[webView attachmentElementMidPoint] to:CGPointMake(300, 300)];
1209
1210     NSArray<NSURL *> *urls = [simulator receivePromisedFiles];
1211     EXPECT_EQ(1U, urls.count);
1212     EXPECT_TRUE([[NSData dataWithContentsOfURL:urls.firstObject] isEqualToData:testPDFData()]);
1213 }
1214
1215 #endif // PLATFORM(MAC)
1216
1217 #if PLATFORM(IOS)
1218
1219 TEST(WKAttachmentTestsIOS, InsertDroppedImageAsAttachment)
1220 {
1221     auto webView = webViewForTestingAttachments();
1222     auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
1223     auto item = adoptNS([[NSItemProvider alloc] init]);
1224     [item registerData:testImageData() type:(NSString *)kUTTypePNG];
1225     [dragAndDropSimulator setExternalItemProviders:@[ item.get() ]];
1226     [dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
1227
1228     EXPECT_EQ(1U, [dragAndDropSimulator insertedAttachments].count);
1229     EXPECT_EQ(0U, [dragAndDropSimulator removedAttachments].count);
1230     auto attachment = retainPtr([dragAndDropSimulator insertedAttachments].firstObject);
1231     [attachment expectRequestedDataToBe:testImageData()];
1232     EXPECT_WK_STREQ("public.png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
1233
1234     {
1235         ObserveAttachmentUpdatesForScope observer(webView.get());
1236         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
1237         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
1238         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
1239     }
1240 }
1241
1242 TEST(WKAttachmentTestsIOS, InsertDroppedAttributedStringContainingAttachment)
1243 {
1244     auto webView = webViewForTestingAttachments();
1245     auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
1246     auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(NSString *)kUTTypePNG]);
1247     auto item = adoptNS([[NSItemProvider alloc] init]);
1248     [item registerObject:[NSAttributedString attributedStringWithAttachment:image.get()] visibility:NSItemProviderRepresentationVisibilityAll];
1249
1250     [dragAndDropSimulator setExternalItemProviders:@[ item.get() ]];
1251     [dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
1252
1253     EXPECT_EQ(1U, [dragAndDropSimulator insertedAttachments].count);
1254     EXPECT_EQ(0U, [dragAndDropSimulator removedAttachments].count);
1255     auto attachment = retainPtr([dragAndDropSimulator insertedAttachments].firstObject);
1256
1257     auto size = platformImageWithData([attachment info].data).size;
1258     EXPECT_EQ(215., size.width);
1259     EXPECT_EQ(174., size.height);
1260     EXPECT_WK_STREQ("image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
1261
1262     {
1263         ObserveAttachmentUpdatesForScope observer(webView.get());
1264         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
1265         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
1266         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
1267     }
1268 }
1269
1270 TEST(WKAttachmentTestsIOS, InsertDroppedRichAndPlainTextFilesAsAttachments)
1271 {
1272     // Here, both rich text and plain text are content types that WebKit already understands how to insert in editable
1273     // areas in the absence of attachment elements. However, due to the explicitly set attachment presentation style
1274     // on the item providers, we should instead treat them as dropped files and insert attachment elements.
1275     // This exercises the scenario of dragging rich and plain text files from Files to Mail.
1276     auto richTextItem = adoptNS([[NSItemProvider alloc] init]);
1277     auto richText = adoptNS([[NSAttributedString alloc] initWithString:@"Hello world" attributes:@{ NSFontAttributeName: [UIFont boldSystemFontOfSize:12] }]);
1278     [richTextItem setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
1279     [richTextItem registerObject:richText.get() visibility:NSItemProviderRepresentationVisibilityAll];
1280     [richTextItem setSuggestedName:@"hello.rtf"];
1281
1282     auto plainTextItem = adoptNS([[NSItemProvider alloc] init]);
1283     [plainTextItem setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
1284     [plainTextItem registerObject:@"Hello world" visibility:NSItemProviderRepresentationVisibilityAll];
1285     [plainTextItem setSuggestedName:@"world.txt"];
1286
1287     auto webView = webViewForTestingAttachments();
1288     auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
1289     [dragAndDropSimulator setExternalItemProviders:@[ richTextItem.get(), plainTextItem.get() ]];
1290     [dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
1291
1292     EXPECT_EQ(2U, [dragAndDropSimulator insertedAttachments].count);
1293     EXPECT_EQ(0U, [dragAndDropSimulator removedAttachments].count);
1294
1295     for (_WKAttachment *attachment in [dragAndDropSimulator insertedAttachments])
1296         EXPECT_GT([attachment info].data.length, 0U);
1297
1298     EXPECT_EQ(2, [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment').length"].intValue);
1299     EXPECT_WK_STREQ("hello.rtf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('title')"]);
1300     EXPECT_WK_STREQ("text/rtf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
1301     EXPECT_WK_STREQ("world.txt", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('title')"]);
1302     EXPECT_WK_STREQ("text/plain", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]);
1303 }
1304
1305 TEST(WKAttachmentTestsIOS, InsertDroppedZipArchiveAsAttachment)
1306 {
1307     // Since WebKit doesn't have any default DOM representation for ZIP archives, we should fall back to inserting
1308     // attachment elements. This exercises the flow of dragging a ZIP file from an app that doesn't specify a preferred
1309     // presentation style (e.g. Notes) into Mail.
1310     auto item = adoptNS([[NSItemProvider alloc] init]);
1311     NSData *data = testZIPData();
1312     [item registerData:data type:(NSString *)kUTTypeZipArchive];
1313     [item setSuggestedName:@"archive.zip"];
1314
1315     auto webView = webViewForTestingAttachments();
1316     auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
1317     [dragAndDropSimulator setExternalItemProviders:@[ item.get() ]];
1318     [dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
1319
1320     EXPECT_EQ(1U, [dragAndDropSimulator insertedAttachments].count);
1321     EXPECT_EQ(0U, [dragAndDropSimulator removedAttachments].count);
1322     [[dragAndDropSimulator insertedAttachments].firstObject expectRequestedDataToBe:data];
1323     EXPECT_EQ(1, [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment').length"].intValue);
1324     EXPECT_WK_STREQ("archive.zip", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
1325     EXPECT_WK_STREQ("application/zip", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
1326 }
1327
1328 TEST(WKAttachmentTestsIOS, InsertDroppedItemProvidersInOrder)
1329 {
1330     // Tests that item providers are inserted in the order they are specified. In this case, the two inserted attachments
1331     // should be separated by a link.
1332     auto firstAttachmentItem = adoptNS([[NSItemProvider alloc] init]);
1333     [firstAttachmentItem setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
1334     [firstAttachmentItem registerObject:@"FIRST" visibility:NSItemProviderRepresentationVisibilityAll];
1335     [firstAttachmentItem setSuggestedName:@"first.txt"];
1336
1337     auto inlineTextItem = adoptNS([[NSItemProvider alloc] init]);
1338     auto appleURL = retainPtr([NSURL URLWithString:@"https://www.apple.com/"]);
1339     [inlineTextItem registerObject:appleURL.get() visibility:NSItemProviderRepresentationVisibilityAll];
1340
1341     auto secondAttachmentItem = adoptNS([[NSItemProvider alloc] init]);
1342     [secondAttachmentItem registerData:testPDFData() type:(NSString *)kUTTypePDF];
1343     [secondAttachmentItem setSuggestedName:@"second.pdf"];
1344
1345     auto webView = webViewForTestingAttachments();
1346     auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
1347     [dragAndDropSimulator setExternalItemProviders:@[ firstAttachmentItem.get(), inlineTextItem.get(), secondAttachmentItem.get() ]];
1348     [dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
1349
1350     EXPECT_EQ(2U, [dragAndDropSimulator insertedAttachments].count);
1351     EXPECT_EQ(0U, [dragAndDropSimulator removedAttachments].count);
1352
1353     for (_WKAttachment *attachment in [dragAndDropSimulator insertedAttachments])
1354         EXPECT_GT([attachment info].data.length, 0U);
1355
1356     [webView expectElementTagsInOrder:@[ @"ATTACHMENT", @"A", @"ATTACHMENT" ]];
1357
1358     EXPECT_WK_STREQ("first.txt", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('title')"]);
1359     EXPECT_WK_STREQ("text/plain", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
1360     EXPECT_WK_STREQ([appleURL absoluteString], [webView valueOfAttribute:@"href" forQuerySelector:@"a"]);
1361     EXPECT_WK_STREQ("second.pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('title')"]);
1362     EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]);
1363 }
1364
1365 TEST(WKAttachmentTestsIOS, DragAttachmentInsertedAsFile)
1366 {
1367     auto item = adoptNS([[NSItemProvider alloc] init]);
1368     auto data = retainPtr(testPDFData());
1369     [item registerData:data.get() type:(NSString *)kUTTypePDF];
1370     [item setSuggestedName:@"document.pdf"];
1371
1372     auto webView = webViewForTestingAttachments();
1373     auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
1374     [dragAndDropSimulator setExternalItemProviders:@[ item.get() ]];
1375     [dragAndDropSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
1376
1377     // First, verify that the attachment was successfully dropped.
1378     EXPECT_EQ(1U, [dragAndDropSimulator insertedAttachments].count);
1379     _WKAttachment *attachment = [dragAndDropSimulator insertedAttachments].firstObject;
1380     [attachment expectRequestedDataToBe:data.get()];
1381     EXPECT_WK_STREQ("document.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
1382     EXPECT_WK_STREQ("application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
1383
1384     [webView evaluateJavaScript:@"getSelection().removeAllRanges()" completionHandler:nil];
1385     [dragAndDropSimulator setExternalItemProviders:@[ ]];
1386     [dragAndDropSimulator runFrom:CGPointMake(25, 25) to:CGPointMake(-100, -100)];
1387
1388     // Next, verify that dragging the attachment produces an item provider with a PDF attachment.
1389     EXPECT_EQ(1U, [dragAndDropSimulator sourceItemProviders].count);
1390     NSItemProvider *itemProvider = [dragAndDropSimulator sourceItemProviders].firstObject;
1391     EXPECT_EQ(UIPreferredPresentationStyleAttachment, itemProvider.preferredPresentationStyle);
1392     [itemProvider expectType:(NSString *)kUTTypePDF withData:data.get()];
1393     EXPECT_WK_STREQ("document.pdf", [itemProvider suggestedName]);
1394     [dragAndDropSimulator endDataTransfer];
1395 }
1396
1397 TEST(WKAttachmentTestsIOS, DragAttachmentInsertedAsData)
1398 {
1399     auto webView = webViewForTestingAttachments();
1400     auto data = retainPtr(testPDFData());
1401     RetainPtr<_WKAttachment> attachment;
1402     {
1403         ObserveAttachmentUpdatesForScope observer(webView.get());
1404         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"document.pdf" contentType:@"application/pdf" data:data.get()];
1405         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
1406     }
1407
1408     // First, verify that the attachment was successfully inserted from raw data.
1409     [attachment expectRequestedDataToBe:data.get()];
1410     EXPECT_WK_STREQ("document.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
1411     EXPECT_WK_STREQ("application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
1412
1413     [webView evaluateJavaScript:@"getSelection().removeAllRanges()" completionHandler:nil];
1414     auto dragAndDropSimulator = adoptNS([[DragAndDropSimulator alloc] initWithWebView:webView.get()]);
1415     [dragAndDropSimulator runFrom:CGPointMake(25, 25) to:CGPointMake(-100, -100)];
1416
1417     // Next, verify that dragging the attachment produces an item provider with a PDF attachment.
1418     EXPECT_EQ(1U, [dragAndDropSimulator sourceItemProviders].count);
1419     NSItemProvider *itemProvider = [dragAndDropSimulator sourceItemProviders].firstObject;
1420     EXPECT_EQ(UIPreferredPresentationStyleAttachment, itemProvider.preferredPresentationStyle);
1421     [itemProvider expectType:(NSString *)kUTTypePDF withData:data.get()];
1422     EXPECT_WK_STREQ("document.pdf", [itemProvider suggestedName]);
1423     [dragAndDropSimulator endDataTransfer];
1424 }
1425
1426 #endif // PLATFORM(IOS)
1427
1428 } // namespace TestWebKitAPI
1429
1430 #endif // WK_API_ENABLED && !PLATFORM(WATCHOS) && !PLATFORM(TVOS)