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

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

Powered by Google App Engine
This is Rietveld 408576698