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