1 /*
2 * Copyright (C) 2005, 2006, 2007, 2008 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 COMPUTER, 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 COMPUTER, 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 "CompositeEditCommand.h"
28
29 #include "AppendNodeCommand.h"
30 #include "ApplyStyleCommand.h"
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSMutableStyleDeclaration.h"
33 #include "CharacterNames.h"
34 #include "DeleteFromTextNodeCommand.h"
35 #include "DeleteSelectionCommand.h"
36 #include "Document.h"
37 #include "DocumentFragment.h"
38 #include "EditorInsertAction.h"
39 #include "HTMLElement.h"
40 #include "HTMLNames.h"
41 #include "InlineTextBox.h"
42 #include "InsertIntoTextNodeCommand.h"
43 #include "InsertLineBreakCommand.h"
44 #include "InsertNodeBeforeCommand.h"
45 #include "InsertParagraphSeparatorCommand.h"
46 #include "InsertTextCommand.h"
47 #include "JoinTextNodesCommand.h"
48 #include "MergeIdenticalElementsCommand.h"
49 #include "Range.h"
50 #include "RemoveCSSPropertyCommand.h"
51 #include "RemoveNodeCommand.h"
52 #include "RemoveNodePreservingChildrenCommand.h"
53 #include "ReplaceNodeWithSpanCommand.h"
54 #include "ReplaceSelectionCommand.h"
55 #include "RenderBlock.h"
56 #include "RenderText.h"
57 #include "SetNodeAttributeCommand.h"
58 #include "SplitElementCommand.h"
59 #include "SplitTextNodeCommand.h"
60 #include "SplitTextNodeContainingElementCommand.h"
61 #include "Text.h"
62 #include "TextIterator.h"
63 #include "WrapContentsInDummySpanCommand.h"
64 #include "htmlediting.h"
65 #include "markup.h"
66 #include "visible_units.h"
67
68 using namespace std;
69
70 namespace WebCore {
71
72 using namespace HTMLNames;
73
CompositeEditCommand(Document * document)74 CompositeEditCommand::CompositeEditCommand(Document *document)
75 : EditCommand(document)
76 {
77 }
78
doUnapply()79 void CompositeEditCommand::doUnapply()
80 {
81 size_t size = m_commands.size();
82 for (size_t i = size; i != 0; --i)
83 m_commands[i - 1]->unapply();
84 }
85
doReapply()86 void CompositeEditCommand::doReapply()
87 {
88 size_t size = m_commands.size();
89 for (size_t i = 0; i != size; ++i)
90 m_commands[i]->reapply();
91 }
92
93 //
94 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
95 //
applyCommandToComposite(PassRefPtr<EditCommand> cmd)96 void CompositeEditCommand::applyCommandToComposite(PassRefPtr<EditCommand> cmd)
97 {
98 cmd->setParent(this);
99 cmd->apply();
100 m_commands.append(cmd);
101 }
102
applyStyle(CSSStyleDeclaration * style,EditAction editingAction)103 void CompositeEditCommand::applyStyle(CSSStyleDeclaration* style, EditAction editingAction)
104 {
105 applyCommandToComposite(ApplyStyleCommand::create(document(), style, editingAction));
106 }
107
applyStyle(CSSStyleDeclaration * style,const Position & start,const Position & end,EditAction editingAction)108 void CompositeEditCommand::applyStyle(CSSStyleDeclaration* style, const Position& start, const Position& end, EditAction editingAction)
109 {
110 applyCommandToComposite(ApplyStyleCommand::create(document(), style, start, end, editingAction));
111 }
112
applyStyledElement(PassRefPtr<Element> element)113 void CompositeEditCommand::applyStyledElement(PassRefPtr<Element> element)
114 {
115 applyCommandToComposite(ApplyStyleCommand::create(element, false));
116 }
117
removeStyledElement(PassRefPtr<Element> element)118 void CompositeEditCommand::removeStyledElement(PassRefPtr<Element> element)
119 {
120 applyCommandToComposite(ApplyStyleCommand::create(element, true));
121 }
122
insertParagraphSeparator(bool useDefaultParagraphElement)123 void CompositeEditCommand::insertParagraphSeparator(bool useDefaultParagraphElement)
124 {
125 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), useDefaultParagraphElement));
126 }
127
insertLineBreak()128 void CompositeEditCommand::insertLineBreak()
129 {
130 applyCommandToComposite(InsertLineBreakCommand::create(document()));
131 }
132
insertNodeBefore(PassRefPtr<Node> insertChild,PassRefPtr<Node> refChild)133 void CompositeEditCommand::insertNodeBefore(PassRefPtr<Node> insertChild, PassRefPtr<Node> refChild)
134 {
135 ASSERT(!refChild->hasTagName(bodyTag));
136 applyCommandToComposite(InsertNodeBeforeCommand::create(insertChild, refChild));
137 }
138
insertNodeAfter(PassRefPtr<Node> insertChild,PassRefPtr<Node> refChild)139 void CompositeEditCommand::insertNodeAfter(PassRefPtr<Node> insertChild, PassRefPtr<Node> refChild)
140 {
141 ASSERT(insertChild);
142 ASSERT(refChild);
143 ASSERT(!refChild->hasTagName(bodyTag));
144 Element* parent = refChild->parentElement();
145 ASSERT(parent);
146 if (parent->lastChild() == refChild)
147 appendNode(insertChild, parent);
148 else {
149 ASSERT(refChild->nextSibling());
150 insertNodeBefore(insertChild, refChild->nextSibling());
151 }
152 }
153
insertNodeAt(PassRefPtr<Node> insertChild,const Position & editingPosition)154 void CompositeEditCommand::insertNodeAt(PassRefPtr<Node> insertChild, const Position& editingPosition)
155 {
156 ASSERT(isEditablePosition(editingPosition));
157 // For editing positions like [table, 0], insert before the table,
158 // likewise for replaced elements, brs, etc.
159 Position p = rangeCompliantEquivalent(editingPosition);
160 Node* refChild = p.node();
161 int offset = p.deprecatedEditingOffset();
162
163 if (canHaveChildrenForEditing(refChild)) {
164 Node* child = refChild->firstChild();
165 for (int i = 0; child && i < offset; i++)
166 child = child->nextSibling();
167 if (child)
168 insertNodeBefore(insertChild, child);
169 else
170 appendNode(insertChild, static_cast<Element*>(refChild));
171 } else if (caretMinOffset(refChild) >= offset)
172 insertNodeBefore(insertChild, refChild);
173 else if (refChild->isTextNode() && caretMaxOffset(refChild) > offset) {
174 splitTextNode(static_cast<Text *>(refChild), offset);
175
176 // Mutation events (bug 22634) from the text node insertion may have removed the refChild
177 if (!refChild->inDocument())
178 return;
179 insertNodeBefore(insertChild, refChild);
180 } else
181 insertNodeAfter(insertChild, refChild);
182 }
183
appendNode(PassRefPtr<Node> node,PassRefPtr<Element> parent)184 void CompositeEditCommand::appendNode(PassRefPtr<Node> node, PassRefPtr<Element> parent)
185 {
186 ASSERT(canHaveChildrenForEditing(parent.get()));
187 applyCommandToComposite(AppendNodeCommand::create(parent, node));
188 }
189
removeChildrenInRange(PassRefPtr<Node> node,unsigned from,unsigned to)190 void CompositeEditCommand::removeChildrenInRange(PassRefPtr<Node> node, unsigned from, unsigned to)
191 {
192 Vector<RefPtr<Node> > children;
193 Node* child = node->childNode(from);
194 for (unsigned i = from; child && i < to; i++, child = child->nextSibling())
195 children.append(child);
196
197 size_t size = children.size();
198 for (size_t i = 0; i < size; ++i)
199 removeNode(children[i].release());
200 }
201
removeNode(PassRefPtr<Node> node)202 void CompositeEditCommand::removeNode(PassRefPtr<Node> node)
203 {
204 applyCommandToComposite(RemoveNodeCommand::create(node));
205 }
206
removeNodePreservingChildren(PassRefPtr<Node> node)207 void CompositeEditCommand::removeNodePreservingChildren(PassRefPtr<Node> node)
208 {
209 applyCommandToComposite(RemoveNodePreservingChildrenCommand::create(node));
210 }
211
removeNodeAndPruneAncestors(PassRefPtr<Node> node)212 void CompositeEditCommand::removeNodeAndPruneAncestors(PassRefPtr<Node> node)
213 {
214 RefPtr<Node> parent = node->parentNode();
215 removeNode(node);
216 prune(parent.release());
217 }
218
replaceNodeWithSpanPreservingChildrenAndAttributes(PassRefPtr<Node> node)219 HTMLElement* CompositeEditCommand::replaceNodeWithSpanPreservingChildrenAndAttributes(PassRefPtr<Node> node)
220 {
221 // It would also be possible to implement all of ReplaceNodeWithSpanCommand
222 // as a series of existing smaller edit commands. Someone who wanted to
223 // reduce the number of edit commands could do so here.
224 RefPtr<ReplaceNodeWithSpanCommand> command = ReplaceNodeWithSpanCommand::create(node);
225 applyCommandToComposite(command);
226 // Returning a raw pointer here is OK because the command is retained by
227 // applyCommandToComposite (thus retaining the span), and the span is also
228 // in the DOM tree, and thus alive whie it has a parent.
229 ASSERT(command->spanElement()->inDocument());
230 return command->spanElement();
231 }
232
hasARenderedDescendant(Node * node)233 static bool hasARenderedDescendant(Node* node)
234 {
235 Node* n = node->firstChild();
236 while (n) {
237 if (n->renderer())
238 return true;
239 n = n->traverseNextNode(node);
240 }
241 return false;
242 }
243
prune(PassRefPtr<Node> node)244 void CompositeEditCommand::prune(PassRefPtr<Node> node)
245 {
246 while (node) {
247 // If you change this rule you may have to add an updateLayout() here.
248 RenderObject* renderer = node->renderer();
249 if (renderer && (!renderer->canHaveChildren() || hasARenderedDescendant(node.get()) || node->rootEditableElement() == node))
250 return;
251
252 RefPtr<Node> next = node->parentNode();
253 removeNode(node);
254 node = next;
255 }
256 }
257
splitTextNode(PassRefPtr<Text> node,unsigned offset)258 void CompositeEditCommand::splitTextNode(PassRefPtr<Text> node, unsigned offset)
259 {
260 applyCommandToComposite(SplitTextNodeCommand::create(node, offset));
261 }
262
splitElement(PassRefPtr<Element> element,PassRefPtr<Node> atChild)263 void CompositeEditCommand::splitElement(PassRefPtr<Element> element, PassRefPtr<Node> atChild)
264 {
265 applyCommandToComposite(SplitElementCommand::create(element, atChild));
266 }
267
mergeIdenticalElements(PassRefPtr<Element> prpFirst,PassRefPtr<Element> prpSecond)268 void CompositeEditCommand::mergeIdenticalElements(PassRefPtr<Element> prpFirst, PassRefPtr<Element> prpSecond)
269 {
270 RefPtr<Element> first = prpFirst;
271 RefPtr<Element> second = prpSecond;
272 ASSERT(!first->isDescendantOf(second.get()) && second != first);
273 if (first->nextSibling() != second) {
274 removeNode(second);
275 insertNodeAfter(second, first);
276 }
277 applyCommandToComposite(MergeIdenticalElementsCommand::create(first, second));
278 }
279
wrapContentsInDummySpan(PassRefPtr<Element> element)280 void CompositeEditCommand::wrapContentsInDummySpan(PassRefPtr<Element> element)
281 {
282 applyCommandToComposite(WrapContentsInDummySpanCommand::create(element));
283 }
284
splitTextNodeContainingElement(PassRefPtr<Text> text,unsigned offset)285 void CompositeEditCommand::splitTextNodeContainingElement(PassRefPtr<Text> text, unsigned offset)
286 {
287 applyCommandToComposite(SplitTextNodeContainingElementCommand::create(text, offset));
288 }
289
joinTextNodes(PassRefPtr<Text> text1,PassRefPtr<Text> text2)290 void CompositeEditCommand::joinTextNodes(PassRefPtr<Text> text1, PassRefPtr<Text> text2)
291 {
292 applyCommandToComposite(JoinTextNodesCommand::create(text1, text2));
293 }
294
inputText(const String & text,bool selectInsertedText)295 void CompositeEditCommand::inputText(const String& text, bool selectInsertedText)
296 {
297 int offset = 0;
298 int length = text.length();
299 RefPtr<Range> startRange = Range::create(document(), Position(document()->documentElement(), 0), endingSelection().start());
300 int startIndex = TextIterator::rangeLength(startRange.get());
301 int newline;
302 do {
303 newline = text.find('\n', offset);
304 if (newline != offset) {
305 RefPtr<InsertTextCommand> command = InsertTextCommand::create(document());
306 applyCommandToComposite(command);
307 int substringLength = newline == -1 ? length - offset : newline - offset;
308 command->input(text.substring(offset, substringLength), false);
309 }
310 if (newline != -1)
311 insertLineBreak();
312
313 offset = newline + 1;
314 } while (newline != -1 && offset != length);
315
316 if (selectInsertedText) {
317 RefPtr<Range> selectedRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, length);
318 setEndingSelection(VisibleSelection(selectedRange.get()));
319 }
320 }
321
insertTextIntoNode(PassRefPtr<Text> node,unsigned offset,const String & text)322 void CompositeEditCommand::insertTextIntoNode(PassRefPtr<Text> node, unsigned offset, const String& text)
323 {
324 applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, text));
325 }
326
deleteTextFromNode(PassRefPtr<Text> node,unsigned offset,unsigned count)327 void CompositeEditCommand::deleteTextFromNode(PassRefPtr<Text> node, unsigned offset, unsigned count)
328 {
329 applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count));
330 }
331
replaceTextInNode(PassRefPtr<Text> node,unsigned offset,unsigned count,const String & replacementText)332 void CompositeEditCommand::replaceTextInNode(PassRefPtr<Text> node, unsigned offset, unsigned count, const String& replacementText)
333 {
334 applyCommandToComposite(DeleteFromTextNodeCommand::create(node.get(), offset, count));
335 applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, replacementText));
336 }
337
positionOutsideTabSpan(const Position & pos)338 Position CompositeEditCommand::positionOutsideTabSpan(const Position& pos)
339 {
340 if (!isTabSpanTextNode(pos.node()))
341 return pos;
342
343 Node* tabSpan = tabSpanNode(pos.node());
344
345 if (pos.deprecatedEditingOffset() <= caretMinOffset(pos.node()))
346 return positionInParentBeforeNode(tabSpan);
347
348 if (pos.deprecatedEditingOffset() >= caretMaxOffset(pos.node()))
349 return positionInParentAfterNode(tabSpan);
350
351 splitTextNodeContainingElement(static_cast<Text *>(pos.node()), pos.deprecatedEditingOffset());
352 return positionInParentBeforeNode(tabSpan);
353 }
354
insertNodeAtTabSpanPosition(PassRefPtr<Node> node,const Position & pos)355 void CompositeEditCommand::insertNodeAtTabSpanPosition(PassRefPtr<Node> node, const Position& pos)
356 {
357 // insert node before, after, or at split of tab span
358 insertNodeAt(node, positionOutsideTabSpan(pos));
359 }
360
deleteSelection(bool smartDelete,bool mergeBlocksAfterDelete,bool replace,bool expandForSpecialElements)361 void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements)
362 {
363 if (endingSelection().isRange())
364 applyCommandToComposite(DeleteSelectionCommand::create(document(), smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements));
365 }
366
deleteSelection(const VisibleSelection & selection,bool smartDelete,bool mergeBlocksAfterDelete,bool replace,bool expandForSpecialElements)367 void CompositeEditCommand::deleteSelection(const VisibleSelection &selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements)
368 {
369 if (selection.isRange())
370 applyCommandToComposite(DeleteSelectionCommand::create(selection, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements));
371 }
372
removeCSSProperty(PassRefPtr<CSSMutableStyleDeclaration> style,CSSPropertyID property)373 void CompositeEditCommand::removeCSSProperty(PassRefPtr<CSSMutableStyleDeclaration> style, CSSPropertyID property)
374 {
375 applyCommandToComposite(RemoveCSSPropertyCommand::create(document(), style, property));
376 }
377
removeNodeAttribute(PassRefPtr<Element> element,const QualifiedName & attribute)378 void CompositeEditCommand::removeNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute)
379 {
380 setNodeAttribute(element, attribute, AtomicString());
381 }
382
setNodeAttribute(PassRefPtr<Element> element,const QualifiedName & attribute,const AtomicString & value)383 void CompositeEditCommand::setNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute, const AtomicString& value)
384 {
385 applyCommandToComposite(SetNodeAttributeCommand::create(element, attribute, value));
386 }
387
isWhitespace(UChar c)388 static inline bool isWhitespace(UChar c)
389 {
390 return c == noBreakSpace || c == ' ' || c == '\n' || c == '\t';
391 }
392
393 // FIXME: Doesn't go into text nodes that contribute adjacent text (siblings, cousins, etc).
rebalanceWhitespaceAt(const Position & position)394 void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position)
395 {
396 Node* node = position.node();
397 if (!node || !node->isTextNode())
398 return;
399 Text* textNode = static_cast<Text*>(node);
400
401 if (textNode->length() == 0)
402 return;
403 RenderObject* renderer = textNode->renderer();
404 if (renderer && !renderer->style()->collapseWhiteSpace())
405 return;
406
407 String text = textNode->data();
408 ASSERT(!text.isEmpty());
409
410 int offset = position.deprecatedEditingOffset();
411 // If neither text[offset] nor text[offset - 1] are some form of whitespace, do nothing.
412 if (!isWhitespace(text[offset])) {
413 offset--;
414 if (offset < 0 || !isWhitespace(text[offset]))
415 return;
416 }
417
418 // Set upstream and downstream to define the extent of the whitespace surrounding text[offset].
419 int upstream = offset;
420 while (upstream > 0 && isWhitespace(text[upstream - 1]))
421 upstream--;
422
423 int downstream = offset;
424 while ((unsigned)downstream + 1 < text.length() && isWhitespace(text[downstream + 1]))
425 downstream++;
426
427 int length = downstream - upstream + 1;
428 ASSERT(length > 0);
429
430 VisiblePosition visibleUpstreamPos(Position(position.node(), upstream));
431 VisiblePosition visibleDownstreamPos(Position(position.node(), downstream + 1));
432
433 String string = text.substring(upstream, length);
434 String rebalancedString = stringWithRebalancedWhitespace(string,
435 // FIXME: Because of the problem mentioned at the top of this function, we must also use nbsps at the start/end of the string because
436 // this function doesn't get all surrounding whitespace, just the whitespace in the current text node.
437 isStartOfParagraph(visibleUpstreamPos) || upstream == 0,
438 isEndOfParagraph(visibleDownstreamPos) || (unsigned)downstream == text.length() - 1);
439
440 if (string != rebalancedString)
441 replaceTextInNode(textNode, upstream, length, rebalancedString);
442 }
443
prepareWhitespaceAtPositionForSplit(Position & position)444 void CompositeEditCommand::prepareWhitespaceAtPositionForSplit(Position& position)
445 {
446 Node* node = position.node();
447 if (!node || !node->isTextNode())
448 return;
449 Text* textNode = static_cast<Text*>(node);
450
451 if (textNode->length() == 0)
452 return;
453 RenderObject* renderer = textNode->renderer();
454 if (renderer && !renderer->style()->collapseWhiteSpace())
455 return;
456
457 // Delete collapsed whitespace so that inserting nbsps doesn't uncollapse it.
458 Position upstreamPos = position.upstream();
459 deleteInsignificantText(position.upstream(), position.downstream());
460 position = upstreamPos.downstream();
461
462 VisiblePosition visiblePos(position);
463 VisiblePosition previousVisiblePos(visiblePos.previous());
464 Position previous(previousVisiblePos.deepEquivalent());
465
466 if (isCollapsibleWhitespace(previousVisiblePos.characterAfter()) && previous.node()->isTextNode() && !previous.node()->hasTagName(brTag))
467 replaceTextInNode(static_cast<Text*>(previous.node()), previous.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
468 if (isCollapsibleWhitespace(visiblePos.characterAfter()) && position.node()->isTextNode() && !position.node()->hasTagName(brTag))
469 replaceTextInNode(static_cast<Text*>(position.node()), position.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
470 }
471
rebalanceWhitespace()472 void CompositeEditCommand::rebalanceWhitespace()
473 {
474 VisibleSelection selection = endingSelection();
475 if (selection.isNone())
476 return;
477
478 rebalanceWhitespaceAt(selection.start());
479 if (selection.isRange())
480 rebalanceWhitespaceAt(selection.end());
481 }
482
deleteInsignificantText(PassRefPtr<Text> textNode,unsigned start,unsigned end)483 void CompositeEditCommand::deleteInsignificantText(PassRefPtr<Text> textNode, unsigned start, unsigned end)
484 {
485 if (!textNode || start >= end)
486 return;
487
488 RenderText* textRenderer = toRenderText(textNode->renderer());
489 if (!textRenderer)
490 return;
491
492 InlineTextBox* box = textRenderer->firstTextBox();
493 if (!box) {
494 // whole text node is empty
495 removeNode(textNode);
496 return;
497 }
498
499 unsigned length = textNode->length();
500 if (start >= length || end > length)
501 return;
502
503 unsigned removed = 0;
504 InlineTextBox* prevBox = 0;
505 String str;
506
507 // This loop structure works to process all gaps preceding a box,
508 // and also will look at the gap after the last box.
509 while (prevBox || box) {
510 unsigned gapStart = prevBox ? prevBox->start() + prevBox->len() : 0;
511 if (end < gapStart)
512 // No more chance for any intersections
513 break;
514
515 unsigned gapEnd = box ? box->start() : length;
516 bool indicesIntersect = start <= gapEnd && end >= gapStart;
517 int gapLen = gapEnd - gapStart;
518 if (indicesIntersect && gapLen > 0) {
519 gapStart = max(gapStart, start);
520 gapEnd = min(gapEnd, end);
521 if (str.isNull())
522 str = textNode->data().substring(start, end - start);
523 // remove text in the gap
524 str.remove(gapStart - start - removed, gapLen);
525 removed += gapLen;
526 }
527
528 prevBox = box;
529 if (box)
530 box = box->nextTextBox();
531 }
532
533 if (!str.isNull()) {
534 // Replace the text between start and end with our pruned version.
535 if (!str.isEmpty())
536 replaceTextInNode(textNode, start, end - start, str);
537 else {
538 // Assert that we are not going to delete all of the text in the node.
539 // If we were, that should have been done above with the call to
540 // removeNode and return.
541 ASSERT(start > 0 || end - start < textNode->length());
542 deleteTextFromNode(textNode, start, end - start);
543 }
544 }
545 }
546
deleteInsignificantText(const Position & start,const Position & end)547 void CompositeEditCommand::deleteInsignificantText(const Position& start, const Position& end)
548 {
549 if (start.isNull() || end.isNull())
550 return;
551
552 if (comparePositions(start, end) >= 0)
553 return;
554
555 Node* next;
556 for (Node* node = start.node(); node; node = next) {
557 next = node->traverseNextNode();
558 if (node->isTextNode()) {
559 Text* textNode = static_cast<Text*>(node);
560 int startOffset = node == start.node() ? start.deprecatedEditingOffset() : 0;
561 int endOffset = node == end.node() ? end.deprecatedEditingOffset() : textNode->length();
562 deleteInsignificantText(textNode, startOffset, endOffset);
563 }
564 if (node == end.node())
565 break;
566 }
567 }
568
deleteInsignificantTextDownstream(const Position & pos)569 void CompositeEditCommand::deleteInsignificantTextDownstream(const Position& pos)
570 {
571 Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream();
572 deleteInsignificantText(pos, end);
573 }
574
appendBlockPlaceholder(PassRefPtr<Element> container)575 PassRefPtr<Node> CompositeEditCommand::appendBlockPlaceholder(PassRefPtr<Element> container)
576 {
577 if (!container)
578 return 0;
579
580 // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964.
581 ASSERT(container->renderer());
582
583 RefPtr<Node> placeholder = createBlockPlaceholderElement(document());
584 appendNode(placeholder, container);
585 return placeholder.release();
586 }
587
insertBlockPlaceholder(const Position & pos)588 PassRefPtr<Node> CompositeEditCommand::insertBlockPlaceholder(const Position& pos)
589 {
590 if (pos.isNull())
591 return 0;
592
593 // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964.
594 ASSERT(pos.node()->renderer());
595
596 RefPtr<Node> placeholder = createBlockPlaceholderElement(document());
597 insertNodeAt(placeholder, pos);
598 return placeholder.release();
599 }
600
addBlockPlaceholderIfNeeded(Element * container)601 PassRefPtr<Node> CompositeEditCommand::addBlockPlaceholderIfNeeded(Element* container)
602 {
603 if (!container)
604 return 0;
605
606 updateLayout();
607
608 RenderObject* renderer = container->renderer();
609 if (!renderer || !renderer->isBlockFlow())
610 return 0;
611
612 // append the placeholder to make sure it follows
613 // any unrendered blocks
614 RenderBlock* block = toRenderBlock(renderer);
615 if (block->height() == 0 || (block->isListItem() && block->isEmpty()))
616 return appendBlockPlaceholder(container);
617
618 return 0;
619 }
620
621 // Assumes that the position is at a placeholder and does the removal without much checking.
removePlaceholderAt(const Position & p)622 void CompositeEditCommand::removePlaceholderAt(const Position& p)
623 {
624 ASSERT(lineBreakExistsAtPosition(p));
625
626 // We are certain that the position is at a line break, but it may be a br or a preserved newline.
627 if (p.anchorNode()->hasTagName(brTag)) {
628 removeNode(p.anchorNode());
629 return;
630 }
631
632 deleteTextFromNode(static_cast<Text*>(p.anchorNode()), p.offsetInContainerNode(), 1);
633 }
634
insertNewDefaultParagraphElementAt(const Position & position)635 PassRefPtr<Node> CompositeEditCommand::insertNewDefaultParagraphElementAt(const Position& position)
636 {
637 RefPtr<Element> paragraphElement = createDefaultParagraphElement(document());
638 ExceptionCode ec;
639 paragraphElement->appendChild(createBreakElement(document()), ec);
640 insertNodeAt(paragraphElement, position);
641 return paragraphElement.release();
642 }
643
644 // If the paragraph is not entirely within it's own block, create one and move the paragraph into
645 // it, and return that block. Otherwise return 0.
moveParagraphContentsToNewBlockIfNecessary(const Position & pos)646 PassRefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position& pos)
647 {
648 if (pos.isNull())
649 return 0;
650
651 updateLayout();
652
653 // It's strange that this function is responsible for verifying that pos has not been invalidated
654 // by an earlier call to this function. The caller, applyBlockStyle, should do this.
655 VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
656 VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos));
657 VisiblePosition visibleParagraphEnd = endOfParagraph(visiblePos);
658 VisiblePosition next = visibleParagraphEnd.next();
659 VisiblePosition visibleEnd = next.isNotNull() ? next : visibleParagraphEnd;
660
661 Position upstreamStart = visibleParagraphStart.deepEquivalent().upstream();
662 Position upstreamEnd = visibleEnd.deepEquivalent().upstream();
663
664 // If there are no VisiblePositions in the same block as pos then
665 // upstreamStart will be outside the paragraph
666 if (comparePositions(pos, upstreamStart) < 0)
667 return 0;
668
669 // Perform some checks to see if we need to perform work in this function.
670 if (isBlock(upstreamStart.node())) {
671 // If the block is the root editable element, always move content to a new block,
672 // since it is illegal to modify attributes on the root editable element for editing.
673 if (upstreamStart.node() == editableRootForPosition(upstreamStart)) {
674 // If the block is the root editable element and it contains no visible content, create a new
675 // block but don't try and move content into it, since there's nothing for moveParagraphs to move.
676 if (!Position::hasRenderedNonAnonymousDescendantsWithHeight(upstreamStart.node()->renderer()))
677 return insertNewDefaultParagraphElementAt(upstreamStart);
678 } else if (isBlock(upstreamEnd.node())) {
679 if (!upstreamEnd.node()->isDescendantOf(upstreamStart.node())) {
680 // If the paragraph end is a descendant of paragraph start, then we need to run
681 // the rest of this function. If not, we can bail here.
682 return 0;
683 }
684 }
685 else if (enclosingBlock(upstreamEnd.node()) != upstreamStart.node()) {
686 // The visibleEnd. It must be an ancestor of the paragraph start.
687 // We can bail as we have a full block to work with.
688 ASSERT(upstreamStart.node()->isDescendantOf(enclosingBlock(upstreamEnd.node())));
689 return 0;
690 }
691 else if (isEndOfDocument(visibleEnd)) {
692 // At the end of the document. We can bail here as well.
693 return 0;
694 }
695 }
696
697 RefPtr<Node> newBlock = insertNewDefaultParagraphElementAt(upstreamStart);
698
699 moveParagraphs(visibleParagraphStart, visibleParagraphEnd, VisiblePosition(Position(newBlock.get(), 0)));
700
701 return newBlock.release();
702 }
703
pushAnchorElementDown(Node * anchorNode)704 void CompositeEditCommand::pushAnchorElementDown(Node* anchorNode)
705 {
706 if (!anchorNode)
707 return;
708
709 ASSERT(anchorNode->isLink());
710
711 setEndingSelection(VisibleSelection::selectionFromContentsOfNode(anchorNode));
712 applyStyledElement(static_cast<Element*>(anchorNode));
713 // Clones of anchorNode have been pushed down, now remove it.
714 if (anchorNode->inDocument())
715 removeNodePreservingChildren(anchorNode);
716 }
717
718 // We must push partially selected anchors down before creating or removing
719 // links from a selection to create fully selected chunks that can be removed.
720 // ApplyStyleCommand doesn't do this for us because styles can be nested.
721 // Anchors cannot be nested.
pushPartiallySelectedAnchorElementsDown()722 void CompositeEditCommand::pushPartiallySelectedAnchorElementsDown()
723 {
724 VisibleSelection originalSelection = endingSelection();
725 VisiblePosition visibleStart(originalSelection.start());
726 VisiblePosition visibleEnd(originalSelection.end());
727
728 Node* startAnchor = enclosingAnchorElement(originalSelection.start());
729 VisiblePosition startOfStartAnchor(Position(startAnchor, 0));
730 if (startAnchor && startOfStartAnchor != visibleStart)
731 pushAnchorElementDown(startAnchor);
732
733 Node* endAnchor = enclosingAnchorElement(originalSelection.end());
734 VisiblePosition endOfEndAnchor(Position(endAnchor, 0));
735 if (endAnchor && endOfEndAnchor != visibleEnd)
736 pushAnchorElementDown(endAnchor);
737
738 ASSERT(originalSelection.start().node()->inDocument() && originalSelection.end().node()->inDocument());
739 setEndingSelection(originalSelection);
740 }
741
742 // Clone the paragraph between start and end under blockElement,
743 // preserving the hierarchy up to outerNode.
744
cloneParagraphUnderNewElement(Position & start,Position & end,Node * outerNode,Element * blockElement)745 void CompositeEditCommand::cloneParagraphUnderNewElement(Position& start, Position& end, Node* outerNode, Element* blockElement)
746 {
747 // First we clone the outerNode
748
749 RefPtr<Node> topNode = outerNode->cloneNode(isTableElement(outerNode));
750 appendNode(topNode, blockElement);
751 RefPtr<Node> lastNode = topNode;
752
753 if (start.node() != outerNode) {
754 Vector<RefPtr<Node> > ancestors;
755
756 // Insert each node from innerNode to outerNode (excluded) in a list.
757 for (Node* n = start.node(); n && n != outerNode; n = n->parentNode())
758 ancestors.append(n);
759
760 // Clone every node between start.node() and outerBlock.
761
762 for (size_t i = ancestors.size(); i != 0; --i) {
763 Node* item = ancestors[i - 1].get();
764 RefPtr<Node> child = item->cloneNode(isTableElement(item));
765 appendNode(child, static_cast<Element *>(lastNode.get()));
766 lastNode = child.release();
767 }
768 }
769
770 // Handle the case of paragraphs with more than one node,
771 // cloning all the siblings until end.node() is reached.
772
773 if (start.node() != end.node() && !start.node()->isDescendantOf(end.node())) {
774 // If end is not a descendant of outerNode we need to
775 // find the first common ancestor and adjust the insertion
776 // point accordingly.
777 while (!end.node()->isDescendantOf(outerNode)) {
778 outerNode = outerNode->parentNode();
779 topNode = topNode->parentNode();
780 }
781
782 for (Node* n = start.node()->traverseNextSibling(outerNode); n; n = n->nextSibling()) {
783 if (n->parentNode() != start.node()->parentNode())
784 lastNode = topNode->lastChild();
785
786 RefPtr<Node> clonedNode = n->cloneNode(true);
787 insertNodeAfter(clonedNode, lastNode);
788 lastNode = clonedNode.release();
789 if (n == end.node() || end.node()->isDescendantOf(n))
790 break;
791 }
792 }
793 }
794
795
796 // There are bugs in deletion when it removes a fully selected table/list.
797 // It expands and removes the entire table/list, but will let content
798 // before and after the table/list collapse onto one line.
799 // Deleting a paragraph will leave a placeholder. Remove it (and prune
800 // empty or unrendered parents).
801
cleanupAfterDeletion()802 void CompositeEditCommand::cleanupAfterDeletion()
803 {
804 VisiblePosition caretAfterDelete = endingSelection().visibleStart();
805 if (isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) {
806 // Note: We want the rightmost candidate.
807 Position position = caretAfterDelete.deepEquivalent().downstream();
808 Node* node = position.node();
809 // Normally deletion will leave a br as a placeholder.
810 if (node->hasTagName(brTag))
811 removeNodeAndPruneAncestors(node);
812 // If the selection to move was empty and in an empty block that
813 // doesn't require a placeholder to prop itself open (like a bordered
814 // div or an li), remove it during the move (the list removal code
815 // expects this behavior).
816 else if (isBlock(node))
817 removeNodeAndPruneAncestors(node);
818 else if (lineBreakExistsAtPosition(position)) {
819 // There is a preserved '\n' at caretAfterDelete.
820 // We can safely assume this is a text node.
821 Text* textNode = static_cast<Text*>(node);
822 if (textNode->length() == 1)
823 removeNodeAndPruneAncestors(node);
824 else
825 deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1);
826 }
827 }
828 }
829
830 // This is a version of moveParagraph that preserves style by keeping the original markup
831 // It is currently used only by IndentOutdentCommand but it is meant to be used in the
832 // future by several other commands such as InsertList and the align commands.
833 // The blockElement parameter is the element to move the paragraph to,
834 // outerNode is the top element of the paragraph hierarchy.
835
moveParagraphWithClones(const VisiblePosition & startOfParagraphToMove,const VisiblePosition & endOfParagraphToMove,Element * blockElement,Node * outerNode)836 void CompositeEditCommand::moveParagraphWithClones(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, Element* blockElement, Node* outerNode)
837 {
838 ASSERT(outerNode);
839 ASSERT(blockElement);
840
841 VisiblePosition beforeParagraph = startOfParagraphToMove.previous();
842 VisiblePosition afterParagraph(endOfParagraphToMove.next());
843
844 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move.
845 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered.
846 Position start = startOfParagraphToMove.deepEquivalent().downstream();
847 Position end = endOfParagraphToMove.deepEquivalent().upstream();
848
849 cloneParagraphUnderNewElement(start, end, outerNode, blockElement);
850
851 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM));
852 deleteSelection(false, false, false, false);
853
854 // There are bugs in deletion when it removes a fully selected table/list.
855 // It expands and removes the entire table/list, but will let content
856 // before and after the table/list collapse onto one line.
857
858 cleanupAfterDeletion();
859
860 // Add a br if pruning an empty block level element caused a collapse. For example:
861 // foo^
862 // <div>bar</div>
863 // baz
864 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would
865 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br.
866 // Must recononicalize these two VisiblePositions after the pruning above.
867 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent());
868 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent());
869
870 if (beforeParagraph.isNotNull() && !isTableElement(beforeParagraph.deepEquivalent().node()) && (!isEndOfParagraph(beforeParagraph) || beforeParagraph == afterParagraph)) {
871 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal.
872 insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent());
873 }
874 }
875
876
877 // This moves a paragraph preserving its style.
moveParagraph(const VisiblePosition & startOfParagraphToMove,const VisiblePosition & endOfParagraphToMove,const VisiblePosition & destination,bool preserveSelection,bool preserveStyle)878 void CompositeEditCommand::moveParagraph(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle)
879 {
880 ASSERT(isStartOfParagraph(startOfParagraphToMove));
881 ASSERT(isEndOfParagraph(endOfParagraphToMove));
882 moveParagraphs(startOfParagraphToMove, endOfParagraphToMove, destination, preserveSelection, preserveStyle);
883 }
884
moveParagraphs(const VisiblePosition & startOfParagraphToMove,const VisiblePosition & endOfParagraphToMove,const VisiblePosition & destination,bool preserveSelection,bool preserveStyle)885 void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle)
886 {
887 if (startOfParagraphToMove == destination)
888 return;
889
890 int startIndex = -1;
891 int endIndex = -1;
892 int destinationIndex = -1;
893 if (preserveSelection && !endingSelection().isNone()) {
894 VisiblePosition visibleStart = endingSelection().visibleStart();
895 VisiblePosition visibleEnd = endingSelection().visibleEnd();
896
897 bool startAfterParagraph = comparePositions(visibleStart, endOfParagraphToMove) > 0;
898 bool endBeforeParagraph = comparePositions(visibleEnd, startOfParagraphToMove) < 0;
899
900 if (!startAfterParagraph && !endBeforeParagraph) {
901 bool startInParagraph = comparePositions(visibleStart, startOfParagraphToMove) >= 0;
902 bool endInParagraph = comparePositions(visibleEnd, endOfParagraphToMove) <= 0;
903
904 startIndex = 0;
905 if (startInParagraph) {
906 RefPtr<Range> startRange = Range::create(document(), rangeCompliantEquivalent(startOfParagraphToMove.deepEquivalent()), rangeCompliantEquivalent(visibleStart.deepEquivalent()));
907 startIndex = TextIterator::rangeLength(startRange.get(), true);
908 }
909
910 endIndex = 0;
911 if (endInParagraph) {
912 RefPtr<Range> endRange = Range::create(document(), rangeCompliantEquivalent(startOfParagraphToMove.deepEquivalent()), rangeCompliantEquivalent(visibleEnd.deepEquivalent()));
913 endIndex = TextIterator::rangeLength(endRange.get(), true);
914 }
915 }
916 }
917
918 VisiblePosition beforeParagraph = startOfParagraphToMove.previous();
919 VisiblePosition afterParagraph(endOfParagraphToMove.next());
920
921 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move.
922 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered.
923 Position start = startOfParagraphToMove.deepEquivalent().downstream();
924 Position end = endOfParagraphToMove.deepEquivalent().upstream();
925
926 // start and end can't be used directly to create a Range; they are "editing positions"
927 Position startRangeCompliant = rangeCompliantEquivalent(start);
928 Position endRangeCompliant = rangeCompliantEquivalent(end);
929 RefPtr<Range> range = Range::create(document(), startRangeCompliant.node(), startRangeCompliant.deprecatedEditingOffset(), endRangeCompliant.node(), endRangeCompliant.deprecatedEditingOffset());
930
931 // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It
932 // shouldn't matter though, since moved paragraphs will usually be quite small.
933 RefPtr<DocumentFragment> fragment = startOfParagraphToMove != endOfParagraphToMove ? createFragmentFromMarkup(document(), createMarkup(range.get(), 0, DoNotAnnotateForInterchange, true), "") : 0;
934
935 // A non-empty paragraph's style is moved when we copy and move it. We don't move
936 // anything if we're given an empty paragraph, but an empty paragraph can have style
937 // too, <div><b><br></b></div> for example. Save it so that we can preserve it later.
938 RefPtr<CSSMutableStyleDeclaration> styleInEmptyParagraph;
939 if (startOfParagraphToMove == endOfParagraphToMove && preserveStyle) {
940 styleInEmptyParagraph = editingStyleAtPosition(startOfParagraphToMove.deepEquivalent(), IncludeTypingStyle);
941 // The moved paragraph should assume the block style of the destination.
942 styleInEmptyParagraph->removeBlockProperties();
943 }
944
945 // FIXME (5098931): We should add a new insert action "WebViewInsertActionMoved" and call shouldInsertFragment here.
946
947 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM));
948 deleteSelection(false, false, false, false);
949
950 ASSERT(destination.deepEquivalent().node()->inDocument());
951
952 cleanupAfterDeletion();
953
954 // Add a br if pruning an empty block level element caused a collapse. For example:
955 // foo^
956 // <div>bar</div>
957 // baz
958 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would
959 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br.
960 // Must recononicalize these two VisiblePositions after the pruning above.
961 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent());
962 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent());
963 if (beforeParagraph.isNotNull() && (!isEndOfParagraph(beforeParagraph) || beforeParagraph == afterParagraph)) {
964 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal.
965 insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent());
966 // Need an updateLayout here in case inserting the br has split a text node.
967 updateLayout();
968 }
969
970 RefPtr<Range> startToDestinationRange(Range::create(document(), Position(document(), 0), rangeCompliantEquivalent(destination.deepEquivalent())));
971 destinationIndex = TextIterator::rangeLength(startToDestinationRange.get(), true);
972
973 setEndingSelection(destination);
974 applyCommandToComposite(ReplaceSelectionCommand::create(document(), fragment, true, false, !preserveStyle, false, true));
975
976 // If the selection is in an empty paragraph, restore styles from the old empty paragraph to the new empty paragraph.
977 bool selectionIsEmptyParagraph = endingSelection().isCaret() && isStartOfParagraph(endingSelection().visibleStart()) && isEndOfParagraph(endingSelection().visibleStart());
978 if (styleInEmptyParagraph && selectionIsEmptyParagraph)
979 applyStyle(styleInEmptyParagraph.get());
980
981 if (preserveSelection && startIndex != -1) {
982 // Fragment creation (using createMarkup) incorrectly uses regular
983 // spaces instead of nbsps for some spaces that were rendered (11475), which
984 // causes spaces to be collapsed during the move operation. This results
985 // in a call to rangeFromLocationAndLength with a location past the end
986 // of the document (which will return null).
987 RefPtr<Range> start = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + startIndex, 0, true);
988 RefPtr<Range> end = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + endIndex, 0, true);
989 if (start && end)
990 setEndingSelection(VisibleSelection(start->startPosition(), end->startPosition(), DOWNSTREAM));
991 }
992 }
993
994 // FIXME: Send an appropriate shouldDeleteRange call.
breakOutOfEmptyListItem()995 bool CompositeEditCommand::breakOutOfEmptyListItem()
996 {
997 Node* emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart());
998 if (!emptyListItem)
999 return false;
1000
1001 RefPtr<CSSMutableStyleDeclaration> style = editingStyleAtPosition(endingSelection().start(), IncludeTypingStyle);
1002
1003 Node* listNode = emptyListItem->parentNode();
1004 // FIXME: Can't we do something better when the immediate parent wasn't a list node?
1005 if (!listNode
1006 || (!listNode->hasTagName(ulTag) && !listNode->hasTagName(olTag))
1007 || !listNode->isContentEditable())
1008 return false;
1009
1010 RefPtr<Element> newBlock = 0;
1011 if (Node* blockEnclosingList = listNode->parentNode()) {
1012 if (blockEnclosingList->hasTagName(liTag)) { // listNode is inside another list item
1013 if (visiblePositionAfterNode(blockEnclosingList) == visiblePositionAfterNode(listNode)) {
1014 // If listNode appears at the end of the outer list item, then move listNode outside of this list item
1015 // e.g. <ul><li>hello <ul><li><br></li></ul> </li></ul> should become <ul><li>hello</li> <ul><li><br></li></ul> </ul> after this section
1016 // If listNode does NOT appear at the end, then we should consider it as a regular paragraph.
1017 // e.g. <ul><li> <ul><li><br></li></ul> hello</li></ul> should become <ul><li> <div><br></div> hello</li></ul> at the end
1018 splitElement(static_cast<Element*>(blockEnclosingList), listNode);
1019 removeNodePreservingChildren(listNode->parentNode());
1020 newBlock = createListItemElement(document());
1021 }
1022 // If listNode does NOT appear at the end of the outer list item, then behave as if in a regular paragraph.
1023 } else if (blockEnclosingList->hasTagName(olTag) || blockEnclosingList->hasTagName(ulTag))
1024 newBlock = createListItemElement(document());
1025 }
1026 if (!newBlock)
1027 newBlock = createDefaultParagraphElement(document());
1028
1029 if (emptyListItem->renderer()->nextSibling()) {
1030 // If emptyListItem follows another list item, split the list node.
1031 if (emptyListItem->renderer()->previousSibling())
1032 splitElement(static_cast<Element*>(listNode), emptyListItem);
1033
1034 // If emptyListItem is followed by other list item, then insert newBlock before the list node.
1035 // Because we have splitted the element, emptyListItem is the first element in the list node.
1036 // i.e. insert newBlock before ul or ol whose first element is emptyListItem
1037 insertNodeBefore(newBlock, listNode);
1038 removeNode(emptyListItem);
1039 } else {
1040 // When emptyListItem does not follow any list item, insert newBlock after the enclosing list node.
1041 // Remove the enclosing node if emptyListItem is the only child; otherwise just remove emptyListItem.
1042 insertNodeAfter(newBlock, listNode);
1043 removeNode(emptyListItem->renderer()->previousSibling() ? emptyListItem : listNode);
1044 }
1045
1046 appendBlockPlaceholder(newBlock);
1047 setEndingSelection(VisibleSelection(Position(newBlock.get(), 0), DOWNSTREAM));
1048
1049 prepareEditingStyleToApplyAt(style.get(), endingSelection().start());
1050 if (style->length())
1051 applyStyle(style.get());
1052
1053 return true;
1054 }
1055
1056 // If the caret is in an empty quoted paragraph, and either there is nothing before that
1057 // paragraph, or what is before is unquoted, and the user presses delete, unquote that paragraph.
breakOutOfEmptyMailBlockquotedParagraph()1058 bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph()
1059 {
1060 if (!endingSelection().isCaret())
1061 return false;
1062
1063 VisiblePosition caret(endingSelection().visibleStart());
1064 Node* highestBlockquote = highestEnclosingNodeOfType(caret.deepEquivalent(), &isMailBlockquote);
1065 if (!highestBlockquote)
1066 return false;
1067
1068 if (!isStartOfParagraph(caret) || !isEndOfParagraph(caret))
1069 return false;
1070
1071 VisiblePosition previous(caret.previous(true));
1072 // Only move forward if there's nothing before the caret, or if there's unquoted content before it.
1073 if (enclosingNodeOfType(previous.deepEquivalent(), &isMailBlockquote))
1074 return false;
1075
1076 RefPtr<Node> br = createBreakElement(document());
1077 // We want to replace this quoted paragraph with an unquoted one, so insert a br
1078 // to hold the caret before the highest blockquote.
1079 insertNodeBefore(br, highestBlockquote);
1080 VisiblePosition atBR(Position(br.get(), 0));
1081 // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert
1082 // a second one.
1083 if (!isStartOfParagraph(atBR))
1084 insertNodeBefore(createBreakElement(document()), br);
1085 setEndingSelection(VisibleSelection(atBR));
1086
1087 // If this is an empty paragraph there must be a line break here.
1088 if (!lineBreakExistsAtVisiblePosition(caret))
1089 return false;
1090
1091 Position caretPos(caret.deepEquivalent());
1092 // A line break is either a br or a preserved newline.
1093 ASSERT(caretPos.node()->hasTagName(brTag) || (caretPos.node()->isTextNode() && caretPos.node()->renderer()->style()->preserveNewline()));
1094
1095 if (caretPos.node()->hasTagName(brTag)) {
1096 Position beforeBR(positionInParentBeforeNode(caretPos.node()));
1097 removeNode(caretPos.node());
1098 prune(beforeBR.node());
1099 } else {
1100 ASSERT(caretPos.deprecatedEditingOffset() == 0);
1101 Text* textNode = static_cast<Text*>(caretPos.node());
1102 Node* parentNode = textNode->parentNode();
1103 // The preserved newline must be the first thing in the node, since otherwise the previous
1104 // paragraph would be quoted, and we verified that it wasn't above.
1105 deleteTextFromNode(textNode, 0, 1);
1106 prune(parentNode);
1107 }
1108
1109 return true;
1110 }
1111
1112 // Operations use this function to avoid inserting content into an anchor when at the start or the end of
1113 // that anchor, as in NSTextView.
1114 // FIXME: This is only an approximation of NSTextViews insertion behavior, which varies depending on how
1115 // the caret was made.
positionAvoidingSpecialElementBoundary(const Position & original)1116 Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Position& original)
1117 {
1118 if (original.isNull())
1119 return original;
1120
1121 VisiblePosition visiblePos(original);
1122 Node* enclosingAnchor = enclosingAnchorElement(original);
1123 Position result = original;
1124
1125 if (!enclosingAnchor)
1126 return result;
1127
1128 // Don't avoid block level anchors, because that would insert content into the wrong paragraph.
1129 if (enclosingAnchor && !isBlock(enclosingAnchor)) {
1130 VisiblePosition firstInAnchor(firstDeepEditingPositionForNode(enclosingAnchor));
1131 VisiblePosition lastInAnchor(lastDeepEditingPositionForNode(enclosingAnchor));
1132 // If visually just after the anchor, insert *inside* the anchor unless it's the last
1133 // VisiblePosition in the document, to match NSTextView.
1134 if (visiblePos == lastInAnchor) {
1135 // Make sure anchors are pushed down before avoiding them so that we don't
1136 // also avoid structural elements like lists and blocks (5142012).
1137 if (original.node() != enclosingAnchor && original.node()->parentNode() != enclosingAnchor) {
1138 pushAnchorElementDown(enclosingAnchor);
1139 enclosingAnchor = enclosingAnchorElement(original);
1140 if (!enclosingAnchor)
1141 return original;
1142 }
1143 // Don't insert outside an anchor if doing so would skip over a line break. It would
1144 // probably be safe to move the line break so that we could still avoid the anchor here.
1145 Position downstream(visiblePos.deepEquivalent().downstream());
1146 if (lineBreakExistsAtVisiblePosition(visiblePos) && downstream.node()->isDescendantOf(enclosingAnchor))
1147 return original;
1148
1149 result = positionInParentAfterNode(enclosingAnchor);
1150 }
1151 // If visually just before an anchor, insert *outside* the anchor unless it's the first
1152 // VisiblePosition in a paragraph, to match NSTextView.
1153 if (visiblePos == firstInAnchor) {
1154 // Make sure anchors are pushed down before avoiding them so that we don't
1155 // also avoid structural elements like lists and blocks (5142012).
1156 if (original.node() != enclosingAnchor && original.node()->parentNode() != enclosingAnchor) {
1157 pushAnchorElementDown(enclosingAnchor);
1158 enclosingAnchor = enclosingAnchorElement(original);
1159 }
1160 if (!enclosingAnchor)
1161 return original;
1162
1163 result = positionInParentBeforeNode(enclosingAnchor);
1164 }
1165 }
1166
1167 if (result.isNull() || !editableRootForPosition(result))
1168 result = original;
1169
1170 return result;
1171 }
1172
1173 // Splits the tree parent by parent until we reach the specified ancestor. We use VisiblePositions
1174 // to determine if the split is necessary. Returns the last split node.
splitTreeToNode(Node * start,Node * end,bool splitAncestor)1175 PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool splitAncestor)
1176 {
1177 ASSERT(start != end);
1178
1179 RefPtr<Node> node;
1180 for (node = start; node && node->parent() != end; node = node->parent()) {
1181 VisiblePosition positionInParent(Position(node->parent(), 0), DOWNSTREAM);
1182 VisiblePosition positionInNode(Position(node, 0), DOWNSTREAM);
1183 if (positionInParent != positionInNode)
1184 applyCommandToComposite(SplitElementCommand::create(static_cast<Element*>(node->parent()), node));
1185 }
1186 if (splitAncestor) {
1187 splitElement(static_cast<Element*>(end), node);
1188 return node->parent();
1189 }
1190 return node.release();
1191 }
1192
createBlockPlaceholderElement(Document * document)1193 PassRefPtr<Element> createBlockPlaceholderElement(Document* document)
1194 {
1195 RefPtr<Element> breakNode = document->createElement(brTag, false);
1196 return breakNode.release();
1197 }
1198
1199 } // namespace WebCore
1200