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 positionBeforeNode(tabSpan);
347
348 if (pos.deprecatedEditingOffset() >= caretMaxOffset(pos.node()))
349 return positionAfterNode(tabSpan);
350
351 splitTextNodeContainingElement(static_cast<Text *>(pos.node()), pos.deprecatedEditingOffset());
352 return positionBeforeNode(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->string()->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 // This moves a paragraph preserving its style.
moveParagraph(const VisiblePosition & startOfParagraphToMove,const VisiblePosition & endOfParagraphToMove,const VisiblePosition & destination,bool preserveSelection,bool preserveStyle)743 void CompositeEditCommand::moveParagraph(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle)
744 {
745 ASSERT(isStartOfParagraph(startOfParagraphToMove));
746 ASSERT(isEndOfParagraph(endOfParagraphToMove));
747 moveParagraphs(startOfParagraphToMove, endOfParagraphToMove, destination, preserveSelection, preserveStyle);
748 }
749
moveParagraphs(const VisiblePosition & startOfParagraphToMove,const VisiblePosition & endOfParagraphToMove,const VisiblePosition & destination,bool preserveSelection,bool preserveStyle)750 void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle)
751 {
752 if (startOfParagraphToMove == destination)
753 return;
754
755 int startIndex = -1;
756 int endIndex = -1;
757 int destinationIndex = -1;
758 if (preserveSelection && !endingSelection().isNone()) {
759 VisiblePosition visibleStart = endingSelection().visibleStart();
760 VisiblePosition visibleEnd = endingSelection().visibleEnd();
761
762 bool startAfterParagraph = comparePositions(visibleStart, endOfParagraphToMove) > 0;
763 bool endBeforeParagraph = comparePositions(visibleEnd, startOfParagraphToMove) < 0;
764
765 if (!startAfterParagraph && !endBeforeParagraph) {
766 bool startInParagraph = comparePositions(visibleStart, startOfParagraphToMove) >= 0;
767 bool endInParagraph = comparePositions(visibleEnd, endOfParagraphToMove) <= 0;
768
769 startIndex = 0;
770 if (startInParagraph) {
771 RefPtr<Range> startRange = Range::create(document(), rangeCompliantEquivalent(startOfParagraphToMove.deepEquivalent()), rangeCompliantEquivalent(visibleStart.deepEquivalent()));
772 startIndex = TextIterator::rangeLength(startRange.get(), true);
773 }
774
775 endIndex = 0;
776 if (endInParagraph) {
777 RefPtr<Range> endRange = Range::create(document(), rangeCompliantEquivalent(startOfParagraphToMove.deepEquivalent()), rangeCompliantEquivalent(visibleEnd.deepEquivalent()));
778 endIndex = TextIterator::rangeLength(endRange.get(), true);
779 }
780 }
781 }
782
783 VisiblePosition beforeParagraph = startOfParagraphToMove.previous();
784 VisiblePosition afterParagraph(endOfParagraphToMove.next());
785
786 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move.
787 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered.
788 Position start = startOfParagraphToMove.deepEquivalent().downstream();
789 Position end = endOfParagraphToMove.deepEquivalent().upstream();
790
791 // start and end can't be used directly to create a Range; they are "editing positions"
792 Position startRangeCompliant = rangeCompliantEquivalent(start);
793 Position endRangeCompliant = rangeCompliantEquivalent(end);
794 RefPtr<Range> range = Range::create(document(), startRangeCompliant.node(), startRangeCompliant.deprecatedEditingOffset(), endRangeCompliant.node(), endRangeCompliant.deprecatedEditingOffset());
795
796 // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It
797 // shouldn't matter though, since moved paragraphs will usually be quite small.
798 RefPtr<DocumentFragment> fragment = startOfParagraphToMove != endOfParagraphToMove ? createFragmentFromMarkup(document(), createMarkup(range.get(), 0, DoNotAnnotateForInterchange, true), "") : 0;
799
800 // A non-empty paragraph's style is moved when we copy and move it. We don't move
801 // anything if we're given an empty paragraph, but an empty paragraph can have style
802 // too, <div><b><br></b></div> for example. Save it so that we can preserve it later.
803 RefPtr<CSSMutableStyleDeclaration> styleInEmptyParagraph;
804 if (startOfParagraphToMove == endOfParagraphToMove && preserveStyle) {
805 styleInEmptyParagraph = editingStyleAtPosition(startOfParagraphToMove.deepEquivalent(), IncludeTypingStyle);
806 // The moved paragraph should assume the block style of the destination.
807 styleInEmptyParagraph->removeBlockProperties();
808 }
809
810 // FIXME (5098931): We should add a new insert action "WebViewInsertActionMoved" and call shouldInsertFragment here.
811
812 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM));
813 deleteSelection(false, false, false, false);
814
815 ASSERT(destination.deepEquivalent().node()->inDocument());
816
817 // There are bugs in deletion when it removes a fully selected table/list.
818 // It expands and removes the entire table/list, but will let content
819 // before and after the table/list collapse onto one line.
820
821 // Deleting a paragraph will leave a placeholder. Remove it (and prune
822 // empty or unrendered parents).
823 VisiblePosition caretAfterDelete = endingSelection().visibleStart();
824 if (isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) {
825 // Note: We want the rightmost candidate.
826 Position position = caretAfterDelete.deepEquivalent().downstream();
827 Node* node = position.node();
828 // Normally deletion will leave a br as a placeholder.
829 if (node->hasTagName(brTag))
830 removeNodeAndPruneAncestors(node);
831 // If the selection to move was empty and in an empty block that
832 // doesn't require a placeholder to prop itself open (like a bordered
833 // div or an li), remove it during the move (the list removal code
834 // expects this behavior).
835 else if (isBlock(node))
836 removeNodeAndPruneAncestors(node);
837 else if (lineBreakExistsAtVisiblePosition(caretAfterDelete)) {
838 // There is a preserved '\n' at caretAfterDelete.
839 Text* textNode = static_cast<Text*>(node);
840 if (textNode->length() == 1)
841 removeNodeAndPruneAncestors(node);
842 else
843 deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1);
844 }
845 }
846
847 // Add a br if pruning an empty block level element caused a collapse. For example:
848 // foo^
849 // <div>bar</div>
850 // baz
851 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would
852 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br.
853 // Must recononicalize these two VisiblePositions after the pruning above.
854 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent());
855 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent());
856 if (beforeParagraph.isNotNull() && (!isEndOfParagraph(beforeParagraph) || beforeParagraph == afterParagraph)) {
857 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal.
858 insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent());
859 // Need an updateLayout here in case inserting the br has split a text node.
860 updateLayout();
861 }
862
863 RefPtr<Range> startToDestinationRange(Range::create(document(), Position(document(), 0), rangeCompliantEquivalent(destination.deepEquivalent())));
864 destinationIndex = TextIterator::rangeLength(startToDestinationRange.get(), true);
865
866 setEndingSelection(destination);
867 applyCommandToComposite(ReplaceSelectionCommand::create(document(), fragment, true, false, !preserveStyle, false, true));
868
869 // If the selection is in an empty paragraph, restore styles from the old empty paragraph to the new empty paragraph.
870 bool selectionIsEmptyParagraph = endingSelection().isCaret() && isStartOfParagraph(endingSelection().visibleStart()) && isEndOfParagraph(endingSelection().visibleStart());
871 if (styleInEmptyParagraph && selectionIsEmptyParagraph)
872 applyStyle(styleInEmptyParagraph.get());
873
874 if (preserveSelection && startIndex != -1) {
875 // Fragment creation (using createMarkup) incorrectly uses regular
876 // spaces instead of nbsps for some spaces that were rendered (11475), which
877 // causes spaces to be collapsed during the move operation. This results
878 // in a call to rangeFromLocationAndLength with a location past the end
879 // of the document (which will return null).
880 RefPtr<Range> start = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + startIndex, 0, true);
881 RefPtr<Range> end = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + endIndex, 0, true);
882 if (start && end)
883 setEndingSelection(VisibleSelection(start->startPosition(), end->startPosition(), DOWNSTREAM));
884 }
885 }
886
887 // FIXME: Send an appropriate shouldDeleteRange call.
breakOutOfEmptyListItem()888 bool CompositeEditCommand::breakOutOfEmptyListItem()
889 {
890 Node* emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart());
891 if (!emptyListItem)
892 return false;
893
894 RefPtr<CSSMutableStyleDeclaration> style = editingStyleAtPosition(endingSelection().start(), IncludeTypingStyle);
895
896 Node* listNode = emptyListItem->parentNode();
897
898 if (!listNode->isContentEditable())
899 return false;
900
901 RefPtr<Element> newBlock = isListElement(listNode->parentNode()) ? createListItemElement(document()) : createDefaultParagraphElement(document());
902
903 if (emptyListItem->renderer()->nextSibling()) {
904 if (emptyListItem->renderer()->previousSibling())
905 splitElement(static_cast<Element*>(listNode), emptyListItem);
906 insertNodeBefore(newBlock, listNode);
907 removeNode(emptyListItem);
908 } else {
909 insertNodeAfter(newBlock, listNode);
910 removeNode(emptyListItem->renderer()->previousSibling() ? emptyListItem : listNode);
911 }
912
913 appendBlockPlaceholder(newBlock);
914 setEndingSelection(VisibleSelection(Position(newBlock.get(), 0), DOWNSTREAM));
915
916 computedStyle(endingSelection().start().node())->diff(style.get());
917 if (style->length() > 0)
918 applyStyle(style.get());
919
920 return true;
921 }
922
923 // If the caret is in an empty quoted paragraph, and either there is nothing before that
924 // paragraph, or what is before is unquoted, and the user presses delete, unquote that paragraph.
breakOutOfEmptyMailBlockquotedParagraph()925 bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph()
926 {
927 if (!endingSelection().isCaret())
928 return false;
929
930 VisiblePosition caret(endingSelection().visibleStart());
931 Node* highestBlockquote = highestEnclosingNodeOfType(caret.deepEquivalent(), &isMailBlockquote);
932 if (!highestBlockquote)
933 return false;
934
935 if (!isStartOfParagraph(caret) || !isEndOfParagraph(caret))
936 return false;
937
938 VisiblePosition previous(caret.previous(true));
939 // Only move forward if there's nothing before the caret, or if there's unquoted content before it.
940 if (enclosingNodeOfType(previous.deepEquivalent(), &isMailBlockquote))
941 return false;
942
943 RefPtr<Node> br = createBreakElement(document());
944 // We want to replace this quoted paragraph with an unquoted one, so insert a br
945 // to hold the caret before the highest blockquote.
946 insertNodeBefore(br, highestBlockquote);
947 VisiblePosition atBR(Position(br.get(), 0));
948 // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert
949 // a second one.
950 if (!isStartOfParagraph(atBR))
951 insertNodeBefore(createBreakElement(document()), br);
952 setEndingSelection(VisibleSelection(atBR));
953
954 // If this is an empty paragraph there must be a line break here.
955 if (!lineBreakExistsAtVisiblePosition(caret))
956 return false;
957
958 Position caretPos(caret.deepEquivalent());
959 // A line break is either a br or a preserved newline.
960 ASSERT(caretPos.node()->hasTagName(brTag) || caretPos.node()->isTextNode() && caretPos.node()->renderer()->style()->preserveNewline());
961
962 if (caretPos.node()->hasTagName(brTag)) {
963 Position beforeBR(positionBeforeNode(caretPos.node()));
964 removeNode(caretPos.node());
965 prune(beforeBR.node());
966 } else {
967 ASSERT(caretPos.deprecatedEditingOffset() == 0);
968 Text* textNode = static_cast<Text*>(caretPos.node());
969 Node* parentNode = textNode->parentNode();
970 // The preserved newline must be the first thing in the node, since otherwise the previous
971 // paragraph would be quoted, and we verified that it wasn't above.
972 deleteTextFromNode(textNode, 0, 1);
973 prune(parentNode);
974 }
975
976 return true;
977 }
978
979 // Operations use this function to avoid inserting content into an anchor when at the start or the end of
980 // that anchor, as in NSTextView.
981 // FIXME: This is only an approximation of NSTextViews insertion behavior, which varies depending on how
982 // the caret was made.
positionAvoidingSpecialElementBoundary(const Position & original)983 Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Position& original)
984 {
985 if (original.isNull())
986 return original;
987
988 VisiblePosition visiblePos(original);
989 Node* enclosingAnchor = enclosingAnchorElement(original);
990 Position result = original;
991
992 if (!enclosingAnchor)
993 return result;
994
995 // Don't avoid block level anchors, because that would insert content into the wrong paragraph.
996 if (enclosingAnchor && !isBlock(enclosingAnchor)) {
997 VisiblePosition firstInAnchor(firstDeepEditingPositionForNode(enclosingAnchor));
998 VisiblePosition lastInAnchor(lastDeepEditingPositionForNode(enclosingAnchor));
999 // If visually just after the anchor, insert *inside* the anchor unless it's the last
1000 // VisiblePosition in the document, to match NSTextView.
1001 if (visiblePos == lastInAnchor) {
1002 // Make sure anchors are pushed down before avoiding them so that we don't
1003 // also avoid structural elements like lists and blocks (5142012).
1004 if (original.node() != enclosingAnchor && original.node()->parentNode() != enclosingAnchor) {
1005 pushAnchorElementDown(enclosingAnchor);
1006 enclosingAnchor = enclosingAnchorElement(original);
1007 if (!enclosingAnchor)
1008 return original;
1009 }
1010 // Don't insert outside an anchor if doing so would skip over a line break. It would
1011 // probably be safe to move the line break so that we could still avoid the anchor here.
1012 Position downstream(visiblePos.deepEquivalent().downstream());
1013 if (lineBreakExistsAtVisiblePosition(visiblePos) && downstream.node()->isDescendantOf(enclosingAnchor))
1014 return original;
1015
1016 result = positionAfterNode(enclosingAnchor);
1017 }
1018 // If visually just before an anchor, insert *outside* the anchor unless it's the first
1019 // VisiblePosition in a paragraph, to match NSTextView.
1020 if (visiblePos == firstInAnchor) {
1021 // Make sure anchors are pushed down before avoiding them so that we don't
1022 // also avoid structural elements like lists and blocks (5142012).
1023 if (original.node() != enclosingAnchor && original.node()->parentNode() != enclosingAnchor) {
1024 pushAnchorElementDown(enclosingAnchor);
1025 enclosingAnchor = enclosingAnchorElement(original);
1026 }
1027 if (!enclosingAnchor)
1028 return original;
1029
1030 result = positionBeforeNode(enclosingAnchor);
1031 }
1032 }
1033
1034 if (result.isNull() || !editableRootForPosition(result))
1035 result = original;
1036
1037 return result;
1038 }
1039
1040 // Splits the tree parent by parent until we reach the specified ancestor. We use VisiblePositions
1041 // to determine if the split is necessary. Returns the last split node.
splitTreeToNode(Node * start,Node * end,bool splitAncestor)1042 PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool splitAncestor)
1043 {
1044 ASSERT(start != end);
1045
1046 RefPtr<Node> node;
1047 for (node = start; node && node->parent() != end; node = node->parent()) {
1048 VisiblePosition positionInParent(Position(node->parent(), 0), DOWNSTREAM);
1049 VisiblePosition positionInNode(Position(node, 0), DOWNSTREAM);
1050 if (positionInParent != positionInNode)
1051 applyCommandToComposite(SplitElementCommand::create(static_cast<Element*>(node->parent()), node));
1052 }
1053 if (splitAncestor) {
1054 splitElement(static_cast<Element*>(end), node);
1055 return node->parent();
1056 }
1057 return node.release();
1058 }
1059
createBlockPlaceholderElement(Document * document)1060 PassRefPtr<Element> createBlockPlaceholderElement(Document* document)
1061 {
1062 RefPtr<Element> breakNode = document->createElement(brTag, false);
1063 return breakNode.release();
1064 }
1065
1066 } // namespace WebCore
1067