Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(62)

Side by Side Diff: Source/core/editing/CompositeEditCommand.cpp

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

Powered by Google App Engine
This is Rietveld 408576698