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