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