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

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

Issue 1294543005: Move execCommand related files in core/editing/ related files into core/editing/commands/ (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: 2015-08-17T17:57:33 Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright (C) 2005, 2006, 2008 Apple Inc. All rights reserved.
3 * Copyright (C) 2009, 2010, 2011 Google Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27 #include "config.h"
28 #include "core/editing/ReplaceSelectionCommand.h"
29
30 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
31 #include "core/CSSPropertyNames.h"
32 #include "core/HTMLNames.h"
33 #include "core/InputTypeNames.h"
34 #include "core/css/CSSStyleDeclaration.h"
35 #include "core/css/StylePropertySet.h"
36 #include "core/dom/Document.h"
37 #include "core/dom/DocumentFragment.h"
38 #include "core/dom/Element.h"
39 #include "core/dom/Text.h"
40 #include "core/editing/ApplyStyleCommand.h"
41 #include "core/editing/BreakBlockquoteCommand.h"
42 #include "core/editing/EditingUtilities.h"
43 #include "core/editing/FrameSelection.h"
44 #include "core/editing/SimplifyMarkupCommand.h"
45 #include "core/editing/SmartReplace.h"
46 #include "core/editing/VisibleUnits.h"
47 #include "core/editing/iterators/TextIterator.h"
48 #include "core/editing/serializers/HTMLInterchange.h"
49 #include "core/editing/serializers/Serialization.h"
50 #include "core/events/BeforeTextInsertedEvent.h"
51 #include "core/frame/LocalFrame.h"
52 #include "core/frame/UseCounter.h"
53 #include "core/html/HTMLBRElement.h"
54 #include "core/html/HTMLElement.h"
55 #include "core/html/HTMLInputElement.h"
56 #include "core/html/HTMLLIElement.h"
57 #include "core/html/HTMLQuoteElement.h"
58 #include "core/html/HTMLSelectElement.h"
59 #include "core/html/HTMLSpanElement.h"
60 #include "core/layout/LayoutObject.h"
61 #include "core/layout/LayoutText.h"
62 #include "wtf/StdLibExtras.h"
63 #include "wtf/Vector.h"
64
65 namespace blink {
66
67 using namespace HTMLNames;
68
69 enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment };
70
71 // --- ReplacementFragment helper class
72
73 class ReplacementFragment final {
74 WTF_MAKE_NONCOPYABLE(ReplacementFragment);
75 STACK_ALLOCATED();
76 public:
77 ReplacementFragment(Document*, DocumentFragment*, const VisibleSelection&);
78
79 Node* firstChild() const;
80 Node* lastChild() const;
81
82 bool isEmpty() const;
83
84 bool hasInterchangeNewlineAtStart() const { return m_hasInterchangeNewlineAt Start; }
85 bool hasInterchangeNewlineAtEnd() const { return m_hasInterchangeNewlineAtEn d; }
86
87 void removeNode(PassRefPtrWillBeRawPtr<Node>);
88 void removeNodePreservingChildren(PassRefPtrWillBeRawPtr<ContainerNode>);
89
90 private:
91 PassRefPtrWillBeRawPtr<HTMLElement> insertFragmentForTestRendering(Element* rootEditableElement);
92 void removeUnrenderedNodes(ContainerNode*);
93 void restoreAndRemoveTestRenderingNodesToFragment(Element*);
94 void removeInterchangeNodes(ContainerNode*);
95
96 void insertNodeBefore(PassRefPtrWillBeRawPtr<Node>, Node* refNode);
97
98 RefPtrWillBeMember<Document> m_document;
99 RefPtrWillBeMember<DocumentFragment> m_fragment;
100 bool m_hasInterchangeNewlineAtStart;
101 bool m_hasInterchangeNewlineAtEnd;
102 };
103
104 static bool isInterchangeHTMLBRElement(const Node* node)
105 {
106 DEFINE_STATIC_LOCAL(String, interchangeNewlineClassString, (AppleInterchange Newline));
107 if (!isHTMLBRElement(node) || toHTMLBRElement(node)->getAttribute(classAttr) != interchangeNewlineClassString)
108 return false;
109 UseCounter::count(node->document(), UseCounter::EditingAppleInterchangeNewli ne);
110 return true;
111 }
112
113 static bool isHTMLInterchangeConvertedSpaceSpan(const Node* node)
114 {
115 DEFINE_STATIC_LOCAL(String, convertedSpaceSpanClassString, (AppleConvertedSp ace));
116 if (!node->isHTMLElement() || toHTMLElement(node)->getAttribute(classAttr) ! = convertedSpaceSpanClassString)
117 return false;
118 UseCounter::count(node->document(), UseCounter::EditingAppleConvertedSpace);
119 return true;
120 }
121
122 static Position positionAvoidingPrecedingNodes(Position pos)
123 {
124 // If we're already on a break, it's probably a placeholder and we shouldn't change our position.
125 if (editingIgnoresContent(pos.anchorNode()))
126 return pos;
127
128 // We also stop when changing block flow elements because even though the vi sual position is the
129 // same. E.g.,
130 // <div>foo^</div>^
131 // The two positions above are the same visual position, but we want to stay in the same block.
132 Element* enclosingBlockElement = enclosingBlock(pos.computeContainerNode());
133 for (Position nextPosition = pos; nextPosition.computeContainerNode() != enc losingBlockElement; pos = nextPosition) {
134 if (lineBreakExistsAtPosition(pos))
135 break;
136
137 if (pos.computeContainerNode()->nonShadowBoundaryParentNode())
138 nextPosition = positionInParentAfterNode(*pos.computeContainerNode() );
139
140 if (nextPosition == pos
141 || enclosingBlock(nextPosition.computeContainerNode()) != enclosingB lockElement
142 || VisiblePosition(pos).deepEquivalent() != VisiblePosition(nextPosi tion).deepEquivalent())
143 break;
144 }
145 return pos;
146 }
147
148 ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f ragment, const VisibleSelection& selection)
149 : m_document(document),
150 m_fragment(fragment),
151 m_hasInterchangeNewlineAtStart(false),
152 m_hasInterchangeNewlineAtEnd(false)
153 {
154 if (!m_document)
155 return;
156 if (!m_fragment || !m_fragment->hasChildren())
157 return;
158
159 RefPtrWillBeRawPtr<Element> editableRoot = selection.rootEditableElement();
160 ASSERT(editableRoot);
161 if (!editableRoot)
162 return;
163
164 Element* shadowAncestorElement;
165 if (editableRoot->isInShadowTree())
166 shadowAncestorElement = editableRoot->shadowHost();
167 else
168 shadowAncestorElement = editableRoot.get();
169
170 if (!editableRoot->getAttributeEventListener(EventTypeNames::webkitBeforeTex tInserted)
171 // FIXME: Remove these checks once textareas and textfields actually reg ister an event handler.
172 && !(shadowAncestorElement && shadowAncestorElement->layoutObject() && s hadowAncestorElement->layoutObject()->isTextControl())
173 && editableRoot->layoutObjectIsRichlyEditable()) {
174 removeInterchangeNodes(m_fragment.get());
175 return;
176 }
177
178 RefPtrWillBeRawPtr<HTMLElement> holder = insertFragmentForTestRendering(edit ableRoot.get());
179 if (!holder) {
180 removeInterchangeNodes(m_fragment.get());
181 return;
182 }
183
184 const EphemeralRange range = VisibleSelection::selectionFromContentsOfNode(h older.get()).toNormalizedEphemeralRange();
185 String text = plainText(range, static_cast<TextIteratorBehavior>(TextIterato rEmitsOriginalText | TextIteratorIgnoresStyleVisibility));
186
187 removeInterchangeNodes(holder.get());
188 removeUnrenderedNodes(holder.get());
189 restoreAndRemoveTestRenderingNodesToFragment(holder.get());
190
191 // Give the root a chance to change the text.
192 RefPtrWillBeRawPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::c reate(text);
193 editableRoot->dispatchEvent(evt);
194 if (text != evt->text() || !editableRoot->layoutObjectIsRichlyEditable()) {
195 restoreAndRemoveTestRenderingNodesToFragment(holder.get());
196
197 m_fragment = createFragmentFromText(selection.toNormalizedEphemeralRange (), evt->text());
198 if (!m_fragment->hasChildren())
199 return;
200
201 holder = insertFragmentForTestRendering(editableRoot.get());
202 removeInterchangeNodes(holder.get());
203 removeUnrenderedNodes(holder.get());
204 restoreAndRemoveTestRenderingNodesToFragment(holder.get());
205 }
206 }
207
208 bool ReplacementFragment::isEmpty() const
209 {
210 return (!m_fragment || !m_fragment->hasChildren()) && !m_hasInterchangeNewli neAtStart && !m_hasInterchangeNewlineAtEnd;
211 }
212
213 Node* ReplacementFragment::firstChild() const
214 {
215 return m_fragment ? m_fragment->firstChild() : 0;
216 }
217
218 Node* ReplacementFragment::lastChild() const
219 {
220 return m_fragment ? m_fragment->lastChild() : 0;
221 }
222
223 void ReplacementFragment::removeNodePreservingChildren(PassRefPtrWillBeRawPtr<Co ntainerNode> node)
224 {
225 if (!node)
226 return;
227
228 while (RefPtrWillBeRawPtr<Node> n = node->firstChild()) {
229 removeNode(n);
230 insertNodeBefore(n.release(), node.get());
231 }
232 removeNode(node);
233 }
234
235 void ReplacementFragment::removeNode(PassRefPtrWillBeRawPtr<Node> node)
236 {
237 if (!node)
238 return;
239
240 ContainerNode* parent = node->nonShadowBoundaryParentNode();
241 if (!parent)
242 return;
243
244 parent->removeChild(node.get());
245 }
246
247 void ReplacementFragment::insertNodeBefore(PassRefPtrWillBeRawPtr<Node> node, No de* refNode)
248 {
249 if (!node || !refNode)
250 return;
251
252 ContainerNode* parent = refNode->nonShadowBoundaryParentNode();
253 if (!parent)
254 return;
255
256 parent->insertBefore(node, refNode);
257 }
258
259 PassRefPtrWillBeRawPtr<HTMLElement> ReplacementFragment::insertFragmentForTestRe ndering(Element* rootEditableElement)
260 {
261 ASSERT(m_document);
262 RefPtrWillBeRawPtr<HTMLElement> holder = createDefaultParagraphElement(*m_do cument.get());
263
264 holder->appendChild(m_fragment);
265 rootEditableElement->appendChild(holder.get());
266 m_document->updateLayoutIgnorePendingStylesheets();
267
268 return holder.release();
269 }
270
271 void ReplacementFragment::restoreAndRemoveTestRenderingNodesToFragment(Element* holder)
272 {
273 if (!holder)
274 return;
275
276 while (RefPtrWillBeRawPtr<Node> node = holder->firstChild()) {
277 holder->removeChild(node.get());
278 m_fragment->appendChild(node.get());
279 }
280
281 removeNode(holder);
282 }
283
284 void ReplacementFragment::removeUnrenderedNodes(ContainerNode* holder)
285 {
286 WillBeHeapVector<RefPtrWillBeMember<Node>> unrendered;
287
288 for (Node& node : NodeTraversal::descendantsOf(*holder)) {
289 if (!isNodeRendered(node) && !isTableStructureNode(&node))
290 unrendered.append(&node);
291 }
292
293 for (auto& node : unrendered)
294 removeNode(node);
295 }
296
297 void ReplacementFragment::removeInterchangeNodes(ContainerNode* container)
298 {
299 m_hasInterchangeNewlineAtStart = false;
300 m_hasInterchangeNewlineAtEnd = false;
301
302 // Interchange newlines at the "start" of the incoming fragment must be
303 // either the first node in the fragment or the first leaf in the fragment.
304 Node* node = container->firstChild();
305 while (node) {
306 if (isInterchangeHTMLBRElement(node)) {
307 m_hasInterchangeNewlineAtStart = true;
308 removeNode(node);
309 break;
310 }
311 node = node->firstChild();
312 }
313 if (!container->hasChildren())
314 return;
315 // Interchange newlines at the "end" of the incoming fragment must be
316 // either the last node in the fragment or the last leaf in the fragment.
317 node = container->lastChild();
318 while (node) {
319 if (isInterchangeHTMLBRElement(node)) {
320 m_hasInterchangeNewlineAtEnd = true;
321 removeNode(node);
322 break;
323 }
324 node = node->lastChild();
325 }
326
327 node = container->firstChild();
328 while (node) {
329 RefPtrWillBeRawPtr<Node> next = NodeTraversal::next(*node);
330 if (isHTMLInterchangeConvertedSpaceSpan(node)) {
331 HTMLElement& element = toHTMLElement(*node);
332 next = NodeTraversal::nextSkippingChildren(element);
333 removeNodePreservingChildren(&element);
334 }
335 node = next.get();
336 }
337 }
338
339 inline void ReplaceSelectionCommand::InsertedNodes::respondToNodeInsertion(Node& node)
340 {
341 if (!m_firstNodeInserted)
342 m_firstNodeInserted = &node;
343
344 m_lastNodeInserted = &node;
345 }
346
347 inline void ReplaceSelectionCommand::InsertedNodes::willRemoveNodePreservingChil dren(Node& node)
348 {
349 if (m_firstNodeInserted.get() == node)
350 m_firstNodeInserted = NodeTraversal::next(node);
351 if (m_lastNodeInserted.get() == node)
352 m_lastNodeInserted = node.lastChild() ? node.lastChild() : NodeTraversal ::nextSkippingChildren(node);
353 }
354
355 inline void ReplaceSelectionCommand::InsertedNodes::willRemoveNode(Node& node)
356 {
357 if (m_firstNodeInserted.get() == node && m_lastNodeInserted.get() == node) {
358 m_firstNodeInserted = nullptr;
359 m_lastNodeInserted = nullptr;
360 } else if (m_firstNodeInserted.get() == node) {
361 m_firstNodeInserted = NodeTraversal::nextSkippingChildren(*m_firstNodeIn serted);
362 } else if (m_lastNodeInserted.get() == node) {
363 m_lastNodeInserted = NodeTraversal::previousSkippingChildren(*m_lastNode Inserted);
364 }
365 }
366
367 inline void ReplaceSelectionCommand::InsertedNodes::didReplaceNode(Node& node, N ode& newNode)
368 {
369 if (m_firstNodeInserted.get() == node)
370 m_firstNodeInserted = &newNode;
371 if (m_lastNodeInserted.get() == node)
372 m_lastNodeInserted = &newNode;
373 }
374
375 ReplaceSelectionCommand::ReplaceSelectionCommand(Document& document, PassRefPtrW illBeRawPtr<DocumentFragment> fragment, CommandOptions options, EditAction editA ction)
376 : CompositeEditCommand(document)
377 , m_selectReplacement(options & SelectReplacement)
378 , m_smartReplace(options & SmartReplace)
379 , m_matchStyle(options & MatchStyle)
380 , m_documentFragment(fragment)
381 , m_preventNesting(options & PreventNesting)
382 , m_movingParagraph(options & MovingParagraph)
383 , m_editAction(editAction)
384 , m_sanitizeFragment(options & SanitizeFragment)
385 , m_shouldMergeEnd(false)
386 {
387 }
388
389 static bool hasMatchingQuoteLevel(VisiblePosition endOfExistingContent, VisibleP osition endOfInsertedContent)
390 {
391 Position existing = endOfExistingContent.deepEquivalent();
392 Position inserted = endOfInsertedContent.deepEquivalent();
393 bool isInsideMailBlockquote = enclosingNodeOfType(inserted, isMailHTMLBlockq uoteElement, CanCrossEditingBoundary);
394 return isInsideMailBlockquote && (numEnclosingMailBlockquotes(existing) == n umEnclosingMailBlockquotes(inserted));
395 }
396
397 bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfPara graph, bool fragmentHasInterchangeNewlineAtStart, bool selectionStartWasInsideMa ilBlockquote)
398 {
399 if (m_movingParagraph)
400 return false;
401
402 VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent());
403 VisiblePosition prev = startOfInsertedContent.previous(CannotCrossEditingBou ndary);
404 if (prev.isNull())
405 return false;
406
407 // When we have matching quote levels, its ok to merge more frequently.
408 // For a successful merge, we still need to make sure that the inserted cont ent starts with the beginning of a paragraph.
409 // And we should only merge here if the selection start was inside a mail bl ockquote. This prevents against removing a
410 // blockquote from newly pasted quoted content that was pasted into an unquo ted position. If that unquoted position happens
411 // to be right after another blockquote, we don't want to merge and risk str ipping a valid block (and newline) from the pasted content.
412 if (isStartOfParagraph(startOfInsertedContent) && selectionStartWasInsideMai lBlockquote && hasMatchingQuoteLevel(prev, positionAtEndOfInsertedContent()))
413 return true;
414
415 return !selectionStartWasStartOfParagraph
416 && !fragmentHasInterchangeNewlineAtStart
417 && isStartOfParagraph(startOfInsertedContent)
418 && !isHTMLBRElement(*startOfInsertedContent.deepEquivalent().anchorNode( ))
419 && shouldMerge(startOfInsertedContent, prev);
420 }
421
422 bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph)
423 {
424 VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent());
425 VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary) ;
426 if (next.isNull())
427 return false;
428
429 return !selectionEndWasEndOfParagraph
430 && isEndOfParagraph(endOfInsertedContent)
431 && !isHTMLBRElement(*endOfInsertedContent.deepEquivalent().anchorNode())
432 && shouldMerge(endOfInsertedContent, next);
433 }
434
435 static bool isMailPasteAsQuotationHTMLBlockQuoteElement(const Node* node)
436 {
437 if (!node || !node->isHTMLElement())
438 return false;
439 const HTMLElement& element = toHTMLElement(*node);
440 if (!element.hasTagName(blockquoteTag) || element.getAttribute(classAttr) != ApplePasteAsQuotation)
441 return false;
442 UseCounter::count(node->document(), UseCounter::EditingApplePasteAsQuotation );
443 return true;
444 }
445
446 static bool isHTMLHeaderElement(const Node* a)
447 {
448 if (!a || !a->isHTMLElement())
449 return false;
450
451 const HTMLElement& element = toHTMLElement(*a);
452 return element.hasTagName(h1Tag)
453 || element.hasTagName(h2Tag)
454 || element.hasTagName(h3Tag)
455 || element.hasTagName(h4Tag)
456 || element.hasTagName(h5Tag)
457 || element.hasTagName(h6Tag);
458 }
459
460 static bool haveSameTagName(Element* a, Element* b)
461 {
462 return a && b && a->tagName() == b->tagName();
463 }
464
465 bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& source, const V isiblePosition& destination)
466 {
467 if (source.isNull() || destination.isNull())
468 return false;
469
470 Node* sourceNode = source.deepEquivalent().anchorNode();
471 Node* destinationNode = destination.deepEquivalent().anchorNode();
472 Element* sourceBlock = enclosingBlock(sourceNode);
473 Element* destinationBlock = enclosingBlock(destinationNode);
474 return !enclosingNodeOfType(source.deepEquivalent(), &isMailPasteAsQuotation HTMLBlockQuoteElement)
475 && sourceBlock && (!sourceBlock->hasTagName(blockquoteTag) || isMailHTML BlockquoteElement(sourceBlock))
476 && enclosingListChild(sourceBlock) == enclosingListChild(destinationNode )
477 && enclosingTableCell(source.deepEquivalent()) == enclosingTableCell(des tination.deepEquivalent())
478 && (!isHTMLHeaderElement(sourceBlock) || haveSameTagName(sourceBlock, de stinationBlock))
479 // Don't merge to or from a position before or after a block because it would
480 // be a no-op and cause infinite recursion.
481 && !isBlock(sourceNode) && !isBlock(destinationNode);
482 }
483
484 // Style rules that match just inserted elements could change their appearance, like
485 // a div inserted into a document with div { display:inline; }.
486 void ReplaceSelectionCommand::removeRedundantStylesAndKeepStyleSpanInline(Insert edNodes& insertedNodes)
487 {
488 RefPtrWillBeRawPtr<Node> pastEndNode = insertedNodes.pastLastLeaf();
489 RefPtrWillBeRawPtr<Node> next = nullptr;
490 for (RefPtrWillBeRawPtr<Node> node = insertedNodes.firstNodeInserted(); node && node != pastEndNode; node = next) {
491 // FIXME: <rdar://problem/5371536> Style rules that match pasted content can change it's appearance
492
493 next = NodeTraversal::next(*node);
494 if (!node->isStyledElement())
495 continue;
496
497 Element* element = toElement(node);
498
499 const StylePropertySet* inlineStyle = element->inlineStyle();
500 RefPtrWillBeRawPtr<EditingStyle> newInlineStyle = EditingStyle::create(i nlineStyle);
501 if (inlineStyle) {
502 if (element->isHTMLElement()) {
503 Vector<QualifiedName> attributes;
504 HTMLElement* htmlElement = toHTMLElement(element);
505 ASSERT(htmlElement);
506
507 if (newInlineStyle->conflictsWithImplicitStyleOfElement(htmlElem ent)) {
508 // e.g. <b style="font-weight: normal;"> is converted to <sp an style="font-weight: normal;">
509 element = replaceElementWithSpanPreservingChildrenAndAttribu tes(htmlElement);
510 inlineStyle = element->inlineStyle();
511 insertedNodes.didReplaceNode(*htmlElement, *element);
512 } else if (newInlineStyle->extractConflictingImplicitStyleOfAttr ibutes(htmlElement, EditingStyle::PreserveWritingDirection, 0, attributes,
513 EditingStyle::DoNotExtractMatchingStyle)) {
514 // e.g. <font size="3" style="font-size: 20px;"> is converte d to <font style="font-size: 20px;">
515 for (size_t i = 0; i < attributes.size(); i++)
516 removeElementAttribute(htmlElement, attributes[i]);
517 }
518 }
519
520 ContainerNode* context = element->parentNode();
521
522 // If Mail wraps the fragment with a Paste as Quotation blockquote, or if you're pasting into a quoted region,
523 // styles from blockquoteNode are allowed to override those from the source document, see <rdar://problem/4930986> and <rdar://problem/5089327>.
524 HTMLQuoteElement* blockquoteElement = !context || isMailPasteAsQuota tionHTMLBlockQuoteElement(context) ?
525 toHTMLQuoteElement(context) :
526 toHTMLQuoteElement(enclosingNodeOfType(firstPositionInNode(conte xt), isMailHTMLBlockquoteElement, CanCrossEditingBoundary));
527 if (blockquoteElement)
528 newInlineStyle->removeStyleFromRulesAndContext(element, document ().documentElement());
529
530 newInlineStyle->removeStyleFromRulesAndContext(element, context);
531 }
532
533 if (!inlineStyle || newInlineStyle->isEmpty()) {
534 if (isStyleSpanOrSpanWithOnlyStyleAttribute(element) || isEmptyFontT ag(element, AllowNonEmptyStyleAttribute)) {
535 insertedNodes.willRemoveNodePreservingChildren(*element);
536 removeNodePreservingChildren(element);
537 continue;
538 }
539 removeElementAttribute(element, styleAttr);
540 } else if (newInlineStyle->style()->propertyCount() != inlineStyle->prop ertyCount()) {
541 setNodeAttribute(element, styleAttr, AtomicString(newInlineStyle->st yle()->asText()));
542 }
543
544 // FIXME: Tolerate differences in id, class, and style attributes.
545 if (element->parentNode() && isNonTableCellHTMLBlockElement(element) && areIdenticalElements(element, element->parentNode())
546 && VisiblePosition(firstPositionInNode(element->parentNode())).deepE quivalent() == VisiblePosition(firstPositionInNode(element)).deepEquivalent()
547 && VisiblePosition(lastPositionInNode(element->parentNode())).deepEq uivalent() == VisiblePosition(lastPositionInNode(element)).deepEquivalent()) {
548 insertedNodes.willRemoveNodePreservingChildren(*element);
549 removeNodePreservingChildren(element);
550 continue;
551 }
552
553 if (element->parentNode() && element->parentNode()->layoutObjectIsRichly Editable())
554 removeElementAttribute(element, contenteditableAttr);
555
556 // WebKit used to not add display: inline and float: none on copy.
557 // Keep this code around for backward compatibility
558 if (isLegacyAppleHTMLSpanElement(element)) {
559 if (!element->hasChildren()) {
560 insertedNodes.willRemoveNodePreservingChildren(*element);
561 removeNodePreservingChildren(element);
562 continue;
563 }
564 // There are other styles that style rules can give to style spans,
565 // but these are the two important ones because they'll prevent
566 // inserted content from appearing in the right paragraph.
567 // FIXME: Hyatt is concerned that selectively using display:inline w ill give inconsistent
568 // results. We already know one issue because td elements ignore the ir display property
569 // in quirks mode (which Mail.app is always in). We should look for an alternative.
570
571 // Mutate using the CSSOM wrapper so we get the same event behavior as a script.
572 if (isBlock(element))
573 element->style()->setPropertyInternal(CSSPropertyDisplay, "inlin e", false, IGNORE_EXCEPTION);
574 if (element->layoutObject() && element->layoutObject()->style()->isF loating())
575 element->style()->setPropertyInternal(CSSPropertyFloat, "none", false, IGNORE_EXCEPTION);
576 }
577 }
578 }
579
580 static bool isProhibitedParagraphChild(const AtomicString& name)
581 {
582 // https://dvcs.w3.org/hg/editing/raw-file/57abe6d3cb60/editing.html#prohibi ted-paragraph-child
583 DEFINE_STATIC_LOCAL(HashSet<AtomicString>, elements, ());
584 if (elements.isEmpty()) {
585 elements.add(addressTag.localName());
586 elements.add(articleTag.localName());
587 elements.add(asideTag.localName());
588 elements.add(blockquoteTag.localName());
589 elements.add(captionTag.localName());
590 elements.add(centerTag.localName());
591 elements.add(colTag.localName());
592 elements.add(colgroupTag.localName());
593 elements.add(ddTag.localName());
594 elements.add(detailsTag.localName());
595 elements.add(dirTag.localName());
596 elements.add(divTag.localName());
597 elements.add(dlTag.localName());
598 elements.add(dtTag.localName());
599 elements.add(fieldsetTag.localName());
600 elements.add(figcaptionTag.localName());
601 elements.add(figureTag.localName());
602 elements.add(footerTag.localName());
603 elements.add(formTag.localName());
604 elements.add(h1Tag.localName());
605 elements.add(h2Tag.localName());
606 elements.add(h3Tag.localName());
607 elements.add(h4Tag.localName());
608 elements.add(h5Tag.localName());
609 elements.add(h6Tag.localName());
610 elements.add(headerTag.localName());
611 elements.add(hgroupTag.localName());
612 elements.add(hrTag.localName());
613 elements.add(liTag.localName());
614 elements.add(listingTag.localName());
615 elements.add(mainTag.localName()); // Missing in the specification.
616 elements.add(menuTag.localName());
617 elements.add(navTag.localName());
618 elements.add(olTag.localName());
619 elements.add(pTag.localName());
620 elements.add(plaintextTag.localName());
621 elements.add(preTag.localName());
622 elements.add(sectionTag.localName());
623 elements.add(summaryTag.localName());
624 elements.add(tableTag.localName());
625 elements.add(tbodyTag.localName());
626 elements.add(tdTag.localName());
627 elements.add(tfootTag.localName());
628 elements.add(thTag.localName());
629 elements.add(theadTag.localName());
630 elements.add(trTag.localName());
631 elements.add(ulTag.localName());
632 elements.add(xmpTag.localName());
633 }
634 return elements.contains(name);
635 }
636
637 void ReplaceSelectionCommand::makeInsertedContentRoundTrippableWithHTMLTreeBuild er(const InsertedNodes& insertedNodes)
638 {
639 RefPtrWillBeRawPtr<Node> pastEndNode = insertedNodes.pastLastLeaf();
640 RefPtrWillBeRawPtr<Node> next = nullptr;
641 for (RefPtrWillBeRawPtr<Node> node = insertedNodes.firstNodeInserted(); node && node != pastEndNode; node = next) {
642 next = NodeTraversal::next(*node);
643
644 if (!node->isHTMLElement())
645 continue;
646
647 HTMLElement& element = toHTMLElement(*node);
648 if (isProhibitedParagraphChild(element.localName())) {
649 if (HTMLElement* paragraphElement = toHTMLElement(enclosingElementWi thTag(positionInParentBeforeNode(element), pTag)))
650 moveElementOutOfAncestor(&element, paragraphElement);
651 }
652
653 if (isHTMLHeaderElement(&element)) {
654 if (HTMLElement* headerElement = toHTMLElement(highestEnclosingNodeO fType(positionInParentBeforeNode(element), isHTMLHeaderElement)))
655 moveElementOutOfAncestor(&element, headerElement);
656 }
657 }
658 }
659
660 void ReplaceSelectionCommand::moveElementOutOfAncestor(PassRefPtrWillBeRawPtr<El ement> prpElement, PassRefPtrWillBeRawPtr<ContainerNode> prpAncestor)
661 {
662 RefPtrWillBeRawPtr<Element> element = prpElement;
663 RefPtrWillBeRawPtr<ContainerNode> ancestor = prpAncestor;
664
665 if (!ancestor->parentNode()->hasEditableStyle())
666 return;
667
668 VisiblePosition positionAtEndOfNode(lastPositionInOrAfterNode(element.get()) );
669 VisiblePosition lastPositionInParagraph(lastPositionInNode(ancestor.get()));
670 if (positionAtEndOfNode.deepEquivalent() == lastPositionInParagraph.deepEqui valent()) {
671 removeNode(element);
672 if (ancestor->nextSibling())
673 insertNodeBefore(element, ancestor->nextSibling());
674 else
675 appendNode(element, ancestor->parentNode());
676 } else {
677 RefPtrWillBeRawPtr<Node> nodeToSplitTo = splitTreeToNode(element.get(), ancestor.get(), true);
678 removeNode(element);
679 insertNodeBefore(element, nodeToSplitTo);
680 }
681 if (!ancestor->hasChildren())
682 removeNode(ancestor.release());
683 }
684
685 static inline bool nodeHasVisibleLayoutText(Text& text)
686 {
687 return text.layoutObject() && text.layoutObject()->resolvedTextLength() > 0;
688 }
689
690 void ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds(InsertedNodes& ins ertedNodes)
691 {
692 document().updateLayoutIgnorePendingStylesheets();
693
694 Node* lastLeafInserted = insertedNodes.lastLeafInserted();
695 if (lastLeafInserted && lastLeafInserted->isTextNode() && !nodeHasVisibleLay outText(toText(*lastLeafInserted))
696 && !enclosingElementWithTag(firstPositionInOrBeforeNode(lastLeafInserted ), selectTag)
697 && !enclosingElementWithTag(firstPositionInOrBeforeNode(lastLeafInserted ), scriptTag)) {
698 insertedNodes.willRemoveNode(*lastLeafInserted);
699 removeNode(lastLeafInserted);
700 }
701
702 // We don't have to make sure that firstNodeInserted isn't inside a select o r script element, because
703 // it is a top level node in the fragment and the user can't insert into tho se elements.
704 Node* firstNodeInserted = insertedNodes.firstNodeInserted();
705 if (firstNodeInserted && firstNodeInserted->isTextNode() && !nodeHasVisibleL ayoutText(toText(*firstNodeInserted))) {
706 insertedNodes.willRemoveNode(*firstNodeInserted);
707 removeNode(firstNodeInserted);
708 }
709 }
710
711 VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent() const
712 {
713 // FIXME: Why is this hack here? What's special about <select> tags?
714 HTMLSelectElement* enclosingSelect = toHTMLSelectElement(enclosingElementWit hTag(m_endOfInsertedContent, selectTag));
715 return VisiblePosition(enclosingSelect ? lastPositionInOrAfterNode(enclosing Select) : m_endOfInsertedContent);
716 }
717
718 VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent() cons t
719 {
720 return VisiblePosition(m_startOfInsertedContent);
721 }
722
723 static void removeHeadContents(ReplacementFragment& fragment)
724 {
725 Node* next = nullptr;
726 for (Node* node = fragment.firstChild(); node; node = next) {
727 if (isHTMLBaseElement(*node)
728 || isHTMLLinkElement(*node)
729 || isHTMLMetaElement(*node)
730 || isHTMLStyleElement(*node)
731 || isHTMLTitleElement(*node)) {
732 next = NodeTraversal::nextSkippingChildren(*node);
733 fragment.removeNode(node);
734 } else {
735 next = NodeTraversal::next(*node);
736 }
737 }
738 }
739
740 // Remove style spans before insertion if they are unnecessary. It's faster bec ause we'll
741 // avoid doing a layout.
742 static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const Position& insertionPos)
743 {
744 Node* topNode = fragment.firstChild();
745
746 // Handling the case where we are doing Paste as Quotation or pasting into q uoted content is more complicated (see handleStyleSpans)
747 // and doesn't receive the optimization.
748 if (isMailPasteAsQuotationHTMLBlockQuoteElement(topNode) || enclosingNodeOfT ype(firstPositionInOrBeforeNode(topNode), isMailHTMLBlockquoteElement, CanCrossE ditingBoundary))
749 return false;
750
751 // Either there are no style spans in the fragment or a WebKit client has ad ded content to the fragment
752 // before inserting it. Look for and handle style spans after insertion.
753 if (!isLegacyAppleHTMLSpanElement(topNode))
754 return false;
755
756 HTMLSpanElement* wrappingStyleSpan = toHTMLSpanElement(topNode);
757 RefPtrWillBeRawPtr<EditingStyle> styleAtInsertionPos = EditingStyle::create( insertionPos.parentAnchoredEquivalent());
758 String styleText = styleAtInsertionPos->style()->asText();
759
760 // FIXME: This string comparison is a naive way of comparing two styles.
761 // We should be taking the diff and check that the diff is empty.
762 if (styleText != wrappingStyleSpan->getAttribute(styleAttr))
763 return false;
764
765 fragment.removeNodePreservingChildren(wrappingStyleSpan);
766 return true;
767 }
768
769 // At copy time, WebKit wraps copied content in a span that contains the source document's
770 // default styles. If the copied Range inherits any other styles from its ances tors, we put
771 // those styles on a second span.
772 // This function removes redundant styles from those spans, and removes the span s if all their
773 // styles are redundant.
774 // We should remove the Apple-style-span class when we're done, see <rdar://prob lem/5685600>.
775 // We should remove styles from spans that are overridden by all of their childr en, either here
776 // or at copy time.
777 void ReplaceSelectionCommand::handleStyleSpans(InsertedNodes& insertedNodes)
778 {
779 HTMLSpanElement* wrappingStyleSpan = nullptr;
780 // The style span that contains the source document's default style should b e at
781 // the top of the fragment, but Mail sometimes adds a wrapper (for Paste As Quotation),
782 // so search for the top level style span instead of assuming it's at the to p.
783
784 for (Node& node : NodeTraversal::startsAt(insertedNodes.firstNodeInserted()) ) {
785 if (isLegacyAppleHTMLSpanElement(&node)) {
786 wrappingStyleSpan = toHTMLSpanElement(&node);
787 break;
788 }
789 }
790
791 // There might not be any style spans if we're pasting from another applicat ion or if
792 // we are here because of a document.execCommand("InsertHTML", ...) call.
793 if (!wrappingStyleSpan)
794 return;
795
796 RefPtrWillBeRawPtr<EditingStyle> style = EditingStyle::create(wrappingStyleS pan->inlineStyle());
797 ContainerNode* context = wrappingStyleSpan->parentNode();
798
799 // If Mail wraps the fragment with a Paste as Quotation blockquote, or if yo u're pasting into a quoted region,
800 // styles from blockquoteElement are allowed to override those from the sour ce document, see <rdar://problem/4930986> and <rdar://problem/5089327>.
801 HTMLQuoteElement* blockquoteElement = isMailPasteAsQuotationHTMLBlockQuoteEl ement(context) ?
802 toHTMLQuoteElement(context) :
803 toHTMLQuoteElement(enclosingNodeOfType(firstPositionInNode(context), isM ailHTMLBlockquoteElement, CanCrossEditingBoundary));
804 if (blockquoteElement)
805 context = document().documentElement();
806
807 // This operation requires that only editing styles to be removed from sourc eDocumentStyle.
808 style->prepareToApplyAt(firstPositionInNode(context));
809
810 // Remove block properties in the span's style. This prevents properties tha t probably have no effect
811 // currently from affecting blocks later if the style is cloned for a new bl ock element during a future
812 // editing operation.
813 // FIXME: They *can* have an effect currently if blocks beneath the style sp an aren't individually marked
814 // with block styles by the editing engine used to style them. WebKit doesn 't do this, but others might.
815 style->removeBlockProperties();
816
817 if (style->isEmpty() || !wrappingStyleSpan->hasChildren()) {
818 insertedNodes.willRemoveNodePreservingChildren(*wrappingStyleSpan);
819 removeNodePreservingChildren(wrappingStyleSpan);
820 } else {
821 setNodeAttribute(wrappingStyleSpan, styleAttr, AtomicString(style->style ()->asText()));
822 }
823 }
824
825 void ReplaceSelectionCommand::mergeEndIfNeeded()
826 {
827 if (!m_shouldMergeEnd)
828 return;
829
830 VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent());
831 VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent());
832
833 // Bail to avoid infinite recursion.
834 if (m_movingParagraph) {
835 ASSERT_NOT_REACHED();
836 return;
837 }
838
839 // Merging two paragraphs will destroy the moved one's block styles. Always move the end of inserted forward
840 // to preserve the block style of the paragraph already in the document, unl ess the paragraph to move would
841 // include the what was the start of the selection that was pasted into, so that we preserve that paragraph's
842 // block styles.
843 bool mergeForward = !(inSameParagraph(startOfInsertedContent, endOfInsertedC ontent) && !isStartOfParagraph(startOfInsertedContent));
844
845 VisiblePosition destination = mergeForward ? endOfInsertedContent.next() : e ndOfInsertedContent;
846 VisiblePosition startOfParagraphToMove = mergeForward ? startOfParagraph(end OfInsertedContent) : endOfInsertedContent.next();
847
848 // Merging forward could result in deleting the destination anchor node.
849 // To avoid this, we add a placeholder node before the start of the paragrap h.
850 if (endOfParagraph(startOfParagraphToMove).deepEquivalent() == destination.d eepEquivalent()) {
851 RefPtrWillBeRawPtr<HTMLBRElement> placeholder = createBreakElement(docum ent());
852 insertNodeBefore(placeholder, startOfParagraphToMove.deepEquivalent().an chorNode());
853 destination = VisiblePosition(positionBeforeNode(placeholder.get()));
854 }
855
856 moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove) , destination);
857
858 // Merging forward will remove m_endOfInsertedContent from the document.
859 if (mergeForward) {
860 if (m_startOfInsertedContent.isOrphan())
861 m_startOfInsertedContent = endingSelection().visibleStart().deepEqui valent();
862 m_endOfInsertedContent = endingSelection().visibleEnd().deepEquivalent( );
863 // If we merged text nodes, m_endOfInsertedContent could be null. If thi s is the case, we use m_startOfInsertedContent.
864 if (m_endOfInsertedContent.isNull())
865 m_endOfInsertedContent = m_startOfInsertedContent;
866 }
867 }
868
869 static Node* enclosingInline(Node* node)
870 {
871 while (ContainerNode* parent = node->parentNode()) {
872 if (isBlockFlowElement(*parent) || isHTMLBodyElement(*parent))
873 return node;
874 // Stop if any previous sibling is a block.
875 for (Node* sibling = node->previousSibling(); sibling; sibling = sibling ->previousSibling()) {
876 if (isBlockFlowElement(*sibling))
877 return node;
878 }
879 node = parent;
880 }
881 return node;
882 }
883
884 static bool isInlineHTMLElementWithStyle(const Node* node)
885 {
886 // We don't want to skip over any block elements.
887 if (isBlock(node))
888 return false;
889
890 if (!node->isHTMLElement())
891 return false;
892
893 // We can skip over elements whose class attribute is
894 // one of our internal classes.
895 const HTMLElement* element = toHTMLElement(node);
896 const AtomicString& classAttributeValue = element->getAttribute(classAttr);
897 if (classAttributeValue == AppleTabSpanClass) {
898 UseCounter::count(element->document(), UseCounter::EditingAppleTabSpanCl ass);
899 return true;
900 }
901 if (classAttributeValue == AppleConvertedSpace) {
902 UseCounter::count(element->document(), UseCounter::EditingAppleConverted Space);
903 return true;
904 }
905 if (classAttributeValue == ApplePasteAsQuotation) {
906 UseCounter::count(element->document(), UseCounter::EditingApplePasteAsQu otation);
907 return true;
908 }
909
910 return EditingStyle::elementIsStyledSpanOrHTMLEquivalent(element);
911 }
912
913 static inline HTMLElement* elementToSplitToAvoidPastingIntoInlineElementsWithSty le(const Position& insertionPos)
914 {
915 Element* containingBlock = enclosingBlock(insertionPos.computeContainerNode( ));
916 return toHTMLElement(highestEnclosingNodeOfType(insertionPos, isInlineHTMLEl ementWithStyle, CannotCrossEditingBoundary, containingBlock));
917 }
918
919 void ReplaceSelectionCommand::doApply()
920 {
921 VisibleSelection selection = endingSelection();
922 ASSERT(selection.isCaretOrRange());
923 ASSERT(selection.start().anchorNode());
924 if (!selection.isNonOrphanedCaretOrRange() || !selection.start().anchorNode( ))
925 return;
926
927 if (!selection.rootEditableElement())
928 return;
929
930 ReplacementFragment fragment(&document(), m_documentFragment.get(), selectio n);
931 if (performTrivialReplace(fragment))
932 return;
933
934 // We can skip matching the style if the selection is plain text.
935 if ((selection.start().anchorNode()->layoutObject() && selection.start().anc horNode()->layoutObject()->style()->userModify() == READ_WRITE_PLAINTEXT_ONLY)
936 && (selection.end().anchorNode()->layoutObject() && selection.end().anch orNode()->layoutObject()->style()->userModify() == READ_WRITE_PLAINTEXT_ONLY))
937 m_matchStyle = false;
938
939 if (m_matchStyle) {
940 m_insertionStyle = EditingStyle::create(selection.start());
941 m_insertionStyle->mergeTypingStyle(&document());
942 }
943
944 VisiblePosition visibleStart = selection.visibleStart();
945 VisiblePosition visibleEnd = selection.visibleEnd();
946
947 bool selectionEndWasEndOfParagraph = isEndOfParagraph(visibleEnd);
948 bool selectionStartWasStartOfParagraph = isStartOfParagraph(visibleStart);
949
950 Element* enclosingBlockOfVisibleStart = enclosingBlock(visibleStart.deepEqui valent().anchorNode());
951
952 Position insertionPos = selection.start();
953 bool startIsInsideMailBlockquote = enclosingNodeOfType(insertionPos, isMailH TMLBlockquoteElement, CanCrossEditingBoundary);
954 bool selectionIsPlainText = !selection.isContentRichlyEditable();
955 Element* currentRoot = selection.rootEditableElement();
956
957 if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && ! startIsInsideMailBlockquote) ||
958 enclosingBlockOfVisibleStart == currentRoot || isListItem(enclosingBlock OfVisibleStart) || selectionIsPlainText)
959 m_preventNesting = false;
960
961 if (selection.isRange()) {
962 // When the end of the selection being pasted into is at the end of a pa ragraph, and that selection
963 // spans multiple blocks, not merging may leave an empty line.
964 // When the start of the selection being pasted into is at the start of a block, not merging
965 // will leave hanging block(s).
966 // Merge blocks if the start of the selection was in a Mail blockquote, since we handle
967 // that case specially to prevent nesting.
968 bool mergeBlocksAfterDelete = startIsInsideMailBlockquote || isEndOfPara graph(visibleEnd) || isStartOfBlock(visibleStart);
969 // FIXME: We should only expand to include fully selected special elemen ts if we are copying a
970 // selection and pasting it on top of itself.
971 deleteSelection(false, mergeBlocksAfterDelete, false);
972 visibleStart = endingSelection().visibleStart();
973 if (fragment.hasInterchangeNewlineAtStart()) {
974 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleSta rt)) {
975 if (!isEndOfEditableOrNonEditableContent(visibleStart))
976 setEndingSelection(visibleStart.next());
977 } else
978 insertParagraphSeparator();
979 }
980 insertionPos = endingSelection().start();
981 } else {
982 ASSERT(selection.isCaret());
983 if (fragment.hasInterchangeNewlineAtStart()) {
984 VisiblePosition next = visibleStart.next(CannotCrossEditingBoundary) ;
985 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleSta rt) && next.isNotNull())
986 setEndingSelection(next);
987 else {
988 insertParagraphSeparator();
989 visibleStart = endingSelection().visibleStart();
990 }
991 }
992 // We split the current paragraph in two to avoid nesting the blocks fro m the fragment inside the current block.
993 // For example paste <div>foo</div><div>bar</div><div>baz</div> into <di v>x^x</div>, where ^ is the caret.
994 // As long as the div styles are the same, visually you'd expect: <div> xbar</div><div>bar</div><div>bazx</div>,
995 // not <div>xbar<div>bar</div><div>bazx</div></div>.
996 // Don't do this if the selection started in a Mail blockquote.
997 if (m_preventNesting && !startIsInsideMailBlockquote && !isEndOfParagrap h(visibleStart) && !isStartOfParagraph(visibleStart)) {
998 insertParagraphSeparator();
999 setEndingSelection(endingSelection().visibleStart().previous());
1000 }
1001 insertionPos = endingSelection().start();
1002 }
1003
1004 // We don't want any of the pasted content to end up nested in a Mail blockq uote, so first break
1005 // out of any surrounding Mail blockquotes. Unless we're inserting in a tabl e, in which case
1006 // breaking the blockquote will prevent the content from actually being inse rted in the table.
1007 if (startIsInsideMailBlockquote && m_preventNesting && !(enclosingNodeOfType (insertionPos, &isTableStructureNode))) {
1008 applyCommandToComposite(BreakBlockquoteCommand::create(document()));
1009 // This will leave a br between the split.
1010 Node* br = endingSelection().start().anchorNode();
1011 ASSERT(isHTMLBRElement(br));
1012 // Insert content between the two blockquotes, but remove the br (since it was just a placeholder).
1013 insertionPos = positionInParentBeforeNode(*br);
1014 removeNode(br);
1015 }
1016
1017 // Inserting content could cause whitespace to collapse, e.g. inserting <div >foo</div> into hello^ world.
1018 prepareWhitespaceAtPositionForSplit(insertionPos);
1019
1020 // If the downstream node has been removed there's no point in continuing.
1021 if (!insertionPos.downstream().anchorNode())
1022 return;
1023
1024 // NOTE: This would be an incorrect usage of downstream() if downstream() we re changed to mean the last position after
1025 // p that maps to the same visible position as p (since in the case where a br is at the end of a block and collapsed
1026 // away, there are positions after the br which map to the same visible posi tion as [br, 0]).
1027 HTMLBRElement* endBR = isHTMLBRElement(*insertionPos.downstream().anchorNode ()) ? toHTMLBRElement(insertionPos.downstream().anchorNode()) : 0;
1028 VisiblePosition originalVisPosBeforeEndBR;
1029 if (endBR)
1030 originalVisPosBeforeEndBR = VisiblePosition(positionBeforeNode(endBR), D OWNSTREAM).previous();
1031
1032 RefPtrWillBeRawPtr<Element> enclosingBlockOfInsertionPos = enclosingBlock(in sertionPos.anchorNode());
1033
1034 // Adjust insertionPos to prevent nesting.
1035 // If the start was in a Mail blockquote, we will have already handled adjus ting insertionPos above.
1036 if (m_preventNesting && enclosingBlockOfInsertionPos && !isTableCell(enclosi ngBlockOfInsertionPos.get()) && !startIsInsideMailBlockquote) {
1037 ASSERT(enclosingBlockOfInsertionPos != currentRoot);
1038 VisiblePosition visibleInsertionPos(insertionPos);
1039 if (isEndOfBlock(visibleInsertionPos) && !(isStartOfBlock(visibleInserti onPos) && fragment.hasInterchangeNewlineAtEnd()))
1040 insertionPos = positionInParentAfterNode(*enclosingBlockOfInsertionP os);
1041 else if (isStartOfBlock(visibleInsertionPos))
1042 insertionPos = positionInParentBeforeNode(*enclosingBlockOfInsertion Pos);
1043 }
1044
1045 // Paste at start or end of link goes outside of link.
1046 insertionPos = positionAvoidingSpecialElementBoundary(insertionPos);
1047
1048 // FIXME: Can this wait until after the operation has been performed? There doesn't seem to be
1049 // any work performed after this that queries or uses the typing style.
1050 if (LocalFrame* frame = document().frame())
1051 frame->selection().clearTypingStyle();
1052
1053 removeHeadContents(fragment);
1054
1055 // We don't want the destination to end up inside nodes that weren't selecte d. To avoid that, we move the
1056 // position forward without changing the visible position so we're still at the same visible location, but
1057 // outside of preceding tags.
1058 insertionPos = positionAvoidingPrecedingNodes(insertionPos);
1059
1060 // Paste into run of tabs splits the tab span.
1061 insertionPos = positionOutsideTabSpan(insertionPos);
1062
1063 bool handledStyleSpans = handleStyleSpansBeforeInsertion(fragment, insertion Pos);
1064
1065 // We're finished if there is nothing to add.
1066 if (fragment.isEmpty() || !fragment.firstChild())
1067 return;
1068
1069 // If we are not trying to match the destination style we prefer a position
1070 // that is outside inline elements that provide style.
1071 // This way we can produce a less verbose markup.
1072 // We can skip this optimization for fragments not wrapped in one of
1073 // our style spans and for positions inside list items
1074 // since insertAsListItems already does the right thing.
1075 if (!m_matchStyle && !enclosingList(insertionPos.computeContainerNode())) {
1076 if (insertionPos.computeContainerNode()->isTextNode() && insertionPos.of fsetInContainerNode() && !insertionPos.atLastEditingPositionForNode()) {
1077 splitTextNode(toText(insertionPos.computeContainerNode()), insertion Pos.offsetInContainerNode());
1078 insertionPos = firstPositionInNode(insertionPos.computeContainerNode ());
1079 }
1080
1081 if (RefPtrWillBeRawPtr<HTMLElement> elementToSplitTo = elementToSplitToA voidPastingIntoInlineElementsWithStyle(insertionPos)) {
1082 if (insertionPos.computeContainerNode() != elementToSplitTo->parentN ode()) {
1083 Node* splitStart = insertionPos.computeNodeAfterPosition();
1084 if (!splitStart)
1085 splitStart = insertionPos.computeContainerNode();
1086 RefPtrWillBeRawPtr<Node> nodeToSplitTo = splitTreeToNode(splitSt art, elementToSplitTo->parentNode()).get();
1087 insertionPos = positionInParentBeforeNode(*nodeToSplitTo);
1088 }
1089 }
1090 }
1091
1092 // FIXME: When pasting rich content we're often prevented from heading down the fast path by style spans. Try
1093 // again here if they've been removed.
1094
1095 // 1) Insert the content.
1096 // 2) Remove redundant styles and style tags, this inner <b> for example: <b >foo <b>bar</b> baz</b>.
1097 // 3) Merge the start of the added content with the content before the posit ion being pasted into.
1098 // 4) Do one of the following: a) expand the last br if the fragment ends wi th one and it collapsed,
1099 // b) merge the last paragraph of the incoming fragment with the paragraph t hat contained the
1100 // end of the selection that was pasted into, or c) handle an interchange ne wline at the end of the
1101 // incoming fragment.
1102 // 5) Add spaces for smart replace.
1103 // 6) Select the replacement if requested, and match style if requested.
1104
1105 InsertedNodes insertedNodes;
1106 RefPtrWillBeRawPtr<Node> refNode = fragment.firstChild();
1107 ASSERT(refNode);
1108 RefPtrWillBeRawPtr<Node> node = refNode->nextSibling();
1109
1110 fragment.removeNode(refNode);
1111
1112 Element* blockStart = enclosingBlock(insertionPos.anchorNode());
1113 if ((isHTMLListElement(refNode.get()) || (isLegacyAppleHTMLSpanElement(refNo de.get()) && isHTMLListElement(refNode->firstChild())))
1114 && blockStart && blockStart->layoutObject()->isListItem())
1115 refNode = insertAsListItems(toHTMLElement(refNode), blockStart, insertio nPos, insertedNodes);
1116 else {
1117 insertNodeAt(refNode, insertionPos);
1118 insertedNodes.respondToNodeInsertion(*refNode);
1119 }
1120
1121 // Mutation events (bug 22634) may have already removed the inserted content
1122 if (!refNode->inDocument())
1123 return;
1124
1125 bool plainTextFragment = isPlainTextMarkup(refNode.get());
1126
1127 while (node) {
1128 RefPtrWillBeRawPtr<Node> next = node->nextSibling();
1129 fragment.removeNode(node.get());
1130 insertNodeAfter(node, refNode);
1131 insertedNodes.respondToNodeInsertion(*node);
1132
1133 // Mutation events (bug 22634) may have already removed the inserted con tent
1134 if (!node->inDocument())
1135 return;
1136
1137 refNode = node;
1138 if (node && plainTextFragment)
1139 plainTextFragment = isPlainTextMarkup(node.get());
1140 node = next;
1141 }
1142
1143 removeUnrenderedTextNodesAtEnds(insertedNodes);
1144
1145 if (!handledStyleSpans)
1146 handleStyleSpans(insertedNodes);
1147
1148 // Mutation events (bug 20161) may have already removed the inserted content
1149 if (!insertedNodes.firstNodeInserted() || !insertedNodes.firstNodeInserted() ->inDocument())
1150 return;
1151
1152 // Scripts specified in javascript protocol may remove |enclosingBlockOfInse rtionPos|
1153 // during insertion, e.g. <iframe src="javascript:...">
1154 if (enclosingBlockOfInsertionPos && !enclosingBlockOfInsertionPos->inDocumen t())
1155 enclosingBlockOfInsertionPos = nullptr;
1156
1157 VisiblePosition startOfInsertedContent(firstPositionInOrBeforeNode(insertedN odes.firstNodeInserted()));
1158
1159 // We inserted before the enclosingBlockOfInsertionPos to prevent nesting, a nd the content before the enclosingBlockOfInsertionPos wasn't in its own block a nd
1160 // didn't have a br after it, so the inserted content ended up in the same p aragraph.
1161 if (!startOfInsertedContent.isNull() && enclosingBlockOfInsertionPos && inse rtionPos.anchorNode() == enclosingBlockOfInsertionPos->parentNode() && (unsigned )insertionPos.computeEditingOffset() < enclosingBlockOfInsertionPos->nodeIndex() && !isStartOfParagraph(startOfInsertedContent))
1162 insertNodeAt(createBreakElement(document()).get(), startOfInsertedConten t.deepEquivalent());
1163
1164 if (endBR && (plainTextFragment || (shouldRemoveEndBR(endBR, originalVisPosB eforeEndBR) && !(fragment.hasInterchangeNewlineAtEnd() && selectionIsPlainText)) )) {
1165 RefPtrWillBeRawPtr<ContainerNode> parent = endBR->parentNode();
1166 insertedNodes.willRemoveNode(*endBR);
1167 removeNode(endBR);
1168 if (Node* nodeToRemove = highestNodeToRemoveInPruning(parent.get())) {
1169 insertedNodes.willRemoveNode(*nodeToRemove);
1170 removeNode(nodeToRemove);
1171 }
1172 }
1173
1174 makeInsertedContentRoundTrippableWithHTMLTreeBuilder(insertedNodes);
1175
1176 removeRedundantStylesAndKeepStyleSpanInline(insertedNodes);
1177
1178 if (m_sanitizeFragment)
1179 applyCommandToComposite(SimplifyMarkupCommand::create(document(), insert edNodes.firstNodeInserted(), insertedNodes.pastLastLeaf()));
1180
1181 // Setup m_startOfInsertedContent and m_endOfInsertedContent. This should be the last two lines of code that access insertedNodes.
1182 m_startOfInsertedContent = firstPositionInOrBeforeNode(insertedNodes.firstNo deInserted());
1183 m_endOfInsertedContent = lastPositionInOrAfterNode(insertedNodes.lastLeafIns erted());
1184
1185 // Determine whether or not we should merge the end of inserted content with what's after it before we do
1186 // the start merge so that the start merge doesn't effect our decision.
1187 m_shouldMergeEnd = shouldMergeEnd(selectionEndWasEndOfParagraph);
1188
1189 if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasIntercha ngeNewlineAtStart(), startIsInsideMailBlockquote)) {
1190 VisiblePosition startOfParagraphToMove = positionAtStartOfInsertedConten t();
1191 VisiblePosition destination = startOfParagraphToMove.previous();
1192 // We need to handle the case where we need to merge the end
1193 // but our destination node is inside an inline that is the last in the block.
1194 // We insert a placeholder before the newly inserted content to avoid be ing merged into the inline.
1195 Node* destinationNode = destination.deepEquivalent().anchorNode();
1196 if (m_shouldMergeEnd && destinationNode != enclosingInline(destinationNo de) && enclosingInline(destinationNode)->nextSibling())
1197 insertNodeBefore(createBreakElement(document()), refNode.get());
1198
1199 // Merging the the first paragraph of inserted content with the content that came
1200 // before the selection that was pasted into would also move content aft er
1201 // the selection that was pasted into if: only one paragraph was being p asted,
1202 // and it was not wrapped in a block, the selection that was pasted into ended
1203 // at the end of a block and the next paragraph didn't start at the star t of a block.
1204 // Insert a line break just after the inserted content to separate it fr om what
1205 // comes after and prevent that from happening.
1206 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
1207 if (startOfParagraph(endOfInsertedContent).deepEquivalent() == startOfPa ragraphToMove.deepEquivalent()) {
1208 insertNodeAt(createBreakElement(document()).get(), endOfInsertedCont ent.deepEquivalent());
1209 // Mutation events (bug 22634) triggered by inserting the <br> might have removed the content we're about to move
1210 if (!startOfParagraphToMove.deepEquivalent().inDocument())
1211 return;
1212 }
1213
1214 // FIXME: Maintain positions for the start and end of inserted content i nstead of keeping nodes. The nodes are
1215 // only ever used to create positions where inserted content starts/ends .
1216 moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToM ove), destination);
1217 m_startOfInsertedContent = endingSelection().visibleStart().deepEquivale nt().downstream();
1218 if (m_endOfInsertedContent.isOrphan())
1219 m_endOfInsertedContent = endingSelection().visibleEnd().deepEquivale nt().upstream();
1220 }
1221
1222 Position lastPositionToSelect;
1223 if (fragment.hasInterchangeNewlineAtEnd()) {
1224 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
1225 VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBound ary);
1226
1227 if (selectionEndWasEndOfParagraph || !isEndOfParagraph(endOfInsertedCont ent) || next.isNull()) {
1228 if (!isStartOfParagraph(endOfInsertedContent)) {
1229 setEndingSelection(endOfInsertedContent);
1230 Element* enclosingBlockElement = enclosingBlock(endOfInsertedCon tent.deepEquivalent().anchorNode());
1231 if (isListItem(enclosingBlockElement)) {
1232 RefPtrWillBeRawPtr<HTMLLIElement> newListItem = createListIt emElement(document());
1233 insertNodeAfter(newListItem, enclosingBlockElement);
1234 setEndingSelection(VisiblePosition(firstPositionInNode(newLi stItem.get())));
1235 } else {
1236 // Use a default paragraph element (a plain div) for the emp ty paragraph, using the last paragraph
1237 // block's style seems to annoy users.
1238 insertParagraphSeparator(true, !startIsInsideMailBlockquote && highestEnclosingNodeOfType(endOfInsertedContent.deepEquivalent(),
1239 isMailHTMLBlockquoteElement, CannotCrossEditingBoundary, insertedNodes.firstNodeInserted()->parentNode()));
1240 }
1241
1242 // Select up to the paragraph separator that was added.
1243 lastPositionToSelect = endingSelection().visibleStart().deepEqui valent();
1244 updateNodesInserted(lastPositionToSelect.anchorNode());
1245 }
1246 } else {
1247 // Select up to the beginning of the next paragraph.
1248 lastPositionToSelect = next.deepEquivalent().downstream();
1249 }
1250 } else {
1251 mergeEndIfNeeded();
1252 }
1253
1254 if (HTMLQuoteElement* mailBlockquote = toHTMLQuoteElement(enclosingNodeOfTyp e(positionAtStartOfInsertedContent().deepEquivalent(), isMailPasteAsQuotationHTM LBlockQuoteElement)))
1255 removeElementAttribute(mailBlockquote, classAttr);
1256
1257 if (shouldPerformSmartReplace())
1258 addSpacesForSmartReplace();
1259
1260 // If we are dealing with a fragment created from plain text
1261 // no style matching is necessary.
1262 if (plainTextFragment)
1263 m_matchStyle = false;
1264
1265 completeHTMLReplacement(lastPositionToSelect);
1266 }
1267
1268 bool ReplaceSelectionCommand::shouldRemoveEndBR(HTMLBRElement* endBR, const Visi blePosition& originalVisPosBeforeEndBR)
1269 {
1270 if (!endBR || !endBR->inDocument())
1271 return false;
1272
1273 VisiblePosition visiblePos(positionBeforeNode(endBR));
1274
1275 // Don't remove the br if nothing was inserted.
1276 if (visiblePos.previous().deepEquivalent() == originalVisPosBeforeEndBR.deep Equivalent())
1277 return false;
1278
1279 // Remove the br if it is collapsed away and so is unnecessary.
1280 if (!document().inNoQuirksMode() && isEndOfBlock(visiblePos) && !isStartOfPa ragraph(visiblePos))
1281 return true;
1282
1283 // A br that was originally holding a line open should be displaced by inser ted content or turned into a line break.
1284 // A br that was originally acting as a line break should still be acting as a line break, not as a placeholder.
1285 return isStartOfParagraph(visiblePos) && isEndOfParagraph(visiblePos);
1286 }
1287
1288 bool ReplaceSelectionCommand::shouldPerformSmartReplace() const
1289 {
1290 if (!m_smartReplace)
1291 return false;
1292
1293 HTMLTextFormControlElement* textControl = enclosingTextFormControl(positionA tStartOfInsertedContent().deepEquivalent());
1294 if (isHTMLInputElement(textControl) && toHTMLInputElement(textControl)->type () == InputTypeNames::password)
1295 return false; // Disable smart replace for password fields.
1296
1297 return true;
1298 }
1299
1300 static bool isCharacterSmartReplaceExemptConsideringNonBreakingSpace(UChar32 cha racter, bool previousCharacter)
1301 {
1302 return isCharacterSmartReplaceExempt(character == noBreakSpaceCharacter ? ' ' : character, previousCharacter);
1303 }
1304
1305 void ReplaceSelectionCommand::addSpacesForSmartReplace()
1306 {
1307 VisiblePosition startOfInsertedContent = positionAtStartOfInsertedContent();
1308 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
1309
1310 Position endUpstream = endOfInsertedContent.deepEquivalent().upstream();
1311 Node* endNode = endUpstream.computeNodeBeforePosition();
1312 int endOffset = endNode && endNode->isTextNode() ? toText(endNode)->length() : 0;
1313 if (endUpstream.isOffsetInAnchor()) {
1314 endNode = endUpstream.computeContainerNode();
1315 endOffset = endUpstream.offsetInContainerNode();
1316 }
1317
1318 bool needsTrailingSpace = !isEndOfParagraph(endOfInsertedContent) && !isChar acterSmartReplaceExemptConsideringNonBreakingSpace(endOfInsertedContent.characte rAfter(), false);
1319 if (needsTrailingSpace && endNode) {
1320 bool collapseWhiteSpace = !endNode->layoutObject() || endNode->layoutObj ect()->style()->collapseWhiteSpace();
1321 if (endNode->isTextNode()) {
1322 insertTextIntoNode(toText(endNode), endOffset, collapseWhiteSpace ? nonBreakingSpaceString() : " ");
1323 if (m_endOfInsertedContent.computeContainerNode() == endNode)
1324 m_endOfInsertedContent = Position(endNode, m_endOfInsertedConten t.offsetInContainerNode() + 1);
1325 } else {
1326 RefPtrWillBeRawPtr<Text> node = document().createEditingTextNode(col lapseWhiteSpace ? nonBreakingSpaceString() : " ");
1327 insertNodeAfter(node, endNode);
1328 updateNodesInserted(node.get());
1329 }
1330 }
1331
1332 document().updateLayout();
1333
1334 Position startDownstream = startOfInsertedContent.deepEquivalent().downstrea m();
1335 Node* startNode = startDownstream.computeNodeAfterPosition();
1336 unsigned startOffset = 0;
1337 if (startDownstream.isOffsetInAnchor()) {
1338 startNode = startDownstream.computeContainerNode();
1339 startOffset = startDownstream.offsetInContainerNode();
1340 }
1341
1342 bool needsLeadingSpace = !isStartOfParagraph(startOfInsertedContent) && !isC haracterSmartReplaceExemptConsideringNonBreakingSpace(startOfInsertedContent.pre vious().characterAfter(), true);
1343 if (needsLeadingSpace && startNode) {
1344 bool collapseWhiteSpace = !startNode->layoutObject() || startNode->layou tObject()->style()->collapseWhiteSpace();
1345 if (startNode->isTextNode()) {
1346 insertTextIntoNode(toText(startNode), startOffset, collapseWhiteSpac e ? nonBreakingSpaceString() : " ");
1347 if (m_endOfInsertedContent.computeContainerNode() == startNode && m_ endOfInsertedContent.offsetInContainerNode())
1348 m_endOfInsertedContent = Position(startNode, m_endOfInsertedCont ent.offsetInContainerNode() + 1);
1349 } else {
1350 RefPtrWillBeRawPtr<Text> node = document().createEditingTextNode(col lapseWhiteSpace ? nonBreakingSpaceString() : " ");
1351 // Don't updateNodesInserted. Doing so would set m_endOfInsertedCont ent to be the node containing the leading space,
1352 // but m_endOfInsertedContent is supposed to mark the end of pasted content.
1353 insertNodeBefore(node, startNode);
1354 m_startOfInsertedContent = firstPositionInNode(node.get());
1355 }
1356 }
1357 }
1358
1359 void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositi onToSelect)
1360 {
1361 Position start = positionAtStartOfInsertedContent().deepEquivalent();
1362 Position end = positionAtEndOfInsertedContent().deepEquivalent();
1363
1364 // Mutation events may have deleted start or end
1365 if (start.isNotNull() && !start.isOrphan() && end.isNotNull() && !end.isOrph an()) {
1366 // FIXME (11475): Remove this and require that the creator of the fragme nt to use nbsps.
1367 rebalanceWhitespaceAt(start);
1368 rebalanceWhitespaceAt(end);
1369
1370 if (m_matchStyle) {
1371 ASSERT(m_insertionStyle);
1372 applyStyle(m_insertionStyle.get(), start, end);
1373 }
1374
1375 if (lastPositionToSelect.isNotNull())
1376 end = lastPositionToSelect;
1377
1378 mergeTextNodesAroundPosition(start, end);
1379 } else if (lastPositionToSelect.isNotNull())
1380 start = end = lastPositionToSelect;
1381 else
1382 return;
1383
1384 if (m_selectReplacement)
1385 setEndingSelection(VisibleSelection(start, end, SEL_DEFAULT_AFFINITY, en dingSelection().isDirectional()));
1386 else
1387 setEndingSelection(VisibleSelection(end, SEL_DEFAULT_AFFINITY, endingSel ection().isDirectional()));
1388 }
1389
1390 void ReplaceSelectionCommand::mergeTextNodesAroundPosition(Position& position, P osition& positionOnlyToBeUpdated)
1391 {
1392 bool positionIsOffsetInAnchor = position.isOffsetInAnchor();
1393 bool positionOnlyToBeUpdatedIsOffsetInAnchor = positionOnlyToBeUpdated.isOff setInAnchor();
1394 RefPtrWillBeRawPtr<Text> text = nullptr;
1395 if (positionIsOffsetInAnchor && position.computeContainerNode() && position. computeContainerNode()->isTextNode())
1396 text = toText(position.computeContainerNode());
1397 else {
1398 Node* before = position.computeNodeBeforePosition();
1399 if (before && before->isTextNode())
1400 text = toText(before);
1401 else {
1402 Node* after = position.computeNodeAfterPosition();
1403 if (after && after->isTextNode())
1404 text = toText(after);
1405 }
1406 }
1407 if (!text)
1408 return;
1409
1410 if (text->previousSibling() && text->previousSibling()->isTextNode()) {
1411 RefPtrWillBeRawPtr<Text> previous = toText(text->previousSibling());
1412 insertTextIntoNode(text, 0, previous->data());
1413
1414 if (positionIsOffsetInAnchor)
1415 position = Position(position.computeContainerNode(), previous->lengt h() + position.offsetInContainerNode());
1416 else
1417 updatePositionForNodeRemoval(position, *previous);
1418
1419 if (positionOnlyToBeUpdatedIsOffsetInAnchor) {
1420 if (positionOnlyToBeUpdated.computeContainerNode() == text)
1421 positionOnlyToBeUpdated = Position(text, previous->length() + po sitionOnlyToBeUpdated.offsetInContainerNode());
1422 else if (positionOnlyToBeUpdated.computeContainerNode() == previous)
1423 positionOnlyToBeUpdated = Position(text, positionOnlyToBeUpdated .offsetInContainerNode());
1424 } else {
1425 updatePositionForNodeRemoval(positionOnlyToBeUpdated, *previous);
1426 }
1427
1428 removeNode(previous);
1429 }
1430 if (text->nextSibling() && text->nextSibling()->isTextNode()) {
1431 RefPtrWillBeRawPtr<Text> next = toText(text->nextSibling());
1432 unsigned originalLength = text->length();
1433 insertTextIntoNode(text, originalLength, next->data());
1434
1435 if (!positionIsOffsetInAnchor)
1436 updatePositionForNodeRemoval(position, *next);
1437
1438 if (positionOnlyToBeUpdatedIsOffsetInAnchor && positionOnlyToBeUpdated.c omputeContainerNode() == next)
1439 positionOnlyToBeUpdated = Position(text, originalLength + positionOn lyToBeUpdated.offsetInContainerNode());
1440 else
1441 updatePositionForNodeRemoval(positionOnlyToBeUpdated, *next);
1442
1443 removeNode(next);
1444 }
1445 }
1446
1447 EditAction ReplaceSelectionCommand::editingAction() const
1448 {
1449 return m_editAction;
1450 }
1451
1452 // If the user is inserting a list into an existing list, instead of nesting the list,
1453 // we put the list items into the existing list.
1454 Node* ReplaceSelectionCommand::insertAsListItems(PassRefPtrWillBeRawPtr<HTMLElem ent> prpListElement, Element* insertionBlock, const Position& insertPos, Inserte dNodes& insertedNodes)
1455 {
1456 RefPtrWillBeRawPtr<HTMLElement> listElement = prpListElement;
1457
1458 while (listElement->hasOneChild() && isHTMLListElement(listElement->firstChi ld()))
1459 listElement = toHTMLElement(listElement->firstChild());
1460
1461 bool isStart = isStartOfParagraph(VisiblePosition(insertPos));
1462 bool isEnd = isEndOfParagraph(VisiblePosition(insertPos));
1463 bool isMiddle = !isStart && !isEnd;
1464 Node* lastNode = insertionBlock;
1465
1466 // If we're in the middle of a list item, we should split it into two separa te
1467 // list items and insert these nodes between them.
1468 if (isMiddle) {
1469 int textNodeOffset = insertPos.offsetInContainerNode();
1470 if (insertPos.anchorNode()->isTextNode() && textNodeOffset > 0)
1471 splitTextNode(toText(insertPos.anchorNode()), textNodeOffset);
1472 splitTreeToNode(insertPos.anchorNode(), lastNode, true);
1473 }
1474
1475 while (RefPtrWillBeRawPtr<Node> listItem = listElement->firstChild()) {
1476 listElement->removeChild(listItem.get(), ASSERT_NO_EXCEPTION);
1477 if (isStart || isMiddle) {
1478 insertNodeBefore(listItem, lastNode);
1479 insertedNodes.respondToNodeInsertion(*listItem);
1480 } else if (isEnd) {
1481 insertNodeAfter(listItem, lastNode);
1482 insertedNodes.respondToNodeInsertion(*listItem);
1483 lastNode = listItem.get();
1484 } else
1485 ASSERT_NOT_REACHED();
1486 }
1487 if (isStart || isMiddle) {
1488 if (Node* node = lastNode->previousSibling())
1489 return node;
1490 }
1491 return lastNode;
1492 }
1493
1494 void ReplaceSelectionCommand::updateNodesInserted(Node *node)
1495 {
1496 if (!node)
1497 return;
1498
1499 if (m_startOfInsertedContent.isNull())
1500 m_startOfInsertedContent = firstPositionInOrBeforeNode(node);
1501
1502 m_endOfInsertedContent = lastPositionInOrAfterNode(&NodeTraversal::lastWithi nOrSelf(*node));
1503 }
1504
1505 // During simple pastes, where we're just pasting a text node into a run of text , we insert the text node
1506 // directly into the text node that holds the selection. This is much faster th an the generalized code in
1507 // ReplaceSelectionCommand, and works around <https://bugs.webkit.org/show_bug.c gi?id=6148> since we don't
1508 // split text nodes.
1509 bool ReplaceSelectionCommand::performTrivialReplace(const ReplacementFragment& f ragment)
1510 {
1511 if (!fragment.firstChild() || fragment.firstChild() != fragment.lastChild() || !fragment.firstChild()->isTextNode())
1512 return false;
1513
1514 // FIXME: Would be nice to handle smart replace in the fast path.
1515 if (m_smartReplace || fragment.hasInterchangeNewlineAtStart() || fragment.ha sInterchangeNewlineAtEnd())
1516 return false;
1517
1518 // e.g. when "bar" is inserted after "foo" in <div><u>foo</u></div>, "bar" s hould not be underlined.
1519 if (elementToSplitToAvoidPastingIntoInlineElementsWithStyle(endingSelection( ).start()))
1520 return false;
1521
1522 RefPtrWillBeRawPtr<Node> nodeAfterInsertionPos = endingSelection().end().dow nstream().anchorNode();
1523 Text* textNode = toText(fragment.firstChild());
1524 // Our fragment creation code handles tabs, spaces, and newlines, so we don' t have to worry about those here.
1525
1526 Position start = endingSelection().start();
1527 Position end = replaceSelectedTextInNode(textNode->data());
1528 if (end.isNull())
1529 return false;
1530
1531 if (nodeAfterInsertionPos && nodeAfterInsertionPos->parentNode() && isHTMLBR Element(*nodeAfterInsertionPos)
1532 && shouldRemoveEndBR(toHTMLBRElement(nodeAfterInsertionPos.get()), Visib lePosition(positionBeforeNode(nodeAfterInsertionPos.get()))))
1533 removeNodeAndPruneAncestors(nodeAfterInsertionPos.get());
1534
1535 VisibleSelection selectionAfterReplace(m_selectReplacement ? start : end, en d);
1536
1537 setEndingSelection(selectionAfterReplace);
1538
1539 return true;
1540 }
1541
1542 DEFINE_TRACE(ReplaceSelectionCommand)
1543 {
1544 visitor->trace(m_startOfInsertedContent);
1545 visitor->trace(m_endOfInsertedContent);
1546 visitor->trace(m_insertionStyle);
1547 visitor->trace(m_documentFragment);
1548 CompositeEditCommand::trace(visitor);
1549 }
1550
1551 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698