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