1/* 2 * Copyright (C) 2005, 2006 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 "InsertLineBreakCommand.h" 28 29#include "Document.h" 30#include "EditingStyle.h" 31#include "Frame.h" 32#include "FrameSelection.h" 33#include "HTMLElement.h" 34#include "HTMLNames.h" 35#include "Range.h" 36#include "RenderObject.h" 37#include "Text.h" 38#include "VisiblePosition.h" 39#include "VisibleUnits.h" 40#include "htmlediting.h" 41 42namespace WebCore { 43 44using namespace HTMLNames; 45 46InsertLineBreakCommand::InsertLineBreakCommand(Document* document) 47 : CompositeEditCommand(document) 48{ 49} 50 51bool InsertLineBreakCommand::preservesTypingStyle() const 52{ 53 return true; 54} 55 56void InsertLineBreakCommand::insertNodeAfterPosition(Node* node, const Position& pos) 57{ 58 // Insert the BR after the caret position. In the case the 59 // position is a block, do an append. We don't want to insert 60 // the BR *after* the block. 61 Element* cb = deprecatedEnclosingBlockFlowElement(pos.deprecatedNode()); 62 if (cb == pos.deprecatedNode()) 63 appendNode(node, cb); 64 else 65 insertNodeAfter(node, pos.deprecatedNode()); 66} 67 68void InsertLineBreakCommand::insertNodeBeforePosition(Node* node, const Position& pos) 69{ 70 // Insert the BR after the caret position. In the case the 71 // position is a block, do an append. We don't want to insert 72 // the BR *before* the block. 73 Element* cb = deprecatedEnclosingBlockFlowElement(pos.deprecatedNode()); 74 if (cb == pos.deprecatedNode()) 75 appendNode(node, cb); 76 else 77 insertNodeBefore(node, pos.deprecatedNode()); 78} 79 80// Whether we should insert a break element or a '\n'. 81bool InsertLineBreakCommand::shouldUseBreakElement(const Position& insertionPos) 82{ 83 // An editing position like [input, 0] actually refers to the position before 84 // the input element, and in that case we need to check the input element's 85 // parent's renderer. 86 Position p(insertionPos.parentAnchoredEquivalent()); 87 return p.deprecatedNode()->renderer() && !p.deprecatedNode()->renderer()->style()->preserveNewline(); 88} 89 90void InsertLineBreakCommand::doApply() 91{ 92 deleteSelection(); 93 VisibleSelection selection = endingSelection(); 94 if (!selection.isNonOrphanedCaretOrRange()) 95 return; 96 97 VisiblePosition caret(selection.visibleStart()); 98 // FIXME: If the node is hidden, we should still be able to insert text. 99 // For now, we return to avoid a crash. https://bugs.webkit.org/show_bug.cgi?id=40342 100 if (caret.isNull()) 101 return; 102 103 Position pos(caret.deepEquivalent()); 104 105 pos = positionAvoidingSpecialElementBoundary(pos); 106 107 pos = positionOutsideTabSpan(pos); 108 109 RefPtr<Node> nodeToInsert; 110 if (shouldUseBreakElement(pos)) 111 nodeToInsert = createBreakElement(document()); 112 else 113 nodeToInsert = document()->createTextNode("\n"); 114 115 // FIXME: Need to merge text nodes when inserting just after or before text. 116 117 if (isEndOfParagraph(caret) && !lineBreakExistsAtVisiblePosition(caret)) { 118 bool needExtraLineBreak = !pos.deprecatedNode()->hasTagName(hrTag) && !pos.deprecatedNode()->hasTagName(tableTag); 119 120 insertNodeAt(nodeToInsert.get(), pos); 121 122 if (needExtraLineBreak) 123 insertNodeBefore(nodeToInsert->cloneNode(false), nodeToInsert); 124 125 VisiblePosition endingPosition(positionBeforeNode(nodeToInsert.get())); 126 setEndingSelection(VisibleSelection(endingPosition, endingSelection().isDirectional())); 127 } else if (pos.deprecatedEditingOffset() <= caretMinOffset(pos.deprecatedNode())) { 128 insertNodeAt(nodeToInsert.get(), pos); 129 130 // Insert an extra br or '\n' if the just inserted one collapsed. 131 if (!isStartOfParagraph(positionBeforeNode(nodeToInsert.get()))) 132 insertNodeBefore(nodeToInsert->cloneNode(false).get(), nodeToInsert.get()); 133 134 setEndingSelection(VisibleSelection(positionInParentAfterNode(nodeToInsert.get()), DOWNSTREAM, endingSelection().isDirectional())); 135 // If we're inserting after all of the rendered text in a text node, or into a non-text node, 136 // a simple insertion is sufficient. 137 } else if (pos.deprecatedEditingOffset() >= caretMaxOffset(pos.deprecatedNode()) || !pos.deprecatedNode()->isTextNode()) { 138 insertNodeAt(nodeToInsert.get(), pos); 139 setEndingSelection(VisibleSelection(positionInParentAfterNode(nodeToInsert.get()), DOWNSTREAM, endingSelection().isDirectional())); 140 } else if (pos.deprecatedNode()->isTextNode()) { 141 // Split a text node 142 Text* textNode = toText(pos.deprecatedNode()); 143 splitTextNode(textNode, pos.deprecatedEditingOffset()); 144 insertNodeBefore(nodeToInsert, textNode); 145 Position endingPosition = firstPositionInNode(textNode); 146 147 // Handle whitespace that occurs after the split 148 document()->updateLayoutIgnorePendingStylesheets(); 149 if (!endingPosition.isRenderedCharacter()) { 150 Position positionBeforeTextNode(positionInParentBeforeNode(textNode)); 151 // Clear out all whitespace and insert one non-breaking space 152 deleteInsignificantTextDownstream(endingPosition); 153 ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace()); 154 // Deleting insignificant whitespace will remove textNode if it contains nothing but insignificant whitespace. 155 if (textNode->inDocument()) 156 insertTextIntoNode(textNode, 0, nonBreakingSpaceString()); 157 else { 158 RefPtr<Text> nbspNode = document()->createTextNode(nonBreakingSpaceString()); 159 insertNodeAt(nbspNode.get(), positionBeforeTextNode); 160 endingPosition = firstPositionInNode(nbspNode.get()); 161 } 162 } 163 164 setEndingSelection(VisibleSelection(endingPosition, DOWNSTREAM, endingSelection().isDirectional())); 165 } 166 167 // Handle the case where there is a typing style. 168 169 RefPtr<EditingStyle> typingStyle = document()->frame()->selection()->typingStyle(); 170 171 if (typingStyle && !typingStyle->isEmpty()) { 172 // Apply the typing style to the inserted line break, so that if the selection 173 // leaves and then comes back, new input will have the right style. 174 // FIXME: We shouldn't always apply the typing style to the line break here, 175 // see <rdar://problem/5794462>. 176 applyStyle(typingStyle.get(), firstPositionInOrBeforeNode(nodeToInsert.get()), lastPositionInOrAfterNode(nodeToInsert.get())); 177 // Even though this applyStyle operates on a Range, it still sets an endingSelection(). 178 // It tries to set a VisibleSelection around the content it operated on. So, that VisibleSelection 179 // will either (a) select the line break we inserted, or it will (b) be a caret just 180 // before the line break (if the line break is at the end of a block it isn't selectable). 181 // So, this next call sets the endingSelection() to a caret just after the line break 182 // that we inserted, or just before it if it's at the end of a block. 183 setEndingSelection(endingSelection().visibleEnd()); 184 } 185 186 rebalanceWhitespace(); 187} 188 189} 190