6ee21d92273dce55f12486f8f9787a2680ee3900
[WebKit-https.git] / Source / WebCore / editing / InsertLineBreakCommand.cpp
1 /*
2  * Copyright (C) 2005, 2006 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 #include "config.h"
27 #include "InsertLineBreakCommand.h"
28
29 #include "Document.h"
30 #include "Frame.h"
31 #include "FrameSelection.h"
32 #include "HTMLElement.h"
33 #include "HTMLHRElement.h"
34 #include "HTMLNames.h"
35 #include "HTMLTableElement.h"
36 #include "RenderElement.h"
37 #include "RenderText.h"
38 #include "Text.h"
39 #include "VisibleUnits.h"
40 #include "htmlediting.h"
41
42 namespace WebCore {
43
44 using namespace HTMLNames;
45
46 InsertLineBreakCommand::InsertLineBreakCommand(Document& document)
47     : CompositeEditCommand(document)
48 {
49 }
50
51 bool InsertLineBreakCommand::preservesTypingStyle() const
52 {
53     return true;
54 }
55
56 void InsertLineBreakCommand::insertNodeAfterPosition(Node* node, const Position& pos)
57 {
58     // Insert the BR after the caret position. In the case the
59     // position is a block, do an append. We don't want to insert
60     // the BR *after* the block.
61     Element* cb = deprecatedEnclosingBlockFlowElement(pos.deprecatedNode());
62     if (cb == pos.deprecatedNode())
63         appendNode(node, cb);
64     else
65         insertNodeAfter(node, pos.deprecatedNode());
66 }
67
68 void InsertLineBreakCommand::insertNodeBeforePosition(Node* node, const Position& pos)
69 {
70     // Insert the BR after the caret position. In the case the
71     // position is a block, do an append. We don't want to insert
72     // the BR *before* the block.
73     Element* cb = deprecatedEnclosingBlockFlowElement(pos.deprecatedNode());
74     if (cb == pos.deprecatedNode())
75         appendNode(node, cb);
76     else
77         insertNodeBefore(node, pos.deprecatedNode());
78 }
79
80 // Whether we should insert a break element or a '\n'.
81 bool InsertLineBreakCommand::shouldUseBreakElement(const Position& insertionPos)
82 {
83     // An editing position like [input, 0] actually refers to the position before
84     // the input element, and in that case we need to check the input element's
85     // parent's renderer.
86     Position p(insertionPos.parentAnchoredEquivalent());
87     return p.deprecatedNode()->renderer() && !p.deprecatedNode()->renderer()->style().preserveNewline();
88 }
89
90 void InsertLineBreakCommand::doApply()
91 {
92     deleteSelection();
93     VisibleSelection selection = endingSelection();
94     if (!selection.isNonOrphanedCaretOrRange())
95         return;
96     
97     VisiblePosition caret(selection.visibleStart());
98     // FIXME: If the node is hidden, we should still be able to insert text. 
99     // For now, we return to avoid a crash.  https://bugs.webkit.org/show_bug.cgi?id=40342
100     if (caret.isNull())
101         return;
102
103     Position pos(caret.deepEquivalent());
104
105     pos = positionAvoidingSpecialElementBoundary(pos);
106     
107     pos = positionOutsideTabSpan(pos);
108
109     RefPtr<Node> nodeToInsert;
110     if (shouldUseBreakElement(pos))
111         nodeToInsert = createBreakElement(document());
112     else
113         nodeToInsert = document().createTextNode("\n");
114     
115     // FIXME: Need to merge text nodes when inserting just after or before text.
116     
117     if (isEndOfParagraph(caret) && !lineBreakExistsAtVisiblePosition(caret)) {
118         bool needExtraLineBreak = !is<HTMLHRElement>(*pos.deprecatedNode()) && !is<HTMLTableElement>(*pos.deprecatedNode());
119         
120         insertNodeAt(nodeToInsert.get(), pos);
121         
122         if (needExtraLineBreak)
123             insertNodeBefore(nodeToInsert->cloneNode(false), nodeToInsert);
124         
125         VisiblePosition endingPosition(positionBeforeNode(nodeToInsert.get()));
126         setEndingSelection(VisibleSelection(endingPosition, endingSelection().isDirectional()));
127     } else if (pos.deprecatedEditingOffset() <= caretMinOffset(pos.deprecatedNode())) {
128         insertNodeAt(nodeToInsert.get(), pos);
129         
130         // Insert an extra br or '\n' if the just inserted one collapsed.
131         if (!isStartOfParagraph(positionBeforeNode(nodeToInsert.get())))
132             insertNodeBefore(nodeToInsert->cloneNode(false).get(), nodeToInsert.get());
133         
134         setEndingSelection(VisibleSelection(positionInParentAfterNode(nodeToInsert.get()), DOWNSTREAM, endingSelection().isDirectional()));
135     // If we're inserting after all of the rendered text in a text node, or into a non-text node,
136     // a simple insertion is sufficient.
137     } else if (pos.deprecatedEditingOffset() >= caretMaxOffset(pos.deprecatedNode()) || !pos.deprecatedNode()->isTextNode()) {
138         insertNodeAt(nodeToInsert.get(), pos);
139         setEndingSelection(VisibleSelection(positionInParentAfterNode(nodeToInsert.get()), DOWNSTREAM, endingSelection().isDirectional()));
140     } else if (is<Text>(*pos.deprecatedNode())) {
141         // Split a text node
142         Text& textNode = downcast<Text>(*pos.deprecatedNode());
143         splitTextNode(&textNode, pos.deprecatedEditingOffset());
144         insertNodeBefore(nodeToInsert, &textNode);
145         Position endingPosition = firstPositionInNode(&textNode);
146         
147         // Handle whitespace that occurs after the split
148         document().updateLayoutIgnorePendingStylesheets();
149         if (!endingPosition.isRenderedCharacter()) {
150             Position positionBeforeTextNode(positionInParentBeforeNode(&textNode));
151             // Clear out all whitespace and insert one non-breaking space
152             deleteInsignificantTextDownstream(endingPosition);
153             ASSERT(!textNode.renderer() || textNode.renderer()->style().collapseWhiteSpace());
154             // Deleting insignificant whitespace will remove textNode if it contains nothing but insignificant whitespace.
155             if (textNode.inDocument())
156                 insertTextIntoNode(&textNode, 0, nonBreakingSpaceString());
157             else {
158                 RefPtr<Text> nbspNode = document().createTextNode(nonBreakingSpaceString());
159                 insertNodeAt(nbspNode.get(), positionBeforeTextNode);
160                 endingPosition = firstPositionInNode(nbspNode.get());
161             }
162         }
163         
164         setEndingSelection(VisibleSelection(endingPosition, DOWNSTREAM, endingSelection().isDirectional()));
165     }
166
167     // Handle the case where there is a typing style.
168
169     RefPtr<EditingStyle> typingStyle = frame().selection().typingStyle();
170
171     if (typingStyle && !typingStyle->isEmpty()) {
172         // Apply the typing style to the inserted line break, so that if the selection
173         // leaves and then comes back, new input will have the right style.
174         // FIXME: We shouldn't always apply the typing style to the line break here,
175         // see <rdar://problem/5794462>.
176         applyStyle(typingStyle.get(), firstPositionInOrBeforeNode(nodeToInsert.get()), lastPositionInOrAfterNode(nodeToInsert.get()));
177         // Even though this applyStyle operates on a Range, it still sets an endingSelection().
178         // It tries to set a VisibleSelection around the content it operated on. So, that VisibleSelection
179         // will either (a) select the line break we inserted, or it will (b) be a caret just 
180         // before the line break (if the line break is at the end of a block it isn't selectable).
181         // So, this next call sets the endingSelection() to a caret just after the line break 
182         // that we inserted, or just before it if it's at the end of a block.
183         setEndingSelection(endingSelection().visibleEnd());
184     }
185
186     rebalanceWhitespace();
187 }
188
189 }