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