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 "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() && curBlock->parentElement()->hasTagName(divTag) && 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.anchorNode()->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, Vector<RefPtr<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 PassRefPtr<Element> InsertParagraphSeparatorCommand::cloneHierarchyUnderNewBlock(const Vector<RefPtr<Element> >& ancestors, PassRefPtr<Element> blockToInsert)
    134 {
    135     // Make clones of ancestors in between the start node and the start block.
    136     RefPtr<Element> parent = blockToInsert;
    137     for (size_t i = ancestors.size(); i != 0; --i) {
    138         RefPtr<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     RefPtr<Element> startBlock = enclosingBlock(insertionPosition.parentAnchoredEquivalent().containerNode());
    167     Position canonicalPos = VisiblePosition(insertionPosition).deepEquivalent();
    168     if (!startBlock
    169         || !startBlock->nonShadowBoundaryParentNode()
    170         || isTableCell(startBlock.get())
    171         || startBlock->hasTagName(formTag)
    172         // 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
    173         || (!canonicalPos.isNull() && canonicalPos.deprecatedNode()->renderer() && canonicalPos.deprecatedNode()->renderer()->isTable())
    174         || (!canonicalPos.isNull() && canonicalPos.deprecatedNode()->hasTagName(hrTag))) {
    175         applyCommandToComposite(InsertLineBreakCommand::create(document()));
    176         return;
    177     }
    178 
    179     // Use the leftmost candidate.
    180     insertionPosition = insertionPosition.upstream();
    181     if (!insertionPosition.isCandidate())
    182         insertionPosition = insertionPosition.downstream();
    183 
    184     // Adjust the insertion position after the delete
    185     insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition);
    186     VisiblePosition visiblePos(insertionPosition, affinity);
    187     calculateStyleBeforeInsertion(insertionPosition);
    188 
    189     //---------------------------------------------------------------------
    190     // Handle special case of typing return on an empty list item
    191     if (breakOutOfEmptyListItem())
    192         return;
    193 
    194     //---------------------------------------------------------------------
    195     // Prepare for more general cases.
    196 
    197     bool isFirstInBlock = isStartOfBlock(visiblePos);
    198     bool isLastInBlock = isEndOfBlock(visiblePos);
    199     bool nestNewBlock = false;
    200 
    201     // Create block to be inserted.
    202     RefPtr<Element> blockToInsert;
    203     if (startBlock->isRootEditableElement()) {
    204         blockToInsert = createDefaultParagraphElement(document());
    205         nestNewBlock = true;
    206     } else if (shouldUseDefaultParagraphElement(startBlock.get()))
    207         blockToInsert = createDefaultParagraphElement(document());
    208     else
    209         blockToInsert = startBlock->cloneElementWithoutChildren();
    210 
    211     //---------------------------------------------------------------------
    212     // Handle case when position is in the last visible position in its block,
    213     // including when the block is empty.
    214     if (isLastInBlock) {
    215         if (nestNewBlock) {
    216             if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) {
    217                 // The block is empty.  Create an empty block to
    218                 // represent the paragraph that we're leaving.
    219                 RefPtr<Element> extraBlock = createDefaultParagraphElement(document());
    220                 appendNode(extraBlock, startBlock);
    221                 appendBlockPlaceholder(extraBlock);
    222             }
    223             appendNode(blockToInsert, startBlock);
    224         } else {
    225             // 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
    226             // into an unquoted area. We then don't want the newline within the blockquote or else it will also be quoted.
    227             if (m_pasteBlockqutoeIntoUnquotedArea) {
    228                 if (Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote))
    229                     startBlock = toElement(highestBlockquote);
    230             }
    231 
    232             // Most of the time we want to stay at the nesting level of the startBlock (e.g., when nesting within lists).  However,
    233             // for div nodes, this can result in nested div tags that are hard to break out of.
    234             Element* siblingNode = startBlock.get();
    235             if (blockToInsert->hasTagName(divTag))
    236                 siblingNode = highestVisuallyEquivalentDivBelowRoot(startBlock.get());
    237             insertNodeAfter(blockToInsert, siblingNode);
    238         }
    239 
    240         // Recreate the same structure in the new paragraph.
    241 
    242         Vector<RefPtr<Element> > ancestors;
    243         getAncestorsInsideBlock(positionOutsideTabSpan(insertionPosition).deprecatedNode(), startBlock.get(), ancestors);
    244         RefPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert);
    245 
    246         appendBlockPlaceholder(parent);
    247 
    248         setEndingSelection(VisibleSelection(firstPositionInNode(parent.get()), DOWNSTREAM, endingSelection().isDirectional()));
    249         return;
    250     }
    251 
    252 
    253     //---------------------------------------------------------------------
    254     // Handle case when position is in the first visible position in its block, and
    255     // similar case where previous position is in another, presumeably nested, block.
    256     if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) {
    257         Node *refNode;
    258 
    259         insertionPosition = positionOutsideTabSpan(insertionPosition);
    260 
    261         if (isFirstInBlock && !nestNewBlock)
    262             refNode = startBlock.get();
    263         else if (isFirstInBlock && nestNewBlock) {
    264             // startBlock should always have children, otherwise isLastInBlock would be true and it's handled above.
    265             ASSERT(startBlock->firstChild());
    266             refNode = startBlock->firstChild();
    267         }
    268         else if (insertionPosition.deprecatedNode() == startBlock && nestNewBlock) {
    269             refNode = startBlock->childNode(insertionPosition.deprecatedEditingOffset());
    270             ASSERT(refNode); // must be true or we'd be in the end of block case
    271         } else
    272             refNode = insertionPosition.deprecatedNode();
    273 
    274         // find ending selection position easily before inserting the paragraph
    275         insertionPosition = insertionPosition.downstream();
    276 
    277         insertNodeBefore(blockToInsert, refNode);
    278 
    279         // Recreate the same structure in the new paragraph.
    280 
    281         Vector<RefPtr<Element> > ancestors;
    282         getAncestorsInsideBlock(positionAvoidingSpecialElementBoundary(positionOutsideTabSpan(insertionPosition)).deprecatedNode(), startBlock.get(), ancestors);
    283 
    284         appendBlockPlaceholder(cloneHierarchyUnderNewBlock(ancestors, blockToInsert));
    285 
    286         // In this case, we need to set the new ending selection.
    287         setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional()));
    288         return;
    289     }
    290 
    291     //---------------------------------------------------------------------
    292     // Handle the (more complicated) general case,
    293 
    294     // All of the content in the current block after visiblePos is
    295     // about to be wrapped in a new paragraph element.  Add a br before
    296     // it if visiblePos is at the start of a paragraph so that the
    297     // content will move down a line.
    298     if (isStartOfParagraph(visiblePos)) {
    299         RefPtr<Element> br = createBreakElement(document());
    300         insertNodeAt(br.get(), insertionPosition);
    301         insertionPosition = positionInParentAfterNode(br.get());
    302         // If the insertion point is a break element, there is nothing else
    303         // we need to do.
    304         if (visiblePos.deepEquivalent().anchorNode()->renderer()->isBR()) {
    305             setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional()));
    306             return;
    307         }
    308     }
    309 
    310     // Move downstream. Typing style code will take care of carrying along the
    311     // style of the upstream position.
    312     insertionPosition = insertionPosition.downstream();
    313 
    314     // At this point, the insertionPosition's node could be a container, and we want to make sure we include
    315     // all of the correct nodes when building the ancestor list.  So this needs to be the deepest representation of the position
    316     // before we walk the DOM tree.
    317     insertionPosition = positionOutsideTabSpan(VisiblePosition(insertionPosition).deepEquivalent());
    318 
    319     // If the returned position lies either at the end or at the start of an element that is ignored by editing
    320     // we should move to its upstream or downstream position.
    321     if (editingIgnoresContent(insertionPosition.deprecatedNode())) {
    322         if (insertionPosition.atLastEditingPositionForNode())
    323             insertionPosition = insertionPosition.downstream();
    324         else if (insertionPosition.atFirstEditingPositionForNode())
    325             insertionPosition = insertionPosition.upstream();
    326     }
    327 
    328     // Make sure we do not cause a rendered space to become unrendered.
    329     // FIXME: We need the affinity for pos, but pos.downstream() does not give it
    330     Position leadingWhitespace = insertionPosition.leadingWhitespacePosition(VP_DEFAULT_AFFINITY);
    331     // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions
    332     // after the preserved newline, causing the newline to be turned into a nbsp.
    333     if (leadingWhitespace.isNotNull() && leadingWhitespace.deprecatedNode()->isTextNode()) {
    334         Text* textNode = toText(leadingWhitespace.deprecatedNode());
    335         ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
    336         replaceTextInNodePreservingMarkers(textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
    337     }
    338 
    339     // Split at pos if in the middle of a text node.
    340     Position positionAfterSplit;
    341     if (insertionPosition.anchorType() == Position::PositionIsOffsetInAnchor && insertionPosition.containerNode()->isTextNode()) {
    342         RefPtr<Text> textNode = toText(insertionPosition.containerNode());
    343         bool atEnd = static_cast<unsigned>(insertionPosition.offsetInContainerNode()) >= textNode->length();
    344         if (insertionPosition.deprecatedEditingOffset() > 0 && !atEnd) {
    345             splitTextNode(textNode, insertionPosition.offsetInContainerNode());
    346             positionAfterSplit = firstPositionInNode(textNode.get());
    347             insertionPosition.moveToPosition(textNode->previousSibling(), insertionPosition.offsetInContainerNode());
    348             visiblePos = VisiblePosition(insertionPosition);
    349         }
    350     }
    351 
    352     // If we got detached due to mutation events, just bail out.
    353     if (!startBlock->parentNode())
    354         return;
    355 
    356     // Put the added block in the tree.
    357     if (nestNewBlock)
    358         appendNode(blockToInsert.get(), startBlock);
    359     else
    360         insertNodeAfter(blockToInsert.get(), startBlock);
    361 
    362     document()->updateLayoutIgnorePendingStylesheets();
    363 
    364     // If the paragraph separator was inserted at the end of a paragraph, an empty line must be
    365     // created.  All of the nodes, starting at visiblePos, are about to be added to the new paragraph
    366     // element.  If the first node to be inserted won't be one that will hold an empty line open, add a br.
    367     if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtVisiblePosition(visiblePos))
    368         appendNode(createBreakElement(document()).get(), blockToInsert.get());
    369 
    370     // Move the start node and the siblings of the start node.
    371     if (VisiblePosition(insertionPosition) != VisiblePosition(positionBeforeNode(blockToInsert.get()))) {
    372         Node* n;
    373         if (insertionPosition.containerNode() == startBlock)
    374             n = insertionPosition.computeNodeAfterPosition();
    375         else {
    376             Node* splitTo = insertionPosition.containerNode();
    377             if (splitTo->isTextNode() && insertionPosition.offsetInContainerNode() >= caretMaxOffset(splitTo))
    378                 splitTo = NodeTraversal::next(splitTo, startBlock.get());
    379             ASSERT(splitTo);
    380             splitTreeToNode(splitTo, startBlock.get());
    381 
    382             for (n = startBlock->firstChild(); n; n = n->nextSibling()) {
    383                 VisiblePosition beforeNodePosition = positionBeforeNode(n);
    384                 if (!beforeNodePosition.isNull() && comparePositions(VisiblePosition(insertionPosition), beforeNodePosition) <= 0)
    385                     break;
    386             }
    387         }
    388 
    389         moveRemainingSiblingsToNewParent(n, blockToInsert.get(), blockToInsert);
    390     }
    391 
    392     // Handle whitespace that occurs after the split
    393     if (positionAfterSplit.isNotNull()) {
    394         document()->updateLayoutIgnorePendingStylesheets();
    395         if (!positionAfterSplit.isRenderedCharacter()) {
    396             // Clear out all whitespace and insert one non-breaking space
    397             ASSERT(!positionAfterSplit.containerNode()->renderer() || positionAfterSplit.containerNode()->renderer()->style()->collapseWhiteSpace());
    398             deleteInsignificantTextDownstream(positionAfterSplit);
    399             if (positionAfterSplit.deprecatedNode()->isTextNode())
    400                 insertTextIntoNode(toText(positionAfterSplit.containerNode()), 0, nonBreakingSpaceString());
    401         }
    402     }
    403 
    404     setEndingSelection(VisibleSelection(firstPositionInNode(blockToInsert.get()), DOWNSTREAM, endingSelection().isDirectional()));
    405     applyStyleAfterInsertion(startBlock.get());
    406 }
    407 
    408 } // namespace WebCore
    409