1/*
2 * Copyright (C) 2005-2017 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 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 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 "ReplaceSelectionCommand.h"
29
30#include "AXObjectCache.h"
31#include "ApplyStyleCommand.h"
32#include "BeforeTextInsertedEvent.h"
33#include "BreakBlockquoteCommand.h"
34#include "CSSStyleDeclaration.h"
35#include "DOMWrapperWorld.h"
36#include "DataTransfer.h"
37#include "Document.h"
38#include "DocumentFragment.h"
39#include "Editing.h"
40#include "EditingBehavior.h"
41#include "ElementIterator.h"
42#include "EventNames.h"
43#include "Frame.h"
44#include "FrameSelection.h"
45#include "HTMLBRElement.h"
46#include "HTMLBaseElement.h"
47#include "HTMLInputElement.h"
48#include "HTMLLIElement.h"
49#include "HTMLLinkElement.h"
50#include "HTMLMetaElement.h"
51#include "HTMLNames.h"
52#include "HTMLStyleElement.h"
53#include "HTMLTitleElement.h"
54#include "NodeList.h"
55#include "NodeRenderStyle.h"
56#include "RenderInline.h"
57#include "RenderText.h"
58#include "SimplifyMarkupCommand.h"
59#include "SmartReplace.h"
60#include "StyleProperties.h"
61#include "Text.h"
62#include "TextIterator.h"
63#include "VisibleUnits.h"
64#include "markup.h"
65#include <wtf/NeverDestroyed.h>
66#include <wtf/StdLibExtras.h>
67
68namespace WebCore {
69
70using namespace HTMLNames;
71
72enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment };
73
74static void removeHeadContents(ReplacementFragment&);
75
76// --- ReplacementFragment helper class
77
78class ReplacementFragment {
79 WTF_MAKE_FAST_ALLOCATED;
80 WTF_MAKE_NONCOPYABLE(ReplacementFragment);
81public:
82 ReplacementFragment(Document&, DocumentFragment*, const VisibleSelection&);
83
84 DocumentFragment* fragment() { return m_fragment.get(); }
85
86 Node* firstChild() const;
87 Node* lastChild() const;
88
89 bool isEmpty() const;
90
91 bool hasInterchangeNewlineAtStart() const { return m_hasInterchangeNewlineAtStart; }
92 bool hasInterchangeNewlineAtEnd() const { return m_hasInterchangeNewlineAtEnd; }
93
94 void removeNode(Node&);
95 void removeNodePreservingChildren(Node&);
96
97private:
98 Ref<HTMLElement> insertFragmentForTestRendering(Node* rootEditableNode);
99 void removeUnrenderedNodes(Node*);
100 void restoreAndRemoveTestRenderingNodesToFragment(StyledElement*);
101 void removeInterchangeNodes(Node*);
102
103 void insertNodeBefore(Node&, Node& refNode);
104
105 Document& document() { return *m_document; }
106
107 RefPtr<Document> m_document;
108 RefPtr<DocumentFragment> m_fragment;
109 bool m_hasInterchangeNewlineAtStart;
110 bool m_hasInterchangeNewlineAtEnd;
111};
112
113static bool isInterchangeNewlineNode(const Node* node)
114{
115 static NeverDestroyed<String> interchangeNewlineClassString(AppleInterchangeNewline);
116 return is<HTMLBRElement>(node) && downcast<HTMLBRElement>(*node).attributeWithoutSynchronization(classAttr) == interchangeNewlineClassString;
117}
118
119static bool isInterchangeConvertedSpaceSpan(const Node* node)
120{
121 static NeverDestroyed<String> convertedSpaceSpanClassString(AppleConvertedSpace);
122 return is<HTMLElement>(node) && downcast<HTMLElement>(*node).attributeWithoutSynchronization(classAttr) == convertedSpaceSpanClassString;
123}
124
125static Position positionAvoidingPrecedingNodes(Position position)
126{
127 ASSERT(position.isNotNull());
128
129 // If we're already on a break, it's probably a placeholder and we shouldn't change our position.
130 if (editingIgnoresContent(*position.deprecatedNode()))
131 return position;
132
133 // We also stop when changing block flow elements because even though the visual position is the
134 // same. E.g.,
135 // <div>foo^</div>^
136 // The two positions above are the same visual position, but we want to stay in the same block.
137 auto* enclosingBlockNode = enclosingBlock(position.containerNode());
138 for (Position nextPosition = position; nextPosition.containerNode() != enclosingBlockNode; position = nextPosition) {
139 if (lineBreakExistsAtPosition(position))
140 break;
141
142 if (position.containerNode()->nonShadowBoundaryParentNode())
143 nextPosition = positionInParentAfterNode(position.containerNode());
144
145 if (nextPosition == position)
146 break;
147 if (enclosingBlock(nextPosition.containerNode()) != enclosingBlockNode)
148 break;
149 if (VisiblePosition(position) != VisiblePosition(nextPosition))
150 break;
151 }
152 return position;
153}
154
155ReplacementFragment::ReplacementFragment(Document& document, DocumentFragment* fragment, const VisibleSelection& selection)
156 : m_document(&document)
157 , m_fragment(fragment)
158 , m_hasInterchangeNewlineAtStart(false)
159 , m_hasInterchangeNewlineAtEnd(false)
160{
161 if (!m_fragment)
162 return;
163 if (!m_fragment->firstChild())
164 return;
165
166 RefPtr<Element> editableRoot = selection.rootEditableElement();
167 ASSERT(editableRoot);
168 if (!editableRoot)
169 return;
170
171 auto* shadowHost = editableRoot->shadowHost();
172 if (!editableRoot->attributeEventListener(eventNames().webkitBeforeTextInsertedEvent, mainThreadNormalWorld())
173 && !(shadowHost && shadowHost->renderer() && shadowHost->renderer()->isTextControl())
174 && editableRoot->hasRichlyEditableStyle()) {
175 removeInterchangeNodes(m_fragment.get());
176 return;
177 }
178
179 RefPtr<StyledElement> holder = insertFragmentForTestRendering(editableRoot.get());
180 if (!holder) {
181 removeInterchangeNodes(m_fragment.get());
182 return;
183 }
184
185 RefPtr<Range> range = VisibleSelection::selectionFromContentsOfNode(holder.get()).toNormalizedRange();
186 String text = plainText(range.get(), static_cast<TextIteratorBehavior>(TextIteratorEmitsOriginalText | TextIteratorIgnoresStyleVisibility));
187
188 removeInterchangeNodes(holder.get());
189 removeUnrenderedNodes(holder.get());
190 restoreAndRemoveTestRenderingNodesToFragment(holder.get());
191
192 // Give the root a chance to change the text.
193 auto event = BeforeTextInsertedEvent::create(text);
194 editableRoot->dispatchEvent(event);
195 if (text != event->text() || !editableRoot->hasRichlyEditableStyle()) {
196 restoreAndRemoveTestRenderingNodesToFragment(holder.get());
197
198 RefPtr<Range> range = selection.toNormalizedRange();
199 if (!range)
200 return;
201
202 m_fragment = createFragmentFromText(*range, event->text());
203 if (!m_fragment->firstChild())
204 return;
205
206 holder = insertFragmentForTestRendering(editableRoot.get());
207 removeInterchangeNodes(holder.get());
208 removeUnrenderedNodes(holder.get());
209 restoreAndRemoveTestRenderingNodesToFragment(holder.get());
210 }
211}
212
213bool ReplacementFragment::isEmpty() const
214{
215 return (!m_fragment || !m_fragment->firstChild()) && !m_hasInterchangeNewlineAtStart && !m_hasInterchangeNewlineAtEnd;
216}
217
218Node *ReplacementFragment::firstChild() const
219{
220 return m_fragment ? m_fragment->firstChild() : 0;
221}
222
223Node *ReplacementFragment::lastChild() const
224{
225 return m_fragment ? m_fragment->lastChild() : 0;
226}
227
228void ReplacementFragment::removeNodePreservingChildren(Node& node)
229{
230 Ref<Node> protectedNode = node;
231 while (RefPtr<Node> n = node.firstChild()) {
232 removeNode(*n);
233 insertNodeBefore(*n, node);
234 }
235 removeNode(node);
236}
237
238void ReplacementFragment::removeNode(Node& node)
239{
240 ContainerNode* parent = node.nonShadowBoundaryParentNode();
241 if (!parent)
242 return;
243
244 parent->removeChild(node);
245}
246
247void ReplacementFragment::insertNodeBefore(Node& node, Node& refNode)
248{
249 ContainerNode* parent = refNode.nonShadowBoundaryParentNode();
250 if (!parent)
251 return;
252
253 parent->insertBefore(node, &refNode);
254}
255
256Ref<HTMLElement> ReplacementFragment::insertFragmentForTestRendering(Node* rootEditableElement)
257{
258 auto holder = createDefaultParagraphElement(document());
259
260 holder->appendChild(*m_fragment);
261 rootEditableElement->appendChild(holder);
262 document().updateLayoutIgnorePendingStylesheets();
263
264 return holder;
265}
266
267void ReplacementFragment::restoreAndRemoveTestRenderingNodesToFragment(StyledElement* holder)
268{
269 if (!holder)
270 return;
271
272 while (RefPtr<Node> node = holder->firstChild()) {
273 holder->removeChild(*node);
274 m_fragment->appendChild(*node);
275 }
276
277 removeNode(*holder);
278}
279
280void ReplacementFragment::removeUnrenderedNodes(Node* holder)
281{
282 Vector<Ref<Node>> unrendered;
283
284 for (Node* node = holder->firstChild(); node; node = NodeTraversal::next(*node, holder)) {
285 if (!isNodeRendered(*node) && !isTableStructureNode(node))
286 unrendered.append(*node);
287 }
288
289 for (auto& node : unrendered)
290 removeNode(node);
291}
292
293void ReplacementFragment::removeInterchangeNodes(Node* container)
294{
295 m_hasInterchangeNewlineAtStart = false;
296 m_hasInterchangeNewlineAtEnd = false;
297
298 // Interchange newlines at the "start" of the incoming fragment must be
299 // either the first node in the fragment or the first leaf in the fragment.
300 Node* node = container->firstChild();
301 while (node) {
302 if (isInterchangeNewlineNode(node)) {
303 m_hasInterchangeNewlineAtStart = true;
304 removeNode(*node);
305 break;
306 }
307 node = node->firstChild();
308 }
309 if (!container->hasChildNodes())
310 return;
311 // Interchange newlines at the "end" of the incoming fragment must be
312 // either the last node in the fragment or the last leaf in the fragment.
313 node = container->lastChild();
314 while (node) {
315 if (isInterchangeNewlineNode(node)) {
316 m_hasInterchangeNewlineAtEnd = true;
317 removeNode(*node);
318 break;
319 }
320 node = node->lastChild();
321 }
322
323 node = container->firstChild();
324 while (node) {
325 RefPtr<Node> next = NodeTraversal::next(*node);
326 if (isInterchangeConvertedSpaceSpan(node)) {
327 next = NodeTraversal::nextSkippingChildren(*node);
328 removeNodePreservingChildren(*node);
329 }
330 node = next.get();
331 }
332}
333
334inline void ReplaceSelectionCommand::InsertedNodes::respondToNodeInsertion(Node* node)
335{
336 if (!node)
337 return;
338
339 if (!m_firstNodeInserted)
340 m_firstNodeInserted = node;
341
342 m_lastNodeInserted = node;
343}
344
345inline void ReplaceSelectionCommand::InsertedNodes::willRemoveNodePreservingChildren(Node* node)
346{
347 if (m_firstNodeInserted == node)
348 m_firstNodeInserted = NodeTraversal::next(*node);
349 if (m_lastNodeInserted == node) {
350 m_lastNodeInserted = node->lastChild() ? node->lastChild() : NodeTraversal::nextSkippingChildren(*node);
351 if (!m_lastNodeInserted) {
352 // If the last inserted node is at the end of the document and doesn't have any children, look backwards for the
353 // previous node as the last inserted node, clamping to the first inserted node if needed to ensure that the
354 // document position of the last inserted node is not behind the first inserted node.
355 auto* previousNode = NodeTraversal::previousSkippingChildren(*node);
356 ASSERT(previousNode);
357 m_lastNodeInserted = m_firstNodeInserted->compareDocumentPosition(*previousNode) & Node::DOCUMENT_POSITION_FOLLOWING ? previousNode : m_firstNodeInserted;
358 }
359 }
360}
361
362inline void ReplaceSelectionCommand::InsertedNodes::willRemoveNode(Node* node)
363{
364 if (m_firstNodeInserted == node && m_lastNodeInserted == node) {
365 m_firstNodeInserted = nullptr;
366 m_lastNodeInserted = nullptr;
367 } else if (m_firstNodeInserted == node)
368 m_firstNodeInserted = NodeTraversal::nextSkippingChildren(*m_firstNodeInserted);
369 else if (m_lastNodeInserted == node)
370 m_lastNodeInserted = NodeTraversal::previousSkippingChildren(*m_lastNodeInserted);
371}
372
373inline void ReplaceSelectionCommand::InsertedNodes::didReplaceNode(Node* node, Node* newNode)
374{
375 if (m_firstNodeInserted == node)
376 m_firstNodeInserted = newNode;
377 if (m_lastNodeInserted == node)
378 m_lastNodeInserted = newNode;
379}
380
381ReplaceSelectionCommand::ReplaceSelectionCommand(Document& document, RefPtr<DocumentFragment>&& fragment, OptionSet<CommandOption> options, EditAction editAction)
382 : CompositeEditCommand(document, editAction)
383 , m_selectReplacement(options & SelectReplacement)
384 , m_smartReplace(options & SmartReplace)
385 , m_matchStyle(options & MatchStyle)
386 , m_documentFragment(fragment)
387 , m_preventNesting(options & PreventNesting)
388 , m_movingParagraph(options & MovingParagraph)
389 , m_sanitizeFragment(options & SanitizeFragment)
390 , m_shouldMergeEnd(false)
391 , m_ignoreMailBlockquote(options & IgnoreMailBlockquote)
392{
393}
394
395static bool hasMatchingQuoteLevel(VisiblePosition endOfExistingContent, VisiblePosition endOfInsertedContent)
396{
397 Position existing = endOfExistingContent.deepEquivalent();
398 Position inserted = endOfInsertedContent.deepEquivalent();
399 bool isInsideMailBlockquote = enclosingNodeOfType(inserted, isMailBlockquote, CanCrossEditingBoundary);
400 return isInsideMailBlockquote && (numEnclosingMailBlockquotes(existing) == numEnclosingMailBlockquotes(inserted));
401}
402
403bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfParagraph, bool fragmentHasInterchangeNewlineAtStart, bool selectionStartWasInsideMailBlockquote)
404{
405 if (m_movingParagraph)
406 return false;
407
408 VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent());
409 VisiblePosition prev = startOfInsertedContent.previous(CannotCrossEditingBoundary);
410 if (prev.isNull())
411 return false;
412
413 // When we have matching quote levels, its ok to merge more frequently.
414 // For a successful merge, we still need to make sure that the inserted content starts with the beginning of a paragraph.
415 // And we should only merge here if the selection start was inside a mail blockquote. This prevents against removing a
416 // blockquote from newly pasted quoted content that was pasted into an unquoted position. If that unquoted position happens
417 // to be right after another blockquote, we don't want to merge and risk stripping a valid block (and newline) from the pasted content.
418 if (isStartOfParagraph(startOfInsertedContent) && selectionStartWasInsideMailBlockquote && hasMatchingQuoteLevel(prev, positionAtEndOfInsertedContent()))
419 return true;
420
421 return !selectionStartWasStartOfParagraph
422 && !fragmentHasInterchangeNewlineAtStart
423 && isStartOfParagraph(startOfInsertedContent)
424 && !startOfInsertedContent.deepEquivalent().deprecatedNode()->hasTagName(brTag)
425 && shouldMerge(startOfInsertedContent, prev);
426}
427
428bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph)
429{
430 VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent());
431 VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary);
432 if (next.isNull())
433 return false;
434
435 return !selectionEndWasEndOfParagraph
436 && isEndOfParagraph(endOfInsertedContent)
437 && !endOfInsertedContent.deepEquivalent().deprecatedNode()->hasTagName(brTag)
438 && shouldMerge(endOfInsertedContent, next);
439}
440
441static bool isMailPasteAsQuotationNode(const Node* node)
442{
443 return node && node->hasTagName(blockquoteTag) && downcast<Element>(node)->attributeWithoutSynchronization(classAttr) == ApplePasteAsQuotation;
444}
445
446static bool isHeaderElement(const Node* a)
447{
448 if (!a)
449 return false;
450
451 return a->hasTagName(h1Tag)
452 || a->hasTagName(h2Tag)
453 || a->hasTagName(h3Tag)
454 || a->hasTagName(h4Tag)
455 || a->hasTagName(h5Tag)
456 || a->hasTagName(h6Tag);
457}
458
459static bool haveSameTagName(Node* a, Node* b)
460{
461 return is<Element>(a) && is<Element>(b) && downcast<Element>(*a).tagName() == downcast<Element>(*b).tagName();
462}
463
464bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& source, const VisiblePosition& destination)
465{
466 if (source.isNull() || destination.isNull())
467 return false;
468
469 auto* sourceNode = source.deepEquivalent().deprecatedNode();
470 auto* destinationNode = destination.deepEquivalent().deprecatedNode();
471 auto* sourceBlock = enclosingBlock(sourceNode);
472 auto* destinationBlock = enclosingBlock(destinationNode);
473 return !enclosingNodeOfType(source.deepEquivalent(), &isMailPasteAsQuotationNode)
474 && sourceBlock
475 && (!sourceBlock->hasTagName(blockquoteTag) || isMailBlockquote(sourceBlock))
476 && enclosingListChild(sourceBlock) == enclosingListChild(destinationNode)
477 && enclosingTableCell(source.deepEquivalent()) == enclosingTableCell(destination.deepEquivalent())
478 && (!isHeaderElement(sourceBlock) || haveSameTagName(sourceBlock, destinationBlock))
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; }.
486void ReplaceSelectionCommand::removeRedundantStylesAndKeepStyleSpanInline(InsertedNodes& insertedNodes)
487{
488 RefPtr<Node> pastEndNode = insertedNodes.pastLastLeaf();
489 RefPtr<Node> next;
490 for (RefPtr<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 (!is<StyledElement>(*node))
495 continue;
496
497 StyledElement* element = downcast<StyledElement>(node.get());
498
499 const StyleProperties* inlineStyle = element->inlineStyle();
500 auto newInlineStyle = EditingStyle::create(inlineStyle);
501 if (inlineStyle) {
502 if (is<HTMLElement>(*element)) {
503 Vector<QualifiedName> attributes;
504 HTMLElement& htmlElement = downcast<HTMLElement>(*element);
505
506 if (newInlineStyle->conflictsWithImplicitStyleOfElement(htmlElement)) {
507 // e.g. <b style="font-weight: normal;"> is converted to <span style="font-weight: normal;">
508 node = replaceElementWithSpanPreservingChildrenAndAttributes(htmlElement);
509 element = downcast<StyledElement>(node.get());
510 insertedNodes.didReplaceNode(&htmlElement, node.get());
511 } else if (newInlineStyle->extractConflictingImplicitStyleOfAttributes(htmlElement, EditingStyle::PreserveWritingDirection, nullptr, attributes, EditingStyle::DoNotExtractMatchingStyle)) {
512 // e.g. <font size="3" style="font-size: 20px;"> is converted to <font style="font-size: 20px;">
513 for (auto& attribute : attributes)
514 removeNodeAttribute(*element, attribute);
515 }
516 }
517
518 ContainerNode* context = element->parentNode();
519
520 // If Mail wraps the fragment with a Paste as Quotation blockquote, or if you're pasting into a quoted region,
521 // styles from blockquoteNode are allowed to override those from the source document, see <rdar://problem/4930986> and <rdar://problem/5089327>.
522 Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context : enclosingNodeOfType(firstPositionInNode(context), isMailBlockquote, CanCrossEditingBoundary);
523 if (blockquoteNode)
524 newInlineStyle->removeStyleFromRulesAndContext(*element, document().documentElement());
525
526 newInlineStyle->removeStyleFromRulesAndContext(*element, context);
527 }
528
529 if (!inlineStyle || newInlineStyle->isEmpty()) {
530 if (isStyleSpanOrSpanWithOnlyStyleAttribute(*element) || isEmptyFontTag(element, AllowNonEmptyStyleAttribute)) {
531 insertedNodes.willRemoveNodePreservingChildren(element);
532 removeNodePreservingChildren(*element);
533 continue;
534 }
535 removeNodeAttribute(*element, styleAttr);
536 } else if (newInlineStyle->style()->propertyCount() != inlineStyle->propertyCount())
537 setNodeAttribute(*element, styleAttr, newInlineStyle->style()->asText());
538
539 // FIXME: Tolerate differences in id, class, and style attributes.
540 if (element->parentNode() && isNonTableCellHTMLBlockElement(element) && areIdenticalElements(*element, *element->parentNode())
541 && VisiblePosition(firstPositionInNode(element->parentNode())) == VisiblePosition(firstPositionInNode(element))
542 && VisiblePosition(lastPositionInNode(element->parentNode())) == VisiblePosition(lastPositionInNode(element))) {
543 insertedNodes.willRemoveNodePreservingChildren(element);
544 removeNodePreservingChildren(*element);
545 continue;
546 }
547
548 if (element->parentNode() && element->parentNode()->hasRichlyEditableStyle())
549 removeNodeAttribute(*element, contenteditableAttr);
550
551 // WebKit used to not add display: inline and float: none on copy.
552 // Keep this code around for backward compatibility
553 if (isLegacyAppleStyleSpan(element)) {
554 if (!element->firstChild()) {
555 insertedNodes.willRemoveNodePreservingChildren(element);
556 removeNodePreservingChildren(*element);
557 continue;
558 }
559 // There are other styles that style rules can give to style spans,
560 // but these are the two important ones because they'll prevent
561 // inserted content from appearing in the right paragraph.
562 // FIXME: Hyatt is concerned that selectively using display:inline will give inconsistent
563 // results. We already know one issue because td elements ignore their display property
564 // in quirks mode (which Mail.app is always in). We should look for an alternative.
565
566 // Mutate using the CSSOM wrapper so we get the same event behavior as a script.
567 if (isBlock(element))
568 element->cssomStyle().setPropertyInternal(CSSPropertyDisplay, "inline", false);
569 if (element->renderer() && element->renderer()->style().isFloating())
570 element->cssomStyle().setPropertyInternal(CSSPropertyFloat, "none", false);
571 }
572 }
573}
574
575static bool isProhibitedParagraphChild(const AtomicString& name)
576{
577 // https://dvcs.w3.org/hg/editing/raw-file/57abe6d3cb60/editing.html#prohibited-paragraph-child
578 static const auto localNames = makeNeverDestroyed([] {
579 static const HTMLQualifiedName* const tags[] = {
580 &addressTag.get(),
581 &articleTag.get(),
582 &asideTag.get(),
583 &blockquoteTag.get(),
584 &captionTag.get(),
585 &centerTag.get(),
586 &colTag.get(),
587 &colgroupTag.get(),
588 &ddTag.get(),
589 &detailsTag.get(),
590 &dirTag.get(),
591 &divTag.get(),
592 &dlTag.get(),
593 &dtTag.get(),
594 &fieldsetTag.get(),
595 &figcaptionTag.get(),
596 &figureTag.get(),
597 &footerTag.get(),
598 &formTag.get(),
599 &h1Tag.get(),
600 &h2Tag.get(),
601 &h3Tag.get(),
602 &h4Tag.get(),
603 &h5Tag.get(),
604 &h6Tag.get(),
605 &headerTag.get(),
606 &hgroupTag.get(),
607 &hrTag.get(),
608 &liTag.get(),
609 &listingTag.get(),
610 &mainTag.get(), // Missing in the specification.
611 &menuTag.get(),
612 &navTag.get(),
613 &olTag.get(),
614 &pTag.get(),
615 &plaintextTag.get(),
616 &preTag.get(),
617 &sectionTag.get(),
618 &summaryTag.get(),
619 &tableTag.get(),
620 &tbodyTag.get(),
621 &tdTag.get(),
622 &tfootTag.get(),
623 &thTag.get(),
624 &theadTag.get(),
625 &trTag.get(),
626 &ulTag.get(),
627 &xmpTag.get(),
628 };
629 HashSet<AtomicString> set;
630 for (auto& tag : tags)
631 set.add(tag->localName());
632 return set;
633 }());
634 return localNames.get().contains(name);
635}
636
637void ReplaceSelectionCommand::makeInsertedContentRoundTrippableWithHTMLTreeBuilder(InsertedNodes& insertedNodes)
638{
639 RefPtr<Node> pastEndNode = insertedNodes.pastLastLeaf();
640 RefPtr<Node> next;
641 for (RefPtr<Node> node = insertedNodes.firstNodeInserted(); node && node != pastEndNode; node = next) {
642 next = NodeTraversal::next(*node);
643
644 if (!is<HTMLElement>(*node))
645 continue;
646
647 if (isProhibitedParagraphChild(downcast<HTMLElement>(*node).localName())) {
648 if (auto* paragraphElement = enclosingElementWithTag(positionInParentBeforeNode(node.get()), pTag)) {
649 auto* parent = paragraphElement->parentNode();
650 if (parent && parent->hasEditableStyle())
651 moveNodeOutOfAncestor(*node, *paragraphElement, insertedNodes);
652 }
653 }
654
655 if (isHeaderElement(node.get())) {
656 if (auto* headerElement = highestEnclosingNodeOfType(positionInParentBeforeNode(node.get()), isHeaderElement)) {
657 if (headerElement->parentNode() && headerElement->parentNode()->isContentRichlyEditable())
658 moveNodeOutOfAncestor(*node, *headerElement, insertedNodes);
659 else {
660 HTMLElement* newSpanElement = replaceElementWithSpanPreservingChildrenAndAttributes(downcast<HTMLElement>(*node));
661 insertedNodes.didReplaceNode(node.get(), newSpanElement);
662 }
663 }
664 }
665 }
666}
667
668void ReplaceSelectionCommand::moveNodeOutOfAncestor(Node& node, Node& ancestor, InsertedNodes& insertedNodes)
669{
670 Ref<Node> protectedNode = node;
671 Ref<Node> protectedAncestor = ancestor;
672
673 VisiblePosition positionAtEndOfNode = lastPositionInOrAfterNode(&node);
674 VisiblePosition lastPositionInParagraph = lastPositionInNode(&ancestor);
675 if (positionAtEndOfNode == lastPositionInParagraph) {
676 removeNode(node);
677 if (ancestor.nextSibling())
678 insertNodeBefore(WTFMove(protectedNode), *ancestor.nextSibling());
679 else
680 appendNode(WTFMove(protectedNode), *ancestor.parentNode());
681 } else {
682 RefPtr<Node> nodeToSplitTo = splitTreeToNode(node, ancestor, true);
683 removeNode(node);
684 insertNodeBefore(WTFMove(protectedNode), *nodeToSplitTo);
685 }
686 if (!ancestor.firstChild()) {
687 insertedNodes.willRemoveNode(&ancestor);
688 removeNode(ancestor);
689 }
690}
691
692static inline bool hasRenderedText(const Text& text)
693{
694 return text.renderer() && text.renderer()->hasRenderedText();
695}
696
697void ReplaceSelectionCommand::removeUnrenderedTextNodesAtEnds(InsertedNodes& insertedNodes)
698{
699 document().updateLayoutIgnorePendingStylesheets();
700
701 Node* lastLeafInserted = insertedNodes.lastLeafInserted();
702 if (is<Text>(lastLeafInserted) && !hasRenderedText(downcast<Text>(*lastLeafInserted))
703 && !enclosingElementWithTag(firstPositionInOrBeforeNode(lastLeafInserted), selectTag)
704 && !enclosingElementWithTag(firstPositionInOrBeforeNode(lastLeafInserted), scriptTag)) {
705 insertedNodes.willRemoveNode(lastLeafInserted);
706 removeNode(*lastLeafInserted);
707 }
708
709 // We don't have to make sure that firstNodeInserted isn't inside a select or script element
710 // because it is a top level node in the fragment and the user can't insert into those elements.
711 Node* firstNodeInserted = insertedNodes.firstNodeInserted();
712 if (is<Text>(firstNodeInserted) && !hasRenderedText(downcast<Text>(*firstNodeInserted))) {
713 insertedNodes.willRemoveNode(firstNodeInserted);
714 removeNode(*firstNodeInserted);
715 }
716}
717
718VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent() const
719{
720 // FIXME: Why is this hack here? What's special about <select> tags?
721 auto* enclosingSelect = enclosingElementWithTag(m_endOfInsertedContent, selectTag);
722 return enclosingSelect ? lastPositionInOrAfterNode(enclosingSelect) : m_endOfInsertedContent;
723}
724
725VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent() const
726{
727 return m_startOfInsertedContent;
728}
729
730static void removeHeadContents(ReplacementFragment& fragment)
731{
732 if (fragment.isEmpty())
733 return;
734
735 Vector<Element*> toRemove;
736
737 auto it = descendantsOfType<Element>(*fragment.fragment()).begin();
738 auto end = descendantsOfType<Element>(*fragment.fragment()).end();
739 while (it != end) {
740 if (is<HTMLBaseElement>(*it) || is<HTMLLinkElement>(*it) || is<HTMLMetaElement>(*it) || is<HTMLTitleElement>(*it)
741 || (is<HTMLStyleElement>(*it) && it->getAttribute(classAttr) != WebKitMSOListQuirksStyle)) {
742 toRemove.append(&*it);
743 it.traverseNextSkippingChildren();
744 continue;
745 }
746 ++it;
747 }
748
749 for (auto& element : toRemove)
750 fragment.removeNode(*element);
751}
752
753// Remove style spans before insertion if they are unnecessary. It's faster because we'll
754// avoid doing a layout.
755static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const Position& insertionPos)
756{
757 Node* topNode = fragment.firstChild();
758
759 // Handling the case where we are doing Paste as Quotation or pasting into quoted content is more complicated (see handleStyleSpans)
760 // and doesn't receive the optimization.
761 if (isMailPasteAsQuotationNode(topNode) || enclosingNodeOfType(firstPositionInOrBeforeNode(topNode), isMailBlockquote, CanCrossEditingBoundary))
762 return false;
763
764 // Either there are no style spans in the fragment or a WebKit client has added content to the fragment
765 // before inserting it. Look for and handle style spans after insertion.
766 if (!isLegacyAppleStyleSpan(topNode))
767 return false;
768
769 Node* wrappingStyleSpan = topNode;
770 auto styleAtInsertionPos = EditingStyle::create(insertionPos.parentAnchoredEquivalent());
771 String styleText = styleAtInsertionPos->style()->asText();
772
773 // FIXME: This string comparison is a naive way of comparing two styles.
774 // We should be taking the diff and check that the diff is empty.
775 if (styleText != downcast<Element>(*wrappingStyleSpan).getAttribute(styleAttr))
776 return false;
777
778 fragment.removeNodePreservingChildren(*wrappingStyleSpan);
779 return true;
780}
781
782// At copy time, WebKit wraps copied content in a span that contains the source document's
783// default styles. If the copied Range inherits any other styles from its ancestors, we put
784// those styles on a second span.
785// This function removes redundant styles from those spans, and removes the spans if all their
786// styles are redundant.
787// We should remove the Apple-style-span class when we're done, see <rdar://problem/5685600>.
788// We should remove styles from spans that are overridden by all of their children, either here
789// or at copy time.
790void ReplaceSelectionCommand::handleStyleSpans(InsertedNodes& insertedNodes)
791{
792 HTMLElement* wrappingStyleSpan = nullptr;
793 // The style span that contains the source document's default style should be at
794 // the top of the fragment, but Mail sometimes adds a wrapper (for Paste As Quotation),
795 // so search for the top level style span instead of assuming it's at the top.
796 for (Node* node = insertedNodes.firstNodeInserted(); node; node = NodeTraversal::next(*node)) {
797 if (isLegacyAppleStyleSpan(node)) {
798 wrappingStyleSpan = downcast<HTMLElement>(node);
799 break;
800 }
801 }
802
803 // There might not be any style spans if we're pasting from another application or if
804 // we are here because of a document.execCommand("InsertHTML", ...) call.
805 if (!wrappingStyleSpan)
806 return;
807
808 auto style = EditingStyle::create(wrappingStyleSpan->inlineStyle());
809 ContainerNode* context = wrappingStyleSpan->parentNode();
810
811 // If Mail wraps the fragment with a Paste as Quotation blockquote, or if you're pasting into a quoted region,
812 // styles from blockquoteNode are allowed to override those from the source document, see <rdar://problem/4930986> and <rdar://problem/5089327>.
813 Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context : enclosingNodeOfType(firstPositionInNode(context), isMailBlockquote, CanCrossEditingBoundary);
814 if (blockquoteNode)
815 context = document().documentElement();
816
817 // This operation requires that only editing styles to be removed from sourceDocumentStyle.
818 style->prepareToApplyAt(firstPositionInNode(context));
819
820 // Remove block properties in the span's style. This prevents properties that probably have no effect
821 // currently from affecting blocks later if the style is cloned for a new block element during a future
822 // editing operation.
823 // FIXME: They *can* have an effect currently if blocks beneath the style span aren't individually marked
824 // with block styles by the editing engine used to style them. WebKit doesn't do this, but others might.
825 style->removeBlockProperties();
826
827 if (style->isEmpty() || !wrappingStyleSpan->firstChild()) {
828 insertedNodes.willRemoveNodePreservingChildren(wrappingStyleSpan);
829 removeNodePreservingChildren(*wrappingStyleSpan);
830 } else
831 setNodeAttribute(*wrappingStyleSpan, styleAttr, style->style()->asText());
832}
833
834void ReplaceSelectionCommand::mergeEndIfNeeded()
835{
836 if (!m_shouldMergeEnd)
837 return;
838
839 VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent());
840 VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent());
841
842 // Bail to avoid infinite recursion.
843 if (m_movingParagraph) {
844 ASSERT_NOT_REACHED();
845 return;
846 }
847
848 // Merging two paragraphs will destroy the moved one's block styles. Always move the end of inserted forward
849 // to preserve the block style of the paragraph already in the document, unless the paragraph to move would
850 // include the what was the start of the selection that was pasted into, so that we preserve that paragraph's
851 // block styles.
852 bool mergeForward = !(inSameParagraph(startOfInsertedContent, endOfInsertedContent) && !isStartOfParagraph(startOfInsertedContent));
853
854 VisiblePosition destination = mergeForward ? endOfInsertedContent.next() : endOfInsertedContent;
855 VisiblePosition startOfParagraphToMove = mergeForward ? startOfParagraph(endOfInsertedContent) : endOfInsertedContent.next();
856
857 // Merging forward could result in deleting the destination anchor node.
858 // To avoid this, we add a placeholder node before the start of the paragraph.
859 if (endOfParagraph(startOfParagraphToMove) == destination) {
860 auto placeholder = HTMLBRElement::create(document());
861 auto* placeholderPtr = placeholder.ptr();
862 insertNodeBefore(WTFMove(placeholder), *startOfParagraphToMove.deepEquivalent().deprecatedNode());
863 destination = VisiblePosition(positionBeforeNode(placeholderPtr));
864 }
865
866 moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
867
868 // Merging forward will remove m_endOfInsertedContent from the document.
869 if (mergeForward) {
870 if (m_startOfInsertedContent.isOrphan())
871 m_startOfInsertedContent = endingSelection().visibleStart().deepEquivalent();
872 m_endOfInsertedContent = endingSelection().visibleEnd().deepEquivalent();
873 // If we merged text nodes, m_endOfInsertedContent could be null. If this is the case, we use m_startOfInsertedContent.
874 if (m_endOfInsertedContent.isNull())
875 m_endOfInsertedContent = m_startOfInsertedContent;
876 }
877}
878
879static Node* enclosingInline(Node* node)
880{
881 while (ContainerNode* parent = node->parentNode()) {
882 if (isBlockFlowElement(*parent) || parent->hasTagName(bodyTag))
883 return node;
884 // Stop if any previous sibling is a block.
885 for (Node* sibling = node->previousSibling(); sibling; sibling = sibling->previousSibling()) {
886 if (isBlockFlowElement(*sibling))
887 return node;
888 }
889 node = parent;
890 }
891 return node;
892}
893
894static bool isInlineNodeWithStyle(const Node* node)
895{
896 // We don't want to skip over any block elements.
897 if (isBlock(node))
898 return false;
899
900 if (!node->isHTMLElement())
901 return false;
902
903 // We can skip over elements whose class attribute is
904 // one of our internal classes.
905 const HTMLElement* element = static_cast<const HTMLElement*>(node);
906 const AtomicString& classAttributeValue = element->attributeWithoutSynchronization(classAttr);
907 if (classAttributeValue == AppleTabSpanClass
908 || classAttributeValue == AppleConvertedSpace
909 || classAttributeValue == ApplePasteAsQuotation)
910 return true;
911
912 return EditingStyle::elementIsStyledSpanOrHTMLEquivalent(*element);
913}
914
915inline Node* nodeToSplitToAvoidPastingIntoInlineNodesWithStyle(const Position& insertionPos)
916{
917 Node* containgBlock = enclosingBlock(insertionPos.containerNode());
918 return highestEnclosingNodeOfType(insertionPos, isInlineNodeWithStyle, CannotCrossEditingBoundary, containgBlock);
919}
920
921bool ReplaceSelectionCommand::willApplyCommand()
922{
923 ensureReplacementFragment();
924 m_documentFragmentPlainText = m_documentFragment->textContent();
925 m_documentFragmentHTMLMarkup = serializeFragment(*m_documentFragment, SerializedNodes::SubtreeIncludingNode);
926 return CompositeEditCommand::willApplyCommand();
927}
928
929static bool hasBlankLineBetweenParagraphs(Position& position)
930{
931 bool reachedBoundaryStart = false;
932 bool reachedBoundaryEnd = false;
933 VisiblePosition visiblePosition(position);
934 VisiblePosition previousPosition = visiblePosition.previous(CannotCrossEditingBoundary, &reachedBoundaryStart);
935 VisiblePosition nextPosition = visiblePosition.next(CannotCrossEditingBoundary, &reachedBoundaryStart);
936 bool hasLineBeforePosition = isEndOfLine(previousPosition);
937
938 return !reachedBoundaryStart && !reachedBoundaryEnd && isBlankParagraph(visiblePosition) && hasLineBeforePosition && isStartOfLine(nextPosition);
939}
940
941void ReplaceSelectionCommand::doApply()
942{
943 VisibleSelection selection = endingSelection();
944 ASSERT(selection.isCaretOrRange());
945 ASSERT(selection.start().deprecatedNode());
946 if (selection.isNoneOrOrphaned() || !selection.start().deprecatedNode() || !selection.isContentEditable())
947 return;
948
949 // In plain text only regions, we create style-less fragments, so the inserted content will automatically
950 // match the style of the surrounding area and so we can avoid unnecessary work below for m_matchStyle.
951 if (!selection.isContentRichlyEditable())
952 m_matchStyle = false;
953
954 ReplacementFragment& fragment = *ensureReplacementFragment();
955 if (performTrivialReplace(fragment))
956 return;
957
958 // We can skip matching the style if the selection is plain text.
959 if ((selection.start().deprecatedNode()->renderer() && selection.start().deprecatedNode()->renderer()->style().userModify() == UserModify::ReadWritePlaintextOnly)
960 && (selection.end().deprecatedNode()->renderer() && selection.end().deprecatedNode()->renderer()->style().userModify() == UserModify::ReadWritePlaintextOnly))
961 m_matchStyle = false;
962
963 if (m_matchStyle) {
964 m_insertionStyle = EditingStyle::create(selection.start());
965 m_insertionStyle->mergeTypingStyle(document());
966 }
967
968 VisiblePosition visibleStart = selection.visibleStart();
969 VisiblePosition visibleEnd = selection.visibleEnd();
970
971 bool selectionEndWasEndOfParagraph = isEndOfParagraph(visibleEnd);
972 bool selectionStartWasStartOfParagraph = isStartOfParagraph(visibleStart);
973
974 Node* startBlock = enclosingBlock(visibleStart.deepEquivalent().deprecatedNode());
975
976 Position insertionPos = selection.start();
977 bool shouldHandleMailBlockquote = enclosingNodeOfType(insertionPos, isMailBlockquote, CanCrossEditingBoundary) && !m_ignoreMailBlockquote;
978 bool selectionIsPlainText = !selection.isContentRichlyEditable();
979 Element* currentRoot = selection.rootEditableElement();
980
981 if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !shouldHandleMailBlockquote)
982 || startBlock == currentRoot || isListItem(startBlock) || selectionIsPlainText)
983 m_preventNesting = false;
984
985 if (selection.isRange()) {
986 // When the end of the selection being pasted into is at the end of a paragraph, and that selection
987 // spans multiple blocks, not merging may leave an empty line.
988 // When the start of the selection being pasted into is at the start of a block, not merging
989 // will leave hanging block(s).
990 // Merge blocks if the start of the selection was in a Mail blockquote, since we handle
991 // that case specially to prevent nesting.
992 bool mergeBlocksAfterDelete = shouldHandleMailBlockquote || isEndOfParagraph(visibleEnd) || isStartOfBlock(visibleStart);
993 // FIXME: We should only expand to include fully selected special elements if we are copying a
994 // selection and pasting it on top of itself.
995 // FIXME: capturing the content of this delete would allow a replace accessibility notification instead of a simple insert
996 deleteSelection(false, mergeBlocksAfterDelete, true, false, true);
997 visibleStart = endingSelection().visibleStart();
998 if (fragment.hasInterchangeNewlineAtStart()) {
999 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
1000 if (!isEndOfEditableOrNonEditableContent(visibleStart))
1001 setEndingSelection(visibleStart.next());
1002 } else
1003 insertParagraphSeparator();
1004 }
1005 insertionPos = endingSelection().start();
1006 } else {
1007 ASSERT(selection.isCaret());
1008 if (fragment.hasInterchangeNewlineAtStart()) {
1009 VisiblePosition next = visibleStart.next(CannotCrossEditingBoundary);
1010 if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart) && next.isNotNull())
1011 setEndingSelection(next);
1012 else {
1013 insertParagraphSeparator();
1014 visibleStart = endingSelection().visibleStart();
1015 }
1016 }
1017 // We split the current paragraph in two to avoid nesting the blocks from the fragment inside the current block.
1018 // For example paste <div>foo</div><div>bar</div><div>baz</div> into <div>x^x</div>, where ^ is the caret.
1019 // As long as the div styles are the same, visually you'd expect: <div>xbar</div><div>bar</div><div>bazx</div>,
1020 // not <div>xbar<div>bar</div><div>bazx</div></div>.
1021 // Don't do this if the selection started in a Mail blockquote.
1022 if (m_preventNesting && !shouldHandleMailBlockquote && !isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) {
1023 insertParagraphSeparator();
1024 setEndingSelection(endingSelection().visibleStart().previous());
1025 }
1026 insertionPos = endingSelection().start();
1027 }
1028
1029 // We don't want any of the pasted content to end up nested in a Mail blockquote, so first break
1030 // out of any surrounding Mail blockquotes. Unless we're inserting in a table, in which case
1031 // breaking the blockquote will prevent the content from actually being inserted in the table.
1032 if (shouldHandleMailBlockquote && m_preventNesting && !(enclosingNodeOfType(insertionPos, &isTableStructureNode))) {
1033 applyCommandToComposite(BreakBlockquoteCommand::create(document()));
1034 // This will leave a br between the split.
1035 Node* br = endingSelection().start().deprecatedNode();
1036 ASSERT(br->hasTagName(brTag));
1037 // Insert content between the two blockquotes, but remove the br (since it was just a placeholder).
1038 insertionPos = positionInParentBeforeNode(br);
1039 removeNode(*br);
1040 }
1041
1042 // Inserting content could cause whitespace to collapse, e.g. inserting <div>foo</div> into hello^ world.
1043 prepareWhitespaceAtPositionForSplit(insertionPos);
1044
1045 // If the downstream node has been removed there's no point in continuing.
1046 if (!insertionPos.downstream().deprecatedNode())
1047 return;
1048
1049 // NOTE: This would be an incorrect usage of downstream() if downstream() were changed to mean the last position after
1050 // 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
1051 // away, there are positions after the br which map to the same visible position as [br, 0]).
1052 RefPtr<Node> endBR = insertionPos.downstream().deprecatedNode()->hasTagName(brTag) ? insertionPos.downstream().deprecatedNode() : nullptr;
1053 VisiblePosition originalVisPosBeforeEndBR;
1054 if (endBR)
1055 originalVisPosBeforeEndBR = VisiblePosition(positionBeforeNode(endBR.get()), DOWNSTREAM).previous();
1056
1057 RefPtr<Node> insertionBlock = enclosingBlock(insertionPos.deprecatedNode());
1058
1059 // Adjust insertionPos to prevent nesting.
1060 // If the start was in a Mail blockquote, we will have already handled adjusting insertionPos above.
1061 if (m_preventNesting && insertionBlock && !isTableCell(insertionBlock.get()) && !shouldHandleMailBlockquote) {
1062 ASSERT(insertionBlock != currentRoot);
1063 VisiblePosition visibleInsertionPos(insertionPos);
1064 if (isEndOfBlock(visibleInsertionPos) && !(isStartOfBlock(visibleInsertionPos) && fragment.hasInterchangeNewlineAtEnd()))
1065 insertionPos = positionInParentAfterNode(insertionBlock.get());
1066 else if (isStartOfBlock(visibleInsertionPos))
1067 insertionPos = positionInParentBeforeNode(insertionBlock.get());
1068 }
1069
1070 // Paste at start or end of link goes outside of link.
1071 insertionPos = positionAvoidingSpecialElementBoundary(insertionPos);
1072
1073 // FIXME: Can this wait until after the operation has been performed? There doesn't seem to be
1074 // any work performed after this that queries or uses the typing style.
1075 frame().selection().clearTypingStyle();
1076
1077 // We don't want the destination to end up inside nodes that weren't selected. To avoid that, we move the
1078 // position forward without changing the visible position so we're still at the same visible location, but
1079 // outside of preceding tags.
1080 insertionPos = positionAvoidingPrecedingNodes(insertionPos);
1081
1082 // Paste into run of tabs splits the tab span.
1083 insertionPos = positionOutsideTabSpan(insertionPos);
1084
1085 bool hasBlankLinesBetweenParagraphs = hasBlankLineBetweenParagraphs(insertionPos);
1086
1087 bool handledStyleSpans = handleStyleSpansBeforeInsertion(fragment, insertionPos);
1088
1089 // We're finished if there is nothing to add.
1090 if (fragment.isEmpty() || !fragment.firstChild())
1091 return;
1092
1093 // If we are not trying to match the destination style we prefer a position
1094 // that is outside inline elements that provide style.
1095 // This way we can produce a less verbose markup.
1096 // We can skip this optimization for fragments not wrapped in one of
1097 // our style spans and for positions inside list items
1098 // since insertAsListItems already does the right thing.
1099 if (!m_matchStyle && !enclosingList(insertionPos.containerNode())) {
1100 if (insertionPos.containerNode()->isTextNode() && insertionPos.offsetInContainerNode() && !insertionPos.atLastEditingPositionForNode()) {
1101 splitTextNode(*insertionPos.containerText(), insertionPos.offsetInContainerNode());
1102 insertionPos = firstPositionInNode(insertionPos.containerNode());
1103 }
1104
1105 if (RefPtr<Node> nodeToSplitTo = nodeToSplitToAvoidPastingIntoInlineNodesWithStyle(insertionPos)) {
1106 if (insertionPos.containerNode() != nodeToSplitTo->parentNode()) {
1107 Node* splitStart = insertionPos.computeNodeAfterPosition();
1108 if (!splitStart)
1109 splitStart = insertionPos.containerNode();
1110 ASSERT(splitStart);
1111 nodeToSplitTo = splitTreeToNode(*splitStart, *nodeToSplitTo->parentNode()).get();
1112 insertionPos = positionInParentBeforeNode(nodeToSplitTo.get());
1113 }
1114 }
1115 }
1116
1117 // FIXME: When pasting rich content we're often prevented from heading down the fast path by style spans. Try
1118 // again here if they've been removed.
1119
1120 // 1) Insert the content.
1121 // 2) Remove redundant styles and style tags, this inner <b> for example: <b>foo <b>bar</b> baz</b>.
1122 // 3) Merge the start of the added content with the content before the position being pasted into.
1123 // 4) Do one of the following: a) expand the last br if the fragment ends with one and it collapsed,
1124 // b) merge the last paragraph of the incoming fragment with the paragraph that contained the
1125 // end of the selection that was pasted into, or c) handle an interchange newline at the end of the
1126 // incoming fragment.
1127 // 5) Add spaces for smart replace.
1128 // 6) Select the replacement if requested, and match style if requested.
1129
1130 InsertedNodes insertedNodes;
1131 RefPtr<Node> refNode = fragment.firstChild();
1132 RefPtr<Node> node = refNode->nextSibling();
1133
1134 if (refNode)
1135 fragment.removeNode(*refNode);
1136
1137 Node* blockStart = enclosingBlock(insertionPos.deprecatedNode());
1138 bool isInsertingIntoList = (isListHTMLElement(refNode.get()) || (isLegacyAppleStyleSpan(refNode.get()) && isListHTMLElement(refNode->firstChild())))
1139 && blockStart && blockStart->renderer()->isListItem();
1140 if (isInsertingIntoList)
1141 refNode = insertAsListItems(downcast<HTMLElement>(*refNode), blockStart, insertionPos, insertedNodes);
1142 else {
1143 insertNodeAt(*refNode, insertionPos);
1144 insertedNodes.respondToNodeInsertion(refNode.get());
1145 }
1146
1147 // Mutation events (bug 22634) may have already removed the inserted content
1148 if (!refNode->isConnected())
1149 return;
1150
1151 bool plainTextFragment = isPlainTextMarkup(refNode.get());
1152
1153 while (node) {
1154 RefPtr<Node> next = node->nextSibling();
1155 fragment.removeNode(*node);
1156 insertNodeAfter(*node, *refNode);
1157 insertedNodes.respondToNodeInsertion(node.get());
1158
1159 // Mutation events (bug 22634) may have already removed the inserted content
1160 if (!node->isConnected())
1161 return;
1162
1163 refNode = node;
1164 if (node && plainTextFragment)
1165 plainTextFragment = isPlainTextMarkup(node.get());
1166 node = next;
1167 }
1168
1169 if (insertedNodes.isEmpty())
1170 return;
1171 removeUnrenderedTextNodesAtEnds(insertedNodes);
1172
1173 if (!handledStyleSpans)
1174 handleStyleSpans(insertedNodes);
1175
1176 // Mutation events (bug 20161) may have already removed the inserted content
1177 if (insertedNodes.isEmpty())
1178 return;
1179 if (!insertedNodes.firstNodeInserted()->isConnected())
1180 return;
1181
1182 VisiblePosition startOfInsertedContent = firstPositionInOrBeforeNode(insertedNodes.firstNodeInserted());
1183
1184 // We inserted before the insertionBlock to prevent nesting, and the content before the insertionBlock wasn't in its own block and
1185 // didn't have a br after it, so the inserted content ended up in the same paragraph.
1186 if (!startOfInsertedContent.isNull() && insertionBlock && insertionPos.deprecatedNode() == insertionBlock->parentNode() && (unsigned)insertionPos.deprecatedEditingOffset() < insertionBlock->computeNodeIndex() && !isStartOfParagraph(startOfInsertedContent))
1187 insertNodeAt(HTMLBRElement::create(document()), startOfInsertedContent.deepEquivalent());
1188
1189 if (endBR && (plainTextFragment || shouldRemoveEndBR(endBR.get(), originalVisPosBeforeEndBR))) {
1190 RefPtr<Node> parent = endBR->parentNode();
1191 insertedNodes.willRemoveNode(endBR.get());
1192 removeNode(*endBR);
1193 if (Node* nodeToRemove = highestNodeToRemoveInPruning(parent.get())) {
1194 insertedNodes.willRemoveNode(nodeToRemove);
1195 removeNode(*nodeToRemove);
1196 }
1197 }
1198
1199 makeInsertedContentRoundTrippableWithHTMLTreeBuilder(insertedNodes);
1200 if (insertedNodes.isEmpty())
1201 return;
1202
1203 removeRedundantStylesAndKeepStyleSpanInline(insertedNodes);
1204 if (insertedNodes.isEmpty())
1205 return;
1206
1207 if (m_sanitizeFragment)
1208 applyCommandToComposite(SimplifyMarkupCommand::create(document(), insertedNodes.firstNodeInserted(), insertedNodes.pastLastLeaf()));
1209
1210 // Setup m_startOfInsertedContent and m_endOfInsertedContent. This should be the last two lines of code that access insertedNodes.
1211 m_startOfInsertedContent = firstPositionInOrBeforeNode(insertedNodes.firstNodeInserted());
1212 m_endOfInsertedContent = lastPositionInOrAfterNode(insertedNodes.lastLeafInserted());
1213
1214 // Determine whether or not we should merge the end of inserted content with what's after it before we do
1215 // the start merge so that the start merge doesn't effect our decision.
1216 m_shouldMergeEnd = shouldMergeEnd(selectionEndWasEndOfParagraph);
1217
1218 if (shouldMergeStart(selectionStartWasStartOfParagraph, fragment.hasInterchangeNewlineAtStart(), shouldHandleMailBlockquote)) {
1219 VisiblePosition startOfParagraphToMove = positionAtStartOfInsertedContent();
1220 VisiblePosition destination = startOfParagraphToMove.previous();
1221 // We need to handle the case where we need to merge the end
1222 // but our destination node is inside an inline that is the last in the block.
1223 // We insert a placeholder before the newly inserted content to avoid being merged into the inline.
1224 Node* destinationNode = destination.deepEquivalent().deprecatedNode();
1225 if (m_shouldMergeEnd && destinationNode != enclosingInline(destinationNode) && enclosingInline(destinationNode)->nextSibling())
1226 insertNodeBefore(HTMLBRElement::create(document()), *refNode);
1227
1228 // Merging the first paragraph of inserted content with the content that came
1229 // before the selection that was pasted into would also move content after
1230 // the selection that was pasted into if: only one paragraph was being pasted,
1231 // and it was not wrapped in a block, the selection that was pasted into ended
1232 // at the end of a block and the next paragraph didn't start at the start of a block.
1233 // Insert a line break just after the inserted content to separate it from what
1234 // comes after and prevent that from happening.
1235 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
1236 if (startOfParagraph(endOfInsertedContent) == startOfParagraphToMove) {
1237 insertNodeAt(HTMLBRElement::create(document()), endOfInsertedContent.deepEquivalent());
1238 // Mutation events (bug 22634) triggered by inserting the <br> might have removed the content we're about to move
1239 if (!startOfParagraphToMove.deepEquivalent().anchorNode()->isConnected())
1240 return;
1241 }
1242
1243 // FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes. The nodes are
1244 // only ever used to create positions where inserted content starts/ends.
1245 moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination);
1246 m_startOfInsertedContent = endingSelection().visibleStart().deepEquivalent().downstream();
1247 if (m_endOfInsertedContent.isOrphan())
1248 m_endOfInsertedContent = endingSelection().visibleEnd().deepEquivalent().upstream();
1249 }
1250
1251 Position lastPositionToSelect;
1252 if (fragment.hasInterchangeNewlineAtEnd()) {
1253 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
1254 VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary);
1255
1256 if (selectionEndWasEndOfParagraph || !isEndOfParagraph(endOfInsertedContent) || next.isNull()) {
1257 if (!isStartOfParagraph(endOfInsertedContent)) {
1258 setEndingSelection(endOfInsertedContent);
1259 Node* enclosingNode = enclosingBlock(endOfInsertedContent.deepEquivalent().deprecatedNode());
1260 if (isListItem(enclosingNode)) {
1261 auto newListItem = HTMLLIElement::create(document());
1262 insertNodeAfter(newListItem.copyRef(), *enclosingNode);
1263 setEndingSelection(VisiblePosition(firstPositionInNode(newListItem.ptr())));
1264 } else {
1265 // Use a default paragraph element (a plain div) for the empty paragraph, using the last paragraph
1266 // block's style seems to annoy users.
1267 insertParagraphSeparator(true, !shouldHandleMailBlockquote && highestEnclosingNodeOfType(endOfInsertedContent.deepEquivalent(),
1268 isMailBlockquote, CannotCrossEditingBoundary, insertedNodes.firstNodeInserted()->parentNode()));
1269 }
1270
1271 // Select up to the paragraph separator that was added.
1272 lastPositionToSelect = endingSelection().visibleStart().deepEquivalent();
1273 updateNodesInserted(lastPositionToSelect.deprecatedNode());
1274 }
1275 } else {
1276 // Select up to the beginning of the next paragraph.
1277 lastPositionToSelect = next.deepEquivalent().downstream();
1278 }
1279
1280 } else
1281 mergeEndIfNeeded();
1282
1283 if (Node* mailBlockquote = enclosingNodeOfType(positionAtStartOfInsertedContent().deepEquivalent(), isMailPasteAsQuotationNode))
1284 removeNodeAttribute(downcast<Element>(*mailBlockquote), classAttr);
1285
1286 if (shouldPerformSmartReplace())
1287 addSpacesForSmartReplace();
1288
1289 if (!isInsertingIntoList && hasBlankLinesBetweenParagraphs && shouldPerformSmartParagraphReplace())
1290 addNewLinesForSmartReplace();
1291
1292 // If we are dealing with a fragment created from plain text
1293 // no style matching is necessary.
1294 if (plainTextFragment)
1295 m_matchStyle = false;
1296
1297 completeHTMLReplacement(lastPositionToSelect);
1298}
1299
1300String ReplaceSelectionCommand::inputEventData() const
1301{
1302 if (isEditingTextAreaOrTextInput())
1303 return m_documentFragment->textContent();
1304
1305 return CompositeEditCommand::inputEventData();
1306}
1307
1308RefPtr<DataTransfer> ReplaceSelectionCommand::inputEventDataTransfer() const
1309{
1310 if (isEditingTextAreaOrTextInput())
1311 return CompositeEditCommand::inputEventDataTransfer();
1312
1313 return DataTransfer::createForInputEvent(m_documentFragmentPlainText, m_documentFragmentHTMLMarkup);
1314}
1315
1316bool ReplaceSelectionCommand::shouldRemoveEndBR(Node* endBR, const VisiblePosition& originalVisPosBeforeEndBR)
1317{
1318 if (!endBR || !endBR->isConnected())
1319 return false;
1320
1321 VisiblePosition visiblePos(positionBeforeNode(endBR));
1322
1323 // Don't remove the br if nothing was inserted.
1324 if (visiblePos.previous() == originalVisPosBeforeEndBR)
1325 return false;
1326
1327 // Remove the br if it is collapsed away and so is unnecessary.
1328 if (!document().inNoQuirksMode() && isEndOfBlock(visiblePos) && !isStartOfParagraph(visiblePos))
1329 return true;
1330
1331 // A br that was originally holding a line open should be displaced by inserted content or turned into a line break.
1332 // A br that was originally acting as a line break should still be acting as a line break, not as a placeholder.
1333 return isStartOfParagraph(visiblePos) && isEndOfParagraph(visiblePos);
1334}
1335
1336bool ReplaceSelectionCommand::shouldPerformSmartReplace() const
1337{
1338 if (!m_smartReplace)
1339 return false;
1340
1341 Element* textControl = enclosingTextFormControl(positionAtStartOfInsertedContent().deepEquivalent());
1342 if (is<HTMLInputElement>(textControl) && downcast<HTMLInputElement>(*textControl).isPasswordField())
1343 return false; // Disable smart replace for password fields.
1344
1345 return true;
1346}
1347
1348bool ReplaceSelectionCommand::shouldPerformSmartParagraphReplace() const
1349{
1350 if (!m_smartReplace)
1351 return false;
1352
1353 if (!document().editingBehavior().shouldSmartInsertDeleteParagraphs())
1354 return false;
1355
1356 return true;
1357}
1358
1359static bool isCharacterSmartReplaceExemptConsideringNonBreakingSpace(UChar32 character, bool previousCharacter)
1360{
1361 return isCharacterSmartReplaceExempt(character == noBreakSpace ? ' ' : character, previousCharacter);
1362}
1363
1364void ReplaceSelectionCommand::addNewLinesForSmartReplace()
1365{
1366 VisiblePosition startOfInsertedContent = positionAtStartOfInsertedContent();
1367 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
1368
1369 bool isPastedContentEntireParagraphs = isStartOfParagraph(startOfInsertedContent) && isEndOfParagraph(endOfInsertedContent);
1370
1371 // If we aren't pasting a paragraph, no need to attempt to insert newlines.
1372 if (!isPastedContentEntireParagraphs)
1373 return;
1374
1375 bool reachedBoundaryStart = false;
1376 bool reachedBoundaryEnd = false;
1377 VisiblePosition positionBeforeStart = startOfInsertedContent.previous(CannotCrossEditingBoundary, &reachedBoundaryStart);
1378 VisiblePosition positionAfterEnd = endOfInsertedContent.next(CannotCrossEditingBoundary, &reachedBoundaryEnd);
1379
1380 if (!reachedBoundaryStart && !reachedBoundaryEnd) {
1381 if (!isBlankParagraph(positionBeforeStart) && !isBlankParagraph(startOfInsertedContent) && isEndOfLine(positionBeforeStart) && !isEndOfEditableOrNonEditableContent(positionAfterEnd) && !isEndOfEditableOrNonEditableContent(endOfInsertedContent)) {
1382 setEndingSelection(startOfInsertedContent);
1383 insertParagraphSeparator();
1384 auto newStart = endingSelection().visibleStart().previous(CannotCrossEditingBoundary, &reachedBoundaryStart);
1385 if (!reachedBoundaryStart)
1386 m_startOfInsertedContent = newStart.deepEquivalent();
1387 }
1388 }
1389
1390 reachedBoundaryStart = false;
1391 reachedBoundaryEnd = false;
1392 positionAfterEnd = endOfInsertedContent.next(CannotCrossEditingBoundary, &reachedBoundaryEnd);
1393 positionBeforeStart = startOfInsertedContent.previous(CannotCrossEditingBoundary, &reachedBoundaryStart);
1394
1395 if (!reachedBoundaryEnd && !reachedBoundaryStart) {
1396 if (!isBlankParagraph(positionAfterEnd) && !isBlankParagraph(endOfInsertedContent) && isStartOfLine(positionAfterEnd) && !isEndOfLine(positionAfterEnd) && !isEndOfEditableOrNonEditableContent(positionAfterEnd)) {
1397 setEndingSelection(endOfInsertedContent);
1398 insertParagraphSeparator();
1399 m_endOfInsertedContent = endingSelection().start();
1400 }
1401 }
1402}
1403
1404void ReplaceSelectionCommand::addSpacesForSmartReplace()
1405{
1406 VisiblePosition startOfInsertedContent = positionAtStartOfInsertedContent();
1407 VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent();
1408
1409 Position endUpstream = endOfInsertedContent.deepEquivalent().upstream();
1410 Node* endNode = endUpstream.computeNodeBeforePosition();
1411 int endOffset = is<Text>(endNode) ? downcast<Text>(*endNode).length() : 0;
1412 if (endUpstream.anchorType() == Position::PositionIsOffsetInAnchor) {
1413 endNode = endUpstream.containerNode();
1414 endOffset = endUpstream.offsetInContainerNode();
1415 }
1416
1417 bool needsTrailingSpace = !isEndOfParagraph(endOfInsertedContent) && !isStartOfParagraph(endOfInsertedContent) && !isCharacterSmartReplaceExemptConsideringNonBreakingSpace(endOfInsertedContent.characterAfter(), false);
1418 if (needsTrailingSpace && endNode) {
1419 bool collapseWhiteSpace = !endNode->renderer() || endNode->renderer()->style().collapseWhiteSpace();
1420 if (is<Text>(*endNode)) {
1421 insertTextIntoNode(downcast<Text>(*endNode), endOffset, collapseWhiteSpace ? nonBreakingSpaceString() : " ");
1422 if (m_endOfInsertedContent.containerNode() == endNode)
1423 m_endOfInsertedContent.moveToOffset(m_endOfInsertedContent.offsetInContainerNode() + 1);
1424 } else {
1425 auto node = document().createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
1426 insertNodeAfter(node.copyRef(), *endNode);
1427 updateNodesInserted(node.ptr());
1428 }
1429 }
1430
1431 document().updateLayout();
1432
1433 Position startDownstream = startOfInsertedContent.deepEquivalent().downstream();
1434 Node* startNode = startDownstream.computeNodeAfterPosition();
1435 unsigned startOffset = 0;
1436 if (startDownstream.anchorType() == Position::PositionIsOffsetInAnchor) {
1437 startNode = startDownstream.containerNode();
1438 startOffset = startDownstream.offsetInContainerNode();
1439 }
1440
1441 bool needsLeadingSpace = !isStartOfParagraph(startOfInsertedContent) && !isEndOfParagraph(startOfInsertedContent) && !isCharacterSmartReplaceExemptConsideringNonBreakingSpace(startOfInsertedContent.previous().characterAfter(), true);
1442 if (needsLeadingSpace && startNode) {
1443 bool collapseWhiteSpace = !startNode->renderer() || startNode->renderer()->style().collapseWhiteSpace();
1444 if (is<Text>(*startNode)) {
1445 insertTextIntoNode(downcast<Text>(*startNode), startOffset, collapseWhiteSpace ? nonBreakingSpaceString() : " ");
1446 if (m_endOfInsertedContent.containerNode() == startNode && m_endOfInsertedContent.offsetInContainerNode())
1447 m_endOfInsertedContent.moveToOffset(m_endOfInsertedContent.offsetInContainerNode() + 1);
1448 } else {
1449 auto node = document().createEditingTextNode(collapseWhiteSpace ? nonBreakingSpaceString() : " ");
1450 auto* nodePtr = node.ptr();
1451 // Don't updateNodesInserted. Doing so would set m_endOfInsertedContent to be the node containing the leading space,
1452 // but m_endOfInsertedContent is supposed to mark the end of pasted content.
1453 insertNodeBefore(WTFMove(node), *startNode);
1454 m_startOfInsertedContent = firstPositionInNode(nodePtr);
1455 }
1456 }
1457}
1458
1459void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositionToSelect)
1460{
1461 Position start = positionAtStartOfInsertedContent().deepEquivalent();
1462 Position end = positionAtEndOfInsertedContent().deepEquivalent();
1463
1464 // Mutation events may have deleted start or end
1465 if (start.isNotNull() && !start.isOrphan() && end.isNotNull() && !end.isOrphan()) {
1466 // FIXME (11475): Remove this and require that the creator of the fragment to use nbsps.
1467 rebalanceWhitespaceAt(start);
1468 rebalanceWhitespaceAt(end);
1469
1470 if (m_matchStyle) {
1471 ASSERT(m_insertionStyle);
1472 applyStyle(m_insertionStyle.get(), start, end);
1473 }
1474
1475 if (lastPositionToSelect.isNotNull())
1476 end = lastPositionToSelect;
1477
1478 mergeTextNodesAroundPosition(start, end);
1479 mergeTextNodesAroundPosition(end, start);
1480 } else if (lastPositionToSelect.isNotNull())
1481 start = end = lastPositionToSelect;
1482 else
1483 return;
1484
1485 if (AXObjectCache::accessibilityEnabled() && editingAction() == EditAction::Paste)
1486 m_visibleSelectionForInsertedText = VisibleSelection(start, end);
1487
1488 if (m_selectReplacement)
1489 setEndingSelection(VisibleSelection(start, end, SEL_DEFAULT_AFFINITY, endingSelection().isDirectional()));
1490 else
1491 setEndingSelection(VisibleSelection(end, SEL_DEFAULT_AFFINITY, endingSelection().isDirectional()));
1492}
1493
1494void ReplaceSelectionCommand::mergeTextNodesAroundPosition(Position& position, Position& positionOnlyToBeUpdated)
1495{
1496 bool positionIsOffsetInAnchor = position.anchorType() == Position::PositionIsOffsetInAnchor;
1497 bool positionOnlyToBeUpdatedIsOffsetInAnchor = positionOnlyToBeUpdated.anchorType() == Position::PositionIsOffsetInAnchor;
1498 RefPtr<Text> text;
1499 if (positionIsOffsetInAnchor && is<Text>(position.containerNode()))
1500 text = downcast<Text>(position.containerNode());
1501 else {
1502 Node* before = position.computeNodeBeforePosition();
1503 if (is<Text>(before))
1504 text = downcast<Text>(before);
1505 else {
1506 Node* after = position.computeNodeAfterPosition();
1507 if (is<Text>(after))
1508 text = downcast<Text>(after);
1509 }
1510 }
1511 if (!text)
1512 return;
1513
1514 if (is<Text>(text->previousSibling())) {
1515 Ref<Text> previous(downcast<Text>(*text->previousSibling()));
1516 insertTextIntoNode(*text, 0, previous->data());
1517
1518 if (positionIsOffsetInAnchor)
1519 position.moveToOffset(previous->length() + position.offsetInContainerNode());
1520 else
1521 updatePositionForNodeRemoval(position, previous.get());
1522
1523 if (positionOnlyToBeUpdatedIsOffsetInAnchor) {
1524 if (positionOnlyToBeUpdated.containerNode() == text)
1525 positionOnlyToBeUpdated.moveToOffset(previous->length() + positionOnlyToBeUpdated.offsetInContainerNode());
1526 else if (positionOnlyToBeUpdated.containerNode() == previous.ptr())
1527 positionOnlyToBeUpdated.moveToPosition(text.get(), positionOnlyToBeUpdated.offsetInContainerNode());
1528 } else
1529 updatePositionForNodeRemoval(positionOnlyToBeUpdated, previous.get());
1530
1531 removeNode(previous);
1532 }
1533 if (is<Text>(text->nextSibling())) {
1534 Ref<Text> next(downcast<Text>(*text->nextSibling()));
1535 unsigned originalLength = text->length();
1536 insertTextIntoNode(*text, originalLength, next->data());
1537
1538 if (!positionIsOffsetInAnchor)
1539 updatePositionForNodeRemoval(position, next.get());
1540
1541 if (positionOnlyToBeUpdatedIsOffsetInAnchor && positionOnlyToBeUpdated.containerNode() == next.ptr())
1542 positionOnlyToBeUpdated.moveToPosition(text.get(), originalLength + positionOnlyToBeUpdated.offsetInContainerNode());
1543 else
1544 updatePositionForNodeRemoval(positionOnlyToBeUpdated, next.get());
1545
1546 removeNode(next);
1547 }
1548}
1549
1550static HTMLElement* singleChildList(HTMLElement& element)
1551{
1552 if (!element.hasOneChild())
1553 return nullptr;
1554
1555 auto& child = *element.firstChild();
1556 return isListHTMLElement(&child) ? &downcast<HTMLElement>(child) : nullptr;
1557}
1558
1559static HTMLElement& deepestSingleChildList(HTMLElement& topLevelList)
1560{
1561 auto* list = &topLevelList;
1562 while (auto* childList = singleChildList(*list))
1563 list = childList;
1564 return *list;
1565}
1566
1567// If the user is inserting a list into an existing list, instead of nesting the list,
1568// we put the list items into the existing list.
1569Node* ReplaceSelectionCommand::insertAsListItems(HTMLElement& passedListElement, Node* insertionBlock, const Position& insertPos, InsertedNodes& insertedNodes)
1570{
1571 Ref<HTMLElement> listElement = deepestSingleChildList(passedListElement);
1572
1573 bool isStart = isStartOfParagraph(insertPos);
1574 bool isEnd = isEndOfParagraph(insertPos);
1575 bool isMiddle = !isStart && !isEnd;
1576 Node* lastNode = insertionBlock;
1577
1578 // If we're in the middle of a list item, we should split it into two separate
1579 // list items and insert these nodes between them.
1580 if (isMiddle) {
1581 int textNodeOffset = insertPos.offsetInContainerNode();
1582 if (is<Text>(*insertPos.deprecatedNode()) && textNodeOffset > 0)
1583 splitTextNode(downcast<Text>(*insertPos.deprecatedNode()), textNodeOffset);
1584 splitTreeToNode(*insertPos.deprecatedNode(), *lastNode, true);
1585 }
1586
1587 while (RefPtr<Node> listItem = listElement->firstChild()) {
1588 listElement->removeChild(*listItem);
1589 if (isStart || isMiddle) {
1590 insertNodeBefore(*listItem, *lastNode);
1591 insertedNodes.respondToNodeInsertion(listItem.get());
1592 } else if (isEnd) {
1593 insertNodeAfter(*listItem, *lastNode);
1594 insertedNodes.respondToNodeInsertion(listItem.get());
1595 lastNode = listItem.get();
1596 } else
1597 ASSERT_NOT_REACHED();
1598 }
1599 if ((isStart || isMiddle) && lastNode->previousSibling())
1600 lastNode = lastNode->previousSibling();
1601 return lastNode;
1602}
1603
1604void ReplaceSelectionCommand::updateNodesInserted(Node *node)
1605{
1606 if (!node)
1607 return;
1608
1609 if (m_startOfInsertedContent.isNull())
1610 m_startOfInsertedContent = firstPositionInOrBeforeNode(node);
1611
1612 m_endOfInsertedContent = lastPositionInOrAfterNode(node->lastDescendant());
1613}
1614
1615ReplacementFragment* ReplaceSelectionCommand::ensureReplacementFragment()
1616{
1617 if (!m_replacementFragment) {
1618 m_replacementFragment = std::make_unique<ReplacementFragment>(document(), m_documentFragment.get(), endingSelection());
1619 removeHeadContents(*m_replacementFragment);
1620 }
1621
1622 return m_replacementFragment.get();
1623}
1624
1625// During simple pastes, where we're just pasting a text node into a run of text, we insert the text node
1626// directly into the text node that holds the selection. This is much faster than the generalized code in
1627// ReplaceSelectionCommand, and works around <https://bugs.webkit.org/show_bug.cgi?id=6148> since we don't
1628// split text nodes.
1629bool ReplaceSelectionCommand::performTrivialReplace(const ReplacementFragment& fragment)
1630{
1631 if (!is<Text>(fragment.firstChild()) || fragment.firstChild() != fragment.lastChild())
1632 return false;
1633
1634 // FIXME: Would be nice to handle smart replace in the fast path.
1635 if (m_smartReplace || fragment.hasInterchangeNewlineAtStart() || fragment.hasInterchangeNewlineAtEnd())
1636 return false;
1637
1638 // e.g. when "bar" is inserted after "foo" in <div><u>foo</u></div>, "bar" should not be underlined.
1639 if (nodeToSplitToAvoidPastingIntoInlineNodesWithStyle(endingSelection().start()))
1640 return false;
1641
1642 RefPtr<Node> nodeAfterInsertionPos = endingSelection().end().downstream().anchorNode();
1643 Text& textNode = downcast<Text>(*fragment.firstChild());
1644 // Our fragment creation code handles tabs, spaces, and newlines, so we don't have to worry about those here.
1645
1646 Position start = endingSelection().start();
1647 Position end = replaceSelectedTextInNode(textNode.data());
1648 if (end.isNull())
1649 return false;
1650
1651 if (nodeAfterInsertionPos && nodeAfterInsertionPos->parentNode() && nodeAfterInsertionPos->hasTagName(brTag)
1652 && shouldRemoveEndBR(nodeAfterInsertionPos.get(), positionBeforeNode(nodeAfterInsertionPos.get())))
1653 removeNodeAndPruneAncestors(*nodeAfterInsertionPos);
1654
1655 VisibleSelection selectionAfterReplace(m_selectReplacement ? start : end, end);
1656
1657 if (AXObjectCache::accessibilityEnabled() && editingAction() == EditAction::Paste)
1658 m_visibleSelectionForInsertedText = VisibleSelection(start, end);
1659
1660 setEndingSelection(selectionAfterReplace);
1661
1662 return true;
1663}
1664
1665} // namespace WebCore
1666