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 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 INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "CompositeEditCommand.h"
28
29#include "AXObjectCache.h"
30#include "AppendNodeCommand.h"
31#include "ApplyStyleCommand.h"
32#include "BreakBlockquoteCommand.h"
33#include "DataTransfer.h"
34#include "DeleteFromTextNodeCommand.h"
35#include "DeleteSelectionCommand.h"
36#include "Document.h"
37#include "DocumentFragment.h"
38#include "DocumentMarkerController.h"
39#include "Editing.h"
40#include "Editor.h"
41#include "EditorInsertAction.h"
42#include "ElementTraversal.h"
43#include "Event.h"
44#include "Frame.h"
45#include "HTMLBRElement.h"
46#include "HTMLDivElement.h"
47#include "HTMLLIElement.h"
48#include "HTMLNames.h"
49#include "HTMLSpanElement.h"
50#include "InlineTextBox.h"
51#include "InsertIntoTextNodeCommand.h"
52#include "InsertLineBreakCommand.h"
53#include "InsertNodeBeforeCommand.h"
54#include "InsertParagraphSeparatorCommand.h"
55#include "InsertTextCommand.h"
56#include "MergeIdenticalElementsCommand.h"
57#include "NodeTraversal.h"
58#include "Range.h"
59#include "RemoveNodeCommand.h"
60#include "RemoveNodePreservingChildrenCommand.h"
61#include "RenderBlockFlow.h"
62#include "RenderText.h"
63#include "RenderedDocumentMarker.h"
64#include "ReplaceNodeWithSpanCommand.h"
65#include "ReplaceSelectionCommand.h"
66#include "ScopedEventQueue.h"
67#include "SetNodeAttributeCommand.h"
68#include "SplitElementCommand.h"
69#include "SplitTextNodeCommand.h"
70#include "SplitTextNodeContainingElementCommand.h"
71#include "StaticRange.h"
72#include "Text.h"
73#include "TextIterator.h"
74#include "VisibleUnits.h"
75#include "WrapContentsInDummySpanCommand.h"
76#include "markup.h"
77
78namespace WebCore {
79
80using namespace HTMLNames;
81
82int AccessibilityUndoReplacedText::indexForVisiblePosition(const VisiblePosition& position, RefPtr<ContainerNode>& scope) const
83{
84 if (position.deepEquivalent().isNull())
85 return -1;
86 return WebCore::indexForVisiblePosition(position, scope);
87}
88
89void AccessibilityUndoReplacedText::configureRangeDeletedByReapplyWithEndingSelection(const VisibleSelection& selection)
90{
91 if (!AXObjectCache::accessibilityEnabled())
92 return;
93 if (selection.isNone())
94 return;
95 m_rangeDeletedByReapply.endIndex.value = indexForVisiblePosition(selection.end(), m_rangeDeletedByReapply.endIndex.scope);
96}
97
98void AccessibilityUndoReplacedText::configureRangeDeletedByReapplyWithStartingSelection(const VisibleSelection& selection)
99{
100 if (!AXObjectCache::accessibilityEnabled())
101 return;
102 if (selection.isNone())
103 return;
104 if (m_rangeDeletedByReapply.startIndex.value == -1)
105 m_rangeDeletedByReapply.startIndex.value = indexForVisiblePosition(selection.start(), m_rangeDeletedByReapply.startIndex.scope);
106}
107
108void AccessibilityUndoReplacedText::setRangeDeletedByUnapply(const VisiblePositionIndexRange& range)
109{
110 if (m_rangeDeletedByUnapply.isNull())
111 m_rangeDeletedByUnapply = range;
112}
113
114void AccessibilityUndoReplacedText::captureTextForUnapply()
115{
116 if (!AXObjectCache::accessibilityEnabled())
117 return;
118 m_replacedText = textDeletedByReapply();
119}
120
121void AccessibilityUndoReplacedText::captureTextForReapply()
122{
123 if (!AXObjectCache::accessibilityEnabled())
124 return;
125 m_replacedText = textDeletedByUnapply();
126}
127
128static String stringForVisiblePositionIndexRange(const VisiblePositionIndexRange& range)
129{
130 if (range.isNull())
131 return String();
132 VisiblePosition start = visiblePositionForIndex(range.startIndex.value, range.startIndex.scope.get());
133 VisiblePosition end = visiblePositionForIndex(range.endIndex.value, range.endIndex.scope.get());
134 return AccessibilityObject::stringForVisiblePositionRange(VisiblePositionRange(start, end));
135}
136
137String AccessibilityUndoReplacedText::textDeletedByUnapply()
138{
139 if (!AXObjectCache::accessibilityEnabled())
140 return String();
141 return stringForVisiblePositionIndexRange(m_rangeDeletedByUnapply);
142}
143
144String AccessibilityUndoReplacedText::textDeletedByReapply()
145{
146 if (!AXObjectCache::accessibilityEnabled())
147 return String();
148 return stringForVisiblePositionIndexRange(m_rangeDeletedByReapply);
149}
150
151static void postTextStateChangeNotification(AXObjectCache* cache, const VisiblePosition& position, const String& deletedText, const String& insertedText)
152{
153 ASSERT(cache);
154 auto* node = highestEditableRoot(position.deepEquivalent(), HasEditableAXRole);
155 if (!node)
156 return;
157 if (insertedText.length() && deletedText.length())
158 cache->postTextReplacementNotification(node, AXTextEditTypeDelete, insertedText, AXTextEditTypeInsert, deletedText, position);
159 else if (deletedText.length())
160 cache->postTextStateChangeNotification(node, AXTextEditTypeInsert, deletedText, position);
161 else if (insertedText.length())
162 cache->postTextStateChangeNotification(node, AXTextEditTypeDelete, insertedText, position);
163}
164
165void AccessibilityUndoReplacedText::postTextStateChangeNotificationForUnapply(AXObjectCache* cache)
166{
167 if (!cache)
168 return;
169 if (!AXObjectCache::accessibilityEnabled())
170 return;
171 if (m_rangeDeletedByUnapply.isNull())
172 return;
173 VisiblePosition position = visiblePositionForIndex(m_rangeDeletedByUnapply.endIndex.value, m_rangeDeletedByUnapply.endIndex.scope.get());
174 if (position.isNull())
175 return;
176 postTextStateChangeNotification(cache, position, textDeletedByUnapply(), m_replacedText);
177 m_replacedText = String();
178}
179
180void AccessibilityUndoReplacedText::postTextStateChangeNotificationForReapply(AXObjectCache* cache)
181{
182 if (!cache)
183 return;
184 if (!AXObjectCache::accessibilityEnabled())
185 return;
186 if (m_rangeDeletedByReapply.isNull())
187 return;
188 VisiblePosition position = visiblePositionForIndex(m_rangeDeletedByReapply.startIndex.value, m_rangeDeletedByReapply.startIndex.scope.get());
189 if (position.isNull())
190 return;
191 postTextStateChangeNotification(cache, position, textDeletedByReapply(), m_replacedText);
192 m_replacedText = String();
193}
194
195Ref<EditCommandComposition> EditCommandComposition::create(Document& document,
196 const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction editAction)
197{
198 return adoptRef(*new EditCommandComposition(document, startingSelection, endingSelection, editAction));
199}
200
201EditCommandComposition::EditCommandComposition(Document& document, const VisibleSelection& startingSelection, const VisibleSelection& endingSelection, EditAction editAction)
202 : m_document(&document)
203 , m_startingSelection(startingSelection)
204 , m_endingSelection(endingSelection)
205 , m_startingRootEditableElement(startingSelection.rootEditableElement())
206 , m_endingRootEditableElement(endingSelection.rootEditableElement())
207 , m_editAction(editAction)
208{
209 m_replacedText.configureRangeDeletedByReapplyWithStartingSelection(startingSelection);
210}
211
212void EditCommandComposition::unapply()
213{
214 ASSERT(m_document);
215 RefPtr<Frame> frame = m_document->frame();
216 if (!frame)
217 return;
218
219 m_replacedText.captureTextForUnapply();
220
221 // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>.
222 // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one
223 // if one is necessary (like for the creation of VisiblePositions).
224 m_document->updateLayoutIgnorePendingStylesheets();
225#if PLATFORM(IOS_FAMILY)
226 // FIXME: Where should iPhone code deal with the composition?
227 // Since editing commands don't save/restore the composition, undoing without fixing
228 // up the composition will leave a stale, invalid composition, as in <rdar://problem/6831637>.
229 // Desktop handles this in -[WebHTMLView _updateSelectionForInputManager], but the phone
230 // goes another route.
231 frame->editor().cancelComposition();
232#endif
233
234 if (!frame->editor().willUnapplyEditing(*this))
235 return;
236
237 size_t size = m_commands.size();
238 for (size_t i = size; i; --i)
239 m_commands[i - 1]->doUnapply();
240
241 frame->editor().unappliedEditing(*this);
242
243 if (AXObjectCache::accessibilityEnabled())
244 m_replacedText.postTextStateChangeNotificationForUnapply(m_document->existingAXObjectCache());
245}
246
247void EditCommandComposition::reapply()
248{
249 ASSERT(m_document);
250 RefPtr<Frame> frame = m_document->frame();
251 if (!frame)
252 return;
253
254 m_replacedText.captureTextForReapply();
255
256 // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>.
257 // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one
258 // if one is necessary (like for the creation of VisiblePositions).
259 m_document->updateLayoutIgnorePendingStylesheets();
260
261 if (!frame->editor().willReapplyEditing(*this))
262 return;
263
264 for (auto& command : m_commands)
265 command->doReapply();
266
267 frame->editor().reappliedEditing(*this);
268
269 if (AXObjectCache::accessibilityEnabled())
270 m_replacedText.postTextStateChangeNotificationForReapply(m_document->existingAXObjectCache());
271}
272
273void EditCommandComposition::append(SimpleEditCommand* command)
274{
275 m_commands.append(command);
276}
277
278void EditCommandComposition::setStartingSelection(const VisibleSelection& selection)
279{
280 m_startingSelection = selection;
281 m_startingRootEditableElement = selection.rootEditableElement();
282 m_replacedText.configureRangeDeletedByReapplyWithStartingSelection(selection);
283}
284
285void EditCommandComposition::setEndingSelection(const VisibleSelection& selection)
286{
287 m_endingSelection = selection;
288 m_endingRootEditableElement = selection.rootEditableElement();
289 m_replacedText.configureRangeDeletedByReapplyWithEndingSelection(selection);
290}
291
292void EditCommandComposition::setRangeDeletedByUnapply(const VisiblePositionIndexRange& range)
293{
294 m_replacedText.setRangeDeletedByUnapply(range);
295}
296
297#ifndef NDEBUG
298void EditCommandComposition::getNodesInCommand(HashSet<Node*>& nodes)
299{
300 for (auto& command : m_commands)
301 command->getNodesInCommand(nodes);
302}
303#endif
304
305String EditCommandComposition::label() const
306{
307 return undoRedoLabel(m_editAction);
308}
309
310CompositeEditCommand::CompositeEditCommand(Document& document, EditAction editingAction)
311 : EditCommand(document, editingAction)
312{
313}
314
315CompositeEditCommand::~CompositeEditCommand()
316{
317 ASSERT(isTopLevelCommand() || !m_composition);
318}
319
320bool CompositeEditCommand::willApplyCommand()
321{
322 return frame().editor().willApplyEditing(*this, targetRangesForBindings());
323}
324
325void CompositeEditCommand::apply()
326{
327 if (!endingSelection().isContentRichlyEditable()) {
328 switch (editingAction()) {
329 case EditAction::TypingDeleteSelection:
330 case EditAction::TypingDeleteBackward:
331 case EditAction::TypingDeleteForward:
332 case EditAction::TypingDeleteWordBackward:
333 case EditAction::TypingDeleteWordForward:
334 case EditAction::TypingDeleteLineBackward:
335 case EditAction::TypingDeleteLineForward:
336 case EditAction::TypingDeletePendingComposition:
337 case EditAction::TypingDeleteFinalComposition:
338 case EditAction::TypingInsertText:
339 case EditAction::TypingInsertLineBreak:
340 case EditAction::TypingInsertParagraph:
341 case EditAction::TypingInsertPendingComposition:
342 case EditAction::TypingInsertFinalComposition:
343 case EditAction::Paste:
344 case EditAction::DeleteByDrag:
345 case EditAction::SetInlineWritingDirection:
346 case EditAction::SetBlockWritingDirection:
347 case EditAction::Cut:
348 case EditAction::Unspecified:
349 case EditAction::Insert:
350 case EditAction::InsertReplacement:
351 case EditAction::InsertFromDrop:
352 case EditAction::Delete:
353 case EditAction::Dictation:
354 break;
355 default:
356 ASSERT_NOT_REACHED();
357 return;
358 }
359 }
360 ensureComposition();
361
362 // Changes to the document may have been made since the last editing operation that require a layout, as in <rdar://problem/5658603>.
363 // Low level operations, like RemoveNodeCommand, don't require a layout because the high level operations that use them perform one
364 // if one is necessary (like for the creation of VisiblePositions).
365 document().updateLayoutIgnorePendingStylesheets();
366
367 if (!willApplyCommand())
368 return;
369
370 {
371 EventQueueScope eventQueueScope;
372 doApply();
373 }
374
375 didApplyCommand();
376 setShouldRetainAutocorrectionIndicator(false);
377}
378
379void CompositeEditCommand::didApplyCommand()
380{
381 frame().editor().appliedEditing(*this);
382}
383
384Vector<RefPtr<StaticRange>> CompositeEditCommand::targetRanges() const
385{
386 ASSERT(!isEditingTextAreaOrTextInput());
387 auto firstRange = frame().selection().selection().firstRange();
388 if (!firstRange)
389 return { };
390
391 return { 1, StaticRange::createFromRange(*firstRange) };
392}
393
394Vector<RefPtr<StaticRange>> CompositeEditCommand::targetRangesForBindings() const
395{
396 if (isEditingTextAreaOrTextInput())
397 return { };
398
399 return targetRanges();
400}
401
402RefPtr<DataTransfer> CompositeEditCommand::inputEventDataTransfer() const
403{
404 return nullptr;
405}
406
407EditCommandComposition* CompositeEditCommand::composition() const
408{
409 for (auto* command = this; command; command = command->parent()) {
410 if (auto composition = command->m_composition) {
411 ASSERT(!command->parent());
412 return composition.get();
413 }
414 }
415 return nullptr;
416}
417
418EditCommandComposition& CompositeEditCommand::ensureComposition()
419{
420 auto* command = this;
421 while (auto* parent = command->parent())
422 command = parent;
423 if (!command->m_composition)
424 command->m_composition = EditCommandComposition::create(document(), startingSelection(), endingSelection(), editingAction());
425 return *command->m_composition;
426}
427
428bool CompositeEditCommand::isCreateLinkCommand() const
429{
430 return false;
431}
432
433bool CompositeEditCommand::preservesTypingStyle() const
434{
435 return false;
436}
437
438bool CompositeEditCommand::isTypingCommand() const
439{
440 return false;
441}
442
443bool CompositeEditCommand::shouldRetainAutocorrectionIndicator() const
444{
445 return false;
446}
447
448void CompositeEditCommand::setShouldRetainAutocorrectionIndicator(bool)
449{
450}
451
452String CompositeEditCommand::inputEventTypeName() const
453{
454 return inputTypeNameForEditingAction(editingAction());
455}
456
457//
458// sugary-sweet convenience functions to help create and apply edit commands in composite commands
459//
460void CompositeEditCommand::applyCommandToComposite(Ref<EditCommand>&& command)
461{
462 command->setParent(this);
463 command->doApply();
464 if (command->isSimpleEditCommand()) {
465 command->setParent(nullptr);
466 ensureComposition().append(toSimpleEditCommand(command.ptr()));
467 }
468 m_commands.append(WTFMove(command));
469}
470
471void CompositeEditCommand::applyCommandToComposite(Ref<CompositeEditCommand>&& command, const VisibleSelection& selection)
472{
473 command->setParent(this);
474 if (selection != command->endingSelection()) {
475 command->setStartingSelection(selection);
476 command->setEndingSelection(selection);
477 }
478 command->doApply();
479 m_commands.append(WTFMove(command));
480}
481
482void CompositeEditCommand::applyStyle(const EditingStyle* style, EditAction editingAction)
483{
484 applyCommandToComposite(ApplyStyleCommand::create(document(), style, editingAction));
485}
486
487void CompositeEditCommand::applyStyle(const EditingStyle* style, const Position& start, const Position& end, EditAction editingAction)
488{
489 applyCommandToComposite(ApplyStyleCommand::create(document(), style, start, end, editingAction));
490}
491
492void CompositeEditCommand::applyStyledElement(Ref<Element>&& element)
493{
494 applyCommandToComposite(ApplyStyleCommand::create(WTFMove(element), false));
495}
496
497void CompositeEditCommand::removeStyledElement(Ref<Element>&& element)
498{
499 applyCommandToComposite(ApplyStyleCommand::create(WTFMove(element), true));
500}
501
502void CompositeEditCommand::insertParagraphSeparator(bool useDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea)
503{
504 applyCommandToComposite(InsertParagraphSeparatorCommand::create(document(), useDefaultParagraphElement, pasteBlockqutoeIntoUnquotedArea, editingAction()));
505}
506
507void CompositeEditCommand::insertLineBreak()
508{
509 applyCommandToComposite(InsertLineBreakCommand::create(document()));
510}
511
512bool CompositeEditCommand::isRemovableBlock(const Node* node)
513{
514 ASSERT(node);
515 if (!is<HTMLDivElement>(*node))
516 return false;
517
518 Node* parentNode = node->parentNode();
519 if (parentNode && parentNode->firstChild() != parentNode->lastChild())
520 return false;
521
522 if (!downcast<HTMLDivElement>(*node).hasAttributes())
523 return true;
524
525 return false;
526}
527
528void CompositeEditCommand::insertNodeBefore(Ref<Node>&& insertChild, Node& refChild, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)
529{
530 applyCommandToComposite(InsertNodeBeforeCommand::create(WTFMove(insertChild), refChild, shouldAssumeContentIsAlwaysEditable, editingAction()));
531}
532
533void CompositeEditCommand::insertNodeAfter(Ref<Node>&& insertChild, Node& refChild)
534{
535 ContainerNode* parent = refChild.parentNode();
536 if (!parent)
537 return;
538
539 ASSERT(!parent->isShadowRoot());
540 if (parent->lastChild() == &refChild)
541 appendNode(WTFMove(insertChild), *parent);
542 else {
543 ASSERT(refChild.nextSibling());
544 insertNodeBefore(WTFMove(insertChild), *refChild.nextSibling());
545 }
546}
547
548void CompositeEditCommand::insertNodeAt(Ref<Node>&& insertChild, const Position& editingPosition)
549{
550 ASSERT(isEditablePosition(editingPosition));
551 // For editing positions like [table, 0], insert before the table,
552 // likewise for replaced elements, brs, etc.
553 Position p = editingPosition.parentAnchoredEquivalent();
554 Node* refChild = p.deprecatedNode();
555 int offset = p.deprecatedEditingOffset();
556
557 if (canHaveChildrenForEditing(*refChild)) {
558 Node* child = refChild->firstChild();
559 for (int i = 0; child && i < offset; i++)
560 child = child->nextSibling();
561 if (child)
562 insertNodeBefore(WTFMove(insertChild), *child);
563 else
564 appendNode(WTFMove(insertChild), downcast<ContainerNode>(*refChild));
565 } else if (caretMinOffset(*refChild) >= offset)
566 insertNodeBefore(WTFMove(insertChild), *refChild);
567 else if (is<Text>(*refChild) && caretMaxOffset(*refChild) > offset) {
568 splitTextNode(downcast<Text>(*refChild), offset);
569
570 // Mutation events (bug 22634) from the text node insertion may have removed the refChild
571 if (!refChild->isConnected())
572 return;
573 insertNodeBefore(WTFMove(insertChild), *refChild);
574 } else
575 insertNodeAfter(WTFMove(insertChild), *refChild);
576}
577
578void CompositeEditCommand::appendNode(Ref<Node>&& node, Ref<ContainerNode>&& parent)
579{
580 ASSERT(canHaveChildrenForEditing(parent));
581 applyCommandToComposite(AppendNodeCommand::create(WTFMove(parent), WTFMove(node), editingAction()));
582}
583
584void CompositeEditCommand::removeChildrenInRange(Node& node, unsigned from, unsigned to)
585{
586 Vector<Ref<Node>> children;
587 Node* child = node.traverseToChildAt(from);
588 for (unsigned i = from; child && i < to; i++, child = child->nextSibling())
589 children.append(*child);
590
591 for (auto& child : children)
592 removeNode(child);
593}
594
595void CompositeEditCommand::removeNode(Node& node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)
596{
597 if (!node.nonShadowBoundaryParentNode())
598 return;
599 applyCommandToComposite(RemoveNodeCommand::create(node, shouldAssumeContentIsAlwaysEditable, editingAction()));
600}
601
602void CompositeEditCommand::removeNodePreservingChildren(Node& node, ShouldAssumeContentIsAlwaysEditable shouldAssumeContentIsAlwaysEditable)
603{
604 applyCommandToComposite(RemoveNodePreservingChildrenCommand::create(node, shouldAssumeContentIsAlwaysEditable, editingAction()));
605}
606
607void CompositeEditCommand::removeNodeAndPruneAncestors(Node& node)
608{
609 RefPtr<ContainerNode> parent = node.parentNode();
610 removeNode(node);
611 prune(parent.get());
612}
613
614void CompositeEditCommand::moveRemainingSiblingsToNewParent(Node* node, Node* pastLastNodeToMove, Element& newParent)
615{
616 NodeVector nodesToRemove;
617 Ref<Element> protectedNewParent = newParent;
618
619 for (; node && node != pastLastNodeToMove; node = node->nextSibling())
620 nodesToRemove.append(*node);
621
622 for (auto& nodeToRemove : nodesToRemove) {
623 removeNode(nodeToRemove);
624 appendNode(WTFMove(nodeToRemove), newParent);
625 }
626}
627
628void CompositeEditCommand::updatePositionForNodeRemovalPreservingChildren(Position& position, Node& node)
629{
630 int offset = (position.anchorType() == Position::PositionIsOffsetInAnchor) ? position.offsetInContainerNode() : 0;
631 updatePositionForNodeRemoval(position, node);
632 if (offset)
633 position.moveToOffset(offset);
634}
635
636HTMLElement* CompositeEditCommand::replaceElementWithSpanPreservingChildrenAndAttributes(HTMLElement& element)
637{
638 // It would also be possible to implement all of ReplaceNodeWithSpanCommand
639 // as a series of existing smaller edit commands. Someone who wanted to
640 // reduce the number of edit commands could do so here.
641 auto command = ReplaceNodeWithSpanCommand::create(element);
642 auto* commandPtr = command.ptr();
643 applyCommandToComposite(WTFMove(command));
644 // Returning a raw pointer here is OK because the command is retained by
645 // applyCommandToComposite (thus retaining the span), and the span is also
646 // in the DOM tree, and thus alive whie it has a parent.
647 ASSERT(commandPtr->spanElement()->isConnected());
648 return commandPtr->spanElement();
649}
650
651void CompositeEditCommand::prune(Node* node)
652{
653 if (auto* highestNodeToRemove = highestNodeToRemoveInPruning(node))
654 removeNode(*highestNodeToRemove);
655}
656
657void CompositeEditCommand::splitTextNode(Text& node, unsigned offset)
658{
659 applyCommandToComposite(SplitTextNodeCommand::create(node, offset));
660}
661
662void CompositeEditCommand::splitElement(Element& element, Node& atChild)
663{
664 applyCommandToComposite(SplitElementCommand::create(element, atChild));
665}
666
667void CompositeEditCommand::mergeIdenticalElements(Element& first, Element& second)
668{
669 Ref<Element> protectedFirst = first;
670 Ref<Element> protectedSecond = second;
671 ASSERT(!first.isDescendantOf(&second) && &second != &first);
672 if (first.nextSibling() != &second) {
673 removeNode(second);
674 insertNodeAfter(second, first);
675 }
676 applyCommandToComposite(MergeIdenticalElementsCommand::create(first, second));
677}
678
679void CompositeEditCommand::wrapContentsInDummySpan(Element& element)
680{
681 applyCommandToComposite(WrapContentsInDummySpanCommand::create(element));
682}
683
684void CompositeEditCommand::splitTextNodeContainingElement(Text& text, unsigned offset)
685{
686 applyCommandToComposite(SplitTextNodeContainingElementCommand::create(text, offset));
687}
688
689void CompositeEditCommand::inputText(const String& text, bool selectInsertedText)
690{
691 unsigned offset = 0;
692 unsigned length = text.length();
693
694 RefPtr<ContainerNode> scope;
695 unsigned startIndex = indexForVisiblePosition(endingSelection().visibleStart(), scope);
696
697 size_t newline;
698 do {
699 newline = text.find('\n', offset);
700 if (newline != offset) {
701 int substringLength = newline == notFound ? length - offset : newline - offset;
702 applyCommandToComposite(InsertTextCommand::create(document(), text.substring(offset, substringLength), false));
703 }
704 if (newline != notFound) {
705 VisiblePosition caret(endingSelection().visibleStart());
706 if (enclosingNodeOfType(caret.deepEquivalent(), &isMailBlockquote)) {
707 // FIXME: Breaking a blockquote when the caret is just after a space will collapse the
708 // space. Modify startIndex or length to compensate for this so that the ending selection
709 // will be positioned correctly.
710 // <rdar://problem/9914462> breaking a Mail blockquote just after a space collapses the space
711 if (caret.previous().characterAfter() == ' ') {
712 if (!offset && !startIndex)
713 startIndex--;
714 else if (!length)
715 length--;
716 }
717 applyCommandToComposite(BreakBlockquoteCommand::create(document()));
718 } else
719 insertLineBreak();
720 }
721
722 offset = newline + 1;
723 } while (newline != notFound && offset != length);
724
725 if (selectInsertedText)
726 setEndingSelection(VisibleSelection(visiblePositionForIndex(startIndex, scope.get()), visiblePositionForIndex(startIndex + length, scope.get())));
727}
728
729void CompositeEditCommand::insertTextIntoNode(Text& node, unsigned offset, const String& text)
730{
731 if (!text.isEmpty())
732 applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, text, editingAction()));
733}
734
735void CompositeEditCommand::deleteTextFromNode(Text& node, unsigned offset, unsigned count)
736{
737 applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count, editingAction()));
738}
739
740void CompositeEditCommand::replaceTextInNode(Text& node, unsigned offset, unsigned count, const String& replacementText)
741{
742 applyCommandToComposite(DeleteFromTextNodeCommand::create(node, offset, count));
743 if (!replacementText.isEmpty())
744 applyCommandToComposite(InsertIntoTextNodeCommand::create(node, offset, replacementText, editingAction()));
745}
746
747Position CompositeEditCommand::replaceSelectedTextInNode(const String& text)
748{
749 Position start = endingSelection().start();
750 Position end = endingSelection().end();
751 if (start.containerNode() != end.containerNode() || !start.containerNode()->isTextNode() || isTabSpanTextNode(start.containerNode()))
752 return Position();
753
754 RefPtr<Text> textNode = start.containerText();
755 replaceTextInNode(*textNode, start.offsetInContainerNode(), end.offsetInContainerNode() - start.offsetInContainerNode(), text);
756
757 return Position(textNode.get(), start.offsetInContainerNode() + text.length());
758}
759
760static Vector<RenderedDocumentMarker> copyMarkers(const Vector<RenderedDocumentMarker*>& markerPointers)
761{
762 Vector<RenderedDocumentMarker> markers;
763 markers.reserveInitialCapacity(markerPointers.size());
764 for (auto& markerPointer : markerPointers)
765 markers.uncheckedAppend(*markerPointer);
766
767 return markers;
768}
769
770void CompositeEditCommand::replaceTextInNodePreservingMarkers(Text& node, unsigned offset, unsigned count, const String& replacementText)
771{
772 Ref<Text> protectedNode(node);
773 DocumentMarkerController& markerController = document().markers();
774 auto markers = copyMarkers(markerController.markersInRange(Range::create(document(), &node, offset, &node, offset + count), DocumentMarker::allMarkers()));
775 replaceTextInNode(node, offset, count, replacementText);
776 auto newRange = Range::create(document(), &node, offset, &node, offset + replacementText.length());
777 for (const auto& marker : markers) {
778#if PLATFORM(IOS_FAMILY)
779 if (marker.isDictation()) {
780 markerController.addMarker(newRange, marker.type(), marker.description(), marker.alternatives(), marker.metadata());
781 continue;
782 }
783#endif
784#if ENABLE(PLATFORM_DRIVEN_TEXT_CHECKING)
785 if (marker.type() == DocumentMarker::PlatformTextChecking) {
786 if (!WTF::holds_alternative<DocumentMarker::PlatformTextCheckingData>(marker.data())) {
787 ASSERT_NOT_REACHED();
788 continue;
789 }
790
791 auto& textCheckingData = WTF::get<DocumentMarker::PlatformTextCheckingData>(marker.data());
792 markerController.addPlatformTextCheckingMarker(newRange, textCheckingData.key, textCheckingData.value);
793 continue;
794 }
795#endif
796 markerController.addMarker(newRange, marker.type(), marker.description());
797 }
798}
799
800Position CompositeEditCommand::positionOutsideTabSpan(const Position& position)
801{
802 if (!isTabSpanTextNode(position.anchorNode()))
803 return position;
804
805 switch (position.anchorType()) {
806 case Position::PositionIsBeforeChildren:
807 case Position::PositionIsAfterChildren:
808 ASSERT_NOT_REACHED();
809 return position;
810 case Position::PositionIsOffsetInAnchor:
811 break;
812 case Position::PositionIsBeforeAnchor:
813 return positionInParentBeforeNode(position.anchorNode());
814 case Position::PositionIsAfterAnchor:
815 return positionInParentAfterNode(position.anchorNode());
816 }
817
818 auto* tabSpan = tabSpanNode(position.containerNode());
819
820 if (position.offsetInContainerNode() <= caretMinOffset(*position.containerNode()))
821 return positionInParentBeforeNode(tabSpan);
822
823 if (position.offsetInContainerNode() >= caretMaxOffset(*position.containerNode()))
824 return positionInParentAfterNode(tabSpan);
825
826 splitTextNodeContainingElement(downcast<Text>(*position.containerNode()), position.offsetInContainerNode());
827 return positionInParentBeforeNode(tabSpan);
828}
829
830void CompositeEditCommand::insertNodeAtTabSpanPosition(Ref<Node>&& node, const Position& pos)
831{
832 // insert node before, after, or at split of tab span
833 insertNodeAt(WTFMove(node), positionOutsideTabSpan(pos));
834}
835
836static EditAction deleteSelectionEditingActionForEditingAction(EditAction editingAction)
837{
838 switch (editingAction) {
839 case EditAction::Cut:
840 return EditAction::Cut;
841 default:
842 return EditAction::Delete;
843 }
844}
845
846void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup)
847{
848 if (endingSelection().isRange())
849 applyCommandToComposite(DeleteSelectionCommand::create(document(), smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup, deleteSelectionEditingActionForEditingAction(editingAction())));
850}
851
852void CompositeEditCommand::deleteSelection(const VisibleSelection &selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements, bool sanitizeMarkup)
853{
854 if (selection.isRange())
855 applyCommandToComposite(DeleteSelectionCommand::create(selection, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements, sanitizeMarkup));
856}
857
858void CompositeEditCommand::removeNodeAttribute(Element& element, const QualifiedName& attribute)
859{
860 setNodeAttribute(element, attribute, nullAtom());
861}
862
863void CompositeEditCommand::setNodeAttribute(Element& element, const QualifiedName& attribute, const AtomicString& value)
864{
865 applyCommandToComposite(SetNodeAttributeCommand::create(element, attribute, value));
866}
867
868static inline bool containsOnlyDeprecatedEditingWhitespace(const String& text)
869{
870 for (unsigned i = 0; i < text.length(); ++i) {
871 if (!deprecatedIsEditingWhitespace(text[i]))
872 return false;
873 }
874 return true;
875}
876
877bool CompositeEditCommand::shouldRebalanceLeadingWhitespaceFor(const String& text) const
878{
879 return containsOnlyDeprecatedEditingWhitespace(text);
880}
881
882bool CompositeEditCommand::canRebalance(const Position& position) const
883{
884 Node* node = position.containerNode();
885 if (position.anchorType() != Position::PositionIsOffsetInAnchor || !is<Text>(node))
886 return false;
887
888 Text& textNode = downcast<Text>(*node);
889 if (!textNode.length())
890 return false;
891
892 node->document().updateStyleIfNeeded();
893
894 RenderObject* renderer = textNode.renderer();
895 if (renderer && !renderer->style().collapseWhiteSpace())
896 return false;
897
898 return true;
899}
900
901// FIXME: Doesn't go into text nodes that contribute adjacent text (siblings, cousins, etc).
902void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position)
903{
904 Node* node = position.containerNode();
905 if (!canRebalance(position))
906 return;
907
908 // If the rebalance is for the single offset, and neither text[offset] nor text[offset - 1] are some form of whitespace, do nothing.
909 int offset = position.deprecatedEditingOffset();
910 String text = downcast<Text>(*node).data();
911 if (!deprecatedIsEditingWhitespace(text[offset])) {
912 offset--;
913 if (offset < 0 || !deprecatedIsEditingWhitespace(text[offset]))
914 return;
915 }
916
917 rebalanceWhitespaceOnTextSubstring(downcast<Text>(*node), position.offsetInContainerNode(), position.offsetInContainerNode());
918}
919
920void CompositeEditCommand::rebalanceWhitespaceOnTextSubstring(Text& textNode, int startOffset, int endOffset)
921{
922 String text = textNode.data();
923 ASSERT(!text.isEmpty());
924
925 // Set upstream and downstream to define the extent of the whitespace surrounding text[offset].
926 int upstream = startOffset;
927 while (upstream > 0 && deprecatedIsEditingWhitespace(text[upstream - 1]))
928 upstream--;
929
930 int downstream = endOffset;
931 while ((unsigned)downstream < text.length() && deprecatedIsEditingWhitespace(text[downstream]))
932 downstream++;
933
934 int length = downstream - upstream;
935 if (!length)
936 return;
937
938 VisiblePosition visibleUpstreamPos(Position(&textNode, upstream));
939 VisiblePosition visibleDownstreamPos(Position(&textNode, downstream));
940
941 String string = text.substring(upstream, length);
942 String rebalancedString = stringWithRebalancedWhitespace(string,
943 // 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
944 // this function doesn't get all surrounding whitespace, just the whitespace in the current text node.
945 isStartOfParagraph(visibleUpstreamPos) || upstream == 0,
946 isEndOfParagraph(visibleDownstreamPos) || (unsigned)downstream == text.length());
947
948 if (string != rebalancedString)
949 replaceTextInNodePreservingMarkers(textNode, upstream, length, rebalancedString);
950}
951
952void CompositeEditCommand::prepareWhitespaceAtPositionForSplit(Position& position)
953{
954 Node* node = position.deprecatedNode();
955 if (!is<Text>(node))
956 return;
957 Text& textNode = downcast<Text>(*node);
958
959 if (!textNode.length())
960 return;
961 RenderObject* renderer = textNode.renderer();
962 if (renderer && !renderer->style().collapseWhiteSpace())
963 return;
964
965 // Delete collapsed whitespace so that inserting nbsps doesn't uncollapse it.
966 Position upstreamPos = position.upstream();
967 deleteInsignificantText(position.upstream(), position.downstream());
968 position = upstreamPos.downstream();
969
970 VisiblePosition visiblePos(position);
971 VisiblePosition previousVisiblePos(visiblePos.previous());
972 Position previous(previousVisiblePos.deepEquivalent());
973
974 if (deprecatedIsCollapsibleWhitespace(previousVisiblePos.characterAfter()) && is<Text>(*previous.deprecatedNode()) && !is<HTMLBRElement>(*previous.deprecatedNode()))
975 replaceTextInNodePreservingMarkers(downcast<Text>(*previous.deprecatedNode()), previous.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
976 if (deprecatedIsCollapsibleWhitespace(visiblePos.characterAfter()) && is<Text>(*position.deprecatedNode()) && !is<HTMLBRElement>(*position.deprecatedNode()))
977 replaceTextInNodePreservingMarkers(downcast<Text>(*position.deprecatedNode()), position.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
978}
979
980void CompositeEditCommand::rebalanceWhitespace()
981{
982 VisibleSelection selection = endingSelection();
983 if (selection.isNone())
984 return;
985
986 rebalanceWhitespaceAt(selection.start());
987 if (selection.isRange())
988 rebalanceWhitespaceAt(selection.end());
989}
990
991void CompositeEditCommand::deleteInsignificantText(Text& textNode, unsigned start, unsigned end)
992{
993 if (start >= end)
994 return;
995
996 document().updateLayout();
997
998 RenderText* textRenderer = textNode.renderer();
999 if (!textRenderer)
1000 return;
1001
1002 Vector<InlineTextBox*> sortedTextBoxes;
1003 size_t sortedTextBoxesPosition = 0;
1004
1005 for (InlineTextBox* textBox = textRenderer->firstTextBox(); textBox; textBox = textBox->nextTextBox())
1006 sortedTextBoxes.append(textBox);
1007
1008 // If there is mixed directionality text, the boxes can be out of order,
1009 // (like Arabic with embedded LTR), so sort them first.
1010 if (textRenderer->containsReversedText())
1011 std::sort(sortedTextBoxes.begin(), sortedTextBoxes.end(), InlineTextBox::compareByStart);
1012 InlineTextBox* box = sortedTextBoxes.isEmpty() ? 0 : sortedTextBoxes[sortedTextBoxesPosition];
1013
1014 if (!box) {
1015 // whole text node is empty
1016 removeNode(textNode);
1017 return;
1018 }
1019
1020 unsigned length = textNode.length();
1021 if (start >= length || end > length)
1022 return;
1023
1024 unsigned removed = 0;
1025 InlineTextBox* prevBox = nullptr;
1026 String str;
1027
1028 // This loop structure works to process all gaps preceding a box,
1029 // and also will look at the gap after the last box.
1030 while (prevBox || box) {
1031 unsigned gapStart = prevBox ? prevBox->start() + prevBox->len() : 0;
1032 if (end < gapStart)
1033 // No more chance for any intersections
1034 break;
1035
1036 unsigned gapEnd = box ? box->start() : length;
1037 bool indicesIntersect = start <= gapEnd && end >= gapStart;
1038 int gapLen = gapEnd - gapStart;
1039 if (indicesIntersect && gapLen > 0) {
1040 gapStart = std::max(gapStart, start);
1041 gapEnd = std::min(gapEnd, end);
1042 if (str.isNull())
1043 str = textNode.data().substring(start, end - start);
1044 // remove text in the gap
1045 str.remove(gapStart - start - removed, gapLen);
1046 removed += gapLen;
1047 }
1048
1049 prevBox = box;
1050 if (box) {
1051 if (++sortedTextBoxesPosition < sortedTextBoxes.size())
1052 box = sortedTextBoxes[sortedTextBoxesPosition];
1053 else
1054 box = nullptr;
1055 }
1056 }
1057
1058 if (!str.isNull()) {
1059 // Replace the text between start and end with our pruned version.
1060 if (!str.isEmpty())
1061 replaceTextInNode(textNode, start, end - start, str);
1062 else {
1063 // Assert that we are not going to delete all of the text in the node.
1064 // If we were, that should have been done above with the call to
1065 // removeNode and return.
1066 ASSERT(start > 0 || end - start < textNode.length());
1067 deleteTextFromNode(textNode, start, end - start);
1068 }
1069 }
1070}
1071
1072void CompositeEditCommand::deleteInsignificantText(const Position& start, const Position& end)
1073{
1074 if (start.isNull() || end.isNull())
1075 return;
1076
1077 if (comparePositions(start, end) >= 0)
1078 return;
1079
1080 Vector<Ref<Text>> nodes;
1081 for (Node* node = start.deprecatedNode(); node; node = NodeTraversal::next(*node)) {
1082 if (is<Text>(*node))
1083 nodes.append(downcast<Text>(*node));
1084 if (node == end.deprecatedNode())
1085 break;
1086 }
1087
1088 for (auto& textNode : nodes) {
1089 int startOffset = textNode.ptr() == start.deprecatedNode() ? start.deprecatedEditingOffset() : 0;
1090 int endOffset = textNode.ptr() == end.deprecatedNode() ? end.deprecatedEditingOffset() : static_cast<int>(textNode->length());
1091 deleteInsignificantText(textNode, startOffset, endOffset);
1092 }
1093}
1094
1095void CompositeEditCommand::deleteInsignificantTextDownstream(const Position& pos)
1096{
1097 Position end = VisiblePosition(pos, VP_DEFAULT_AFFINITY).next().deepEquivalent().downstream();
1098 deleteInsignificantText(pos, end);
1099}
1100
1101Ref<Element> CompositeEditCommand::appendBlockPlaceholder(Ref<Element>&& container)
1102{
1103 document().updateLayoutIgnorePendingStylesheets();
1104
1105 // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964.
1106 ASSERT(container->renderer());
1107
1108 auto placeholder = createBlockPlaceholderElement(document());
1109 appendNode(placeholder.copyRef(), WTFMove(container));
1110 return placeholder;
1111}
1112
1113RefPtr<Node> CompositeEditCommand::insertBlockPlaceholder(const Position& pos)
1114{
1115 if (pos.isNull())
1116 return nullptr;
1117
1118 // Should assert isBlockFlow || isInlineFlow when deletion improves. See 4244964.
1119 ASSERT(pos.deprecatedNode()->renderer());
1120
1121 auto placeholder = createBlockPlaceholderElement(document());
1122 insertNodeAt(placeholder.copyRef(), pos);
1123 return placeholder;
1124}
1125
1126RefPtr<Node> CompositeEditCommand::addBlockPlaceholderIfNeeded(Element* container)
1127{
1128 if (!container)
1129 return nullptr;
1130
1131 document().updateLayoutIgnorePendingStylesheets();
1132
1133 auto* renderer = container->renderer();
1134 if (!is<RenderBlockFlow>(renderer))
1135 return nullptr;
1136
1137 // Append the placeholder to make sure it follows any unrendered blocks.
1138 auto& blockFlow = downcast<RenderBlockFlow>(*renderer);
1139 if (!blockFlow.height() || (blockFlow.isListItem() && !blockFlow.firstChild()))
1140 return appendBlockPlaceholder(*container);
1141
1142 return nullptr;
1143}
1144
1145// Assumes that the position is at a placeholder and does the removal without much checking.
1146void CompositeEditCommand::removePlaceholderAt(const Position& p)
1147{
1148 ASSERT(lineBreakExistsAtPosition(p));
1149
1150 // We are certain that the position is at a line break, but it may be a br or a preserved newline.
1151 if (is<HTMLBRElement>(*p.anchorNode())) {
1152 removeNode(*p.anchorNode());
1153 return;
1154 }
1155
1156 deleteTextFromNode(downcast<Text>(*p.anchorNode()), p.offsetInContainerNode(), 1);
1157}
1158
1159Ref<HTMLElement> CompositeEditCommand::insertNewDefaultParagraphElementAt(const Position& position)
1160{
1161 auto paragraphElement = createDefaultParagraphElement(document());
1162 paragraphElement->appendChild(HTMLBRElement::create(document()));
1163 insertNodeAt(paragraphElement.copyRef(), position);
1164 return paragraphElement;
1165}
1166
1167// If the paragraph is not entirely within it's own block, create one and move the paragraph into
1168// it, and return that block. Otherwise return 0.
1169RefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessary(const Position& pos)
1170{
1171 if (pos.isNull())
1172 return nullptr;
1173
1174 document().updateLayoutIgnorePendingStylesheets();
1175
1176 // It's strange that this function is responsible for verifying that pos has not been invalidated
1177 // by an earlier call to this function. The caller, applyBlockStyle, should do this.
1178 VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
1179 VisiblePosition visibleParagraphStart(startOfParagraph(visiblePos));
1180 VisiblePosition visibleParagraphEnd = endOfParagraph(visiblePos);
1181 VisiblePosition next = visibleParagraphEnd.next();
1182 VisiblePosition visibleEnd = next.isNotNull() ? next : visibleParagraphEnd;
1183
1184 Position upstreamStart = visibleParagraphStart.deepEquivalent().upstream();
1185 Position upstreamEnd = visibleEnd.deepEquivalent().upstream();
1186
1187 // If there are no VisiblePositions in the same block as pos then
1188 // upstreamStart will be outside the paragraph
1189 if (comparePositions(pos, upstreamStart) < 0)
1190 return nullptr;
1191
1192 // Perform some checks to see if we need to perform work in this function.
1193 if (isBlock(upstreamStart.deprecatedNode())) {
1194 // If the block is the root editable element, always move content to a new block,
1195 // since it is illegal to modify attributes on the root editable element for editing.
1196 if (upstreamStart.deprecatedNode() == editableRootForPosition(upstreamStart)) {
1197 // If the block is the root editable element and it contains no visible content, create a new
1198 // block but don't try and move content into it, since there's nothing for moveParagraphs to move.
1199 if (!Position::hasRenderedNonAnonymousDescendantsWithHeight(downcast<RenderElement>(*upstreamStart.deprecatedNode()->renderer())))
1200 return insertNewDefaultParagraphElementAt(upstreamStart);
1201 } else if (isBlock(upstreamEnd.deprecatedNode())) {
1202 if (!upstreamEnd.deprecatedNode()->isDescendantOf(upstreamStart.deprecatedNode())) {
1203 // If the paragraph end is a descendant of paragraph start, then we need to run
1204 // the rest of this function. If not, we can bail here.
1205 return nullptr;
1206 }
1207 } else if (enclosingBlock(upstreamEnd.deprecatedNode()) != upstreamStart.deprecatedNode()) {
1208 // The visibleEnd. If it is an ancestor of the paragraph start, then
1209 // we can bail as we have a full block to work with.
1210 if (upstreamStart.deprecatedNode()->isDescendantOf(enclosingBlock(upstreamEnd.deprecatedNode())))
1211 return nullptr;
1212 } else if (isEndOfEditableOrNonEditableContent(visibleEnd)) {
1213 // At the end of the editable region. We can bail here as well.
1214 return nullptr;
1215 }
1216 }
1217
1218 // If upstreamStart is not editable, then we can bail here.
1219 if (!isEditablePosition(upstreamStart))
1220 return nullptr;
1221 auto newBlock = insertNewDefaultParagraphElementAt(upstreamStart);
1222
1223 bool endWasBr = visibleParagraphEnd.deepEquivalent().deprecatedNode()->hasTagName(brTag);
1224
1225 moveParagraphs(visibleParagraphStart, visibleParagraphEnd, VisiblePosition(firstPositionInNode(newBlock.ptr())));
1226
1227 if (newBlock->lastChild() && newBlock->lastChild()->hasTagName(brTag) && !endWasBr)
1228 removeNode(*newBlock->lastChild());
1229
1230 return newBlock;
1231}
1232
1233void CompositeEditCommand::pushAnchorElementDown(Element& anchorElement)
1234{
1235 ASSERT(anchorElement.isLink());
1236
1237 setEndingSelection(VisibleSelection::selectionFromContentsOfNode(&anchorElement));
1238 applyStyledElement(anchorElement);
1239 // Clones of anchorElement have been pushed down, now remove it.
1240 if (anchorElement.isConnected())
1241 removeNodePreservingChildren(anchorElement);
1242}
1243
1244// Clone the paragraph between start and end under blockElement,
1245// preserving the hierarchy up to outerNode.
1246
1247void CompositeEditCommand::cloneParagraphUnderNewElement(const Position& start, const Position& end, Node* passedOuterNode, Element* blockElement)
1248{
1249 ASSERT(comparePositions(start, end) <= 0);
1250
1251 // First we clone the outerNode
1252 RefPtr<Node> lastNode;
1253 RefPtr<Node> outerNode = passedOuterNode;
1254
1255 if (outerNode->isRootEditableElement()) {
1256 lastNode = blockElement;
1257 } else {
1258 lastNode = outerNode->cloneNode(isRenderedTable(outerNode.get()));
1259 appendNode(*lastNode, *blockElement);
1260 }
1261
1262 if (start.deprecatedNode() != outerNode && lastNode->isElementNode() && start.anchorNode()->isDescendantOf(outerNode.get())) {
1263 Vector<RefPtr<Node>> ancestors;
1264
1265 // Insert each node from innerNode to outerNode (excluded) in a list.
1266 for (Node* n = start.deprecatedNode(); n && n != outerNode; n = n->parentNode())
1267 ancestors.append(n);
1268
1269 // Clone every node between start.deprecatedNode() and outerBlock.
1270
1271 for (size_t i = ancestors.size(); i != 0; --i) {
1272 Node* item = ancestors[i - 1].get();
1273 auto child = item->cloneNode(isRenderedTable(item));
1274 appendNode(child.copyRef(), downcast<Element>(*lastNode));
1275 lastNode = WTFMove(child);
1276 }
1277 }
1278
1279 // Handle the case of paragraphs with more than one node,
1280 // cloning all the siblings until end.deprecatedNode() is reached.
1281
1282 if (start.deprecatedNode() != end.deprecatedNode() && !start.deprecatedNode()->isDescendantOf(end.deprecatedNode())) {
1283 // If end is not a descendant of outerNode we need to
1284 // find the first common ancestor to increase the scope
1285 // of our nextSibling traversal.
1286 while (!end.deprecatedNode()->isDescendantOf(outerNode.get())) {
1287 outerNode = outerNode->parentNode();
1288 }
1289
1290 RefPtr<Node> startNode = start.deprecatedNode();
1291 for (RefPtr<Node> node = NodeTraversal::nextSkippingChildren(*startNode, outerNode.get()); node; node = NodeTraversal::nextSkippingChildren(*node, outerNode.get())) {
1292 // Move lastNode up in the tree as much as node was moved up in the
1293 // tree by NodeTraversal::nextSkippingChildren, so that the relative depth between
1294 // node and the original start node is maintained in the clone.
1295 while (startNode->parentNode() != node->parentNode()) {
1296 startNode = startNode->parentNode();
1297 lastNode = lastNode->parentNode();
1298 }
1299
1300 auto clonedNode = node->cloneNode(true);
1301 insertNodeAfter(clonedNode.copyRef(), *lastNode);
1302 lastNode = WTFMove(clonedNode);
1303 if (node == end.deprecatedNode() || end.deprecatedNode()->isDescendantOf(*node))
1304 break;
1305 }
1306 }
1307}
1308
1309
1310// There are bugs in deletion when it removes a fully selected table/list.
1311// It expands and removes the entire table/list, but will let content
1312// before and after the table/list collapse onto one line.
1313// Deleting a paragraph will leave a placeholder. Remove it (and prune
1314// empty or unrendered parents).
1315
1316void CompositeEditCommand::cleanupAfterDeletion(VisiblePosition destination)
1317{
1318 VisiblePosition caretAfterDelete = endingSelection().visibleStart();
1319 if (!caretAfterDelete.equals(destination) && isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) {
1320 // Note: We want the rightmost candidate.
1321 Position position = caretAfterDelete.deepEquivalent().downstream();
1322 Node* node = position.deprecatedNode();
1323 ASSERT(node);
1324 // Normally deletion will leave a br as a placeholder.
1325 if (is<HTMLBRElement>(*node))
1326 removeNodeAndPruneAncestors(*node);
1327 // If the selection to move was empty and in an empty block that
1328 // doesn't require a placeholder to prop itself open (like a bordered
1329 // div or an li), remove it during the move (the list removal code
1330 // expects this behavior).
1331 else if (isBlock(node)) {
1332 // If caret position after deletion and destination position coincides,
1333 // node should not be removed.
1334 if (!position.rendersInDifferentPosition(destination.deepEquivalent())) {
1335 prune(node);
1336 return;
1337 }
1338 removeNodeAndPruneAncestors(*node);
1339 }
1340 else if (lineBreakExistsAtPosition(position)) {
1341 // There is a preserved '\n' at caretAfterDelete.
1342 // We can safely assume this is a text node.
1343 Text& textNode = downcast<Text>(*node);
1344 if (textNode.length() == 1)
1345 removeNodeAndPruneAncestors(textNode);
1346 else
1347 deleteTextFromNode(textNode, position.deprecatedEditingOffset(), 1);
1348 }
1349 }
1350}
1351
1352// This is a version of moveParagraph that preserves style by keeping the original markup
1353// It is currently used only by IndentOutdentCommand but it is meant to be used in the
1354// future by several other commands such as InsertList and the align commands.
1355// The blockElement parameter is the element to move the paragraph to,
1356// outerNode is the top element of the paragraph hierarchy.
1357
1358void CompositeEditCommand::moveParagraphWithClones(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, Element* blockElement, Node* outerNode)
1359{
1360 if (startOfParagraphToMove.isNull() || endOfParagraphToMove.isNull())
1361 return;
1362
1363 ASSERT(outerNode);
1364 ASSERT(blockElement);
1365
1366 VisiblePosition beforeParagraph = startOfParagraphToMove.previous();
1367 VisiblePosition afterParagraph(endOfParagraphToMove.next());
1368
1369 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move.
1370 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered.
1371 Position start = startOfParagraphToMove.deepEquivalent().downstream();
1372 Position end = startOfParagraphToMove == endOfParagraphToMove ? start : endOfParagraphToMove.deepEquivalent().upstream();
1373
1374 cloneParagraphUnderNewElement(start, end, outerNode, blockElement);
1375
1376 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM));
1377 deleteSelection(false, false, false, false);
1378
1379 // There are bugs in deletion when it removes a fully selected table/list.
1380 // It expands and removes the entire table/list, but will let content
1381 // before and after the table/list collapse onto one line.
1382
1383 cleanupAfterDeletion();
1384
1385 // Add a br if pruning an empty block level element caused a collapse. For example:
1386 // foo^
1387 // <div>bar</div>
1388 // baz
1389 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would
1390 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br.
1391 // Must recononicalize these two VisiblePositions after the pruning above.
1392 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent());
1393 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent());
1394
1395 if (beforeParagraph.isNotNull() && !isRenderedTable(beforeParagraph.deepEquivalent().deprecatedNode())
1396 && ((!isEndOfParagraph(beforeParagraph) && !isStartOfParagraph(beforeParagraph)) || beforeParagraph == afterParagraph)
1397 && isEditablePosition(beforeParagraph.deepEquivalent())) {
1398 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal.
1399 insertNodeAt(HTMLBRElement::create(document()), beforeParagraph.deepEquivalent());
1400 }
1401}
1402
1403
1404// This moves a paragraph preserving its style.
1405void CompositeEditCommand::moveParagraph(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle)
1406{
1407 ASSERT(isStartOfParagraph(startOfParagraphToMove));
1408 ASSERT(isEndOfParagraph(endOfParagraphToMove));
1409 moveParagraphs(startOfParagraphToMove, endOfParagraphToMove, destination, preserveSelection, preserveStyle);
1410}
1411
1412void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle)
1413{
1414 if (startOfParagraphToMove == destination)
1415 return;
1416
1417 int startIndex = -1;
1418 int endIndex = -1;
1419 int destinationIndex = -1;
1420 bool originalIsDirectional = endingSelection().isDirectional();
1421 if (preserveSelection && !endingSelection().isNone()) {
1422 VisiblePosition visibleStart = endingSelection().visibleStart();
1423 VisiblePosition visibleEnd = endingSelection().visibleEnd();
1424
1425 bool startAfterParagraph = comparePositions(visibleStart, endOfParagraphToMove) > 0;
1426 bool endBeforeParagraph = comparePositions(visibleEnd, startOfParagraphToMove) < 0;
1427
1428 if (!startAfterParagraph && !endBeforeParagraph) {
1429 bool startInParagraph = comparePositions(visibleStart, startOfParagraphToMove) >= 0;
1430 bool endInParagraph = comparePositions(visibleEnd, endOfParagraphToMove) <= 0;
1431
1432 startIndex = 0;
1433 if (startInParagraph) {
1434 auto startRange = Range::create(document(), startOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent(), visibleStart.deepEquivalent().parentAnchoredEquivalent());
1435 startIndex = TextIterator::rangeLength(startRange.ptr(), true);
1436 }
1437
1438 endIndex = 0;
1439 if (endInParagraph) {
1440 auto endRange = Range::create(document(), startOfParagraphToMove.deepEquivalent().parentAnchoredEquivalent(), visibleEnd.deepEquivalent().parentAnchoredEquivalent());
1441 endIndex = TextIterator::rangeLength(endRange.ptr(), true);
1442 }
1443 }
1444 }
1445
1446 VisiblePosition beforeParagraph = startOfParagraphToMove.previous(CannotCrossEditingBoundary);
1447 VisiblePosition afterParagraph(endOfParagraphToMove.next(CannotCrossEditingBoundary));
1448
1449 // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move.
1450 // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered.
1451 Position start = startOfParagraphToMove.deepEquivalent().downstream();
1452 Position end = endOfParagraphToMove.deepEquivalent().upstream();
1453
1454 // start and end can't be used directly to create a Range; they are "editing positions"
1455 Position startRangeCompliant = start.parentAnchoredEquivalent();
1456 Position endRangeCompliant = end.parentAnchoredEquivalent();
1457 auto range = Range::create(document(), startRangeCompliant.deprecatedNode(), startRangeCompliant.deprecatedEditingOffset(), endRangeCompliant.deprecatedNode(), endRangeCompliant.deprecatedEditingOffset());
1458
1459 // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It
1460 // shouldn't matter though, since moved paragraphs will usually be quite small.
1461 RefPtr<DocumentFragment> fragment;
1462 // This used to use a ternary for initialization, but that confused some versions of GCC, see bug 37912
1463 if (startOfParagraphToMove != endOfParagraphToMove)
1464 fragment = createFragmentFromMarkup(document(), serializePreservingVisualAppearance(range.get(), nullptr, AnnotateForInterchange::No, ConvertBlocksToInlines::Yes), emptyString());
1465
1466 // A non-empty paragraph's style is moved when we copy and move it. We don't move
1467 // anything if we're given an empty paragraph, but an empty paragraph can have style
1468 // too, <div><b><br></b></div> for example. Save it so that we can preserve it later.
1469 RefPtr<EditingStyle> styleInEmptyParagraph;
1470#if !PLATFORM(IOS_FAMILY)
1471 if (startOfParagraphToMove == endOfParagraphToMove && preserveStyle) {
1472#else
1473 if (startOfParagraphToMove == endOfParagraphToMove && preserveStyle && isRichlyEditablePosition(destination.deepEquivalent())) {
1474#endif
1475 styleInEmptyParagraph = EditingStyle::create(startOfParagraphToMove.deepEquivalent());
1476 styleInEmptyParagraph->mergeTypingStyle(document());
1477 // The moved paragraph should assume the block style of the destination.
1478 styleInEmptyParagraph->removeBlockProperties();
1479 }
1480
1481 // FIXME (5098931): We should add a new insert action "WebViewInsertActionMoved" and call shouldInsertFragment here.
1482
1483 setEndingSelection(VisibleSelection(start, end, DOWNSTREAM));
1484 frame().editor().clearMisspellingsAndBadGrammar(endingSelection());
1485 deleteSelection(false, false, false, false);
1486
1487 ASSERT(destination.deepEquivalent().anchorNode()->isConnected());
1488 cleanupAfterDeletion(destination);
1489 ASSERT(destination.deepEquivalent().anchorNode()->isConnected());
1490
1491 // Add a br if pruning an empty block level element caused a collapse. For example:
1492 // foo^
1493 // <div>bar</div>
1494 // baz
1495 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would
1496 // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br.
1497 // Must recononicalize these two VisiblePositions after the pruning above.
1498 beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent());
1499 afterParagraph = VisiblePosition(afterParagraph.deepEquivalent());
1500 if (beforeParagraph.isNotNull() && ((!isStartOfParagraph(beforeParagraph) && !isEndOfParagraph(beforeParagraph)) || beforeParagraph == afterParagraph)) {
1501 // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal.
1502 insertNodeAt(HTMLBRElement::create(document()), beforeParagraph.deepEquivalent());
1503 // Need an updateLayout here in case inserting the br has split a text node.
1504 document().updateLayoutIgnorePendingStylesheets();
1505 }
1506
1507 RefPtr<ContainerNode> editableRoot = destination.rootEditableElement();
1508 if (!editableRoot)
1509 editableRoot = &document();
1510
1511 auto startToDestinationRange = Range::create(document(), firstPositionInNode(editableRoot.get()), destination.deepEquivalent().parentAnchoredEquivalent());
1512 destinationIndex = TextIterator::rangeLength(startToDestinationRange.ptr(), true);
1513
1514 setEndingSelection(VisibleSelection(destination, originalIsDirectional));
1515 ASSERT(endingSelection().isCaretOrRange());
1516 OptionSet<ReplaceSelectionCommand::CommandOption> options { ReplaceSelectionCommand::SelectReplacement, ReplaceSelectionCommand::MovingParagraph };
1517 if (!preserveStyle)
1518 options.add(ReplaceSelectionCommand::MatchStyle);
1519 applyCommandToComposite(ReplaceSelectionCommand::create(document(), WTFMove(fragment), options));
1520
1521 frame().editor().markMisspellingsAndBadGrammar(endingSelection());
1522
1523 // If the selection is in an empty paragraph, restore styles from the old empty paragraph to the new empty paragraph.
1524 bool selectionIsEmptyParagraph = endingSelection().isCaret() && isStartOfParagraph(endingSelection().visibleStart()) && isEndOfParagraph(endingSelection().visibleStart());
1525 if (styleInEmptyParagraph && selectionIsEmptyParagraph)
1526 applyStyle(styleInEmptyParagraph.get());
1527
1528 if (preserveSelection && startIndex != -1) {
1529 // Fragment creation (using createMarkup) incorrectly uses regular
1530 // spaces instead of nbsps for some spaces that were rendered (11475), which
1531 // causes spaces to be collapsed during the move operation. This results
1532 // in a call to rangeFromLocationAndLength with a location past the end
1533 // of the document (which will return null).
1534 RefPtr<Range> start = TextIterator::rangeFromLocationAndLength(editableRoot.get(), destinationIndex + startIndex, 0, true);
1535 RefPtr<Range> end = TextIterator::rangeFromLocationAndLength(editableRoot.get(), destinationIndex + endIndex, 0, true);
1536 if (start && end)
1537 setEndingSelection(VisibleSelection(start->startPosition(), end->startPosition(), DOWNSTREAM, originalIsDirectional));
1538 }
1539}
1540
1541Optional<VisibleSelection> CompositeEditCommand::shouldBreakOutOfEmptyListItem() const
1542{
1543 auto emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart());
1544 if (!emptyListItem)
1545 return WTF::nullopt;
1546
1547 auto listNode = emptyListItem->parentNode();
1548 // FIXME: Can't we do something better when the immediate parent wasn't a list node?
1549 if (!listNode
1550 || (!listNode->hasTagName(ulTag) && !listNode->hasTagName(olTag))
1551 || !listNode->hasEditableStyle()
1552 || listNode == emptyListItem->rootEditableElement())
1553 return WTF::nullopt;
1554
1555 return VisibleSelection(endingSelection().start().previous(BackwardDeletion), endingSelection().end());
1556}
1557
1558// FIXME: Send an appropriate shouldDeleteRange call.
1559bool CompositeEditCommand::breakOutOfEmptyListItem()
1560{
1561 if (!shouldBreakOutOfEmptyListItem())
1562 return false;
1563
1564 auto emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart());
1565 auto listNode = emptyListItem->parentNode();
1566 auto style = EditingStyle::create(endingSelection().start());
1567 style->mergeTypingStyle(document());
1568
1569 RefPtr<Element> newBlock;
1570 if (ContainerNode* blockEnclosingList = listNode->parentNode()) {
1571 if (is<HTMLLIElement>(*blockEnclosingList)) { // listNode is inside another list item
1572 if (visiblePositionAfterNode(*blockEnclosingList) == visiblePositionAfterNode(*listNode)) {
1573 // If listNode appears at the end of the outer list item, then move listNode outside of this list item
1574 // 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
1575 // If listNode does NOT appear at the end, then we should consider it as a regular paragraph.
1576 // 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
1577 splitElement(downcast<HTMLLIElement>(*blockEnclosingList), *listNode);
1578 removeNodePreservingChildren(*listNode->parentNode());
1579 newBlock = HTMLLIElement::create(document());
1580 }
1581 // If listNode does NOT appear at the end of the outer list item, then behave as if in a regular paragraph.
1582 } else if (blockEnclosingList->hasTagName(olTag) || blockEnclosingList->hasTagName(ulTag))
1583 newBlock = HTMLLIElement::create(document());
1584 }
1585 if (!newBlock)
1586 newBlock = createDefaultParagraphElement(document());
1587
1588 RefPtr<Node> previousListNode = emptyListItem->isElementNode() ? ElementTraversal::previousSibling(*emptyListItem): emptyListItem->previousSibling();
1589 RefPtr<Node> nextListNode = emptyListItem->isElementNode() ? ElementTraversal::nextSibling(*emptyListItem): emptyListItem->nextSibling();
1590 if (isListItem(nextListNode.get()) || isListHTMLElement(nextListNode.get())) {
1591 // If emptyListItem follows another list item or nested list, split the list node.
1592 if (isListItem(previousListNode.get()) || isListHTMLElement(previousListNode.get()))
1593 splitElement(downcast<Element>(*listNode), *emptyListItem);
1594
1595 // If emptyListItem is followed by other list item or nested list, then insert newBlock before the list node.
1596 // Because we have splitted the element, emptyListItem is the first element in the list node.
1597 // i.e. insert newBlock before ul or ol whose first element is emptyListItem
1598 insertNodeBefore(*newBlock, *listNode);
1599 removeNode(*emptyListItem);
1600 } else {
1601 // When emptyListItem does not follow any list item or nested list, insert newBlock after the enclosing list node.
1602 // Remove the enclosing node if emptyListItem is the only child; otherwise just remove emptyListItem.
1603 insertNodeAfter(*newBlock, *listNode);
1604 removeNode(isListItem(previousListNode.get()) || isListHTMLElement(previousListNode.get()) ? *emptyListItem : *listNode);
1605 }
1606
1607 appendBlockPlaceholder(*newBlock);
1608 setEndingSelection(VisibleSelection(firstPositionInNode(newBlock.get()), DOWNSTREAM, endingSelection().isDirectional()));
1609
1610 style->prepareToApplyAt(endingSelection().start());
1611 if (!style->isEmpty())
1612 applyStyle(style.ptr());
1613
1614 return true;
1615}
1616
1617// If the caret is in an empty quoted paragraph, and either there is nothing before that
1618// paragraph, or what is before is unquoted, and the user presses delete, unquote that paragraph.
1619bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph()
1620{
1621 if (!endingSelection().isCaret())
1622 return false;
1623
1624 VisiblePosition caret(endingSelection().visibleStart());
1625 Node* highestBlockquote = highestEnclosingNodeOfType(caret.deepEquivalent(), &isMailBlockquote);
1626 if (!highestBlockquote)
1627 return false;
1628
1629 if (!isStartOfParagraph(caret) || !isEndOfParagraph(caret))
1630 return false;
1631
1632 VisiblePosition previous(caret.previous(CannotCrossEditingBoundary));
1633 // Only move forward if there's nothing before the caret, or if there's unquoted content before it.
1634 if (enclosingNodeOfType(previous.deepEquivalent(), &isMailBlockquote))
1635 return false;
1636
1637 auto br = HTMLBRElement::create(document());
1638 auto* brPtr = br.ptr();
1639 // We want to replace this quoted paragraph with an unquoted one, so insert a br
1640 // to hold the caret before the highest blockquote.
1641 insertNodeBefore(WTFMove(br), *highestBlockquote);
1642 VisiblePosition atBR(positionBeforeNode(brPtr));
1643 // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert
1644 // a second one.
1645 if (!isStartOfParagraph(atBR))
1646 insertNodeBefore(HTMLBRElement::create(document()), *brPtr);
1647 setEndingSelection(VisibleSelection(atBR, endingSelection().isDirectional()));
1648
1649 // If this is an empty paragraph there must be a line break here.
1650 if (!lineBreakExistsAtVisiblePosition(caret))
1651 return false;
1652
1653 Position caretPos(caret.deepEquivalent().downstream());
1654 // A line break is either a br or a preserved newline.
1655 ASSERT(caretPos.deprecatedNode()->hasTagName(brTag) || (caretPos.deprecatedNode()->isTextNode() && caretPos.deprecatedNode()->renderer()->style().preserveNewline()));
1656
1657 if (caretPos.deprecatedNode()->hasTagName(brTag))
1658 removeNodeAndPruneAncestors(*caretPos.deprecatedNode());
1659 else if (is<Text>(*caretPos.deprecatedNode())) {
1660 ASSERT(caretPos.deprecatedEditingOffset() == 0);
1661 Text& textNode = downcast<Text>(*caretPos.deprecatedNode());
1662 ContainerNode* parentNode = textNode.parentNode();
1663 // The preserved newline must be the first thing in the node, since otherwise the previous
1664 // paragraph would be quoted, and we verified that it wasn't above.
1665 deleteTextFromNode(textNode, 0, 1);
1666 prune(parentNode);
1667 }
1668
1669 return true;
1670}
1671
1672// Operations use this function to avoid inserting content into an anchor when at the start or the end of
1673// that anchor, as in NSTextView.
1674// FIXME: This is only an approximation of NSTextViews insertion behavior, which varies depending on how
1675// the caret was made.
1676Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Position& original)
1677{
1678 if (original.isNull())
1679 return original;
1680
1681 VisiblePosition visiblePos(original);
1682 Element* enclosingAnchor = enclosingAnchorElement(original);
1683 Position result = original;
1684
1685 if (!enclosingAnchor)
1686 return result;
1687
1688 // Don't avoid block level anchors, because that would insert content into the wrong paragraph.
1689 if (enclosingAnchor && !isBlock(enclosingAnchor)) {
1690 VisiblePosition firstInAnchor(firstPositionInNode(enclosingAnchor));
1691 VisiblePosition lastInAnchor(lastPositionInNode(enclosingAnchor));
1692 // If visually just after the anchor, insert *inside* the anchor unless it's the last
1693 // VisiblePosition in the document, to match NSTextView.
1694 if (visiblePos == lastInAnchor) {
1695 // Make sure anchors are pushed down before avoiding them so that we don't
1696 // also avoid structural elements like lists and blocks (5142012).
1697 if (original.deprecatedNode() != enclosingAnchor && original.deprecatedNode()->parentNode() != enclosingAnchor) {
1698 pushAnchorElementDown(*enclosingAnchor);
1699 enclosingAnchor = enclosingAnchorElement(original);
1700 if (!enclosingAnchor)
1701 return original;
1702 }
1703 // Don't insert outside an anchor if doing so would skip over a line break. It would
1704 // probably be safe to move the line break so that we could still avoid the anchor here.
1705 Position downstream(visiblePos.deepEquivalent().downstream());
1706 if (lineBreakExistsAtVisiblePosition(visiblePos) && downstream.deprecatedNode()->isDescendantOf(enclosingAnchor))
1707 return original;
1708
1709 result = positionInParentAfterNode(enclosingAnchor);
1710 }
1711 // If visually just before an anchor, insert *outside* the anchor unless it's the first
1712 // VisiblePosition in a paragraph, to match NSTextView.
1713 if (visiblePos == firstInAnchor) {
1714 // Make sure anchors are pushed down before avoiding them so that we don't
1715 // also avoid structural elements like lists and blocks (5142012).
1716 if (original.deprecatedNode() != enclosingAnchor && original.deprecatedNode()->parentNode() != enclosingAnchor) {
1717 pushAnchorElementDown(*enclosingAnchor);
1718 enclosingAnchor = enclosingAnchorElement(original);
1719 }
1720 if (!enclosingAnchor)
1721 return original;
1722
1723 result = positionInParentBeforeNode(enclosingAnchor);
1724 }
1725 }
1726
1727 if (result.isNull() || !editableRootForPosition(result))
1728 result = original;
1729
1730 return result;
1731}
1732
1733// Splits the tree parent by parent until we reach the specified ancestor. We use VisiblePositions
1734// to determine if the split is necessary. Returns the last split node.
1735RefPtr<Node> CompositeEditCommand::splitTreeToNode(Node& start, Node& end, bool shouldSplitAncestor)
1736{
1737 ASSERT(&start != &end);
1738
1739 RefPtr<Node> adjustedEnd = &end;
1740 if (shouldSplitAncestor && adjustedEnd->parentNode())
1741 adjustedEnd = adjustedEnd->parentNode();
1742
1743 ASSERT(adjustedEnd);
1744 RefPtr<Node> node;
1745 for (node = &start; node && node->parentNode() != adjustedEnd; node = node->parentNode()) {
1746 if (!is<Element>(*node->parentNode()))
1747 break;
1748 // Do not split a node when doing so introduces an empty node.
1749 VisiblePosition positionInParent = firstPositionInNode(node->parentNode());
1750 VisiblePosition positionInNode = firstPositionInOrBeforeNode(node.get());
1751 if (positionInParent != positionInNode)
1752 splitElement(downcast<Element>(*node->parentNode()), *node);
1753 }
1754
1755 return node;
1756}
1757
1758Ref<Element> createBlockPlaceholderElement(Document& document)
1759{
1760 return HTMLBRElement::create(document);
1761}
1762
1763} // namespace WebCore
1764