Font::primaryFontData() should return a reference
[WebKit-https.git] / Source / WebCore / editing / mac / EditorMac.mm
1 /*
2  * Copyright (C) 2006, 2007, 2008, 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. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
24  */
25
26 #import "config.h"
27 #import "Editor.h"
28
29 #import "BlockExceptions.h"
30 #import "CachedResourceLoader.h"
31 #import "ColorMac.h"
32 #import "DOMRangeInternal.h"
33 #import "DataTransfer.h"
34 #import "DocumentFragment.h"
35 #import "DocumentLoader.h"
36 #import "Editor.h"
37 #import "EditorClient.h"
38 #import "Font.h"
39 #import "Frame.h"
40 #import "FrameLoaderClient.h"
41 #import "FrameView.h"
42 #import "HTMLConverter.h"
43 #import "HTMLElement.h"
44 #import "HTMLNames.h"
45 #import "LegacyWebArchive.h"
46 #import "MIMETypeRegistry.h"
47 #import "NodeTraversal.h"
48 #import "Page.h"
49 #import "Pasteboard.h"
50 #import "PasteboardStrategy.h"
51 #import "PlatformStrategies.h"
52 #import "Range.h"
53 #import "RenderBlock.h"
54 #import "RenderImage.h"
55 #import "RuntimeApplicationChecks.h"
56 #import "Sound.h"
57 #import "StyleProperties.h"
58 #import "Text.h"
59 #import "TypingCommand.h"
60 #import "UUID.h"
61 #import "WebNSAttributedStringExtras.h"
62 #import "htmlediting.h"
63 #import "markup.h"
64
65 namespace WebCore {
66
67 using namespace HTMLNames;
68
69 void Editor::showFontPanel()
70 {
71     [[NSFontManager sharedFontManager] orderFrontFontPanel:nil];
72 }
73
74 void Editor::showStylesPanel()
75 {
76     [[NSFontManager sharedFontManager] orderFrontStylesPanel:nil];
77 }
78
79 void Editor::showColorPanel()
80 {
81     [[NSApplication sharedApplication] orderFrontColorPanel:nil];
82 }
83
84 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText, MailBlockquoteHandling mailBlockquoteHandling)
85 {
86     RefPtr<Range> range = selectedRange();
87
88     // FIXME: How can this hard-coded pasteboard name be right, given that the passed-in pasteboard has a name?
89     client()->setInsertionPasteboard(NSGeneralPboard);
90
91     bool chosePlainText;
92     RefPtr<DocumentFragment> fragment = webContentFromPasteboard(*pasteboard, *range, allowPlainText, chosePlainText);
93
94     if (fragment && shouldInsertFragment(fragment, range, EditorInsertActionPasted))
95         pasteAsFragment(fragment, canSmartReplaceWithPasteboard(*pasteboard), false, mailBlockquoteHandling);
96
97     client()->setInsertionPasteboard(String());
98 }
99
100 bool Editor::insertParagraphSeparatorInQuotedContent()
101 {
102     // FIXME: Why is this missing calls to canEdit, canEditRichly, etc.?
103     TypingCommand::insertParagraphSeparatorInQuotedContent(document());
104     revealSelectionAfterEditingOperation();
105     return true;
106 }
107
108 const SimpleFontData* Editor::fontForSelection(bool& hasMultipleFonts) const
109 {
110     hasMultipleFonts = false;
111
112     if (!m_frame.selection().isRange()) {
113         Node* nodeToRemove;
114         RenderStyle* style = styleForSelectionStart(&m_frame, nodeToRemove); // sets nodeToRemove
115
116         const SimpleFontData* result = nullptr;
117         if (style)
118             result = &style->font().primaryFontData();
119
120         if (nodeToRemove)
121             nodeToRemove->remove(ASSERT_NO_EXCEPTION);
122
123         return result;
124     }
125
126     const SimpleFontData* font = 0;
127     RefPtr<Range> range = m_frame.selection().toNormalizedRange();
128     Node* startNode = adjustedSelectionStartForStyleComputation(m_frame.selection().selection()).deprecatedNode();
129     if (range && startNode) {
130         Node* pastEnd = range->pastLastNode();
131         // In the loop below, n should eventually match pastEnd and not become nil, but we've seen at least one
132         // unreproducible case where this didn't happen, so check for null also.
133         for (Node* node = startNode; node && node != pastEnd; node = NodeTraversal::next(node)) {
134             auto renderer = node->renderer();
135             if (!renderer)
136                 continue;
137             // FIXME: Are there any node types that have renderers, but that we should be skipping?
138             const SimpleFontData& primaryFont = renderer->style().font().primaryFontData();
139             if (!font)
140                 font = &primaryFont;
141             else if (font != &primaryFont) {
142                 hasMultipleFonts = true;
143                 break;
144             }
145         }
146     }
147
148     return font;
149 }
150
151 NSDictionary* Editor::fontAttributesForSelectionStart() const
152 {
153     Node* nodeToRemove;
154     RenderStyle* style = styleForSelectionStart(&m_frame, nodeToRemove);
155     if (!style)
156         return nil;
157
158     NSMutableDictionary* result = [NSMutableDictionary dictionary];
159
160     if (style->visitedDependentColor(CSSPropertyBackgroundColor).isValid() && style->visitedDependentColor(CSSPropertyBackgroundColor).alpha() != 0)
161         [result setObject:nsColor(style->visitedDependentColor(CSSPropertyBackgroundColor)) forKey:NSBackgroundColorAttributeName];
162
163     if (style->font().primaryFontData().getNSFont())
164         [result setObject:style->font().primaryFontData().getNSFont() forKey:NSFontAttributeName];
165
166     if (style->visitedDependentColor(CSSPropertyColor).isValid() && style->visitedDependentColor(CSSPropertyColor) != Color::black)
167         [result setObject:nsColor(style->visitedDependentColor(CSSPropertyColor)) forKey:NSForegroundColorAttributeName];
168
169     const ShadowData* shadow = style->textShadow();
170     if (shadow) {
171         RetainPtr<NSShadow> s = adoptNS([[NSShadow alloc] init]);
172         [s.get() setShadowOffset:NSMakeSize(shadow->x(), shadow->y())];
173         [s.get() setShadowBlurRadius:shadow->radius()];
174         [s.get() setShadowColor:nsColor(shadow->color())];
175         [result setObject:s.get() forKey:NSShadowAttributeName];
176     }
177
178     int decoration = style->textDecorationsInEffect();
179     if (decoration & TextDecorationLineThrough)
180         [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSStrikethroughStyleAttributeName];
181
182     int superscriptInt = 0;
183     switch (style->verticalAlign()) {
184         case BASELINE:
185         case BOTTOM:
186         case BASELINE_MIDDLE:
187         case LENGTH:
188         case MIDDLE:
189         case TEXT_BOTTOM:
190         case TEXT_TOP:
191         case TOP:
192             break;
193         case SUB:
194             superscriptInt = -1;
195             break;
196         case SUPER:
197             superscriptInt = 1;
198             break;
199     }
200     if (superscriptInt)
201         [result setObject:[NSNumber numberWithInt:superscriptInt] forKey:NSSuperscriptAttributeName];
202
203     if (decoration & TextDecorationUnderline)
204         [result setObject:[NSNumber numberWithInt:NSUnderlineStyleSingle] forKey:NSUnderlineStyleAttributeName];
205
206     if (nodeToRemove)
207         nodeToRemove->remove(ASSERT_NO_EXCEPTION);
208
209     return result;
210 }
211
212 bool Editor::canCopyExcludingStandaloneImages()
213 {
214     const VisibleSelection& selection = m_frame.selection().selection();
215     return selection.isRange() && !selection.isInPasswordField();
216 }
217
218 void Editor::takeFindStringFromSelection()
219 {
220     if (!canCopyExcludingStandaloneImages()) {
221         systemBeep();
222         return;
223     }
224
225     Vector<String> types;
226     types.append(String(NSStringPboardType));
227     platformStrategies()->pasteboardStrategy()->setTypes(types, NSFindPboard);
228     platformStrategies()->pasteboardStrategy()->setStringForType(m_frame.displayStringModifiedByEncoding(selectedTextForDataTransfer()), NSStringPboardType, NSFindPboard);
229 }
230
231 void Editor::readSelectionFromPasteboard(const String& pasteboardName, MailBlockquoteHandling mailBlockquoteHandling)
232 {
233     Pasteboard pasteboard(pasteboardName);
234     if (m_frame.selection().selection().isContentRichlyEditable())
235         pasteWithPasteboard(&pasteboard, true, mailBlockquoteHandling);
236     else
237         pasteAsPlainTextWithPasteboard(pasteboard);
238 }
239
240 static void maybeCopyNodeAttributesToFragment(const Node& node, DocumentFragment& fragment)
241 {
242     // This is only supported for single-Node fragments.
243     Node* firstChild = fragment.firstChild();
244     if (!firstChild || firstChild != fragment.lastChild())
245         return;
246
247     // And only supported for HTML elements.
248     if (!node.isHTMLElement() || !firstChild->isHTMLElement())
249         return;
250
251     // And only if the source Element and destination Element have the same HTML tag name.
252     const HTMLElement& oldElement = downcast<HTMLElement>(node);
253     HTMLElement& newElement = downcast<HTMLElement>(*firstChild);
254     if (oldElement.localName() != newElement.localName())
255         return;
256
257     for (const Attribute& attribute : oldElement.attributesIterator()) {
258         if (newElement.hasAttribute(attribute.name()))
259             continue;
260         newElement.setAttribute(attribute.name(), attribute.value());
261     }
262 }
263
264 void Editor::replaceNodeFromPasteboard(Node* node, const String& pasteboardName)
265 {
266     ASSERT(node);
267
268     if (&node->document() != m_frame.document())
269         return;
270
271     RefPtr<Range> range = Range::create(node->document(), Position(node, Position::PositionIsBeforeAnchor), Position(node, Position::PositionIsAfterAnchor));
272     m_frame.selection().setSelection(VisibleSelection(range.get()), FrameSelection::DoNotSetFocus);
273
274     Pasteboard pasteboard(pasteboardName);
275
276     if (!m_frame.selection().selection().isContentRichlyEditable()) {
277         pasteAsPlainTextWithPasteboard(pasteboard);
278         return;
279     }
280
281     // FIXME: How can this hard-coded pasteboard name be right, given that the passed-in pasteboard has a name?
282     client()->setInsertionPasteboard(NSGeneralPboard);
283
284     bool chosePlainText;
285     if (RefPtr<DocumentFragment> fragment = webContentFromPasteboard(pasteboard, *range, true, chosePlainText)) {
286         maybeCopyNodeAttributesToFragment(*node, *fragment);
287         if (shouldInsertFragment(fragment, range, EditorInsertActionPasted))
288             pasteAsFragment(fragment.release(), canSmartReplaceWithPasteboard(pasteboard), false, MailBlockquoteHandling::IgnoreBlockquote);
289     }
290
291     client()->setInsertionPasteboard(String());
292 }
293
294 // FIXME: Makes no sense that selectedTextForDataTransfer always includes alt text, but stringSelectionForPasteboard does not.
295 // This was left in a bad state when selectedTextForDataTransfer was added. Need to look over clients and fix this.
296 String Editor::stringSelectionForPasteboard()
297 {
298     if (!canCopy())
299         return "";
300     String text = selectedText();
301     text.replace(noBreakSpace, ' ');
302     return text;
303 }
304
305 String Editor::stringSelectionForPasteboardWithImageAltText()
306 {
307     if (!canCopy())
308         return "";
309     String text = selectedTextForDataTransfer();
310     text.replace(noBreakSpace, ' ');
311     return text;
312 }
313
314 PassRefPtr<SharedBuffer> Editor::selectionInWebArchiveFormat()
315 {
316     RefPtr<LegacyWebArchive> archive = LegacyWebArchive::createFromSelection(&m_frame);
317     return archive ? SharedBuffer::wrapCFData(archive->rawDataRepresentation().get()) : 0;
318 }
319
320 PassRefPtr<Range> Editor::adjustedSelectionRange()
321 {
322     // FIXME: Why do we need to adjust the selection to include the anchor tag it's in?
323     // Whoever wrote this code originally forgot to leave us a comment explaining the rationale.
324     RefPtr<Range> range = selectedRange();
325     Node* commonAncestor = range->commonAncestorContainer(IGNORE_EXCEPTION);
326     ASSERT(commonAncestor);
327     auto* enclosingAnchor = enclosingElementWithTag(firstPositionInNode(commonAncestor), HTMLNames::aTag);
328     if (enclosingAnchor && comparePositions(firstPositionInOrBeforeNode(range->startPosition().anchorNode()), range->startPosition()) >= 0)
329         range->setStart(enclosingAnchor, 0, IGNORE_EXCEPTION);
330     return range;
331 }
332     
333 static PassRefPtr<SharedBuffer> dataInRTFDFormat(NSAttributedString *string)
334 {
335     NSUInteger length = string.length;
336     if (!length)
337         return nullptr;
338
339     BEGIN_BLOCK_OBJC_EXCEPTIONS;
340     return SharedBuffer::wrapNSData([string RTFDFromRange:NSMakeRange(0, length) documentAttributes:nil]);
341     END_BLOCK_OBJC_EXCEPTIONS;
342
343     return nullptr;
344 }
345
346 static PassRefPtr<SharedBuffer> dataInRTFFormat(NSAttributedString *string)
347 {
348     NSUInteger length = string.length;
349     if (!length)
350         return nullptr;
351
352     BEGIN_BLOCK_OBJC_EXCEPTIONS;
353     return SharedBuffer::wrapNSData([string RTFFromRange:NSMakeRange(0, length) documentAttributes:nil]);
354     END_BLOCK_OBJC_EXCEPTIONS;
355
356     return nullptr;
357 }
358
359 PassRefPtr<SharedBuffer> Editor::dataSelectionForPasteboard(const String& pasteboardType)
360 {
361     // FIXME: The interface to this function is awkward. We'd probably be better off with three separate functions.
362     // As of this writing, this is only used in WebKit2 to implement the method -[WKView writeSelectionToPasteboard:types:],
363     // which is only used to support OS X services.
364
365     // FIXME: Does this function really need to use adjustedSelectionRange()? Because writeSelectionToPasteboard() just uses selectedRange().
366     if (!canCopy())
367         return nullptr;
368
369     if (pasteboardType == WebArchivePboardType)
370         return selectionInWebArchiveFormat();
371
372     if (pasteboardType == String(NSRTFDPboardType))
373        return dataInRTFDFormat(attributedStringFromRange(*adjustedSelectionRange()));
374
375     if (pasteboardType == String(NSRTFPboardType)) {
376         NSAttributedString* attributedString = attributedStringFromRange(*adjustedSelectionRange());
377         // FIXME: Why is this attachment character stripping needed here, but not needed in writeSelectionToPasteboard?
378         if ([attributedString containsAttachments])
379             attributedString = attributedStringByStrippingAttachmentCharacters(attributedString);
380         return dataInRTFFormat(attributedString);
381     }
382
383     return 0;
384 }
385
386 void Editor::writeSelectionToPasteboard(Pasteboard& pasteboard)
387 {
388     NSAttributedString *attributedString = attributedStringFromRange(*selectedRange());
389
390     PasteboardWebContent content;
391     content.canSmartCopyOrDelete = canSmartCopyOrDelete();
392     content.dataInWebArchiveFormat = selectionInWebArchiveFormat();
393     content.dataInRTFDFormat = [attributedString containsAttachments] ? dataInRTFDFormat(attributedString) : 0;
394     content.dataInRTFFormat = dataInRTFFormat(attributedString);
395     content.dataInStringFormat = stringSelectionForPasteboardWithImageAltText();
396     client()->getClientPasteboardDataForRange(selectedRange().get(), content.clientTypes, content.clientData);
397
398     pasteboard.write(content);
399 }
400
401 static void getImage(Element& imageElement, RefPtr<Image>& image, CachedImage*& cachedImage)
402 {
403     auto* renderer = imageElement.renderer();
404     if (!is<RenderImage>(renderer))
405         return;
406
407     CachedImage* tentativeCachedImage = downcast<RenderImage>(*renderer).cachedImage();
408     if (!tentativeCachedImage || tentativeCachedImage->errorOccurred())
409         return;
410
411     image = tentativeCachedImage->imageForRenderer(renderer);
412     if (!image)
413         return;
414
415     cachedImage = tentativeCachedImage;
416 }
417
418 void Editor::fillInUserVisibleForm(PasteboardURL& pasteboardURL)
419 {
420     pasteboardURL.userVisibleForm = client()->userVisibleString(pasteboardURL.url);
421 }
422
423 String Editor::plainTextFromPasteboard(const PasteboardPlainText& text)
424 {
425     String string = text.text;
426
427     // FIXME: It's not clear this is 100% correct since we know -[NSURL URLWithString:] does not handle
428     // all the same cases we handle well in the URL code for creating an NSURL.
429     if (text.isURL)
430         string = client()->userVisibleString([NSURL URLWithString:string]);
431
432     // FIXME: WTF should offer a non-Mac-specific way to convert string to precomposed form so we can do it for all platforms.
433     return [(NSString *)string precomposedStringWithCanonicalMapping];
434 }
435
436 void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElement, const URL& url, const String& title)
437 {
438     PasteboardImage pasteboardImage;
439
440     CachedImage* cachedImage;
441     getImage(imageElement, pasteboardImage.image, cachedImage);
442     if (!pasteboardImage.image)
443         return;
444     ASSERT(cachedImage);
445
446     pasteboardImage.url.url = url;
447     pasteboardImage.url.title = title;
448     pasteboardImage.url.userVisibleForm = client()->userVisibleString(pasteboardImage.url.url);
449     pasteboardImage.resourceData = cachedImage->resourceBuffer();
450     pasteboardImage.resourceMIMEType = cachedImage->response().mimeType();
451
452     pasteboard.write(pasteboardImage);
453 }
454
455 class Editor::WebContentReader final : public PasteboardWebContentReader {
456 public:
457     Frame& frame;
458     Range& context;
459     const bool allowPlainText;
460
461     RefPtr<DocumentFragment> fragment;
462     bool madeFragmentFromPlainText;
463
464     WebContentReader(Frame& frame, Range& context, bool allowPlainText)
465         : frame(frame)
466         , context(context)
467         , allowPlainText(allowPlainText)
468         , madeFragmentFromPlainText(false)
469     {
470     }
471
472 private:
473     virtual bool readWebArchive(PassRefPtr<SharedBuffer>) override;
474     virtual bool readFilenames(const Vector<String>&) override;
475     virtual bool readHTML(const String&) override;
476     virtual bool readRTFD(PassRefPtr<SharedBuffer>) override;
477     virtual bool readRTF(PassRefPtr<SharedBuffer>) override;
478     virtual bool readImage(PassRefPtr<SharedBuffer>, const String& type) override;
479     virtual bool readURL(const URL&, const String& title) override;
480     virtual bool readPlainText(const String&) override;
481 };
482
483 bool Editor::WebContentReader::readWebArchive(PassRefPtr<SharedBuffer> buffer)
484 {
485     if (!frame.document())
486         return false;
487
488     RefPtr<LegacyWebArchive> archive = LegacyWebArchive::create(URL(), buffer.get());
489     if (!archive)
490         return false;
491
492     RefPtr<ArchiveResource> mainResource = archive->mainResource();
493     if (!mainResource)
494         return false;
495
496     const String& type = mainResource->mimeType();
497
498     if (frame.loader().client().canShowMIMETypeAsHTML(type)) {
499         // FIXME: The code in createFragmentAndAddResources calls setDefersLoading(true). Don't we need that here?
500         if (DocumentLoader* loader = frame.loader().documentLoader())
501             loader->addAllArchiveResources(archive.get());
502
503         String markupString = String::fromUTF8(mainResource->data()->data(), mainResource->data()->size());
504         fragment = createFragmentFromMarkup(*frame.document(), markupString, mainResource->url(), DisallowScriptingAndPluginContent);
505         return true;
506     }
507
508     if (MIMETypeRegistry::isSupportedImageMIMEType(type)) {
509         fragment = frame.editor().createFragmentForImageResourceAndAddResource(mainResource.release());
510         return true;
511     }
512
513     return false;
514 }
515
516 bool Editor::WebContentReader::readFilenames(const Vector<String>& paths)
517 {
518     size_t size = paths.size();
519     if (!size)
520         return false;
521
522     if (!frame.document())
523         return false;
524     Document& document = *frame.document();
525
526     fragment = document.createDocumentFragment();
527
528     for (size_t i = 0; i < size; i++) {
529         String text = paths[i];
530         text = frame.editor().client()->userVisibleString([NSURL fileURLWithPath:text]);
531
532         RefPtr<HTMLElement> paragraph = createDefaultParagraphElement(document);
533         paragraph->appendChild(document.createTextNode(text));
534         fragment->appendChild(paragraph.release());
535     }
536
537     return true;
538 }
539
540 bool Editor::WebContentReader::readHTML(const String& string)
541 {
542     String stringOmittingMicrosoftPrefix = string;
543
544     // This code was added to make HTML paste from Microsoft Word on Mac work, back in 2004.
545     // It's a simple-minded way to ignore the CF_HTML clipboard format, just skipping over the
546     // description part and parsing the entire context plus fragment.
547     if (string.startsWith("Version:")) {
548         size_t location = string.findIgnoringCase("<html");
549         if (location != notFound)
550             stringOmittingMicrosoftPrefix = string.substring(location);
551     }
552
553     if (stringOmittingMicrosoftPrefix.isEmpty())
554         return false;
555
556     if (!frame.document())
557         return false;
558     Document& document = *frame.document();
559
560     fragment = createFragmentFromMarkup(document, stringOmittingMicrosoftPrefix, emptyString(), DisallowScriptingAndPluginContent);
561     return fragment;
562 }
563
564 bool Editor::WebContentReader::readRTFD(PassRefPtr<SharedBuffer> buffer)
565 {
566     fragment = frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTFD:buffer->createNSData().get() documentAttributes:nullptr]).get());
567     return fragment;
568 }
569
570 bool Editor::WebContentReader::readRTF(PassRefPtr<SharedBuffer> buffer)
571 {
572     fragment = frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTF:buffer->createNSData().get() documentAttributes:nullptr]).get());
573     return fragment;
574 }
575
576 bool Editor::WebContentReader::readImage(PassRefPtr<SharedBuffer> buffer, const String& type)
577 {
578     ASSERT(type.contains('/'));
579     String typeAsFilenameWithExtension = type;
580     typeAsFilenameWithExtension.replace('/', '.');
581     URL imageURL = URL::fakeURLWithRelativePart(typeAsFilenameWithExtension);
582
583     fragment = frame.editor().createFragmentForImageResourceAndAddResource(ArchiveResource::create(buffer, imageURL, type, emptyString(), emptyString()));
584     return fragment;
585 }
586
587 bool Editor::WebContentReader::readURL(const URL& url, const String& title)
588 {
589     if (url.string().isEmpty())
590         return false;
591
592     RefPtr<Element> anchor = frame.document()->createElement(HTMLNames::aTag, false);
593     anchor->setAttribute(HTMLNames::hrefAttr, url.string());
594     anchor->appendChild(frame.document()->createTextNode([title precomposedStringWithCanonicalMapping]));
595
596     fragment = frame.document()->createDocumentFragment();
597     fragment->appendChild(anchor.release());
598     return true;
599 }
600
601 bool Editor::WebContentReader::readPlainText(const String& text)
602 {
603     if (!allowPlainText)
604         return false;
605
606     fragment = createFragmentFromText(context, [text precomposedStringWithCanonicalMapping]);
607     if (!fragment)
608         return false;
609
610     madeFragmentFromPlainText = true;
611     return true;
612 }
613
614 // FIXME: Should give this function a name that makes it clear it adds resources to the document loader as a side effect.
615 // Or refactor so it does not do that.
616 PassRefPtr<DocumentFragment> Editor::webContentFromPasteboard(Pasteboard& pasteboard, Range& context, bool allowPlainText, bool& chosePlainText)
617 {
618     WebContentReader reader(m_frame, context, allowPlainText);
619     pasteboard.read(reader);
620     chosePlainText = reader.madeFragmentFromPlainText;
621     return reader.fragment.release();
622 }
623
624 PassRefPtr<DocumentFragment> Editor::createFragmentForImageResourceAndAddResource(PassRefPtr<ArchiveResource> resource)
625 {
626     if (!resource)
627         return nullptr;
628
629     // FIXME: The code in createFragmentAndAddResources calls setDefersLoading(true). Don't we need that here?
630     if (DocumentLoader* loader = m_frame.loader().documentLoader())
631         loader->addArchiveResource(resource.get());
632
633     RefPtr<Element> imageElement = document().createElement(HTMLNames::imgTag, false);
634     imageElement->setAttribute(HTMLNames::srcAttr, resource->url().string());
635
636     RefPtr<DocumentFragment> fragment = document().createDocumentFragment();
637     fragment->appendChild(imageElement.release());
638
639     return fragment.release();
640 }
641
642 PassRefPtr<DocumentFragment> Editor::createFragmentAndAddResources(NSAttributedString *string)
643 {
644     if (!m_frame.page() || !document().isHTMLDocument())
645         return nullptr;
646
647     if (!string)
648         return nullptr;
649
650     bool wasDeferringCallbacks = m_frame.page()->defersLoading();
651     if (!wasDeferringCallbacks)
652         m_frame.page()->setDefersLoading(true);
653
654     Vector<RefPtr<ArchiveResource>> resources;
655     RefPtr<DocumentFragment> fragment = client()->documentFragmentFromAttributedString(string, resources);
656
657     if (DocumentLoader* loader = m_frame.loader().documentLoader()) {
658         for (size_t i = 0, size = resources.size(); i < size; ++i)
659             loader->addArchiveResource(resources[i]);
660     }
661
662     if (!wasDeferringCallbacks)
663         m_frame.page()->setDefersLoading(false);
664
665     return fragment.release();
666 }
667
668 void Editor::replaceSelectionWithAttributedString(NSAttributedString *attributedString, MailBlockquoteHandling mailBlockquoteHandling)
669 {
670     if (m_frame.selection().isNone())
671         return;
672
673     if (m_frame.selection().selection().isContentRichlyEditable()) {
674         RefPtr<DocumentFragment> fragment = createFragmentAndAddResources(attributedString);
675         if (fragment && shouldInsertFragment(fragment, selectedRange(), EditorInsertActionPasted))
676             pasteAsFragment(fragment, false, false, mailBlockquoteHandling);
677     } else {
678         String text = [attributedString string];
679         if (shouldInsertText(text, selectedRange().get(), EditorInsertActionPasted))
680             pasteAsPlainText(text, false);
681     }
682 }
683
684 } // namespace WebCore