Changeset 226093 in webkit
- Timestamp:
- Dec 18, 2017, 8:03:21 PM (7 years ago)
- Location:
- trunk
- Files:
-
- 8 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/WebCore/ChangeLog
r226090 r226093 1 2017-12-18 Wenson Hsieh <wenson_hsieh@apple.com> 2 3 [Attachment Support] Support representing pasted or dropped content using attachment elements 4 https://bugs.webkit.org/show_bug.cgi?id=180892 5 <rdar://problem/36064210> 6 7 Reviewed by Tim Horton. 8 9 Support dropping and pasting attributed strings that contain NSTextAttachments. Teaches 10 replaceRichContentWithAttachmentsIfNecessary to replace object elements with attachments; see comments below for 11 more details. 12 13 Test: WKAttachmentTests.InsertPastedAttributedStringContainingMultipleAttachments 14 15 * editing/WebContentReader.h: 16 17 Add BlobReplacementInfo, which contains a map of blob URLs to replacement Blobs, as well as a map of blob URLs 18 to replaced subresource URLs. 19 20 (WebCore::BlobReplacementInfo::isEmpty const): 21 * editing/cocoa/EditorCocoa.mm: 22 (WebCore::Editor::replaceSelectionWithAttributedString): 23 * editing/cocoa/WebArchiveResourceFromNSAttributedString.h: 24 * editing/cocoa/WebArchiveResourceFromNSAttributedString.mm: 25 26 Implement -[WebArchiveResourceFromNSAttributedString MIMEType]. UIFoundation asks for -MIMEType in the process 27 of generating markup from NSTextAttachments; this currently causes the web process to crash on an unrecognized 28 selector. 29 30 Additionally, work around <rdar://problem/36074429>, a UIFoundation bug in which all but a few hard-coded file 31 extensions actually yield MIME types that are more specific than "application/octet-stream". This can safely be 32 removed once <rdar://problem/36074429> is addressed. 33 34 (-[WebArchiveResourceFromNSAttributedString MIMEType]): 35 * editing/cocoa/WebContentReaderCocoa.mm: 36 (WebCore::replaceRichContentWithAttachmentsIfNecessary): 37 38 Try to replace object elements with attachments, and also tweak the title of the attachment's File to use the 39 replaced subresource's filename if possible. Additionally, abstracts out information for replacing object or 40 image elements (formerly a pair of { File, Element }) into a separate struct, and add a AttachmentDisplayMode 41 parameter to determine whether the attachment should be presented in-line (in the case of images), or as an icon. 42 43 (WebCore::attributesForAttributedStringConversion): 44 45 Only exclude object elements from being generated from NSTextAttachments if the attachment element runtime 46 feature is disabled, or !ENABLE(ATTACHMENT_ELEMENT). 47 48 (WebCore::createFragmentAndAddResources): 49 50 Additionally keep track of a mapping from blob URL => replaced subresource URL. In all the places where we 51 previously only plumbed a map of blob URL => Blob, use a BlobReplacementInfo struct instead, which now includes 52 a map from blob URL => replaced URL. 53 54 (WebCore::sanitizeMarkupWithArchive): 55 (WebCore::WebContentReader::readWebArchive): 56 (WebCore::WebContentMarkupReader::readWebArchive): 57 (WebCore::WebContentReader::readRTFD): 58 (WebCore::WebContentMarkupReader::readRTFD): 59 (WebCore::WebContentReader::readRTF): 60 (WebCore::WebContentMarkupReader::readRTF): 61 (WebCore::WebContentReader::readImage): 62 1 63 2017-12-18 Youenn Fablet <youenn@apple.com> 2 64 -
trunk/Source/WebCore/editing/WebContentReader.h
r226085 r226093 107 107 }; 108 108 109 void replaceRichContentWithAttachmentsIfNecessary(DocumentFragment&, HashMap<AtomicString, RefPtr<Blob>>&& urlToBlobMap); 110 RefPtr<DocumentFragment> createFragmentAndAddResources(Frame&, NSAttributedString*, HashMap<AtomicString, RefPtr<Blob>>& urlToBlobMap); 109 struct BlobReplacementInfo { 110 HashMap<AtomicString, RefPtr<Blob>> blobURLToBlobMap; 111 HashMap<AtomicString, AtomicString> blobURLToReplacedURLMap; 112 bool isEmpty() const { return blobURLToBlobMap.isEmpty(); } 113 }; 114 115 void replaceRichContentWithAttachmentsIfNecessary(DocumentFragment&, BlobReplacementInfo&&); 116 RefPtr<DocumentFragment> createFragmentAndAddResources(Frame&, NSAttributedString*, BlobReplacementInfo&); 111 117 #endif 112 118 -
trunk/Source/WebCore/editing/cocoa/EditorCocoa.mm
r226085 r226093 223 223 224 224 if (m_frame.selection().selection().isContentRichlyEditable()) { 225 HashMap<AtomicString, RefPtr<Blob>> urlToBlobMap;226 if (auto fragment = createFragmentAndAddResources(m_frame, attributedString, urlToBlobMap)) {227 replaceRichContentWithAttachmentsIfNecessary(*fragment, WTFMove( urlToBlobMap));225 BlobReplacementInfo replacementInfo; 226 if (auto fragment = createFragmentAndAddResources(m_frame, attributedString, replacementInfo)) { 227 replaceRichContentWithAttachmentsIfNecessary(*fragment, WTFMove(replacementInfo)); 228 228 if (shouldInsertFragment(*fragment, selectedRange().get(), EditorInsertAction::Pasted)) 229 229 pasteAsFragment(fragment.releaseNonNull(), false, false, mailBlockquoteHandling); -
trunk/Source/WebCore/editing/cocoa/WebArchiveResourceFromNSAttributedString.h
r222239 r226093 37 37 } 38 38 - (instancetype)initWithData:(NSData *)data URL:(NSURL *)URL MIMEType:(NSString *)MIMEType textEncodingName:(NSString *)textEncodingName frameName:(NSString *)frameName; 39 - (NSString *)MIMEType; 39 40 - (NSURL *)URL; 40 41 -
trunk/Source/WebCore/editing/cocoa/WebArchiveResourceFromNSAttributedString.mm
r224267 r226093 28 28 29 29 #import "ArchiveResource.h" 30 #import "MIMETypeRegistry.h" 30 31 31 32 using namespace WebCore; … … 43 44 } 44 45 46 if ([MIMEType isEqualToString:@"application/octet-stream"]) { 47 // FIXME: This is a workaround for <rdar://problem/36074429>, and can be removed once that is fixed. 48 auto mimeTypeFromURL = MIMETypeRegistry::getMIMETypeForExtension(URL.pathExtension); 49 if (!mimeTypeFromURL.isEmpty()) 50 MIMEType = mimeTypeFromURL; 51 } 52 45 53 resource = ArchiveResource::create(SharedBuffer::create(adoptNS([data copy]).get()), URL, MIMEType, textEncodingName, frameName, { }); 46 54 if (!resource) { … … 52 60 } 53 61 62 - (NSString *)MIMEType 63 { 64 return resource->mimeType(); 65 } 66 54 67 - (NSURL *)URL 55 68 { -
trunk/Source/WebCore/editing/cocoa/WebContentReaderCocoa.mm
r226089 r226093 43 43 #import "HTMLIFrameElement.h" 44 44 #import "HTMLImageElement.h" 45 #import "HTMLObjectElement.h" 45 46 #import "LegacyWebArchive.h" 46 47 #import "MainFrame.h" … … 51 52 #import "SocketProvider.h" 52 53 #import "TypedElementDescendantIterator.h" 54 #import "URLParser.h" 53 55 #import "WebArchiveResourceFromNSAttributedString.h" 54 56 #import "WebArchiveResourceWebResourceHandler.h" … … 77 79 #if ENABLE(ATTACHMENT_ELEMENT) 78 80 79 void replaceRichContentWithAttachmentsIfNecessary(DocumentFragment& fragment, HashMap<AtomicString, RefPtr<Blob>>&& urlToBlobMap) 80 { 81 if (!RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled() || urlToBlobMap.isEmpty()) 81 struct AttachmentReplacementInfo { 82 AttachmentDisplayMode displayMode; 83 Ref<File> file; 84 Ref<Element> elementToReplace; 85 }; 86 87 void replaceRichContentWithAttachmentsIfNecessary(DocumentFragment& fragment, BlobReplacementInfo&& replacementInfo) 88 { 89 if (!RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled() || replacementInfo.isEmpty()) 82 90 return; 83 91 84 92 Vector<Ref<Element>> elementsToRemove; 85 Vector< std::pair<Ref<File>, Ref<Element>>> filesForElementsToReplace;93 Vector<AttachmentReplacementInfo> attachmentReplacementInfo; 86 94 for (auto& image : descendantsOfType<HTMLImageElement>(fragment)) { 87 95 auto url = image.attributeWithoutSynchronization(HTMLNames::srcAttr); 88 89 if (url.isEmpty()) { 90 elementsToRemove.append(image); 91 continue; 92 } 93 94 auto blob = urlToBlobMap.get(url); 95 if (!blob) { 96 elementsToRemove.append(image); 97 continue; 98 } 99 100 auto title = image.attributeWithoutSynchronization(HTMLNames::titleAttr); 96 if (url.isEmpty()) 97 continue; 98 99 auto blob = replacementInfo.blobURLToBlobMap.get(url); 100 if (!blob) 101 continue; 102 103 auto title = URLParser { replacementInfo.blobURLToReplacedURLMap.get(url) }.result().lastPathComponent(); 101 104 if (title.isEmpty()) 102 105 title = AtomicString("media"); 103 106 104 filesForElementsToReplace.append({ File::create(*blob, title), image }); 105 } 106 107 for (auto& fileAndElement : filesForElementsToReplace) { 108 auto& file = fileAndElement.first; 109 auto& elementToReplace = fileAndElement.second; 107 attachmentReplacementInfo.append({ AttachmentDisplayMode::InPlace, File::create(*blob, title), image }); 108 } 109 110 for (auto& object : descendantsOfType<HTMLObjectElement>(fragment)) { 111 auto url = object.attributeWithoutSynchronization(HTMLNames::dataAttr); 112 if (url.isEmpty()) { 113 elementsToRemove.append(object); 114 continue; 115 } 116 117 auto blob = replacementInfo.blobURLToBlobMap.get(url); 118 if (!blob) { 119 elementsToRemove.append(object); 120 continue; 121 } 122 123 auto title = URLParser { replacementInfo.blobURLToReplacedURLMap.get(url) }.result().lastPathComponent(); 124 if (title.isEmpty()) 125 title = AtomicString("file"); 126 127 attachmentReplacementInfo.append({ AttachmentDisplayMode::AsIcon, File::create(*blob, title), object }); 128 } 129 130 for (auto& info : attachmentReplacementInfo) { 131 auto file = WTFMove(info.file); 132 auto elementToReplace = WTFMove(info.elementToReplace); 110 133 auto parent = makeRefPtr(elementToReplace->parentNode()); 111 134 if (!parent) … … 115 138 attachment->setUniqueIdentifier(createCanonicalUUIDString()); 116 139 attachment->setFile(WTFMove(file), HTMLAttachmentElement::UpdateDisplayAttributes::Yes); 117 attachment->updateDisplayMode( AttachmentDisplayMode::InPlace);140 attachment->updateDisplayMode(info.displayMode); 118 141 parent->replaceChild(attachment, elementToReplace); 119 142 } … … 136 159 { 137 160 // This function needs to be kept in sync with identically named one in WebKitLegacy, which is used on older OS versions. 138 RetainPtr<NS Array> excludedElements = adoptNS([[NSArray alloc] initWithObjects:161 RetainPtr<NSMutableArray> excludedElements = adoptNS([[NSMutableArray alloc] initWithObjects: 139 162 // Omit style since we want style to be inline so the fragment can be easily inserted. 140 163 @"style", … … 145 168 // Omit deprecated tags. 146 169 @"applet", @"basefont", @"center", @"dir", @"font", @"menu", @"s", @"strike", @"u", 170 #if !ENABLE(ATTACHMENT_ELEMENT) 147 171 // Omit object so no file attachments are part of the fragment. 148 @"object", nil]); 172 @"object", 173 #endif 174 nil]); 175 176 #if ENABLE(ATTACHMENT_ELEMENT) 177 if (!RuntimeEnabledFeatures::sharedFeatures().attachmentElementEnabled()) 178 [excludedElements addObject:@"object"]; 179 #endif 149 180 150 181 #if PLATFORM(IOS) … … 221 252 }; 222 253 223 RefPtr<DocumentFragment> createFragmentAndAddResources(Frame& frame, NSAttributedString *string, HashMap<AtomicString, RefPtr<Blob>>& urlToBlobMap)254 RefPtr<DocumentFragment> createFragmentAndAddResources(Frame& frame, NSAttributedString *string, BlobReplacementInfo& replacementInfo) 224 255 { 225 256 if (!frame.page() || !frame.document()) … … 240 271 String blobURL = DOMURL::createObjectURL(document, blob); 241 272 blobURLMap.set(subresource->url().string(), blobURL); 242 urlToBlobMap.set(blobURL, WTFMove(blob)); 273 replacementInfo.blobURLToBlobMap.set(blobURL, WTFMove(blob)); 274 replacementInfo.blobURLToReplacedURLMap.set(blobURL, subresource->url().string()); 243 275 } 244 276 replaceSubresourceURLs(*fragmentAndResources.fragment, WTFMove(blobURLMap)); … … 270 302 } 271 303 272 static String sanitizeMarkupWithArchive(Document& destinationDocument, MarkupAndArchive& markupAndArchive, const std::function<bool(const String)>& canShowMIMETypeAsHTML, HashMap<AtomicString, RefPtr<Blob>>& urlToBlobMap)304 static String sanitizeMarkupWithArchive(Document& destinationDocument, MarkupAndArchive& markupAndArchive, const std::function<bool(const String)>& canShowMIMETypeAsHTML, BlobReplacementInfo& replacementInfo) 273 305 { 274 306 auto page = createPageForSanitizingWebContent(); … … 282 314 String blobURL = DOMURL::createObjectURL(destinationDocument, blob); 283 315 blobURLMap.set(subresource->url().string(), blobURL); 284 urlToBlobMap.set(blobURL, WTFMove(blob)); 316 replacementInfo.blobURLToBlobMap.set(blobURL, WTFMove(blob)); 317 replacementInfo.blobURLToReplacedURLMap.set(blobURL, subresource->url().string()); 285 318 } 286 319 … … 298 331 MarkupAndArchive subframeContent = { String::fromUTF8(subframeMainResource->data().data(), subframeMainResource->data().size()), 299 332 subframeMainResource.releaseNonNull(), subframeArchive.copyRef() }; 300 auto subframeMarkup = sanitizeMarkupWithArchive(destinationDocument, subframeContent, canShowMIMETypeAsHTML, urlToBlobMap);333 auto subframeMarkup = sanitizeMarkupWithArchive(destinationDocument, subframeContent, canShowMIMETypeAsHTML, replacementInfo); 301 334 302 335 CString utf8 = subframeMarkup.utf8(); … … 308 341 String subframeBlobURL = DOMURL::createObjectURL(destinationDocument, blob); 309 342 blobURLMap.set(subframeURL.string(), subframeBlobURL); 310 urlToBlobMap.set(subframeBlobURL, WTFMove(blob)); 343 replacementInfo.blobURLToBlobMap.set(subframeBlobURL, WTFMove(blob)); 344 replacementInfo.blobURLToReplacedURLMap.set(subframeBlobURL, subframeURL.string()); 311 345 } 312 346 … … 339 373 } 340 374 341 HashMap<AtomicString, RefPtr<Blob>> urlToBlobMap;375 BlobReplacementInfo replacementInfo; 342 376 String sanitizedMarkup = sanitizeMarkupWithArchive(*frame.document(), *result, [&] (const String& type) { 343 377 return frame.loader().client().canShowMIMETypeAsHTML(type); 344 }, urlToBlobMap);378 }, replacementInfo); 345 379 fragment = createFragmentFromMarkup(*frame.document(), sanitizedMarkup, blankURL(), DisallowScriptingAndPluginContent); 346 380 … … 348 382 return false; 349 383 350 replaceRichContentWithAttachmentsIfNecessary(*fragment, WTFMove( urlToBlobMap));384 replaceRichContentWithAttachmentsIfNecessary(*fragment, WTFMove(replacementInfo)); 351 385 return true; 352 386 } … … 368 402 } 369 403 370 HashMap<AtomicString, RefPtr<Blob>> urlToBlobMap;404 BlobReplacementInfo replacementInfo; 371 405 markup = sanitizeMarkupWithArchive(*frame.document(), *result, [&] (const String& type) { 372 406 return frame.loader().client().canShowMIMETypeAsHTML(type); 373 }, urlToBlobMap);407 }, replacementInfo); 374 408 375 409 return true; … … 421 455 static RefPtr<DocumentFragment> createFragmentFromAttributedString(Frame& frame, NSAttributedString *string) 422 456 { 423 HashMap<AtomicString, RefPtr<Blob>> urlToBlobMap;424 auto fragment = createFragmentAndAddResources(frame, string, urlToBlobMap);457 BlobReplacementInfo replacementInfo; 458 auto fragment = createFragmentAndAddResources(frame, string, replacementInfo); 425 459 if (!fragment) 426 460 return nullptr; 427 461 428 replaceRichContentWithAttachmentsIfNecessary(*fragment, WTFMove( urlToBlobMap));462 replaceRichContentWithAttachmentsIfNecessary(*fragment, WTFMove(replacementInfo)); 429 463 return fragment; 430 464 } … … 505 539 return false; 506 540 507 replaceRichContentWithAttachmentsIfNecessary(*fragment, {{ blobURL, WTFMove(blob)}});508 return true; 509 } 510 511 } 541 replaceRichContentWithAttachmentsIfNecessary(*fragment, {{{ blobURL, WTFMove(blob) }}, { }}); 542 return true; 543 } 544 545 } -
trunk/Tools/ChangeLog
r226088 r226093 1 2017-12-18 Wenson Hsieh <wenson_hsieh@apple.com> 2 3 [Attachment Support] Support representing pasted or dropped content using attachment elements 4 https://bugs.webkit.org/show_bug.cgi?id=180892 5 <rdar://problem/36064210> 6 7 Reviewed by Tim Horton. 8 9 Adds a new API test to exercise pasting an attributed string with multiple attachments of different types. 10 11 * TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm: 12 (testZIPData): 13 (platformCopyRichTextWithMultipleAttachments): 14 (TestWebKitAPI::TEST): 15 1 16 2017-12-18 Brady Eidson <beidson@apple.com> 2 17 -
trunk/Tools/TestWebKitAPI/Tests/WebKitCocoa/WKAttachmentTests.mm
r226085 r226093 137 137 } 138 138 139 static NSData *testZIPData() 140 { 141 NSURL *zipFileURL = [[NSBundle mainBundle] URLForResource:@"compressed-files" withExtension:@"zip" subdirectory:@"TestWebKitAPI.resources"]; 142 return [NSData dataWithContentsOfURL:zipFileURL]; 143 } 144 139 145 static NSData *testHTMLData() 140 146 { … … 308 314 309 315 #pragma mark - Platform testing helper functions 316 317 void platformCopyRichTextWithMultipleAttachments() 318 { 319 auto image = adoptNS([[NSTextAttachment alloc] initWithData:testImageData() ofType:(NSString *)kUTTypePNG]); 320 auto pdf = adoptNS([[NSTextAttachment alloc] initWithData:testPDFData() ofType:(NSString *)kUTTypePDF]); 321 auto zip = adoptNS([[NSTextAttachment alloc] initWithData:testZIPData() ofType:(NSString *)kUTTypeZipArchive]); 322 323 auto richText = adoptNS([[NSMutableAttributedString alloc] init]); 324 [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:image.get()]]; 325 [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:pdf.get()]]; 326 [richText appendAttributedString:[NSAttributedString attributedStringWithAttachment:zip.get()]]; 327 328 #if PLATFORM(MAC) 329 NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; 330 [pasteboard clearContents]; 331 [pasteboard writeObjects:@[ richText.get() ]]; 332 #elif PLATFORM(IOS) 333 auto item = adoptNS([[NSItemProvider alloc] init]); 334 [item registerObject:richText.get() visibility:NSItemProviderRepresentationVisibilityAll]; 335 [UIPasteboard generalPasteboard].itemProviders = @[ item.get() ]; 336 #endif 337 } 310 338 311 339 void platformCopyRichTextWithImage() … … 801 829 } 802 830 831 TEST(WKAttachmentTests, InsertPastedAttributedStringContainingMultipleAttachments) 832 { 833 platformCopyRichTextWithMultipleAttachments(); 834 835 RetainPtr<_WKAttachment> imageAttachment; 836 RetainPtr<_WKAttachment> zipAttachment; 837 RetainPtr<_WKAttachment> pdfAttachment; 838 auto webView = webViewForTestingAttachments(); 839 { 840 ObserveAttachmentUpdatesForScope observer(webView.get()); 841 [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil]; 842 EXPECT_EQ(0U, observer.observer().removed.count); 843 EXPECT_EQ(3U, observer.observer().inserted.count); 844 for (_WKAttachment *attachment in observer.observer().inserted) { 845 NSData *data = [attachment synchronouslyRequestData:nil]; 846 if ([data isEqualToData:testZIPData()]) 847 zipAttachment = attachment; 848 else if ([data isEqualToData:testPDFData()]) 849 pdfAttachment = attachment; 850 else if ([data isEqualToData:testImageData()]) 851 imageAttachment = attachment; 852 } 853 } 854 855 EXPECT_TRUE(zipAttachment && imageAttachment && pdfAttachment); 856 EXPECT_EQ(3, [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment').length"].integerValue); 857 EXPECT_WK_STREQ("image/png", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[0].getAttribute('type')"]); 858 EXPECT_WK_STREQ("application/pdf", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[1].getAttribute('type')"]); 859 EXPECT_WK_STREQ("application/zip", [webView stringByEvaluatingJavaScript:@"document.querySelectorAll('attachment')[2].getAttribute('type')"]); 860 861 { 862 ObserveAttachmentUpdatesForScope observer(webView.get()); 863 [webView _synchronouslyExecuteEditCommand:@"SelectAll" argument:nil]; 864 [webView _synchronouslyExecuteEditCommand:@"DeleteBackward" argument:nil]; 865 NSArray<_WKAttachment *> *removedAttachments = [observer.observer() removed]; 866 EXPECT_EQ(3U, removedAttachments.count); 867 EXPECT_TRUE([removedAttachments containsObject:zipAttachment.get()]); 868 EXPECT_TRUE([removedAttachments containsObject:imageAttachment.get()]); 869 EXPECT_TRUE([removedAttachments containsObject:pdfAttachment.get()]); 870 } 871 } 872 803 873 #pragma mark - Platform-specific tests 804 874
Note:
See TracChangeset
for help on using the changeset viewer.