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