• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006, 2008 Apple Inc. All rights reserved.
3  * Copyright (C) 2010 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
16  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
17  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
18  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
19  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
20  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26 
27 #include "config.h"
28 #include "ApplyBlockElementCommand.h"
29 
30 #include "HTMLElement.h"
31 #include "HTMLNames.h"
32 #include "Text.h"
33 #include "TextIterator.h"
34 #include "VisiblePosition.h"
35 #include "htmlediting.h"
36 #include "visible_units.h"
37 
38 namespace WebCore {
39 
40 using namespace HTMLNames;
41 
ApplyBlockElementCommand(Document * document,const QualifiedName & tagName,const AtomicString & className,const AtomicString & inlineStyle)42 ApplyBlockElementCommand::ApplyBlockElementCommand(Document* document, const QualifiedName& tagName, const AtomicString& className, const AtomicString& inlineStyle)
43     : CompositeEditCommand(document)
44     , m_tagName(tagName)
45     , m_className(className)
46     , m_inlineStyle(inlineStyle)
47 {
48 }
49 
ApplyBlockElementCommand(Document * document,const QualifiedName & tagName)50 ApplyBlockElementCommand::ApplyBlockElementCommand(Document* document, const QualifiedName& tagName)
51     : CompositeEditCommand(document)
52     , m_tagName(tagName)
53 {
54 }
55 
doApply()56 void ApplyBlockElementCommand::doApply()
57 {
58     if (!endingSelection().isNonOrphanedCaretOrRange())
59         return;
60 
61     if (!endingSelection().rootEditableElement())
62         return;
63 
64     VisiblePosition visibleEnd = endingSelection().visibleEnd();
65     VisiblePosition visibleStart = endingSelection().visibleStart();
66     // When a selection ends at the start of a paragraph, we rarely paint
67     // the selection gap before that paragraph, because there often is no gap.
68     // In a case like this, it's not obvious to the user that the selection
69     // ends "inside" that paragraph, so it would be confusing if Indent/Outdent
70     // operated on that paragraph.
71     // FIXME: We paint the gap before some paragraphs that are indented with left
72     // margin/padding, but not others.  We should make the gap painting more consistent and
73     // then use a left margin/padding rule here.
74     if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd))
75         setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(CannotCrossEditingBoundary)));
76 
77     VisibleSelection selection = selectionForParagraphIteration(endingSelection());
78     VisiblePosition startOfSelection = selection.visibleStart();
79     VisiblePosition endOfSelection = selection.visibleEnd();
80     ASSERT(!startOfSelection.isNull());
81     ASSERT(!endOfSelection.isNull());
82     int startIndex = indexForVisiblePosition(startOfSelection);
83     int endIndex = indexForVisiblePosition(endOfSelection);
84 
85     formatSelection(startOfSelection, endOfSelection);
86 
87     updateLayout();
88 
89     RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true);
90     RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true);
91     if (startRange && endRange)
92         setEndingSelection(VisibleSelection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM));
93 }
94 
formatSelection(const VisiblePosition & startOfSelection,const VisiblePosition & endOfSelection)95 void ApplyBlockElementCommand::formatSelection(const VisiblePosition& startOfSelection, const VisiblePosition& endOfSelection)
96 {
97     // Special case empty unsplittable elements because there's nothing to split
98     // and there's nothing to move.
99     Position start = startOfSelection.deepEquivalent().downstream();
100     if (isAtUnsplittableElement(start)) {
101         RefPtr<Element> blockquote = createBlockElement();
102         insertNodeAt(blockquote, start);
103         RefPtr<Element> placeholder = createBreakElement(document());
104         appendNode(placeholder, blockquote);
105         setEndingSelection(VisibleSelection(positionBeforeNode(placeholder.get()), DOWNSTREAM));
106         return;
107     }
108 
109     RefPtr<Element> blockquoteForNextIndent;
110     VisiblePosition endOfCurrentParagraph = endOfParagraph(startOfSelection);
111     VisiblePosition endAfterSelection = endOfParagraph(endOfParagraph(endOfSelection).next());
112     m_endOfLastParagraph = endOfParagraph(endOfSelection).deepEquivalent();
113 
114     bool atEnd = false;
115     Position end;
116     while (endOfCurrentParagraph != endAfterSelection && !atEnd) {
117         if (endOfCurrentParagraph.deepEquivalent() == m_endOfLastParagraph)
118             atEnd = true;
119 
120         rangeForParagraphSplittingTextNodesIfNeeded(endOfCurrentParagraph, start, end);
121         endOfCurrentParagraph = end;
122 
123         Position afterEnd = end.next();
124         Node* enclosingCell = enclosingNodeOfType(start, &isTableCell);
125         VisiblePosition endOfNextParagraph = endOfNextParagrahSplittingTextNodesIfNeeded(endOfCurrentParagraph, start, end);
126 
127         formatRange(start, end, m_endOfLastParagraph, blockquoteForNextIndent);
128 
129         // Don't put the next paragraph in the blockquote we just created for this paragraph unless
130         // the next paragraph is in the same cell.
131         if (enclosingCell && enclosingCell != enclosingNodeOfType(endOfNextParagraph.deepEquivalent(), &isTableCell))
132             blockquoteForNextIndent = 0;
133 
134         // indentIntoBlockquote could move more than one paragraph if the paragraph
135         // is in a list item or a table. As a result, endAfterSelection could refer to a position
136         // no longer in the document.
137         if (endAfterSelection.isNotNull() && !endAfterSelection.deepEquivalent().anchorNode()->inDocument())
138             break;
139         // Sanity check: Make sure our moveParagraph calls didn't remove endOfNextParagraph.deepEquivalent().deprecatedNode()
140         // If somehow we did, return to prevent crashes.
141         if (endOfNextParagraph.isNotNull() && !endOfNextParagraph.deepEquivalent().anchorNode()->inDocument()) {
142             ASSERT_NOT_REACHED();
143             return;
144         }
145         endOfCurrentParagraph = endOfNextParagraph;
146     }
147 }
148 
isNewLineAtPosition(const Position & position)149 static bool isNewLineAtPosition(const Position& position)
150 {
151     if (position.anchorType() != Position::PositionIsOffsetInAnchor)
152         return false;
153 
154     Node* textNode = position.containerNode();
155     int offset = position.offsetInContainerNode();
156     if (!textNode || !textNode->isTextNode() || offset < 0 || offset >= textNode->maxCharacterOffset())
157         return false;
158 
159     ExceptionCode ec = 0;
160     String textAtPosition = static_cast<Text*>(textNode)->substringData(offset, 1, ec);
161     if (ec)
162         return false;
163 
164     return textAtPosition[0] == '\n';
165 }
166 
renderStyleOfEnclosingTextNode(const Position & position)167 static RenderStyle* renderStyleOfEnclosingTextNode(const Position& position)
168 {
169     if (position.anchorType() != Position::PositionIsOffsetInAnchor
170         || !position.containerNode()
171         || !position.containerNode()->isTextNode()
172         || !position.containerNode()->renderer())
173         return 0;
174     return position.containerNode()->renderer()->style();
175 }
176 
rangeForParagraphSplittingTextNodesIfNeeded(const VisiblePosition & endOfCurrentParagraph,Position & start,Position & end)177 void ApplyBlockElementCommand::rangeForParagraphSplittingTextNodesIfNeeded(const VisiblePosition& endOfCurrentParagraph, Position& start, Position& end)
178 {
179     start = startOfParagraph(endOfCurrentParagraph).deepEquivalent();
180     end = endOfCurrentParagraph.deepEquivalent();
181 
182     RenderStyle* startStyle = renderStyleOfEnclosingTextNode(start);
183     bool isStartAndEndOnSameNode = false;
184     if (startStyle) {
185         isStartAndEndOnSameNode = renderStyleOfEnclosingTextNode(end) && start.deprecatedNode() == end.deprecatedNode();
186         bool isStartAndEndOfLastParagraphOnSameNode = renderStyleOfEnclosingTextNode(m_endOfLastParagraph) && start.deprecatedNode() == m_endOfLastParagraph.deprecatedNode();
187 
188         // Avoid obtanining the start of next paragraph for start
189         if (startStyle->preserveNewline() && isNewLineAtPosition(start) && !isNewLineAtPosition(start.previous()) && start.offsetInContainerNode() > 0)
190             start = startOfParagraph(end.previous()).deepEquivalent();
191 
192         // If start is in the middle of a text node, split.
193         if (!startStyle->collapseWhiteSpace() && start.offsetInContainerNode() > 0) {
194             int startOffset = start.offsetInContainerNode();
195             splitTextNode(static_cast<Text*>(start.deprecatedNode()), startOffset);
196             start = firstPositionInOrBeforeNode(start.deprecatedNode());
197             if (isStartAndEndOnSameNode) {
198                 ASSERT(end.offsetInContainerNode() >= startOffset);
199                 end = Position(end.deprecatedNode(), end.offsetInContainerNode() - startOffset, Position::PositionIsOffsetInAnchor);
200             }
201             if (isStartAndEndOfLastParagraphOnSameNode) {
202                 ASSERT(m_endOfLastParagraph.offsetInContainerNode() >= startOffset);
203                 m_endOfLastParagraph = Position(m_endOfLastParagraph.deprecatedNode(), m_endOfLastParagraph.offsetInContainerNode() - startOffset,
204                     Position::PositionIsOffsetInAnchor);
205             }
206         }
207     }
208 
209     RenderStyle* endStyle = renderStyleOfEnclosingTextNode(end);
210     if (endStyle) {
211         bool isEndAndEndOfLastParagraphOnSameNode = renderStyleOfEnclosingTextNode(m_endOfLastParagraph) && end.deprecatedNode() == m_endOfLastParagraph.deprecatedNode();
212         // Include \n at the end of line if we're at an empty paragraph
213         if (endStyle->preserveNewline() && start == end
214             && end.offsetInContainerNode() < end.containerNode()->maxCharacterOffset()) {
215             int endOffset = end.offsetInContainerNode();
216             if (!isNewLineAtPosition(end.previous()) && isNewLineAtPosition(end))
217                 end = Position(end.deprecatedNode(), endOffset + 1, Position::PositionIsOffsetInAnchor);
218             if (isEndAndEndOfLastParagraphOnSameNode && end.offsetInContainerNode() >= m_endOfLastParagraph.offsetInContainerNode())
219                 m_endOfLastParagraph = end;
220         }
221 
222         // If end is in the middle of a text node, split.
223         if (!endStyle->collapseWhiteSpace() && end.offsetInContainerNode()
224             && end.offsetInContainerNode() < end.containerNode()->maxCharacterOffset()) {
225             splitTextNode(static_cast<Text*>(end.deprecatedNode()), end.offsetInContainerNode());
226             if (isStartAndEndOnSameNode)
227                 start = firstPositionInOrBeforeNode(end.deprecatedNode()->previousSibling());
228             if (isEndAndEndOfLastParagraphOnSameNode) {
229                 if (m_endOfLastParagraph.offsetInContainerNode() == end.offsetInContainerNode())
230                     m_endOfLastParagraph = lastPositionInNode(end.deprecatedNode()->previousSibling());
231                 else
232                     m_endOfLastParagraph = Position(end.deprecatedNode(), m_endOfLastParagraph.offsetInContainerNode() - end.offsetInContainerNode(),
233                                                     Position::PositionIsOffsetInAnchor);
234             }
235             end = lastPositionInNode(end.deprecatedNode()->previousSibling());
236         }
237     }
238 }
239 
endOfNextParagrahSplittingTextNodesIfNeeded(VisiblePosition & endOfCurrentParagraph,Position & start,Position & end)240 VisiblePosition ApplyBlockElementCommand::endOfNextParagrahSplittingTextNodesIfNeeded(VisiblePosition& endOfCurrentParagraph, Position& start, Position& end)
241 {
242     VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next());
243     Position position = endOfNextParagraph.deepEquivalent();
244     RenderStyle* style = renderStyleOfEnclosingTextNode(position);
245     if (!style)
246         return endOfNextParagraph;
247 
248     RefPtr<Node> containerNode = position.containerNode();
249     if (!style->preserveNewline() || !position.offsetInContainerNode()
250         || !isNewLineAtPosition(Position(containerNode.get(), 0, Position::PositionIsOffsetInAnchor)))
251         return endOfNextParagraph;
252 
253     // \n at the beginning of the text node immediately following the current paragraph is trimmed by moveParagraphWithClones.
254     // If endOfNextParagraph was pointing at this same text node, endOfNextParagraph will be shifted by one paragraph.
255     // Avoid this by splitting "\n"
256     splitTextNode(static_cast<Text*>(containerNode.get()), 1);
257 
258     if (start.anchorType() == Position::PositionIsOffsetInAnchor && containerNode.get() == start.containerNode()) {
259         ASSERT(start.offsetInContainerNode() < position.offsetInContainerNode());
260         start = Position(containerNode->previousSibling(), start.offsetInContainerNode(), Position::PositionIsOffsetInAnchor);
261     }
262     if (end.anchorType() == Position::PositionIsOffsetInAnchor && containerNode.get() == end.containerNode()) {
263         ASSERT(end.offsetInContainerNode() < position.offsetInContainerNode());
264         end = Position(containerNode->previousSibling(), end.offsetInContainerNode(), Position::PositionIsOffsetInAnchor);
265     }
266     if (m_endOfLastParagraph.anchorType() == Position::PositionIsOffsetInAnchor && containerNode.get() == m_endOfLastParagraph.containerNode()) {
267         if (m_endOfLastParagraph.offsetInContainerNode() < position.offsetInContainerNode())
268             m_endOfLastParagraph = Position(containerNode->previousSibling(), m_endOfLastParagraph.offsetInContainerNode(), Position::PositionIsOffsetInAnchor);
269         else
270             m_endOfLastParagraph = Position(containerNode, m_endOfLastParagraph.offsetInContainerNode() - 1, Position::PositionIsOffsetInAnchor);
271     }
272 
273     return Position(containerNode.get(), position.offsetInContainerNode() - 1, Position::PositionIsOffsetInAnchor);
274 }
275 
createBlockElement() const276 PassRefPtr<Element> ApplyBlockElementCommand::createBlockElement() const
277 {
278     RefPtr<Element> element = createHTMLElement(document(), m_tagName);
279     if (m_className.length())
280         element->setAttribute(classAttr, m_className);
281     if (m_inlineStyle.length())
282         element->setAttribute(styleAttr, m_inlineStyle);
283     return element.release();
284 }
285 
286 }
287