[Attachment Support] Create attachment elements when dropping files on iOS
[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 }
54
55 - (instancetype)init
56 {
57     if (self = [super init]) {
58         _inserted = adoptNS([[NSMutableArray alloc] init]);
59         _removed = adoptNS([[NSMutableArray alloc] init]);
60     }
61     return self;
62 }
63
64 - (NSArray<_WKAttachment *> *)inserted
65 {
66     return _inserted.get();
67 }
68
69 - (NSArray<_WKAttachment *> *)removed
70 {
71     return _removed.get();
72 }
73
74 - (void)_webView:(WKWebView *)webView didInsertAttachment:(_WKAttachment *)attachment
75 {
76     [_inserted addObject:attachment];
77 }
78
79 - (void)_webView:(WKWebView *)webView didRemoveAttachment:(_WKAttachment *)attachment
80 {
81     [_removed addObject:attachment];
82 }
83
84 @end
85
86 namespace TestWebKitAPI {
87
88 class ObserveAttachmentUpdatesForScope {
89 public:
90     ObserveAttachmentUpdatesForScope(TestWKWebView *webView)
91         : m_webView(webView)
92     {
93         m_previousDelegate = webView.UIDelegate;
94         m_observer = adoptNS([[AttachmentUpdateObserver alloc] init]);
95         webView.UIDelegate = m_observer.get();
96     }
97
98     ~ObserveAttachmentUpdatesForScope()
99     {
100         [m_webView setUIDelegate:m_previousDelegate.get()];
101     }
102
103     AttachmentUpdateObserver *observer() const { return m_observer.get(); }
104
105     void expectAttachmentUpdates(NSArray<_WKAttachment *> *removed, NSArray<_WKAttachment *> *inserted)
106     {
107         BOOL removedAttachmentsMatch = [observer().removed isEqual:removed];
108         if (!removedAttachmentsMatch)
109             NSLog(@"Expected removed attachments: %@ to match %@.", observer().removed, removed);
110         EXPECT_TRUE(removedAttachmentsMatch);
111
112         BOOL insertedAttachmentsMatch = [observer().inserted isEqual:inserted];
113         if (!insertedAttachmentsMatch)
114             NSLog(@"Expected inserted attachments: %@ to match %@.", observer().inserted, inserted);
115         EXPECT_TRUE(insertedAttachmentsMatch);
116     }
117
118 private:
119     RetainPtr<AttachmentUpdateObserver> m_observer;
120     RetainPtr<TestWKWebView> m_webView;
121     RetainPtr<id> m_previousDelegate;
122 };
123
124 }
125
126 @interface TestWKWebView (AttachmentTesting)
127 @end
128
129 static RetainPtr<TestWKWebView> webViewForTestingAttachments()
130 {
131     auto configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
132     [configuration _setAttachmentElementEnabled:YES];
133     WKPreferencesSetCustomPasteboardDataEnabled((WKPreferencesRef)[configuration preferences], YES);
134
135     auto webView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 320, 500) configuration:configuration.get()]);
136     [webView synchronouslyLoadHTMLString:@"<script>focus = () => document.body.focus()</script><body onload=focus() contenteditable></body>"];
137
138     return webView;
139 }
140
141 static NSData *testZIPData()
142 {
143     NSURL *zipFileURL = [[NSBundle mainBundle] URLForResource:@"compressed-files" withExtension:@"zip" subdirectory:@"TestWebKitAPI.resources"];
144     return [NSData dataWithContentsOfURL:zipFileURL];
145 }
146
147 static NSData *testHTMLData()
148 {
149     return [@"<a href='#'>This is some HTML data</a>" dataUsingEncoding:NSUTF8StringEncoding];
150 }
151
152 static NSURL *testImageFileURL()
153 {
154     return [[NSBundle mainBundle] URLForResource:@"icon" withExtension:@"png" subdirectory:@"TestWebKitAPI.resources"];
155 }
156
157 static NSData *testImageData()
158 {
159     return [NSData dataWithContentsOfURL:testImageFileURL()];
160 }
161
162 static NSData *testVideoData()
163 {
164     NSURL *url = [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"mp4" subdirectory:@"TestWebKitAPI.resources"];
165     return [NSData dataWithContentsOfURL:url];
166 }
167
168 static NSURL *testPDFFileURL()
169 {
170     return [[NSBundle mainBundle] URLForResource:@"test" withExtension:@"pdf" subdirectory:@"TestWebKitAPI.resources"];
171 }
172
173 static NSData *testPDFData()
174 {
175     return [NSData dataWithContentsOfURL:testPDFFileURL()];
176 }
177
178 static _WKAttachmentDisplayOptions *displayOptionsWithMode(_WKAttachmentDisplayMode mode)
179 {
180     _WKAttachmentDisplayOptions *options = [[[_WKAttachmentDisplayOptions alloc] init] autorelease];
181     options.mode = mode;
182     return options;
183 }
184
185 @implementation TestWKWebView (AttachmentTesting)
186
187 - (BOOL)_synchronouslyExecuteEditCommand:(NSString *)command argument:(NSString *)argument
188 {
189     __block bool done = false;
190     __block bool success;
191     [self _executeEditCommand:command argument:argument completion:^(BOOL completionSuccess) {
192         done = true;
193         success = completionSuccess;
194     }];
195     TestWebKitAPI::Util::run(&done);
196     return success;
197 }
198
199 - (_WKAttachment *)synchronouslyInsertAttachmentWithFilename:(NSString *)filename contentType:(NSString *)contentType data:(NSData *)data options:(_WKAttachmentDisplayOptions *)options
200 {
201     __block bool done = false;
202     RetainPtr<_WKAttachment> attachment = [self _insertAttachmentWithFilename:filename contentType:contentType data:data options:options completion:^(BOOL) {
203         done = true;
204     }];
205     TestWebKitAPI::Util::run(&done);
206     return attachment.autorelease();
207 }
208
209 - (CGSize)attachmentElementSize
210 {
211     __block CGSize size;
212     __block bool doneEvaluatingScript = false;
213     [self evaluateJavaScript:@"r = document.querySelector('attachment').getBoundingClientRect(); [r.width, r.height]" completionHandler:^(NSArray<NSNumber *> *sizeResult, NSError *) {
214         size = CGSizeMake(sizeResult.firstObject.floatValue, sizeResult.lastObject.floatValue);
215         doneEvaluatingScript = true;
216     }];
217     TestWebKitAPI::Util::run(&doneEvaluatingScript);
218     return size;
219 }
220
221 - (void)waitForAttachmentElementSizeToBecome:(CGSize)expectedSize
222 {
223     while ([[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantPast]]) {
224         if (CGSizeEqualToSize(self.attachmentElementSize, expectedSize))
225             break;
226     }
227 }
228
229 - (BOOL)hasAttribute:(NSString *)attributeName forQuerySelector:(NSString *)querySelector
230 {
231     return [self stringByEvaluatingJavaScript:[NSString stringWithFormat:@"document.querySelector('%@').hasAttribute('%@')", querySelector, attributeName]].boolValue;
232 }
233
234 - (NSString *)valueOfAttribute:(NSString *)attributeName forQuerySelector:(NSString *)querySelector
235 {
236     return [self stringByEvaluatingJavaScript:[NSString stringWithFormat:@"document.querySelector('%@').getAttribute('%@')", querySelector, attributeName]];
237 }
238
239 - (void)expectUpdatesAfterCommand:(NSString *)command withArgument:(NSString *)argument expectedRemovals:(NSArray<_WKAttachment *> *)removed expectedInsertions:(NSArray<_WKAttachment *> *)inserted
240 {
241     TestWebKitAPI::ObserveAttachmentUpdatesForScope observer(self);
242     EXPECT_TRUE([self _synchronouslyExecuteEditCommand:command argument:argument]);
243     observer.expectAttachmentUpdates(removed, inserted);
244 }
245
246 @end
247
248 @implementation NSData (AttachmentTesting)
249
250 - (NSString *)shortDescription
251 {
252     return [NSString stringWithFormat:@"<%tu bytes>", self.length];
253 }
254
255 @end
256
257 @implementation _WKAttachment (AttachmentTesting)
258
259 - (void)synchronouslySetDisplayOptions:(_WKAttachmentDisplayOptions *)options error:(NSError **)error
260 {
261     __block RetainPtr<NSError> resultError;
262     __block bool done = false;
263     [self setDisplayOptions:options completion:^(NSError *error) {
264         resultError = error;
265         done = true;
266     }];
267
268     TestWebKitAPI::Util::run(&done);
269
270     if (error)
271         *error = resultError.autorelease();
272 }
273
274 - (NSData *)synchronouslyRequestData:(NSError **)error
275 {
276     __block RetainPtr<NSData> result;
277     __block RetainPtr<NSError> resultError;
278     __block bool done = false;
279     [self requestData:^(NSData *data, NSError *error) {
280         result = data;
281         resultError = error;
282         done = true;
283     }];
284
285     TestWebKitAPI::Util::run(&done);
286
287     if (error)
288         *error = resultError.autorelease();
289
290     return result.autorelease();
291 }
292
293 - (void)synchronouslySetData:(NSData *)data newContentType:(NSString *)newContentType newFilename:(NSString *)newFilename error:(NSError **)error
294 {
295     __block RetainPtr<NSError> resultError;
296     __block bool done = false;
297     [self setData:data newContentType:newContentType newFilename:newFilename completion:^(NSError *error) {
298         resultError = error;
299         done = true;
300     }];
301
302     TestWebKitAPI::Util::run(&done);
303
304     if (error)
305         *error = resultError.autorelease();
306 }
307
308 - (void)expectRequestedDataToBe:(NSData *)expectedData
309 {
310     NSError *dataRequestError = nil;
311     NSData *observedData = [self synchronouslyRequestData:&dataRequestError];
312     BOOL observedDataIsEqualToExpectedData = [observedData isEqualToData:expectedData] || observedData == expectedData;
313     EXPECT_TRUE(observedDataIsEqualToExpectedData);
314     if (!observedDataIsEqualToExpectedData) {
315         NSLog(@"Expected data: %@ but observed: %@ for %@", [expectedData shortDescription], [observedData shortDescription], self);
316         NSLog(@"Observed error: %@ while reading data for %@", dataRequestError, self);
317     }
318 }
319
320 @end
321
322 #pragma mark - Platform testing helper functions
323
324 #if PLATFORM(IOS)
325
326 typedef void(^ItemProviderDataLoadHandler)(NSData *, NSError *);
327
328 @implementation NSItemProvider (AttachmentTesting)
329
330 - (void)registerData:(NSData *)data type:(NSString *)type
331 {
332     [self registerDataRepresentationForTypeIdentifier:type visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[protectedData = retainPtr(data)] (ItemProviderDataLoadHandler completionHandler) -> NSProgress * {
333         completionHandler(protectedData.get(), nil);
334         return nil;
335     }];
336 }
337
338 @end
339
340 #endif // PLATFORM(IOS)
341
342 void platformCopyRichTextWithMultipleAttachments()
343 {
344     auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(NSString *)kUTTypePNG]);
345     auto pdf = adoptNS([[NSTextAttachment alloc] initWithData:testPDFData() ofType:(NSString *)kUTTypePDF]);
346     auto zip = adoptNS([[NSTextAttachment alloc] initWithData:testZIPData() ofType:(NSString *)kUTTypeZipArchive]);
347
348     auto richText = adoptNS([[NSMutableAttributedString alloc] init]);
349     [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:image.get()]];
350     [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:pdf.get()]];
351     [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:zip.get()]];
352
353 #if PLATFORM(MAC)
354     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
355     [pasteboard clearContents];
356     [pasteboard writeObjects:@[ richText.get() ]];
357 #elif PLATFORM(IOS)
358     auto item = adoptNS([[NSItemProvider alloc] init]);
359     [item registerObject:richText.get() visibility:NSItemProviderRepresentationVisibilityAll];
360     [UIPasteboard generalPasteboard].itemProviders = @[ item.get() ];
361 #endif
362 }
363
364 void platformCopyRichTextWithImage()
365 {
366     auto richText = adoptNS([[NSMutableAttributedString alloc] init]);
367     auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(NSString *)kUTTypePNG]);
368
369     [richText appendAttributedString:[[[NSAttributedString alloc] initWithString:@"Lorem ipsum "] autorelease]];
370     [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:image.get()]];
371     [richText appendAttributedString:[[[NSAttributedString alloc] initWithString:@" dolor sit amet."] autorelease]];
372
373 #if PLATFORM(MAC)
374     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
375     [pasteboard clearContents];
376     [pasteboard writeObjects:@[ richText.get() ]];
377 #elif PLATFORM(IOS)
378     auto item = adoptNS([[NSItemProvider alloc] init]);
379     [item registerObject:richText.get() visibility:NSItemProviderRepresentationVisibilityAll];
380     [UIPasteboard generalPasteboard].itemProviders = @[ item.get() ];
381 #endif
382 }
383
384 void platformCopyPNG()
385 {
386 #if PLATFORM(MAC)
387     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
388     [pasteboard declareTypes:@[NSPasteboardTypePNG] owner:nil];
389     [pasteboard setData:testImageData() forType:NSPasteboardTypePNG];
390 #elif PLATFORM(IOS)
391     UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
392     auto item = adoptNS([[UIItemProvider alloc] init]);
393     [item setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
394     [item registerData:testImageData() type:(NSString *)kUTTypePNG];
395     pasteboard.itemProviders = @[ item.get() ];
396 #endif
397 }
398
399 #if PLATFORM(MAC)
400 typedef NSImage PlatformImage;
401 #else
402 typedef UIImage PlatformImage;
403 #endif
404
405 PlatformImage *platformImageWithData(NSData *data)
406 {
407 #if PLATFORM(MAC)
408     return [[[NSImage alloc] initWithData:data] autorelease];
409 #else
410     return [UIImage imageWithData:data];
411 #endif
412 }
413
414 namespace TestWebKitAPI {
415
416 #pragma mark - Platform-agnostic tests
417
418 TEST(WKAttachmentTests, AttachmentElementInsertion)
419 {
420     auto webView = webViewForTestingAttachments();
421     RetainPtr<_WKAttachment> firstAttachment;
422     RetainPtr<_WKAttachment> secondAttachment;
423     {
424         ObserveAttachmentUpdatesForScope observer(webView.get());
425         // Use the given content type for the attachment element's type.
426         firstAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo" contentType:@"text/html" data:testHTMLData() options:nil];
427         EXPECT_WK_STREQ(@"foo", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
428         EXPECT_WK_STREQ(@"text/html", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
429         EXPECT_WK_STREQ(@"38 bytes", [webView valueOfAttribute:@"subtitle" forQuerySelector:@"attachment"]);
430         observer.expectAttachmentUpdates(@[ ], @[ firstAttachment.get() ]);
431     }
432
433     [firstAttachment expectRequestedDataToBe:testHTMLData()];
434
435     {
436         ObserveAttachmentUpdatesForScope scope(webView.get());
437         // Since no content type is explicitly specified, compute it from the file extension.
438         [webView _executeEditCommand:@"DeleteBackward" argument:nil completion:nil];
439         secondAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"bar.png" contentType:nil data:testImageData() options:nil];
440         EXPECT_WK_STREQ(@"bar.png", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
441         EXPECT_WK_STREQ(@"image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
442         EXPECT_WK_STREQ(@"37 KB", [webView valueOfAttribute:@"subtitle" forQuerySelector:@"attachment"]);
443         scope.expectAttachmentUpdates(@[ firstAttachment.get() ], @[ secondAttachment.get() ]);
444     }
445
446     [firstAttachment expectRequestedDataToBe:nil];
447     [secondAttachment expectRequestedDataToBe:testImageData()];
448     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
449     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
450 }
451
452 TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingAndDeletingNewline)
453 {
454     auto webView = webViewForTestingAttachments();
455     RetainPtr<_WKAttachment> attachment;
456     {
457         ObserveAttachmentUpdatesForScope observer(webView.get());
458         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil];
459         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
460     }
461     [webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
462     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
463     [webView stringByEvaluatingJavaScript:@"getSelection().collapse(document.body)"];
464     [webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
465
466     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
467     [attachment expectRequestedDataToBe:testHTMLData()];
468
469     [webView expectUpdatesAfterCommand:@"DeleteForward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
470     [attachment expectRequestedDataToBe:nil];
471 }
472
473 TEST(WKAttachmentTests, AttachmentUpdatesWhenUndoingAndRedoing)
474 {
475     auto webView = webViewForTestingAttachments();
476     RetainPtr<NSData> htmlData = testHTMLData();
477     RetainPtr<_WKAttachment> attachment;
478     {
479         ObserveAttachmentUpdatesForScope observer(webView.get());
480         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil];
481         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
482     }
483     [webView expectUpdatesAfterCommand:@"Undo" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
484     [attachment expectRequestedDataToBe:nil];
485
486     [webView expectUpdatesAfterCommand:@"Redo" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
487     [attachment expectRequestedDataToBe:htmlData.get()];
488
489     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
490     [attachment expectRequestedDataToBe:nil];
491
492     [webView expectUpdatesAfterCommand:@"Undo" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
493     [attachment expectRequestedDataToBe:htmlData.get()];
494
495     [webView expectUpdatesAfterCommand:@"Redo" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
496     [attachment expectRequestedDataToBe:nil];
497 }
498
499 TEST(WKAttachmentTests, AttachmentUpdatesWhenChangingFontStyles)
500 {
501     auto webView = webViewForTestingAttachments();
502     RetainPtr<_WKAttachment> attachment;
503     [webView _synchronouslyExecuteEditCommand:@"InsertText" argument:@"Hello"];
504     {
505         ObserveAttachmentUpdatesForScope observer(webView.get());
506         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil];
507         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
508     }
509     [webView expectUpdatesAfterCommand:@"InsertText" withArgument:@"World" expectedRemovals:@[] expectedInsertions:@[]];
510     [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
511     [webView expectUpdatesAfterCommand:@"ToggleBold" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
512     [webView expectUpdatesAfterCommand:@"ToggleItalic" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
513     [webView expectUpdatesAfterCommand:@"ToggleUnderline" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
514     [attachment expectRequestedDataToBe:testHTMLData()];
515     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
516     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
517
518     // Inserting text should delete the current selection, removing the attachment in the process.
519     [webView expectUpdatesAfterCommand:@"InsertText" withArgument:@"foo" expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
520     [attachment expectRequestedDataToBe:nil];
521 }
522
523 TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingLists)
524 {
525     auto webView = webViewForTestingAttachments();
526     RetainPtr<_WKAttachment> attachment;
527     {
528         ObserveAttachmentUpdatesForScope observer(webView.get());
529         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil];
530         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
531     }
532     [webView expectUpdatesAfterCommand:@"InsertOrderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
533     // This edit command behaves more like a "toggle", and will actually break us out of the list we just inserted.
534     [webView expectUpdatesAfterCommand:@"InsertOrderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
535     [webView expectUpdatesAfterCommand:@"InsertUnorderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
536     [webView expectUpdatesAfterCommand:@"InsertUnorderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
537     [attachment expectRequestedDataToBe:testHTMLData()];
538     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
539     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
540 }
541
542 TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingRichMarkup)
543 {
544     auto webView = webViewForTestingAttachments();
545     RetainPtr<_WKAttachment> attachment;
546     {
547         ObserveAttachmentUpdatesForScope observer(webView.get());
548         [webView _synchronouslyExecuteEditCommand:@"InsertHTML" argument:@"<div><strong><attachment title='a' webkitattachmentid='a06fec41-9aa0-4c2c-ba3a-0149b54aad99'></attachment></strong></div>"];
549         attachment = observer.observer().inserted[0];
550         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
551     }
552     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
553     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
554     {
555         ObserveAttachmentUpdatesForScope observer(webView.get());
556         [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').remove()"];
557         [webView waitForNextPresentationUpdate];
558         observer.expectAttachmentUpdates(@[attachment.get()], @[ ]);
559     }
560     [attachment expectRequestedDataToBe:nil];
561 }
562
563 TEST(WKAttachmentTests, AttachmentUpdatesWhenCuttingAndPasting)
564 {
565     auto webView = webViewForTestingAttachments();
566     RetainPtr<_WKAttachment> attachment;
567     {
568         ObserveAttachmentUpdatesForScope observer(webView.get());
569         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil];
570         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
571     }
572     [attachment expectRequestedDataToBe:testHTMLData()];
573     [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
574     {
575         ObserveAttachmentUpdatesForScope observer(webView.get());
576         [webView _synchronouslyExecuteEditCommand:@"Cut" argument:nil];
577         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
578     }
579     [attachment expectRequestedDataToBe:nil];
580     {
581         ObserveAttachmentUpdatesForScope observer(webView.get());
582         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
583         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
584     }
585     [attachment expectRequestedDataToBe:testHTMLData()];
586     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
587     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
588 }
589
590 TEST(WKAttachmentTests, AttachmentDataForEmptyFile)
591 {
592     auto webView = webViewForTestingAttachments();
593     RetainPtr<_WKAttachment> attachment;
594     {
595         ObserveAttachmentUpdatesForScope observer(webView.get());
596         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"empty.txt" contentType:@"text/plain" data:[NSData data] options:nil];
597         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
598     }
599     [attachment expectRequestedDataToBe:[NSData data]];
600     {
601         ObserveAttachmentUpdatesForScope scope(webView.get());
602         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
603         scope.expectAttachmentUpdates(@[attachment.get()], @[]);
604     }
605     [attachment expectRequestedDataToBe:nil];
606 }
607
608 TEST(WKAttachmentTests, MultipleSimultaneousAttachmentDataRequests)
609 {
610     auto webView = webViewForTestingAttachments();
611     RetainPtr<NSData> htmlData = testHTMLData();
612     RetainPtr<_WKAttachment> attachment;
613     {
614         ObserveAttachmentUpdatesForScope observer(webView.get());
615         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:htmlData.get() options:nil];
616         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
617     }
618     __block RetainPtr<NSData> dataForFirstRequest;
619     __block RetainPtr<NSData> dataForSecondRequest;
620     __block bool done = false;
621     [attachment requestData:^(NSData *data, NSError *error) {
622         EXPECT_TRUE(!error);
623         dataForFirstRequest = data;
624     }];
625     [attachment requestData:^(NSData *data, NSError *error) {
626         EXPECT_TRUE(!error);
627         dataForSecondRequest = data;
628         done = true;
629     }];
630
631     Util::run(&done);
632
633     EXPECT_TRUE([dataForFirstRequest isEqualToData:htmlData.get()]);
634     EXPECT_TRUE([dataForSecondRequest isEqualToData:htmlData.get()]);
635 }
636
637 TEST(WKAttachmentTests, InPlaceImageAttachmentToggleDisplayMode)
638 {
639     auto webView = webViewForTestingAttachments();
640     RetainPtr<NSData> imageData = testImageData();
641     RetainPtr<_WKAttachment> attachment;
642     {
643         ObserveAttachmentUpdatesForScope observer(webView.get());
644         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"icon.png" contentType:@"image/png" data:imageData.get() options:displayOptionsWithMode(_WKAttachmentDisplayModeAsIcon)];
645         [attachment expectRequestedDataToBe:imageData.get()];
646         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
647     }
648     CGSize iconAttachmentSize = [webView attachmentElementSize];
649
650     [attachment synchronouslySetDisplayOptions:displayOptionsWithMode(_WKAttachmentDisplayModeInPlace) error:nil];
651     [attachment expectRequestedDataToBe:imageData.get()];
652     [webView waitForAttachmentElementSizeToBecome:CGSizeMake(215, 174)];
653
654     [attachment synchronouslySetDisplayOptions:displayOptionsWithMode(_WKAttachmentDisplayModeAsIcon) error:nil];
655     [attachment expectRequestedDataToBe:imageData.get()];
656     [webView waitForAttachmentElementSizeToBecome:iconAttachmentSize];
657
658     [attachment synchronouslySetDisplayOptions:displayOptionsWithMode(_WKAttachmentDisplayModeInPlace) error:nil];
659     [attachment expectRequestedDataToBe:imageData.get()];
660     [webView waitForAttachmentElementSizeToBecome:CGSizeMake(215, 174)];
661 }
662
663 TEST(WKAttachmentTests, InPlaceImageAttachmentParagraphInsertion)
664 {
665     auto webView = webViewForTestingAttachments();
666     RetainPtr<NSData> imageData = testImageData();
667     RetainPtr<_WKAttachment> attachment;
668     {
669         ObserveAttachmentUpdatesForScope observer(webView.get());
670         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"icon.png" contentType:@"image/png" data:imageData.get() options:displayOptionsWithMode(_WKAttachmentDisplayModeInPlace)];
671         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
672     }
673     [webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
674     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
675     [webView stringByEvaluatingJavaScript:@"getSelection().collapse(document.body)"];
676     [webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
677     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
678
679     [attachment expectRequestedDataToBe:imageData.get()];
680     [webView waitForAttachmentElementSizeToBecome:CGSizeMake(215, 174)];
681
682     [webView expectUpdatesAfterCommand:@"DeleteForward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
683 }
684
685 TEST(WKAttachmentTests, InPlaceVideoAttachmentInsertionWithinList)
686 {
687     auto webView = webViewForTestingAttachments();
688     RetainPtr<NSData> videoData = testVideoData();
689     RetainPtr<_WKAttachment> attachment;
690
691     [webView _synchronouslyExecuteEditCommand:@"InsertOrderedList" argument:nil];
692     {
693         ObserveAttachmentUpdatesForScope observer(webView.get());
694         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"test.mp4" contentType:@"video/mp4" data:videoData.get() options:displayOptionsWithMode(_WKAttachmentDisplayModeInPlace)];
695         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
696     }
697     [webView waitForAttachmentElementSizeToBecome:CGSizeMake(320, 240)];
698
699     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
700     [webView expectUpdatesAfterCommand:@"Undo" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
701     [webView expectUpdatesAfterCommand:@"InsertOrderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
702
703     [webView waitForAttachmentElementSizeToBecome:CGSizeMake(320, 240)];
704     [attachment expectRequestedDataToBe:videoData.get()];
705 }
706
707 TEST(WKAttachmentTests, InPlacePDFAttachmentCutAndPaste)
708 {
709     auto webView = webViewForTestingAttachments();
710     RetainPtr<NSData> pdfData = testPDFData();
711     RetainPtr<_WKAttachment> attachment;
712     {
713         ObserveAttachmentUpdatesForScope observer(webView.get());
714         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"test.pdf" contentType:@"application/pdf" data:pdfData.get() options:displayOptionsWithMode(_WKAttachmentDisplayModeInPlace)];
715         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
716         [webView waitForAttachmentElementSizeToBecome:CGSizeMake(130, 29)];
717     }
718
719     [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
720     [webView expectUpdatesAfterCommand:@"Cut" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
721
722     [webView expectUpdatesAfterCommand:@"Paste" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
723     [webView waitForAttachmentElementSizeToBecome:CGSizeMake(130, 29)];
724     [attachment expectRequestedDataToBe:pdfData.get()];
725 }
726
727 TEST(WKAttachmentTests, ChangeAttachmentDataAndFileInformation)
728 {
729     auto webView = webViewForTestingAttachments();
730     RetainPtr<_WKAttachment> attachment;
731     {
732         RetainPtr<NSData> pdfData = testPDFData();
733         ObserveAttachmentUpdatesForScope observer(webView.get());
734         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"test.pdf" contentType:@"application/pdf" data:pdfData.get() options:displayOptionsWithMode(_WKAttachmentDisplayModeAsIcon)];
735         [attachment expectRequestedDataToBe:pdfData.get()];
736         EXPECT_WK_STREQ(@"test.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
737         EXPECT_WK_STREQ(@"application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
738         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
739     }
740     {
741         RetainPtr<NSData> imageData = testImageData();
742         ObserveAttachmentUpdatesForScope observer(webView.get());
743         [attachment synchronouslySetData:imageData.get() newContentType:@"image/png" newFilename:@"icon.png" error:nil];
744         [attachment expectRequestedDataToBe:imageData.get()];
745         EXPECT_WK_STREQ(@"icon.png", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
746         EXPECT_WK_STREQ(@"image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
747         observer.expectAttachmentUpdates(@[], @[]);
748     }
749     {
750         RetainPtr<NSData> textData = testHTMLData();
751         ObserveAttachmentUpdatesForScope observer(webView.get());
752         [attachment synchronouslySetDisplayOptions:displayOptionsWithMode(_WKAttachmentDisplayModeAsIcon) error:nil];
753         // The new content type should be inferred from the file name.
754         [attachment synchronouslySetData:textData.get() newContentType:nil newFilename:@"foo.txt" error:nil];
755         [attachment expectRequestedDataToBe:textData.get()];
756         EXPECT_WK_STREQ(@"foo.txt", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
757         EXPECT_WK_STREQ(@"text/plain", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
758         observer.expectAttachmentUpdates(@[], @[]);
759     }
760     {
761         RetainPtr<NSData> secondTextData = [@"Hello world" dataUsingEncoding:NSUTF8StringEncoding];
762         ObserveAttachmentUpdatesForScope observer(webView.get());
763         // Both the previous file name and type should be inferred.
764         [attachment synchronouslySetData:secondTextData.get() newContentType:nil newFilename:nil error:nil];
765         [attachment expectRequestedDataToBe:secondTextData.get()];
766         EXPECT_WK_STREQ(@"foo.txt", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
767         EXPECT_WK_STREQ(@"text/plain", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
768         observer.expectAttachmentUpdates(@[], @[]);
769     }
770     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
771 }
772
773 TEST(WKAttachmentTests, ChangeAttachmentDataUpdatesWithInPlaceDisplay)
774 {
775     auto webView = webViewForTestingAttachments();
776     RetainPtr<_WKAttachment> attachment;
777     {
778         RetainPtr<NSData> pdfData = testPDFData();
779         ObserveAttachmentUpdatesForScope observer(webView.get());
780         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"test.pdf" contentType:@"application/pdf" data:pdfData.get() options:displayOptionsWithMode(_WKAttachmentDisplayModeInPlace)];
781         [webView waitForAttachmentElementSizeToBecome:CGSizeMake(130, 29)];
782         [attachment expectRequestedDataToBe:pdfData.get()];
783         EXPECT_WK_STREQ(@"test.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
784         EXPECT_WK_STREQ(@"application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
785         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
786     }
787     {
788         RetainPtr<NSData> videoData = testVideoData();
789         ObserveAttachmentUpdatesForScope observer(webView.get());
790         [attachment synchronouslySetData:videoData.get() newContentType:@"video/mp4" newFilename:@"test.mp4" error:nil];
791         [webView waitForAttachmentElementSizeToBecome:CGSizeMake(320, 240)];
792         [attachment expectRequestedDataToBe:videoData.get()];
793         EXPECT_WK_STREQ(@"test.mp4", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
794         EXPECT_WK_STREQ(@"video/mp4", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
795         observer.expectAttachmentUpdates(@[], @[]);
796     }
797     {
798         RetainPtr<NSData> imageData = testImageData();
799         ObserveAttachmentUpdatesForScope observer(webView.get());
800         [attachment synchronouslySetData:imageData.get() newContentType:@"image/png" newFilename:@"icon.png" error:nil];
801         [webView waitForAttachmentElementSizeToBecome:CGSizeMake(215, 174)];
802         [attachment expectRequestedDataToBe:imageData.get()];
803         EXPECT_WK_STREQ(@"icon.png", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
804         EXPECT_WK_STREQ(@"image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
805         observer.expectAttachmentUpdates(@[], @[]);
806     }
807     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
808 }
809
810 TEST(WKAttachmentTests, InsertPastedImageAsAttachment)
811 {
812     platformCopyPNG();
813
814     RetainPtr<_WKAttachment> attachment;
815     auto webView = webViewForTestingAttachments();
816     {
817         ObserveAttachmentUpdatesForScope observer(webView.get());
818         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
819         EXPECT_EQ(1U, observer.observer().inserted.count);
820         attachment = observer.observer().inserted[0];
821     }
822
823     auto size = platformImageWithData([attachment synchronouslyRequestData:nil]).size;
824     EXPECT_EQ(215., size.width);
825     EXPECT_EQ(174., size.height);
826
827     {
828         ObserveAttachmentUpdatesForScope observer(webView.get());
829         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
830         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
831         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
832     }
833 }
834
835 TEST(WKAttachmentTests, InsertPastedAttributedStringContainingImage)
836 {
837     platformCopyRichTextWithImage();
838
839     RetainPtr<_WKAttachment> attachment;
840     auto webView = webViewForTestingAttachments();
841     {
842         ObserveAttachmentUpdatesForScope observer(webView.get());
843         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
844         EXPECT_EQ(0U, observer.observer().removed.count);
845         EXPECT_EQ(1U, observer.observer().inserted.count);
846         attachment = observer.observer().inserted[0];
847     }
848
849     [attachment expectRequestedDataToBe:testImageData()];
850     EXPECT_WK_STREQ("Lorem ipsum  dolor sit amet.", [webView stringByEvaluatingJavaScript:@"document.body.textContent"]);
851     EXPECT_WK_STREQ("image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
852
853     {
854         ObserveAttachmentUpdatesForScope observer(webView.get());
855         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
856         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
857         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
858     }
859 }
860
861 TEST(WKAttachmentTests, InsertPastedAttributedStringContainingMultipleAttachments)
862 {
863     platformCopyRichTextWithMultipleAttachments();
864
865     RetainPtr<_WKAttachment> imageAttachment;
866     RetainPtr<_WKAttachment> zipAttachment;
867     RetainPtr<_WKAttachment> pdfAttachment;
868     auto webView = webViewForTestingAttachments();
869     {
870         ObserveAttachmentUpdatesForScope observer(webView.get());
871         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
872         EXPECT_EQ(0U, observer.observer().removed.count);
873         EXPECT_EQ(3U, observer.observer().inserted.count);
874         for (_WKAttachment *attachment in observer.observer().inserted) {
875             NSData *data = [attachment synchronouslyRequestData:nil];
876             if ([data isEqualToData:testZIPData()])
877                 zipAttachment = attachment;
878             else if ([data isEqualToData:testPDFData()])
879                 pdfAttachment = attachment;
880             else if ([data isEqualToData:testImageData()])
881                 imageAttachment = attachment;
882         }
883     }
884
885     EXPECT_TRUE(zipAttachment && imageAttachment && pdfAttachment);
886     EXPECT_EQ(3, [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment').length"].integerValue);
887     EXPECT_WK_STREQ("image/png", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
888     EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]);
889
890     NSString *zipAttachmentType = [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[2].getAttribute('type')"];
891 #if USES_MODERN_ATTRIBUTED_STRING_CONVERSION
892     EXPECT_WK_STREQ("application/zip", zipAttachmentType);
893 #else
894     EXPECT_WK_STREQ("application/octet-stream", zipAttachmentType);
895 #endif
896
897     {
898         ObserveAttachmentUpdatesForScope observer(webView.get());
899         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
900         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
901         NSArray<_WKAttachment *> *removedAttachments = [observer.observer() removed];
902         EXPECT_EQ(3U, removedAttachments.count);
903         EXPECT_TRUE([removedAttachments containsObject:zipAttachment.get()]);
904         EXPECT_TRUE([removedAttachments containsObject:imageAttachment.get()]);
905         EXPECT_TRUE([removedAttachments containsObject:pdfAttachment.get()]);
906     }
907 }
908
909 TEST(WKAttachmentTests, DoNotInsertDataURLImagesAsAttachments)
910 {
911     auto webContentSourceView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]);
912     [webContentSourceView synchronouslyLoadTestPageNamed:@"apple-data-url"];
913     [webContentSourceView selectAll:nil];
914     [webContentSourceView _synchronouslyExecuteEditCommand:@"Copy" argument:nil];
915
916     auto webView = webViewForTestingAttachments();
917     {
918         ObserveAttachmentUpdatesForScope observer(webView.get());
919         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
920         EXPECT_EQ(0U, observer.observer().inserted.count);
921     }
922
923     EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"Boolean(document.querySelector('attachment'))"].boolValue);
924     EXPECT_EQ(1990, [webView stringByEvaluatingJavaScript:@"document.querySelector('img').src.length"].integerValue);
925     EXPECT_WK_STREQ("This is an apple", [webView stringByEvaluatingJavaScript:@"document.body.textContent"]);
926 }
927
928 #pragma mark - Platform-specific tests
929
930 #if PLATFORM(MAC)
931
932 TEST(WKAttachmentTestsMac, InsertPastedFileURLsAsAttachments)
933 {
934     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
935     [pasteboard clearContents];
936     [pasteboard declareTypes:@[NSFilenamesPboardType] owner:nil];
937     [pasteboard setPropertyList:@[testPDFFileURL().path, testImageFileURL().path] forType:NSFilenamesPboardType];
938
939     RetainPtr<NSArray<_WKAttachment *>> insertedAttachments;
940     auto webView = webViewForTestingAttachments();
941     {
942         ObserveAttachmentUpdatesForScope observer(webView.get());
943         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
944         insertedAttachments = [observer.observer() inserted];
945         EXPECT_EQ(2U, [insertedAttachments count]);
946     }
947
948     NSArray<NSData *> *expectedAttachmentData = @[ testPDFData(), testImageData() ];
949     EXPECT_TRUE([expectedAttachmentData containsObject:[[insertedAttachments firstObject] synchronouslyRequestData:nil]]);
950     EXPECT_TRUE([expectedAttachmentData containsObject:[[insertedAttachments lastObject] synchronouslyRequestData:nil]]);
951     EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
952     EXPECT_WK_STREQ("test.pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('title')"]);
953     EXPECT_WK_STREQ("image/png", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]);
954     EXPECT_WK_STREQ("icon.png", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('title')"]);
955
956     {
957         ObserveAttachmentUpdatesForScope observer(webView.get());
958         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
959         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
960         NSArray<_WKAttachment *> *removedAttachments = [observer.observer() removed];
961         EXPECT_EQ(2U, removedAttachments.count);
962         EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments firstObject]]);
963         EXPECT_TRUE([removedAttachments.lastObject isEqual:[insertedAttachments lastObject]]);
964     }
965 }
966
967 #endif // PLATFORM(MAC)
968
969 #if PLATFORM(IOS)
970
971 TEST(WKAttachmentTestsIOS, InsertDroppedImageAsAttachment)
972 {
973     auto webView = webViewForTestingAttachments();
974     auto draggingSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
975     auto item = adoptNS([[NSItemProvider alloc] init]);
976     [item registerData:testImageData() type:(NSString *)kUTTypePNG];
977     [draggingSimulator setExternalItemProviders:@[ item.get() ]];
978     [draggingSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
979
980     EXPECT_EQ(1U, [draggingSimulator insertedAttachments].count);
981     EXPECT_EQ(0U, [draggingSimulator removedAttachments].count);
982     auto attachment = retainPtr([draggingSimulator insertedAttachments].firstObject);
983     [attachment expectRequestedDataToBe:testImageData()];
984     EXPECT_WK_STREQ("public.png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
985
986     {
987         ObserveAttachmentUpdatesForScope observer(webView.get());
988         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
989         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
990         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
991     }
992 }
993
994 TEST(WKAttachmentTestsIOS, InsertDroppedAttributedStringContainingAttachment)
995 {
996     auto webView = webViewForTestingAttachments();
997     auto draggingSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
998     auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(NSString *)kUTTypePNG]);
999     auto item = adoptNS([[NSItemProvider alloc] init]);
1000     [item registerObject:[NSAttributedString attributedStringWithAttachment:image.get()] visibility:NSItemProviderRepresentationVisibilityAll];
1001
1002     [draggingSimulator setExternalItemProviders:@[ item.get() ]];
1003     [draggingSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
1004
1005     EXPECT_EQ(1U, [draggingSimulator insertedAttachments].count);
1006     EXPECT_EQ(0U, [draggingSimulator removedAttachments].count);
1007     auto attachment = retainPtr([draggingSimulator insertedAttachments].firstObject);
1008
1009     auto size = platformImageWithData([attachment synchronouslyRequestData:nil]).size;
1010     EXPECT_EQ(215., size.width);
1011     EXPECT_EQ(174., size.height);
1012     EXPECT_WK_STREQ("image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
1013
1014     {
1015         ObserveAttachmentUpdatesForScope observer(webView.get());
1016         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
1017         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
1018         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
1019     }
1020 }
1021
1022 TEST(WKAttachmentTestsIOS, InsertDroppedRichAndPlainTextFilesAsAttachments)
1023 {
1024     // Here, both rich text and plain text are content types that WebKit already understands how to insert in editable
1025     // areas in the absence of attachment elements. However, due to the explicitly set attachment presentation style
1026     // on the item providers, we should instead treat them as dropped files and insert attachment elements.
1027     // This exercises the scenario of dragging rich and plain text files from Files to Mail.
1028     auto richTextItem = adoptNS([[NSItemProvider alloc] init]);
1029     auto richText = adoptNS([[NSAttributedString alloc] initWithString:@"Hello world" attributes:@{ NSFontAttributeName: [UIFont boldSystemFontOfSize:12] }]);
1030     [richTextItem setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
1031     [richTextItem registerObject:richText.get() visibility:NSItemProviderRepresentationVisibilityAll];
1032     [richTextItem setSuggestedName:@"hello.rtf"];
1033
1034     auto plainTextItem = adoptNS([[NSItemProvider alloc] init]);
1035     [plainTextItem setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
1036     [plainTextItem registerObject:@"Hello world" visibility:NSItemProviderRepresentationVisibilityAll];
1037     [plainTextItem setSuggestedName:@"world.txt"];
1038
1039     auto webView = webViewForTestingAttachments();
1040     auto draggingSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
1041     [draggingSimulator setExternalItemProviders:@[ richTextItem.get(), plainTextItem.get() ]];
1042     [draggingSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
1043
1044     EXPECT_EQ(2U, [draggingSimulator insertedAttachments].count);
1045     EXPECT_EQ(0U, [draggingSimulator removedAttachments].count);
1046
1047     for (_WKAttachment *attachment in [draggingSimulator insertedAttachments]) {
1048         NSError *error = nil;
1049         EXPECT_GT([attachment synchronouslyRequestData:&error].length, 0U);
1050         EXPECT_TRUE(!error);
1051         if (error)
1052             NSLog(@"Error: %@", error);
1053     }
1054
1055     EXPECT_EQ(2, [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment').length"].intValue);
1056     EXPECT_WK_STREQ("hello.rtf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('title')"]);
1057     EXPECT_WK_STREQ("text/rtf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
1058     EXPECT_WK_STREQ("world.txt", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('title')"]);
1059     EXPECT_WK_STREQ("text/plain", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]);
1060 }
1061
1062 TEST(WKAttachmentTestsIOS, InsertDroppedZipArchiveAsAttachment)
1063 {
1064     // Since WebKit doesn't have any default DOM representation for ZIP archives, we should fall back to inserting
1065     // attachment elements. This exercises the flow of dragging a ZIP file from an app that doesn't specify a preferred
1066     // presentation style (e.g. Notes) into Mail.
1067     auto item = adoptNS([[NSItemProvider alloc] init]);
1068     NSData *data = testZIPData();
1069     [item registerData:data type:(NSString *)kUTTypeZipArchive];
1070     [item setSuggestedName:@"archive.zip"];
1071
1072     auto webView = webViewForTestingAttachments();
1073     auto draggingSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
1074     [draggingSimulator setExternalItemProviders:@[ item.get() ]];
1075     [draggingSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
1076
1077     EXPECT_EQ(1U, [draggingSimulator insertedAttachments].count);
1078     EXPECT_EQ(0U, [draggingSimulator removedAttachments].count);
1079     [[draggingSimulator insertedAttachments].firstObject expectRequestedDataToBe:data];
1080     EXPECT_EQ(1, [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment').length"].intValue);
1081     EXPECT_WK_STREQ("archive.zip", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
1082     EXPECT_WK_STREQ("application/zip", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
1083 }
1084
1085 TEST(WKAttachmentTestsIOS, InsertDroppedItemProvidersInOrder)
1086 {
1087     // Tests that item providers are inserted in the order they are specified. In this case, the two inserted attachments
1088     // should be separated by a link.
1089     auto firstAttachmentItem = adoptNS([[NSItemProvider alloc] init]);
1090     [firstAttachmentItem setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
1091     [firstAttachmentItem registerObject:@"FIRST" visibility:NSItemProviderRepresentationVisibilityAll];
1092     [firstAttachmentItem setSuggestedName:@"first.txt"];
1093
1094     auto inlineTextItem = adoptNS([[NSItemProvider alloc] init]);
1095     auto appleURL = retainPtr([NSURL URLWithString:@"https://www.apple.com/"]);
1096     [inlineTextItem registerObject:appleURL.get() visibility:NSItemProviderRepresentationVisibilityAll];
1097
1098     auto secondAttachmentItem = adoptNS([[NSItemProvider alloc] init]);
1099     [secondAttachmentItem registerData:testPDFData() type:(NSString *)kUTTypePDF];
1100     [secondAttachmentItem setSuggestedName:@"second.pdf"];
1101
1102     auto webView = webViewForTestingAttachments();
1103     auto draggingSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
1104     [draggingSimulator setExternalItemProviders:@[ firstAttachmentItem.get(), inlineTextItem.get(), secondAttachmentItem.get() ]];
1105     [draggingSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
1106
1107     EXPECT_EQ(2U, [draggingSimulator insertedAttachments].count);
1108     EXPECT_EQ(0U, [draggingSimulator removedAttachments].count);
1109
1110     for (_WKAttachment *attachment in [draggingSimulator insertedAttachments]) {
1111         NSError *error = nil;
1112         EXPECT_GT([attachment synchronouslyRequestData:&error].length, 0U);
1113         EXPECT_TRUE(!error);
1114         if (error)
1115             NSLog(@"Error: %@", error);
1116     }
1117
1118     NSArray *observedElementTags = (NSArray *)[webView objectByEvaluatingJavaScript:@"Array.from(document.body.children).map(e => e.tagName)"];
1119     NSArray *expectedElementTags = @[ @"ATTACHMENT", @"A", @"ATTACHMENT" ];
1120     EXPECT_TRUE([observedElementTags isEqualToArray:expectedElementTags]);
1121     if (![observedElementTags isEqualToArray:expectedElementTags])
1122         NSLog(@"Observed elements: %@ did not match expectations: %@", observedElementTags, expectedElementTags);
1123
1124     EXPECT_WK_STREQ("first.txt", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('title')"]);
1125     EXPECT_WK_STREQ("text/plain", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
1126     EXPECT_WK_STREQ([appleURL absoluteString], [webView valueOfAttribute:@"href" forQuerySelector:@"a"]);
1127     EXPECT_WK_STREQ("second.pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('title')"]);
1128     EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]);
1129 }
1130
1131 #endif // PLATFORM(IOS)
1132
1133 } // namespace TestWebKitAPI
1134
1135 #endif // WK_API_ENABLED && !PLATFORM(WATCHOS) && !PLATFORM(TVOS)