226ead5a43bf5982d9b1ba8149a208e12ae7011f
[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 = NULL;
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
198         if (nodeToRemove) {
199             ExceptionCode ec;
200             nodeToRemove->remove(ec);
201             ASSERT(!ec);
202         }
203
204         return result;
205     }
206
207     const Font* font = 0;
208     RefPtr<Range> range = m_frame.selection().toNormalizedRange();
209     if (Node* startNode = adjustedSelectionStartForStyleComputation(m_frame.selection().selection()).deprecatedNode()) {
210         Node* pastEnd = range->pastLastNode();
211         // In the loop below, n should eventually match pastEnd and not become nil, but we've seen at least one
212         // unreproducible case where this didn't happen, so check for null also.
213         for (Node* node = startNode; node && node != pastEnd; node = NodeTraversal::next(*node)) {
214             auto renderer = node->renderer();
215             if (!renderer)
216                 continue;
217             // FIXME: Are there any node types that have renderers, but that we should be skipping?
218             const Font& primaryFont = renderer->style().fontCascade().primaryFont();
219             if (!font)
220                 font = &primaryFont;
221             else if (font != &primaryFont) {
222                 hasMultipleFonts = true;
223                 break;
224             }
225         }
226     }
227
228     return font;
229 }
230
231 NSDictionary* Editor::fontAttributesForSelectionStart() const
232 {
233     Node* nodeToRemove;
234     RenderStyle* style = styleForSelectionStart(&m_frame, nodeToRemove);
235     if (!style)
236         return nil;
237
238     NSMutableDictionary* result = [NSMutableDictionary dictionary];
239     
240     CTFontRef font = style->fontCascade().primaryFont().getCTFont();
241     if (font)
242         [result setObject:(id)font forKey:NSFontAttributeName];
243
244     getTextDecorationAttributesRespectingTypingStyle(*style, result);
245
246     if (nodeToRemove)
247         nodeToRemove->remove(ASSERT_NO_EXCEPTION);
248     
249     return result;
250 }
251
252 void Editor::removeUnchangeableStyles()
253 {
254     // This function removes styles that the user cannot modify by applying their default values.
255     
256     RefPtr<EditingStyle> editingStyle = EditingStyle::create(m_frame.document()->bodyOrFrameset());
257     RefPtr<MutableStyleProperties> defaultStyle = editingStyle.get()->style()->mutableCopy();
258     
259     // Text widgets implement background color via the UIView property. Their body element will not have one.
260     defaultStyle->setProperty(CSSPropertyBackgroundColor, "rgba(255, 255, 255, 0.0)");
261     
262     // Remove properties that the user can modify, like font-weight. 
263     // Also remove font-family, per HI spec.
264     // FIXME: it'd be nice if knowledge about which styles were unchangeable was not hard-coded here.
265     defaultStyle->removeProperty(CSSPropertyFontWeight);
266     defaultStyle->removeProperty(CSSPropertyFontStyle);
267     defaultStyle->removeProperty(CSSPropertyFontVariant);
268     // FIXME: we should handle also pasted quoted text, strikethrough, etc. <rdar://problem/9255115>
269     defaultStyle->removeProperty(CSSPropertyTextDecoration);
270     defaultStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect); // implements underline
271
272     // FIXME add EditActionMatchStlye <rdar://problem/9156507> Undo rich text's paste & match style should say "Undo Match Style"
273     applyStyleToSelection(defaultStyle.get(), EditActionChangeAttributes);
274 }
275
276 static PassRefPtr<SharedBuffer> dataInRTFDFormat(NSAttributedString *string)
277 {
278     NSUInteger length = string.length;
279     if (!length)
280         return nullptr;
281
282     BEGIN_BLOCK_OBJC_EXCEPTIONS;
283     return SharedBuffer::wrapNSData([string RTFDFromRange:NSMakeRange(0, length) documentAttributes:nil]);
284     END_BLOCK_OBJC_EXCEPTIONS;
285
286     return nullptr;
287 }
288
289 static PassRefPtr<SharedBuffer> dataInRTFFormat(NSAttributedString *string)
290 {
291     NSUInteger length = string.length;
292     if (!length)
293         return nullptr;
294
295     BEGIN_BLOCK_OBJC_EXCEPTIONS;
296     return SharedBuffer::wrapNSData([string RTFFromRange:NSMakeRange(0, length) documentAttributes:nil]);
297     END_BLOCK_OBJC_EXCEPTIONS;
298
299     return nullptr;
300 }
301
302 String Editor::stringSelectionForPasteboardWithImageAltText()
303 {
304     String text = selectedTextForDataTransfer();
305     text.replace(noBreakSpace, ' ');
306     return text;
307 }
308
309 PassRefPtr<SharedBuffer> Editor::selectionInWebArchiveFormat()
310 {
311     RefPtr<LegacyWebArchive> archive = LegacyWebArchive::createFromSelection(&m_frame);
312     return archive ? SharedBuffer::wrapCFData(archive->rawDataRepresentation().get()) : nullptr;
313 }
314
315 void Editor::writeSelectionToPasteboard(Pasteboard& pasteboard)
316 {
317     NSAttributedString *attributedString = attributedStringFromRange(*selectedRange());
318
319     PasteboardWebContent content;
320     content.canSmartCopyOrDelete = canSmartCopyOrDelete();
321     content.dataInWebArchiveFormat = selectionInWebArchiveFormat();
322     content.dataInRTFDFormat = [attributedString containsAttachments] ? dataInRTFDFormat(attributedString) : 0;
323     content.dataInRTFFormat = dataInRTFFormat(attributedString);
324     content.dataInStringFormat = stringSelectionForPasteboardWithImageAltText();
325     client()->getClientPasteboardDataForRange(selectedRange().get(), content.clientTypes, content.clientData);
326
327     pasteboard.write(content);
328 }
329
330 static void getImage(Element& imageElement, RefPtr<Image>& image, CachedImage*& cachedImage)
331 {
332     auto* renderer = imageElement.renderer();
333     if (!is<RenderImage>(renderer))
334         return;
335
336     CachedImage* tentativeCachedImage = downcast<RenderImage>(*renderer).cachedImage();
337     if (!tentativeCachedImage || tentativeCachedImage->errorOccurred())
338         return;
339
340     image = tentativeCachedImage->imageForRenderer(renderer);
341     if (!image)
342         return;
343     
344     cachedImage = tentativeCachedImage;
345 }
346
347 void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElement, const URL&, const String& title)
348 {
349     PasteboardImage pasteboardImage;
350
351     CachedImage* cachedImage;
352     getImage(imageElement, pasteboardImage.image, cachedImage);
353     if (!pasteboardImage.image)
354         return;
355     ASSERT(cachedImage);
356
357     pasteboardImage.url.url = imageElement.document().completeURL(stripLeadingAndTrailingHTMLSpaces(imageElement.imageSourceURL()));
358     pasteboardImage.url.title = title;
359     pasteboardImage.resourceMIMEType = pasteboard.resourceMIMEType(cachedImage->response().mimeType());
360     pasteboardImage.resourceData = cachedImage->resourceBuffer();
361
362     pasteboard.write(pasteboardImage);
363 }
364
365 class Editor::WebContentReader final : public PasteboardWebContentReader {
366 public:
367     WebContentReader(Frame& frame, Range& context, bool allowPlainText)
368         : frame(frame)
369         , context(context)
370         , allowPlainText(allowPlainText)
371         , madeFragmentFromPlainText(false)
372     {
373     }
374
375     void addFragment(PassRefPtr<DocumentFragment>);
376
377     Frame& frame;
378     Range& context;
379     const bool allowPlainText;
380
381     RefPtr<DocumentFragment> fragment;
382     bool madeFragmentFromPlainText;
383
384 private:
385     virtual bool readWebArchive(PassRefPtr<SharedBuffer>) override;
386     virtual bool readFilenames(const Vector<String>&) override;
387     virtual bool readHTML(const String&) override;
388     virtual bool readRTFD(PassRefPtr<SharedBuffer>) override;
389     virtual bool readRTF(PassRefPtr<SharedBuffer>) override;
390     virtual bool readImage(PassRefPtr<SharedBuffer>, const String& type) override;
391     virtual bool readURL(const URL&, const String& title) override;
392     virtual bool readPlainText(const String&) override;
393 };
394
395 void Editor::WebContentReader::addFragment(PassRefPtr<DocumentFragment> newFragment)
396 {
397     if (fragment) {
398         if (newFragment && newFragment->firstChild()) {
399             ExceptionCode ec;
400             fragment->appendChild(newFragment->firstChild(), ec);
401         }
402     } else
403         fragment = newFragment;
404 }
405
406 bool Editor::WebContentReader::readWebArchive(PassRefPtr<SharedBuffer> buffer)
407 {
408     if (!frame.document())
409         return false;
410
411     RefPtr<LegacyWebArchive> archive = LegacyWebArchive::create(URL(), buffer.get());
412     if (!archive)
413         return false;
414
415     RefPtr<ArchiveResource> mainResource = archive->mainResource();
416     if (!mainResource)
417         return false;
418
419     const String& type = mainResource->mimeType();
420
421     if (frame.loader().client().canShowMIMETypeAsHTML(type)) {
422         // FIXME: The code in createFragmentAndAddResources calls setDefersLoading(true). Don't we need that here?
423         if (DocumentLoader* loader = frame.loader().documentLoader())
424             loader->addAllArchiveResources(archive.get());
425
426         String markupString = String::fromUTF8(mainResource->data()->data(), mainResource->data()->size());
427         addFragment(createFragmentFromMarkup(*frame.document(), markupString, mainResource->url(), DisallowScriptingAndPluginContent));
428         return true;
429     }
430
431     return false;
432 }
433
434 bool Editor::WebContentReader::readFilenames(const Vector<String>&)
435 {
436     return false;
437 }
438
439 bool Editor::WebContentReader::readHTML(const String& string)
440 {
441     if (!frame.document())
442         return false;
443
444     addFragment(createFragmentFromMarkup(*frame.document(), string, emptyString(), DisallowScriptingAndPluginContent));
445     return true;
446 }
447
448 bool Editor::WebContentReader::readRTFD(PassRefPtr<SharedBuffer> buffer)
449 {
450     addFragment(frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTFD:buffer->createNSData().get() documentAttributes:nullptr]).get()));
451     return fragment;
452 }
453
454 bool Editor::WebContentReader::readRTF(PassRefPtr<SharedBuffer> buffer)
455 {
456     addFragment(frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTF:buffer->createNSData().get() documentAttributes:nullptr]).get()));
457     return fragment;
458 }
459
460 bool Editor::WebContentReader::readImage(PassRefPtr<SharedBuffer> buffer, const String& type)
461 {
462     RetainPtr<CFStringRef> stringType = type.createCFString();
463     RetainPtr<NSString> filenameExtension = adoptNS((NSString *)UTTypeCopyPreferredTagWithClass(stringType.get(), kUTTagClassFilenameExtension));
464     NSString *relativeURLPart = [@"image" stringByAppendingString:filenameExtension.get()];
465     RetainPtr<NSString> mimeType = adoptNS((NSString *)UTTypeCopyPreferredTagWithClass(stringType.get(), kUTTagClassMIMEType));
466
467     addFragment(frame.editor().createFragmentForImageResourceAndAddResource(ArchiveResource::create(buffer, URL::fakeURLWithRelativePart(relativeURLPart), mimeType.get(), emptyString(), emptyString())));
468     return fragment;
469 }
470
471 bool Editor::WebContentReader::readURL(const URL& url, const String&)
472 {
473     if (url.isEmpty())
474         return false;
475
476     if (!frame.editor().client()->hasRichlyEditableSelection()) {
477         if (readPlainText([(NSURL *)url absoluteString]))
478             return true;
479     }
480
481     if ([(NSURL *)url isFileURL]) {
482         NSString *localPath = [(NSURL *)url relativePath];
483         // Only allow url attachments from ~/Media for now.
484         if (![localPath hasPrefix:[(NSString *)CPSharedResourcesDirectory() stringByAppendingString:@"/Media/DCIM/"]])
485             return false;
486
487         RetainPtr<NSString> fileType = adoptNS((NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[localPath pathExtension], NULL));
488         NSData *data = [NSData dataWithContentsOfFile:localPath];
489         if (UTTypeConformsTo((CFStringRef)fileType.get(), kUTTypePNG)) {
490             addFragment(frame.editor().createFragmentForImageResourceAndAddResource(ArchiveResource::create(SharedBuffer::wrapNSData([[data copy] autorelease]), URL::fakeURLWithRelativePart("image.png"), @"image/png", emptyString(), emptyString())));
491             return fragment;
492         } else if (UTTypeConformsTo((CFStringRef)fileType.get(), kUTTypeJPEG)) {
493             addFragment(frame.editor().createFragmentForImageResourceAndAddResource(ArchiveResource::create(SharedBuffer::wrapNSData([[data copy] autorelease]), URL::fakeURLWithRelativePart("image.jpg"), @"image/jpg", emptyString(), emptyString())));
494             return fragment;
495         }
496     } else {
497         RefPtr<Element> anchor = frame.document()->createElement(HTMLNames::aTag, false);
498         anchor->setAttribute(HTMLNames::hrefAttr, url.string());
499         anchor->appendChild(frame.document()->createTextNode([[(NSURL *)url absoluteString] precomposedStringWithCanonicalMapping]));
500
501         RefPtr<DocumentFragment> newFragment = frame.document()->createDocumentFragment();
502         newFragment->appendChild(anchor.release());
503         addFragment(newFragment);
504         return true;
505     }
506     return false;
507 }
508
509 bool Editor::WebContentReader::readPlainText(const String& text)
510 {
511     if (!allowPlainText)
512         return false;
513
514     addFragment(createFragmentFromText(context, [text precomposedStringWithCanonicalMapping]));
515     if (!fragment)
516         return false;
517
518     madeFragmentFromPlainText = true;
519     return true;
520 }
521
522 // FIXME: Should give this function a name that makes it clear it adds resources to the document loader as a side effect.
523 // Or refactor so it does not do that.
524 PassRefPtr<DocumentFragment> Editor::webContentFromPasteboard(Pasteboard& pasteboard, Range& context, bool allowPlainText, bool& chosePlainText)
525 {
526     WebContentReader reader(m_frame, context, allowPlainText);
527     pasteboard.read(reader);
528     chosePlainText = reader.madeFragmentFromPlainText;
529     return reader.fragment.release();
530 }
531
532 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText, MailBlockquoteHandling mailBlockquoteHandling)
533 {
534     RefPtr<Range> range = selectedRange();
535     WebContentReader reader(m_frame, *range, allowPlainText);
536     int numberOfPasteboardItems = client()->getPasteboardItemsCount();
537     for (int i = 0; i < numberOfPasteboardItems; ++i) {
538         RefPtr<DocumentFragment> fragment = client()->documentFragmentFromDelegate(i);
539         if (!fragment)
540             continue;
541
542         reader.addFragment(fragment);
543     }
544
545     RefPtr<DocumentFragment> fragment = reader.fragment;
546     if (!fragment) {
547         bool chosePlainTextIgnored;
548         fragment = webContentFromPasteboard(*pasteboard, *range, allowPlainText, chosePlainTextIgnored);
549     }
550
551     if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
552         pasteAsFragment(fragment, canSmartReplaceWithPasteboard(*pasteboard), false, mailBlockquoteHandling);
553 }
554
555 PassRefPtr<DocumentFragment> Editor::createFragmentAndAddResources(NSAttributedString *string)
556 {
557     if (!m_frame.page() || !m_frame.document() || !m_frame.document()->isHTMLDocument())
558         return nullptr;
559
560     if (!string)
561         return nullptr;
562
563     bool wasDeferringCallbacks = m_frame.page()->defersLoading();
564     if (!wasDeferringCallbacks)
565         m_frame.page()->setDefersLoading(true);
566
567     Vector<RefPtr<ArchiveResource>> resources;
568     RefPtr<DocumentFragment> fragment = client()->documentFragmentFromAttributedString(string, resources);
569
570     if (DocumentLoader* loader = m_frame.loader().documentLoader()) {
571         for (size_t i = 0, size = resources.size(); i < size; ++i)
572             loader->addArchiveResource(resources[i]);
573     }
574
575     if (!wasDeferringCallbacks)
576         m_frame.page()->setDefersLoading(false);
577     
578     return fragment.release();
579 }
580
581 PassRefPtr<DocumentFragment> Editor::createFragmentForImageResourceAndAddResource(PassRefPtr<ArchiveResource> resource)
582 {
583     if (!resource)
584         return nullptr;
585
586     RefPtr<Element> imageElement = m_frame.document()->createElement(HTMLNames::imgTag, false);
587     // FIXME: The code in createFragmentAndAddResources calls setDefersLoading(true). Don't we need that here?
588     if (DocumentLoader* loader = m_frame.loader().documentLoader())
589         loader->addArchiveResource(resource.get());
590
591     NSURL *URL = resource->url();
592     imageElement->setAttribute(HTMLNames::srcAttr, [URL isFileURL] ? [URL absoluteString] : resource->url());
593
594     RefPtr<DocumentFragment> fragment = m_frame.document()->createDocumentFragment();
595     fragment->appendChild(imageElement.release());
596
597     return fragment.release();
598 }
599
600 void Editor::replaceSelectionWithAttributedString(NSAttributedString *attributedString, MailBlockquoteHandling mailBlockquoteHandling)
601 {
602     if (m_frame.selection().isNone())
603         return;
604
605     if (m_frame.selection().selection().isContentRichlyEditable()) {
606         RefPtr<DocumentFragment> fragment = createFragmentAndAddResources(attributedString);
607         if (fragment && shouldInsertFragment(fragment, selectedRange(), EditorInsertActionPasted))
608             pasteAsFragment(fragment, false, false, mailBlockquoteHandling);
609     } else {
610         String text = [attributedString string];
611         if (shouldInsertText(text, selectedRange().get(), EditorInsertActionPasted))
612             pasteAsPlainText(text, false);
613     }
614 }
615
616 } // namespace WebCore