Add support for using the current text selection as the find string on iOS
[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 #if PLATFORM(MAC)
30
31 #import "Blob.h"
32 #import "CSSPrimitiveValueMappings.h"
33 #import "CSSValuePool.h"
34 #import "DataTransfer.h"
35 #import "DocumentFragment.h"
36 #import "Editing.h"
37 #import "EditorClient.h"
38 #import "Frame.h"
39 #import "FrameView.h"
40 #import "HTMLConverter.h"
41 #import "HTMLElement.h"
42 #import "HTMLNames.h"
43 #import "LegacyNSPasteboardTypes.h"
44 #import "LegacyWebArchive.h"
45 #import "Pasteboard.h"
46 #import "PasteboardStrategy.h"
47 #import "PlatformStrategies.h"
48 #import "Range.h"
49 #import "RenderBlock.h"
50 #import "RenderImage.h"
51 #import "RuntimeApplicationChecks.h"
52 #import "RuntimeEnabledFeatures.h"
53 #import "StyleProperties.h"
54 #import "WebContentReader.h"
55 #import "WebNSAttributedStringExtras.h"
56 #import "markup.h"
57 #import <AppKit/AppKit.h>
58 #import <wtf/cocoa/NSURLExtras.h>
59
60 namespace WebCore {
61
62 using namespace HTMLNames;
63
64 void Editor::showFontPanel()
65 {
66     [[NSFontManager sharedFontManager] orderFrontFontPanel:nil];
67 }
68
69 void Editor::showStylesPanel()
70 {
71     [[NSFontManager sharedFontManager] orderFrontStylesPanel:nil];
72 }
73
74 void Editor::showColorPanel()
75 {
76     [[NSApplication sharedApplication] orderFrontColorPanel:nil];
77 }
78
79 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, OptionSet<PasteOption> options)
80 {
81     RefPtr<Range> range = selectedRange();
82
83     ALLOW_DEPRECATED_DECLARATIONS_BEGIN
84     // FIXME: How can this hard-coded pasteboard name be right, given that the passed-in pasteboard has a name?
85     client()->setInsertionPasteboard(NSGeneralPboard);
86     ALLOW_DEPRECATED_DECLARATIONS_END
87
88     bool chosePlainText;
89     RefPtr<DocumentFragment> fragment = webContentFromPasteboard(*pasteboard, *range, options.contains(PasteOption::AllowPlainText), chosePlainText);
90
91     if (fragment && options.contains(PasteOption::AsQuotation))
92         quoteFragmentForPasting(*fragment);
93
94     if (fragment && shouldInsertFragment(*fragment, range.get(), EditorInsertAction::Pasted))
95         pasteAsFragment(fragment.releaseNonNull(), canSmartReplaceWithPasteboard(*pasteboard), false, options.contains(PasteOption::IgnoreMailBlockquote) ? MailBlockquoteHandling::IgnoreBlockquote : MailBlockquoteHandling::RespectBlockquote );
96
97     client()->setInsertionPasteboard(String());
98 }
99
100 void Editor::readSelectionFromPasteboard(const String& pasteboardName)
101 {
102     Pasteboard pasteboard(pasteboardName);
103     if (m_frame.selection().selection().isContentRichlyEditable())
104         pasteWithPasteboard(&pasteboard, { PasteOption::AllowPlainText });
105     else
106         pasteAsPlainTextWithPasteboard(pasteboard);
107 }
108
109 static void maybeCopyNodeAttributesToFragment(const Node& node, DocumentFragment& fragment)
110 {
111     // This is only supported for single-Node fragments.
112     Node* firstChild = fragment.firstChild();
113     if (!firstChild || firstChild != fragment.lastChild())
114         return;
115
116     // And only supported for HTML elements.
117     if (!node.isHTMLElement() || !firstChild->isHTMLElement())
118         return;
119
120     // And only if the source Element and destination Element have the same HTML tag name.
121     const HTMLElement& oldElement = downcast<HTMLElement>(node);
122     HTMLElement& newElement = downcast<HTMLElement>(*firstChild);
123     if (oldElement.localName() != newElement.localName())
124         return;
125
126     for (const Attribute& attribute : oldElement.attributesIterator()) {
127         if (newElement.hasAttribute(attribute.name()))
128             continue;
129         newElement.setAttribute(attribute.name(), attribute.value());
130     }
131 }
132
133 void Editor::replaceNodeFromPasteboard(Node* node, const String& pasteboardName)
134 {
135     ASSERT(node);
136
137     if (&node->document() != m_frame.document())
138         return;
139
140     Ref<Frame> protector(m_frame);
141     auto range = Range::create(node->document(), Position(node, Position::PositionIsBeforeAnchor), Position(node, Position::PositionIsAfterAnchor));
142     m_frame.selection().setSelection(VisibleSelection(range.get()), FrameSelection::DoNotSetFocus);
143
144     Pasteboard pasteboard(pasteboardName);
145
146     if (!m_frame.selection().selection().isContentRichlyEditable()) {
147         pasteAsPlainTextWithPasteboard(pasteboard);
148         return;
149     }
150
151     ALLOW_DEPRECATED_DECLARATIONS_BEGIN
152     // FIXME: How can this hard-coded pasteboard name be right, given that the passed-in pasteboard has a name?
153     client()->setInsertionPasteboard(NSGeneralPboard);
154     ALLOW_DEPRECATED_DECLARATIONS_END
155
156     bool chosePlainText;
157     if (RefPtr<DocumentFragment> fragment = webContentFromPasteboard(pasteboard, range.get(), true, chosePlainText)) {
158         maybeCopyNodeAttributesToFragment(*node, *fragment);
159         if (shouldInsertFragment(*fragment, range.ptr(), EditorInsertAction::Pasted))
160             pasteAsFragment(fragment.releaseNonNull(), canSmartReplaceWithPasteboard(pasteboard), false, MailBlockquoteHandling::IgnoreBlockquote);
161     }
162
163     client()->setInsertionPasteboard(String());
164 }
165
166 RefPtr<SharedBuffer> Editor::imageInWebArchiveFormat(Element& imageElement)
167 {
168     auto archive = LegacyWebArchive::create(imageElement);
169     if (!archive)
170         return nullptr;
171     return SharedBuffer::create(archive->rawDataRepresentation().get());
172 }
173
174 RefPtr<SharedBuffer> Editor::dataSelectionForPasteboard(const String& pasteboardType)
175 {
176     // FIXME: The interface to this function is awkward. We'd probably be better off with three separate functions.
177     // As of this writing, this is only used in WebKit2 to implement the method -[WKView writeSelectionToPasteboard:types:],
178     // which is only used to support OS X services.
179
180     // FIXME: Does this function really need to use adjustedSelectionRange()? Because writeSelectionToPasteboard() just uses selectedRange().
181     if (!canCopy())
182         return nullptr;
183
184     if (pasteboardType == WebArchivePboardType)
185         return selectionInWebArchiveFormat();
186
187     if (pasteboardType == String(legacyRTFDPasteboardType()))
188        return dataInRTFDFormat(attributedStringFromRange(*adjustedSelectionRange()));
189
190     if (pasteboardType == String(legacyRTFPasteboardType())) {
191         NSAttributedString* attributedString = attributedStringFromRange(*adjustedSelectionRange());
192         // FIXME: Why is this attachment character stripping needed here, but not needed in writeSelectionToPasteboard?
193         if ([attributedString containsAttachments])
194             attributedString = attributedStringByStrippingAttachmentCharacters(attributedString);
195         return dataInRTFFormat(attributedString);
196     }
197
198     return nullptr;
199 }
200
201 static void getImage(Element& imageElement, RefPtr<Image>& image, CachedImage*& cachedImage)
202 {
203     auto* renderer = imageElement.renderer();
204     if (!is<RenderImage>(renderer))
205         return;
206
207     CachedImage* tentativeCachedImage = downcast<RenderImage>(*renderer).cachedImage();
208     if (!tentativeCachedImage || tentativeCachedImage->errorOccurred())
209         return;
210
211     image = tentativeCachedImage->imageForRenderer(renderer);
212     if (!image)
213         return;
214
215     cachedImage = tentativeCachedImage;
216 }
217
218 void Editor::selectionWillChange()
219 {
220     if (!hasComposition() || ignoreSelectionChanges() || m_frame.selection().isNone())
221         return;
222
223     cancelComposition();
224     client()->canceledComposition();
225 }
226
227 String Editor::plainTextFromPasteboard(const PasteboardPlainText& text)
228 {
229     auto string = text.text;
230
231     // FIXME: It's not clear this is 100% correct since we know -[NSURL URLWithString:] does not handle
232     // all the same cases we handle well in the URL code for creating an NSURL.
233     if (text.isURL)
234         string = WTF::userVisibleString([NSURL URLWithString:string]);
235
236     // FIXME: WTF should offer a non-Mac-specific way to convert string to precomposed form so we can do it for all platforms.
237     return [(NSString *)string precomposedStringWithCanonicalMapping];
238 }
239
240 void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElement, const URL& url, const String& title)
241 {
242     PasteboardImage pasteboardImage;
243
244     CachedImage* cachedImage = nullptr;
245     getImage(imageElement, pasteboardImage.image, cachedImage);
246     if (!pasteboardImage.image)
247         return;
248     ASSERT(cachedImage);
249
250     pasteboardImage.dataInWebArchiveFormat = imageInWebArchiveFormat(imageElement);
251     pasteboardImage.url.url = url;
252     pasteboardImage.url.title = title;
253     pasteboardImage.url.userVisibleForm = WTF::userVisibleString(pasteboardImage.url.url);
254     pasteboardImage.resourceData = cachedImage->resourceBuffer();
255     pasteboardImage.resourceMIMEType = cachedImage->response().mimeType();
256
257     pasteboard.write(pasteboardImage);
258 }
259
260 } // namespace WebCore
261
262 #endif // PLATFORM(MAC)