Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(124)

Side by Side Diff: Source/core/editing/ApplyBlockElementCommand.cpp

Issue 1294543005: Move execCommand related files in core/editing/ related files into core/editing/commands/ (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: 2015-08-18T14:20:58 Rebase for merging code style fixes Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2010 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include "config.h"
28 #include "core/editing/ApplyBlockElementCommand.h"
29
30 #include "bindings/core/v8/ExceptionState.h"
31 #include "core/HTMLNames.h"
32 #include "core/dom/NodeComputedStyle.h"
33 #include "core/dom/Text.h"
34 #include "core/editing/EditingUtilities.h"
35 #include "core/editing/VisiblePosition.h"
36 #include "core/editing/VisibleUnits.h"
37 #include "core/html/HTMLBRElement.h"
38 #include "core/html/HTMLElement.h"
39 #include "core/layout/LayoutObject.h"
40 #include "core/style/ComputedStyle.h"
41
42 namespace blink {
43
44 using namespace HTMLNames;
45
46 ApplyBlockElementCommand::ApplyBlockElementCommand(Document& document, const Qua lifiedName& tagName, const AtomicString& inlineStyle)
47 : CompositeEditCommand(document)
48 , m_tagName(tagName)
49 , m_inlineStyle(inlineStyle)
50 {
51 }
52
53 ApplyBlockElementCommand::ApplyBlockElementCommand(Document& document, const Qua lifiedName& tagName)
54 : CompositeEditCommand(document)
55 , m_tagName(tagName)
56 {
57 }
58
59 void ApplyBlockElementCommand::doApply()
60 {
61 if (!endingSelection().rootEditableElement())
62 return;
63
64 VisiblePosition visibleEnd = endingSelection().visibleEnd();
65 VisiblePosition visibleStart = endingSelection().visibleStart();
66 if (visibleStart.isNull() || visibleStart.isOrphan() || visibleEnd.isNull() || visibleEnd.isOrphan())
67 return;
68
69 // When a selection ends at the start of a paragraph, we rarely paint
70 // the selection gap before that paragraph, because there often is no gap.
71 // In a case like this, it's not obvious to the user that the selection
72 // ends "inside" that paragraph, so it would be confusing if Indent/Outdent
73 // operated on that paragraph.
74 // FIXME: We paint the gap before some paragraphs that are indented with lef t
75 // margin/padding, but not others. We should make the gap painting more con sistent and
76 // then use a left margin/padding rule here.
77 if (visibleEnd.deepEquivalent() != visibleStart.deepEquivalent() && isStartO fParagraph(visibleEnd)) {
78 VisibleSelection newSelection(visibleStart, visibleEnd.previous(CannotCr ossEditingBoundary), endingSelection().isDirectional());
79 if (newSelection.isNone())
80 return;
81 setEndingSelection(newSelection);
82 }
83
84 VisibleSelection selection = selectionForParagraphIteration(endingSelection( ));
85 VisiblePosition startOfSelection = selection.visibleStart();
86 VisiblePosition endOfSelection = selection.visibleEnd();
87 ASSERT(!startOfSelection.isNull());
88 ASSERT(!endOfSelection.isNull());
89 RefPtrWillBeRawPtr<ContainerNode> startScope = nullptr;
90 int startIndex = indexForVisiblePosition(startOfSelection, startScope);
91 RefPtrWillBeRawPtr<ContainerNode> endScope = nullptr;
92 int endIndex = indexForVisiblePosition(endOfSelection, endScope);
93
94 formatSelection(startOfSelection, endOfSelection);
95
96 document().updateLayoutIgnorePendingStylesheets();
97
98 ASSERT(startScope == endScope);
99 ASSERT(startIndex >= 0);
100 ASSERT(startIndex <= endIndex);
101 if (startScope == endScope && startIndex >= 0 && startIndex <= endIndex) {
102 VisiblePosition start(visiblePositionForIndex(startIndex, startScope.get ()));
103 VisiblePosition end(visiblePositionForIndex(endIndex, endScope.get()));
104 if (start.isNotNull() && end.isNotNull())
105 setEndingSelection(VisibleSelection(start, end, endingSelection().is Directional()));
106 }
107 }
108
109 void ApplyBlockElementCommand::formatSelection(const VisiblePosition& startOfSel ection, const VisiblePosition& endOfSelection)
110 {
111 // Special case empty unsplittable elements because there's nothing to split
112 // and there's nothing to move.
113 Position start = startOfSelection.deepEquivalent().downstream();
114 if (isAtUnsplittableElement(start)) {
115 RefPtrWillBeRawPtr<HTMLElement> blockquote = createBlockElement();
116 insertNodeAt(blockquote, start);
117 RefPtrWillBeRawPtr<HTMLBRElement> placeholder = createBreakElement(docum ent());
118 appendNode(placeholder, blockquote);
119 setEndingSelection(VisibleSelection(positionBeforeNode(placeholder.get() ), DOWNSTREAM, endingSelection().isDirectional()));
120 return;
121 }
122
123 RefPtrWillBeRawPtr<HTMLElement> blockquoteForNextIndent = nullptr;
124 VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection);
125 VisiblePosition endOfLastParagraph = endOfParagraph(endOfSelection);
126 VisiblePosition endAfterSelection = endOfParagraph(endOfLastParagraph.next() );
127 m_endOfLastParagraph = endOfLastParagraph.deepEquivalent();
128
129 bool atEnd = false;
130 Position end;
131 while (endOfCurrentParagraph.deepEquivalent() != endAfterSelection.deepEquiv alent() && !atEnd) {
132 if (endOfCurrentParagraph.deepEquivalent() == m_endOfLastParagraph)
133 atEnd = true;
134
135 rangeForParagraphSplittingTextNodesIfNeeded(endOfCurrentParagraph, start , end);
136 endOfCurrentParagraph = VisiblePosition(end);
137
138 Node* enclosingCell = enclosingNodeOfType(start, &isTableCell);
139 VisiblePosition endOfNextParagraph = endOfNextParagrahSplittingTextNodes IfNeeded(endOfCurrentParagraph, start, end);
140
141 formatRange(start, end, m_endOfLastParagraph, blockquoteForNextIndent);
142
143 // Don't put the next paragraph in the blockquote we just created for th is paragraph unless
144 // the next paragraph is in the same cell.
145 if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParag raph.deepEquivalent(), &isTableCell))
146 blockquoteForNextIndent = nullptr;
147
148 // indentIntoBlockquote could move more than one paragraph if the paragr aph
149 // is in a list item or a table. As a result, endAfterSelection could re fer to a position
150 // no longer in the document.
151 if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent() .inDocument())
152 break;
153 // Sanity check: Make sure our moveParagraph calls didn't remove endOfNe xtParagraph.deepEquivalent().anchorNode()
154 // If somehow, e.g. mutation event handler, we did, return to prevent cr ashes.
155 if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent ().inDocument())
156 return;
157 endOfCurrentParagraph = endOfNextParagraph;
158 }
159 }
160
161 static bool isNewLineAtPosition(const Position& position)
162 {
163 Node* textNode = position.computeContainerNode();
164 int offset = position.offsetInContainerNode();
165 if (!textNode || !textNode->isTextNode() || offset < 0 || offset >= textNode ->maxCharacterOffset())
166 return false;
167
168 TrackExceptionState exceptionState;
169 String textAtPosition = toText(textNode)->substringData(offset, 1, exception State);
170 if (exceptionState.hadException())
171 return false;
172
173 return textAtPosition[0] == '\n';
174 }
175
176 static const ComputedStyle* computedStyleOfEnclosingTextNode(const Position& pos ition)
177 {
178 if (!position.isOffsetInAnchor() || !position.computeContainerNode() || !pos ition.computeContainerNode()->isTextNode())
179 return 0;
180 return position.computeContainerNode()->computedStyle();
181 }
182
183 void ApplyBlockElementCommand::rangeForParagraphSplittingTextNodesIfNeeded(const VisiblePosition& endOfCurrentParagraph, Position& start, Position& end)
184 {
185 start = startOfParagraph(endOfCurrentParagraph).deepEquivalent();
186 end = endOfCurrentParagraph.deepEquivalent();
187
188 document().updateLayoutTreeIfNeeded();
189
190 bool isStartAndEndOnSameNode = false;
191 if (const ComputedStyle* startStyle = computedStyleOfEnclosingTextNode(start )) {
192 isStartAndEndOnSameNode = computedStyleOfEnclosingTextNode(end) && start .computeContainerNode() == end.computeContainerNode();
193 bool isStartAndEndOfLastParagraphOnSameNode = computedStyleOfEnclosingTe xtNode(m_endOfLastParagraph) && start.computeContainerNode() == m_endOfLastParag raph.computeContainerNode();
194
195 // Avoid obtanining the start of next paragraph for start
196 if (startStyle->preserveNewline() && isNewLineAtPosition(start) && !isNe wLineAtPosition(start.previous()) && start.offsetInContainerNode() > 0)
197 start = startOfParagraph(VisiblePosition(end.previous())).deepEquiva lent();
198
199 // If start is in the middle of a text node, split.
200 if (!startStyle->collapseWhiteSpace() && start.offsetInContainerNode() > 0) {
201 int startOffset = start.offsetInContainerNode();
202 Text* startText = toText(start.computeContainerNode());
203 splitTextNode(startText, startOffset);
204 start = firstPositionInNode(startText);
205 if (isStartAndEndOnSameNode) {
206 ASSERT(end.offsetInContainerNode() >= startOffset);
207 end = Position(startText, end.offsetInContainerNode() - startOff set);
208 }
209 if (isStartAndEndOfLastParagraphOnSameNode) {
210 ASSERT(m_endOfLastParagraph.offsetInContainerNode() >= startOffs et);
211 m_endOfLastParagraph = Position(startText, m_endOfLastParagraph. offsetInContainerNode() - startOffset);
212 }
213 }
214 }
215
216 document().updateLayoutTreeIfNeeded();
217
218 if (const ComputedStyle* endStyle = computedStyleOfEnclosingTextNode(end)) {
219 bool isEndAndEndOfLastParagraphOnSameNode = computedStyleOfEnclosingText Node(m_endOfLastParagraph) && end.anchorNode() == m_endOfLastParagraph.anchorNod e();
220 // Include \n at the end of line if we're at an empty paragraph
221 if (endStyle->preserveNewline() && start == end && end.offsetInContainer Node() < end.computeContainerNode()->maxCharacterOffset()) {
222 int endOffset = end.offsetInContainerNode();
223 if (!isNewLineAtPosition(end.previous()) && isNewLineAtPosition(end) )
224 end = Position(end.computeContainerNode(), endOffset + 1);
225 if (isEndAndEndOfLastParagraphOnSameNode && end.offsetInContainerNod e() >= m_endOfLastParagraph.offsetInContainerNode())
226 m_endOfLastParagraph = end;
227 }
228
229 // If end is in the middle of a text node, split.
230 if (endStyle->userModify() != READ_ONLY && !endStyle->collapseWhiteSpace () && end.offsetInContainerNode() && end.offsetInContainerNode() < end.computeCo ntainerNode()->maxCharacterOffset()) {
231 RefPtrWillBeRawPtr<Text> endContainer = toText(end.computeContainerN ode());
232 splitTextNode(endContainer, end.offsetInContainerNode());
233 if (isStartAndEndOnSameNode)
234 start = firstPositionInOrBeforeNode(endContainer->previousSiblin g());
235 if (isEndAndEndOfLastParagraphOnSameNode) {
236 if (m_endOfLastParagraph.offsetInContainerNode() == end.offsetIn ContainerNode())
237 m_endOfLastParagraph = lastPositionInOrAfterNode(endContaine r->previousSibling());
238 else
239 m_endOfLastParagraph = Position(endContainer, m_endOfLastPar agraph.offsetInContainerNode() - end.offsetInContainerNode());
240 }
241 end = lastPositionInNode(endContainer->previousSibling());
242 }
243 }
244 }
245
246 VisiblePosition ApplyBlockElementCommand::endOfNextParagrahSplittingTextNodesIfN eeded(VisiblePosition& endOfCurrentParagraph, Position& start, Position& end)
247 {
248 VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.ne xt());
249 Position position = endOfNextParagraph.deepEquivalent();
250 const ComputedStyle* style = computedStyleOfEnclosingTextNode(position);
251 if (!style)
252 return endOfNextParagraph;
253
254 RefPtrWillBeRawPtr<Text> text = toText(position.computeContainerNode());
255 if (!style->preserveNewline() || !position.offsetInContainerNode() || !isNew LineAtPosition(firstPositionInNode(text.get())))
256 return endOfNextParagraph;
257
258 // \n at the beginning of the text node immediately following the current pa ragraph is trimmed by moveParagraphWithClones.
259 // If endOfNextParagraph was pointing at this same text node, endOfNextParag raph will be shifted by one paragraph.
260 // Avoid this by splitting "\n"
261 splitTextNode(text, 1);
262
263 if (text == start.computeContainerNode() && text->previousSibling() && text- >previousSibling()->isTextNode()) {
264 ASSERT(start.offsetInContainerNode() < position.offsetInContainerNode()) ;
265 start = Position(toText(text->previousSibling()), start.offsetInContaine rNode());
266 }
267 if (text == end.computeContainerNode() && text->previousSibling() && text->p reviousSibling()->isTextNode()) {
268 ASSERT(end.offsetInContainerNode() < position.offsetInContainerNode());
269 end = Position(toText(text->previousSibling()), end.offsetInContainerNod e());
270 }
271 if (text == m_endOfLastParagraph.computeContainerNode()) {
272 if (m_endOfLastParagraph.offsetInContainerNode() < position.offsetInCont ainerNode()) {
273 // We can only fix endOfLastParagraph if the previous node was still text and hasn't been modified by script.
274 if (text->previousSibling()->isTextNode()
275 && static_cast<unsigned>(m_endOfLastParagraph.offsetInContainerN ode()) <= toText(text->previousSibling())->length())
276 m_endOfLastParagraph = Position(toText(text->previousSibling()), m_endOfLastParagraph.offsetInContainerNode());
277 } else {
278 m_endOfLastParagraph = Position(text.get(), m_endOfLastParagraph.off setInContainerNode() - 1);
279 }
280 }
281
282 return VisiblePosition(Position(text.get(), position.offsetInContainerNode() - 1));
283 }
284
285 PassRefPtrWillBeRawPtr<HTMLElement> ApplyBlockElementCommand::createBlockElement () const
286 {
287 RefPtrWillBeRawPtr<HTMLElement> element = createHTMLElement(document(), m_ta gName);
288 if (m_inlineStyle.length())
289 element->setAttribute(styleAttr, m_inlineStyle);
290 return element.release();
291 }
292
293 DEFINE_TRACE(ApplyBlockElementCommand)
294 {
295 visitor->trace(m_endOfLastParagraph);
296 CompositeEditCommand::trace(visitor);
297 }
298
299 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698