05dc8a8c0dfde21c1206df99a8127e3cabe7d173
[WebKit-https.git] / Source / WebCore / editing / cocoa / EditorCocoa.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 "ArchiveResource.h"
30 #import "CSSValueList.h"
31 #import "CSSValuePool.h"
32 #import "CachedResourceLoader.h"
33 #import "ColorMac.h"
34 #import "DocumentFragment.h"
35 #import "DocumentLoader.h"
36 #import "Editing.h"
37 #import "EditingStyle.h"
38 #import "EditorClient.h"
39 #import "Frame.h"
40 #import "FrameSelection.h"
41 #import "HTMLConverter.h"
42 #import "HTMLImageElement.h"
43 #import "HTMLSpanElement.h"
44 #import "LegacyWebArchive.h"
45 #import "NSAttributedStringSPI.h"
46 #import "Pasteboard.h"
47 #import "RenderElement.h"
48 #import "RenderStyle.h"
49 #import "SoftLinking.h"
50 #import "Text.h"
51 #import <wtf/BlockObjCExceptions.h>
52
53 #if PLATFORM(IOS)
54 SOFT_LINK_PRIVATE_FRAMEWORK(WebKitLegacy)
55 #endif
56
57 #if PLATFORM(MAC)
58 SOFT_LINK_FRAMEWORK_IN_UMBRELLA(WebKit, WebKitLegacy)
59 #endif
60
61 // FIXME: Get rid of this and change NSAttributedString conversion so it doesn't use WebKitLegacy (cf. rdar://problem/30597352).
62 SOFT_LINK(WebKitLegacy, _WebCreateFragment, void, (WebCore::Document& document, NSAttributedString *string, WebCore::FragmentAndResources& result), (document, string, result))
63
64 namespace WebCore {
65
66 void Editor::getTextDecorationAttributesRespectingTypingStyle(const RenderStyle& style, NSMutableDictionary* result) const
67 {
68     RefPtr<EditingStyle> typingStyle = m_frame.selection().typingStyle();
69     if (typingStyle && typingStyle->style()) {
70         RefPtr<CSSValue> value = typingStyle->style()->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
71         if (value && value->isValueList()) {
72             CSSValueList& valueList = downcast<CSSValueList>(*value);
73             if (valueList.hasValue(CSSValuePool::singleton().createIdentifierValue(CSSValueLineThrough).ptr()))
74                 [result setObject:@(NSUnderlineStyleSingle) forKey:NSStrikethroughStyleAttributeName];
75             if (valueList.hasValue(CSSValuePool::singleton().createIdentifierValue(CSSValueUnderline).ptr()))
76                 [result setObject:@(NSUnderlineStyleSingle) forKey:NSUnderlineStyleAttributeName];
77         }
78     } else {
79         int decoration = style.textDecorationsInEffect();
80         if (decoration & TextDecorationLineThrough)
81             [result setObject:@(NSUnderlineStyleSingle) forKey:NSStrikethroughStyleAttributeName];
82         if (decoration & TextDecorationUnderline)
83             [result setObject:@(NSUnderlineStyleSingle) forKey:NSUnderlineStyleAttributeName];
84     }
85 }
86
87 RetainPtr<NSDictionary> Editor::fontAttributesForSelectionStart() const
88 {
89     Node* nodeToRemove;
90     auto* style = styleForSelectionStart(&m_frame, nodeToRemove);
91     if (!style)
92         return nil;
93
94     RetainPtr<NSMutableDictionary> attributes = adoptNS([[NSMutableDictionary alloc] init]);
95
96     if (auto ctFont = style->fontCascade().primaryFont().getCTFont())
97         [attributes setObject:(id)ctFont forKey:NSFontAttributeName];
98
99     // FIXME: Why would we not want to retrieve these attributes on iOS?
100 #if PLATFORM(MAC)
101     if (style->visitedDependentColor(CSSPropertyBackgroundColor).isVisible())
102         [attributes setObject:nsColor(style->visitedDependentColor(CSSPropertyBackgroundColor)) forKey:NSBackgroundColorAttributeName];
103
104     if (style->visitedDependentColor(CSSPropertyColor).isValid() && !Color::isBlackColor(style->visitedDependentColor(CSSPropertyColor)))
105         [attributes setObject:nsColor(style->visitedDependentColor(CSSPropertyColor)) forKey:NSForegroundColorAttributeName];
106
107     const ShadowData* shadowData = style->textShadow();
108     if (shadowData) {
109         RetainPtr<NSShadow> platformShadow = adoptNS([[NSShadow alloc] init]);
110         [platformShadow setShadowOffset:NSMakeSize(shadowData->x(), shadowData->y())];
111         [platformShadow setShadowBlurRadius:shadowData->radius()];
112         [platformShadow setShadowColor:nsColor(shadowData->color())];
113         [attributes setObject:platformShadow.get() forKey:NSShadowAttributeName];
114     }
115
116     int superscriptInt = 0;
117     switch (style->verticalAlign()) {
118     case BASELINE:
119     case BOTTOM:
120     case BASELINE_MIDDLE:
121     case LENGTH:
122     case MIDDLE:
123     case TEXT_BOTTOM:
124     case TEXT_TOP:
125     case TOP:
126         break;
127     case SUB:
128         superscriptInt = -1;
129         break;
130     case SUPER:
131         superscriptInt = 1;
132         break;
133     }
134     if (superscriptInt)
135         [attributes setObject:@(superscriptInt) forKey:NSSuperscriptAttributeName];
136 #endif
137
138     getTextDecorationAttributesRespectingTypingStyle(*style, attributes.get());
139
140     if (nodeToRemove)
141         nodeToRemove->remove();
142
143     return attributes;
144 }
145
146 FragmentAndResources Editor::createFragment(NSAttributedString *string)
147 {
148     // FIXME: The algorithm to convert an attributed string into HTML should be implemented here in WebCore.
149     // For now, though, we call into WebKitLegacy, which in turn calls into AppKit/TextKit.
150     FragmentAndResources result;
151     _WebCreateFragment(*m_frame.document(), string, result);
152     return result;
153 }
154
155 static RefPtr<SharedBuffer> archivedDataForAttributedString(NSAttributedString *attributedString)
156 {
157     if (!attributedString.length)
158         return nullptr;
159
160     return SharedBuffer::create([NSKeyedArchiver archivedDataWithRootObject:attributedString]);
161 }
162
163 void Editor::writeSelectionToPasteboard(Pasteboard& pasteboard)
164 {
165     NSAttributedString *attributedString = attributedStringFromRange(*selectedRange());
166
167     PasteboardWebContent content;
168     content.canSmartCopyOrDelete = canSmartCopyOrDelete();
169     content.dataInWebArchiveFormat = selectionInWebArchiveFormat();
170     content.dataInRTFDFormat = attributedString.containsAttachments ? dataInRTFDFormat(attributedString) : nullptr;
171     content.dataInRTFFormat = dataInRTFFormat(attributedString);
172     content.dataInAttributedStringFormat = archivedDataForAttributedString(attributedString);
173     // FIXME: Why don't we want this on iOS?
174 #if PLATFORM(MAC)
175     content.dataInHTMLFormat = selectionInHTMLFormat();
176 #endif
177     content.dataInStringFormat = stringSelectionForPasteboardWithImageAltText();
178     client()->getClientPasteboardDataForRange(selectedRange().get(), content.clientTypes, content.clientData);
179
180     pasteboard.write(content);
181 }
182
183 void Editor::writeSelection(PasteboardWriterData& pasteboardWriterData)
184 {
185     NSAttributedString *attributedString = attributedStringFromRange(*selectedRange());
186
187     PasteboardWriterData::WebContent webContent;
188     webContent.canSmartCopyOrDelete = canSmartCopyOrDelete();
189     webContent.dataInWebArchiveFormat = selectionInWebArchiveFormat();
190     webContent.dataInRTFDFormat = attributedString.containsAttachments ? dataInRTFDFormat(attributedString) : nullptr;
191     webContent.dataInRTFFormat = dataInRTFFormat(attributedString);
192     webContent.dataInAttributedStringFormat = archivedDataForAttributedString(attributedString);
193     // FIXME: Why don't we want this on iOS?
194 #if PLATFORM(MAC)
195     webContent.dataInHTMLFormat = selectionInHTMLFormat();
196 #endif
197     webContent.dataInStringFormat = stringSelectionForPasteboardWithImageAltText();
198     client()->getClientPasteboardDataForRange(selectedRange().get(), webContent.clientTypes, webContent.clientData);
199
200     pasteboardWriterData.setWebContent(WTFMove(webContent));
201 }
202
203 RefPtr<SharedBuffer> Editor::selectionInWebArchiveFormat()
204 {
205     RefPtr<LegacyWebArchive> archive = LegacyWebArchive::createFromSelection(&m_frame);
206     if (!archive)
207         return nullptr;
208     return SharedBuffer::create(archive->rawDataRepresentation().get());
209 }
210
211 // FIXME: Makes no sense that selectedTextForDataTransfer always includes alt text, but stringSelectionForPasteboard does not.
212 // This was left in a bad state when selectedTextForDataTransfer was added. Need to look over clients and fix this.
213 String Editor::stringSelectionForPasteboard()
214 {
215     if (!canCopy())
216         return emptyString();
217     String text = selectedText();
218     text.replace(noBreakSpace, ' ');
219     return text;
220 }
221
222 String Editor::stringSelectionForPasteboardWithImageAltText()
223 {
224     if (!canCopy())
225         return emptyString();
226     String text = selectedTextForDataTransfer();
227     text.replace(noBreakSpace, ' ');
228     return text;
229 }
230
231 void Editor::replaceSelectionWithAttributedString(NSAttributedString *attributedString, MailBlockquoteHandling mailBlockquoteHandling)
232 {
233     if (m_frame.selection().isNone())
234         return;
235
236     if (m_frame.selection().selection().isContentRichlyEditable()) {
237         RefPtr<DocumentFragment> fragment = createFragmentAndAddResources(attributedString);
238         if (fragment && shouldInsertFragment(fragment, selectedRange(), EditorInsertAction::Pasted))
239             pasteAsFragment(fragment.releaseNonNull(), false, false, mailBlockquoteHandling);
240     } else {
241         String text = attributedString.string;
242         if (shouldInsertText(text, selectedRange().get(), EditorInsertAction::Pasted))
243             pasteAsPlainText(text, false);
244     }
245 }
246
247 RefPtr<DocumentFragment> Editor::createFragmentForImageResourceAndAddResource(RefPtr<ArchiveResource>&& resource)
248 {
249     if (!resource)
250         return nullptr;
251
252     // FIXME: Why is this different?
253 #if PLATFORM(MAC)
254     String resourceURL = resource->url().string();
255 #else
256     NSURL *URL = resource->url();
257     String resourceURL = URL.isFileURL ? URL.absoluteString : resource->url();
258 #endif
259
260     if (DocumentLoader* loader = m_frame.loader().documentLoader())
261         loader->addArchiveResource(resource.releaseNonNull());
262
263     auto imageElement = HTMLImageElement::create(*m_frame.document());
264     imageElement->setAttributeWithoutSynchronization(HTMLNames::srcAttr, resourceURL);
265
266     auto fragment = m_frame.document()->createDocumentFragment();
267     fragment->appendChild(imageElement);
268     
269     return WTFMove(fragment);
270 }
271
272 RefPtr<SharedBuffer> Editor::dataInRTFDFormat(NSAttributedString *string)
273 {
274     NSUInteger length = string.length;
275     if (!length)
276         return nullptr;
277
278     BEGIN_BLOCK_OBJC_EXCEPTIONS;
279     return SharedBuffer::create([string RTFDFromRange:NSMakeRange(0, length) documentAttributes:@{ }]);
280     END_BLOCK_OBJC_EXCEPTIONS;
281
282     return nullptr;
283 }
284
285 RefPtr<SharedBuffer> Editor::dataInRTFFormat(NSAttributedString *string)
286 {
287     NSUInteger length = string.length;
288     if (!length)
289         return nullptr;
290
291     BEGIN_BLOCK_OBJC_EXCEPTIONS;
292     return SharedBuffer::create([string RTFFromRange:NSMakeRange(0, length) documentAttributes:@{ }]);
293     END_BLOCK_OBJC_EXCEPTIONS;
294
295     return nullptr;
296 }
297
298 RefPtr<DocumentFragment> Editor::createFragmentAndAddResources(NSAttributedString *string)
299 {
300     if (!m_frame.page() || !m_frame.document())
301         return nullptr;
302
303     auto& document = *m_frame.document();
304     if (!document.isHTMLDocument() || !string)
305         return nullptr;
306
307     bool wasDeferringCallbacks = m_frame.page()->defersLoading();
308     if (!wasDeferringCallbacks)
309         m_frame.page()->setDefersLoading(true);
310
311     auto& cachedResourceLoader = document.cachedResourceLoader();
312     bool wasImagesEnabled = cachedResourceLoader.imagesEnabled();
313     if (wasImagesEnabled)
314         cachedResourceLoader.setImagesEnabled(false);
315
316     auto fragmentAndResources = createFragment(string);
317
318     if (auto* loader = m_frame.loader().documentLoader()) {
319         for (auto& resource : fragmentAndResources.resources)
320             loader->addArchiveResource(WTFMove(resource));
321     }
322
323     if (wasImagesEnabled)
324         cachedResourceLoader.setImagesEnabled(true);
325     if (!wasDeferringCallbacks)
326         m_frame.page()->setDefersLoading(false);
327     
328     return WTFMove(fragmentAndResources.fragment);
329 }
330
331 }