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