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