[Attachment Support] Support dragging attachment elements out as files on macOS
[WebKit-https.git] / Source / WebCore / editing / cocoa / EditorCocoa.mm
1 /*
2  * Copyright (C) 2006-2017 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 "FontCascade.h"
40 #import "Frame.h"
41 #import "FrameLoader.h"
42 #import "FrameSelection.h"
43 #import "HTMLAttachmentElement.h"
44 #import "HTMLConverter.h"
45 #import "HTMLImageElement.h"
46 #import "HTMLSpanElement.h"
47 #import "LegacyWebArchive.h"
48 #import "Pasteboard.h"
49 #import "RenderElement.h"
50 #import "RenderStyle.h"
51 #import "Text.h"
52 #import "WebContentReader.h"
53 #import "WebCoreNSURLExtras.h"
54 #import "markup.h"
55 #import <pal/spi/cocoa/NSAttributedStringSPI.h>
56 #import <pal/spi/cocoa/NSKeyedArchiverSPI.h>
57 #import <wtf/BlockObjCExceptions.h>
58
59 namespace WebCore {
60
61 void Editor::getTextDecorationAttributesRespectingTypingStyle(const RenderStyle& style, NSMutableDictionary* result) const
62 {
63     RefPtr<EditingStyle> typingStyle = m_frame.selection().typingStyle();
64     if (typingStyle && typingStyle->style()) {
65         RefPtr<CSSValue> value = typingStyle->style()->getPropertyCSSValue(CSSPropertyWebkitTextDecorationsInEffect);
66         if (value && value->isValueList()) {
67             CSSValueList& valueList = downcast<CSSValueList>(*value);
68             if (valueList.hasValue(CSSValuePool::singleton().createIdentifierValue(CSSValueLineThrough).ptr()))
69                 [result setObject:@(NSUnderlineStyleSingle) forKey:NSStrikethroughStyleAttributeName];
70             if (valueList.hasValue(CSSValuePool::singleton().createIdentifierValue(CSSValueUnderline).ptr()))
71                 [result setObject:@(NSUnderlineStyleSingle) forKey:NSUnderlineStyleAttributeName];
72         }
73     } else {
74         auto decoration = style.textDecorationsInEffect();
75         if (decoration & TextDecoration::LineThrough)
76             [result setObject:@(NSUnderlineStyleSingle) forKey:NSStrikethroughStyleAttributeName];
77         if (decoration & TextDecoration::Underline)
78             [result setObject:@(NSUnderlineStyleSingle) forKey:NSUnderlineStyleAttributeName];
79     }
80 }
81
82 RetainPtr<NSDictionary> Editor::fontAttributesForSelectionStart() const
83 {
84     Node* nodeToRemove;
85     auto* style = styleForSelectionStart(&m_frame, nodeToRemove);
86     if (!style)
87         return nil;
88
89     RetainPtr<NSMutableDictionary> attributes = adoptNS([[NSMutableDictionary alloc] init]);
90
91     if (auto ctFont = style->fontCascade().primaryFont().getCTFont())
92         [attributes setObject:(__bridge id)ctFont forKey:NSFontAttributeName];
93
94     // FIXME: Why would we not want to retrieve these attributes on iOS?
95 #if PLATFORM(MAC)
96     // FIXME: for now, always report the colors after applying -apple-color-filter. In future not all clients
97     // may want this, so we may have to add a setting to control it. See also editingAttributedStringFromRange().
98     Color backgroundColor = style->visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);
99     if (backgroundColor.isVisible())
100         [attributes setObject:nsColor(backgroundColor) forKey:NSBackgroundColorAttributeName];
101
102     Color foregroundColor = style->visitedDependentColorWithColorFilter(CSSPropertyColor);
103     // FIXME: isBlackColor not suitable for dark mode.
104     if (foregroundColor.isValid() && !Color::isBlackColor(foregroundColor))
105         [attributes setObject:nsColor(foregroundColor) 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 VerticalAlign::Baseline:
119     case VerticalAlign::Bottom:
120     case VerticalAlign::BaselineMiddle:
121     case VerticalAlign::Length:
122     case VerticalAlign::Middle:
123     case VerticalAlign::TextBottom:
124     case VerticalAlign::TextTop:
125     case VerticalAlign::Top:
126         break;
127     case VerticalAlign::Sub:
128         superscriptInt = -1;
129         break;
130     case VerticalAlign::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 static RefPtr<SharedBuffer> archivedDataForAttributedString(NSAttributedString *attributedString)
147 {
148     if (!attributedString.length)
149         return nullptr;
150
151     return SharedBuffer::create(securelyArchivedDataWithRootObject(attributedString));
152 }
153
154 String Editor::selectionInHTMLFormat()
155 {
156     if (auto range = selectedRange())
157         return createMarkup(*range, nullptr, AnnotateForInterchange, false, ResolveNonLocalURLs);
158     return { };
159 }
160
161 #if ENABLE(ATTACHMENT_ELEMENT)
162
163 void Editor::getPasteboardTypesAndDataForAttachment(HTMLAttachmentElement& attachment, Vector<String>& outTypes, Vector<RefPtr<SharedBuffer>>& outData)
164 {
165     auto attachmentRange = Range::create(attachment.document(), { &attachment, Position::PositionIsBeforeAnchor }, { &attachment, Position::PositionIsAfterAnchor });
166     client()->getClientPasteboardDataForRange(attachmentRange.ptr(), outTypes, outData);
167     // FIXME: We should additionally write the attachment as a web archive here, such that drag and drop within the
168     // same page doesn't destroy and recreate attachments unnecessarily. This is also needed to preserve the attachment
169     // display mode when dragging and dropping or cutting and pasting. For the time being, this is disabled because
170     // inserting attachment elements from web archive data sometimes causes attachment data to be lost; this requires
171     // further investigation.
172 #if PLATFORM(MAC)
173     // On macOS, we currently write the attachment as a web archive; we can't do the same for iOS and remove the platform guard above
174     // quite yet without breaking drag moves. This investigation is tracked in <https://bugs.webkit.org/show_bug.cgi?id=181514>.
175     // See the above FIXME for more details.
176     if (auto archive = LegacyWebArchive::create(attachmentRange.ptr())) {
177         if (auto webArchiveData = archive->rawDataRepresentation()) {
178             outTypes.append(WebArchivePboardType);
179             outData.append(SharedBuffer::create(webArchiveData.get()));
180         }
181     }
182 #endif
183 }
184
185 #endif
186
187 void Editor::writeSelectionToPasteboard(Pasteboard& pasteboard)
188 {
189     NSAttributedString *attributedString = attributedStringFromRange(*selectedRange());
190
191     PasteboardWebContent content;
192     content.contentOrigin = m_frame.document()->originIdentifierForPasteboard();
193     content.canSmartCopyOrDelete = canSmartCopyOrDelete();
194     content.dataInWebArchiveFormat = selectionInWebArchiveFormat();
195     content.dataInRTFDFormat = attributedString.containsAttachments ? dataInRTFDFormat(attributedString) : nullptr;
196     content.dataInRTFFormat = dataInRTFFormat(attributedString);
197     content.dataInAttributedStringFormat = archivedDataForAttributedString(attributedString);
198     content.dataInHTMLFormat = selectionInHTMLFormat();
199     content.dataInStringFormat = stringSelectionForPasteboardWithImageAltText();
200     client()->getClientPasteboardDataForRange(selectedRange().get(), content.clientTypes, content.clientData);
201
202     pasteboard.write(content);
203 }
204
205 void Editor::writeSelection(PasteboardWriterData& pasteboardWriterData)
206 {
207     NSAttributedString *attributedString = attributedStringFromRange(*selectedRange());
208
209     PasteboardWriterData::WebContent webContent;
210     webContent.contentOrigin = m_frame.document()->originIdentifierForPasteboard();
211     webContent.canSmartCopyOrDelete = canSmartCopyOrDelete();
212     webContent.dataInWebArchiveFormat = selectionInWebArchiveFormat();
213     webContent.dataInRTFDFormat = attributedString.containsAttachments ? dataInRTFDFormat(attributedString) : nullptr;
214     webContent.dataInRTFFormat = dataInRTFFormat(attributedString);
215     webContent.dataInAttributedStringFormat = archivedDataForAttributedString(attributedString);
216     webContent.dataInHTMLFormat = selectionInHTMLFormat();
217     webContent.dataInStringFormat = stringSelectionForPasteboardWithImageAltText();
218     client()->getClientPasteboardDataForRange(selectedRange().get(), webContent.clientTypes, webContent.clientData);
219
220     pasteboardWriterData.setWebContent(WTFMove(webContent));
221 }
222
223 RefPtr<SharedBuffer> Editor::selectionInWebArchiveFormat()
224 {
225     RefPtr<LegacyWebArchive> archive = LegacyWebArchive::createFromSelection(&m_frame);
226     if (!archive)
227         return nullptr;
228     return SharedBuffer::create(archive->rawDataRepresentation().get());
229 }
230
231 // FIXME: Makes no sense that selectedTextForDataTransfer always includes alt text, but stringSelectionForPasteboard does not.
232 // This was left in a bad state when selectedTextForDataTransfer was added. Need to look over clients and fix this.
233 String Editor::stringSelectionForPasteboard()
234 {
235     if (!canCopy())
236         return emptyString();
237     String text = selectedText();
238     text.replace(noBreakSpace, ' ');
239     return text;
240 }
241
242 String Editor::stringSelectionForPasteboardWithImageAltText()
243 {
244     if (!canCopy())
245         return emptyString();
246     String text = selectedTextForDataTransfer();
247     text.replace(noBreakSpace, ' ');
248     return text;
249 }
250
251 void Editor::replaceSelectionWithAttributedString(NSAttributedString *attributedString, MailBlockquoteHandling mailBlockquoteHandling)
252 {
253     if (m_frame.selection().isNone())
254         return;
255
256     if (m_frame.selection().selection().isContentRichlyEditable()) {
257         if (auto fragment = createFragmentAndAddResources(m_frame, attributedString)) {
258             if (shouldInsertFragment(*fragment, selectedRange().get(), EditorInsertAction::Pasted))
259                 pasteAsFragment(fragment.releaseNonNull(), false, false, mailBlockquoteHandling);
260         }
261     } else {
262         String text = attributedString.string;
263         if (shouldInsertText(text, selectedRange().get(), EditorInsertAction::Pasted))
264             pasteAsPlainText(text, false);
265     }
266 }
267
268 String Editor::userVisibleString(const URL& url)
269 {
270     return WebCore::userVisibleString(url);
271 }
272
273 RefPtr<SharedBuffer> Editor::dataInRTFDFormat(NSAttributedString *string)
274 {
275     NSUInteger length = string.length;
276     if (!length)
277         return nullptr;
278
279     BEGIN_BLOCK_OBJC_EXCEPTIONS;
280     return SharedBuffer::create([string RTFDFromRange:NSMakeRange(0, length) documentAttributes:@{ }]);
281     END_BLOCK_OBJC_EXCEPTIONS;
282
283     return nullptr;
284 }
285
286 RefPtr<SharedBuffer> Editor::dataInRTFFormat(NSAttributedString *string)
287 {
288     NSUInteger length = string.length;
289     if (!length)
290         return nullptr;
291
292     BEGIN_BLOCK_OBJC_EXCEPTIONS;
293     return SharedBuffer::create([string RTFFromRange:NSMakeRange(0, length) documentAttributes:@{ }]);
294     END_BLOCK_OBJC_EXCEPTIONS;
295
296     return nullptr;
297 }
298
299 }