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