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 "third_party/blink/renderer/core/editing/commands/composite_edit_command.h"
27
28 #include <algorithm>
29
30 #include "third_party/blink/renderer/core/accessibility/blink_ax_event_intent.h"
31 #include "third_party/blink/renderer/core/accessibility/scoped_blink_ax_event_intent.h"
32 #include "third_party/blink/renderer/core/dom/document.h"
33 #include "third_party/blink/renderer/core/dom/document_fragment.h"
34 #include "third_party/blink/renderer/core/dom/element_traversal.h"
35 #include "third_party/blink/renderer/core/dom/events/scoped_event_queue.h"
36 #include "third_party/blink/renderer/core/dom/node_traversal.h"
37 #include "third_party/blink/renderer/core/dom/range.h"
38 #include "third_party/blink/renderer/core/dom/text.h"
39 #include "third_party/blink/renderer/core/editing/commands/append_node_command.h"
40 #include "third_party/blink/renderer/core/editing/commands/apply_style_command.h"
41 #include "third_party/blink/renderer/core/editing/commands/delete_from_text_node_command.h"
42 #include "third_party/blink/renderer/core/editing/commands/delete_selection_command.h"
43 #include "third_party/blink/renderer/core/editing/commands/editing_commands_utilities.h"
44 #include "third_party/blink/renderer/core/editing/commands/insert_into_text_node_command.h"
45 #include "third_party/blink/renderer/core/editing/commands/insert_line_break_command.h"
46 #include "third_party/blink/renderer/core/editing/commands/insert_node_before_command.h"
47 #include "third_party/blink/renderer/core/editing/commands/insert_paragraph_separator_command.h"
48 #include "third_party/blink/renderer/core/editing/commands/merge_identical_elements_command.h"
49 #include "third_party/blink/renderer/core/editing/commands/remove_css_property_command.h"
50 #include "third_party/blink/renderer/core/editing/commands/remove_node_command.h"
51 #include "third_party/blink/renderer/core/editing/commands/remove_node_preserving_children_command.h"
52 #include "third_party/blink/renderer/core/editing/commands/replace_node_with_span_command.h"
53 #include "third_party/blink/renderer/core/editing/commands/replace_selection_command.h"
54 #include "third_party/blink/renderer/core/editing/commands/set_character_data_command.h"
55 #include "third_party/blink/renderer/core/editing/commands/set_node_attribute_command.h"
56 #include "third_party/blink/renderer/core/editing/commands/split_element_command.h"
57 #include "third_party/blink/renderer/core/editing/commands/split_text_node_command.h"
58 #include "third_party/blink/renderer/core/editing/commands/split_text_node_containing_element_command.h"
59 #include "third_party/blink/renderer/core/editing/commands/undo_stack.h"
60 #include "third_party/blink/renderer/core/editing/commands/wrap_contents_in_dummy_span_command.h"
61 #include "third_party/blink/renderer/core/editing/editing_utilities.h"
62 #include "third_party/blink/renderer/core/editing/editor.h"
63 #include "third_party/blink/renderer/core/editing/ephemeral_range.h"
64 #include "third_party/blink/renderer/core/editing/frame_selection.h"
65 #include "third_party/blink/renderer/core/editing/iterators/text_iterator.h"
66 #include "third_party/blink/renderer/core/editing/markers/document_marker_controller.h"
67 #include "third_party/blink/renderer/core/editing/plain_text_range.h"
68 #include "third_party/blink/renderer/core/editing/relocatable_position.h"
69 #include "third_party/blink/renderer/core/editing/selection_template.h"
70 #include "third_party/blink/renderer/core/editing/serializers/serialization.h"
71 #include "third_party/blink/renderer/core/editing/visible_position.h"
72 #include "third_party/blink/renderer/core/editing/visible_selection.h"
73 #include "third_party/blink/renderer/core/editing/visible_units.h"
74 #include "third_party/blink/renderer/core/frame/local_frame.h"
75 #include "third_party/blink/renderer/core/html/html_br_element.h"
76 #include "third_party/blink/renderer/core/html/html_div_element.h"
77 #include "third_party/blink/renderer/core/html/html_element.h"
78 #include "third_party/blink/renderer/core/html/html_li_element.h"
79 #include "third_party/blink/renderer/core/html/html_quote_element.h"
80 #include "third_party/blink/renderer/core/html/html_span_element.h"
81 #include "third_party/blink/renderer/core/html_names.h"
82 #include "third_party/blink/renderer/core/layout/layout_block.h"
83 #include "third_party/blink/renderer/core/layout/layout_list_item.h"
84 #include "third_party/blink/renderer/core/layout/layout_text.h"
85 #include "third_party/blink/renderer/core/layout/line/inline_text_box.h"
86 #include "third_party/blink/renderer/platform/bindings/exception_state.h"
87 #include "third_party/blink/renderer/platform/heap/heap.h"
88
89 namespace blink {
90
CompositeEditCommand(Document & document)91 CompositeEditCommand::CompositeEditCommand(Document& document)
92 : EditCommand(document) {
93 const VisibleSelection& visible_selection =
94 document.GetFrame()
95 ->Selection()
96 .ComputeVisibleSelectionInDOMTreeDeprecated();
97 SetStartingSelection(
98 SelectionForUndoStep::From(visible_selection.AsSelection()));
99 SetEndingSelection(starting_selection_);
100 }
101
~CompositeEditCommand()102 CompositeEditCommand::~CompositeEditCommand() {
103 DCHECK(IsTopLevelCommand() || !undo_step_);
104 }
105
EndingVisibleSelection() const106 VisibleSelection CompositeEditCommand::EndingVisibleSelection() const {
107 // TODO(editing-dev): The use of
108 // |Document::UpdateStyleAndLayout()|
109 // needs to be audited. See http://crbug.com/590369 for more details.
110 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
111 return CreateVisibleSelection(ending_selection_);
112 }
113
Apply()114 bool CompositeEditCommand::Apply() {
115 DCHECK(!IsCommandGroupWrapper());
116 if (!IsRichlyEditablePosition(EndingVisibleSelection().Base())) {
117 switch (GetInputType()) {
118 case InputEvent::InputType::kInsertText:
119 case InputEvent::InputType::kInsertLineBreak:
120 case InputEvent::InputType::kInsertParagraph:
121 case InputEvent::InputType::kInsertFromPaste:
122 case InputEvent::InputType::kInsertFromDrop:
123 case InputEvent::InputType::kInsertFromYank:
124 case InputEvent::InputType::kInsertTranspose:
125 case InputEvent::InputType::kInsertReplacementText:
126 case InputEvent::InputType::kInsertCompositionText:
127 case InputEvent::InputType::kDeleteWordBackward:
128 case InputEvent::InputType::kDeleteWordForward:
129 case InputEvent::InputType::kDeleteSoftLineBackward:
130 case InputEvent::InputType::kDeleteSoftLineForward:
131 case InputEvent::InputType::kDeleteHardLineBackward:
132 case InputEvent::InputType::kDeleteHardLineForward:
133 case InputEvent::InputType::kDeleteContentBackward:
134 case InputEvent::InputType::kDeleteContentForward:
135 case InputEvent::InputType::kDeleteByCut:
136 case InputEvent::InputType::kDeleteByDrag:
137 case InputEvent::InputType::kNone:
138 break;
139 default:
140 return false;
141 }
142 }
143 EnsureUndoStep();
144
145 // Changes to the document may have been made since the last editing operation
146 // that require a layout, as in <rdar://problem/5658603>. Low level
147 // operations, like RemoveNodeCommand, don't require a layout because the high
148 // level operations that use them perform one if one is necessary (like for
149 // the creation of VisiblePositions).
150 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
151
152 LocalFrame* frame = GetDocument().GetFrame();
153 DCHECK(frame);
154 // directional is stored at the top level command, so that before and after
155 // executing command same directional will be there.
156 SetSelectionIsDirectional(frame->Selection().IsDirectional());
157 GetUndoStep()->SetSelectionIsDirectional(SelectionIsDirectional());
158
159 // Provides details to accessibility about any text change caused by applying
160 // this command, throughout the current call stack.
161 ScopedBlinkAXEventIntent scoped_blink_ax_event_intent(
162 BlinkAXEventIntent::FromEditCommand(*this), &GetDocument());
163
164 EditingState editing_state;
165 EventQueueScope event_queue_scope;
166 DoApply(&editing_state);
167
168 // Only need to call appliedEditing for top-level commands, and TypingCommands
169 // do it on their own (see TypingCommand::typingAddedToOpenCommand).
170 if (!IsTypingCommand())
171 AppliedEditing();
172 return !editing_state.IsAborted();
173 }
174
EnsureUndoStep()175 UndoStep* CompositeEditCommand::EnsureUndoStep() {
176 CompositeEditCommand* command = this;
177 while (command && command->Parent())
178 command = command->Parent();
179 if (!command->undo_step_) {
180 command->undo_step_ = MakeGarbageCollected<UndoStep>(
181 &GetDocument(), StartingSelection(), EndingSelection(), GetInputType());
182 }
183 return command->undo_step_.Get();
184 }
185
PreservesTypingStyle() const186 bool CompositeEditCommand::PreservesTypingStyle() const {
187 return false;
188 }
189
IsTypingCommand() const190 bool CompositeEditCommand::IsTypingCommand() const {
191 return false;
192 }
193
IsCommandGroupWrapper() const194 bool CompositeEditCommand::IsCommandGroupWrapper() const {
195 return false;
196 }
197
IsDragAndDropCommand() const198 bool CompositeEditCommand::IsDragAndDropCommand() const {
199 return false;
200 }
201
IsReplaceSelectionCommand() const202 bool CompositeEditCommand::IsReplaceSelectionCommand() const {
203 return false;
204 }
205
206 //
207 // sugary-sweet convenience functions to help create and apply edit commands in
208 // composite commands
209 //
ApplyCommandToComposite(EditCommand * command,EditingState * editing_state)210 void CompositeEditCommand::ApplyCommandToComposite(
211 EditCommand* command,
212 EditingState* editing_state) {
213 command->SetParent(this);
214 command->SetSelectionIsDirectional(SelectionIsDirectional());
215 command->DoApply(editing_state);
216 if (editing_state->IsAborted()) {
217 command->SetParent(nullptr);
218 return;
219 }
220 if (auto* simple_edit_command = DynamicTo<SimpleEditCommand>(command)) {
221 command->SetParent(nullptr);
222 EnsureUndoStep()->Append(simple_edit_command);
223 }
224 commands_.push_back(command);
225 }
226
AppendCommandToUndoStep(CompositeEditCommand * command)227 void CompositeEditCommand::AppendCommandToUndoStep(
228 CompositeEditCommand* command) {
229 EnsureUndoStep()->Append(command->EnsureUndoStep());
230 command->undo_step_ = nullptr;
231 command->SetParent(this);
232 commands_.push_back(command);
233 }
234
ApplyStyle(const EditingStyle * style,EditingState * editing_state)235 void CompositeEditCommand::ApplyStyle(const EditingStyle* style,
236 EditingState* editing_state) {
237 ApplyCommandToComposite(
238 MakeGarbageCollected<ApplyStyleCommand>(GetDocument(), style,
239 InputEvent::InputType::kNone),
240 editing_state);
241 }
242
ApplyStyle(const EditingStyle * style,const Position & start,const Position & end,EditingState * editing_state)243 void CompositeEditCommand::ApplyStyle(const EditingStyle* style,
244 const Position& start,
245 const Position& end,
246 EditingState* editing_state) {
247 ApplyCommandToComposite(
248 MakeGarbageCollected<ApplyStyleCommand>(GetDocument(), style, start, end),
249 editing_state);
250 }
251
ApplyStyledElement(Element * element,EditingState * editing_state)252 void CompositeEditCommand::ApplyStyledElement(Element* element,
253 EditingState* editing_state) {
254 ApplyCommandToComposite(
255 MakeGarbageCollected<ApplyStyleCommand>(element, false), editing_state);
256 }
257
RemoveStyledElement(Element * element,EditingState * editing_state)258 void CompositeEditCommand::RemoveStyledElement(Element* element,
259 EditingState* editing_state) {
260 ApplyCommandToComposite(
261 MakeGarbageCollected<ApplyStyleCommand>(element, true), editing_state);
262 }
263
InsertParagraphSeparator(EditingState * editing_state,bool use_default_paragraph_element,bool paste_blockqutoe_into_unquoted_area)264 void CompositeEditCommand::InsertParagraphSeparator(
265 EditingState* editing_state,
266 bool use_default_paragraph_element,
267 bool paste_blockqutoe_into_unquoted_area) {
268 ApplyCommandToComposite(MakeGarbageCollected<InsertParagraphSeparatorCommand>(
269 GetDocument(), use_default_paragraph_element,
270 paste_blockqutoe_into_unquoted_area),
271 editing_state);
272 }
273
IsRemovableBlock(const Node * node)274 bool CompositeEditCommand::IsRemovableBlock(const Node* node) {
275 DCHECK(node);
276 const auto* element = DynamicTo<HTMLDivElement>(node);
277 if (!element)
278 return false;
279
280 ContainerNode* parent_node = element->parentNode();
281 if (parent_node && parent_node->firstChild() != parent_node->lastChild())
282 return false;
283
284 if (!element->hasAttributes())
285 return true;
286
287 return false;
288 }
289
InsertNodeBefore(Node * insert_child,Node * ref_child,EditingState * editing_state,ShouldAssumeContentIsAlwaysEditable should_assume_content_is_always_editable)290 void CompositeEditCommand::InsertNodeBefore(
291 Node* insert_child,
292 Node* ref_child,
293 EditingState* editing_state,
294 ShouldAssumeContentIsAlwaysEditable
295 should_assume_content_is_always_editable) {
296 ABORT_EDITING_COMMAND_IF(GetDocument().body() == ref_child);
297 ABORT_EDITING_COMMAND_IF(!ref_child->parentNode());
298 // TODO(editing-dev): Use of UpdateStyleAndLayout
299 // needs to be audited. See http://crbug.com/590369 for more details.
300 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
301 ABORT_EDITING_COMMAND_IF(!HasEditableStyle(*ref_child->parentNode()) &&
302 ref_child->parentNode()->InActiveDocument());
303 ApplyCommandToComposite(
304 MakeGarbageCollected<InsertNodeBeforeCommand>(
305 insert_child, ref_child, should_assume_content_is_always_editable),
306 editing_state);
307 }
308
InsertNodeAfter(Node * insert_child,Node * ref_child,EditingState * editing_state)309 void CompositeEditCommand::InsertNodeAfter(Node* insert_child,
310 Node* ref_child,
311 EditingState* editing_state) {
312 ABORT_EDITING_COMMAND_IF(!ref_child->parentNode());
313 DCHECK(insert_child);
314 DCHECK(ref_child);
315 DCHECK_NE(GetDocument().body(), ref_child);
316 ContainerNode* parent = ref_child->parentNode();
317 DCHECK(parent);
318 DCHECK(!parent->IsShadowRoot()) << parent;
319 if (parent->lastChild() == ref_child) {
320 AppendNode(insert_child, parent, editing_state);
321 } else {
322 DCHECK(ref_child->nextSibling()) << ref_child;
323 InsertNodeBefore(insert_child, ref_child->nextSibling(), editing_state);
324 }
325 }
326
InsertNodeAt(Node * insert_child,const Position & editing_position,EditingState * editing_state)327 void CompositeEditCommand::InsertNodeAt(Node* insert_child,
328 const Position& editing_position,
329 EditingState* editing_state) {
330 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
331 ABORT_EDITING_COMMAND_IF(!IsEditablePosition(editing_position));
332 // For editing positions like [table, 0], insert before the table,
333 // likewise for replaced elements, brs, etc.
334 Position p = editing_position.ParentAnchoredEquivalent();
335 Node* ref_child = p.AnchorNode();
336 int offset = p.OffsetInContainerNode();
337
338 auto* ref_child_text_node = DynamicTo<Text>(ref_child);
339 if (CanHaveChildrenForEditing(ref_child)) {
340 Node* child = ref_child->firstChild();
341 for (int i = 0; child && i < offset; i++)
342 child = child->nextSibling();
343 if (child)
344 InsertNodeBefore(insert_child, child, editing_state);
345 else
346 AppendNode(insert_child, To<ContainerNode>(ref_child), editing_state);
347 } else if (CaretMinOffset(ref_child) >= offset) {
348 InsertNodeBefore(insert_child, ref_child, editing_state);
349 } else if (ref_child_text_node && CaretMaxOffset(ref_child) > offset) {
350 SplitTextNode(ref_child_text_node, offset);
351
352 // Mutation events (bug 22634) from the text node insertion may have
353 // removed the refChild
354 if (!ref_child->isConnected())
355 return;
356 InsertNodeBefore(insert_child, ref_child, editing_state);
357 } else {
358 InsertNodeAfter(insert_child, ref_child, editing_state);
359 }
360 }
361
AppendNode(Node * node,ContainerNode * parent,EditingState * editing_state)362 void CompositeEditCommand::AppendNode(Node* node,
363 ContainerNode* parent,
364 EditingState* editing_state) {
365 // When cloneParagraphUnderNewElement() clones the fallback content
366 // of an OBJECT element, the ASSERT below may fire since the return
367 // value of canHaveChildrenForEditing is not reliable until the layout
368 // object of the OBJECT is created. Hence we ignore this check for OBJECTs.
369 // TODO(yosin): We should move following |ABORT_EDITING_COMMAND_IF|s to
370 // |AppendNodeCommand|.
371 // TODO(yosin): We should get rid of |canHaveChildrenForEditing()|, since
372 // |cloneParagraphUnderNewElement()| attempt to clone non-well-formed HTML,
373 // produced by JavaScript.
374 auto* parent_element = DynamicTo<Element>(parent);
375 ABORT_EDITING_COMMAND_IF(!CanHaveChildrenForEditing(parent) &&
376 !(parent_element && parent_element->TagQName() ==
377 html_names::kObjectTag));
378 ABORT_EDITING_COMMAND_IF(!HasEditableStyle(*parent) &&
379 parent->InActiveDocument());
380 ApplyCommandToComposite(MakeGarbageCollected<AppendNodeCommand>(parent, node),
381 editing_state);
382 }
383
RemoveAllChildrenIfPossible(ContainerNode * container,EditingState * editing_state,ShouldAssumeContentIsAlwaysEditable should_assume_content_is_always_editable)384 void CompositeEditCommand::RemoveAllChildrenIfPossible(
385 ContainerNode* container,
386 EditingState* editing_state,
387 ShouldAssumeContentIsAlwaysEditable
388 should_assume_content_is_always_editable) {
389 Node* child = container->firstChild();
390 while (child) {
391 Node* const next = child->nextSibling();
392 RemoveNode(child, editing_state, should_assume_content_is_always_editable);
393 if (editing_state->IsAborted())
394 return;
395 if (next && next->parentNode() != container) {
396 // |RemoveNode()| moves |next| outside |node|.
397 return;
398 }
399 child = next;
400 }
401 }
402
RemoveChildrenInRange(Node * node,unsigned from,unsigned to,EditingState * editing_state)403 void CompositeEditCommand::RemoveChildrenInRange(Node* node,
404 unsigned from,
405 unsigned to,
406 EditingState* editing_state) {
407 HeapVector<Member<Node>> children;
408 Node* child = NodeTraversal::ChildAt(*node, from);
409 for (unsigned i = from; child && i < to; i++, child = child->nextSibling())
410 children.push_back(child);
411
412 size_t size = children.size();
413 for (wtf_size_t i = 0; i < size; ++i) {
414 RemoveNode(children[i].Release(), editing_state);
415 if (editing_state->IsAborted())
416 return;
417 }
418 }
419
RemoveNode(Node * node,EditingState * editing_state,ShouldAssumeContentIsAlwaysEditable should_assume_content_is_always_editable)420 void CompositeEditCommand::RemoveNode(
421 Node* node,
422 EditingState* editing_state,
423 ShouldAssumeContentIsAlwaysEditable
424 should_assume_content_is_always_editable) {
425 if (!node || !node->NonShadowBoundaryParentNode())
426 return;
427 ABORT_EDITING_COMMAND_IF(!node->GetDocument().GetFrame());
428 ApplyCommandToComposite(MakeGarbageCollected<RemoveNodeCommand>(
429 node, should_assume_content_is_always_editable),
430 editing_state);
431 }
432
RemoveNodePreservingChildren(Node * node,EditingState * editing_state,ShouldAssumeContentIsAlwaysEditable should_assume_content_is_always_editable)433 void CompositeEditCommand::RemoveNodePreservingChildren(
434 Node* node,
435 EditingState* editing_state,
436 ShouldAssumeContentIsAlwaysEditable
437 should_assume_content_is_always_editable) {
438 ABORT_EDITING_COMMAND_IF(!node->GetDocument().GetFrame());
439 ApplyCommandToComposite(
440 MakeGarbageCollected<RemoveNodePreservingChildrenCommand>(
441 node, should_assume_content_is_always_editable),
442 editing_state);
443 }
444
RemoveNodeAndPruneAncestors(Node * node,EditingState * editing_state,Node * exclude_node)445 void CompositeEditCommand::RemoveNodeAndPruneAncestors(
446 Node* node,
447 EditingState* editing_state,
448 Node* exclude_node) {
449 DCHECK_NE(node, exclude_node);
450 ContainerNode* parent = node->parentNode();
451 RemoveNode(node, editing_state);
452 if (editing_state->IsAborted())
453 return;
454 Prune(parent, editing_state, exclude_node);
455 }
456
MoveRemainingSiblingsToNewParent(Node * node,Node * past_last_node_to_move,Element * new_parent,EditingState * editing_state)457 void CompositeEditCommand::MoveRemainingSiblingsToNewParent(
458 Node* node,
459 Node* past_last_node_to_move,
460 Element* new_parent,
461 EditingState* editing_state) {
462 NodeVector nodes_to_remove;
463
464 for (; node && node != past_last_node_to_move; node = node->nextSibling())
465 nodes_to_remove.push_back(node);
466
467 for (unsigned i = 0; i < nodes_to_remove.size(); i++) {
468 RemoveNode(nodes_to_remove[i], editing_state);
469 if (editing_state->IsAborted())
470 return;
471 AppendNode(nodes_to_remove[i], new_parent, editing_state);
472 if (editing_state->IsAborted())
473 return;
474 }
475 }
476
UpdatePositionForNodeRemovalPreservingChildren(Position & position,Node & node)477 void CompositeEditCommand::UpdatePositionForNodeRemovalPreservingChildren(
478 Position& position,
479 Node& node) {
480 int offset =
481 position.IsOffsetInAnchor() ? position.OffsetInContainerNode() : 0;
482 position = ComputePositionForNodeRemoval(position, node);
483 if (offset == 0)
484 return;
485 position = Position::CreateWithoutValidationDeprecated(
486 *position.ComputeContainerNode(), offset);
487 }
488
489 HTMLSpanElement*
ReplaceElementWithSpanPreservingChildrenAndAttributes(HTMLElement * node)490 CompositeEditCommand::ReplaceElementWithSpanPreservingChildrenAndAttributes(
491 HTMLElement* node) {
492 // It would also be possible to implement all of ReplaceNodeWithSpanCommand
493 // as a series of existing smaller edit commands. Someone who wanted to
494 // reduce the number of edit commands could do so here.
495 auto* command = MakeGarbageCollected<ReplaceNodeWithSpanCommand>(node);
496 // ReplaceNodeWithSpanCommand is never aborted.
497 ApplyCommandToComposite(command, ASSERT_NO_EDITING_ABORT);
498 // Returning a raw pointer here is OK because the command is retained by
499 // applyCommandToComposite (thus retaining the span), and the span is also
500 // in the DOM tree, and thus alive whie it has a parent.
501 DCHECK(command->SpanElement()->isConnected()) << command->SpanElement();
502 return command->SpanElement();
503 }
504
Prune(Node * node,EditingState * editing_state,Node * exclude_node)505 void CompositeEditCommand::Prune(Node* node,
506 EditingState* editing_state,
507 Node* exclude_node) {
508 if (Node* highest_node_to_remove =
509 HighestNodeToRemoveInPruning(node, exclude_node))
510 RemoveNode(highest_node_to_remove, editing_state);
511 }
512
SplitTextNode(Text * node,unsigned offset)513 void CompositeEditCommand::SplitTextNode(Text* node, unsigned offset) {
514 // SplitTextNodeCommand is never aborted.
515 ApplyCommandToComposite(
516 MakeGarbageCollected<SplitTextNodeCommand>(node, offset),
517 ASSERT_NO_EDITING_ABORT);
518 }
519
SplitElement(Element * element,Node * at_child)520 void CompositeEditCommand::SplitElement(Element* element, Node* at_child) {
521 // SplitElementCommand is never aborted.
522 ApplyCommandToComposite(
523 MakeGarbageCollected<SplitElementCommand>(element, at_child),
524 ASSERT_NO_EDITING_ABORT);
525 }
526
MergeIdenticalElements(Element * first,Element * second,EditingState * editing_state)527 void CompositeEditCommand::MergeIdenticalElements(Element* first,
528 Element* second,
529 EditingState* editing_state) {
530 DCHECK(!first->IsDescendantOf(second)) << first << " " << second;
531 DCHECK_NE(second, first);
532 if (first->nextSibling() != second) {
533 RemoveNode(second, editing_state);
534 if (editing_state->IsAborted())
535 return;
536 InsertNodeAfter(second, first, editing_state);
537 if (editing_state->IsAborted())
538 return;
539 }
540 ApplyCommandToComposite(
541 MakeGarbageCollected<MergeIdenticalElementsCommand>(first, second),
542 editing_state);
543 }
544
WrapContentsInDummySpan(Element * element)545 void CompositeEditCommand::WrapContentsInDummySpan(Element* element) {
546 // WrapContentsInDummySpanCommand is never aborted.
547 ApplyCommandToComposite(
548 MakeGarbageCollected<WrapContentsInDummySpanCommand>(element),
549 ASSERT_NO_EDITING_ABORT);
550 }
551
SplitTextNodeContainingElement(Text * text,unsigned offset)552 void CompositeEditCommand::SplitTextNodeContainingElement(Text* text,
553 unsigned offset) {
554 // SplitTextNodeContainingElementCommand is never aborted.
555 ApplyCommandToComposite(
556 MakeGarbageCollected<SplitTextNodeContainingElementCommand>(text, offset),
557 ASSERT_NO_EDITING_ABORT);
558 }
559
InsertTextIntoNode(Text * node,unsigned offset,const String & text)560 void CompositeEditCommand::InsertTextIntoNode(Text* node,
561 unsigned offset,
562 const String& text) {
563 // InsertIntoTextNodeCommand is never aborted.
564 if (!text.IsEmpty())
565 ApplyCommandToComposite(
566 MakeGarbageCollected<InsertIntoTextNodeCommand>(node, offset, text),
567 ASSERT_NO_EDITING_ABORT);
568 }
569
DeleteTextFromNode(Text * node,unsigned offset,unsigned count)570 void CompositeEditCommand::DeleteTextFromNode(Text* node,
571 unsigned offset,
572 unsigned count) {
573 // DeleteFromTextNodeCommand is never aborted.
574 ApplyCommandToComposite(
575 MakeGarbageCollected<DeleteFromTextNodeCommand>(node, offset, count),
576 ASSERT_NO_EDITING_ABORT);
577 }
578
ReplaceTextInNode(Text * node,unsigned offset,unsigned count,const String & replacement_text)579 void CompositeEditCommand::ReplaceTextInNode(Text* node,
580 unsigned offset,
581 unsigned count,
582 const String& replacement_text) {
583 // SetCharacterDataCommand is never aborted.
584 ApplyCommandToComposite(MakeGarbageCollected<SetCharacterDataCommand>(
585 node, offset, count, replacement_text),
586 ASSERT_NO_EDITING_ABORT);
587 }
588
ReplaceSelectedTextInNode(const String & text)589 Position CompositeEditCommand::ReplaceSelectedTextInNode(const String& text) {
590 const Position& start = EndingSelection().Start();
591 const Position& end = EndingSelection().End();
592 auto* text_node = DynamicTo<Text>(start.ComputeContainerNode());
593 if (!text_node || text_node != end.ComputeContainerNode() ||
594 IsTabHTMLSpanElementTextNode(text_node))
595 return Position();
596
597 ReplaceTextInNode(text_node, start.OffsetInContainerNode(),
598 end.OffsetInContainerNode() - start.OffsetInContainerNode(),
599 text);
600
601 return Position(text_node, start.OffsetInContainerNode() + text.length());
602 }
603
PositionOutsideTabSpan(const Position & pos)604 Position CompositeEditCommand::PositionOutsideTabSpan(const Position& pos) {
605 if (!IsTabHTMLSpanElementTextNode(pos.AnchorNode()))
606 return pos;
607
608 switch (pos.AnchorType()) {
609 case PositionAnchorType::kBeforeChildren:
610 case PositionAnchorType::kAfterChildren:
611 NOTREACHED();
612 return pos;
613 case PositionAnchorType::kOffsetInAnchor:
614 break;
615 case PositionAnchorType::kBeforeAnchor:
616 return Position::InParentBeforeNode(*pos.AnchorNode());
617 case PositionAnchorType::kAfterAnchor:
618 return Position::InParentAfterNode(*pos.AnchorNode());
619 }
620
621 HTMLSpanElement* tab_span = TabSpanElement(pos.ComputeContainerNode());
622 DCHECK(tab_span);
623
624 // TODO(editing-dev): Hoist this UpdateStyleAndLayout
625 // to the callers. See crbug.com/590369 for details.
626 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
627
628 if (pos.OffsetInContainerNode() <= CaretMinOffset(pos.ComputeContainerNode()))
629 return Position::InParentBeforeNode(*tab_span);
630
631 if (pos.OffsetInContainerNode() >= CaretMaxOffset(pos.ComputeContainerNode()))
632 return Position::InParentAfterNode(*tab_span);
633
634 SplitTextNodeContainingElement(To<Text>(pos.ComputeContainerNode()),
635 pos.OffsetInContainerNode());
636 return Position::InParentBeforeNode(*tab_span);
637 }
638
InsertNodeAtTabSpanPosition(Node * node,const Position & pos,EditingState * editing_state)639 void CompositeEditCommand::InsertNodeAtTabSpanPosition(
640 Node* node,
641 const Position& pos,
642 EditingState* editing_state) {
643 // insert node before, after, or at split of tab span
644 InsertNodeAt(node, PositionOutsideTabSpan(pos), editing_state);
645 }
646
DeleteSelection(EditingState * editing_state,const DeleteSelectionOptions & options)647 bool CompositeEditCommand::DeleteSelection(
648 EditingState* editing_state,
649 const DeleteSelectionOptions& options) {
650 if (!EndingSelection().IsRange())
651 return true;
652
653 ApplyCommandToComposite(
654 MakeGarbageCollected<DeleteSelectionCommand>(GetDocument(), options),
655 editing_state);
656 if (editing_state->IsAborted())
657 return false;
658
659 if (!EndingSelection().IsValidFor(GetDocument())) {
660 editing_state->Abort();
661 return false;
662 }
663 return true;
664 }
665
RemoveCSSProperty(Element * element,CSSPropertyID property)666 void CompositeEditCommand::RemoveCSSProperty(Element* element,
667 CSSPropertyID property) {
668 // RemoveCSSPropertyCommand is never aborted.
669 ApplyCommandToComposite(MakeGarbageCollected<RemoveCSSPropertyCommand>(
670 GetDocument(), element, property),
671 ASSERT_NO_EDITING_ABORT);
672 }
673
RemoveElementAttribute(Element * element,const QualifiedName & attribute)674 void CompositeEditCommand::RemoveElementAttribute(
675 Element* element,
676 const QualifiedName& attribute) {
677 SetNodeAttribute(element, attribute, AtomicString());
678 }
679
SetNodeAttribute(Element * element,const QualifiedName & attribute,const AtomicString & value)680 void CompositeEditCommand::SetNodeAttribute(Element* element,
681 const QualifiedName& attribute,
682 const AtomicString& value) {
683 // SetNodeAttributeCommand is never aborted.
684 ApplyCommandToComposite(
685 MakeGarbageCollected<SetNodeAttributeCommand>(element, attribute, value),
686 ASSERT_NO_EDITING_ABORT);
687 }
688
CanRebalance(const Position & position) const689 bool CompositeEditCommand::CanRebalance(const Position& position) const {
690 // TODO(editing-dev): Use of UpdateStyleAndLayout()
691 // needs to be audited. See http://crbug.com/590369 for more details.
692 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
693
694 auto* text_node = DynamicTo<Text>(position.ComputeContainerNode());
695 if (!position.IsOffsetInAnchor() || !text_node ||
696 !HasRichlyEditableStyle(*text_node))
697 return false;
698
699 if (text_node->length() == 0)
700 return false;
701
702 LayoutText* layout_text = text_node->GetLayoutObject();
703 if (layout_text && !layout_text->Style()->CollapseWhiteSpace())
704 return false;
705
706 return true;
707 }
708
709 // FIXME: Doesn't go into text nodes that contribute adjacent text (siblings,
710 // cousins, etc).
RebalanceWhitespaceAt(const Position & position)711 void CompositeEditCommand::RebalanceWhitespaceAt(const Position& position) {
712 Node* node = position.ComputeContainerNode();
713 if (!CanRebalance(position))
714 return;
715
716 // If the rebalance is for the single offset, and neither text[offset] nor
717 // text[offset - 1] are some form of whitespace, do nothing.
718 int offset = position.ComputeOffsetInContainerNode();
719 String text = To<Text>(node)->data();
720 if (!IsWhitespace(text[offset])) {
721 offset--;
722 if (offset < 0 || !IsWhitespace(text[offset]))
723 return;
724 }
725
726 RebalanceWhitespaceOnTextSubstring(To<Text>(node),
727 position.OffsetInContainerNode(),
728 position.OffsetInContainerNode());
729 }
730
RebalanceWhitespaceOnTextSubstring(Text * text_node,int start_offset,int end_offset)731 void CompositeEditCommand::RebalanceWhitespaceOnTextSubstring(Text* text_node,
732 int start_offset,
733 int end_offset) {
734 String text = text_node->data();
735 DCHECK(!text.IsEmpty());
736
737 // Set upstream and downstream to define the extent of the whitespace
738 // surrounding text[offset].
739 int upstream = start_offset;
740 while (upstream > 0 && IsWhitespace(text[upstream - 1]))
741 upstream--;
742
743 int downstream = end_offset;
744 while ((unsigned)downstream < text.length() && IsWhitespace(text[downstream]))
745 downstream++;
746
747 int length = downstream - upstream;
748 if (!length)
749 return;
750
751 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
752 VisiblePosition visible_upstream_pos =
753 CreateVisiblePosition(Position(text_node, upstream));
754 VisiblePosition visible_downstream_pos =
755 CreateVisiblePosition(Position(text_node, downstream));
756
757 String string = text.Substring(upstream, length);
758 // FIXME: Because of the problem mentioned at the top of this function, we
759 // must also use nbsps at the start/end of the string because this function
760 // doesn't get all surrounding whitespace, just the whitespace in the
761 // current text node. However, if the next sibling node is a text node
762 // (not empty, see http://crbug.com/632300), we should use a plain space.
763 // See http://crbug.com/310149
764 auto* next_text_node = DynamicTo<Text>(text_node->nextSibling());
765 const bool next_sibling_is_text_node =
766 next_text_node && next_text_node->data().length() &&
767 !IsWhitespace(next_text_node->data()[0]);
768 const bool should_emit_nbs_pbefore_end =
769 (IsEndOfParagraph(visible_downstream_pos) ||
770 (unsigned)downstream == text.length()) &&
771 !next_sibling_is_text_node;
772 String rebalanced_string = StringWithRebalancedWhitespace(
773 string, IsStartOfParagraph(visible_upstream_pos) || !upstream,
774 should_emit_nbs_pbefore_end);
775
776 if (string != rebalanced_string)
777 ReplaceTextInNode(text_node, upstream, length, rebalanced_string);
778 }
779
PrepareWhitespaceAtPositionForSplit(Position & position)780 void CompositeEditCommand::PrepareWhitespaceAtPositionForSplit(
781 Position& position) {
782 if (!IsRichlyEditablePosition(position))
783 return;
784
785 auto* text_node = DynamicTo<Text>(position.AnchorNode());
786 if (!text_node)
787 return;
788
789 if (text_node->length() == 0)
790 return;
791 LayoutText* layout_text = text_node->GetLayoutObject();
792 if (layout_text && !layout_text->Style()->CollapseWhiteSpace())
793 return;
794
795 // Delete collapsed whitespace so that inserting nbsps doesn't uncollapse it.
796 Position upstream_pos = MostBackwardCaretPosition(position);
797 DeleteInsignificantText(upstream_pos, MostForwardCaretPosition(position));
798
799 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
800 position = MostForwardCaretPosition(upstream_pos);
801 VisiblePosition visible_pos = CreateVisiblePosition(position);
802 VisiblePosition previous_visible_pos = PreviousPositionOf(visible_pos);
803 ReplaceCollapsibleWhitespaceWithNonBreakingSpaceIfNeeded(
804 previous_visible_pos);
805
806 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
807 ReplaceCollapsibleWhitespaceWithNonBreakingSpaceIfNeeded(
808 CreateVisiblePosition(position));
809 }
810
811 void CompositeEditCommand::
ReplaceCollapsibleWhitespaceWithNonBreakingSpaceIfNeeded(const VisiblePosition & visible_position)812 ReplaceCollapsibleWhitespaceWithNonBreakingSpaceIfNeeded(
813 const VisiblePosition& visible_position) {
814 if (!IsCollapsibleWhitespace(CharacterAfter(visible_position)))
815 return;
816 Position pos = MostForwardCaretPosition(visible_position.DeepEquivalent());
817 auto* container_text_node = DynamicTo<Text>(pos.ComputeContainerNode());
818 if (!container_text_node)
819 return;
820 ReplaceTextInNode(container_text_node, pos.OffsetInContainerNode(), 1,
821 NonBreakingSpaceString());
822 }
823
RebalanceWhitespace()824 void CompositeEditCommand::RebalanceWhitespace() {
825 VisibleSelection selection = EndingVisibleSelection();
826 if (selection.IsNone())
827 return;
828
829 RebalanceWhitespaceAt(selection.Start());
830 if (selection.IsRange())
831 RebalanceWhitespaceAt(selection.End());
832 }
833
DeleteInsignificantText(Text * text_node,unsigned start,unsigned end)834 void CompositeEditCommand::DeleteInsignificantText(Text* text_node,
835 unsigned start,
836 unsigned end) {
837 if (!text_node || start >= end)
838 return;
839
840 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
841
842 LayoutText* text_layout_object = text_node->GetLayoutObject();
843 if (!text_layout_object)
844 return;
845
846 if (!text_layout_object->HasInlineFragments()) {
847 // whole text node is empty
848 // Removing a Text node won't dispatch synchronous events.
849 RemoveNode(text_node, ASSERT_NO_EDITING_ABORT);
850 return;
851 }
852 unsigned length = text_node->length();
853 if (start >= length || end > length)
854 return;
855
856 if (text_layout_object->IsInLayoutNGInlineFormattingContext()) {
857 const String string = PlainText(
858 EphemeralRange(Position(*text_node, start), Position(*text_node, end)));
859 if (string.IsEmpty())
860 return DeleteTextFromNode(text_node, start, end - start);
861 // Replace the text between start and end with collapsed version.
862 return ReplaceTextInNode(text_node, start, end - start, string);
863 }
864
865 Vector<InlineTextBox*> sorted_text_boxes;
866 wtf_size_t sorted_text_boxes_position = 0;
867
868 for (InlineTextBox* text_box : text_layout_object->TextBoxes())
869 sorted_text_boxes.push_back(text_box);
870
871 // If there is mixed directionality text, the boxes can be out of order,
872 // (like Arabic with embedded LTR), so sort them first.
873 if (text_layout_object->ContainsReversedText())
874 std::sort(sorted_text_boxes.begin(), sorted_text_boxes.end(),
875 InlineTextBox::CompareByStart);
876 InlineTextBox* box = sorted_text_boxes.IsEmpty()
877 ? 0
878 : sorted_text_boxes[sorted_text_boxes_position];
879
880 unsigned removed = 0;
881 InlineTextBox* prev_box = nullptr;
882 String str;
883
884 // This loop structure works to process all gaps preceding a box,
885 // and also will look at the gap after the last box.
886 while (prev_box || box) {
887 unsigned gap_start = prev_box ? prev_box->Start() + prev_box->Len() : 0;
888 if (end < gap_start) {
889 // No more chance for any intersections
890 break;
891 }
892
893 unsigned gap_end = box ? box->Start() : length;
894 bool indices_intersect = start <= gap_end && end >= gap_start;
895 int gap_len = gap_end - gap_start;
896 if (indices_intersect && gap_len > 0) {
897 gap_start = std::max(gap_start, start);
898 if (str.IsNull())
899 str = text_node->data().Substring(start, end - start);
900 // remove text in the gap
901 str.Remove(gap_start - start - removed, gap_len);
902 removed += gap_len;
903 }
904
905 prev_box = box;
906 if (box) {
907 if (++sorted_text_boxes_position < sorted_text_boxes.size())
908 box = sorted_text_boxes[sorted_text_boxes_position];
909 else
910 box = nullptr;
911 }
912 }
913
914 if (!str.IsNull()) {
915 // Replace the text between start and end with our pruned version.
916 if (!str.IsEmpty()) {
917 ReplaceTextInNode(text_node, start, end - start, str);
918 } else {
919 // Assert that we are not going to delete all of the text in the node.
920 // If we were, that should have been done above with the call to
921 // removeNode and return.
922 DCHECK(start > 0 || end - start < text_node->length());
923 DeleteTextFromNode(text_node, start, end - start);
924 }
925 }
926 }
927
DeleteInsignificantText(const Position & start,const Position & end)928 void CompositeEditCommand::DeleteInsignificantText(const Position& start,
929 const Position& end) {
930 if (start.IsNull() || end.IsNull())
931 return;
932
933 if (ComparePositions(start, end) >= 0)
934 return;
935
936 HeapVector<Member<Text>> nodes;
937 for (Node& node : NodeTraversal::StartsAt(*start.AnchorNode())) {
938 if (auto* text_node = DynamicTo<Text>(&node))
939 nodes.push_back(text_node);
940 if (&node == end.AnchorNode())
941 break;
942 }
943
944 for (const auto& node : nodes) {
945 Text* text_node = node;
946 int start_offset = text_node == start.AnchorNode()
947 ? start.ComputeOffsetInContainerNode()
948 : 0;
949 int end_offset = text_node == end.AnchorNode()
950 ? end.ComputeOffsetInContainerNode()
951 : static_cast<int>(text_node->length());
952 DeleteInsignificantText(text_node, start_offset, end_offset);
953 }
954 }
955
DeleteInsignificantTextDownstream(const Position & pos)956 void CompositeEditCommand::DeleteInsignificantTextDownstream(
957 const Position& pos) {
958 DCHECK(!GetDocument().NeedsLayoutTreeUpdate());
959 Position end = MostForwardCaretPosition(
960 NextPositionOf(CreateVisiblePosition(pos)).DeepEquivalent());
961 DeleteInsignificantText(pos, end);
962 }
963
AppendBlockPlaceholder(Element * container,EditingState * editing_state)964 HTMLBRElement* CompositeEditCommand::AppendBlockPlaceholder(
965 Element* container,
966 EditingState* editing_state) {
967 if (!container)
968 return nullptr;
969
970 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
971
972 // Should assert isLayoutBlockFlow || isInlineFlow when deletion improves. See
973 // 4244964.
974 DCHECK(container->GetLayoutObject()) << container;
975
976 auto* placeholder = MakeGarbageCollected<HTMLBRElement>(GetDocument());
977 AppendNode(placeholder, container, editing_state);
978 if (editing_state->IsAborted())
979 return nullptr;
980 return placeholder;
981 }
982
InsertBlockPlaceholder(const Position & pos,EditingState * editing_state)983 HTMLBRElement* CompositeEditCommand::InsertBlockPlaceholder(
984 const Position& pos,
985 EditingState* editing_state) {
986 if (pos.IsNull())
987 return nullptr;
988
989 // Should assert isLayoutBlockFlow || isInlineFlow when deletion improves. See
990 // 4244964.
991 DCHECK(pos.AnchorNode()->GetLayoutObject()) << pos;
992
993 auto* placeholder = MakeGarbageCollected<HTMLBRElement>(GetDocument());
994 InsertNodeAt(placeholder, pos, editing_state);
995 if (editing_state->IsAborted())
996 return nullptr;
997 return placeholder;
998 }
999
IsEmptyListItem(const LayoutBlockFlow & block_flow)1000 static bool IsEmptyListItem(const LayoutBlockFlow& block_flow) {
1001 if (block_flow.IsLayoutNGListItem())
1002 return !block_flow.FirstChild();
1003 if (block_flow.IsListItem())
1004 return To<LayoutListItem>(block_flow).IsEmpty();
1005 return false;
1006 }
1007
AddBlockPlaceholderIfNeeded(Element * container,EditingState * editing_state)1008 HTMLBRElement* CompositeEditCommand::AddBlockPlaceholderIfNeeded(
1009 Element* container,
1010 EditingState* editing_state) {
1011 if (!container)
1012 return nullptr;
1013
1014 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1015
1016 auto* block = DynamicTo<LayoutBlockFlow>(container->GetLayoutObject());
1017 if (!block)
1018 return nullptr;
1019
1020 // append the placeholder to make sure it follows
1021 // any unrendered blocks
1022 if (block->Size().Height() == 0 || IsEmptyListItem(*block))
1023 return AppendBlockPlaceholder(container, editing_state);
1024
1025 return nullptr;
1026 }
1027
1028 // Assumes that the position is at a placeholder and does the removal without
1029 // much checking.
RemovePlaceholderAt(const Position & p)1030 void CompositeEditCommand::RemovePlaceholderAt(const Position& p) {
1031 DCHECK(LineBreakExistsAtPosition(p)) << p;
1032
1033 // We are certain that the position is at a line break, but it may be a br or
1034 // a preserved newline.
1035 if (IsA<HTMLBRElement>(*p.AnchorNode())) {
1036 // Removing a BR element won't dispatch synchronous events.
1037 RemoveNode(p.AnchorNode(), ASSERT_NO_EDITING_ABORT);
1038 return;
1039 }
1040
1041 DeleteTextFromNode(To<Text>(p.AnchorNode()), p.OffsetInContainerNode(), 1);
1042 }
1043
InsertNewDefaultParagraphElementAt(const Position & position,EditingState * editing_state)1044 HTMLElement* CompositeEditCommand::InsertNewDefaultParagraphElementAt(
1045 const Position& position,
1046 EditingState* editing_state) {
1047 HTMLElement* paragraph_element = CreateDefaultParagraphElement(GetDocument());
1048 paragraph_element->AppendChild(
1049 MakeGarbageCollected<HTMLBRElement>(GetDocument()));
1050 InsertNodeAt(paragraph_element, position, editing_state);
1051 if (editing_state->IsAborted())
1052 return nullptr;
1053 return paragraph_element;
1054 }
1055
1056 // If the paragraph is not entirely within it's own block, create one and move
1057 // the paragraph into it, and return that block. Otherwise return 0.
MoveParagraphContentsToNewBlockIfNecessary(const Position & pos,EditingState * editing_state)1058 HTMLElement* CompositeEditCommand::MoveParagraphContentsToNewBlockIfNecessary(
1059 const Position& pos,
1060 EditingState* editing_state) {
1061 DCHECK(!GetDocument().NeedsLayoutTreeUpdate());
1062 DCHECK(IsEditablePosition(pos)) << pos;
1063
1064 // It's strange that this function is responsible for verifying that pos has
1065 // not been invalidated by an earlier call to this function. The caller,
1066 // applyBlockStyle, should do this.
1067 VisiblePosition visible_pos = CreateVisiblePosition(pos);
1068 VisiblePosition visible_paragraph_start = StartOfParagraph(visible_pos);
1069 VisiblePosition visible_paragraph_end = EndOfParagraph(visible_pos);
1070 VisiblePosition next = NextPositionOf(visible_paragraph_end);
1071 VisiblePosition visible_end = next.IsNotNull() ? next : visible_paragraph_end;
1072
1073 Position upstream_start =
1074 MostBackwardCaretPosition(visible_paragraph_start.DeepEquivalent());
1075 Position upstream_end =
1076 MostBackwardCaretPosition(visible_end.DeepEquivalent());
1077
1078 // If there are no VisiblePositions in the same block as pos then
1079 // upstreamStart will be outside the paragraph
1080 if (ComparePositions(pos, upstream_start) < 0)
1081 return nullptr;
1082
1083 // Perform some checks to see if we need to perform work in this function.
1084 if (IsEnclosingBlock(upstream_start.AnchorNode())) {
1085 // If the block is the root editable element, always move content to a new
1086 // block, since it is illegal to modify attributes on the root editable
1087 // element for editing.
1088 if (upstream_start.AnchorNode() == RootEditableElementOf(upstream_start)) {
1089 // If the block is the root editable element and it contains no visible
1090 // content, create a new block but don't try and move content into it,
1091 // since there's nothing for moveParagraphs to move.
1092 if (!HasRenderedNonAnonymousDescendantsWithHeight(
1093 upstream_start.AnchorNode()->GetLayoutObject()))
1094 return InsertNewDefaultParagraphElementAt(upstream_start,
1095 editing_state);
1096 } else if (IsEnclosingBlock(upstream_end.AnchorNode())) {
1097 if (!upstream_end.AnchorNode()->IsDescendantOf(
1098 upstream_start.AnchorNode())) {
1099 // If the paragraph end is a descendant of paragraph start, then we need
1100 // to run the rest of this function. If not, we can bail here.
1101 return nullptr;
1102 }
1103 } else if (EnclosingBlock(upstream_end.AnchorNode()) !=
1104 upstream_start.AnchorNode()) {
1105 // It should be an ancestor of the paragraph start.
1106 // We can bail as we have a full block to work with.
1107 return nullptr;
1108 } else if (IsEndOfEditableOrNonEditableContent(visible_end)) {
1109 // At the end of the editable region. We can bail here as well.
1110 return nullptr;
1111 }
1112 }
1113
1114 if (visible_paragraph_end.IsNull())
1115 return nullptr;
1116
1117 HTMLElement* const new_block =
1118 InsertNewDefaultParagraphElementAt(upstream_start, editing_state);
1119 if (editing_state->IsAborted())
1120 return nullptr;
1121 DCHECK(new_block);
1122
1123 bool end_was_br =
1124 IsA<HTMLBRElement>(*visible_paragraph_end.DeepEquivalent().AnchorNode());
1125
1126 // Inserting default paragraph element can change visible position. We
1127 // should update visible positions before use them.
1128 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1129 const VisiblePosition& destination =
1130 VisiblePosition::FirstPositionInNode(*new_block);
1131 if (destination.IsNull()) {
1132 // Reached by CompositeEditingCommandTest
1133 // .MoveParagraphContentsToNewBlockWithNonEditableStyle.
1134 editing_state->Abort();
1135 return nullptr;
1136 }
1137
1138 visible_pos = CreateVisiblePosition(pos);
1139 visible_paragraph_start = StartOfParagraph(visible_pos);
1140 visible_paragraph_end = EndOfParagraph(visible_pos);
1141 MoveParagraphs(visible_paragraph_start, visible_paragraph_end, destination,
1142 editing_state);
1143 if (editing_state->IsAborted())
1144 return nullptr;
1145
1146 if (new_block->lastChild() && IsA<HTMLBRElement>(*new_block->lastChild()) &&
1147 !end_was_br) {
1148 RemoveNode(new_block->lastChild(), editing_state);
1149 if (editing_state->IsAborted())
1150 return nullptr;
1151 }
1152
1153 return new_block;
1154 }
1155
PushAnchorElementDown(Element * anchor_node,EditingState * editing_state)1156 void CompositeEditCommand::PushAnchorElementDown(Element* anchor_node,
1157 EditingState* editing_state) {
1158 if (!anchor_node)
1159 return;
1160
1161 DCHECK(anchor_node->IsLink()) << anchor_node;
1162
1163 const VisibleSelection& visible_selection = CreateVisibleSelection(
1164 SelectionInDOMTree::Builder().SelectAllChildren(*anchor_node).Build());
1165 SetEndingSelection(
1166 SelectionForUndoStep::From(visible_selection.AsSelection()));
1167 ApplyStyledElement(anchor_node, editing_state);
1168 if (editing_state->IsAborted())
1169 return;
1170 // Clones of anchorNode have been pushed down, now remove it.
1171 if (anchor_node->isConnected())
1172 RemoveNodePreservingChildren(anchor_node, editing_state);
1173 }
1174
1175 // Clone the paragraph between start and end under blockElement,
1176 // preserving the hierarchy up to outerNode.
1177
CloneParagraphUnderNewElement(const Position & start,const Position & end,Node * passed_outer_node,Element * block_element,EditingState * editing_state)1178 void CompositeEditCommand::CloneParagraphUnderNewElement(
1179 const Position& start,
1180 const Position& end,
1181 Node* passed_outer_node,
1182 Element* block_element,
1183 EditingState* editing_state) {
1184 DCHECK_LE(start, end);
1185 DCHECK(passed_outer_node);
1186 DCHECK(block_element);
1187
1188 // First we clone the outerNode
1189 Node* last_node = nullptr;
1190 Node* outer_node = passed_outer_node;
1191
1192 if (IsRootEditableElement(*outer_node)) {
1193 last_node = block_element;
1194 } else {
1195 last_node = outer_node->cloneNode(IsDisplayInsideTable(outer_node));
1196 AppendNode(last_node, block_element, editing_state);
1197 if (editing_state->IsAborted())
1198 return;
1199 }
1200
1201 if (start.AnchorNode() != outer_node && last_node->IsElementNode() &&
1202 start.AnchorNode()->IsDescendantOf(outer_node)) {
1203 HeapVector<Member<Node>> ancestors;
1204
1205 // Insert each node from innerNode to outerNode (excluded) in a list.
1206 for (Node& runner :
1207 NodeTraversal::InclusiveAncestorsOf(*start.AnchorNode())) {
1208 if (runner == outer_node)
1209 break;
1210 ancestors.push_back(runner);
1211 }
1212
1213 // Clone every node between start.anchorNode() and outerBlock.
1214
1215 for (wtf_size_t i = ancestors.size(); i != 0; --i) {
1216 Node* item = ancestors[i - 1].Get();
1217 Node* child = item->cloneNode(IsDisplayInsideTable(item));
1218 AppendNode(child, To<Element>(last_node), editing_state);
1219 if (editing_state->IsAborted())
1220 return;
1221 last_node = child;
1222 }
1223 }
1224
1225 // Scripts specified in javascript protocol may remove |outerNode|
1226 // during insertion, e.g. <iframe src="javascript:...">
1227 if (!outer_node->isConnected())
1228 return;
1229
1230 // Handle the case of paragraphs with more than one node,
1231 // cloning all the siblings until end.anchorNode() is reached.
1232
1233 if (start.AnchorNode() != end.AnchorNode() &&
1234 !start.AnchorNode()->IsDescendantOf(end.AnchorNode())) {
1235 // If end is not a descendant of outerNode we need to
1236 // find the first common ancestor to increase the scope
1237 // of our nextSibling traversal.
1238 while (outer_node && !end.AnchorNode()->IsDescendantOf(outer_node)) {
1239 outer_node = outer_node->parentNode();
1240 }
1241
1242 if (!outer_node)
1243 return;
1244
1245 Node* start_node = start.AnchorNode();
1246 for (Node* node =
1247 NodeTraversal::NextSkippingChildren(*start_node, outer_node);
1248 node; node = NodeTraversal::NextSkippingChildren(*node, outer_node)) {
1249 // Move lastNode up in the tree as much as node was moved up in the tree
1250 // by NodeTraversal::nextSkippingChildren, so that the relative depth
1251 // between node and the original start node is maintained in the clone.
1252 while (start_node && last_node &&
1253 start_node->parentNode() != node->parentNode()) {
1254 start_node = start_node->parentNode();
1255 last_node = last_node->parentNode();
1256 }
1257
1258 if (!last_node || !last_node->parentNode())
1259 return;
1260
1261 Node* cloned_node = node->cloneNode(true);
1262 InsertNodeAfter(cloned_node, last_node, editing_state);
1263 if (editing_state->IsAborted())
1264 return;
1265 last_node = cloned_node;
1266 if (node == end.AnchorNode() || end.AnchorNode()->IsDescendantOf(node))
1267 break;
1268 }
1269 }
1270 }
1271
1272 // There are bugs in deletion when it removes a fully selected table/list.
1273 // It expands and removes the entire table/list, but will let content
1274 // before and after the table/list collapse onto one line.
1275 // Deleting a paragraph will leave a placeholder. Remove it (and prune
1276 // empty or unrendered parents).
1277
CleanupAfterDeletion(EditingState * editing_state)1278 void CompositeEditCommand::CleanupAfterDeletion(EditingState* editing_state) {
1279 CleanupAfterDeletion(editing_state, VisiblePosition());
1280 }
1281
CleanupAfterDeletion(EditingState * editing_state,VisiblePosition destination)1282 void CompositeEditCommand::CleanupAfterDeletion(EditingState* editing_state,
1283 VisiblePosition destination) {
1284 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1285
1286 VisiblePosition caret_after_delete = EndingVisibleSelection().VisibleStart();
1287 Node* destination_node = destination.DeepEquivalent().AnchorNode();
1288 if (caret_after_delete.DeepEquivalent() != destination.DeepEquivalent() &&
1289 IsStartOfParagraph(caret_after_delete) &&
1290 IsEndOfParagraph(caret_after_delete)) {
1291 // Note: We want the rightmost candidate.
1292 Position position =
1293 MostForwardCaretPosition(caret_after_delete.DeepEquivalent());
1294 Node* node = position.AnchorNode();
1295
1296 // InsertListCommandTest.CleanupNodeSameAsDestinationNode reaches here.
1297 ABORT_EDITING_COMMAND_IF(destination_node == node);
1298 // Bail if we'd remove an ancestor of our destination.
1299 if (destination_node && destination_node->IsDescendantOf(node))
1300 return;
1301
1302 // Normally deletion will leave a br as a placeholder.
1303 if (IsA<HTMLBRElement>(*node)) {
1304 RemoveNodeAndPruneAncestors(node, editing_state, destination_node);
1305
1306 // If the selection to move was empty and in an empty block that
1307 // doesn't require a placeholder to prop itself open (like a bordered
1308 // div or an li), remove it during the move (the list removal code
1309 // expects this behavior).
1310 } else if (IsEnclosingBlock(node)) {
1311 // If caret position after deletion and destination position coincides,
1312 // node should not be removed.
1313 if (!RendersInDifferentPosition(position, destination.DeepEquivalent())) {
1314 Prune(node, editing_state, destination_node);
1315 return;
1316 }
1317 RemoveNodeAndPruneAncestors(node, editing_state, destination_node);
1318 } else if (LineBreakExistsAtPosition(position)) {
1319 // There is a preserved '\n' at caretAfterDelete.
1320 // We can safely assume this is a text node.
1321 auto* text_node = To<Text>(node);
1322 if (text_node->length() == 1)
1323 RemoveNodeAndPruneAncestors(node, editing_state, destination_node);
1324 else
1325 DeleteTextFromNode(text_node, position.ComputeOffsetInContainerNode(),
1326 1);
1327 }
1328 }
1329 }
1330
1331 // This is a version of moveParagraph that preserves style by keeping the
1332 // original markup. It is currently used only by IndentOutdentCommand but it is
1333 // meant to be used in the future by several other commands such as InsertList
1334 // and the align commands.
1335 // The blockElement parameter is the element to move the paragraph to, outerNode
1336 // is the top element of the paragraph hierarchy.
1337
MoveParagraphWithClones(const VisiblePosition & start_of_paragraph_to_move,const VisiblePosition & end_of_paragraph_to_move,HTMLElement * block_element,Node * outer_node,EditingState * editing_state)1338 void CompositeEditCommand::MoveParagraphWithClones(
1339 const VisiblePosition& start_of_paragraph_to_move,
1340 const VisiblePosition& end_of_paragraph_to_move,
1341 HTMLElement* block_element,
1342 Node* outer_node,
1343 EditingState* editing_state) {
1344 // InsertListCommandTest.InsertListWithCollapsedVisibility reaches here.
1345 ABORT_EDITING_COMMAND_IF(start_of_paragraph_to_move.IsNull());
1346 ABORT_EDITING_COMMAND_IF(end_of_paragraph_to_move.IsNull());
1347 DCHECK(outer_node);
1348 DCHECK(block_element);
1349
1350 RelocatablePosition relocatable_before_paragraph(
1351 PreviousPositionOf(start_of_paragraph_to_move).DeepEquivalent());
1352 RelocatablePosition relocatable_after_paragraph(
1353 NextPositionOf(end_of_paragraph_to_move).DeepEquivalent());
1354
1355 // We upstream() the end and downstream() the start so that we don't include
1356 // collapsed whitespace in the move. When we paste a fragment, spaces after
1357 // the end and before the start are treated as though they were rendered.
1358 Position start =
1359 MostForwardCaretPosition(start_of_paragraph_to_move.DeepEquivalent());
1360 Position end = start_of_paragraph_to_move.DeepEquivalent() ==
1361 end_of_paragraph_to_move.DeepEquivalent()
1362 ? start
1363 : MostBackwardCaretPosition(
1364 end_of_paragraph_to_move.DeepEquivalent());
1365 if (ComparePositions(start, end) > 0)
1366 end = start;
1367
1368 CloneParagraphUnderNewElement(start, end, outer_node, block_element,
1369 editing_state);
1370
1371 SetEndingSelection(SelectionForUndoStep::From(
1372 SelectionInDOMTree::Builder().Collapse(start).Extend(end).Build()));
1373 if (!DeleteSelection(
1374 editing_state,
1375 DeleteSelectionOptions::Builder().SetSanitizeMarkup(true).Build()))
1376 return;
1377
1378 // There are bugs in deletion when it removes a fully selected table/list.
1379 // It expands and removes the entire table/list, but will let content
1380 // before and after the table/list collapse onto one line.
1381
1382 CleanupAfterDeletion(editing_state);
1383 if (editing_state->IsAborted())
1384 return;
1385
1386 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1387
1388 // Add a br if pruning an empty block level element caused a collapse. For
1389 // example:
1390 // foo^
1391 // <div>bar</div>
1392 // baz
1393 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That
1394 // would cause 'baz' to collapse onto the line with 'foobar' unless we insert
1395 // a br. Must recononicalize these two VisiblePositions after the pruning
1396 // above.
1397 const VisiblePosition& before_paragraph =
1398 CreateVisiblePosition(relocatable_before_paragraph.GetPosition());
1399 const VisiblePosition& after_paragraph =
1400 CreateVisiblePosition(relocatable_after_paragraph.GetPosition());
1401
1402 if (before_paragraph.IsNotNull() &&
1403 !IsDisplayInsideTable(before_paragraph.DeepEquivalent().AnchorNode()) &&
1404 ((!IsEndOfParagraph(before_paragraph) &&
1405 !IsStartOfParagraph(before_paragraph)) ||
1406 before_paragraph.DeepEquivalent() == after_paragraph.DeepEquivalent())) {
1407 // FIXME: Trim text between beforeParagraph and afterParagraph if they
1408 // aren't equal.
1409 InsertNodeAt(MakeGarbageCollected<HTMLBRElement>(GetDocument()),
1410 before_paragraph.DeepEquivalent(), editing_state);
1411 }
1412 }
1413
MoveParagraph(const VisiblePosition & start_of_paragraph_to_move,const VisiblePosition & end_of_paragraph_to_move,const VisiblePosition & destination,EditingState * editing_state,ShouldPreserveSelection should_preserve_selection,ShouldPreserveStyle should_preserve_style,Node * constraining_ancestor)1414 void CompositeEditCommand::MoveParagraph(
1415 const VisiblePosition& start_of_paragraph_to_move,
1416 const VisiblePosition& end_of_paragraph_to_move,
1417 const VisiblePosition& destination,
1418 EditingState* editing_state,
1419 ShouldPreserveSelection should_preserve_selection,
1420 ShouldPreserveStyle should_preserve_style,
1421 Node* constraining_ancestor) {
1422 DCHECK(!GetDocument().NeedsLayoutTreeUpdate());
1423 DCHECK(IsStartOfParagraph(start_of_paragraph_to_move))
1424 << start_of_paragraph_to_move;
1425 DCHECK(IsEndOfParagraph(end_of_paragraph_to_move))
1426 << end_of_paragraph_to_move;
1427 MoveParagraphs(start_of_paragraph_to_move, end_of_paragraph_to_move,
1428 destination, editing_state, should_preserve_selection,
1429 should_preserve_style, constraining_ancestor);
1430 }
1431
MoveParagraphs(const VisiblePosition & start_of_paragraph_to_move,const VisiblePosition & end_of_paragraph_to_move,const VisiblePosition & destination,EditingState * editing_state,ShouldPreserveSelection should_preserve_selection,ShouldPreserveStyle should_preserve_style,Node * constraining_ancestor)1432 void CompositeEditCommand::MoveParagraphs(
1433 const VisiblePosition& start_of_paragraph_to_move,
1434 const VisiblePosition& end_of_paragraph_to_move,
1435 const VisiblePosition& destination,
1436 EditingState* editing_state,
1437 ShouldPreserveSelection should_preserve_selection,
1438 ShouldPreserveStyle should_preserve_style,
1439 Node* constraining_ancestor) {
1440 DCHECK(!GetDocument().NeedsLayoutTreeUpdate());
1441 DCHECK(start_of_paragraph_to_move.IsNotNull());
1442 DCHECK(end_of_paragraph_to_move.IsNotNull());
1443 DCHECK(destination.IsNotNull());
1444
1445 if (start_of_paragraph_to_move.DeepEquivalent() ==
1446 destination.DeepEquivalent() ||
1447 start_of_paragraph_to_move.IsNull())
1448 return;
1449
1450 // Can't move the range to a destination inside itself.
1451 if (destination.DeepEquivalent() >=
1452 start_of_paragraph_to_move.DeepEquivalent() &&
1453 destination.DeepEquivalent() <=
1454 end_of_paragraph_to_move.DeepEquivalent()) {
1455 // Reached by unit test TypingCommandTest.insertLineBreakWithIllFormedHTML
1456 // and ApplyStyleCommandTest.JustifyRightDetachesDestination
1457 editing_state->Abort();
1458 return;
1459 }
1460
1461 int start_index = -1;
1462 int end_index = -1;
1463 int destination_index = -1;
1464 if (should_preserve_selection == kPreserveSelection &&
1465 !EndingSelection().IsNone()) {
1466 VisiblePosition visible_start = EndingVisibleSelection().VisibleStart();
1467 VisiblePosition visible_end = EndingVisibleSelection().VisibleEnd();
1468
1469 bool start_after_paragraph =
1470 ComparePositions(visible_start, end_of_paragraph_to_move) > 0;
1471 bool end_before_paragraph =
1472 ComparePositions(visible_end, start_of_paragraph_to_move) < 0;
1473
1474 if (!start_after_paragraph && !end_before_paragraph) {
1475 bool start_in_paragraph =
1476 ComparePositions(visible_start, start_of_paragraph_to_move) >= 0;
1477 bool end_in_paragraph =
1478 ComparePositions(visible_end, end_of_paragraph_to_move) <= 0;
1479
1480 const TextIteratorBehavior behavior =
1481 TextIteratorBehavior::AllVisiblePositionsRangeLengthBehavior();
1482
1483 start_index = 0;
1484 if (start_in_paragraph) {
1485 start_index = TextIterator::RangeLength(
1486 start_of_paragraph_to_move.ToParentAnchoredPosition(),
1487 visible_start.ToParentAnchoredPosition(), behavior);
1488 }
1489
1490 end_index = 0;
1491 if (end_in_paragraph) {
1492 end_index = TextIterator::RangeLength(
1493 start_of_paragraph_to_move.ToParentAnchoredPosition(),
1494 visible_end.ToParentAnchoredPosition(), behavior);
1495 }
1496 }
1497 }
1498
1499 RelocatablePosition before_paragraph_position(
1500 PreviousPositionOf(start_of_paragraph_to_move,
1501 kCannotCrossEditingBoundary)
1502 .DeepEquivalent());
1503 RelocatablePosition after_paragraph_position(
1504 NextPositionOf(end_of_paragraph_to_move, kCannotCrossEditingBoundary)
1505 .DeepEquivalent());
1506
1507 // We upstream() the end and downstream() the start so that we don't include
1508 // collapsed whitespace in the move. When we paste a fragment, spaces after
1509 // the end and before the start are treated as though they were rendered.
1510 Position start =
1511 MostForwardCaretPosition(start_of_paragraph_to_move.DeepEquivalent());
1512 Position end =
1513 MostBackwardCaretPosition(end_of_paragraph_to_move.DeepEquivalent());
1514
1515 // FIXME: This is an inefficient way to preserve style on nodes in the
1516 // paragraph to move. It shouldn't matter though, since moved paragraphs will
1517 // usually be quite small.
1518 DocumentFragment* fragment = nullptr;
1519 if (start_of_paragraph_to_move.DeepEquivalent() !=
1520 end_of_paragraph_to_move.DeepEquivalent()) {
1521 const String paragraphs_markup = CreateMarkup(
1522 start.ParentAnchoredEquivalent(), end.ParentAnchoredEquivalent(),
1523 CreateMarkupOptions::Builder()
1524 .SetShouldConvertBlocksToInlines(true)
1525 .SetConstrainingAncestor(constraining_ancestor)
1526 .Build());
1527 fragment = CreateSanitizedFragmentFromMarkupWithContext(
1528 GetDocument(), paragraphs_markup, 0, paragraphs_markup.length(), "");
1529 }
1530
1531 // A non-empty paragraph's style is moved when we copy and move it. We don't
1532 // move anything if we're given an empty paragraph, but an empty paragraph can
1533 // have style too, <div><b><br></b></div> for example. Save it so that we can
1534 // preserve it later.
1535 EditingStyle* style_in_empty_paragraph = nullptr;
1536 if (start_of_paragraph_to_move.DeepEquivalent() ==
1537 end_of_paragraph_to_move.DeepEquivalent() &&
1538 should_preserve_style == kPreserveStyle) {
1539 style_in_empty_paragraph = MakeGarbageCollected<EditingStyle>(
1540 start_of_paragraph_to_move.DeepEquivalent());
1541 style_in_empty_paragraph->MergeTypingStyle(&GetDocument());
1542 // The moved paragraph should assume the block style of the destination.
1543 style_in_empty_paragraph->RemoveBlockProperties(
1544 GetDocument().GetExecutionContext());
1545 }
1546
1547 // FIXME (5098931): We should add a new insert action
1548 // "WebViewInsertActionMoved" and call shouldInsertFragment here.
1549
1550 DCHECK(!GetDocument().NeedsLayoutTreeUpdate());
1551
1552 const VisibleSelection& selection_to_delete = CreateVisibleSelection(
1553 SelectionInDOMTree::Builder().Collapse(start).Extend(end).Build());
1554 SetEndingSelection(
1555 SelectionForUndoStep::From(selection_to_delete.AsSelection()));
1556 if (!DeleteSelection(
1557 editing_state,
1558 DeleteSelectionOptions::Builder().SetSanitizeMarkup(true).Build()))
1559 return;
1560
1561 DCHECK(destination.DeepEquivalent().IsConnected()) << destination;
1562 CleanupAfterDeletion(editing_state, destination);
1563 if (editing_state->IsAborted())
1564 return;
1565 DCHECK(destination.DeepEquivalent().IsConnected()) << destination;
1566
1567 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1568
1569 // Add a br if pruning an empty block level element caused a collapse. For
1570 // example:
1571 // foo^
1572 // <div>bar</div>
1573 // baz
1574 // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That
1575 // would cause 'baz' to collapse onto the line with 'foobar' unless we insert
1576 // a br. Must recononicalize these two VisiblePositions after the pruning
1577 // above.
1578 VisiblePosition before_paragraph =
1579 CreateVisiblePosition(before_paragraph_position.GetPosition());
1580 VisiblePosition after_paragraph =
1581 CreateVisiblePosition(after_paragraph_position.GetPosition());
1582 if (before_paragraph.IsNotNull() &&
1583 ((!IsStartOfParagraph(before_paragraph) &&
1584 !IsEndOfParagraph(before_paragraph)) ||
1585 before_paragraph.DeepEquivalent() == after_paragraph.DeepEquivalent())) {
1586 // FIXME: Trim text between beforeParagraph and afterParagraph if they
1587 // aren't equal.
1588 InsertNodeAt(MakeGarbageCollected<HTMLBRElement>(GetDocument()),
1589 before_paragraph.DeepEquivalent(), editing_state);
1590 if (editing_state->IsAborted())
1591 return;
1592 }
1593
1594 // TextIterator::rangeLength requires clean layout.
1595 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1596
1597 destination_index = TextIterator::RangeLength(
1598 Position::FirstPositionInNode(*GetDocument().documentElement()),
1599 destination.ToParentAnchoredPosition(),
1600 TextIteratorBehavior::AllVisiblePositionsRangeLengthBehavior());
1601
1602 const VisibleSelection& destination_selection =
1603 CreateVisibleSelection(SelectionInDOMTree::Builder()
1604 .Collapse(destination.ToPositionWithAffinity())
1605 .Build());
1606 if (EndingSelection().IsNone()) {
1607 // We abort executing command since |destination| becomes invisible.
1608 editing_state->Abort();
1609 return;
1610 }
1611 SetEndingSelection(
1612 SelectionForUndoStep::From(destination_selection.AsSelection()));
1613 ReplaceSelectionCommand::CommandOptions options =
1614 ReplaceSelectionCommand::kSelectReplacement |
1615 ReplaceSelectionCommand::kMovingParagraph;
1616 if (should_preserve_style == kDoNotPreserveStyle)
1617 options |= ReplaceSelectionCommand::kMatchStyle;
1618 ApplyCommandToComposite(MakeGarbageCollected<ReplaceSelectionCommand>(
1619 GetDocument(), fragment, options),
1620 editing_state);
1621 if (editing_state->IsAborted())
1622 return;
1623 ABORT_EDITING_COMMAND_IF(!EndingSelection().IsValidFor(GetDocument()));
1624
1625 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1626
1627 // If the selection is in an empty paragraph, restore styles from the old
1628 // empty paragraph to the new empty paragraph.
1629 bool selection_is_empty_paragraph =
1630 EndingSelection().IsCaret() &&
1631 IsStartOfParagraph(EndingVisibleSelection().VisibleStart()) &&
1632 IsEndOfParagraph(EndingVisibleSelection().VisibleStart());
1633 if (style_in_empty_paragraph && selection_is_empty_paragraph) {
1634 ApplyStyle(style_in_empty_paragraph, editing_state);
1635 if (editing_state->IsAborted())
1636 return;
1637 }
1638
1639 if (should_preserve_selection == kDoNotPreserveSelection || start_index == -1)
1640 return;
1641 Element* document_element = GetDocument().documentElement();
1642 if (!document_element)
1643 return;
1644
1645 // We need clean layout in order to compute plain-text ranges below.
1646 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1647
1648 // Fragment creation (using createMarkup) incorrectly uses regular spaces
1649 // instead of nbsps for some spaces that were rendered (11475), which causes
1650 // spaces to be collapsed during the move operation. This results in a call
1651 // to rangeFromLocationAndLength with a location past the end of the
1652 // document (which will return null).
1653 EphemeralRange start_range = PlainTextRange(destination_index + start_index)
1654 .CreateRangeForSelection(*document_element);
1655 if (start_range.IsNull())
1656 return;
1657 EphemeralRange end_range = PlainTextRange(destination_index + end_index)
1658 .CreateRangeForSelection(*document_element);
1659 if (end_range.IsNull())
1660 return;
1661 const VisibleSelection& visible_selection =
1662 CreateVisibleSelection(SelectionInDOMTree::Builder()
1663 .Collapse(start_range.StartPosition())
1664 .Extend(end_range.StartPosition())
1665 .Build());
1666 SetEndingSelection(
1667 SelectionForUndoStep::From(visible_selection.AsSelection()));
1668 }
1669
1670 // FIXME: Send an appropriate shouldDeleteRange call.
BreakOutOfEmptyListItem(EditingState * editing_state)1671 bool CompositeEditCommand::BreakOutOfEmptyListItem(
1672 EditingState* editing_state) {
1673 DCHECK(!GetDocument().NeedsLayoutTreeUpdate());
1674 Node* empty_list_item =
1675 EnclosingEmptyListItem(EndingVisibleSelection().VisibleStart());
1676 if (!empty_list_item)
1677 return false;
1678
1679 EditingStyle* style =
1680 MakeGarbageCollected<EditingStyle>(EndingSelection().Start());
1681 style->MergeTypingStyle(&GetDocument());
1682
1683 ContainerNode* list_node = empty_list_item->parentNode();
1684 // FIXME: Can't we do something better when the immediate parent wasn't a list
1685 // node?
1686 if (!list_node ||
1687 (!IsA<HTMLUListElement>(*list_node) &&
1688 !IsA<HTMLOListElement>(*list_node)) ||
1689 !HasEditableStyle(*list_node) ||
1690 list_node == RootEditableElement(*empty_list_item))
1691 return false;
1692
1693 HTMLElement* new_block = nullptr;
1694 if (ContainerNode* block_enclosing_list = list_node->parentNode()) {
1695 if (block_enclosing_list->HasTagName(
1696 html_names::kLiTag)) { // listNode is inside another list item
1697 if (CreateVisiblePosition(PositionAfterNode(*block_enclosing_list))
1698 .DeepEquivalent() ==
1699 CreateVisiblePosition(PositionAfterNode(*list_node))
1700 .DeepEquivalent()) {
1701 // If listNode appears at the end of the outer list item, then move
1702 // listNode outside of this list item, e.g.
1703 // <ul><li>hello <ul><li><br></li></ul> </li></ul>
1704 // should become
1705 // <ul><li>hello</li> <ul><li><br></li></ul> </ul>
1706 // after this section.
1707 //
1708 // If listNode does NOT appear at the end, then we should consider it as
1709 // a regular paragraph, e.g.
1710 // <ul><li> <ul><li><br></li></ul> hello</li></ul>
1711 // should become
1712 // <ul><li> <div><br></div> hello</li></ul>
1713 // at the end
1714 SplitElement(To<Element>(block_enclosing_list), list_node);
1715 RemoveNodePreservingChildren(list_node->parentNode(), editing_state);
1716 if (editing_state->IsAborted())
1717 return false;
1718 new_block = MakeGarbageCollected<HTMLLIElement>(GetDocument());
1719 }
1720 // If listNode does NOT appear at the end of the outer list item, then
1721 // behave as if in a regular paragraph.
1722 } else if (block_enclosing_list->HasTagName(html_names::kOlTag) ||
1723 block_enclosing_list->HasTagName(html_names::kUlTag)) {
1724 new_block = MakeGarbageCollected<HTMLLIElement>(GetDocument());
1725 }
1726 }
1727 if (!new_block)
1728 new_block = CreateDefaultParagraphElement(GetDocument());
1729
1730 Node* previous_list_node =
1731 empty_list_item->IsElementNode()
1732 ? ElementTraversal::PreviousSibling(*empty_list_item)
1733 : empty_list_item->previousSibling();
1734 Node* next_list_node = empty_list_item->IsElementNode()
1735 ? ElementTraversal::NextSibling(*empty_list_item)
1736 : empty_list_item->nextSibling();
1737 if (next_list_node && IsListElementTag(list_node)) {
1738 // If emptyListItem follows another list item or nested list, split the list
1739 // node.
1740 if (IsListItemTag(previous_list_node) ||
1741 IsHTMLListElement(previous_list_node)) {
1742 SplitElement(To<Element>(list_node), empty_list_item);
1743 }
1744
1745 // If emptyListItem is followed by other list item or nested list, then
1746 // insert newBlock before the list node. Because we have split the
1747 // element, emptyListItem is the first element in the list node.
1748 // i.e. insert newBlock before ul or ol whose first element is emptyListItem
1749 InsertNodeBefore(new_block, list_node, editing_state);
1750 if (editing_state->IsAborted())
1751 return false;
1752 RemoveNode(empty_list_item, editing_state);
1753 if (editing_state->IsAborted())
1754 return false;
1755 } else {
1756 // When emptyListItem does not follow any list item or nested list, insert
1757 // newBlock after the enclosing list node. Remove the enclosing node if
1758 // emptyListItem is the only child; otherwise just remove emptyListItem.
1759 // <ul> <ul>
1760 // <li> <li>
1761 // abc abc
1762 // <ul> <ul>
1763 // <li>def</li> <li>def</li>
1764 // <li>{}<br></li> -> </ul>
1765 // </ul> <div>{}<br></div>
1766 // ghi ghi
1767 // </li> </li>
1768 // </ul> </ul>
1769 InsertNodeAfter(new_block, list_node, editing_state);
1770 if (editing_state->IsAborted())
1771 return false;
1772 RemoveNode(previous_list_node ? empty_list_item : list_node, editing_state);
1773 if (editing_state->IsAborted())
1774 return false;
1775 }
1776
1777 AppendBlockPlaceholder(new_block, editing_state);
1778 if (editing_state->IsAborted())
1779 return false;
1780
1781 SetEndingSelection(SelectionForUndoStep::From(
1782 SelectionInDOMTree::Builder()
1783 .Collapse(Position::FirstPositionInNode(*new_block))
1784 .Build()));
1785
1786 style->PrepareToApplyAt(EndingSelection().Start());
1787 if (!style->IsEmpty()) {
1788 ApplyStyle(style, editing_state);
1789 if (editing_state->IsAborted())
1790 return false;
1791 }
1792
1793 return true;
1794 }
1795
1796 // If the caret is in an empty quoted paragraph, and either there is nothing
1797 // before that paragraph, or what is before is unquoted, and the user presses
1798 // delete, unquote that paragraph.
BreakOutOfEmptyMailBlockquotedParagraph(EditingState * editing_state)1799 bool CompositeEditCommand::BreakOutOfEmptyMailBlockquotedParagraph(
1800 EditingState* editing_state) {
1801 if (!EndingSelection().IsCaret())
1802 return false;
1803
1804 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1805
1806 VisiblePosition caret = EndingVisibleSelection().VisibleStart();
1807 auto* highest_blockquote = To<HTMLQuoteElement>(HighestEnclosingNodeOfType(
1808 caret.DeepEquivalent(), &IsMailHTMLBlockquoteElement));
1809 if (!highest_blockquote)
1810 return false;
1811
1812 if (!IsStartOfParagraph(caret) || !IsEndOfParagraph(caret))
1813 return false;
1814
1815 VisiblePosition previous =
1816 PreviousPositionOf(caret, kCannotCrossEditingBoundary);
1817 // Only move forward if there's nothing before the caret, or if there's
1818 // unquoted content before it.
1819 if (EnclosingNodeOfType(previous.DeepEquivalent(),
1820 &IsMailHTMLBlockquoteElement))
1821 return false;
1822
1823 auto* br = MakeGarbageCollected<HTMLBRElement>(GetDocument());
1824 // We want to replace this quoted paragraph with an unquoted one, so insert a
1825 // br to hold the caret before the highest blockquote.
1826 InsertNodeBefore(br, highest_blockquote, editing_state);
1827 if (editing_state->IsAborted())
1828 return false;
1829
1830 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1831
1832 VisiblePosition at_br = VisiblePosition::BeforeNode(*br);
1833 // If the br we inserted collapsed, for example:
1834 // foo<br><blockquote>...</blockquote>
1835 // insert a second one.
1836 if (!IsStartOfParagraph(at_br)) {
1837 InsertNodeBefore(MakeGarbageCollected<HTMLBRElement>(GetDocument()), br,
1838 editing_state);
1839 if (editing_state->IsAborted())
1840 return false;
1841 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1842 }
1843 SetEndingSelection(SelectionForUndoStep::From(
1844 SelectionInDOMTree::Builder()
1845 .Collapse(at_br.ToPositionWithAffinity())
1846 .Build()));
1847
1848 // If this is an empty paragraph there must be a line break here.
1849 if (!LineBreakExistsAtVisiblePosition(caret))
1850 return false;
1851
1852 Position caret_pos(MostForwardCaretPosition(caret.DeepEquivalent()));
1853 // A line break is either a br or a preserved newline.
1854 DCHECK(
1855 IsA<HTMLBRElement>(caret_pos.AnchorNode()) ||
1856 (caret_pos.AnchorNode()->IsTextNode() &&
1857 caret_pos.AnchorNode()->GetLayoutObject()->Style()->PreserveNewline()))
1858 << caret_pos;
1859
1860 if (IsA<HTMLBRElement>(*caret_pos.AnchorNode())) {
1861 RemoveNodeAndPruneAncestors(caret_pos.AnchorNode(), editing_state);
1862 if (editing_state->IsAborted())
1863 return false;
1864 } else if (auto* text_node = DynamicTo<Text>(caret_pos.AnchorNode())) {
1865 DCHECK_EQ(caret_pos.ComputeOffsetInContainerNode(), 0);
1866 ContainerNode* parent_node = text_node->parentNode();
1867 // The preserved newline must be the first thing in the node, since
1868 // otherwise the previous paragraph would be quoted, and we verified that it
1869 // wasn't above.
1870 DeleteTextFromNode(text_node, 0, 1);
1871 Prune(parent_node, editing_state);
1872 if (editing_state->IsAborted())
1873 return false;
1874 }
1875
1876 return true;
1877 }
1878
1879 // Operations use this function to avoid inserting content into an anchor when
1880 // at the start or the end of that anchor, as in NSTextView.
1881 // FIXME: This is only an approximation of NSTextViews insertion behavior, which
1882 // varies depending on how the caret was made.
PositionAvoidingSpecialElementBoundary(const Position & original,EditingState * editing_state)1883 Position CompositeEditCommand::PositionAvoidingSpecialElementBoundary(
1884 const Position& original,
1885 EditingState* editing_state) {
1886 if (original.IsNull())
1887 return original;
1888
1889 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1890 VisiblePosition visible_pos = CreateVisiblePosition(original);
1891 Element* enclosing_anchor = EnclosingAnchorElement(original);
1892 Position result = original;
1893
1894 if (!enclosing_anchor)
1895 return result;
1896
1897 // Don't avoid block level anchors, because that would insert content into the
1898 // wrong paragraph.
1899 if (enclosing_anchor && !IsEnclosingBlock(enclosing_anchor)) {
1900 VisiblePosition first_in_anchor =
1901 VisiblePosition::FirstPositionInNode(*enclosing_anchor);
1902 VisiblePosition last_in_anchor =
1903 VisiblePosition::LastPositionInNode(*enclosing_anchor);
1904 // If visually just after the anchor, insert *inside* the anchor unless it's
1905 // the last VisiblePosition in the document, to match NSTextView.
1906 if (visible_pos.DeepEquivalent() == last_in_anchor.DeepEquivalent()) {
1907 // Make sure anchors are pushed down before avoiding them so that we don't
1908 // also avoid structural elements like lists and blocks (5142012).
1909 if (original.AnchorNode() != enclosing_anchor &&
1910 original.AnchorNode()->parentNode() != enclosing_anchor) {
1911 PushAnchorElementDown(enclosing_anchor, editing_state);
1912 if (editing_state->IsAborted())
1913 return original;
1914 enclosing_anchor = EnclosingAnchorElement(original);
1915 if (!enclosing_anchor)
1916 return original;
1917 }
1918
1919 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1920
1921 // Don't insert outside an anchor if doing so would skip over a line
1922 // break. It would probably be safe to move the line break so that we
1923 // could still avoid the anchor here.
1924 Position downstream(
1925 MostForwardCaretPosition(visible_pos.DeepEquivalent()));
1926 if (LineBreakExistsAtVisiblePosition(visible_pos) &&
1927 downstream.AnchorNode()->IsDescendantOf(enclosing_anchor))
1928 return original;
1929
1930 result = Position::InParentAfterNode(*enclosing_anchor);
1931 }
1932
1933 // If visually just before an anchor, insert *outside* the anchor unless
1934 // it's the first VisiblePosition in a paragraph, to match NSTextView.
1935 if (visible_pos.DeepEquivalent() == first_in_anchor.DeepEquivalent()) {
1936 // Make sure anchors are pushed down before avoiding them so that we don't
1937 // also avoid structural elements like lists and blocks (5142012).
1938 if (original.AnchorNode() != enclosing_anchor &&
1939 original.AnchorNode()->parentNode() != enclosing_anchor) {
1940 PushAnchorElementDown(enclosing_anchor, editing_state);
1941 if (editing_state->IsAborted())
1942 return original;
1943 enclosing_anchor = EnclosingAnchorElement(original);
1944 }
1945 if (!enclosing_anchor)
1946 return original;
1947
1948 result = Position::InParentBeforeNode(*enclosing_anchor);
1949 }
1950 }
1951
1952 if (result.IsNull() || !RootEditableElementOf(result))
1953 result = original;
1954
1955 return result;
1956 }
1957
1958 // Splits the tree parent by parent until we reach the specified ancestor. We
1959 // use VisiblePositions to determine if the split is necessary. Returns the last
1960 // split node.
SplitTreeToNode(Node * start,Node * end,bool should_split_ancestor)1961 Node* CompositeEditCommand::SplitTreeToNode(Node* start,
1962 Node* end,
1963 bool should_split_ancestor) {
1964 DCHECK(start);
1965 DCHECK(end);
1966 DCHECK_NE(start, end);
1967
1968 if (should_split_ancestor && end->parentNode())
1969 end = end->parentNode();
1970 if (!start->IsDescendantOf(end))
1971 return end;
1972
1973 Node* end_node = end;
1974 Node* node = nullptr;
1975 for (node = start; node->parentNode() != end_node;
1976 node = node->parentNode()) {
1977 Element* parent_element = node->parentElement();
1978 if (!parent_element)
1979 break;
1980
1981 GetDocument().UpdateStyleAndLayout(DocumentUpdateReason::kEditing);
1982
1983 // Do not split a node when doing so introduces an empty node.
1984 VisiblePosition position_in_parent =
1985 VisiblePosition::FirstPositionInNode(*parent_element);
1986 VisiblePosition position_in_node =
1987 CreateVisiblePosition(FirstPositionInOrBeforeNode(*node));
1988 if (position_in_parent.DeepEquivalent() !=
1989 position_in_node.DeepEquivalent())
1990 SplitElement(parent_element, node);
1991 }
1992
1993 return node;
1994 }
1995
SetStartingSelection(const SelectionForUndoStep & selection)1996 void CompositeEditCommand::SetStartingSelection(
1997 const SelectionForUndoStep& selection) {
1998 for (CompositeEditCommand* command = this;; command = command->Parent()) {
1999 if (UndoStep* undo_step = command->GetUndoStep()) {
2000 DCHECK(command->IsTopLevelCommand());
2001 undo_step->SetStartingSelection(selection);
2002 }
2003 command->starting_selection_ = selection;
2004 if (!command->Parent() || command->Parent()->IsFirstCommand(command))
2005 break;
2006 }
2007 }
2008
SetEndingSelection(const SelectionForUndoStep & selection)2009 void CompositeEditCommand::SetEndingSelection(
2010 const SelectionForUndoStep& selection) {
2011 for (CompositeEditCommand* command = this; command;
2012 command = command->Parent()) {
2013 if (UndoStep* undo_step = command->GetUndoStep()) {
2014 DCHECK(command->IsTopLevelCommand());
2015 undo_step->SetEndingSelection(selection);
2016 }
2017 command->ending_selection_ = selection;
2018 }
2019 }
2020
SetParent(CompositeEditCommand * parent)2021 void CompositeEditCommand::SetParent(CompositeEditCommand* parent) {
2022 EditCommand::SetParent(parent);
2023 if (!parent)
2024 return;
2025 starting_selection_ = parent->ending_selection_;
2026 ending_selection_ = parent->ending_selection_;
2027 }
2028
2029 // Determines whether a node is inside a range or visibly starts and ends at the
2030 // boundaries of the range. Call this function to determine whether a node is
2031 // visibly fit inside selectedRange
IsNodeVisiblyContainedWithin(Node & node,const EphemeralRange & selected_range)2032 bool CompositeEditCommand::IsNodeVisiblyContainedWithin(
2033 Node& node,
2034 const EphemeralRange& selected_range) {
2035 DCHECK(!NeedsLayoutTreeUpdate(node));
2036 DocumentLifecycle::DisallowTransitionScope disallow_transition(
2037 node.GetDocument().Lifecycle());
2038
2039 if (IsNodeFullyContained(selected_range, node))
2040 return true;
2041
2042 bool start_is_visually_same =
2043 CreateVisiblePosition(PositionBeforeNode(node)).DeepEquivalent() ==
2044 CreateVisiblePosition(selected_range.StartPosition()).DeepEquivalent();
2045 if (start_is_visually_same &&
2046 ComparePositions(Position::InParentAfterNode(node),
2047 selected_range.EndPosition()) < 0)
2048 return true;
2049
2050 bool end_is_visually_same =
2051 CreateVisiblePosition(PositionAfterNode(node)).DeepEquivalent() ==
2052 CreateVisiblePosition(selected_range.EndPosition()).DeepEquivalent();
2053 if (end_is_visually_same &&
2054 ComparePositions(selected_range.StartPosition(),
2055 Position::InParentBeforeNode(node)) < 0)
2056 return true;
2057
2058 return start_is_visually_same && end_is_visually_same;
2059 }
2060
Trace(Visitor * visitor) const2061 void CompositeEditCommand::Trace(Visitor* visitor) const {
2062 visitor->Trace(commands_);
2063 visitor->Trace(starting_selection_);
2064 visitor->Trace(ending_selection_);
2065 visitor->Trace(undo_step_);
2066 EditCommand::Trace(visitor);
2067 }
2068
AppliedEditing()2069 void CompositeEditCommand::AppliedEditing() {
2070 DCHECK(!IsCommandGroupWrapper());
2071 EventQueueScope scope;
2072
2073 const UndoStep& undo_step = *GetUndoStep();
2074 DispatchEditableContentChangedEvents(undo_step.StartingRootEditableElement(),
2075 undo_step.EndingRootEditableElement());
2076 LocalFrame* const frame = GetDocument().GetFrame();
2077 Editor& editor = frame->GetEditor();
2078 // TODO(editing-dev): Filter empty InputType after spec is finalized.
2079 DispatchInputEventEditableContentChanged(
2080 undo_step.StartingRootEditableElement(),
2081 undo_step.EndingRootEditableElement(), GetInputType(),
2082 TextDataForInputEvent(), IsComposingFromCommand(this));
2083
2084 const SelectionInDOMTree& new_selection =
2085 CorrectedSelectionAfterCommand(EndingSelection(), &GetDocument());
2086
2087 // Don't clear the typing style with this selection change. We do those things
2088 // elsewhere if necessary.
2089 ChangeSelectionAfterCommand(frame, new_selection,
2090 SetSelectionOptions::Builder()
2091 .SetIsDirectional(SelectionIsDirectional())
2092 .Build());
2093
2094 if (!PreservesTypingStyle())
2095 editor.ClearTypingStyle();
2096
2097 CompositeEditCommand* const last_edit_command = editor.LastEditCommand();
2098 // Command will be equal to last edit command only in the case of typing
2099 if (last_edit_command == this) {
2100 DCHECK(IsTypingCommand());
2101 } else if (last_edit_command && last_edit_command->IsDragAndDropCommand() &&
2102 (GetInputType() == InputEvent::InputType::kDeleteByDrag ||
2103 GetInputType() == InputEvent::InputType::kInsertFromDrop)) {
2104 // Only register undo entry when combined with other commands.
2105 if (!last_edit_command->GetUndoStep()) {
2106 editor.GetUndoStack().RegisterUndoStep(
2107 last_edit_command->EnsureUndoStep());
2108 }
2109 last_edit_command->EnsureUndoStep()->SetEndingSelection(
2110 EnsureUndoStep()->EndingSelection());
2111 last_edit_command->GetUndoStep()->SetSelectionIsDirectional(
2112 GetUndoStep()->SelectionIsDirectional());
2113 last_edit_command->AppendCommandToUndoStep(this);
2114 } else {
2115 // Only register a new undo command if the command passed in is
2116 // different from the last command
2117 editor.SetLastEditCommand(this);
2118 editor.GetUndoStack().RegisterUndoStep(EnsureUndoStep());
2119 }
2120
2121 editor.RespondToChangedContents(new_selection.Base());
2122 }
2123
2124 } // namespace blink
2125