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