[iOS] Remove soft linking of MobileCoreServices.framework
[WebKit-https.git] / Source / WebCore / editing / ios / EditorIOS.mm
1 /*
2  * Copyright (C) 2006, 2007, 2013 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 #import "Editor.h"
28
29 #import "CSSComputedStyleDeclaration.h"
30 #import "CSSPrimitiveValueMappings.h"
31 #import "CachedImage.h"
32 #import "CachedResourceLoader.h"
33 #import "DataTransfer.h"
34 #import "DocumentFragment.h"
35 #import "DocumentLoader.h"
36 #import "EditorClient.h"
37 #import "FontCascade.h"
38 #import "Frame.h"
39 #import "FrameLoaderClient.h"
40 #import "HTMLConverter.h"
41 #import "HTMLImageElement.h"
42 #import "HTMLInputElement.h"
43 #import "HTMLNames.h"
44 #import "HTMLParserIdioms.h"
45 #import "HTMLTextAreaElement.h"
46 #import "LegacyWebArchive.h"
47 #import "NSAttributedStringSPI.h"
48 #import "NodeTraversal.h"
49 #import "Page.h"
50 #import "Pasteboard.h"
51 #import "RenderBlock.h"
52 #import "RenderImage.h"
53 #import "SharedBuffer.h"
54 #import "SoftLinking.h"
55 #import "StyleProperties.h"
56 #import "Text.h"
57 #import "TypingCommand.h"
58 #import "WAKAppKitStubs.h"
59 #import "htmlediting.h"
60 #import "markup.h"
61 #import <MobileCoreServices/MobileCoreServices.h>
62 #import <wtf/BlockObjCExceptions.h>
63
64 SOFT_LINK_FRAMEWORK(AppSupport)
65 SOFT_LINK(AppSupport, CPSharedResourcesDirectory, CFStringRef, (void), ())
66
67 @interface NSAttributedString (NSAttributedStringKitAdditions)
68 - (id)initWithRTF:(NSData *)data documentAttributes:(NSDictionary **)dict;
69 - (id)initWithRTFD:(NSData *)data documentAttributes:(NSDictionary **)dict;
70 - (NSData *)RTFFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict;
71 - (NSData *)RTFDFromRange:(NSRange)range documentAttributes:(NSDictionary *)dict;
72 - (BOOL)containsAttachments;
73 @end
74
75 namespace WebCore {
76
77 using namespace HTMLNames;
78
79 void Editor::showFontPanel()
80 {
81 }
82
83 void Editor::showStylesPanel()
84 {
85 }
86
87 void Editor::showColorPanel()
88 {
89 }
90
91 void Editor::setTextAlignmentForChangedBaseWritingDirection(WritingDirection direction)
92 {
93     // Note that the passed-in argument is the direction that has been changed to by
94     // some code or user interaction outside the scope of this function. The former
95     // direction is not known, nor is it required for the kind of text alignment
96     // changes done by this function.
97     //
98     // Rules:
99     // When text has no explicit alignment, set to alignment to match the writing direction.
100     // If the text has left or right alignment, flip left->right and right->left. 
101     // Otherwise, do nothing.
102
103     RefPtr<EditingStyle> selectionStyle = EditingStyle::styleAtSelectionStart(m_frame.selection().selection());
104     if (!selectionStyle || !selectionStyle->style())
105          return;
106
107     RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(selectionStyle->style()->getPropertyCSSValue(CSSPropertyTextAlign));
108     if (!value)
109         return;
110         
111     const char *newValue = nullptr;
112     ETextAlign textAlign = *value;
113     switch (textAlign) {
114         case TASTART:
115         case TAEND:
116         {
117             switch (direction) {
118                 case NaturalWritingDirection:
119                     // no-op
120                     break;
121                 case LeftToRightWritingDirection:
122                     newValue = "left";
123                     break;
124                 case RightToLeftWritingDirection:
125                     newValue = "right";
126                     break;
127             }
128             break;
129         }
130         case LEFT:
131         case WEBKIT_LEFT:
132             newValue = "right";
133             break;
134         case RIGHT:
135         case WEBKIT_RIGHT:
136             newValue = "left";
137             break;
138         case CENTER:
139         case WEBKIT_CENTER:
140         case JUSTIFY:
141             // no-op
142             break;
143     }
144
145     if (!newValue)
146         return;
147
148     Element* focusedElement = m_frame.document()->focusedElement();
149     if (focusedElement && (is<HTMLTextAreaElement>(*focusedElement) || (is<HTMLInputElement>(*focusedElement)
150         && (downcast<HTMLInputElement>(*focusedElement).isTextField()
151             || downcast<HTMLInputElement>(*focusedElement).isSearchField())))) {
152         if (direction == NaturalWritingDirection)
153             return;
154         downcast<HTMLElement>(*focusedElement).setAttributeWithoutSynchronization(alignAttr, newValue);
155         m_frame.document()->updateStyleIfNeeded();
156         return;
157     }
158
159     RefPtr<MutableStyleProperties> style = MutableStyleProperties::create();
160     style->setProperty(CSSPropertyTextAlign, newValue);
161     applyParagraphStyle(style.get());
162 }
163
164 const Font* Editor::fontForSelection(bool& hasMultipleFonts) const
165 {
166     hasMultipleFonts = false;
167
168     if (!m_frame.selection().isRange()) {
169         Node* nodeToRemove;
170         auto* style = styleForSelectionStart(&m_frame, nodeToRemove); // sets nodeToRemove
171
172         const Font* result = nullptr;
173         if (style) {
174             result = &style->fontCascade().primaryFont();
175             if (nodeToRemove)
176                 nodeToRemove->remove(ASSERT_NO_EXCEPTION);
177         }
178
179         return result;
180     }
181
182     const Font* font = nullptr;
183     RefPtr<Range> range = m_frame.selection().toNormalizedRange();
184     if (Node* startNode = adjustedSelectionStartForStyleComputation(m_frame.selection().selection()).deprecatedNode()) {
185         Node* pastEnd = range->pastLastNode();
186         // In the loop below, n should eventually match pastEnd and not become nil, but we've seen at least one
187         // unreproducible case where this didn't happen, so check for null also.
188         for (Node* node = startNode; node && node != pastEnd; node = NodeTraversal::next(*node)) {
189             auto renderer = node->renderer();
190             if (!renderer)
191                 continue;
192             // FIXME: Are there any node types that have renderers, but that we should be skipping?
193             const Font& primaryFont = renderer->style().fontCascade().primaryFont();
194             if (!font)
195                 font = &primaryFont;
196             else if (font != &primaryFont) {
197                 hasMultipleFonts = true;
198                 break;
199             }
200         }
201     }
202
203     return font;
204 }
205
206 NSDictionary* Editor::fontAttributesForSelectionStart() const
207 {
208     Node* nodeToRemove;
209     auto* style = styleForSelectionStart(&m_frame, nodeToRemove);
210     if (!style)
211         return nil;
212
213     NSMutableDictionary* result = [NSMutableDictionary dictionary];
214     
215     CTFontRef font = style->fontCascade().primaryFont().getCTFont();
216     if (font)
217         [result setObject:(id)font forKey:NSFontAttributeName];
218
219     getTextDecorationAttributesRespectingTypingStyle(*style, result);
220
221     if (nodeToRemove)
222         nodeToRemove->remove(ASSERT_NO_EXCEPTION);
223     
224     return result;
225 }
226
227 void Editor::removeUnchangeableStyles()
228 {
229     // This function removes styles that the user cannot modify by applying their default values.
230     
231     RefPtr<EditingStyle> editingStyle = EditingStyle::create(m_frame.document()->bodyOrFrameset());
232     RefPtr<MutableStyleProperties> defaultStyle = editingStyle.get()->style()->mutableCopy();
233     
234     // Text widgets implement background color via the UIView property. Their body element will not have one.
235     defaultStyle->setProperty(CSSPropertyBackgroundColor, "rgba(255, 255, 255, 0.0)");
236     
237     // Remove properties that the user can modify, like font-weight. 
238     // Also remove font-family, per HI spec.
239     // FIXME: it'd be nice if knowledge about which styles were unchangeable was not hard-coded here.
240     defaultStyle->removeProperty(CSSPropertyFontWeight);
241     defaultStyle->removeProperty(CSSPropertyFontStyle);
242     defaultStyle->removeProperty(CSSPropertyFontVariantCaps);
243     // FIXME: we should handle also pasted quoted text, strikethrough, etc. <rdar://problem/9255115>
244     defaultStyle->removeProperty(CSSPropertyTextDecoration);
245     defaultStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect); // implements underline
246
247     // FIXME add EditActionMatchStlye <rdar://problem/9156507> Undo rich text's paste & match style should say "Undo Match Style"
248     applyStyleToSelection(defaultStyle.get(), EditActionChangeAttributes);
249 }
250
251 static RefPtr<SharedBuffer> dataInRTFDFormat(NSAttributedString *string)
252 {
253     NSUInteger length = string.length;
254     if (!length)
255         return nullptr;
256
257     BEGIN_BLOCK_OBJC_EXCEPTIONS;
258     return SharedBuffer::wrapNSData([string RTFDFromRange:NSMakeRange(0, length) documentAttributes:nil]);
259     END_BLOCK_OBJC_EXCEPTIONS;
260
261     return nullptr;
262 }
263
264 static RefPtr<SharedBuffer> dataInRTFFormat(NSAttributedString *string)
265 {
266     NSUInteger length = string.length;
267     if (!length)
268         return nullptr;
269
270     BEGIN_BLOCK_OBJC_EXCEPTIONS;
271     return SharedBuffer::wrapNSData([string RTFFromRange:NSMakeRange(0, length) documentAttributes:nil]);
272     END_BLOCK_OBJC_EXCEPTIONS;
273
274     return nullptr;
275 }
276
277 String Editor::stringSelectionForPasteboardWithImageAltText()
278 {
279     String text = selectedTextForDataTransfer();
280     text.replace(noBreakSpace, ' ');
281     return text;
282 }
283
284 RefPtr<SharedBuffer> Editor::selectionInWebArchiveFormat()
285 {
286     RefPtr<LegacyWebArchive> archive = LegacyWebArchive::createFromSelection(&m_frame);
287     if (!archive)
288         return nullptr;
289     return SharedBuffer::wrapCFData(archive->rawDataRepresentation().get());
290 }
291
292 void Editor::writeSelectionToPasteboard(Pasteboard& pasteboard)
293 {
294     NSAttributedString *attributedString = attributedStringFromRange(*selectedRange());
295
296     PasteboardWebContent content;
297     content.canSmartCopyOrDelete = canSmartCopyOrDelete();
298     content.dataInWebArchiveFormat = selectionInWebArchiveFormat();
299     content.dataInRTFDFormat = [attributedString containsAttachments] ? dataInRTFDFormat(attributedString) : 0;
300     content.dataInRTFFormat = dataInRTFFormat(attributedString);
301     content.dataInStringFormat = stringSelectionForPasteboardWithImageAltText();
302     client()->getClientPasteboardDataForRange(selectedRange().get(), content.clientTypes, content.clientData);
303
304     pasteboard.write(content);
305 }
306
307 static void getImage(Element& imageElement, RefPtr<Image>& image, CachedImage*& cachedImage)
308 {
309     auto* renderer = imageElement.renderer();
310     if (!is<RenderImage>(renderer))
311         return;
312
313     CachedImage* tentativeCachedImage = downcast<RenderImage>(*renderer).cachedImage();
314     if (!tentativeCachedImage || tentativeCachedImage->errorOccurred())
315         return;
316
317     image = tentativeCachedImage->imageForRenderer(renderer);
318     if (!image)
319         return;
320     
321     cachedImage = tentativeCachedImage;
322 }
323
324 void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElement, const URL&, const String& title)
325 {
326     PasteboardImage pasteboardImage;
327
328     CachedImage* cachedImage;
329     getImage(imageElement, pasteboardImage.image, cachedImage);
330     if (!pasteboardImage.image)
331         return;
332     ASSERT(cachedImage);
333
334     pasteboardImage.url.url = imageElement.document().completeURL(stripLeadingAndTrailingHTMLSpaces(imageElement.imageSourceURL()));
335     pasteboardImage.url.title = title;
336     pasteboardImage.resourceMIMEType = pasteboard.resourceMIMEType(cachedImage->response().mimeType());
337     pasteboardImage.resourceData = cachedImage->resourceBuffer();
338
339     pasteboard.write(pasteboardImage);
340 }
341
342 class Editor::WebContentReader final : public PasteboardWebContentReader {
343 public:
344     WebContentReader(Frame& frame, Range& context, bool allowPlainText)
345         : frame(frame)
346         , context(context)
347         , allowPlainText(allowPlainText)
348         , madeFragmentFromPlainText(false)
349     {
350     }
351
352     void addFragment(RefPtr<DocumentFragment>&&);
353
354     Frame& frame;
355     Range& context;
356     const bool allowPlainText;
357
358     RefPtr<DocumentFragment> fragment;
359     bool madeFragmentFromPlainText;
360
361 private:
362     bool readWebArchive(SharedBuffer*) override;
363     bool readFilenames(const Vector<String>&) override;
364     bool readHTML(const String&) override;
365     bool readRTFD(SharedBuffer&) override;
366     bool readRTF(SharedBuffer&) override;
367     bool readImage(Ref<SharedBuffer>&&, const String& type) override;
368     bool readURL(const URL&, const String& title) override;
369     bool readPlainText(const String&) override;
370 };
371
372 void Editor::WebContentReader::addFragment(RefPtr<DocumentFragment>&& newFragment)
373 {
374     if (fragment) {
375         if (newFragment && newFragment->firstChild()) {
376             ExceptionCode ec;
377             fragment->appendChild(*newFragment->firstChild(), ec);
378         }
379     } else
380         fragment = WTFMove(newFragment);
381 }
382
383 bool Editor::WebContentReader::readWebArchive(SharedBuffer* buffer)
384 {
385     if (!frame.document())
386         return false;
387
388     if (!buffer)
389         return false;
390
391     RefPtr<LegacyWebArchive> archive = LegacyWebArchive::create(URL(), *buffer);
392     if (!archive)
393         return false;
394
395     RefPtr<ArchiveResource> mainResource = archive->mainResource();
396     if (!mainResource)
397         return false;
398
399     const String& type = mainResource->mimeType();
400
401     if (frame.loader().client().canShowMIMETypeAsHTML(type)) {
402         // FIXME: The code in createFragmentAndAddResources calls setDefersLoading(true). Don't we need that here?
403         if (DocumentLoader* loader = frame.loader().documentLoader())
404             loader->addAllArchiveResources(archive.get());
405
406         String markupString = String::fromUTF8(mainResource->data().data(), mainResource->data().size());
407         addFragment(createFragmentFromMarkup(*frame.document(), markupString, mainResource->url(), DisallowScriptingAndPluginContent));
408         return true;
409     }
410
411     return false;
412 }
413
414 bool Editor::WebContentReader::readFilenames(const Vector<String>&)
415 {
416     return false;
417 }
418
419 bool Editor::WebContentReader::readHTML(const String& string)
420 {
421     if (!frame.document())
422         return false;
423
424     addFragment(createFragmentFromMarkup(*frame.document(), string, emptyString(), DisallowScriptingAndPluginContent));
425     return true;
426 }
427
428 bool Editor::WebContentReader::readRTFD(SharedBuffer& buffer)
429 {
430     addFragment(frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTFD:buffer.createNSData().get() documentAttributes:nullptr]).get()));
431     return fragment;
432 }
433
434 bool Editor::WebContentReader::readRTF(SharedBuffer& buffer)
435 {
436     addFragment(frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTF:buffer.createNSData().get() documentAttributes:nullptr]).get()));
437     return fragment;
438 }
439
440 bool Editor::WebContentReader::readImage(Ref<SharedBuffer>&& buffer, const String& type)
441 {
442     RetainPtr<CFStringRef> stringType = type.createCFString();
443     RetainPtr<NSString> filenameExtension = adoptNS((NSString *)UTTypeCopyPreferredTagWithClass(stringType.get(), kUTTagClassFilenameExtension));
444     NSString *relativeURLPart = [@"image" stringByAppendingString:filenameExtension.get()];
445     RetainPtr<NSString> mimeType = adoptNS((NSString *)UTTypeCopyPreferredTagWithClass(stringType.get(), kUTTagClassMIMEType));
446
447     addFragment(frame.editor().createFragmentForImageResourceAndAddResource(ArchiveResource::create(WTFMove(buffer), URL::fakeURLWithRelativePart(relativeURLPart), mimeType.get(), emptyString(), emptyString())));
448     return fragment;
449 }
450
451 bool Editor::WebContentReader::readURL(const URL& url, const String&)
452 {
453     if (url.isEmpty())
454         return false;
455
456     if (!frame.editor().client()->hasRichlyEditableSelection()) {
457         if (readPlainText([(NSURL *)url absoluteString]))
458             return true;
459     }
460
461     if ([(NSURL *)url isFileURL]) {
462         NSString *localPath = [(NSURL *)url relativePath];
463         // Only allow url attachments from ~/Media for now.
464         if (![localPath hasPrefix:[(NSString *)CPSharedResourcesDirectory() stringByAppendingString:@"/Media/DCIM/"]])
465             return false;
466
467         RetainPtr<NSString> fileType = adoptNS((NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[localPath pathExtension], NULL));
468         NSData *data = [NSData dataWithContentsOfFile:localPath];
469         if (UTTypeConformsTo((CFStringRef)fileType.get(), kUTTypePNG)) {
470             addFragment(frame.editor().createFragmentForImageResourceAndAddResource(ArchiveResource::create(SharedBuffer::wrapNSData([[data copy] autorelease]), URL::fakeURLWithRelativePart("image.png"), @"image/png", emptyString(), emptyString())));
471             return fragment;
472         } else if (UTTypeConformsTo((CFStringRef)fileType.get(), kUTTypeJPEG)) {
473             addFragment(frame.editor().createFragmentForImageResourceAndAddResource(ArchiveResource::create(SharedBuffer::wrapNSData([[data copy] autorelease]), URL::fakeURLWithRelativePart("image.jpg"), @"image/jpg", emptyString(), emptyString())));
474             return fragment;
475         }
476     } else {
477         auto anchor = frame.document()->createElement(HTMLNames::aTag, false);
478         anchor->setAttributeWithoutSynchronization(HTMLNames::hrefAttr, url.string());
479         anchor->appendChild(frame.document()->createTextNode([[(NSURL *)url absoluteString] precomposedStringWithCanonicalMapping]));
480
481         auto newFragment = frame.document()->createDocumentFragment();
482         newFragment->appendChild(anchor);
483         addFragment(WTFMove(newFragment));
484         return true;
485     }
486     return false;
487 }
488
489 bool Editor::WebContentReader::readPlainText(const String& text)
490 {
491     if (!allowPlainText)
492         return false;
493
494     addFragment(createFragmentFromText(context, [text precomposedStringWithCanonicalMapping]));
495     if (!fragment)
496         return false;
497
498     madeFragmentFromPlainText = true;
499     return true;
500 }
501
502 // FIXME: Should give this function a name that makes it clear it adds resources to the document loader as a side effect.
503 // Or refactor so it does not do that.
504 RefPtr<DocumentFragment> Editor::webContentFromPasteboard(Pasteboard& pasteboard, Range& context, bool allowPlainText, bool& chosePlainText)
505 {
506     WebContentReader reader(m_frame, context, allowPlainText);
507     pasteboard.read(reader);
508     chosePlainText = reader.madeFragmentFromPlainText;
509     return WTFMove(reader.fragment);
510 }
511
512 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText, MailBlockquoteHandling mailBlockquoteHandling)
513 {
514     RefPtr<Range> range = selectedRange();
515     WebContentReader reader(m_frame, *range, allowPlainText);
516     int numberOfPasteboardItems = client()->getPasteboardItemsCount();
517     for (int i = 0; i < numberOfPasteboardItems; ++i) {
518         RefPtr<DocumentFragment> fragment = client()->documentFragmentFromDelegate(i);
519         if (!fragment)
520             continue;
521
522         reader.addFragment(WTFMove(fragment));
523     }
524
525     RefPtr<DocumentFragment> fragment = reader.fragment;
526     if (!fragment) {
527         bool chosePlainTextIgnored;
528         fragment = webContentFromPasteboard(*pasteboard, *range, allowPlainText, chosePlainTextIgnored);
529     }
530
531     if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
532         pasteAsFragment(fragment.releaseNonNull(), canSmartReplaceWithPasteboard(*pasteboard), false, mailBlockquoteHandling);
533 }
534
535 RefPtr<DocumentFragment> Editor::createFragmentAndAddResources(NSAttributedString *string)
536 {
537     if (!m_frame.page() || !m_frame.document())
538         return nullptr;
539
540     auto& document = *m_frame.document();
541     if (!document.isHTMLDocument() || !string)
542         return nullptr;
543
544     bool wasDeferringCallbacks = m_frame.page()->defersLoading();
545     if (!wasDeferringCallbacks)
546         m_frame.page()->setDefersLoading(true);
547
548     auto& cachedResourceLoader = document.cachedResourceLoader();
549     bool wasImagesEnabled = cachedResourceLoader.imagesEnabled();
550     if (wasImagesEnabled)
551         cachedResourceLoader.setImagesEnabled(false);
552
553     auto fragmentAndResources = createFragment(string);
554
555     if (DocumentLoader* loader = m_frame.loader().documentLoader()) {
556         for (auto& resource : fragmentAndResources.resources) {
557             if (resource)
558                 loader->addArchiveResource(resource.releaseNonNull());
559         }
560     }
561
562     if (wasImagesEnabled)
563         cachedResourceLoader.setImagesEnabled(true);
564     if (!wasDeferringCallbacks)
565         m_frame.page()->setDefersLoading(false);
566     
567     return WTFMove(fragmentAndResources.fragment);
568 }
569
570 RefPtr<DocumentFragment> Editor::createFragmentForImageResourceAndAddResource(RefPtr<ArchiveResource>&& resource)
571 {
572     if (!resource)
573         return nullptr;
574
575     NSURL *URL = resource->url();
576     String resourceURL = [URL isFileURL] ? [URL absoluteString] : resource->url();
577
578     if (DocumentLoader* loader = m_frame.loader().documentLoader())
579         loader->addArchiveResource(resource.releaseNonNull());
580
581     auto imageElement = HTMLImageElement::create(*m_frame.document());
582     imageElement->setAttributeWithoutSynchronization(HTMLNames::srcAttr, resourceURL);
583
584     auto fragment = m_frame.document()->createDocumentFragment();
585     fragment->appendChild(imageElement);
586
587     return WTFMove(fragment);
588 }
589
590 void Editor::replaceSelectionWithAttributedString(NSAttributedString *attributedString, MailBlockquoteHandling mailBlockquoteHandling)
591 {
592     if (m_frame.selection().isNone())
593         return;
594
595     if (m_frame.selection().selection().isContentRichlyEditable()) {
596         RefPtr<DocumentFragment> fragment = createFragmentAndAddResources(attributedString);
597         if (fragment && shouldInsertFragment(fragment, selectedRange(), EditorInsertActionPasted))
598             pasteAsFragment(fragment.releaseNonNull(), false, false, mailBlockquoteHandling);
599     } else {
600         String text = [attributedString string];
601         if (shouldInsertText(text, selectedRange().get(), EditorInsertActionPasted))
602             pasteAsPlainText(text, false);
603     }
604 }
605
606 } // namespace WebCore