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