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