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