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 "core/editing/CompositeEditCommand.h"
28
29 #include "HTMLNames.h"
30 #include "bindings/v8/ExceptionStatePlaceholder.h"
31 #include "core/dom/Document.h"
32 #include "core/dom/DocumentFragment.h"
33 #include "core/dom/DocumentMarkerController.h"
34 #include "core/dom/NodeTraversal.h"
35 #include "core/dom/Range.h"
36 #include "core/events/ScopedEventQueue.h"
37 #include "core/dom/Text.h"
38 #include "core/editing/AppendNodeCommand.h"
39 #include "core/editing/ApplyStyleCommand.h"
40 #include "core/editing/DeleteFromTextNodeCommand.h"
41 #include "core/editing/DeleteSelectionCommand.h"
42 #include "core/editing/Editor.h"
43 #include "core/editing/InsertIntoTextNodeCommand.h"
44 #include "core/editing/InsertLineBreakCommand.h"
45 #include "core/editing/InsertNodeBeforeCommand.h"
46 #include "core/editing/InsertParagraphSeparatorCommand.h"
47 #include "core/editing/MergeIdenticalElementsCommand.h"
48 #include "core/editing/PlainTextRange.h"
49 #include "core/editing/RemoveCSSPropertyCommand.h"
50 #include "core/editing/RemoveNodeCommand.h"
51 #include "core/editing/RemoveNodePreservingChildrenCommand.h"
52 #include "core/editing/ReplaceNodeWithSpanCommand.h"
53 #include "core/editing/ReplaceSelectionCommand.h"
54 #include "core/editing/SetNodeAttributeCommand.h"
55 #include "core/editing/SpellChecker.h"
56 #include "core/editing/SplitElementCommand.h"
57 #include "core/editing/SplitTextNodeCommand.h"
58 #include "core/editing/SplitTextNodeContainingElementCommand.h"
59 #include "core/editing/TextIterator.h"
60 #include "core/editing/VisibleUnits.h"
61 #include "core/editing/WrapContentsInDummySpanCommand.h"
62 #include "core/editing/htmlediting.h"
63 #include "core/editing/markup.h"
64 #include "core/html/HTMLElement.h"
65 #include "core/frame/Frame.h"
66 #include "core/rendering/InlineTextBox.h"
67 #include "core/rendering/RenderBlock.h"
68 #include "core/rendering/RenderText.h"
69
70 using namespace std;
71
72 namespace WebCore {
73
74 using namespace HTMLNames;
75
76 namespace {
77 class ReentrancyGuard {
78 public:
isRecursiveCall()79 static bool isRecursiveCall() { return s_nestingCounter; }
80
81 class Scope {
82 public:
Scope()83 Scope() { ++s_nestingCounter; }
~Scope()84 ~Scope() { --s_nestingCounter; }
85 };
86 friend class Scope;
87
88 private:
89 static int s_nestingCounter;
90 };
91 int ReentrancyGuard::s_nestingCounter;
92 }
93
create(Document * document,const VisibleSelection & startingSelection,const VisibleSelection & endingSelection,EditAction editAction)94 PassRefPtr<EditCommandComposition> EditCommandComposition::create(Document* document,
95 const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction editAction)
96 {
97 return adoptRef(new EditCommandComposition(document, startingSelection, endingSelection, editAction));
98 }
99
EditCommandComposition(Document * document,const VisibleSelection & startingSelection,const VisibleSelection & endingSelection,EditAction editAction)100 EditCommandComposition::EditCommandComposition(Document* document, const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction editAction)
101 : m_document(document)
102 , m_startingSelection(startingSelection)
103 , m_endingSelection(endingSelection)
104 , m_startingRootEditableElement(startingSelection.rootEditableElement())
105 , m_endingRootEditableElement(endingSelection.rootEditableElement())
106 , m_editAction(editAction)
107 {
108 }
109
belongsTo(const Frame & frame) const110 bool EditCommandComposition::belongsTo(const Frame& frame) const
111 {
112 ASSERT(m_document);
113 return m_document->frame() == &frame;
114 }
115
unapply()116 void EditCommandComposition::unapply()
117 {
118 ASSERT(m_document);
119 RefPtr<Frame> frame = m_document->frame();
120 ASSERT(frame);
121
122 // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>.
123 // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one
124 // if one is necessary (like for the creation of VisiblePositions).
125 m_document->updateLayoutIgnorePendingStylesheets();
126
127 {
128 size_t size = m_commands.size();
129 for (size_t i = size; i; --i)
130 m_commands[i - 1]->doUnapply();
131 }
132
133 frame->editor().unappliedEditing(this);
134 }
135
reapply()136 void EditCommandComposition::reapply()
137 {
138 ASSERT(m_document);
139 RefPtr<Frame> frame = m_document->frame();
140 ASSERT(frame);
141
142 // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>.
143 // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one
144 // if one is necessary (like for the creation of VisiblePositions).
145 m_document->updateLayoutIgnorePendingStylesheets();
146
147 {
148 size_t size = m_commands.size();
149 for (size_t i = 0; i != size; ++i)
150 m_commands[i]->doReapply();
151 }
152
153 frame->editor().reappliedEditing(this);
154 }
155
append(SimpleEditCommand * command)156 void EditCommandComposition::append(SimpleEditCommand* command)
157 {
158 m_commands.append(command);
159 }
160
setStartingSelection(const VisibleSelection & selection)161 void EditCommandComposition::setStartingSelection(const VisibleSelection& selection)
162 {
163 m_startingSelection = selection;
164 m_startingRootEditableElement = selection.rootEditableElement();
165 }
166
setEndingSelection(const VisibleSelection & selection)167 void EditCommandComposition::setEndingSelection(const VisibleSelection& selection)
168 {
169 m_endingSelection = selection;
170 m_endingRootEditableElement = selection.rootEditableElement();
171 }
172
CompositeEditCommand(Document & document)173 CompositeEditCommand::CompositeEditCommand(Document& document)
174 : EditCommand(document)
175 {
176 }
177
~CompositeEditCommand()178 CompositeEditCommand::~CompositeEditCommand()
179 {
180 ASSERT(isTopLevelCommand() || !m_composition);
181 }
182
apply()183 void CompositeEditCommand::apply()
184 {
185 // We don't allow recusrive |apply()| to protect against attack code.
186 // Recursive call of |apply()| could be happened by moving iframe
187 // with script triggered by insertion, e.g. <iframe src="javascript:...">
188 // <iframe onload="...">. This usage is valid as of the specification
189 // although, it isn't common use case, rather it is used as attack code.
190 if (ReentrancyGuard::isRecursiveCall())
191 return;
192
193 if (!endingSelection().isContentRichlyEditable()) {
194 switch (editingAction()) {
195 case EditActionTyping:
196 case EditActionPaste:
197 case EditActionDrag:
198 case EditActionSetWritingDirection:
199 case EditActionCut:
200 case EditActionUnspecified:
201 break;
202 default:
203 ASSERT_NOT_REACHED();
204 return;
205 }
206 }
207 ensureComposition();
208
209 // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>.
210 // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one
211 // if one is necessary (like for the creation of VisiblePositions).
212 document().updateLayoutIgnorePendingStylesheets();
213
214 Frame* frame = document().frame();
215 ASSERT(frame);
216 {
217 EventQueueScope eventQueueScope;
218 ReentrancyGuard::Scope reentrancyGuardScope;
219 doApply();
220 }
221
222 // Only need to call appliedEditing for top-level commands,
223 // and TypingCommands do it on their own (see TypingCommand::typingAddedToOpenCommand).
224 if (!isTypingCommand())
225 frame->editor().appliedEditing(this);
226 setShouldRetainAutocorrectionIndicator(false);
227 }
228
ensureComposition()229 EditCommandComposition* CompositeEditCommand::ensureComposition()
230 {
231 CompositeEditCommand* command = this;
232 while (command && command->parent())
233 command = command->parent();
234 if (!command->m_composition)
235 command->m_composition = EditCommandComposition::create(&document(), startingSelection(), endingSelection(), editingAction());
236 return command->m_composition.get();
237 }
238
isCreateLinkCommand() const239 bool CompositeEditCommand::isCreateLinkCommand() const
240 {
241 return false;
242 }
243
preservesTypingStyle() const244 bool CompositeEditCommand::preservesTypingStyle() const
245 {
246 return false;
247 }
248
isTypingCommand() const249 bool CompositeEditCommand::isTypingCommand() const
250 {
251 return false;
252 }
253
shouldRetainAutocorrectionIndicator() const254 bool CompositeEditCommand::shouldRetainAutocorrectionIndicator() const
255 {
256 return false;
257 }
258
setShouldRetainAutocorrectionIndicator(bool)259 void CompositeEditCommand::setShouldRetainAutocorrectionIndicator(bool)
260 {
261 }
262
263 //
264 // sugary-sweet convenience functions to help create and apply edit commands in composite commands
265 //
applyCommandToComposite(PassRefPtr<EditCommand> prpCommand)266 void CompositeEditCommand::applyCommandToComposite(PassRefPtr<EditCommand> prpCommand)
267 {
268 RefPtr<EditCommand> command = prpCommand;
269 command->setParent(this);
270 command->doApply();
271 if (command->isSimpleEditCommand()) {
272 command->setParent(0);
273 ensureComposition()->append(toSimpleEditCommand(command.get()));
274 }
275 m_commands.append(command.release());
276 }
277
applyCommandToComposite(PassRefPtr<CompositeEditCommand> command,const VisibleSelection & selection)278 void CompositeEditCommand::applyCommandToComposite(PassRefPtr<CompositeEditCommand> command, const VisibleSelection& selection)
279 {
280 command->setParent(this);
281 if (selection != command->endingSelection()) {
282 command->setStartingSelection(selection);
283 command->setEndingSelection(selection);
284 }
285 command->doApply();
286 m_commands.append(command);
287 }
288
applyStyle(const EditingStyle * style,EditAction editingAction)289 void CompositeEditCommand::applyStyle(const EditingStyle* style, EditAction editingAction)
290 {
291 applyCommandToComposite(ApplyStyleCommand::create(document(), style, editingAction));
292 }
293
applyStyle(const EditingStyle * style,const Position & start,const Position & end,EditAction editingAction)294 void CompositeEditCommand::applyStyle(const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction)
295 {
296 applyCommandToComposite(ApplyStyleCommand::create(document(), style, start, end, editingAction));
297 }
298
applyStyledElement(PassRefPtr<Element> element)299 void CompositeEditCommand::applyStyledElement(PassRefPtr<Element> element)
300 {
301 applyCommandToComposite(ApplyStyleCommand::create(element, false));
302 }
303
removeStyledElement(PassRefPtr<Element> element)304 void CompositeEditCommand::removeStyledElement(PassRefPtr<Element> element)
305 {
306 applyCommandToComposite(ApplyStyleCommand::create(element, true));
307 }
308
insertParagraphSeparator(bool useDefaultParagraphElement,bool pasteBlockqutoeIntoUnquotedArea)309 void CompositeEditCommand::insertParagraphSeparator(bool useDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea)
310 {
311 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), useDefaultParagraphElement, pasteBlockqutoeIntoUnquotedArea));
312 }
313
insertLineBreak()314 void CompositeEditCommand::insertLineBreak()
315 {
316 applyCommandToComposite(InsertLineBreakCommand::create(document()));
317 }
318
isRemovableBlock(const Node * node)319 bool CompositeEditCommand::isRemovableBlock(const Node* node)
320 {
321 if (!node->hasTagName(divTag))
322 return false;
323
324 Node* parentNode = node->parentNode();
325 if (parentNode && parentNode->firstChild() != parentNode->lastChild())
326 return false;
327
328 if (!toElement(node)->hasAttributes())
329 return true;
330
331 return false;
332 }
333
insertNodeBefore(PassRefPtr<Node> insertChild,PassRefPtr<Node> refChild,ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)334 void CompositeEditCommand::insertNodeBefore(PassRefPtr<Node> insertChild, PassRefPtr<Node> refChild, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)
335 {
336 ASSERT(!refChild->hasTagName(bodyTag));
337 applyCommandToComposite(InsertNodeBeforeCommand::create(insertChild, refChild, shouldAssumeContentIsAlwaysEditable));
338 }
339
insertNodeAfter(PassRefPtr<Node> insertChild,PassRefPtr<Node> refChild)340 void CompositeEditCommand::insertNodeAfter(PassRefPtr<Node> insertChild, PassRefPtr<Node> refChild)
341 {
342 ASSERT(insertChild);
343 ASSERT(refChild);
344 ASSERT(!refChild->hasTagName(bodyTag));
345 ContainerNode* parent = refChild->parentNode();
346 ASSERT(parent);
347 ASSERT(!parent->isShadowRoot());
348 if (parent->lastChild() == refChild)
349 appendNode(insertChild, parent);
350 else {
351 ASSERT(refChild->nextSibling());
352 insertNodeBefore(insertChild, refChild->nextSibling());
353 }
354 }
355
insertNodeAt(PassRefPtr<Node> insertChild,const Position & editingPosition)356 void CompositeEditCommand::insertNodeAt(PassRefPtr<Node> insertChild, const Position& editingPosition)
357 {
358 ASSERT(isEditablePosition(editingPosition));
359 // For editing positions like [table, 0], insert before the table,
360 // likewise for replaced elements, brs, etc.
361 Position p = editingPosition.parentAnchoredEquivalent();
362 Node* refChild = p.deprecatedNode();
363 int offset = p.deprecatedEditingOffset();
364
365 if (canHaveChildrenForEditing(refChild)) {
366 Node* child = refChild->firstChild();
367 for (int i = 0; child && i < offset; i++)
368 child = child->nextSibling();
369 if (child)
370 insertNodeBefore(insertChild, child);
371 else
372 appendNode(insertChild, toContainerNode(refChild));
373 } else if (caretMinOffset(refChild) >= offset)
374 insertNodeBefore(insertChild, refChild);
375 else if (refChild->isTextNode() && caretMaxOffset(refChild) > offset) {
376 splitTextNode(toText(refChild), offset);
377
378 // Mutation events (bug 22634) from the text node insertion may have removed the refChild
379 if (!refChild->inDocument())
380 return;
381 insertNodeBefore(insertChild, refChild);
382 } else
383 insertNodeAfter(insertChild, refChild);
384 }
385
appendNode(PassRefPtr<Node> node,PassRefPtr<ContainerNode> parent)386 void CompositeEditCommand::appendNode(PassRefPtr<Node> node, PassRefPtr<ContainerNode> parent)
387 {
388 ASSERT(canHaveChildrenForEditing(parent.get()));
389 applyCommandToComposite(AppendNodeCommand::create(parent, node));
390 }
391
removeChildrenInRange(PassRefPtr<Node> node,unsigned from,unsigned to)392 void CompositeEditCommand::removeChildrenInRange(PassRefPtr<Node> node, unsigned from, unsigned to)
393 {
394 Vector<RefPtr<Node> > children;
395 Node* child = node->childNode(from);
396 for (unsigned i = from; child && i < to; i++, child = child->nextSibling())
397 children.append(child);
398
399 size_t size = children.size();
400 for (size_t i = 0; i < size; ++i)
401 removeNode(children[i].release());
402 }
403
removeNode(PassRefPtr<Node> node,ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)404 void CompositeEditCommand::removeNode(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)
405 {
406 if (!node || !node->nonShadowBoundaryParentNode())
407 return;
408 applyCommandToComposite(RemoveNodeCommand::create(node, shouldAssumeContentIsAlwaysEditable));
409 }
410
removeNodePreservingChildren(PassRefPtr<Node> node,ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)411 void CompositeEditCommand::removeNodePreservingChildren(PassRefPtr<Node> node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)
412 {
413 applyCommandToComposite(RemoveNodePreservingChildrenCommand::create(node, shouldAssumeContentIsAlwaysEditable));
414 }
415
removeNodeAndPruneAncestors(PassRefPtr<Node> node,Node * excludeNode)416 void CompositeEditCommand::removeNodeAndPruneAncestors(PassRefPtr<Node> node, Node* excludeNode)
417 {
418 ASSERT(node.get() != excludeNode);
419 RefPtr<ContainerNode> parent = node->parentNode();
420 removeNode(node);
421 prune(parent.release(), excludeNode);
422 }
423
moveRemainingSiblingsToNewParent(Node * node,Node * pastLastNodeToMove,PassRefPtr<Element> prpNewParent)424 void CompositeEditCommand::moveRemainingSiblingsToNewParent(Node* node, Node* pastLastNodeToMove, PassRefPtr<Element> prpNewParent)
425 {
426 NodeVector nodesToRemove;
427 RefPtr<Element> newParent = prpNewParent;
428
429 for (; node && node != pastLastNodeToMove; node = node->nextSibling())
430 nodesToRemove.append(node);
431
432 for (unsigned i = 0; i < nodesToRemove.size(); i++) {
433 removeNode(nodesToRemove[i]);
434 appendNode(nodesToRemove[i], newParent);
435 }
436 }
437
updatePositionForNodeRemovalPreservingChildren(Position & position,Node * node)438 void CompositeEditCommand::updatePositionForNodeRemovalPreservingChildren(Position& position, Node* node)
439 {
440 int offset = (position.anchorType() == Position::PositionIsOffsetInAnchor) ? position.offsetInContainerNode() : 0;
441 updatePositionForNodeRemoval(position, node);
442 if (offset)
443 position.moveToOffset(offset);
444 }
445
replaceElementWithSpanPreservingChildrenAndAttributes(PassRefPtr<HTMLElement> node)446 HTMLElement* CompositeEditCommand::replaceElementWithSpanPreservingChildrenAndAttributes(PassRefPtr<HTMLElement> node)
447 {
448 // It would also be possible to implement all of ReplaceNodeWithSpanCommand
449 // as a series of existing smaller edit commands. Someone who wanted to
450 // reduce the number of edit commands could do so here.
451 RefPtr<ReplaceNodeWithSpanCommand> command = ReplaceNodeWithSpanCommand::create(node);
452 applyCommandToComposite(command);
453 // Returning a raw pointer here is OK because the command is retained by
454 // applyCommandToComposite (thus retaining the span), and the span is also
455 // in the DOM tree, and thus alive whie it has a parent.
456 ASSERT(command->spanElement()->inDocument());
457 return command->spanElement();
458 }
459
prune(PassRefPtr<Node> node,Node * excludeNode)460 void CompositeEditCommand::prune(PassRefPtr<Node> node, Node* excludeNode)
461 {
462 if (RefPtr<Node> highestNodeToRemove = highestNodeToRemoveInPruning(node.get(), excludeNode))
463 removeNode(highestNodeToRemove.release());
464 }
465
splitTextNode(PassRefPtr<Text> node,unsigned offset)466 void CompositeEditCommand::splitTextNode(PassRefPtr<Text> node, unsigned offset)
467 {
468 applyCommandToComposite(SplitTextNodeCommand::create(node, offset));
469 }
470
splitElement(PassRefPtr<Element> element,PassRefPtr<Node> atChild)471 void CompositeEditCommand::splitElement(PassRefPtr<Element> element, PassRefPtr<Node> atChild)
472 {
473 applyCommandToComposite(SplitElementCommand::create(element, atChild));
474 }
475
mergeIdenticalElements(PassRefPtr<Element> prpFirst,PassRefPtr<Element> prpSecond)476 void CompositeEditCommand::mergeIdenticalElements(PassRefPtr<Element> prpFirst, PassRefPtr<Element> prpSecond)
477 {
478 RefPtr<Element> first = prpFirst;
479 RefPtr<Element> second = prpSecond;
480 ASSERT(!first->isDescendantOf(second.get()) && second != first);
481 if (first->nextSibling() != second) {
482 removeNode(second);
483 insertNodeAfter(second, first);
484 }
485 applyCommandToComposite(MergeIdenticalElementsCommand::create(first, second));
486 }
487
wrapContentsInDummySpan(PassRefPtr<Element> element)488 void CompositeEditCommand::wrapContentsInDummySpan(PassRefPtr<Element> element)
489 {
490 applyCommandToComposite(WrapContentsInDummySpanCommand::create(element));
491 }
492
splitTextNodeContainingElement(PassRefPtr<Text> text,unsigned offset)493 void CompositeEditCommand::splitTextNodeContainingElement(PassRefPtr<Text> text, unsigned offset)
494 {
495 applyCommandToComposite(SplitTextNodeContainingElementCommand::create(text, offset));
496 }
497
insertTextIntoNode(PassRefPtr<Text> node,unsigned offset,const String & text)498 void CompositeEditCommand::insertTextIntoNode(PassRefPtr<Text> node, unsigned offset, const String& text)
499 {
500 if (!text.isEmpty())
501 applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, text));
502 }
503
deleteTextFromNode(PassRefPtr<Text> node,unsigned offset,unsigned count)504 void CompositeEditCommand::deleteTextFromNode(PassRefPtr<Text> node, unsigned offset, unsigned count)
505 {
506 applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count));
507 }
508
replaceTextInNode(PassRefPtr<Text> prpNode,unsigned offset,unsigned count,const String & replacementText)509 void CompositeEditCommand::replaceTextInNode(PassRefPtr<Text> prpNode, unsigned offset, unsigned count, const String& replacementText)
510 {
511 RefPtr<Text> node(prpNode);
512 applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count));
513 if (!replacementText.isEmpty())
514 applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, replacementText));
515 }
516
replaceSelectedTextInNode(const String & text)517 Position CompositeEditCommand::replaceSelectedTextInNode(const String& text)
518 {
519 Position start = endingSelection().start();
520 Position end = endingSelection().end();
521 if (start.containerNode() != end.containerNode() || !start.containerNode()->isTextNode() || isTabSpanTextNode(start.containerNode()))
522 return Position();
523
524 RefPtr<Text> textNode = start.containerText();
525 replaceTextInNode(textNode, start.offsetInContainerNode(), end.offsetInContainerNode() - start.offsetInContainerNode(), text);
526
527 return Position(textNode.release(), start.offsetInContainerNode() + text.length());
528 }
529
copyMarkers(const Vector<DocumentMarker * > & markerPointers,Vector<DocumentMarker> & markers)530 static void copyMarkers(const Vector<DocumentMarker*>& markerPointers, Vector<DocumentMarker>& markers)
531 {
532 size_t arraySize = markerPointers.size();
533 markers.reserveCapacity(arraySize);
534 for (size_t i = 0; i < arraySize; ++i)
535 markers.append(*markerPointers[i]);
536 }
537
replaceTextInNodePreservingMarkers(PassRefPtr<Text> prpNode,unsigned offset,unsigned count,const String & replacementText)538 void CompositeEditCommand::replaceTextInNodePreservingMarkers(PassRefPtr<Text> prpNode, unsigned offset, unsigned count, const String& replacementText)
539 {
540 RefPtr<Text> node(prpNode);
541 DocumentMarkerController* markerController = document().markers();
542 Vector<DocumentMarker> markers;
543 copyMarkers(markerController->markersInRange(Range::create(document(), node.get(), offset, node.get(), offset + count).get(), DocumentMarker::AllMarkers()), markers);
544 replaceTextInNode(node, offset, count, replacementText);
545 RefPtr<Range> newRange = Range::create(document(), node.get(), offset, node.get(), offset + replacementText.length());
546 for (size_t i = 0; i < markers.size(); ++i)
547 markerController->addMarker(newRange.get(), markers[i].type(), markers[i].description());
548 }
549
positionOutsideTabSpan(const Position & pos)550 Position CompositeEditCommand::positionOutsideTabSpan(const Position& pos)
551 {
552 if (!isTabSpanTextNode(pos.anchorNode()))
553 return pos;
554
555 switch (pos.anchorType()) {
556 case Position::PositionIsBeforeChildren:
557 case Position::PositionIsAfterChildren:
558 ASSERT_NOT_REACHED();
559 return pos;
560 case Position::PositionIsOffsetInAnchor:
561 break;
562 case Position::PositionIsBeforeAnchor:
563 return positionInParentBeforeNode(pos.anchorNode());
564 case Position::PositionIsAfterAnchor:
565 return positionInParentAfterNode(pos.anchorNode());
566 }
567
568 Node* tabSpan = tabSpanNode(pos.containerNode());
569
570 if (pos.offsetInContainerNode() <= caretMinOffset(pos.containerNode()))
571 return positionInParentBeforeNode(tabSpan);
572
573 if (pos.offsetInContainerNode() >= caretMaxOffset(pos.containerNode()))
574 return positionInParentAfterNode(tabSpan);
575
576 splitTextNodeContainingElement(toText(pos.containerNode()), pos.offsetInContainerNode());
577 return positionInParentBeforeNode(tabSpan);
578 }
579
insertNodeAtTabSpanPosition(PassRefPtr<Node> node,const Position & pos)580 void CompositeEditCommand::insertNodeAtTabSpanPosition(PassRefPtr<Node> node, const Position& pos)
581 {
582 // insert node before, after, or at split of tab span
583 insertNodeAt(node, positionOutsideTabSpan(pos));
584 }
585
deleteSelection(bool smartDelete,bool mergeBlocksAfterDelete,bool replace,bool expandForSpecialElements,bool sanitizeMarkup)586 void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup)
587 {
588 if (endingSelection().isRange())
589 applyCommandToComposite(DeleteSelectionCommand::create(document(), smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup));
590 }
591
deleteSelection(const VisibleSelection & selection,bool smartDelete,bool mergeBlocksAfterDelete,bool replace,bool expandForSpecialElements,bool sanitizeMarkup)592 void CompositeEditCommand::deleteSelection(const VisibleSelection &selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup)
593 {
594 if (selection.isRange())
595 applyCommandToComposite(DeleteSelectionCommand::create(selection, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup));
596 }
597
removeCSSProperty(PassRefPtr<Element> element,CSSPropertyID property)598 void CompositeEditCommand::removeCSSProperty(PassRefPtr<Element> element, CSSPropertyID property)
599 {
600 applyCommandToComposite(RemoveCSSPropertyCommand::create(document(), element, property));
601 }
602
removeNodeAttribute(PassRefPtr<Element> element,const QualifiedName & attribute)603 void CompositeEditCommand::removeNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute)
604 {
605 setNodeAttribute(element, attribute, AtomicString());
606 }
607
setNodeAttribute(PassRefPtr<Element> element,const QualifiedName & attribute,const AtomicString & value)608 void CompositeEditCommand::setNodeAttribute(PassRefPtr<Element> element, const QualifiedName& attribute, const AtomicString& value)
609 {
610 applyCommandToComposite(SetNodeAttributeCommand::create(element, attribute, value));
611 }
612
containsOnlyWhitespace(const String & text)613 static inline bool containsOnlyWhitespace(const String& text)
614 {
615 for (unsigned i = 0; i < text.length(); ++i) {
616 if (!isWhitespace(text[i]))
617 return false;
618 }
619
620 return true;
621 }
622
shouldRebalanceLeadingWhitespaceFor(const String & text) const623 bool CompositeEditCommand::shouldRebalanceLeadingWhitespaceFor(const String& text) const
624 {
625 return containsOnlyWhitespace(text);
626 }
627
canRebalance(const Position & position) const628 bool CompositeEditCommand::canRebalance(const Position& position) const
629 {
630 Node* node = position.containerNode();
631 if (position.anchorType() != Position::PositionIsOffsetInAnchor || !node || !node->isTextNode())
632 return false;
633
634 Text* textNode = toText(node);
635 if (textNode->length() == 0)
636 return false;
637
638 RenderObject* renderer = textNode->renderer();
639 if (renderer && !renderer->style()->collapseWhiteSpace())
640 return false;
641
642 return true;
643 }
644
645 // FIXME: Doesn't go into text nodes that contribute adjacent text (siblings, cousins, etc).
rebalanceWhitespaceAt(const Position & position)646 void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position)
647 {
648 Node* node = position.containerNode();
649 if (!canRebalance(position))
650 return;
651
652 // If the rebalance is for the single offset, and neither text[offset] nor text[offset - 1] are some form of whitespace, do nothing.
653 int offset = position.deprecatedEditingOffset();
654 String text = toText(node)->data();
655 if (!isWhitespace(text[offset])) {
656 offset--;
657 if (offset < 0 || !isWhitespace(text[offset]))
658 return;
659 }
660
661 rebalanceWhitespaceOnTextSubstring(toText(node), position.offsetInContainerNode(), position.offsetInContainerNode());
662 }
663
rebalanceWhitespaceOnTextSubstring(PassRefPtr<Text> prpTextNode,int startOffset,int endOffset)664 void CompositeEditCommand::rebalanceWhitespaceOnTextSubstring(PassRefPtr<Text> prpTextNode, int startOffset, int endOffset)
665 {
666 RefPtr<Text> textNode = prpTextNode;
667
668 String text = textNode->data();
669 ASSERT(!text.isEmpty());
670
671 // Set upstream and downstream to define the extent of the whitespace surrounding text[offset].
672 int upstream = startOffset;
673 while (upstream > 0 && isWhitespace(text[upstream - 1]))
674 upstream--;
675
676 int downstream = endOffset;
677 while ((unsigned)downstream < text.length() && isWhitespace(text[downstream]))
678 downstream++;
679
680 int length = downstream - upstream;
681 if (!length)
682 return;
683
684 VisiblePosition visibleUpstreamPos(Position(textNode, upstream));
685 VisiblePosition visibleDownstreamPos(Position(textNode, downstream));
686
687 String string = text.substring(upstream, length);
688 String rebalancedString = stringWithRebalancedWhitespace(string,
689 // 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
690 // this function doesn't get all surrounding whitespace, just the whitespace in the current text node.
691 isStartOfParagraph(visibleUpstreamPos) || upstream == 0,
692 isEndOfParagraph(visibleDownstreamPos) || (unsigned)downstream == text.length());
693
694 if (string != rebalancedString)
695 replaceTextInNodePreservingMarkers(textNode.release(), upstream, length, rebalancedString);
696 }
697
prepareWhitespaceAtPositionForSplit(Position & position)698 void CompositeEditCommand::prepareWhitespaceAtPositionForSplit(Position& position)
699 {
700 Node* node = position.deprecatedNode();
701 if (!node || !node->isTextNode())
702 return;
703 Text* textNode = toText(node);
704
705 if (textNode->length() == 0)
706 return;
707 RenderObject* renderer = textNode->renderer();
708 if (renderer && !renderer->style()->collapseWhiteSpace())
709 return;
710
711 // Delete collapsed whitespace so that inserting nbsps doesn't uncollapse it.
712 Position upstreamPos = position.upstream();
713 deleteInsignificantText(upstreamPos, position.downstream());
714 position = upstreamPos.downstream();
715
716 VisiblePosition visiblePos(position);
717 VisiblePosition previousVisiblePos(visiblePos.previous());
718 replaceCollapsibleWhitespaceWithNonBreakingSpaceIfNeeded(previousVisiblePos);
719 replaceCollapsibleWhitespaceWithNonBreakingSpaceIfNeeded(visiblePos);
720 }
721
replaceCollapsibleWhitespaceWithNonBreakingSpaceIfNeeded(const VisiblePosition & visiblePosition)722 void CompositeEditCommand::replaceCollapsibleWhitespaceWithNonBreakingSpaceIfNeeded(const VisiblePosition& visiblePosition)
723 {
724 if (!isCollapsibleWhitespace(visiblePosition.characterAfter()))
725 return;
726 Position pos = visiblePosition.deepEquivalent().downstream();
727 if (!pos.containerNode() || !pos.containerNode()->isTextNode() || pos.containerNode()->hasTagName(brTag))
728 return;
729 replaceTextInNodePreservingMarkers(pos.containerText(), pos.offsetInContainerNode(), 1, nonBreakingSpaceString());
730 }
731
rebalanceWhitespace()732 void CompositeEditCommand::rebalanceWhitespace()
733 {
734 VisibleSelection selection = endingSelection();
735 if (selection.isNone())
736 return;
737
738 rebalanceWhitespaceAt(selection.start());
739 if (selection.isRange())
740 rebalanceWhitespaceAt(selection.end());
741 }
742
deleteInsignificantText(PassRefPtr<Text> textNode,unsigned start,unsigned end)743 void CompositeEditCommand::deleteInsignificantText(PassRefPtr<Text> textNode, unsigned start, unsigned end)
744 {
745 if (!textNode || start >= end)
746 return;
747
748 document().updateLayout();
749
750 RenderText* textRenderer = toRenderText(textNode->renderer());
751 if (!textRenderer)
752 return;
753
754 Vector<InlineTextBox*> sortedTextBoxes;
755 size_t sortedTextBoxesPosition = 0;
756
757 for (InlineTextBox* textBox = textRenderer->firstTextBox(); textBox; textBox = textBox->nextTextBox())
758 sortedTextBoxes.append(textBox);
759
760 // If there is mixed directionality text, the boxes can be out of order,
761 // (like Arabic with embedded LTR), so sort them first.
762 if (textRenderer->containsReversedText())
763 std::sort(sortedTextBoxes.begin(), sortedTextBoxes.end(), InlineTextBox::compareByStart);
764 InlineTextBox* box = sortedTextBoxes.isEmpty() ? 0 : sortedTextBoxes[sortedTextBoxesPosition];
765
766 if (!box) {
767 // whole text node is empty
768 removeNode(textNode);
769 return;
770 }
771
772 unsigned length = textNode->length();
773 if (start >= length || end > length)
774 return;
775
776 unsigned removed = 0;
777 InlineTextBox* prevBox = 0;
778 String str;
779
780 // This loop structure works to process all gaps preceding a box,
781 // and also will look at the gap after the last box.
782 while (prevBox || box) {
783 unsigned gapStart = prevBox ? prevBox->start() + prevBox->len() : 0;
784 if (end < gapStart)
785 // No more chance for any intersections
786 break;
787
788 unsigned gapEnd = box ? box->start() : length;
789 bool indicesIntersect = start <= gapEnd && end >= gapStart;
790 int gapLen = gapEnd - gapStart;
791 if (indicesIntersect && gapLen > 0) {
792 gapStart = max(gapStart, start);
793 gapEnd = min(gapEnd, end);
794 if (str.isNull())
795 str = textNode->data().substring(start, end - start);
796 // remove text in the gap
797 str.remove(gapStart - start - removed, gapLen);
798 removed += gapLen;
799 }
800
801 prevBox = box;
802 if (box) {
803 if (++sortedTextBoxesPosition < sortedTextBoxes.size())
804 box = sortedTextBoxes[sortedTextBoxesPosition];
805 else
806 box = 0;
807 }
808 }
809
810 if (!str.isNull()) {
811 // Replace the text between start and end with our pruned version.
812 if (!str.isEmpty())
813 replaceTextInNode(textNode, start, end - start, str);
814 else {
815 // Assert that we are not going to delete all of the text in the node.
816 // If we were, that should have been done above with the call to
817 // removeNode and return.
818 ASSERT(start > 0 || end - start < textNode->length());
819 deleteTextFromNode(textNode, start, end - start);
820 }
821 }
822 }
823
deleteInsignificantText(const Position & start,const Position & end)824 void CompositeEditCommand::deleteInsignificantText(const Position& start, const Position& end)
825 {
826 if (start.isNull() || end.isNull())
827 return;
828
829 if (comparePositions(start, end) >= 0)
830 return;
831
832 Vector<RefPtr<Text> > nodes;
833 for (Node* node = start.deprecatedNode(); node; node = NodeTraversal::next(*node)) {
834 if (node->isTextNode())
835 nodes.append(toText(node));
836 if (node == end.deprecatedNode())
837 break;
838 }
839
840 for (size_t i = 0; i < nodes.size(); ++i) {
841 Text* textNode = nodes[i].get();
842 int startOffset = textNode == start.deprecatedNode() ? start.deprecatedEditingOffset() : 0;
843 int endOffset = textNode == end.deprecatedNode() ? end.deprecatedEditingOffset() : static_cast<int>(textNode->length());
844 deleteInsignificantText(textNode, startOffset, endOffset);
845 }
846 }
847
deleteInsignificantTextDownstream(const Position & pos)848 void CompositeEditCommand::deleteInsignificantTextDownstream(const Position& pos)
849 {
850 Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream();
851 deleteInsignificantText(pos, end);
852 }
853
appendBlockPlaceholder(PassRefPtr<Element> container)854 PassRefPtr<Node> CompositeEditCommand::appendBlockPlaceholder(PassRefPtr<Element> container)
855 {
856 if (!container)
857 return 0;
858
859 document().updateLayoutIgnorePendingStylesheets();
860
861 // Should assert isRenderBlockFlow || isInlineFlow when deletion improves. See 4244964.
862 ASSERT(container->renderer());
863
864 RefPtr<Node> placeholder = createBlockPlaceholderElement(document());
865 appendNode(placeholder, container);
866 return placeholder.release();
867 }
868
insertBlockPlaceholder(const Position & pos)869 PassRefPtr<Node> CompositeEditCommand::insertBlockPlaceholder(const Position& pos)
870 {
871 if (pos.isNull())
872 return 0;
873
874 // Should assert isRenderBlockFlow || isInlineFlow when deletion improves. See 4244964.
875 ASSERT(pos.deprecatedNode()->renderer());
876
877 RefPtr<Node> placeholder = createBlockPlaceholderElement(document());
878 insertNodeAt(placeholder, pos);
879 return placeholder.release();
880 }
881
addBlockPlaceholderIfNeeded(Element * container)882 PassRefPtr<Node> CompositeEditCommand::addBlockPlaceholderIfNeeded(Element* container)
883 {
884 if (!container)
885 return 0;
886
887 document().updateLayoutIgnorePendingStylesheets();
888
889 RenderObject* renderer = container->renderer();
890 if (!renderer || !renderer->isRenderBlockFlow())
891 return 0;
892
893 // append the placeholder to make sure it follows
894 // any unrendered blocks
895 RenderBlock* block = toRenderBlock(renderer);
896 if (block->height() == 0 || (block->isListItem() && block->isEmpty()))
897 return appendBlockPlaceholder(container);
898
899 return 0;
900 }
901
902 // Assumes that the position is at a placeholder and does the removal without much checking.
removePlaceholderAt(const Position & p)903 void CompositeEditCommand::removePlaceholderAt(const Position& p)
904 {
905 ASSERT(lineBreakExistsAtPosition(p));
906
907 // We are certain that the position is at a line break, but it may be a br or a preserved newline.
908 if (p.anchorNode()->hasTagName(brTag)) {
909 removeNode(p.anchorNode());
910 return;
911 }
912
913 deleteTextFromNode(toText(p.anchorNode()), p.offsetInContainerNode(), 1);
914 }
915
insertNewDefaultParagraphElementAt(const Position & position)916 PassRefPtr<Node> CompositeEditCommand::insertNewDefaultParagraphElementAt(const Position& position)
917 {
918 RefPtr<Element> paragraphElement = createDefaultParagraphElement(document());
919 paragraphElement->appendChild(createBreakElement(document()));
920 insertNodeAt(paragraphElement, position);
921 return paragraphElement.release();
922 }
923
924 // If the paragraph is not entirely within it's own block, create one and move the paragraph into
925 // it, and return that block. Otherwise return 0.
moveParagraphContentsToNewBlockIfNecessary(const Position & pos)926 PassRefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position& pos)
927 {
928 if (pos.isNull())
929 return 0;
930
931 document().updateLayoutIgnorePendingStylesheets();
932
933 // It's strange that this function is responsible for verifying that pos has not been invalidated
934 // by an earlier call to this function. The caller, applyBlockStyle, should do this.
935 VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
936 VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos));
937 VisiblePosition visibleParagraphEnd = endOfParagraph(visiblePos);
938 VisiblePosition next = visibleParagraphEnd.next();
939 VisiblePosition visibleEnd = next.isNotNull() ? next : visibleParagraphEnd;
940
941 Position upstreamStart = visibleParagraphStart.deepEquivalent().upstream();
942 Position upstreamEnd = visibleEnd.deepEquivalent().upstream();
943
944 // If there are no VisiblePositions in the same block as pos then
945 // upstreamStart will be outside the paragraph
946 if (comparePositions(pos, upstreamStart) < 0)
947 return 0;
948
949 // Perform some checks to see if we need to perform work in this function.
950 if (isBlock(upstreamStart.deprecatedNode())) {
951 // If the block is the root editable element, always move content to a new block,
952 // since it is illegal to modify attributes on the root editable element for editing.
953 if (upstreamStart.deprecatedNode() == editableRootForPosition(upstreamStart)) {
954 // If the block is the root editable element and it contains no visible content, create a new
955 // block but don't try and move content into it, since there's nothing for moveParagraphs to move.
956 if (!Position::hasRenderedNonAnonymousDescendantsWithHeight(upstreamStart.deprecatedNode()->renderer()))
957 return insertNewDefaultParagraphElementAt(upstreamStart);
958 } else if (isBlock(upstreamEnd.deprecatedNode())) {
959 if (!upstreamEnd.deprecatedNode()->isDescendantOf(upstreamStart.deprecatedNode())) {
960 // If the paragraph end is a descendant of paragraph start, then we need to run
961 // the rest of this function. If not, we can bail here.
962 return 0;
963 }
964 } else if (enclosingBlock(upstreamEnd.deprecatedNode()) != upstreamStart.deprecatedNode()) {
965 // The visibleEnd. It must be an ancestor of the paragraph start.
966 // We can bail as we have a full block to work with.
967 ASSERT(upstreamStart.deprecatedNode()->isDescendantOf(enclosingBlock(upstreamEnd.deprecatedNode())));
968 return 0;
969 } else if (isEndOfEditableOrNonEditableContent(visibleEnd)) {
970 // At the end of the editable region. We can bail here as well.
971 return 0;
972 }
973 }
974
975 RefPtr<Node> newBlock = insertNewDefaultParagraphElementAt(upstreamStart);
976
977 bool endWasBr = visibleParagraphEnd.deepEquivalent().deprecatedNode()->hasTagName(brTag);
978
979 moveParagraphs(visibleParagraphStart, visibleParagraphEnd, VisiblePosition(firstPositionInNode(newBlock.get())));
980
981 if (newBlock->lastChild() && newBlock->lastChild()->hasTagName(brTag) && !endWasBr)
982 removeNode(newBlock->lastChild());
983
984 return newBlock.release();
985 }
986
pushAnchorElementDown(Node * anchorNode)987 void CompositeEditCommand::pushAnchorElementDown(Node* anchorNode)
988 {
989 if (!anchorNode)
990 return;
991
992 ASSERT(anchorNode->isLink());
993
994 setEndingSelection(VisibleSelection::selectionFromContentsOfNode(anchorNode));
995 applyStyledElement(toElement(anchorNode));
996 // Clones of anchorNode have been pushed down, now remove it.
997 if (anchorNode->inDocument())
998 removeNodePreservingChildren(anchorNode);
999 }
1000
1001 // Clone the paragraph between start and end under blockElement,
1002 // preserving the hierarchy up to outerNode.
1003
cloneParagraphUnderNewElement(Position & start,Position & end,Node * passedOuterNode,Element * blockElement)1004 void CompositeEditCommand::cloneParagraphUnderNewElement(Position& start, Position& end, Node* passedOuterNode, Element* blockElement)
1005 {
1006 // First we clone the outerNode
1007 RefPtr<Node> lastNode;
1008 RefPtr<Node> outerNode = passedOuterNode;
1009
1010 if (outerNode->isRootEditableElement()) {
1011 lastNode = blockElement;
1012 } else {
1013 lastNode = outerNode->cloneNode(isTableElement(outerNode.get()));
1014 appendNode(lastNode, blockElement);
1015 }
1016
1017 if (start.anchorNode() != outerNode && lastNode->isElementNode() && start.anchorNode()->isDescendantOf(outerNode.get())) {
1018 Vector<RefPtr<Node> > ancestors;
1019
1020 // Insert each node from innerNode to outerNode (excluded) in a list.
1021 for (Node* n = start.deprecatedNode(); n && n != outerNode; n = n->parentNode())
1022 ancestors.append(n);
1023
1024 // Clone every node between start.deprecatedNode() and outerBlock.
1025
1026 for (size_t i = ancestors.size(); i != 0; --i) {
1027 Node* item = ancestors[i - 1].get();
1028 RefPtr<Node> child = item->cloneNode(isTableElement(item));
1029 appendNode(child, toElement(lastNode));
1030 lastNode = child.release();
1031 }
1032 }
1033
1034 // Scripts specified in javascript protocol may remove |outerNode|
1035 // during insertion, e.g. <iframe src="javascript:...">
1036 if (!outerNode->inDocument())
1037 return;
1038
1039 // Handle the case of paragraphs with more than one node,
1040 // cloning all the siblings until end.deprecatedNode() is reached.
1041
1042 if (start.deprecatedNode() != end.deprecatedNode() && !start.deprecatedNode()->isDescendantOf(end.deprecatedNode())) {
1043 // If end is not a descendant of outerNode we need to
1044 // find the first common ancestor to increase the scope
1045 // of our nextSibling traversal.
1046 while (!end.deprecatedNode()->isDescendantOf(outerNode.get())) {
1047 outerNode = outerNode->parentNode();
1048 }
1049
1050 RefPtr<Node> startNode = start.deprecatedNode();
1051 for (RefPtr<Node> node = NodeTraversal::nextSkippingChildren(*startNode, outerNode.get()); node; node = NodeTraversal::nextSkippingChildren(*node, outerNode.get())) {
1052 // Move lastNode up in the tree as much as node was moved up in the
1053 // tree by NodeTraversal::nextSkippingChildren, so that the relative depth between
1054 // node and the original start node is maintained in the clone.
1055 while (startNode->parentNode() != node->parentNode()) {
1056 startNode = startNode->parentNode();
1057 lastNode = lastNode->parentNode();
1058 }
1059
1060 RefPtr<Node> clonedNode = node->cloneNode(true);
1061 insertNodeAfter(clonedNode, lastNode);
1062 lastNode = clonedNode.release();
1063 if (node == end.deprecatedNode() || end.deprecatedNode()->isDescendantOf(node.get()))
1064 break;
1065 }
1066 }
1067 }
1068
1069
1070 // There are bugs in deletion when it removes a fully selected table/list.
1071 // It expands and removes the entire table/list, but will let content
1072 // before and after the table/list collapse onto one line.
1073 // Deleting a paragraph will leave a placeholder. Remove it (and prune
1074 // empty or unrendered parents).
1075
cleanupAfterDeletion(VisiblePosition destination)1076 void CompositeEditCommand::cleanupAfterDeletion(VisiblePosition destination)
1077 {
1078 VisiblePosition caretAfterDelete = endingSelection().visibleStart();
1079 Node* destinationNode = destination.deepEquivalent().anchorNode();
1080 if (caretAfterDelete != destination && isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) {
1081 // Note: We want the rightmost candidate.
1082 Position position = caretAfterDelete.deepEquivalent().downstream();
1083 Node* node = position.deprecatedNode();
1084
1085 // Bail if we'd remove an ancestor of our destination.
1086 if (destinationNode && destinationNode->isDescendantOf(node))
1087 return;
1088
1089 // Normally deletion will leave a br as a placeholder.
1090 if (node->hasTagName(brTag)) {
1091 removeNodeAndPruneAncestors(node, destinationNode);
1092
1093 // If the selection to move was empty and in an empty block that
1094 // doesn't require a placeholder to prop itself open (like a bordered
1095 // div or an li), remove it during the move (the list removal code
1096 // expects this behavior).
1097 } else if (isBlock(node)) {
1098 // If caret position after deletion and destination position coincides,
1099 // node should not be removed.
1100 if (!position.rendersInDifferentPosition(destination.deepEquivalent())) {
1101 prune(node, destinationNode);
1102 return;
1103 }
1104 removeNodeAndPruneAncestors(node, destinationNode);
1105 }
1106 else if (lineBreakExistsAtPosition(position)) {
1107 // There is a preserved '\n' at caretAfterDelete.
1108 // We can safely assume this is a text node.
1109 Text* textNode = toText(node);
1110 if (textNode->length() == 1)
1111 removeNodeAndPruneAncestors(node, destinationNode);
1112 else
1113 deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1);
1114 }
1115 }
1116 }
1117
1118 // This is a version of moveParagraph that preserves style by keeping the original markup
1119 // It is currently used only by IndentOutdentCommand but it is meant to be used in the
1120 // future by several other commands such as InsertList and the align commands.
1121 // The blockElement parameter is the element to move the paragraph to,
1122 // outerNode is the top element of the paragraph hierarchy.
1123
moveParagraphWithClones(const VisiblePosition & startOfParagraphToMove,const VisiblePosition & endOfParagraphToMove,Element * blockElement,Node * outerNode)1124 void CompositeEditCommand::moveParagraphWithClones(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, Element* blockElement, Node* outerNode)
1125 {
1126 ASSERT(outerNode);
1127 ASSERT(blockElement);
1128
1129 VisiblePosition beforeParagraph = startOfParagraphToMove.previous();
1130 VisiblePosition afterParagraph(endOfParagraphToMove.next());
1131
1132 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move.
1133 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered.
1134 Position start = startOfParagraphToMove.deepEquivalent().downstream();
1135 Position end = endOfParagraphToMove.deepEquivalent().upstream();
1136
1137 cloneParagraphUnderNewElement(start, end, outerNode, blockElement);
1138
1139 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM));
1140 deleteSelection(false, false, false, false);
1141
1142 // There are bugs in deletion when it removes a fully selected table/list.
1143 // It expands and removes the entire table/list, but will let content
1144 // before and after the table/list collapse onto one line.
1145
1146 cleanupAfterDeletion();
1147
1148 // Add a br if pruning an empty block level element caused a collapse. For example:
1149 // foo^
1150 // <div>bar</div>
1151 // baz
1152 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would
1153 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br.
1154 // Must recononicalize these two VisiblePositions after the pruning above.
1155 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent());
1156 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent());
1157
1158 if (beforeParagraph.isNotNull() && !isRenderedTable(beforeParagraph.deepEquivalent().deprecatedNode())
1159 && ((!isEndOfParagraph(beforeParagraph) && !isStartOfParagraph(beforeParagraph)) || beforeParagraph == afterParagraph)) {
1160 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal.
1161 insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent());
1162 }
1163 }
1164
moveParagraph(const VisiblePosition & startOfParagraphToMove,const VisiblePosition & endOfParagraphToMove,const VisiblePosition & destination,bool preserveSelection,bool preserveStyle,Node * constrainingAncestor)1165 void CompositeEditCommand::moveParagraph(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle, Node* constrainingAncestor)
1166 {
1167 ASSERT(isStartOfParagraph(startOfParagraphToMove));
1168 ASSERT(isEndOfParagraph(endOfParagraphToMove));
1169 moveParagraphs(startOfParagraphToMove, endOfParagraphToMove, destination, preserveSelection, preserveStyle, constrainingAncestor);
1170 }
1171
moveParagraphs(const VisiblePosition & startOfParagraphToMove,const VisiblePosition & endOfParagraphToMove,const VisiblePosition & destination,bool preserveSelection,bool preserveStyle,Node * constrainingAncestor)1172 void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle, Node* constrainingAncestor)
1173 {
1174 if (startOfParagraphToMove == destination)
1175 return;
1176
1177 int startIndex = -1;
1178 int endIndex = -1;
1179 int destinationIndex = -1;
1180 bool originalIsDirectional = endingSelection().isDirectional();
1181 if (preserveSelection && !endingSelection().isNone()) {
1182 VisiblePosition visibleStart = endingSelection().visibleStart();
1183 VisiblePosition visibleEnd = endingSelection().visibleEnd();
1184
1185 bool startAfterParagraph = comparePositions(visibleStart, endOfParagraphToMove) > 0;
1186 bool endBeforeParagraph = comparePositions(visibleEnd, startOfParagraphToMove) < 0;
1187
1188 if (!startAfterParagraph && !endBeforeParagraph) {
1189 bool startInParagraph = comparePositions(visibleStart, startOfParagraphToMove) >= 0;
1190 bool endInParagraph = comparePositions(visibleEnd, endOfParagraphToMove) <= 0;
1191
1192 startIndex = 0;
1193 if (startInParagraph) {
1194 RefPtr<Range> startRange = Range::create(document(), startOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent(), visibleStart.deepEquivalent().parentAnchoredEquivalent());
1195 startIndex = TextIterator::rangeLength(startRange.get(), true);
1196 }
1197
1198 endIndex = 0;
1199 if (endInParagraph) {
1200 RefPtr<Range> endRange = Range::create(document(), startOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent(), visibleEnd.deepEquivalent().parentAnchoredEquivalent());
1201 endIndex = TextIterator::rangeLength(endRange.get(), true);
1202 }
1203 }
1204 }
1205
1206 VisiblePosition beforeParagraph = startOfParagraphToMove.previous(CannotCrossEditingBoundary);
1207 VisiblePosition afterParagraph(endOfParagraphToMove.next(CannotCrossEditingBoundary));
1208
1209 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move.
1210 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered.
1211 Position start = startOfParagraphToMove.deepEquivalent().downstream();
1212 Position end = endOfParagraphToMove.deepEquivalent().upstream();
1213
1214 // start and end can't be used directly to create a Range; they are "editing positions"
1215 Position startRangeCompliant = start.parentAnchoredEquivalent();
1216 Position endRangeCompliant = end.parentAnchoredEquivalent();
1217 RefPtr<Range> range = Range::create(document(), startRangeCompliant.deprecatedNode(), startRangeCompliant.deprecatedEditingOffset(), endRangeCompliant.deprecatedNode(), endRangeCompliant.deprecatedEditingOffset());
1218
1219 // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It
1220 // shouldn't matter though, since moved paragraphs will usually be quite small.
1221 RefPtr<DocumentFragment> fragment = startOfParagraphToMove != endOfParagraphToMove ?
1222 createFragmentFromMarkup(document(), createMarkup(range.get(), 0, DoNotAnnotateForInterchange, true, DoNotResolveURLs, constrainingAncestor), "") : 0;
1223
1224 // A non-empty paragraph's style is moved when we copy and move it. We don't move
1225 // anything if we're given an empty paragraph, but an empty paragraph can have style
1226 // too, <div><b><br></b></div> for example. Save it so that we can preserve it later.
1227 RefPtr<EditingStyle> styleInEmptyParagraph;
1228 if (startOfParagraphToMove == endOfParagraphToMove && preserveStyle) {
1229 styleInEmptyParagraph = EditingStyle::create(startOfParagraphToMove.deepEquivalent());
1230 styleInEmptyParagraph->mergeTypingStyle(&document());
1231 // The moved paragraph should assume the block style of the destination.
1232 styleInEmptyParagraph->removeBlockProperties();
1233 }
1234
1235 // FIXME (5098931): We should add a new insert action "WebViewInsertActionMoved" and call shouldInsertFragment here.
1236
1237 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM));
1238 document().frame()->spellChecker().clearMisspellingsAndBadGrammar(endingSelection());
1239 deleteSelection(false, false, false, false);
1240
1241 ASSERT(destination.deepEquivalent().inDocument());
1242 cleanupAfterDeletion(destination);
1243 ASSERT(destination.deepEquivalent().inDocument());
1244
1245 // Add a br if pruning an empty block level element caused a collapse. For example:
1246 // foo^
1247 // <div>bar</div>
1248 // baz
1249 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would
1250 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br.
1251 // Must recononicalize these two VisiblePositions after the pruning above.
1252 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent());
1253 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent());
1254 if (beforeParagraph.isNotNull() && (!isEndOfParagraph(beforeParagraph) || beforeParagraph == afterParagraph)) {
1255 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal.
1256 insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent());
1257 // Need an updateLayout here in case inserting the br has split a text node.
1258 document().updateLayoutIgnorePendingStylesheets();
1259 }
1260
1261 RefPtr<Range> startToDestinationRange(Range::create(document(), firstPositionInNode(document().documentElement()), destination.deepEquivalent().parentAnchoredEquivalent()));
1262 destinationIndex = TextIterator::rangeLength(startToDestinationRange.get(), true);
1263
1264 setEndingSelection(VisibleSelection(destination, originalIsDirectional));
1265 ASSERT(endingSelection().isCaretOrRange());
1266 ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::MovingParagraph;
1267 if (!preserveStyle)
1268 options |= ReplaceSelectionCommand::MatchStyle;
1269 applyCommandToComposite(ReplaceSelectionCommand::create(document(), fragment, options));
1270
1271 document().frame()->spellChecker().markMisspellingsAndBadGrammar(endingSelection());
1272
1273 // If the selection is in an empty paragraph, restore styles from the old empty paragraph to the new empty paragraph.
1274 bool selectionIsEmptyParagraph = endingSelection().isCaret() && isStartOfParagraph(endingSelection().visibleStart()) && isEndOfParagraph(endingSelection().visibleStart());
1275 if (styleInEmptyParagraph && selectionIsEmptyParagraph)
1276 applyStyle(styleInEmptyParagraph.get());
1277
1278 if (preserveSelection && startIndex != -1) {
1279 // Fragment creation (using createMarkup) incorrectly uses regular
1280 // spaces instead of nbsps for some spaces that were rendered (11475), which
1281 // causes spaces to be collapsed during the move operation. This results
1282 // in a call to rangeFromLocationAndLength with a location past the end
1283 // of the document (which will return null).
1284 RefPtr<Range> start = PlainTextRange(destinationIndex + startIndex).createRangeForSelection(*document().documentElement());
1285 RefPtr<Range> end = PlainTextRange(destinationIndex + endIndex).createRangeForSelection(*document().documentElement());
1286 if (start && end)
1287 setEndingSelection(VisibleSelection(start->startPosition(), end->startPosition(), DOWNSTREAM, originalIsDirectional));
1288 }
1289 }
1290
1291 // FIXME: Send an appropriate shouldDeleteRange call.
breakOutOfEmptyListItem()1292 bool CompositeEditCommand::breakOutOfEmptyListItem()
1293 {
1294 RefPtr<Node> emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart());
1295 if (!emptyListItem)
1296 return false;
1297
1298 RefPtr<EditingStyle> style = EditingStyle::create(endingSelection().start());
1299 style->mergeTypingStyle(&document());
1300
1301 RefPtr<ContainerNode> listNode = emptyListItem->parentNode();
1302 // FIXME: Can't we do something better when the immediate parent wasn't a list node?
1303 if (!listNode
1304 || (!listNode->hasTagName(ulTag) && !listNode->hasTagName(olTag))
1305 || !listNode->rendererIsEditable()
1306 || listNode == emptyListItem->rootEditableElement())
1307 return false;
1308
1309 RefPtr<Element> newBlock = 0;
1310 if (ContainerNode* blockEnclosingList = listNode->parentNode()) {
1311 if (blockEnclosingList->hasTagName(liTag)) { // listNode is inside another list item
1312 if (visiblePositionAfterNode(blockEnclosingList) == visiblePositionAfterNode(listNode.get())) {
1313 // If listNode appears at the end of the outer list item, then move listNode outside of this list item
1314 // 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
1315 // If listNode does NOT appear at the end, then we should consider it as a regular paragraph.
1316 // 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
1317 splitElement(toElement(blockEnclosingList), listNode);
1318 removeNodePreservingChildren(listNode->parentNode());
1319 newBlock = createListItemElement(document());
1320 }
1321 // If listNode does NOT appear at the end of the outer list item, then behave as if in a regular paragraph.
1322 } else if (blockEnclosingList->hasTagName(olTag) || blockEnclosingList->hasTagName(ulTag)) {
1323 newBlock = createListItemElement(document());
1324 }
1325 }
1326 if (!newBlock)
1327 newBlock = createDefaultParagraphElement(document());
1328
1329 RefPtr<Node> previousListNode = emptyListItem->isElementNode() ? toElement(emptyListItem)->previousElementSibling(): emptyListItem->previousSibling();
1330 RefPtr<Node> nextListNode = emptyListItem->isElementNode() ? toElement(emptyListItem)->nextElementSibling(): emptyListItem->nextSibling();
1331 if (isListItem(nextListNode.get()) || isListElement(nextListNode.get())) {
1332 // If emptyListItem follows another list item or nested list, split the list node.
1333 if (isListItem(previousListNode.get()) || isListElement(previousListNode.get()))
1334 splitElement(toElement(listNode), emptyListItem);
1335
1336 // If emptyListItem is followed by other list item or nested list, then insert newBlock before the list node.
1337 // Because we have splitted the element, emptyListItem is the first element in the list node.
1338 // i.e. insert newBlock before ul or ol whose first element is emptyListItem
1339 insertNodeBefore(newBlock, listNode);
1340 removeNode(emptyListItem);
1341 } else {
1342 // When emptyListItem does not follow any list item or nested list, insert newBlock after the enclosing list node.
1343 // Remove the enclosing node if emptyListItem is the only child; otherwise just remove emptyListItem.
1344 insertNodeAfter(newBlock, listNode);
1345 removeNode(isListItem(previousListNode.get()) || isListElement(previousListNode.get()) ? emptyListItem.get() : listNode.get());
1346 }
1347
1348 appendBlockPlaceholder(newBlock);
1349 setEndingSelection(VisibleSelection(firstPositionInNode(newBlock.get()), DOWNSTREAM, endingSelection().isDirectional()));
1350
1351 style->prepareToApplyAt(endingSelection().start());
1352 if (!style->isEmpty())
1353 applyStyle(style.get());
1354
1355 return true;
1356 }
1357
1358 // If the caret is in an empty quoted paragraph, and either there is nothing before that
1359 // paragraph, or what is before is unquoted, and the user presses delete, unquote that paragraph.
breakOutOfEmptyMailBlockquotedParagraph()1360 bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph()
1361 {
1362 if (!endingSelection().isCaret())
1363 return false;
1364
1365 VisiblePosition caret(endingSelection().visibleStart());
1366 Node* highestBlockquote = highestEnclosingNodeOfType(caret.deepEquivalent(), &isMailBlockquote);
1367 if (!highestBlockquote)
1368 return false;
1369
1370 if (!isStartOfParagraph(caret) || !isEndOfParagraph(caret))
1371 return false;
1372
1373 VisiblePosition previous(caret.previous(CannotCrossEditingBoundary));
1374 // Only move forward if there's nothing before the caret, or if there's unquoted content before it.
1375 if (enclosingNodeOfType(previous.deepEquivalent(), &isMailBlockquote))
1376 return false;
1377
1378 RefPtr<Node> br = createBreakElement(document());
1379 // We want to replace this quoted paragraph with an unquoted one, so insert a br
1380 // to hold the caret before the highest blockquote.
1381 insertNodeBefore(br, highestBlockquote);
1382 VisiblePosition atBR(positionBeforeNode(br.get()));
1383 // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert
1384 // a second one.
1385 if (!isStartOfParagraph(atBR))
1386 insertNodeBefore(createBreakElement(document()), br);
1387 setEndingSelection(VisibleSelection(atBR, endingSelection().isDirectional()));
1388
1389 // If this is an empty paragraph there must be a line break here.
1390 if (!lineBreakExistsAtVisiblePosition(caret))
1391 return false;
1392
1393 Position caretPos(caret.deepEquivalent().downstream());
1394 // A line break is either a br or a preserved newline.
1395 ASSERT(caretPos.deprecatedNode()->hasTagName(brTag) || (caretPos.deprecatedNode()->isTextNode() && caretPos.deprecatedNode()->renderer()->style()->preserveNewline()));
1396
1397 if (caretPos.deprecatedNode()->hasTagName(brTag))
1398 removeNodeAndPruneAncestors(caretPos.deprecatedNode());
1399 else if (caretPos.deprecatedNode()->isTextNode()) {
1400 ASSERT(caretPos.deprecatedEditingOffset() == 0);
1401 Text* textNode = toText(caretPos.deprecatedNode());
1402 ContainerNode* parentNode = textNode->parentNode();
1403 // The preserved newline must be the first thing in the node, since otherwise the previous
1404 // paragraph would be quoted, and we verified that it wasn't above.
1405 deleteTextFromNode(textNode, 0, 1);
1406 prune(parentNode);
1407 }
1408
1409 return true;
1410 }
1411
1412 // Operations use this function to avoid inserting content into an anchor when at the start or the end of
1413 // that anchor, as in NSTextView.
1414 // FIXME: This is only an approximation of NSTextViews insertion behavior, which varies depending on how
1415 // the caret was made.
positionAvoidingSpecialElementBoundary(const Position & original)1416 Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Position& original)
1417 {
1418 if (original.isNull())
1419 return original;
1420
1421 VisiblePosition visiblePos(original);
1422 Node* enclosingAnchor = enclosingAnchorElement(original);
1423 Position result = original;
1424
1425 if (!enclosingAnchor)
1426 return result;
1427
1428 // Don't avoid block level anchors, because that would insert content into the wrong paragraph.
1429 if (enclosingAnchor && !isBlock(enclosingAnchor)) {
1430 VisiblePosition firstInAnchor(firstPositionInNode(enclosingAnchor));
1431 VisiblePosition lastInAnchor(lastPositionInNode(enclosingAnchor));
1432 // If visually just after the anchor, insert *inside* the anchor unless it's the last
1433 // VisiblePosition in the document, to match NSTextView.
1434 if (visiblePos == lastInAnchor) {
1435 // Make sure anchors are pushed down before avoiding them so that we don't
1436 // also avoid structural elements like lists and blocks (5142012).
1437 if (original.deprecatedNode() != enclosingAnchor && original.deprecatedNode()->parentNode() != enclosingAnchor) {
1438 pushAnchorElementDown(enclosingAnchor);
1439 enclosingAnchor = enclosingAnchorElement(original);
1440 if (!enclosingAnchor)
1441 return original;
1442 }
1443 // Don't insert outside an anchor if doing so would skip over a line break. It would
1444 // probably be safe to move the line break so that we could still avoid the anchor here.
1445 Position downstream(visiblePos.deepEquivalent().downstream());
1446 if (lineBreakExistsAtVisiblePosition(visiblePos) && downstream.deprecatedNode()->isDescendantOf(enclosingAnchor))
1447 return original;
1448
1449 result = positionInParentAfterNode(enclosingAnchor);
1450 }
1451 // If visually just before an anchor, insert *outside* the anchor unless it's the first
1452 // VisiblePosition in a paragraph, to match NSTextView.
1453 if (visiblePos == firstInAnchor) {
1454 // Make sure anchors are pushed down before avoiding them so that we don't
1455 // also avoid structural elements like lists and blocks (5142012).
1456 if (original.deprecatedNode() != enclosingAnchor && original.deprecatedNode()->parentNode() != enclosingAnchor) {
1457 pushAnchorElementDown(enclosingAnchor);
1458 enclosingAnchor = enclosingAnchorElement(original);
1459 }
1460 if (!enclosingAnchor)
1461 return original;
1462
1463 result = positionInParentBeforeNode(enclosingAnchor);
1464 }
1465 }
1466
1467 if (result.isNull() || !editableRootForPosition(result))
1468 result = original;
1469
1470 return result;
1471 }
1472
1473 // Splits the tree parent by parent until we reach the specified ancestor. We use VisiblePositions
1474 // to determine if the split is necessary. Returns the last split node.
splitTreeToNode(Node * start,Node * end,bool shouldSplitAncestor)1475 PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool shouldSplitAncestor)
1476 {
1477 ASSERT(start);
1478 ASSERT(end);
1479 ASSERT(start != end);
1480
1481 RefPtr<Node> node;
1482 if (shouldSplitAncestor && end->parentNode())
1483 end = end->parentNode();
1484
1485 RefPtr<Node> endNode = end;
1486 for (node = start; node && node->parentNode() != endNode; node = node->parentNode()) {
1487 if (!node->parentNode()->isElementNode())
1488 break;
1489 // Do not split a node when doing so introduces an empty node.
1490 VisiblePosition positionInParent = firstPositionInNode(node->parentNode());
1491 VisiblePosition positionInNode = firstPositionInOrBeforeNode(node.get());
1492 if (positionInParent != positionInNode)
1493 splitElement(toElement(node->parentNode()), node);
1494 }
1495
1496 return node.release();
1497 }
1498
createBlockPlaceholderElement(Document & document)1499 PassRefPtr<Element> createBlockPlaceholderElement(Document& document)
1500 {
1501 RefPtr<Element> breakNode = document.createElement(brTag, false);
1502 return breakNode.release();
1503 }
1504
1505 } // namespace WebCore
1506