985addd77b8d79b7e100febf9ddc8b4c5c55dde4
[WebKit-https.git] / Source / WebCore / editing / ios / EditorIOS.mm
1 /*
2  * Copyright (C) 2006, 2007, 2013 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. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "Editor.h"
28
29 #if PLATFORM(IOS_FAMILY)
30
31 #import "CSSComputedStyleDeclaration.h"
32 #import "CSSPrimitiveValueMappings.h"
33 #import "CachedImage.h"
34 #import "DataTransfer.h"
35 #import "DictationCommandIOS.h"
36 #import "DocumentFragment.h"
37 #import "DocumentMarkerController.h"
38 #import "Editing.h"
39 #import "EditorClient.h"
40 #import "Frame.h"
41 #import "HTMLConverter.h"
42 #import "HTMLInputElement.h"
43 #import "HTMLNames.h"
44 #import "HTMLParserIdioms.h"
45 #import "HTMLTextAreaElement.h"
46 #import "Pasteboard.h"
47 #import "RenderBlock.h"
48 #import "RenderImage.h"
49 #import "SharedBuffer.h"
50 #import "StyleProperties.h"
51 #import "Text.h"
52 #import "TypingCommand.h"
53 #import "WAKAppKitStubs.h"
54 #import "WebContentReader.h"
55 #import "markup.h"
56 #import <wtf/text/StringBuilder.h>
57
58 namespace WebCore {
59
60 using namespace HTMLNames;
61
62 void Editor::showFontPanel()
63 {
64 }
65
66 void Editor::showStylesPanel()
67 {
68 }
69
70 void Editor::showColorPanel()
71 {
72 }
73
74 void Editor::setTextAlignmentForChangedBaseWritingDirection(WritingDirection direction)
75 {
76     // Note that the passed-in argument is the direction that has been changed to by
77     // some code or user interaction outside the scope of this function. The former
78     // direction is not known, nor is it required for the kind of text alignment
79     // changes done by this function.
80     //
81     // Rules:
82     // When text has no explicit alignment, set to alignment to match the writing direction.
83     // If the text has left or right alignment, flip left->right and right->left. 
84     // Otherwise, do nothing.
85
86     auto selectionStyle = EditingStyle::styleAtSelectionStart(m_frame.selection().selection());
87     if (!selectionStyle || !selectionStyle->style())
88          return;
89
90     RefPtr<CSSPrimitiveValue> value = static_pointer_cast<CSSPrimitiveValue>(selectionStyle->style()->getPropertyCSSValue(CSSPropertyTextAlign));
91     if (!value)
92         return;
93         
94     const char *newValue = nullptr;
95     TextAlignMode textAlign = *value;
96     switch (textAlign) {
97     case TextAlignMode::Start:
98     case TextAlignMode::End: {
99         switch (direction) {
100         case WritingDirection::Natural:
101             // no-op
102             break;
103         case WritingDirection::LeftToRight:
104             newValue = "left";
105             break;
106         case WritingDirection::RightToLeft:
107             newValue = "right";
108             break;
109         }
110         break;
111     }
112     case TextAlignMode::Left:
113     case TextAlignMode::WebKitLeft:
114         newValue = "right";
115         break;
116     case TextAlignMode::Right:
117     case TextAlignMode::WebKitRight:
118         newValue = "left";
119         break;
120     case TextAlignMode::Center:
121     case TextAlignMode::WebKitCenter:
122     case TextAlignMode::Justify:
123         // no-op
124         break;
125     }
126
127     if (!newValue)
128         return;
129
130     Element* focusedElement = m_frame.document()->focusedElement();
131     if (focusedElement && (is<HTMLTextAreaElement>(*focusedElement) || (is<HTMLInputElement>(*focusedElement)
132         && (downcast<HTMLInputElement>(*focusedElement).isTextField()
133             || downcast<HTMLInputElement>(*focusedElement).isSearchField())))) {
134         if (direction == WritingDirection::Natural)
135             return;
136         downcast<HTMLElement>(*focusedElement).setAttributeWithoutSynchronization(alignAttr, newValue);
137         m_frame.document()->updateStyleIfNeeded();
138         return;
139     }
140
141     auto style = MutableStyleProperties::create();
142     style->setProperty(CSSPropertyTextAlign, newValue);
143     applyParagraphStyle(style.ptr());
144 }
145
146 void Editor::removeUnchangeableStyles()
147 {
148     // This function removes styles that the user cannot modify by applying their default values.
149     
150     auto editingStyle = EditingStyle::create(m_frame.document()->bodyOrFrameset());
151     auto defaultStyle = editingStyle->style()->mutableCopy();
152     
153     // Text widgets implement background color via the UIView property. Their body element will not have one.
154     defaultStyle->setProperty(CSSPropertyBackgroundColor, "rgba(255, 255, 255, 0.0)");
155     
156     // Remove properties that the user can modify, like font-weight. 
157     // Also remove font-family, per HI spec.
158     // FIXME: it'd be nice if knowledge about which styles were unchangeable was not hard-coded here.
159     defaultStyle->removeProperty(CSSPropertyFontWeight);
160     defaultStyle->removeProperty(CSSPropertyFontStyle);
161     defaultStyle->removeProperty(CSSPropertyFontVariantCaps);
162     // FIXME: we should handle also pasted quoted text, strikethrough, etc. <rdar://problem/9255115>
163     defaultStyle->removeProperty(CSSPropertyTextDecoration);
164     defaultStyle->removeProperty(CSSPropertyWebkitTextDecorationsInEffect); // implements underline
165
166     // FIXME add EditAction::MatchStlye <rdar://problem/9156507> Undo rich text's paste & match style should say "Undo Match Style"
167     applyStyleToSelection(defaultStyle.ptr(), EditAction::ChangeAttributes);
168 }
169
170 static void getImage(Element& imageElement, RefPtr<Image>& image, CachedImage*& cachedImage)
171 {
172     auto* renderer = imageElement.renderer();
173     if (!is<RenderImage>(renderer))
174         return;
175
176     CachedImage* tentativeCachedImage = downcast<RenderImage>(*renderer).cachedImage();
177     if (!tentativeCachedImage || tentativeCachedImage->errorOccurred())
178         return;
179
180     image = tentativeCachedImage->imageForRenderer(renderer);
181     if (!image)
182         return;
183     
184     cachedImage = tentativeCachedImage;
185 }
186
187 void Editor::writeImageToPasteboard(Pasteboard& pasteboard, Element& imageElement, const URL& url, const String& title)
188 {
189     PasteboardImage pasteboardImage;
190
191     RefPtr<Image> image;
192     CachedImage* cachedImage = nullptr;
193     getImage(imageElement, image, cachedImage);
194     if (!image)
195         return;
196     ASSERT(cachedImage);
197
198     auto imageSourceURL = imageElement.document().completeURL(stripLeadingAndTrailingHTMLSpaces(imageElement.imageSourceURL()));
199
200     auto pasteboardImageURL = url.isEmpty() ? imageSourceURL : url;
201     if (!pasteboardImageURL.isLocalFile()) {
202         pasteboardImage.url.url = pasteboardImageURL;
203         pasteboardImage.url.title = title;
204     }
205     pasteboardImage.suggestedName = imageSourceURL.lastPathComponent();
206     pasteboardImage.imageSize = image->size();
207     pasteboardImage.resourceMIMEType = pasteboard.resourceMIMEType(cachedImage->response().mimeType());
208     pasteboardImage.resourceData = cachedImage->resourceBuffer();
209
210     Position beforeImagePosition(&imageElement, Position::PositionIsBeforeAnchor);
211     Position afterImagePosition(&imageElement, Position::PositionIsAfterAnchor);
212     auto imageRange = Range::create(imageElement.document(), beforeImagePosition, afterImagePosition);
213     client()->getClientPasteboardDataForRange(imageRange.ptr(), pasteboardImage.clientTypes, pasteboardImage.clientData);
214
215     pasteboard.write(pasteboardImage);
216 }
217
218 void Editor::pasteWithPasteboard(Pasteboard* pasteboard, OptionSet<PasteOption> options)
219 {
220     RefPtr<Range> range = selectedRange();
221     bool allowPlainText = options.contains(PasteOption::AllowPlainText);
222     WebContentReader reader(m_frame, *range, allowPlainText);
223     int numberOfPasteboardItems = client()->getPasteboardItemsCount();
224     for (int i = 0; i < numberOfPasteboardItems; ++i) {
225         RefPtr<DocumentFragment> fragment = client()->documentFragmentFromDelegate(i);
226         if (!fragment)
227             continue;
228         reader.addFragment(fragment.releaseNonNull());
229     }
230
231     RefPtr<DocumentFragment> fragment = reader.fragment;
232     if (!fragment) {
233         bool chosePlainTextIgnored;
234         fragment = webContentFromPasteboard(*pasteboard, *range, allowPlainText, chosePlainTextIgnored);
235     }
236
237     if (fragment && options.contains(PasteOption::AsQuotation))
238         quoteFragmentForPasting(*fragment);
239
240     if (fragment && shouldInsertFragment(*fragment, range.get(), EditorInsertAction::Pasted))
241         pasteAsFragment(fragment.releaseNonNull(), canSmartReplaceWithPasteboard(*pasteboard), false, options.contains(PasteOption::IgnoreMailBlockquote) ? MailBlockquoteHandling::IgnoreBlockquote : MailBlockquoteHandling::RespectBlockquote);
242 }
243
244 void Editor::insertDictationPhrases(Vector<Vector<String>>&& dictationPhrases, RetainPtr<id> metadata)
245 {
246     if (m_frame.selection().isNone())
247         return;
248
249     if (dictationPhrases.isEmpty())
250         return;
251
252     DictationCommandIOS::create(document(), WTFMove(dictationPhrases), WTFMove(metadata))->apply();
253 }
254
255 void Editor::setDictationPhrasesAsChildOfElement(const Vector<Vector<String>>& dictationPhrases, RetainPtr<id> metadata, Element& element)
256 {
257     // Clear the composition.
258     clear();
259
260     // Clear the Undo stack, since the operations that follow are not Undoable, and will corrupt the stack.
261     // Some day we could make them Undoable, and let callers clear the Undo stack explicitly if they wish.
262     clearUndoRedoOperations();
263
264     m_frame.selection().clear();
265
266     element.removeChildren();
267
268     if (dictationPhrases.isEmpty()) {
269         client()->respondToChangedContents();
270         return;
271     }
272
273     RefPtr<Range> context = document().createRange();
274     context->selectNodeContents(element);
275
276     StringBuilder dictationPhrasesBuilder;
277     for (auto& interpretations : dictationPhrases)
278         dictationPhrasesBuilder.append(interpretations[0]);
279
280     element.appendChild(createFragmentFromText(*context, dictationPhrasesBuilder.toString()));
281
282     // We need a layout in order to add markers below.
283     document().updateLayout();
284
285     if (!element.firstChild()->isTextNode()) {
286         // Shouldn't happen.
287         ASSERT(element.firstChild()->isTextNode());
288         return;
289     }
290
291     Text& textNode = downcast<Text>(*element.firstChild());
292     int previousDictationPhraseStart = 0;
293     for (auto& interpretations : dictationPhrases) {
294         int dictationPhraseLength = interpretations[0].length();
295         int dictationPhraseEnd = previousDictationPhraseStart + dictationPhraseLength;
296         if (interpretations.size() > 1) {
297             auto dictationPhraseRange = Range::create(document(), &textNode, previousDictationPhraseStart, &textNode, dictationPhraseEnd);
298             document().markers().addDictationPhraseWithAlternativesMarker(dictationPhraseRange, interpretations);
299         }
300         previousDictationPhraseStart = dictationPhraseEnd;
301     }
302
303     auto resultRange = Range::create(document(), &textNode, 0, &textNode, textNode.length());
304     document().markers().addDictationResultMarker(resultRange, metadata);
305
306     client()->respondToChangedContents();
307 }
308
309 void Editor::confirmMarkedText()
310 {
311     // FIXME: This is a hacky workaround for the keyboard calling this method too late -
312     // after the selection and focus have already changed. See <rdar://problem/5975559>.
313     Element* focused = document().focusedElement();
314     Node* composition = compositionNode();
315     if (composition && focused && focused != composition && !composition->isDescendantOrShadowDescendantOf(focused)) {
316         cancelComposition();
317         document().setFocusedElement(focused);
318     } else
319         confirmComposition();
320 }
321
322 void Editor::setTextAsChildOfElement(const String& text, Element& element)
323 {
324     // Clear the composition
325     clear();
326
327     // Clear the Undo stack, since the operations that follow are not Undoable, and will corrupt the stack.
328     // Some day we could make them Undoable, and let callers clear the Undo stack explicitly if they wish.
329     clearUndoRedoOperations();
330
331     // If the element is empty already and we're not adding text, we can early return and avoid clearing/setting
332     // a selection at [0, 0] and the expense involved in creation VisiblePositions.
333     if (!element.firstChild() && text.isEmpty())
334         return;
335
336     // As a side effect this function sets a caret selection after the inserted content. Much of what
337     // follows is more expensive if there is a selection, so clear it since it's going to change anyway.
338     m_frame.selection().clear();
339
340     // clear out all current children of element
341     element.removeChildren();
342
343     if (text.length()) {
344         // insert new text
345         // remove element from tree while doing it
346         // FIXME: The element we're inserting into is often the body element. It seems strange to be removing it
347         // (even if it is only temporary). ReplaceSelectionCommand doesn't bother doing this when it inserts
348         // content, why should we here?
349         RefPtr<Node> parent = element.parentNode();
350         RefPtr<Node> siblingAfter = element.nextSibling();
351         if (parent)
352             element.remove();
353
354         auto context = document().createRange();
355         context->selectNodeContents(element);
356         element.appendChild(createFragmentFromText(context, text));
357
358         // restore element to document
359         if (parent) {
360             if (siblingAfter)
361                 parent->insertBefore(element, siblingAfter.get());
362             else
363                 parent->appendChild(element);
364         }
365     }
366
367     // set the selection to the end
368     VisibleSelection selection;
369
370     Position pos = createLegacyEditingPosition(&element, element.countChildNodes());
371
372     VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
373     if (visiblePos.isNull())
374         return;
375
376     selection.setBase(visiblePos);
377     selection.setExtent(visiblePos);
378
379     m_frame.selection().setSelection(selection);
380
381     client()->respondToChangedContents();
382 }
383
384 // If the selection is adjusted from UIKit without closing the typing, the typing command may
385 // have a stale selection.
386 void Editor::ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping()
387 {
388     TypingCommand::ensureLastEditCommandHasCurrentSelectionIfOpenForMoreTyping(&m_frame, m_frame.selection().selection());
389 }
390
391 } // namespace WebCore
392
393 #endif // PLATFORM(IOS_FAMILY)