Home | History | Annotate | Download | only in editing
      1 /*
      2  * Copyright (C) 2005, 2006 Apple Computer, Inc.  All rights reserved.
      3  *
      4  * Redistribution and use in source and binary forms, with or without
      5  * modification, are permitted provided that the following conditions
      6  * are met:
      7  * 1. Redistributions of source code must retain the above copyright
      8  *    notice, this list of conditions and the following disclaimer.
      9  * 2. Redistributions in binary form must reproduce the above copyright
     10  *    notice, this list of conditions and the following disclaimer in the
     11  *    documentation and/or other materials provided with the distribution.
     12  *
     13  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     24  */
     25 
     26 #include "config.h"
     27 #include "core/editing/InsertParagraphSeparatorCommand.h"
     28 
     29 #include "core/HTMLNames.h"
     30 #include "core/dom/Document.h"
     31 #include "core/dom/NodeTraversal.h"
     32 #include "core/dom/Text.h"
     33 #include "core/editing/EditingStyle.h"
     34 #include "core/editing/InsertLineBreakCommand.h"
     35 #include "core/editing/VisibleUnits.h"
     36 #include "core/editing/htmlediting.h"
     37 #include "core/html/HTMLElement.h"
     38 #include "core/rendering/RenderObject.h"
     39 
     40 namespace WebCore {
     41 
     42 using namespace HTMLNames;
     43 
     44 // When inserting a new line, we want to avoid nesting empty divs if we can.  Otherwise, when
     45 // pasting, it's easy to have each new line be a div deeper than the previous.  E.g., in the case
     46 // below, we want to insert at ^ instead of |.
     47 // <div>foo<div>bar</div>|</div>^
     48 static Element* highestVisuallyEquivalentDivBelowRoot(Element* startBlock)
     49 {
     50     Element* curBlock = startBlock;
     51     // We don't want to return a root node (if it happens to be a div, e.g., in a document fragment) because there are no
     52     // siblings for us to append to.
     53     while (!curBlock->nextSibling() && isHTMLDivElement(*curBlock->parentElement()) && curBlock->parentElement()->parentElement()) {
     54         if (curBlock->parentElement()->hasAttributes())
     55             break;
     56         curBlock = curBlock->parentElement();
     57     }
     58     return curBlock;
     59 }
     60 
     61 InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(Document& document, bool mustUseDefaultParagraphElement, bool pasteBlockqutoeIntoUnquotedArea)
     62     : CompositeEditCommand(document)
     63     , m_mustUseDefaultParagraphElement(mustUseDefaultParagraphElement)
     64     , m_pasteBlockqutoeIntoUnquotedArea(pasteBlockqutoeIntoUnquotedArea)
     65 {
     66 }
     67 
     68 bool InsertParagraphSeparatorCommand::preservesTypingStyle() const
     69 {
     70     return true;
     71 }
     72 
     73 void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos)
     74 {
     75     // It is only important to set a style to apply later if we're at the boundaries of
     76     // a paragraph. Otherwise, content that is moved as part of the work of the command
     77     // will lend their styles to the new paragraph without any extra work needed.
     78     VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
     79     if (!isStartOfParagraph(visiblePos) && !isEndOfParagraph(visiblePos))
     80         return;
     81 
     82     ASSERT(pos.isNotNull());
     83     m_style = EditingStyle::create(pos);
     84     m_style->mergeTypingStyle(pos.document());
     85 }
     86 
     87 void InsertParagraphSeparatorCommand::applyStyleAfterInsertion(Node* originalEnclosingBlock)
     88 {
     89     // Not only do we break out of header tags, but we also do not preserve the typing style,
     90     // in order to match other browsers.
     91     if (originalEnclosingBlock->hasTagName(h1Tag) ||
     92         originalEnclosingBlock->hasTagName(h2Tag) ||
     93         originalEnclosingBlock->hasTagName(h3Tag) ||
     94         originalEnclosingBlock->hasTagName(h4Tag) ||
     95         originalEnclosingBlock->hasTagName(h5Tag))
     96         return;
     97 
     98     if (!m_style)
     99         return;
    100 
    101     m_style->prepareToApplyAt(endingSelection().start());
    102     if (!m_style->isEmpty())
    103         applyStyle(m_style.get());
    104 }
    105 
    106 bool InsertParagraphSeparatorCommand::shouldUseDefaultParagraphElement(Node* enclosingBlock) const
    107 {
    108     if (m_mustUseDefaultParagraphElement)
    109         return true;
    110 
    111     // Assumes that if there was a range selection, it was already deleted.
    112     if (!isEndOfBlock(endingSelection().visibleStart()))
    113         return false;
    114 
    115     return enclosingBlock->hasTagName(h1Tag) ||
    116            enclosingBlock->hasTagName(h2Tag) ||
    117            enclosingBlock->hasTagName(h3Tag) ||
    118            enclosingBlock->hasTagName(h4Tag) ||
    119            enclosingBlock->hasTagName(h5Tag);
    120 }
    121 
    122 void InsertParagraphSeparatorCommand::getAncestorsInsideBlock(const Node* insertionNode, Element* outerBlock, WillBeHeapVector<RefPtrWillBeMember<Element> >& ancestors)
    123 {
    124     ancestors.clear();
    125 
    126     // Build up list of ancestors elements between the insertion node and the outer block.
    127     if (insertionNode != outerBlock) {
    128         for (Element* n = insertionNode->parentElement(); n && n != outerBlock; n = n->parentElement())
    129             ancestors.append(n);
    130     }
    131 }
    132 
    133 PassRefPtrWillBeRawPtr<Element> InsertParagraphSeparatorCommand::cloneHierarchyUnderNewBlock(const WillBeHeapVector<RefPtrWillBeMember<Element> >& ancestors, PassRefPtrWillBeRawPtr<Element> blockToInsert)
    134 {
    135     // Make clones of ancestors in between the start node and the start block.
    136     RefPtrWillBeRawPtr<Element> parent = blockToInsert;
    137     for (size_t i = ancestors.size(); i != 0; --i) {
    138         RefPtrWillBeRawPtr<Element> child = ancestors[i - 1]->cloneElementWithoutChildren();
    139         // It should always be okay to remove id from the cloned elements, since the originals are not deleted.
    140         child->removeAttribute(idAttr);
    141         appendNode(child, parent);
    142         parent = child.release();
    143     }
    144 
    145     return parent.release();
    146 }
    147 
    148 void InsertParagraphSeparatorCommand::doApply()
    149 {
    150     if (!endingSelection().isNonOrphanedCaretOrRange())
    151         return;
    152 
    153     Position insertionPosition = endingSelection().start();
    154 
    155     EAffinity affinity = endingSelection().affinity();
    156 
    157     // Delete the current selection.
    158     if (endingSelection().isRange()) {
    159         calculateStyleBeforeInsertion(insertionPosition);
    160         deleteSelection(false, true);
    161         insertionPosition = endingSelection().start();
    162         affinity = endingSelection().affinity();
    163     }
    164 
    165     // FIXME: The parentAnchoredEquivalent conversion needs to be moved into enclosingBlock.
    166     RefPtrWillBeRawPtr<Element> startBlock = enclosingBlock(insertionPosition.parentAnchoredEquivalent().containerNode());
    167     Node* listChildNode = enclosingListChild(insertionPosition.parentAnchoredEquivalent().containerNode());
    168     RefPtrWillBeRawPtr<Element> listChild = listChildNode && listChildNode->isHTMLElement() ? toHTMLElement(listChildNode) : 0;
    169     Position canonicalPos = VisiblePosition(insertionPosition).deepEquivalent();
    170     if (!startBlock
    171         || !startBlock->nonShadowBoundaryParentNode()
    172         || isTableCell(startBlock.get())
    173         || isHTMLFormElement(*startBlock)
    174         // FIXME: If the node is hidden, we don't have a canonical position so we will do the wrong thing for tables and <hr>. https://bugs.webkit.org/show_bug.cgi?id=40342
    175         || (!canonicalPos.isNull() && isRenderedTable(canonicalPos.deprecatedNode()))
    176         || (!canonicalPos.isNull() && isHTMLHRElement(*canonicalPos.deprecatedNode()))) {
    177         applyCommandToComposite(InsertLineBreakCommand::create(document()));
    178         return;
    179     }
    180 
    181     // Use the leftmost candidate.
    182     insertionPosition = insertionPosition.upstream();
    183     if (!insertionPosition.isCandidate())
    184         insertionPosition = insertionPosition.downstream();
    185 
    186     // Adjust the insertion position after the delete
    187     insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition);
    188     VisiblePosition visiblePos(insertionPosition, affinity);
    189     calculateStyleBeforeInsertion(insertionPosition);
    190 
    191     //---------------------------------------------------------------------
    192     // Handle special case of typing return on an empty list item
    193     if (breakOutOfEmptyListItem())
    194         return;
    195 
    196     //---------------------------------------------------------------------
    197     // Prepare for more general cases.
    198 
    199     bool isFirstInBlock = isStartOfBlock(visiblePos);
    200     bool isLastInBlock = isEndOfBlock(visiblePos);
    201     bool nestNewBlock = false;
    202 
    203     // Create block to be inserted.
    204     RefPtrWillBeRawPtr<Element> blockToInsert = nullptr;
    205     if (startBlock->isRootEditableElement()) {
    206         blockToInsert = createDefaultParagraphElement(document());
    207         nestNewBlock = true;
    208     } else if (shouldUseDefaultParagraphElement(startBlock.get())) {
    209         blockToInsert = createDefaultParagraphElement(document());
    210     } else {
    211         blockToInsert = startBlock->cloneElementWithoutChildren();
    212     }
    213 
    214     //---------------------------------------------------------------------
    215     // Handle case when position is in the last visible position in its block,
    216     // including when the block is empty.
    217     if (isLastInBlock) {
    218         if (nestNewBlock) {
    219             if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) {
    220                 // The block is empty.  Create an empty block to
    221                 // represent the paragraph that we're leaving.
    222                 RefPtrWillBeRawPtr<Element> extraBlock = createDefaultParagraphElement(document());
    223                 appendNode(extraBlock, startBlock);
    224                 appendBlockPlaceholder(extraBlock);
    225             }
    226             appendNode(blockToInsert, startBlock);
    227         } else {
    228             // We can get here if we pasted a copied portion of a blockquote with a newline at the end and are trying to paste it
    229             // into an unquoted area. We then don't want the newline within the blockquote or else it will also be quoted.
    230             if (m_pasteBlockqutoeIntoUnquotedArea) {
    231                 if (Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote))
    232                     startBlock = toElement(highestBlockquote);
    233             }
    234 
    235             if (listChild && listChild != startBlock) {
    236                 RefPtrWillBeRawPtr<Element> listChildToInsert = listChild->cloneElementWithoutChildren();
    237                 appendNode(blockToInsert, listChildToInsert.get());
    238                 insertNodeAfter(listChildToInsert.get(), listChild);
    239             } else {
    240                 // Most of the time we want to stay at the nesting level of the startBlock (e.g., when nesting within lists). However,
    241                 // for div nodes, this can result in nested div tags that are hard to break out of.
    242                 Element* siblingNode = startBlock.get();
    243                 if (isHTMLDivElement(*blockToInsert))
    244                     siblingNode = highestVisuallyEquivalentDivBelowRoot(startBlock.get());
    245                 insertNodeAfter(blockToInsert, siblingNode);
    246             }
    247         }
    248 
    249         // Recreate the same structure in the new paragraph.
    250 
    251         WillBeHeapVector<RefPtrWillBeMember<Element> > ancestors;
    252         getAncestorsInsideBlock(positionOutsideTabSpan(insertionPosition).deprecatedNode(), startBlock.get(), ancestors);
    253         RefPtrWillBeRawPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert);
    254 
    255         appendBlockPlaceholder(parent);
    256 
    257         setEndingSelection(VisibleSelection(firstPositionInNode(parent.get()), DOWNSTREAM, endingSelection().isDirectional()));
    258         return;
    259     }
    260 
    261 
    262     //---------------------------------------------------------------------
    263     // Handle case when position is in the first visible position in its block, and
    264     // similar case where previous position is in another, presumeably nested, block.
    265     if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) {
    266         Node* refNode = 0;
    267         insertionPosition = positionOutsideTabSpan(insertionPosition);
    268 
    269         if (isFirstInBlock && !nestNewBlock) {
    270             if (listChild && listChild != startBlock) {
    271                 RefPtrWillBeRawPtr<Element> listChildToInsert = listChild->cloneElementWithoutChildren();
    272                 appendNode(blockToInsert, listChildToInsert.get());
    273                 insertNodeBefore(listChildToInsert.get(), listChild);
    274             } else {
    275                 refNode = startBlock.get();
    276             }
    277         } else if (isFirstInBlock && nestNewBlock) {
    278             // startBlock should always have children, otherwise isLastInBlock would be true and it's handled above.
    279             ASSERT(startBlock->firstChild());
    280             refNode = startBlock->firstChild();
    281         }
    282         else if (insertionPosition.deprecatedNode() == startBlock && nestNewBlock) {
    283             refNode = startBlock->traverseToChildAt(insertionPosition.deprecatedEditingOffset());
    284             ASSERT(refNode); // must be true or we'd be in the end of block case
    285         } else
    286             refNode = insertionPosition.deprecatedNode();
    287 
    288         // find ending selection position easily before inserting the paragraph
    289         insertionPosition = insertionPosition.downstream();
    290 
    291         if (refNode)
    292             insertNodeBefore(blockToInsert, refNode);
    293 
    294         // Recreate the same structure in the new paragraph.
    295 
    296         WillBeHeapVector<RefPtrWillBeMember<Element> > ancestors;
    297         getAncestorsInsideBlock(positionAvoidingSpecialElementBoundary(positionOutsideTabSpan(insertionPosition)).deprecatedNode(), startBlock.get(), ancestors);
    298 
    299         appendBlockPlaceholder(cloneHierarchyUnderNewBlock(ancestors, blockToInsert));
    300 
    301         // In this case, we need to set the new ending selection.
    302         setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional()));
    303         return;
    304     }
    305 
    306     //---------------------------------------------------------------------
    307     // Handle the (more complicated) general case,
    308 
    309     // All of the content in the current block after visiblePos is
    310     // about to be wrapped in a new paragraph element.  Add a br before
    311     // it if visiblePos is at the start of a paragraph so that the
    312     // content will move down a line.
    313     if (isStartOfParagraph(visiblePos)) {
    314         RefPtrWillBeRawPtr<Element> br = createBreakElement(document());
    315         insertNodeAt(br.get(), insertionPosition);
    316         insertionPosition = positionInParentAfterNode(*br);
    317         // If the insertion point is a break element, there is nothing else
    318         // we need to do.
    319         if (visiblePos.deepEquivalent().anchorNode()->renderer()->isBR()) {
    320             setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional()));
    321             return;
    322         }
    323     }
    324 
    325     // Move downstream. Typing style code will take care of carrying along the
    326     // style of the upstream position.
    327     insertionPosition = insertionPosition.downstream();
    328 
    329     // At this point, the insertionPosition's node could be a container, and we want to make sure we include
    330     // all of the correct nodes when building the ancestor list.  So this needs to be the deepest representation of the position
    331     // before we walk the DOM tree.
    332     insertionPosition = positionOutsideTabSpan(VisiblePosition(insertionPosition).deepEquivalent());
    333 
    334     // If the returned position lies either at the end or at the start of an element that is ignored by editing
    335     // we should move to its upstream or downstream position.
    336     if (editingIgnoresContent(insertionPosition.deprecatedNode())) {
    337         if (insertionPosition.atLastEditingPositionForNode())
    338             insertionPosition = insertionPosition.downstream();
    339         else if (insertionPosition.atFirstEditingPositionForNode())
    340             insertionPosition = insertionPosition.upstream();
    341     }
    342 
    343     // Make sure we do not cause a rendered space to become unrendered.
    344     // FIXME: We need the affinity for pos, but pos.downstream() does not give it
    345     Position leadingWhitespace = insertionPosition.leadingWhitespacePosition(VP_DEFAULT_AFFINITY);
    346     // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions
    347     // after the preserved newline, causing the newline to be turned into a nbsp.
    348     if (leadingWhitespace.isNotNull() && leadingWhitespace.deprecatedNode()->isTextNode()) {
    349         Text* textNode = toText(leadingWhitespace.deprecatedNode());
    350         ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
    351         replaceTextInNodePreservingMarkers(textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
    352     }
    353 
    354     // Split at pos if in the middle of a text node.
    355     Position positionAfterSplit;
    356     if (insertionPosition.anchorType() == Position::PositionIsOffsetInAnchor && insertionPosition.containerNode()->isTextNode()) {
    357         RefPtrWillBeRawPtr<Text> textNode = toText(insertionPosition.containerNode());
    358         bool atEnd = static_cast<unsigned>(insertionPosition.offsetInContainerNode()) >= textNode->length();
    359         if (insertionPosition.deprecatedEditingOffset() > 0 && !atEnd) {
    360             splitTextNode(textNode, insertionPosition.offsetInContainerNode());
    361             positionAfterSplit = firstPositionInNode(textNode.get());
    362             insertionPosition.moveToPosition(textNode->previousSibling(), insertionPosition.offsetInContainerNode());
    363             visiblePos = VisiblePosition(insertionPosition);
    364         }
    365     }
    366 
    367     // If we got detached due to mutation events, just bail out.
    368     if (!startBlock->parentNode())
    369         return;
    370 
    371     // Put the added block in the tree.
    372     if (nestNewBlock) {
    373         appendNode(blockToInsert.get(), startBlock);
    374     } else if (listChild && listChild != startBlock) {
    375         RefPtrWillBeRawPtr<Element> listChildToInsert = listChild->cloneElementWithoutChildren();
    376         appendNode(blockToInsert.get(), listChildToInsert.get());
    377         insertNodeAfter(listChildToInsert.get(), listChild);
    378     } else {
    379         insertNodeAfter(blockToInsert.get(), startBlock);
    380     }
    381 
    382     document().updateLayoutIgnorePendingStylesheets();
    383 
    384     // If the paragraph separator was inserted at the end of a paragraph, an empty line must be
    385     // created.  All of the nodes, starting at visiblePos, are about to be added to the new paragraph
    386     // element.  If the first node to be inserted won't be one that will hold an empty line open, add a br.
    387     if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtVisiblePosition(visiblePos))
    388         appendNode(createBreakElement(document()).get(), blockToInsert.get());
    389 
    390     // Move the start node and the siblings of the start node.
    391     if (VisiblePosition(insertionPosition) != VisiblePosition(positionBeforeNode(blockToInsert.get()))) {
    392         Node* n;
    393         if (insertionPosition.containerNode() == startBlock)
    394             n = insertionPosition.computeNodeAfterPosition();
    395         else {
    396             Node* splitTo = insertionPosition.containerNode();
    397             if (splitTo->isTextNode() && insertionPosition.offsetInContainerNode() >= caretMaxOffset(splitTo))
    398                 splitTo = NodeTraversal::next(*splitTo, startBlock.get());
    399             ASSERT(splitTo);
    400             splitTreeToNode(splitTo, startBlock.get());
    401 
    402             for (n = startBlock->firstChild(); n; n = n->nextSibling()) {
    403                 VisiblePosition beforeNodePosition(positionBeforeNode(n));
    404                 if (!beforeNodePosition.isNull() && comparePositions(VisiblePosition(insertionPosition), beforeNodePosition) <= 0)
    405                     break;
    406             }
    407         }
    408 
    409         moveRemainingSiblingsToNewParent(n, blockToInsert.get(), blockToInsert);
    410     }
    411 
    412     // Handle whitespace that occurs after the split
    413     if (positionAfterSplit.isNotNull()) {
    414         document().updateLayoutIgnorePendingStylesheets();
    415         if (!positionAfterSplit.isRenderedCharacter()) {
    416             // Clear out all whitespace and insert one non-breaking space
    417             ASSERT(!positionAfterSplit.containerNode()->renderer() || positionAfterSplit.containerNode()->renderer()->style()->collapseWhiteSpace());
    418             deleteInsignificantTextDownstream(positionAfterSplit);
    419             if (positionAfterSplit.deprecatedNode()->isTextNode())
    420                 insertTextIntoNode(toText(positionAfterSplit.containerNode()), 0, nonBreakingSpaceString());
    421         }
    422     }
    423 
    424     setEndingSelection(VisibleSelection(firstPositionInNode(blockToInsert.get()), DOWNSTREAM, endingSelection().isDirectional()));
    425     applyStyleAfterInsertion(startBlock.get());
    426 }
    427 
    428 void InsertParagraphSeparatorCommand::trace(Visitor *visitor)
    429 {
    430     visitor->trace(m_style);
    431     CompositeEditCommand::trace(visitor);
    432 }
    433 
    434 
    435 } // namespace WebCore
    436