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

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-18T14:20:58 Rebase for merging code style fixes Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright (C) 2005, 2006, 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
864 // this is the case, we use m_startOfInsertedContent.
865 if (m_endOfInsertedContent.isNull())
866 m_endOfInsertedContent = m_startOfInsertedContent;
867 }
868 }
869
870 static Node* enclosingInline(Node* node)
871 {
872 while (ContainerNode* parent = node->parentNode()) {
873 if (isBlockFlowElement(*parent) || isHTMLBodyElement(*parent))
874 return node;
875 // Stop if any previous sibling is a block.
876 for (Node* sibling = node->previousSibling(); sibling; sibling = sibling ->previousSibling()) {
877 if (isBlockFlowElement(*sibling))
878 return node;
879 }
880 node = parent;
881 }
882 return node;
883 }
884
885 static bool isInlineHTMLElementWithStyle(const Node* node)
886 {
887 // We don't want to skip over any block elements.
888 if (isBlock(node))
889 return false;
890
891 if (!node->isHTMLElement())
892 return false;
893
894 // We can skip over elements whose class attribute is
895 // one of our internal classes.
896 const HTMLElement* element = toHTMLElement(node);
897 const AtomicString& classAttributeValue = element->getAttribute(classAttr);
898 if (classAttributeValue == AppleTabSpanClass) {
899 UseCounter::count(element->document(), UseCounter::EditingAppleTabSpanCl ass);
900 return true;
901 }
902 if (classAttributeValue == AppleConvertedSpace) {
903 UseCounter::count(element->document(), UseCounter::EditingAppleConverted Space);
904 return true;
905 }
906 if (classAttributeValue == ApplePasteAsQuotation) {
907 UseCounter::count(element->document(), UseCounter::EditingApplePasteAsQu otation);
908 return true;
909 }
910
911 return EditingStyle::elementIsStyledSpanOrHTMLEquivalent(element);
912 }
913
914 static inline HTMLElement* elementToSplitToAvoidPastingIntoInlineElementsWithSty le(const Position& insertionPos)
915 {
916 Element* containingBlock = enclosingBlock(insertionPos.computeContainerNode( ));
917 return toHTMLElement(highestEnclosingNodeOfType(insertionPos, isInlineHTMLEl ementWithStyle, CannotCrossEditingBoundary, containingBlock));
918 }
919
920 void ReplaceSelectionCommand::doApply()
921 {
922 VisibleSelection selection = endingSelection();
923 ASSERT(selection.isCaretOrRange());
924 ASSERT(selection.start().anchorNode());
925 if (!selection.isNonOrphanedCaretOrRange() || !selection.start().anchorNode( ))
926 return;
927
928 if (!selection.rootEditableElement())
929 return;
930
931 ReplacementFragment fragment(&document(), m_documentFragment.get(), selectio n);
932 if (performTrivialReplace(fragment))
933 return;
934
935 // We can skip matching the style if the selection is plain text.
936 if ((selection.start().anchorNode()->layoutObject() && selection.start().anc horNode()->layoutObject()->style()->userModify() == READ_WRITE_PLAINTEXT_ONLY)
937 && (selection.end().anchorNode()->layoutObject() && selection.end().anch orNode()->layoutObject()->style()->userModify() == READ_WRITE_PLAINTEXT_ONLY))
938 m_matchStyle = false;
939
940 if (m_matchStyle) {
941 m_insertionStyle = EditingStyle::create(selection.start());
942 m_insertionStyle->mergeTypingStyle(&document());
943 }
944
945 VisiblePosition visibleStart = selection.visibleStart();
946 VisiblePosition visibleEnd = selection.visibleEnd();
947
948 bool selectionEndWasEndOfParagraph = isEndOfParagraph(visibleEnd);
949 bool selectionStartWasStartOfParagraph = isStartOfParagraph(visibleStart);
950
951 Element* enclosingBlockOfVisibleStart = enclosingBlock(visibleStart.deepEqui valent().anchorNode());
952
953 Position insertionPos = selection.start();
954 bool startIsInsideMailBlockquote = enclosingNodeOfType(insertionPos, isMailH TMLBlockquoteElement, CanCrossEditingBoundary);
955 bool selectionIsPlainText = !selection.isContentRichlyEditable();
956 Element* currentRoot = selection.rootEditableElement();
957
958 if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && ! startIsInsideMailBlockquote)
959 || enclosingBlockOfVisibleStart == currentRoot
960 || isListItem(enclosingBlockOfVisibleStart)
961 || selectionIsPlainText) {
962 m_preventNesting = false;
963 }
964
965 if (selection.isRange()) {
966 // When the end of the selection being pasted into is at the end of a pa ragraph, and that selection
967 // spans multiple blocks, not merging may leave an empty line.
968 // When the start of the selection being pasted into is at the start of a block, not merging
969 // will leave hanging block(s).
970 // Merge blocks if the start of the selection was in a Mail blockquote, since we handle
971 // that case specially to prevent nesting.
972 bool mergeBlocksAfterDelete = startIsInsideMailBlockquote || isEndOfPara graph(visibleEnd) || isStartOfBlock(visibleStart);
973 // FIXME: We should only expand to include fully selected special elemen ts if we are copying a
974 // selection and pasting it on top of itself.
975 deleteSelection(false, mergeBlocksAfterDelete, false);
976 visibleStart = endingSelection().visibleStart();
977 if (fragment.hasInterchangeNewlineAtStart()) {
978 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleSta rt)) {
979 if (!isEndOfEditableOrNonEditableContent(visibleStart))
980 setEndingSelection(visibleStart.next());
981 } else {
982 insertParagraphSeparator();
983 }
984 }
985 insertionPos = endingSelection().start();
986 } else {
987 ASSERT(selection.isCaret());
988 if (fragment.hasInterchangeNewlineAtStart()) {
989 VisiblePosition next = visibleStart.next(CannotCrossEditingBoundary) ;
990 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleSta rt) && next.isNotNull()) {
991 setEndingSelection(next);
992 } else {
993 insertParagraphSeparator();
994 visibleStart = endingSelection().visibleStart();
995 }
996 }
997 // We split the current paragraph in two to avoid nesting the blocks fro m the fragment inside the current block.
998 // For example paste <div>foo</div><div>bar</div><div>baz</div> into <di v>x^x</div>, where ^ is the caret.
999 // As long as the div styles are the same, visually you'd expect: <div> xbar</div><div>bar</div><div>bazx</div>,
1000 // not <div>xbar<div>bar</div><div>bazx</div></div>.
1001 // Don't do this if the selection started in a Mail blockquote.
1002 if (m_preventNesting && !startIsInsideMailBlockquote && !isEndOfParagrap h(visibleStart) && !isStartOfParagraph(visibleStart)) {
1003 insertParagraphSeparator();
1004 setEndingSelection(endingSelection().visibleStart().previous());
1005 }
1006 insertionPos = endingSelection().start();
1007 }
1008
1009 // We don't want any of the pasted content to end up nested in a Mail blockq uote, so first break
1010 // out of any surrounding Mail blockquotes. Unless we're inserting in a tabl e, in which case
1011 // breaking the blockquote will prevent the content from actually being inse rted in the table.
1012 if (startIsInsideMailBlockquote && m_preventNesting && !(enclosingNodeOfType (insertionPos, &isTableStructureNode))) {
1013 applyCommandToComposite(BreakBlockquoteCommand::create(document()));
1014 // This will leave a br between the split.
1015 Node* br = endingSelection().start().anchorNode();
1016 ASSERT(isHTMLBRElement(br));
1017 // Insert content between the two blockquotes, but remove the br (since it was just a placeholder).
1018 insertionPos = positionInParentBeforeNode(*br);
1019 removeNode(br);
1020 }
1021
1022 // Inserting content could cause whitespace to collapse, e.g. inserting <div >foo</div> into hello^ world.
1023 prepareWhitespaceAtPositionForSplit(insertionPos);
1024
1025 // If the downstream node has been removed there's no point in continuing.
1026 if (!insertionPos.downstream().anchorNode())
1027 return;
1028
1029 // NOTE: This would be an incorrect usage of downstream() if downstream() we re changed to mean the last position after
1030 // 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
1031 // away, there are positions after the br which map to the same visible posi tion as [br, 0]).
1032 HTMLBRElement* endBR = isHTMLBRElement(*insertionPos.downstream().anchorNode ()) ? toHTMLBRElement(insertionPos.downstream().anchorNode()) : 0;
1033 VisiblePosition originalVisPosBeforeEndBR;
1034 if (endBR)
1035 originalVisPosBeforeEndBR = VisiblePosition(positionBeforeNode(endBR), D OWNSTREAM).previous();
1036
1037 RefPtrWillBeRawPtr<Element> enclosingBlockOfInsertionPos = enclosingBlock(in sertionPos.anchorNode());
1038
1039 // Adjust insertionPos to prevent nesting.
1040 // If the start was in a Mail blockquote, we will have already handled adjus ting insertionPos above.
1041 if (m_preventNesting && enclosingBlockOfInsertionPos && !isTableCell(enclosi ngBlockOfInsertionPos.get()) && !startIsInsideMailBlockquote) {
1042 ASSERT(enclosingBlockOfInsertionPos != currentRoot);
1043 VisiblePosition visibleInsertionPos(insertionPos);
1044 if (isEndOfBlock(visibleInsertionPos) && !(isStartOfBlock(visibleInserti onPos) && fragment.hasInterchangeNewlineAtEnd()))
1045 insertionPos = positionInParentAfterNode(*enclosingBlockOfInsertionP os);
1046 else if (isStartOfBlock(visibleInsertionPos))
1047 insertionPos = positionInParentBeforeNode(*enclosingBlockOfInsertion Pos);
1048 }
1049
1050 // Paste at start or end of link goes outside of link.
1051 insertionPos = positionAvoidingSpecialElementBoundary(insertionPos);
1052
1053 // FIXME: Can this wait until after the operation has been performed? There doesn't seem to be
1054 // any work performed after this that queries or uses the typing style.
1055 if (LocalFrame* frame = document().frame())
1056 frame->selection().clearTypingStyle();
1057
1058 removeHeadContents(fragment);
1059
1060 // We don't want the destination to end up inside nodes that weren't selecte d. To avoid that, we move the
1061 // position forward without changing the visible position so we're still at the same visible location, but
1062 // outside of preceding tags.
1063 insertionPos = positionAvoidingPrecedingNodes(insertionPos);
1064
1065 // Paste into run of tabs splits the tab span.
1066 insertionPos = positionOutsideTabSpan(insertionPos);
1067
1068 bool handledStyleSpans = handleStyleSpansBeforeInsertion(fragment, insertion Pos);
1069
1070 // We're finished if there is nothing to add.
1071 if (fragment.isEmpty() || !fragment.firstChild())
1072 return;
1073
1074 // If we are not trying to match the destination style we prefer a position
1075 // that is outside inline elements that provide style.
1076 // This way we can produce a less verbose markup.
1077 // We can skip this optimization for fragments not wrapped in one of
1078 // our style spans and for positions inside list items
1079 // since insertAsListItems already does the right thing.
1080 if (!m_matchStyle && !enclosingList(insertionPos.computeContainerNode())) {
1081 if (insertionPos.computeContainerNode()->isTextNode() && insertionPos.of fsetInContainerNode() && !insertionPos.atLastEditingPositionForNode()) {
1082 splitTextNode(toText(insertionPos.computeContainerNode()), insertion Pos.offsetInContainerNode());
1083 insertionPos = firstPositionInNode(insertionPos.computeContainerNode ());
1084 }
1085
1086 if (RefPtrWillBeRawPtr<HTMLElement> elementToSplitTo = elementToSplitToA voidPastingIntoInlineElementsWithStyle(insertionPos)) {
1087 if (insertionPos.computeContainerNode() != elementToSplitTo->parentN ode()) {
1088 Node* splitStart = insertionPos.computeNodeAfterPosition();
1089 if (!splitStart)
1090 splitStart = insertionPos.computeContainerNode();
1091 RefPtrWillBeRawPtr<Node> nodeToSplitTo = splitTreeToNode(splitSt art, elementToSplitTo->parentNode()).get();
1092 insertionPos = positionInParentBeforeNode(*nodeToSplitTo);
1093 }
1094 }
1095 }
1096
1097 // FIXME: When pasting rich content we're often prevented from heading down the fast path by style spans. Try
1098 // again here if they've been removed.
1099
1100 // 1) Insert the content.
1101 // 2) Remove redundant styles and style tags, this inner <b> for example: <b >foo <b>bar</b> baz</b>.
1102 // 3) Merge the start of the added content with the content before the posit ion being pasted into.
1103 // 4) Do one of the following: a) expand the last br if the fragment ends wi th one and it collapsed,
1104 // b) merge the last paragraph of the incoming fragment with the paragraph t hat contained the
1105 // end of the selection that was pasted into, or c) handle an interchange ne wline at the end of the
1106 // incoming fragment.
1107 // 5) Add spaces for smart replace.
1108 // 6) Select the replacement if requested, and match style if requested.
1109
1110 InsertedNodes insertedNodes;
1111 RefPtrWillBeRawPtr<Node> refNode = fragment.firstChild();
1112 ASSERT(refNode);
1113 RefPtrWillBeRawPtr<Node> node = refNode->nextSibling();
1114
1115 fragment.removeNode(refNode);
1116
1117 Element* blockStart = enclosingBlock(insertionPos.anchorNode());
1118 if ((isHTMLListElement(refNode.get()) || (isLegacyAppleHTMLSpanElement(refNo de.get()) && isHTMLListElement(refNode->firstChild())))
1119 && blockStart && blockStart->layoutObject()->isListItem()) {
1120 refNode = insertAsListItems(toHTMLElement(refNode), blockStart, insertio nPos, insertedNodes);
1121 } else {
1122 insertNodeAt(refNode, insertionPos);
1123 insertedNodes.respondToNodeInsertion(*refNode);
1124 }
1125
1126 // Mutation events (bug 22634) may have already removed the inserted content
1127 if (!refNode->inDocument())
1128 return;
1129
1130 bool plainTextFragment = isPlainTextMarkup(refNode.get());
1131
1132 while (node) {
1133 RefPtrWillBeRawPtr<Node> next = node->nextSibling();
1134 fragment.removeNode(node.get());
1135 insertNodeAfter(node, refNode);
1136 insertedNodes.respondToNodeInsertion(*node);
1137
1138 // Mutation events (bug 22634) may have already removed the inserted con tent
1139 if (!node->inDocument())
1140 return;
1141
1142 refNode = node;
1143 if (node && plainTextFragment)
1144 plainTextFragment = isPlainTextMarkup(node.get());
1145 node = next;
1146 }
1147
1148 removeUnrenderedTextNodesAtEnds(insertedNodes);
1149
1150 if (!handledStyleSpans)
1151 handleStyleSpans(insertedNodes);
1152
1153 // Mutation events (bug 20161) may have already removed the inserted content
1154 if (!insertedNodes.firstNodeInserted() || !insertedNodes.firstNodeInserted() ->inDocument())
1155 return;
1156
1157 // Scripts specified in javascript protocol may remove |enclosingBlockOfInse rtionPos|
1158 // during insertion, e.g. <iframe src="javascript:...">
1159 if (enclosingBlockOfInsertionPos && !enclosingBlockOfInsertionPos->inDocumen t())
1160 enclosingBlockOfInsertionPos = nullptr;
1161
1162 VisiblePosition startOfInsertedContent(firstPositionInOrBeforeNode(insertedN odes.firstNodeInserted()));
1163
1164 // We inserted before the enclosingBlockOfInsertionPos to prevent nesting, a nd the content before the enclosingBlockOfInsertionPos wasn't in its own block a nd
1165 // didn't have a br after it, so the inserted content ended up in the same p aragraph.
1166 if (!startOfInsertedContent.isNull() && enclosingBlockOfInsertionPos && inse rtionPos.anchorNode() == enclosingBlockOfInsertionPos->parentNode() && (unsigned )insertionPos.computeEditingOffset() < enclosingBlockOfInsertionPos->nodeIndex() && !isStartOfParagraph(startOfInsertedContent))
1167 insertNodeAt(createBreakElement(document()).get(), startOfInsertedConten t.deepEquivalent());
1168
1169 if (endBR && (plainTextFragment || (shouldRemoveEndBR(endBR, originalVisPosB eforeEndBR) && !(fragment.hasInterchangeNewlineAtEnd() && selectionIsPlainText)) )) {
1170 RefPtrWillBeRawPtr<ContainerNode> parent = endBR->parentNode();
1171 insertedNodes.willRemoveNode(*endBR);
1172 removeNode(endBR);
1173 if (Node* nodeToRemove = highestNodeToRemoveInPruning(parent.get())) {
1174 insertedNodes.willRemoveNode(*nodeToRemove);
1175 removeNode(nodeToRemove);
1176 }
1177 }
1178
1179 makeInsertedContentRoundTrippableWithHTMLTreeBuilder(insertedNodes);
1180
1181 removeRedundantStylesAndKeepStyleSpanInline(insertedNodes);
1182
1183 if (m_sanitizeFragment)
1184 applyCommandToComposite(SimplifyMarkupCommand::create(document(), insert edNodes.firstNodeInserted(), insertedNodes.pastLastLeaf()));
1185
1186 // Setup m_startOfInsertedContent and m_endOfInsertedContent. This should be the last two lines of code that access insertedNodes.
1187 m_startOfInsertedContent = firstPositionInOrBeforeNode(insertedNodes.firstNo deInserted());
1188 m_endOfInsertedContent = lastPositionInOrAfterNode(insertedNodes.lastLeafIns erted());
1189
1190 // Determine whether or not we should merge the end of inserted content with what's after it before we do
1191 // the start merge so that the start merge doesn't effect our decision.
1192 m_shouldMergeEnd = shouldMergeEnd(selectionEndWasEndOfParagraph);
1193
1194 if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasIntercha ngeNewlineAtStart(), startIsInsideMailBlockquote)) {
1195 VisiblePosition startOfParagraphToMove = positionAtStartOfInsertedConten t();
1196 VisiblePosition destination = startOfParagraphToMove.previous();
1197 // We need to handle the case where we need to merge the end
1198 // but our destination node is inside an inline that is the last in the block.
1199 // We insert a placeholder before the newly inserted content to avoid be ing merged into the inline.
1200 Node* destinationNode = destination.deepEquivalent().anchorNode();
1201 if (m_shouldMergeEnd && destinationNode != enclosingInline(destinationNo de) && enclosingInline(destinationNode)->nextSibling())
1202 insertNodeBefore(createBreakElement(document()), refNode.get());
1203
1204 // Merging the the first paragraph of inserted content with the content that came
1205 // before the selection that was pasted into would also move content aft er
1206 // the selection that was pasted into if: only one paragraph was being p asted,
1207 // and it was not wrapped in a block, the selection that was pasted into ended
1208 // at the end of a block and the next paragraph didn't start at the star t of a block.
1209 // Insert a line break just after the inserted content to separate it fr om what
1210 // comes after and prevent that from happening.
1211 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
1212 if (startOfParagraph(endOfInsertedContent).deepEquivalent() == startOfPa ragraphToMove.deepEquivalent()) {
1213 insertNodeAt(createBreakElement(document()).get(), endOfInsertedCont ent.deepEquivalent());
1214 // Mutation events (bug 22634) triggered by inserting the <br> might have removed the content we're about to move
1215 if (!startOfParagraphToMove.deepEquivalent().inDocument())
1216 return;
1217 }
1218
1219 // FIXME: Maintain positions for the start and end of inserted content i nstead of keeping nodes. The nodes are
1220 // only ever used to create positions where inserted content starts/ends .
1221 moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToM ove), destination);
1222 m_startOfInsertedContent = endingSelection().visibleStart().deepEquivale nt().downstream();
1223 if (m_endOfInsertedContent.isOrphan())
1224 m_endOfInsertedContent = endingSelection().visibleEnd().deepEquivale nt().upstream();
1225 }
1226
1227 Position lastPositionToSelect;
1228 if (fragment.hasInterchangeNewlineAtEnd()) {
1229 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
1230 VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBound ary);
1231
1232 if (selectionEndWasEndOfParagraph || !isEndOfParagraph(endOfInsertedCont ent) || next.isNull()) {
1233 if (!isStartOfParagraph(endOfInsertedContent)) {
1234 setEndingSelection(endOfInsertedContent);
1235 Element* enclosingBlockElement = enclosingBlock(endOfInsertedCon tent.deepEquivalent().anchorNode());
1236 if (isListItem(enclosingBlockElement)) {
1237 RefPtrWillBeRawPtr<HTMLLIElement> newListItem = createListIt emElement(document());
1238 insertNodeAfter(newListItem, enclosingBlockElement);
1239 setEndingSelection(VisiblePosition(firstPositionInNode(newLi stItem.get())));
1240 } else {
1241 // Use a default paragraph element (a plain div) for the emp ty paragraph, using the last paragraph
1242 // block's style seems to annoy users.
1243 insertParagraphSeparator(true, !startIsInsideMailBlockquote && highestEnclosingNodeOfType(endOfInsertedContent.deepEquivalent(),
1244 isMailHTMLBlockquoteElement, CannotCrossEditingBoundary, insertedNodes.firstNodeInserted()->parentNode()));
1245 }
1246
1247 // Select up to the paragraph separator that was added.
1248 lastPositionToSelect = endingSelection().visibleStart().deepEqui valent();
1249 updateNodesInserted(lastPositionToSelect.anchorNode());
1250 }
1251 } else {
1252 // Select up to the beginning of the next paragraph.
1253 lastPositionToSelect = next.deepEquivalent().downstream();
1254 }
1255 } else {
1256 mergeEndIfNeeded();
1257 }
1258
1259 if (HTMLQuoteElement* mailBlockquote = toHTMLQuoteElement(enclosingNodeOfTyp e(positionAtStartOfInsertedContent().deepEquivalent(), isMailPasteAsQuotationHTM LBlockQuoteElement)))
1260 removeElementAttribute(mailBlockquote, classAttr);
1261
1262 if (shouldPerformSmartReplace())
1263 addSpacesForSmartReplace();
1264
1265 // If we are dealing with a fragment created from plain text
1266 // no style matching is necessary.
1267 if (plainTextFragment)
1268 m_matchStyle = false;
1269
1270 completeHTMLReplacement(lastPositionToSelect);
1271 }
1272
1273 bool ReplaceSelectionCommand::shouldRemoveEndBR(HTMLBRElement* endBR, const Visi blePosition& originalVisPosBeforeEndBR)
1274 {
1275 if (!endBR || !endBR->inDocument())
1276 return false;
1277
1278 VisiblePosition visiblePos(positionBeforeNode(endBR));
1279
1280 // Don't remove the br if nothing was inserted.
1281 if (visiblePos.previous().deepEquivalent() == originalVisPosBeforeEndBR.deep Equivalent())
1282 return false;
1283
1284 // Remove the br if it is collapsed away and so is unnecessary.
1285 if (!document().inNoQuirksMode() && isEndOfBlock(visiblePos) && !isStartOfPa ragraph(visiblePos))
1286 return true;
1287
1288 // A br that was originally holding a line open should be displaced by inser ted content or turned into a line break.
1289 // A br that was originally acting as a line break should still be acting as a line break, not as a placeholder.
1290 return isStartOfParagraph(visiblePos) && isEndOfParagraph(visiblePos);
1291 }
1292
1293 bool ReplaceSelectionCommand::shouldPerformSmartReplace() const
1294 {
1295 if (!m_smartReplace)
1296 return false;
1297
1298 HTMLTextFormControlElement* textControl = enclosingTextFormControl(positionA tStartOfInsertedContent().deepEquivalent());
1299 if (isHTMLInputElement(textControl) && toHTMLInputElement(textControl)->type () == InputTypeNames::password)
1300 return false; // Disable smart replace for password fields.
1301
1302 return true;
1303 }
1304
1305 static bool isCharacterSmartReplaceExemptConsideringNonBreakingSpace(UChar32 cha racter, bool previousCharacter)
1306 {
1307 return isCharacterSmartReplaceExempt(character == noBreakSpaceCharacter ? ' ' : character, previousCharacter);
1308 }
1309
1310 void ReplaceSelectionCommand::addSpacesForSmartReplace()
1311 {
1312 VisiblePosition startOfInsertedContent = positionAtStartOfInsertedContent();
1313 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
1314
1315 Position endUpstream = endOfInsertedContent.deepEquivalent().upstream();
1316 Node* endNode = endUpstream.computeNodeBeforePosition();
1317 int endOffset = endNode && endNode->isTextNode() ? toText(endNode)->length() : 0;
1318 if (endUpstream.isOffsetInAnchor()) {
1319 endNode = endUpstream.computeContainerNode();
1320 endOffset = endUpstream.offsetInContainerNode();
1321 }
1322
1323 bool needsTrailingSpace = !isEndOfParagraph(endOfInsertedContent) && !isChar acterSmartReplaceExemptConsideringNonBreakingSpace(endOfInsertedContent.characte rAfter(), false);
1324 if (needsTrailingSpace && endNode) {
1325 bool collapseWhiteSpace = !endNode->layoutObject() || endNode->layoutObj ect()->style()->collapseWhiteSpace();
1326 if (endNode->isTextNode()) {
1327 insertTextIntoNode(toText(endNode), endOffset, collapseWhiteSpace ? nonBreakingSpaceString() : " ");
1328 if (m_endOfInsertedContent.computeContainerNode() == endNode)
1329 m_endOfInsertedContent = Position(endNode, m_endOfInsertedConten t.offsetInContainerNode() + 1);
1330 } else {
1331 RefPtrWillBeRawPtr<Text> node = document().createEditingTextNode(col lapseWhiteSpace ? nonBreakingSpaceString() : " ");
1332 insertNodeAfter(node, endNode);
1333 updateNodesInserted(node.get());
1334 }
1335 }
1336
1337 document().updateLayout();
1338
1339 Position startDownstream = startOfInsertedContent.deepEquivalent().downstrea m();
1340 Node* startNode = startDownstream.computeNodeAfterPosition();
1341 unsigned startOffset = 0;
1342 if (startDownstream.isOffsetInAnchor()) {
1343 startNode = startDownstream.computeContainerNode();
1344 startOffset = startDownstream.offsetInContainerNode();
1345 }
1346
1347 bool needsLeadingSpace = !isStartOfParagraph(startOfInsertedContent) && !isC haracterSmartReplaceExemptConsideringNonBreakingSpace(startOfInsertedContent.pre vious().characterAfter(), true);
1348 if (needsLeadingSpace && startNode) {
1349 bool collapseWhiteSpace = !startNode->layoutObject() || startNode->layou tObject()->style()->collapseWhiteSpace();
1350 if (startNode->isTextNode()) {
1351 insertTextIntoNode(toText(startNode), startOffset, collapseWhiteSpac e ? nonBreakingSpaceString() : " ");
1352 if (m_endOfInsertedContent.computeContainerNode() == startNode && m_ endOfInsertedContent.offsetInContainerNode())
1353 m_endOfInsertedContent = Position(startNode, m_endOfInsertedCont ent.offsetInContainerNode() + 1);
1354 } else {
1355 RefPtrWillBeRawPtr<Text> node = document().createEditingTextNode(col lapseWhiteSpace ? nonBreakingSpaceString() : " ");
1356 // Don't updateNodesInserted. Doing so would set m_endOfInsertedCont ent to be the node containing the leading space,
1357 // but m_endOfInsertedContent is supposed to mark the end of pasted content.
1358 insertNodeBefore(node, startNode);
1359 m_startOfInsertedContent = firstPositionInNode(node.get());
1360 }
1361 }
1362 }
1363
1364 void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositi onToSelect)
1365 {
1366 Position start = positionAtStartOfInsertedContent().deepEquivalent();
1367 Position end = positionAtEndOfInsertedContent().deepEquivalent();
1368
1369 // Mutation events may have deleted start or end
1370 if (start.isNotNull() && !start.isOrphan() && end.isNotNull() && !end.isOrph an()) {
1371 // FIXME (11475): Remove this and require that the creator of the fragme nt to use nbsps.
1372 rebalanceWhitespaceAt(start);
1373 rebalanceWhitespaceAt(end);
1374
1375 if (m_matchStyle) {
1376 ASSERT(m_insertionStyle);
1377 applyStyle(m_insertionStyle.get(), start, end);
1378 }
1379
1380 if (lastPositionToSelect.isNotNull())
1381 end = lastPositionToSelect;
1382
1383 mergeTextNodesAroundPosition(start, end);
1384 } else if (lastPositionToSelect.isNotNull()) {
1385 start = end = lastPositionToSelect;
1386 } else {
1387 return;
1388 }
1389
1390 if (m_selectReplacement)
1391 setEndingSelection(VisibleSelection(start, end, SEL_DEFAULT_AFFINITY, en dingSelection().isDirectional()));
1392 else
1393 setEndingSelection(VisibleSelection(end, SEL_DEFAULT_AFFINITY, endingSel ection().isDirectional()));
1394 }
1395
1396 void ReplaceSelectionCommand::mergeTextNodesAroundPosition(Position& position, P osition& positionOnlyToBeUpdated)
1397 {
1398 bool positionIsOffsetInAnchor = position.isOffsetInAnchor();
1399 bool positionOnlyToBeUpdatedIsOffsetInAnchor = positionOnlyToBeUpdated.isOff setInAnchor();
1400 RefPtrWillBeRawPtr<Text> text = nullptr;
1401 if (positionIsOffsetInAnchor && position.computeContainerNode() && position. computeContainerNode()->isTextNode()) {
1402 text = toText(position.computeContainerNode());
1403 } else {
1404 Node* before = position.computeNodeBeforePosition();
1405 if (before && before->isTextNode()) {
1406 text = toText(before);
1407 } else {
1408 Node* after = position.computeNodeAfterPosition();
1409 if (after && after->isTextNode())
1410 text = toText(after);
1411 }
1412 }
1413 if (!text)
1414 return;
1415
1416 if (text->previousSibling() && text->previousSibling()->isTextNode()) {
1417 RefPtrWillBeRawPtr<Text> previous = toText(text->previousSibling());
1418 insertTextIntoNode(text, 0, previous->data());
1419
1420 if (positionIsOffsetInAnchor)
1421 position = Position(position.computeContainerNode(), previous->lengt h() + position.offsetInContainerNode());
1422 else
1423 updatePositionForNodeRemoval(position, *previous);
1424
1425 if (positionOnlyToBeUpdatedIsOffsetInAnchor) {
1426 if (positionOnlyToBeUpdated.computeContainerNode() == text)
1427 positionOnlyToBeUpdated = Position(text, previous->length() + po sitionOnlyToBeUpdated.offsetInContainerNode());
1428 else if (positionOnlyToBeUpdated.computeContainerNode() == previous)
1429 positionOnlyToBeUpdated = Position(text, positionOnlyToBeUpdated .offsetInContainerNode());
1430 } else {
1431 updatePositionForNodeRemoval(positionOnlyToBeUpdated, *previous);
1432 }
1433
1434 removeNode(previous);
1435 }
1436 if (text->nextSibling() && text->nextSibling()->isTextNode()) {
1437 RefPtrWillBeRawPtr<Text> next = toText(text->nextSibling());
1438 unsigned originalLength = text->length();
1439 insertTextIntoNode(text, originalLength, next->data());
1440
1441 if (!positionIsOffsetInAnchor)
1442 updatePositionForNodeRemoval(position, *next);
1443
1444 if (positionOnlyToBeUpdatedIsOffsetInAnchor && positionOnlyToBeUpdated.c omputeContainerNode() == next)
1445 positionOnlyToBeUpdated = Position(text, originalLength + positionOn lyToBeUpdated.offsetInContainerNode());
1446 else
1447 updatePositionForNodeRemoval(positionOnlyToBeUpdated, *next);
1448
1449 removeNode(next);
1450 }
1451 }
1452
1453 EditAction ReplaceSelectionCommand::editingAction() const
1454 {
1455 return m_editAction;
1456 }
1457
1458 // If the user is inserting a list into an existing list, instead of nesting the list,
1459 // we put the list items into the existing list.
1460 Node* ReplaceSelectionCommand::insertAsListItems(PassRefPtrWillBeRawPtr<HTMLElem ent> prpListElement, Element* insertionBlock, const Position& insertPos, Inserte dNodes& insertedNodes)
1461 {
1462 RefPtrWillBeRawPtr<HTMLElement> listElement = prpListElement;
1463
1464 while (listElement->hasOneChild() && isHTMLListElement(listElement->firstChi ld()))
1465 listElement = toHTMLElement(listElement->firstChild());
1466
1467 bool isStart = isStartOfParagraph(VisiblePosition(insertPos));
1468 bool isEnd = isEndOfParagraph(VisiblePosition(insertPos));
1469 bool isMiddle = !isStart && !isEnd;
1470 Node* lastNode = insertionBlock;
1471
1472 // If we're in the middle of a list item, we should split it into two separa te
1473 // list items and insert these nodes between them.
1474 if (isMiddle) {
1475 int textNodeOffset = insertPos.offsetInContainerNode();
1476 if (insertPos.anchorNode()->isTextNode() && textNodeOffset > 0)
1477 splitTextNode(toText(insertPos.anchorNode()), textNodeOffset);
1478 splitTreeToNode(insertPos.anchorNode(), lastNode, true);
1479 }
1480
1481 while (RefPtrWillBeRawPtr<Node> listItem = listElement->firstChild()) {
1482 listElement->removeChild(listItem.get(), ASSERT_NO_EXCEPTION);
1483 if (isStart || isMiddle) {
1484 insertNodeBefore(listItem, lastNode);
1485 insertedNodes.respondToNodeInsertion(*listItem);
1486 } else if (isEnd) {
1487 insertNodeAfter(listItem, lastNode);
1488 insertedNodes.respondToNodeInsertion(*listItem);
1489 lastNode = listItem.get();
1490 } else {
1491 ASSERT_NOT_REACHED();
1492 }
1493 }
1494 if (isStart || isMiddle) {
1495 if (Node* node = lastNode->previousSibling())
1496 return node;
1497 }
1498 return lastNode;
1499 }
1500
1501 void ReplaceSelectionCommand::updateNodesInserted(Node *node)
1502 {
1503 if (!node)
1504 return;
1505
1506 if (m_startOfInsertedContent.isNull())
1507 m_startOfInsertedContent = firstPositionInOrBeforeNode(node);
1508
1509 m_endOfInsertedContent = lastPositionInOrAfterNode(&NodeTraversal::lastWithi nOrSelf(*node));
1510 }
1511
1512 // During simple pastes, where we're just pasting a text node into a run of text , we insert the text node
1513 // directly into the text node that holds the selection. This is much faster th an the generalized code in
1514 // ReplaceSelectionCommand, and works around <https://bugs.webkit.org/show_bug.c gi?id=6148> since we don't
1515 // split text nodes.
1516 bool ReplaceSelectionCommand::performTrivialReplace(const ReplacementFragment& f ragment)
1517 {
1518 if (!fragment.firstChild() || fragment.firstChild() != fragment.lastChild() || !fragment.firstChild()->isTextNode())
1519 return false;
1520
1521 // FIXME: Would be nice to handle smart replace in the fast path.
1522 if (m_smartReplace || fragment.hasInterchangeNewlineAtStart() || fragment.ha sInterchangeNewlineAtEnd())
1523 return false;
1524
1525 // e.g. when "bar" is inserted after "foo" in <div><u>foo</u></div>, "bar" s hould not be underlined.
1526 if (elementToSplitToAvoidPastingIntoInlineElementsWithStyle(endingSelection( ).start()))
1527 return false;
1528
1529 RefPtrWillBeRawPtr<Node> nodeAfterInsertionPos = endingSelection().end().dow nstream().anchorNode();
1530 Text* textNode = toText(fragment.firstChild());
1531 // Our fragment creation code handles tabs, spaces, and newlines, so we don' t have to worry about those here.
1532
1533 Position start = endingSelection().start();
1534 Position end = replaceSelectedTextInNode(textNode->data());
1535 if (end.isNull())
1536 return false;
1537
1538 if (nodeAfterInsertionPos && nodeAfterInsertionPos->parentNode() && isHTMLBR Element(*nodeAfterInsertionPos)
1539 && shouldRemoveEndBR(toHTMLBRElement(nodeAfterInsertionPos.get()), Visib lePosition(positionBeforeNode(nodeAfterInsertionPos.get()))))
1540 removeNodeAndPruneAncestors(nodeAfterInsertionPos.get());
1541
1542 VisibleSelection selectionAfterReplace(m_selectReplacement ? start : end, en d);
1543
1544 setEndingSelection(selectionAfterReplace);
1545
1546 return true;
1547 }
1548
1549 DEFINE_TRACE(ReplaceSelectionCommand)
1550 {
1551 visitor->trace(m_startOfInsertedContent);
1552 visitor->trace(m_endOfInsertedContent);
1553 visitor->trace(m_insertionStyle);
1554 visitor->trace(m_documentFragment);
1555 CompositeEditCommand::trace(visitor);
1556 }
1557
1558 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698