60efef7bf9f54f2d9415474f9fae1dfd6449272a
[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 "DOMURL.h"
33 #import "DataTransfer.h"
34 #import "DocumentFragment.h"
35 #import "DocumentLoader.h"
36 #import "Editing.h"
37 #import "Editor.h"
38 #import "EditorClient.h"
39 #import "File.h"
40 #import "FontCascade.h"
41 #import "Frame.h"
42 #import "FrameLoaderClient.h"
43 #import "FrameView.h"
44 #import "HTMLAnchorElement.h"
45 #import "HTMLAttachmentElement.h"
46 #import "HTMLConverter.h"
47 #import "HTMLElement.h"
48 #import "HTMLImageElement.h"
49 #import "HTMLNames.h"
50 #import "LegacyWebArchive.h"
51 #import "MIMETypeRegistry.h"
52 #import "NodeTraversal.h"
53 #import "Page.h"
54 #import "Pasteboard.h"
55 #import "PasteboardStrategy.h"
56 #import "PlatformStrategies.h"
57 #import "Range.h"
58 #import "RenderBlock.h"
59 #import "RenderImage.h"
60 #import "RuntimeApplicationChecks.h"
61 #import "Settings.h"
62 #import "Sound.h"
63 #import "StyleProperties.h"
64 #import "Text.h"
65 #import "TypingCommand.h"
66 #import "UUID.h"
67 #import "WebNSAttributedStringExtras.h"
68 #import "markup.h"
69
70 namespace WebCore {
71
72 using namespace HTMLNames;
73
74 void Editor::showFontPanel()
75 {
76     [[NSFontManager sharedFontManager] orderFrontFontPanel:nil];
77 }
78
79 void Editor::showStylesPanel()
80 {
81     [[NSFontManager sharedFontManager] orderFrontStylesPanel:nil];
82 }
83
84 void Editor::showColorPanel()
85 {
86     [[NSApplication sharedApplication] orderFrontColorPanel:nil];
87 }
88
89 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, bool allowPlainText, MailBlockquoteHandling mailBlockquoteHandling)
90 {
91     RefPtr<Range> range = selectedRange();
92
93 #pragma clang diagnostic push
94 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
95     // FIXME: How can this hard-coded pasteboard name be right, given that the passed-in pasteboard has a name?
96     client()->setInsertionPasteboard(NSGeneralPboard);
97 #pragma clang diagnostic pop
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 bool Editor::canCopyExcludingStandaloneImages()
109 {
110     const VisibleSelection& selection = m_frame.selection().selection();
111     return selection.isRange() && !selection.isInPasswordField();
112 }
113
114 void Editor::takeFindStringFromSelection()
115 {
116     if (!canCopyExcludingStandaloneImages()) {
117         systemBeep();
118         return;
119     }
120
121     Vector<String> types;
122     types.append(String(NSStringPboardType));
123 #pragma clang diagnostic push
124 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
125     platformStrategies()->pasteboardStrategy()->setTypes(types, NSFindPboard);
126     platformStrategies()->pasteboardStrategy()->setStringForType(m_frame.displayStringModifiedByEncoding(selectedTextForDataTransfer()), NSStringPboardType, NSFindPboard);
127 #pragma clang diagnostic pop
128 }
129
130 void Editor::readSelectionFromPasteboard(const String& pasteboardName, MailBlockquoteHandling mailBlockquoteHandling)
131 {
132     Pasteboard pasteboard(pasteboardName);
133     if (m_frame.selection().selection().isContentRichlyEditable())
134         pasteWithPasteboard(&pasteboard, true, mailBlockquoteHandling);
135     else
136         pasteAsPlainTextWithPasteboard(pasteboard);
137 }
138
139 static void maybeCopyNodeAttributesToFragment(const Node& node, DocumentFragment& fragment)
140 {
141     // This is only supported for single-Node fragments.
142     Node* firstChild = fragment.firstChild();
143     if (!firstChild || firstChild != fragment.lastChild())
144         return;
145
146     // And only supported for HTML elements.
147     if (!node.isHTMLElement() || !firstChild->isHTMLElement())
148         return;
149
150     // And only if the source Element and destination Element have the same HTML tag name.
151     const HTMLElement& oldElement = downcast<HTMLElement>(node);
152     HTMLElement& newElement = downcast<HTMLElement>(*firstChild);
153     if (oldElement.localName() != newElement.localName())
154         return;
155
156     for (const Attribute& attribute : oldElement.attributesIterator()) {
157         if (newElement.hasAttribute(attribute.name()))
158             continue;
159         newElement.setAttribute(attribute.name(), attribute.value());
160     }
161 }
162
163 void Editor::replaceNodeFromPasteboard(Node* node, const String& pasteboardName)
164 {
165     ASSERT(node);
166
167     if (&node->document() != m_frame.document())
168         return;
169
170     Ref<Frame> protector(m_frame);
171     RefPtr<Range> range = Range::create(node->document(), Position(node, Position::PositionIsBeforeAnchor), Position(node, Position::PositionIsAfterAnchor));
172     m_frame.selection().setSelection(VisibleSelection(*range), FrameSelection::DoNotSetFocus);
173
174     Pasteboard pasteboard(pasteboardName);
175
176     if (!m_frame.selection().selection().isContentRichlyEditable()) {
177         pasteAsPlainTextWithPasteboard(pasteboard);
178         return;
179     }
180
181 #pragma clang diagnostic push
182 #pragma clang diagnostic ignored "-Wdeprecated-declarations"
183     // FIXME: How can this hard-coded pasteboard name be right, given that the passed-in pasteboard has a name?
184     client()->setInsertionPasteboard(NSGeneralPboard);
185 #pragma clang diagnostic pop
186
187     bool chosePlainText;
188     if (RefPtr<DocumentFragment> fragment = webContentFromPasteboard(pasteboard, *range, true, chosePlainText)) {
189         maybeCopyNodeAttributesToFragment(*node, *fragment);
190         if (shouldInsertFragment(fragment, range, EditorInsertAction::Pasted))
191             pasteAsFragment(fragment.releaseNonNull(), canSmartReplaceWithPasteboard(pasteboard), false, MailBlockquoteHandling::IgnoreBlockquote);
192     }
193
194     client()->setInsertionPasteboard(String());
195 }
196
197 String Editor::selectionInHTMLFormat()
198 {
199     return createMarkup(*selectedRange(), nullptr, AnnotateForInterchange, false, ResolveNonLocalURLs);
200 }
201
202 RefPtr<SharedBuffer> Editor::imageInWebArchiveFormat(Element& imageElement)
203 {
204     RefPtr<LegacyWebArchive> archive = LegacyWebArchive::create(imageElement);
205     if (!archive)
206         return nullptr;
207     return SharedBuffer::wrapCFData(archive->rawDataRepresentation().get());
208 }
209
210 RefPtr<SharedBuffer> Editor::dataSelectionForPasteboard(const String& pasteboardType)
211 {
212     // FIXME: The interface to this function is awkward. We'd probably be better off with three separate functions.
213     // As of this writing, this is only used in WebKit2 to implement the method -[WKView writeSelectionToPasteboard:types:],
214     // which is only used to support OS X services.
215
216     // FIXME: Does this function really need to use adjustedSelectionRange()? Because writeSelectionToPasteboard() just uses selectedRange().
217     if (!canCopy())
218         return nullptr;
219
220     if (pasteboardType == WebArchivePboardType)
221         return selectionInWebArchiveFormat();
222
223     if (pasteboardType == String(NSRTFDPboardType))
224        return dataInRTFDFormat(attributedStringFromRange(*adjustedSelectionRange()));
225
226     if (pasteboardType == String(NSRTFPboardType)) {
227         NSAttributedString* attributedString = attributedStringFromRange(*adjustedSelectionRange());
228         // FIXME: Why is this attachment character stripping needed here, but not needed in writeSelectionToPasteboard?
229         if ([attributedString containsAttachments])
230             attributedString = attributedStringByStrippingAttachmentCharacters(attributedString);
231         return dataInRTFFormat(attributedString);
232     }
233
234     return nullptr;
235 }
236
237 static void getImage(Element& imageElement, RefPtr<Image>& image, CachedImage*& cachedImage)
238 {
239     auto* renderer = imageElement.renderer();
240     if (!is<RenderImage>(renderer))
241         return;
242
243     CachedImage* tentativeCachedImage = downcast<RenderImage>(*renderer).cachedImage();
244     if (!tentativeCachedImage || tentativeCachedImage->errorOccurred())
245         return;
246
247     image = tentativeCachedImage->imageForRenderer(renderer);
248     if (!image)
249         return;
250
251     cachedImage = tentativeCachedImage;
252 }
253
254 String Editor::userVisibleString(const URL& url)
255 {
256     return client()->userVisibleString(url);
257 }
258
259 void Editor::selectionWillChange()
260 {
261     if (!hasComposition() || ignoreSelectionChanges() || m_frame.selection().isNone())
262         return;
263
264     cancelComposition();
265     client()->canceledComposition();
266 }
267
268 String Editor::plainTextFromPasteboard(const PasteboardPlainText& text)
269 {
270     String string = text.text;
271
272     // FIXME: It's not clear this is 100% correct since we know -[NSURL URLWithString:] does not handle
273     // all the same cases we handle well in the URL code for creating an NSURL.
274     if (text.isURL)
275         string = client()->userVisibleString([NSURL URLWithString:string]);
276
277     // FIXME: WTF should offer a non-Mac-specific way to convert string to precomposed form so we can do it for all platforms.
278     return [(NSString *)string precomposedStringWithCanonicalMapping];
279 }
280
281 void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElement, const URL& url, const String& title)
282 {
283     PasteboardImage pasteboardImage;
284
285     CachedImage* cachedImage;
286     getImage(imageElement, pasteboardImage.image, cachedImage);
287     if (!pasteboardImage.image)
288         return;
289     ASSERT(cachedImage);
290
291     pasteboardImage.dataInWebArchiveFormat = imageInWebArchiveFormat(imageElement);
292     pasteboardImage.url.url = url;
293     pasteboardImage.url.title = title;
294     pasteboardImage.url.userVisibleForm = client()->userVisibleString(pasteboardImage.url.url);
295     pasteboardImage.resourceData = cachedImage->resourceBuffer();
296     pasteboardImage.resourceMIMEType = cachedImage->response().mimeType();
297
298     pasteboard.write(pasteboardImage);
299 }
300
301 class Editor::WebContentReader final : public PasteboardWebContentReader {
302 public:
303     Frame& frame;
304     Range& context;
305     const bool allowPlainText;
306
307     RefPtr<DocumentFragment> fragment;
308     bool madeFragmentFromPlainText;
309
310     WebContentReader(Frame& frame, Range& context, bool allowPlainText)
311         : frame(frame)
312         , context(context)
313         , allowPlainText(allowPlainText)
314         , madeFragmentFromPlainText(false)
315     {
316     }
317
318 private:
319     bool readWebArchive(SharedBuffer*) override;
320     bool readFilenames(const Vector<String>&) override;
321     bool readHTML(const String&) override;
322     bool readRTFD(SharedBuffer&) override;
323     bool readRTF(SharedBuffer&) override;
324     bool readImage(Ref<SharedBuffer>&&, const String& type) override;
325     bool readURL(const URL&, const String& title) override;
326     bool readPlainText(const String&) override;
327 };
328
329 bool Editor::WebContentReader::readWebArchive(SharedBuffer* buffer)
330 {
331     if (frame.settings().preferMIMETypeForImages())
332         return false;
333
334     if (!frame.document())
335         return false;
336
337     if (!buffer)
338         return false;
339
340     auto archive = LegacyWebArchive::create(URL(), *buffer);
341     if (!archive)
342         return false;
343
344     RefPtr<ArchiveResource> mainResource = archive->mainResource();
345     if (!mainResource)
346         return false;
347
348     const String& type = mainResource->mimeType();
349
350     if (frame.loader().client().canShowMIMETypeAsHTML(type)) {
351         // FIXME: The code in createFragmentAndAddResources calls setDefersLoading(true). Don't we need that here?
352         if (DocumentLoader* loader = frame.loader().documentLoader())
353             loader->addAllArchiveResources(*archive);
354
355         String markupString = String::fromUTF8(mainResource->data().data(), mainResource->data().size());
356         fragment = createFragmentFromMarkup(*frame.document(), markupString, mainResource->url(), DisallowScriptingAndPluginContent);
357         return true;
358     }
359
360     if (MIMETypeRegistry::isSupportedImageMIMEType(type)) {
361         fragment = frame.editor().createFragmentForImageResourceAndAddResource(WTFMove(mainResource));
362         return true;
363     }
364
365     return false;
366 }
367
368 bool Editor::WebContentReader::readFilenames(const Vector<String>& paths)
369 {
370     if (paths.isEmpty())
371         return false;
372
373     if (!frame.document())
374         return false;
375     Document& document = *frame.document();
376
377     fragment = document.createDocumentFragment();
378
379     for (auto& text : paths) {
380 #if ENABLE(ATTACHMENT_ELEMENT)
381         auto attachment = HTMLAttachmentElement::create(attachmentTag, document);
382         attachment->setFile(File::create([[NSURL fileURLWithPath:text] path]).ptr());
383         fragment->appendChild(attachment);
384 #else
385         auto paragraph = createDefaultParagraphElement(document);
386         paragraph->appendChild(document.createTextNode(frame.editor().client()->userVisibleString([NSURL fileURLWithPath:text])));
387         fragment->appendChild(paragraph);
388 #endif
389     }
390
391     return true;
392 }
393
394 bool Editor::WebContentReader::readHTML(const String& string)
395 {
396     String stringOmittingMicrosoftPrefix = string;
397
398     // This code was added to make HTML paste from Microsoft Word on Mac work, back in 2004.
399     // It's a simple-minded way to ignore the CF_HTML clipboard format, just skipping over the
400     // description part and parsing the entire context plus fragment.
401     if (string.startsWith("Version:")) {
402         size_t location = string.findIgnoringCase("<html");
403         if (location != notFound)
404             stringOmittingMicrosoftPrefix = string.substring(location);
405     }
406
407     if (stringOmittingMicrosoftPrefix.isEmpty())
408         return false;
409
410     if (!frame.document())
411         return false;
412     Document& document = *frame.document();
413
414     fragment = createFragmentFromMarkup(document, stringOmittingMicrosoftPrefix, emptyString(), DisallowScriptingAndPluginContent);
415     return fragment;
416 }
417
418 bool Editor::WebContentReader::readRTFD(SharedBuffer& buffer)
419 {
420     if (frame.settings().preferMIMETypeForImages())
421         return false;
422
423     fragment = frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTFD:buffer.createNSData().get() documentAttributes:nullptr]).get());
424     return fragment;
425 }
426
427 bool Editor::WebContentReader::readRTF(SharedBuffer& buffer)
428 {
429     if (frame.settings().preferMIMETypeForImages())
430         return false;
431
432     fragment = frame.editor().createFragmentAndAddResources(adoptNS([[NSAttributedString alloc] initWithRTF:buffer.createNSData().get() documentAttributes:nullptr]).get());
433     return fragment;
434 }
435
436 bool Editor::WebContentReader::readImage(Ref<SharedBuffer>&& buffer, const String& type)
437 {
438     ASSERT(type.contains('/'));
439     String typeAsFilenameWithExtension = type;
440     typeAsFilenameWithExtension.replace('/', '.');
441
442     Vector<uint8_t> data;
443     data.append(buffer->data(), buffer->size());
444     auto blob = Blob::create(WTFMove(data), type);
445     ASSERT(frame.document());
446     String blobURL = DOMURL::createObjectURL(*frame.document(), blob);
447
448     fragment = frame.editor().createFragmentForImageAndURL(blobURL);
449     return fragment;
450 }
451
452 bool Editor::WebContentReader::readURL(const URL& url, const String& title)
453 {
454     if (url.string().isEmpty())
455         return false;
456
457     auto anchor = HTMLAnchorElement::create(*frame.document());
458     anchor->setAttributeWithoutSynchronization(HTMLNames::hrefAttr, url.string());
459     anchor->appendChild(frame.document()->createTextNode([title precomposedStringWithCanonicalMapping]));
460
461     fragment = frame.document()->createDocumentFragment();
462     fragment->appendChild(anchor);
463     return true;
464 }
465
466 bool Editor::WebContentReader::readPlainText(const String& text)
467 {
468     if (!allowPlainText)
469         return false;
470
471     fragment = createFragmentFromText(context, [text precomposedStringWithCanonicalMapping]);
472     if (!fragment)
473         return false;
474
475     madeFragmentFromPlainText = true;
476     return true;
477 }
478
479 // FIXME: Should give this function a name that makes it clear it adds resources to the document loader as a side effect.
480 // Or refactor so it does not do that.
481 RefPtr<DocumentFragment> Editor::webContentFromPasteboard(Pasteboard& pasteboard, Range& context, bool allowPlainText, bool& chosePlainText)
482 {
483     WebContentReader reader(m_frame, context, allowPlainText);
484     pasteboard.read(reader);
485     chosePlainText = reader.madeFragmentFromPlainText;
486     return WTFMove(reader.fragment);
487 }
488
489 void Editor::applyFontStyles(const String& fontFamily, double fontSize, unsigned fontTraits)
490 {
491     auto& cssValuePool = CSSValuePool::singleton();
492     Ref<MutableStyleProperties> style = MutableStyleProperties::create();
493     style->setProperty(CSSPropertyFontFamily, cssValuePool.createFontFamilyValue(fontFamily));
494     style->setProperty(CSSPropertyFontStyle, (fontTraits & NSFontItalicTrait) ? CSSValueItalic : CSSValueNormal);
495     style->setProperty(CSSPropertyFontWeight, (fontTraits & NSFontBoldTrait) ? CSSValueBold : CSSValueNormal);
496     style->setProperty(CSSPropertyFontSize, cssValuePool.createValue(fontSize, CSSPrimitiveValue::CSS_PX));
497     applyStyleToSelection(style.ptr(), EditActionSetFont);
498 }
499
500 } // namespace WebCore