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