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