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