Home | History | Annotate | Download | only in editing
      1 /*
      2  * Copyright (C) 2005 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/InsertTextCommand.h"
     28 
     29 #include "core/dom/Document.h"
     30 #include "core/dom/Element.h"
     31 #include "core/dom/Text.h"
     32 #include "core/editing/Editor.h"
     33 #include "core/editing/VisibleUnits.h"
     34 #include "core/editing/htmlediting.h"
     35 #include "core/frame/LocalFrame.h"
     36 
     37 namespace WebCore {
     38 
     39 InsertTextCommand::InsertTextCommand(Document& document, const String& text, bool selectInsertedText, RebalanceType rebalanceType)
     40     : CompositeEditCommand(document)
     41     , m_text(text)
     42     , m_selectInsertedText(selectInsertedText)
     43     , m_rebalanceType(rebalanceType)
     44 {
     45 }
     46 
     47 Position InsertTextCommand::positionInsideTextNode(const Position& p)
     48 {
     49     Position pos = p;
     50     if (isTabSpanTextNode(pos.anchorNode())) {
     51         RefPtrWillBeRawPtr<Node> textNode = document().createEditingTextNode("");
     52         insertNodeAtTabSpanPosition(textNode.get(), pos);
     53         return firstPositionInNode(textNode.get());
     54     }
     55 
     56     // Prepare for text input by looking at the specified position.
     57     // It may be necessary to insert a text node to receive characters.
     58     if (!pos.containerNode()->isTextNode()) {
     59         RefPtrWillBeRawPtr<Node> textNode = document().createEditingTextNode("");
     60         insertNodeAt(textNode.get(), pos);
     61         return firstPositionInNode(textNode.get());
     62     }
     63 
     64     return pos;
     65 }
     66 
     67 void InsertTextCommand::setEndingSelectionWithoutValidation(const Position& startPosition, const Position& endPosition)
     68 {
     69     // We could have inserted a part of composed character sequence,
     70     // so we are basically treating ending selection as a range to avoid validation.
     71     // <http://bugs.webkit.org/show_bug.cgi?id=15781>
     72     VisibleSelection forcedEndingSelection;
     73     forcedEndingSelection.setWithoutValidation(startPosition, endPosition);
     74     forcedEndingSelection.setIsDirectional(endingSelection().isDirectional());
     75     setEndingSelection(forcedEndingSelection);
     76 }
     77 
     78 // This avoids the expense of a full fledged delete operation, and avoids a layout that typically results
     79 // from text removal.
     80 bool InsertTextCommand::performTrivialReplace(const String& text, bool selectInsertedText)
     81 {
     82     if (!endingSelection().isRange())
     83         return false;
     84 
     85     if (text.contains('\t') || text.contains(' ') || text.contains('\n'))
     86         return false;
     87 
     88     Position start = endingSelection().start();
     89     Position endPosition = replaceSelectedTextInNode(text);
     90     if (endPosition.isNull())
     91         return false;
     92 
     93     setEndingSelectionWithoutValidation(start, endPosition);
     94     if (!selectInsertedText)
     95         setEndingSelection(VisibleSelection(endingSelection().visibleEnd(), endingSelection().isDirectional()));
     96 
     97     return true;
     98 }
     99 
    100 bool InsertTextCommand::performOverwrite(const String& text, bool selectInsertedText)
    101 {
    102     Position start = endingSelection().start();
    103     RefPtrWillBeRawPtr<Text> textNode = start.containerText();
    104     if (!textNode)
    105         return false;
    106 
    107     unsigned count = std::min(text.length(), textNode->length() - start.offsetInContainerNode());
    108     if (!count)
    109         return false;
    110 
    111     replaceTextInNode(textNode, start.offsetInContainerNode(), count, text);
    112 
    113     Position endPosition = Position(textNode.release(), start.offsetInContainerNode() + text.length());
    114     setEndingSelectionWithoutValidation(start, endPosition);
    115     if (!selectInsertedText)
    116         setEndingSelection(VisibleSelection(endingSelection().visibleEnd(), endingSelection().isDirectional()));
    117 
    118     return true;
    119 }
    120 
    121 void InsertTextCommand::doApply()
    122 {
    123     ASSERT(m_text.find('\n') == kNotFound);
    124 
    125     if (!endingSelection().isNonOrphanedCaretOrRange())
    126         return;
    127 
    128     // Delete the current selection.
    129     // FIXME: This delete operation blows away the typing style.
    130     if (endingSelection().isRange()) {
    131         if (performTrivialReplace(m_text, m_selectInsertedText))
    132             return;
    133         bool endOfSelectionWasAtStartOfBlock = isStartOfBlock(endingSelection().visibleEnd());
    134         deleteSelection(false, true, false, false);
    135         // deleteSelection eventually makes a new endingSelection out of a Position. If that Position doesn't have
    136         // a renderer (e.g. it is on a <frameset> in the DOM), the VisibleSelection cannot be canonicalized to
    137         // anything other than NoSelection. The rest of this function requires a real endingSelection, so bail out.
    138         if (endingSelection().isNone())
    139             return;
    140         if (endOfSelectionWasAtStartOfBlock) {
    141             if (EditingStyle* typingStyle = document().frame()->selection().typingStyle())
    142                 typingStyle->removeBlockProperties();
    143         }
    144     } else if (document().frame()->editor().isOverwriteModeEnabled()) {
    145         if (performOverwrite(m_text, m_selectInsertedText))
    146             return;
    147     }
    148 
    149     Position startPosition(endingSelection().start());
    150 
    151     Position placeholder;
    152     // We want to remove preserved newlines and brs that will collapse (and thus become unnecessary) when content
    153     // is inserted just before them.
    154     // FIXME: We shouldn't really have to do this, but removing placeholders is a workaround for 9661.
    155     // If the caret is just before a placeholder, downstream will normalize the caret to it.
    156     Position downstream(startPosition.downstream());
    157     if (lineBreakExistsAtPosition(downstream)) {
    158         // FIXME: This doesn't handle placeholders at the end of anonymous blocks.
    159         VisiblePosition caret(startPosition);
    160         if (isEndOfBlock(caret) && isStartOfParagraph(caret))
    161             placeholder = downstream;
    162         // Don't remove the placeholder yet, otherwise the block we're inserting into would collapse before
    163         // we get a chance to insert into it.  We check for a placeholder now, though, because doing so requires
    164         // the creation of a VisiblePosition, and if we did that post-insertion it would force a layout.
    165     }
    166 
    167     // Insert the character at the leftmost candidate.
    168     startPosition = startPosition.upstream();
    169 
    170     // It is possible for the node that contains startPosition to contain only unrendered whitespace,
    171     // and so deleteInsignificantText could remove it.  Save the position before the node in case that happens.
    172     ASSERT(startPosition.containerNode());
    173     Position positionBeforeStartNode(positionInParentBeforeNode(*startPosition.containerNode()));
    174     deleteInsignificantText(startPosition, startPosition.downstream());
    175     if (!startPosition.inDocument())
    176         startPosition = positionBeforeStartNode;
    177     if (!startPosition.isCandidate())
    178         startPosition = startPosition.downstream();
    179 
    180     startPosition = positionAvoidingSpecialElementBoundary(startPosition);
    181 
    182     Position endPosition;
    183 
    184     if (m_text == "\t") {
    185         endPosition = insertTab(startPosition);
    186         startPosition = endPosition.previous();
    187         if (placeholder.isNotNull())
    188             removePlaceholderAt(placeholder);
    189     } else {
    190         // Make sure the document is set up to receive m_text
    191         startPosition = positionInsideTextNode(startPosition);
    192         ASSERT(startPosition.anchorType() == Position::PositionIsOffsetInAnchor);
    193         ASSERT(startPosition.containerNode());
    194         ASSERT(startPosition.containerNode()->isTextNode());
    195         if (placeholder.isNotNull())
    196             removePlaceholderAt(placeholder);
    197         RefPtrWillBeRawPtr<Text> textNode = startPosition.containerText();
    198         const unsigned offset = startPosition.offsetInContainerNode();
    199 
    200         insertTextIntoNode(textNode, offset, m_text);
    201         endPosition = Position(textNode, offset + m_text.length());
    202 
    203         if (m_rebalanceType == RebalanceLeadingAndTrailingWhitespaces) {
    204             // The insertion may require adjusting adjacent whitespace, if it is present.
    205             rebalanceWhitespaceAt(endPosition);
    206             // Rebalancing on both sides isn't necessary if we've inserted only spaces.
    207             if (!shouldRebalanceLeadingWhitespaceFor(m_text))
    208                 rebalanceWhitespaceAt(startPosition);
    209         } else {
    210             ASSERT(m_rebalanceType == RebalanceAllWhitespaces);
    211             if (canRebalance(startPosition) && canRebalance(endPosition))
    212                 rebalanceWhitespaceOnTextSubstring(textNode, startPosition.offsetInContainerNode(), endPosition.offsetInContainerNode());
    213         }
    214     }
    215 
    216     setEndingSelectionWithoutValidation(startPosition, endPosition);
    217 
    218     // Handle the case where there is a typing style.
    219     if (RefPtrWillBeRawPtr<EditingStyle> typingStyle = document().frame()->selection().typingStyle()) {
    220         typingStyle->prepareToApplyAt(endPosition, EditingStyle::PreserveWritingDirection);
    221         if (!typingStyle->isEmpty())
    222             applyStyle(typingStyle.get());
    223     }
    224 
    225     if (!m_selectInsertedText)
    226         setEndingSelection(VisibleSelection(endingSelection().end(), endingSelection().affinity(), endingSelection().isDirectional()));
    227 }
    228 
    229 Position InsertTextCommand::insertTab(const Position& pos)
    230 {
    231     Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent();
    232     if (insertPos.isNull())
    233         return pos;
    234 
    235     Node* node = insertPos.containerNode();
    236     unsigned offset = node->isTextNode() ? insertPos.offsetInContainerNode() : 0;
    237 
    238     // keep tabs coalesced in tab span
    239     if (isTabSpanTextNode(node)) {
    240         RefPtrWillBeRawPtr<Text> textNode = toText(node);
    241         insertTextIntoNode(textNode, offset, "\t");
    242         return Position(textNode.release(), offset + 1);
    243     }
    244 
    245     // create new tab span
    246     RefPtrWillBeRawPtr<Element> spanNode = createTabSpanElement(document());
    247 
    248     // place it
    249     if (!node->isTextNode()) {
    250         insertNodeAt(spanNode.get(), insertPos);
    251     } else {
    252         RefPtrWillBeRawPtr<Text> textNode = toText(node);
    253         if (offset >= textNode->length())
    254             insertNodeAfter(spanNode, textNode.release());
    255         else {
    256             // split node to make room for the span
    257             // NOTE: splitTextNode uses textNode for the
    258             // second node in the split, so we need to
    259             // insert the span before it.
    260             if (offset > 0)
    261                 splitTextNode(textNode, offset);
    262             insertNodeBefore(spanNode, textNode.release());
    263         }
    264     }
    265 
    266     // return the position following the new tab
    267     return lastPositionInNode(spanNode.get());
    268 }
    269 
    270 }
    271