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