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