04ea16c77e62ef76d95850e62caebd9de5252816
[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 void platformCopyRichTextWithMultipleAttachments()
325 {
326     auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(NSString *)kUTTypePNG]);
327     auto pdf = adoptNS([[NSTextAttachment alloc] initWithData:testPDFData() ofType:(NSString *)kUTTypePDF]);
328     auto zip = adoptNS([[NSTextAttachment alloc] initWithData:testZIPData() ofType:(NSString *)kUTTypeZipArchive]);
329
330     auto richText = adoptNS([[NSMutableAttributedString alloc] init]);
331     [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:image.get()]];
332     [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:pdf.get()]];
333     [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:zip.get()]];
334
335 #if PLATFORM(MAC)
336     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
337     [pasteboard clearContents];
338     [pasteboard writeObjects:@[ richText.get() ]];
339 #elif PLATFORM(IOS)
340     auto item = adoptNS([[NSItemProvider alloc] init]);
341     [item registerObject:richText.get() visibility:NSItemProviderRepresentationVisibilityAll];
342     [UIPasteboard generalPasteboard].itemProviders = @[ item.get() ];
343 #endif
344 }
345
346 void platformCopyRichTextWithImage()
347 {
348     auto richText = adoptNS([[NSMutableAttributedString alloc] init]);
349     auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(NSString *)kUTTypePNG]);
350
351     [richText appendAttributedString:[[[NSAttributedString alloc] initWithString:@"Lorem ipsum "] autorelease]];
352     [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:image.get()]];
353     [richText appendAttributedString:[[[NSAttributedString alloc] initWithString:@" dolor sit amet."] autorelease]];
354
355 #if PLATFORM(MAC)
356     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
357     [pasteboard clearContents];
358     [pasteboard writeObjects:@[ richText.get() ]];
359 #elif PLATFORM(IOS)
360     auto item = adoptNS([[NSItemProvider alloc] init]);
361     [item registerObject:richText.get() visibility:NSItemProviderRepresentationVisibilityAll];
362     [UIPasteboard generalPasteboard].itemProviders = @[ item.get() ];
363 #endif
364 }
365
366 typedef void(^ItemProviderDataLoadHandler)(NSData *, NSError *);
367
368 void platformCopyPNG()
369 {
370 #if PLATFORM(MAC)
371     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
372     [pasteboard declareTypes:@[NSPasteboardTypePNG] owner:nil];
373     [pasteboard setData:testImageData() forType:NSPasteboardTypePNG];
374 #elif PLATFORM(IOS)
375     UIPasteboard *pasteboard = [UIPasteboard generalPasteboard];
376     auto item = adoptNS([[UIItemProvider alloc] init]);
377     [item setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
378     [item registerDataRepresentationForTypeIdentifier:(NSString *)kUTTypePNG visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[] (ItemProviderDataLoadHandler completionHandler) -> NSProgress * {
379         completionHandler(testImageData(), nil);
380         return nil;
381     }];
382     pasteboard.itemProviders = @[ item.get() ];
383 #endif
384 }
385
386 #if PLATFORM(MAC)
387 typedef NSImage PlatformImage;
388 #else
389 typedef UIImage PlatformImage;
390 #endif
391
392 PlatformImage *platformImageWithData(NSData *data)
393 {
394 #if PLATFORM(MAC)
395     return [[[NSImage alloc] initWithData:data] autorelease];
396 #else
397     return [UIImage imageWithData:data];
398 #endif
399 }
400
401 namespace TestWebKitAPI {
402
403 #pragma mark - Platform-agnostic tests
404
405 TEST(WKAttachmentTests, AttachmentElementInsertion)
406 {
407     auto webView = webViewForTestingAttachments();
408     RetainPtr<_WKAttachment> firstAttachment;
409     RetainPtr<_WKAttachment> secondAttachment;
410     {
411         ObserveAttachmentUpdatesForScope observer(webView.get());
412         // Use the given content type for the attachment element's type.
413         firstAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo" contentType:@"text/html" data:testHTMLData() options:nil];
414         EXPECT_WK_STREQ(@"foo", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
415         EXPECT_WK_STREQ(@"text/html", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
416         EXPECT_WK_STREQ(@"38 bytes", [webView valueOfAttribute:@"subtitle" forQuerySelector:@"attachment"]);
417         observer.expectAttachmentUpdates(@[ ], @[ firstAttachment.get() ]);
418     }
419
420     [firstAttachment expectRequestedDataToBe:testHTMLData()];
421
422     {
423         ObserveAttachmentUpdatesForScope scope(webView.get());
424         // Since no content type is explicitly specified, compute it from the file extension.
425         [webView _executeEditCommand:@"DeleteBackward" argument:nil completion:nil];
426         secondAttachment = [webView synchronouslyInsertAttachmentWithFilename:@"bar.png" contentType:nil data:testImageData() options:nil];
427         EXPECT_WK_STREQ(@"bar.png", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
428         EXPECT_WK_STREQ(@"image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
429         EXPECT_WK_STREQ(@"37 KB", [webView valueOfAttribute:@"subtitle" forQuerySelector:@"attachment"]);
430         scope.expectAttachmentUpdates(@[ firstAttachment.get() ], @[ secondAttachment.get() ]);
431     }
432
433     [firstAttachment expectRequestedDataToBe:nil];
434     [secondAttachment expectRequestedDataToBe:testImageData()];
435     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
436     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
437 }
438
439 TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingAndDeletingNewline)
440 {
441     auto webView = webViewForTestingAttachments();
442     RetainPtr<_WKAttachment> attachment;
443     {
444         ObserveAttachmentUpdatesForScope observer(webView.get());
445         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil];
446         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
447     }
448     [webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
449     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
450     [webView stringByEvaluatingJavaScript:@"getSelection().collapse(document.body)"];
451     [webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
452
453     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
454     [attachment expectRequestedDataToBe:testHTMLData()];
455
456     [webView expectUpdatesAfterCommand:@"DeleteForward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
457     [attachment expectRequestedDataToBe:nil];
458 }
459
460 TEST(WKAttachmentTests, AttachmentUpdatesWhenUndoingAndRedoing)
461 {
462     auto webView = webViewForTestingAttachments();
463     RetainPtr<NSData> htmlData = testHTMLData();
464     RetainPtr<_WKAttachment> attachment;
465     {
466         ObserveAttachmentUpdatesForScope observer(webView.get());
467         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil];
468         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
469     }
470     [webView expectUpdatesAfterCommand:@"Undo" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
471     [attachment expectRequestedDataToBe:nil];
472
473     [webView expectUpdatesAfterCommand:@"Redo" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
474     [attachment expectRequestedDataToBe:htmlData.get()];
475
476     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
477     [attachment expectRequestedDataToBe:nil];
478
479     [webView expectUpdatesAfterCommand:@"Undo" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
480     [attachment expectRequestedDataToBe:htmlData.get()];
481
482     [webView expectUpdatesAfterCommand:@"Redo" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
483     [attachment expectRequestedDataToBe:nil];
484 }
485
486 TEST(WKAttachmentTests, AttachmentUpdatesWhenChangingFontStyles)
487 {
488     auto webView = webViewForTestingAttachments();
489     RetainPtr<_WKAttachment> attachment;
490     [webView _synchronouslyExecuteEditCommand:@"InsertText" argument:@"Hello"];
491     {
492         ObserveAttachmentUpdatesForScope observer(webView.get());
493         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil];
494         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
495     }
496     [webView expectUpdatesAfterCommand:@"InsertText" withArgument:@"World" expectedRemovals:@[] expectedInsertions:@[]];
497     [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
498     [webView expectUpdatesAfterCommand:@"ToggleBold" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
499     [webView expectUpdatesAfterCommand:@"ToggleItalic" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
500     [webView expectUpdatesAfterCommand:@"ToggleUnderline" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
501     [attachment expectRequestedDataToBe:testHTMLData()];
502     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
503     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
504
505     // Inserting text should delete the current selection, removing the attachment in the process.
506     [webView expectUpdatesAfterCommand:@"InsertText" withArgument:@"foo" expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
507     [attachment expectRequestedDataToBe:nil];
508 }
509
510 TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingLists)
511 {
512     auto webView = webViewForTestingAttachments();
513     RetainPtr<_WKAttachment> attachment;
514     {
515         ObserveAttachmentUpdatesForScope observer(webView.get());
516         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil];
517         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
518     }
519     [webView expectUpdatesAfterCommand:@"InsertOrderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
520     // This edit command behaves more like a "toggle", and will actually break us out of the list we just inserted.
521     [webView expectUpdatesAfterCommand:@"InsertOrderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
522     [webView expectUpdatesAfterCommand:@"InsertUnorderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
523     [webView expectUpdatesAfterCommand:@"InsertUnorderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
524     [attachment expectRequestedDataToBe:testHTMLData()];
525     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
526     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
527 }
528
529 TEST(WKAttachmentTests, AttachmentUpdatesWhenInsertingRichMarkup)
530 {
531     auto webView = webViewForTestingAttachments();
532     RetainPtr<_WKAttachment> attachment;
533     {
534         ObserveAttachmentUpdatesForScope observer(webView.get());
535         [webView _synchronouslyExecuteEditCommand:@"InsertHTML" argument:@"<div><strong><attachment title='a' webkitattachmentid='a06fec41-9aa0-4c2c-ba3a-0149b54aad99'></attachment></strong></div>"];
536         attachment = observer.observer().inserted[0];
537         observer.expectAttachmentUpdates(@[ ], @[attachment.get()]);
538     }
539     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
540     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
541     {
542         ObserveAttachmentUpdatesForScope observer(webView.get());
543         [webView stringByEvaluatingJavaScript:@"document.querySelector('attachment').remove()"];
544         [webView waitForNextPresentationUpdate];
545         observer.expectAttachmentUpdates(@[attachment.get()], @[ ]);
546     }
547     [attachment expectRequestedDataToBe:nil];
548 }
549
550 TEST(WKAttachmentTests, AttachmentUpdatesWhenCuttingAndPasting)
551 {
552     auto webView = webViewForTestingAttachments();
553     RetainPtr<_WKAttachment> attachment;
554     {
555         ObserveAttachmentUpdatesForScope observer(webView.get());
556         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:testHTMLData() options:nil];
557         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
558     }
559     [attachment expectRequestedDataToBe:testHTMLData()];
560     [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
561     {
562         ObserveAttachmentUpdatesForScope observer(webView.get());
563         [webView _synchronouslyExecuteEditCommand:@"Cut" argument:nil];
564         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
565     }
566     [attachment expectRequestedDataToBe:nil];
567     {
568         ObserveAttachmentUpdatesForScope observer(webView.get());
569         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
570         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
571     }
572     [attachment expectRequestedDataToBe:testHTMLData()];
573     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentbloburl" forQuerySelector:@"attachment"]);
574     EXPECT_FALSE([webView hasAttribute:@"webkitattachmentpath" forQuerySelector:@"attachment"]);
575 }
576
577 TEST(WKAttachmentTests, AttachmentDataForEmptyFile)
578 {
579     auto webView = webViewForTestingAttachments();
580     RetainPtr<_WKAttachment> attachment;
581     {
582         ObserveAttachmentUpdatesForScope observer(webView.get());
583         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"empty.txt" contentType:@"text/plain" data:[NSData data] options:nil];
584         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
585     }
586     [attachment expectRequestedDataToBe:[NSData data]];
587     {
588         ObserveAttachmentUpdatesForScope scope(webView.get());
589         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
590         scope.expectAttachmentUpdates(@[attachment.get()], @[]);
591     }
592     [attachment expectRequestedDataToBe:nil];
593 }
594
595 TEST(WKAttachmentTests, MultipleSimultaneousAttachmentDataRequests)
596 {
597     auto webView = webViewForTestingAttachments();
598     RetainPtr<NSData> htmlData = testHTMLData();
599     RetainPtr<_WKAttachment> attachment;
600     {
601         ObserveAttachmentUpdatesForScope observer(webView.get());
602         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"foo.txt" contentType:@"text/plain" data:htmlData.get() options:nil];
603         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
604     }
605     __block RetainPtr<NSData> dataForFirstRequest;
606     __block RetainPtr<NSData> dataForSecondRequest;
607     __block bool done = false;
608     [attachment requestData:^(NSData *data, NSError *error) {
609         EXPECT_TRUE(!error);
610         dataForFirstRequest = data;
611     }];
612     [attachment requestData:^(NSData *data, NSError *error) {
613         EXPECT_TRUE(!error);
614         dataForSecondRequest = data;
615         done = true;
616     }];
617
618     Util::run(&done);
619
620     EXPECT_TRUE([dataForFirstRequest isEqualToData:htmlData.get()]);
621     EXPECT_TRUE([dataForSecondRequest isEqualToData:htmlData.get()]);
622 }
623
624 TEST(WKAttachmentTests, InPlaceImageAttachmentToggleDisplayMode)
625 {
626     auto webView = webViewForTestingAttachments();
627     RetainPtr<NSData> imageData = testImageData();
628     RetainPtr<_WKAttachment> attachment;
629     {
630         ObserveAttachmentUpdatesForScope observer(webView.get());
631         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"icon.png" contentType:@"image/png" data:imageData.get() options:displayOptionsWithMode(_WKAttachmentDisplayModeAsIcon)];
632         [attachment expectRequestedDataToBe:imageData.get()];
633         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
634     }
635     CGSize iconAttachmentSize = [webView attachmentElementSize];
636
637     [attachment synchronouslySetDisplayOptions:displayOptionsWithMode(_WKAttachmentDisplayModeInPlace) error:nil];
638     [attachment expectRequestedDataToBe:imageData.get()];
639     [webView waitForAttachmentElementSizeToBecome:CGSizeMake(215, 174)];
640
641     [attachment synchronouslySetDisplayOptions:displayOptionsWithMode(_WKAttachmentDisplayModeAsIcon) error:nil];
642     [attachment expectRequestedDataToBe:imageData.get()];
643     [webView waitForAttachmentElementSizeToBecome:iconAttachmentSize];
644
645     [attachment synchronouslySetDisplayOptions:displayOptionsWithMode(_WKAttachmentDisplayModeInPlace) error:nil];
646     [attachment expectRequestedDataToBe:imageData.get()];
647     [webView waitForAttachmentElementSizeToBecome:CGSizeMake(215, 174)];
648 }
649
650 TEST(WKAttachmentTests, InPlaceImageAttachmentParagraphInsertion)
651 {
652     auto webView = webViewForTestingAttachments();
653     RetainPtr<NSData> imageData = testImageData();
654     RetainPtr<_WKAttachment> attachment;
655     {
656         ObserveAttachmentUpdatesForScope observer(webView.get());
657         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"icon.png" contentType:@"image/png" data:imageData.get() options:displayOptionsWithMode(_WKAttachmentDisplayModeInPlace)];
658         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
659     }
660     [webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
661     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
662     [webView stringByEvaluatingJavaScript:@"getSelection().collapse(document.body)"];
663     [webView expectUpdatesAfterCommand:@"InsertParagraph" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
664     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
665
666     [attachment expectRequestedDataToBe:imageData.get()];
667     [webView waitForAttachmentElementSizeToBecome:CGSizeMake(215, 174)];
668
669     [webView expectUpdatesAfterCommand:@"DeleteForward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
670 }
671
672 TEST(WKAttachmentTests, InPlaceVideoAttachmentInsertionWithinList)
673 {
674     auto webView = webViewForTestingAttachments();
675     RetainPtr<NSData> videoData = testVideoData();
676     RetainPtr<_WKAttachment> attachment;
677
678     [webView _synchronouslyExecuteEditCommand:@"InsertOrderedList" argument:nil];
679     {
680         ObserveAttachmentUpdatesForScope observer(webView.get());
681         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"test.mp4" contentType:@"video/mp4" data:videoData.get() options:displayOptionsWithMode(_WKAttachmentDisplayModeInPlace)];
682         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
683     }
684     [webView waitForAttachmentElementSizeToBecome:CGSizeMake(320, 240)];
685
686     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
687     [webView expectUpdatesAfterCommand:@"Undo" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
688     [webView expectUpdatesAfterCommand:@"InsertOrderedList" withArgument:nil expectedRemovals:@[] expectedInsertions:@[]];
689
690     [webView waitForAttachmentElementSizeToBecome:CGSizeMake(320, 240)];
691     [attachment expectRequestedDataToBe:videoData.get()];
692 }
693
694 TEST(WKAttachmentTests, InPlacePDFAttachmentCutAndPaste)
695 {
696     auto webView = webViewForTestingAttachments();
697     RetainPtr<NSData> pdfData = testPDFData();
698     RetainPtr<_WKAttachment> attachment;
699     {
700         ObserveAttachmentUpdatesForScope observer(webView.get());
701         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"test.pdf" contentType:@"application/pdf" data:pdfData.get() options:displayOptionsWithMode(_WKAttachmentDisplayModeInPlace)];
702         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
703         [webView waitForAttachmentElementSizeToBecome:CGSizeMake(130, 29)];
704     }
705
706     [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
707     [webView expectUpdatesAfterCommand:@"Cut" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
708
709     [webView expectUpdatesAfterCommand:@"Paste" withArgument:nil expectedRemovals:@[] expectedInsertions:@[attachment.get()]];
710     [webView waitForAttachmentElementSizeToBecome:CGSizeMake(130, 29)];
711     [attachment expectRequestedDataToBe:pdfData.get()];
712 }
713
714 TEST(WKAttachmentTests, ChangeAttachmentDataAndFileInformation)
715 {
716     auto webView = webViewForTestingAttachments();
717     RetainPtr<_WKAttachment> attachment;
718     {
719         RetainPtr<NSData> pdfData = testPDFData();
720         ObserveAttachmentUpdatesForScope observer(webView.get());
721         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"test.pdf" contentType:@"application/pdf" data:pdfData.get() options:displayOptionsWithMode(_WKAttachmentDisplayModeAsIcon)];
722         [attachment expectRequestedDataToBe:pdfData.get()];
723         EXPECT_WK_STREQ(@"test.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
724         EXPECT_WK_STREQ(@"application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
725         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
726     }
727     {
728         RetainPtr<NSData> imageData = testImageData();
729         ObserveAttachmentUpdatesForScope observer(webView.get());
730         [attachment synchronouslySetData:imageData.get() newContentType:@"image/png" newFilename:@"icon.png" error:nil];
731         [attachment expectRequestedDataToBe:imageData.get()];
732         EXPECT_WK_STREQ(@"icon.png", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
733         EXPECT_WK_STREQ(@"image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
734         observer.expectAttachmentUpdates(@[], @[]);
735     }
736     {
737         RetainPtr<NSData> textData = testHTMLData();
738         ObserveAttachmentUpdatesForScope observer(webView.get());
739         [attachment synchronouslySetDisplayOptions:displayOptionsWithMode(_WKAttachmentDisplayModeAsIcon) error:nil];
740         // The new content type should be inferred from the file name.
741         [attachment synchronouslySetData:textData.get() newContentType:nil newFilename:@"foo.txt" error:nil];
742         [attachment expectRequestedDataToBe:textData.get()];
743         EXPECT_WK_STREQ(@"foo.txt", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
744         EXPECT_WK_STREQ(@"text/plain", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
745         observer.expectAttachmentUpdates(@[], @[]);
746     }
747     {
748         RetainPtr<NSData> secondTextData = [@"Hello world" dataUsingEncoding:NSUTF8StringEncoding];
749         ObserveAttachmentUpdatesForScope observer(webView.get());
750         // Both the previous file name and type should be inferred.
751         [attachment synchronouslySetData:secondTextData.get() newContentType:nil newFilename:nil error:nil];
752         [attachment expectRequestedDataToBe:secondTextData.get()];
753         EXPECT_WK_STREQ(@"foo.txt", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
754         EXPECT_WK_STREQ(@"text/plain", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
755         observer.expectAttachmentUpdates(@[], @[]);
756     }
757     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
758 }
759
760 TEST(WKAttachmentTests, ChangeAttachmentDataUpdatesWithInPlaceDisplay)
761 {
762     auto webView = webViewForTestingAttachments();
763     RetainPtr<_WKAttachment> attachment;
764     {
765         RetainPtr<NSData> pdfData = testPDFData();
766         ObserveAttachmentUpdatesForScope observer(webView.get());
767         attachment = [webView synchronouslyInsertAttachmentWithFilename:@"test.pdf" contentType:@"application/pdf" data:pdfData.get() options:displayOptionsWithMode(_WKAttachmentDisplayModeInPlace)];
768         [webView waitForAttachmentElementSizeToBecome:CGSizeMake(130, 29)];
769         [attachment expectRequestedDataToBe:pdfData.get()];
770         EXPECT_WK_STREQ(@"test.pdf", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
771         EXPECT_WK_STREQ(@"application/pdf", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
772         observer.expectAttachmentUpdates(@[], @[attachment.get()]);
773     }
774     {
775         RetainPtr<NSData> videoData = testVideoData();
776         ObserveAttachmentUpdatesForScope observer(webView.get());
777         [attachment synchronouslySetData:videoData.get() newContentType:@"video/mp4" newFilename:@"test.mp4" error:nil];
778         [webView waitForAttachmentElementSizeToBecome:CGSizeMake(320, 240)];
779         [attachment expectRequestedDataToBe:videoData.get()];
780         EXPECT_WK_STREQ(@"test.mp4", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
781         EXPECT_WK_STREQ(@"video/mp4", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
782         observer.expectAttachmentUpdates(@[], @[]);
783     }
784     {
785         RetainPtr<NSData> imageData = testImageData();
786         ObserveAttachmentUpdatesForScope observer(webView.get());
787         [attachment synchronouslySetData:imageData.get() newContentType:@"image/png" newFilename:@"icon.png" error:nil];
788         [webView waitForAttachmentElementSizeToBecome:CGSizeMake(215, 174)];
789         [attachment expectRequestedDataToBe:imageData.get()];
790         EXPECT_WK_STREQ(@"icon.png", [webView valueOfAttribute:@"title" forQuerySelector:@"attachment"]);
791         EXPECT_WK_STREQ(@"image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
792         observer.expectAttachmentUpdates(@[], @[]);
793     }
794     [webView expectUpdatesAfterCommand:@"DeleteBackward" withArgument:nil expectedRemovals:@[attachment.get()] expectedInsertions:@[]];
795 }
796
797 TEST(WKAttachmentTests, InsertPastedImageAsAttachment)
798 {
799     platformCopyPNG();
800
801     RetainPtr<_WKAttachment> attachment;
802     auto webView = webViewForTestingAttachments();
803     {
804         ObserveAttachmentUpdatesForScope observer(webView.get());
805         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
806         EXPECT_EQ(1U, observer.observer().inserted.count);
807         attachment = observer.observer().inserted[0];
808     }
809
810     auto size = platformImageWithData([attachment synchronouslyRequestData:nil]).size;
811     EXPECT_EQ(215., size.width);
812     EXPECT_EQ(174., size.height);
813
814     {
815         ObserveAttachmentUpdatesForScope observer(webView.get());
816         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
817         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
818         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
819     }
820 }
821
822 TEST(WKAttachmentTests, InsertPastedAttributedStringContainingImage)
823 {
824     platformCopyRichTextWithImage();
825
826     RetainPtr<_WKAttachment> attachment;
827     auto webView = webViewForTestingAttachments();
828     {
829         ObserveAttachmentUpdatesForScope observer(webView.get());
830         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
831         EXPECT_EQ(0U, observer.observer().removed.count);
832         EXPECT_EQ(1U, observer.observer().inserted.count);
833         attachment = observer.observer().inserted[0];
834     }
835
836     [attachment expectRequestedDataToBe:testImageData()];
837     EXPECT_WK_STREQ("Lorem ipsum  dolor sit amet.", [webView stringByEvaluatingJavaScript:@"document.body.textContent"]);
838     EXPECT_WK_STREQ("image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
839
840     {
841         ObserveAttachmentUpdatesForScope observer(webView.get());
842         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
843         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
844         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
845     }
846 }
847
848 TEST(WKAttachmentTests, InsertPastedAttributedStringContainingMultipleAttachments)
849 {
850     platformCopyRichTextWithMultipleAttachments();
851
852     RetainPtr<_WKAttachment> imageAttachment;
853     RetainPtr<_WKAttachment> zipAttachment;
854     RetainPtr<_WKAttachment> pdfAttachment;
855     auto webView = webViewForTestingAttachments();
856     {
857         ObserveAttachmentUpdatesForScope observer(webView.get());
858         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
859         EXPECT_EQ(0U, observer.observer().removed.count);
860         EXPECT_EQ(3U, observer.observer().inserted.count);
861         for (_WKAttachment *attachment in observer.observer().inserted) {
862             NSData *data = [attachment synchronouslyRequestData:nil];
863             if ([data isEqualToData:testZIPData()])
864                 zipAttachment = attachment;
865             else if ([data isEqualToData:testPDFData()])
866                 pdfAttachment = attachment;
867             else if ([data isEqualToData:testImageData()])
868                 imageAttachment = attachment;
869         }
870     }
871
872     EXPECT_TRUE(zipAttachment && imageAttachment && pdfAttachment);
873     EXPECT_EQ(3, [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment').length"].integerValue);
874     EXPECT_WK_STREQ("image/png", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
875     EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]);
876
877     NSString *zipAttachmentType = [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[2].getAttribute('type')"];
878 #if USES_MODERN_ATTRIBUTED_STRING_CONVERSION
879     EXPECT_WK_STREQ("application/zip", zipAttachmentType);
880 #else
881     EXPECT_WK_STREQ("application/octet-stream", zipAttachmentType);
882 #endif
883
884     {
885         ObserveAttachmentUpdatesForScope observer(webView.get());
886         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
887         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
888         NSArray<_WKAttachment *> *removedAttachments = [observer.observer() removed];
889         EXPECT_EQ(3U, removedAttachments.count);
890         EXPECT_TRUE([removedAttachments containsObject:zipAttachment.get()]);
891         EXPECT_TRUE([removedAttachments containsObject:imageAttachment.get()]);
892         EXPECT_TRUE([removedAttachments containsObject:pdfAttachment.get()]);
893     }
894 }
895
896 TEST(WKAttachmentTests, DoNotInsertDataURLImagesAsAttachments)
897 {
898     auto webContentSourceView = adoptNS([[TestWKWebView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)]);
899     [webContentSourceView synchronouslyLoadTestPageNamed:@"apple-data-url"];
900     [webContentSourceView selectAll:nil];
901     [webContentSourceView _synchronouslyExecuteEditCommand:@"Copy" argument:nil];
902
903     auto webView = webViewForTestingAttachments();
904     {
905         ObserveAttachmentUpdatesForScope observer(webView.get());
906         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
907         EXPECT_EQ(0U, observer.observer().inserted.count);
908     }
909
910     EXPECT_FALSE([webView stringByEvaluatingJavaScript:@"Boolean(document.querySelector('attachment'))"].boolValue);
911     EXPECT_EQ(1990, [webView stringByEvaluatingJavaScript:@"document.querySelector('img').src.length"].integerValue);
912     EXPECT_WK_STREQ("This is an apple", [webView stringByEvaluatingJavaScript:@"document.body.textContent"]);
913 }
914
915 #pragma mark - Platform-specific tests
916
917 #if PLATFORM(MAC)
918
919 TEST(WKAttachmentTestsMac, InsertPastedFileURLsAsAttachments)
920 {
921     NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
922     [pasteboard clearContents];
923     [pasteboard declareTypes:@[NSFilenamesPboardType] owner:nil];
924     [pasteboard setPropertyList:@[testPDFFileURL().path, testImageFileURL().path] forType:NSFilenamesPboardType];
925
926     RetainPtr<NSArray<_WKAttachment *>> insertedAttachments;
927     auto webView = webViewForTestingAttachments();
928     {
929         ObserveAttachmentUpdatesForScope observer(webView.get());
930         [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
931         insertedAttachments = [observer.observer() inserted];
932         EXPECT_EQ(2U, [insertedAttachments count]);
933     }
934
935     NSArray<NSData *> *expectedAttachmentData = @[ testPDFData(), testImageData() ];
936     EXPECT_TRUE([expectedAttachmentData containsObject:[[insertedAttachments firstObject] synchronouslyRequestData:nil]]);
937     EXPECT_TRUE([expectedAttachmentData containsObject:[[insertedAttachments lastObject] synchronouslyRequestData:nil]]);
938     EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]);
939     EXPECT_WK_STREQ("test.pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('title')"]);
940     EXPECT_WK_STREQ("image/png", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]);
941     EXPECT_WK_STREQ("icon.png", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('title')"]);
942
943     {
944         ObserveAttachmentUpdatesForScope observer(webView.get());
945         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
946         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
947         NSArray<_WKAttachment *> *removedAttachments = [observer.observer() removed];
948         EXPECT_EQ(2U, removedAttachments.count);
949         EXPECT_TRUE([removedAttachments containsObject:[insertedAttachments firstObject]]);
950         EXPECT_TRUE([removedAttachments.lastObject isEqual:[insertedAttachments lastObject]]);
951     }
952 }
953
954 #endif // PLATFORM(MAC)
955
956 #if PLATFORM(IOS)
957
958 TEST(WKAttachmentTestsIOS, InsertDroppedImageAsAttachment)
959 {
960     auto webView = webViewForTestingAttachments();
961     auto draggingSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
962     auto item = adoptNS([[NSItemProvider alloc] init]);
963     [item setPreferredPresentationStyle:UIPreferredPresentationStyleAttachment];
964     [item registerDataRepresentationForTypeIdentifier:(NSString *)kUTTypePNG visibility:NSItemProviderRepresentationVisibilityAll loadHandler:[] (ItemProviderDataLoadHandler completionHandler) -> NSProgress * {
965         completionHandler(testImageData(), nil);
966         return nil;
967     }];
968     [draggingSimulator setExternalItemProviders:@[ item.get() ]];
969     [draggingSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
970
971     EXPECT_EQ(1U, [draggingSimulator insertedAttachments].count);
972     EXPECT_EQ(0U, [draggingSimulator removedAttachments].count);
973     auto attachment = retainPtr([draggingSimulator insertedAttachments].firstObject);
974     [attachment expectRequestedDataToBe:testImageData()];
975     EXPECT_WK_STREQ("public.png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
976
977     {
978         ObserveAttachmentUpdatesForScope observer(webView.get());
979         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
980         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
981         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
982     }
983 }
984
985 TEST(WKAttachmentTestsIOS, InsertDroppedAttributedStringContainingAttachment)
986 {
987     auto webView = webViewForTestingAttachments();
988     auto draggingSimulator = adoptNS([[DataInteractionSimulator alloc] initWithWebView:webView.get()]);
989     auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(NSString *)kUTTypePNG]);
990     auto item = adoptNS([[NSItemProvider alloc] init]);
991     [item registerObject:[NSAttributedString attributedStringWithAttachment:image.get()] visibility:NSItemProviderRepresentationVisibilityAll];
992
993     [draggingSimulator setExternalItemProviders:@[ item.get() ]];
994     [draggingSimulator runFrom:CGPointZero to:CGPointMake(50, 50)];
995
996     EXPECT_EQ(1U, [draggingSimulator insertedAttachments].count);
997     EXPECT_EQ(0U, [draggingSimulator removedAttachments].count);
998     auto attachment = retainPtr([draggingSimulator insertedAttachments].firstObject);
999
1000     auto size = platformImageWithData([attachment synchronouslyRequestData:nil]).size;
1001     EXPECT_EQ(215., size.width);
1002     EXPECT_EQ(174., size.height);
1003     EXPECT_WK_STREQ("image/png", [webView valueOfAttribute:@"type" forQuerySelector:@"attachment"]);
1004
1005     {
1006         ObserveAttachmentUpdatesForScope observer(webView.get());
1007         [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil];
1008         [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil];
1009         observer.expectAttachmentUpdates(@[attachment.get()], @[]);
1010     }
1011 }
1012
1013 #endif // PLATFORM(IOS)
1014
1015 } // namespace TestWebKitAPI
1016
1017 #endif // WK_API_ENABLED && !PLATFORM(WATCHOS) && !PLATFORM(TVOS)