• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "InsertParagraphSeparatorCommand.h"
28 
29 #include "CSSComputedStyleDeclaration.h"
30 #include "CSSMutableStyleDeclaration.h"
31 #include "CSSPropertyNames.h"
32 #include "Document.h"
33 #include "HTMLElement.h"
34 #include "HTMLNames.h"
35 #include "InsertLineBreakCommand.h"
36 #include "Logging.h"
37 #include "RenderObject.h"
38 #include "Text.h"
39 #include "htmlediting.h"
40 #include "visible_units.h"
41 #include "ApplyStyleCommand.h"
42 
43 namespace WebCore {
44 
45 using namespace HTMLNames;
46 
InsertParagraphSeparatorCommand(Document * document,bool mustUseDefaultParagraphElement)47 InsertParagraphSeparatorCommand::InsertParagraphSeparatorCommand(Document *document, bool mustUseDefaultParagraphElement)
48     : CompositeEditCommand(document)
49     , m_mustUseDefaultParagraphElement(mustUseDefaultParagraphElement)
50 {
51 }
52 
preservesTypingStyle() const53 bool InsertParagraphSeparatorCommand::preservesTypingStyle() const
54 {
55     return true;
56 }
57 
calculateStyleBeforeInsertion(const Position & pos)58 void InsertParagraphSeparatorCommand::calculateStyleBeforeInsertion(const Position &pos)
59 {
60     // It is only important to set a style to apply later if we're at the boundaries of
61     // a paragraph. Otherwise, content that is moved as part of the work of the command
62     // will lend their styles to the new paragraph without any extra work needed.
63     VisiblePosition visiblePos(pos, VP_DEFAULT_AFFINITY);
64     if (!isStartOfParagraph(visiblePos) && !isEndOfParagraph(visiblePos))
65         return;
66 
67     m_style = editingStyleAtPosition(pos, IncludeTypingStyle);
68 }
69 
applyStyleAfterInsertion(Node * originalEnclosingBlock)70 void InsertParagraphSeparatorCommand::applyStyleAfterInsertion(Node* originalEnclosingBlock)
71 {
72     // Not only do we break out of header tags, but we also do not preserve the typing style,
73     // in order to match other browsers.
74     if (originalEnclosingBlock->hasTagName(h1Tag) ||
75         originalEnclosingBlock->hasTagName(h2Tag) ||
76         originalEnclosingBlock->hasTagName(h3Tag) ||
77         originalEnclosingBlock->hasTagName(h4Tag) ||
78         originalEnclosingBlock->hasTagName(h5Tag))
79         return;
80 
81     if (!m_style)
82         return;
83 
84     prepareEditingStyleToApplyAt(m_style.get(), endingSelection().start());
85 
86     if (m_style->length() > 0)
87         applyStyle(m_style.get());
88 }
89 
shouldUseDefaultParagraphElement(Node * enclosingBlock) const90 bool InsertParagraphSeparatorCommand::shouldUseDefaultParagraphElement(Node* enclosingBlock) const
91 {
92     if (m_mustUseDefaultParagraphElement)
93         return true;
94 
95     // Assumes that if there was a range selection, it was already deleted.
96     if (!isEndOfBlock(endingSelection().visibleStart()))
97         return false;
98 
99     return enclosingBlock->hasTagName(h1Tag) ||
100            enclosingBlock->hasTagName(h2Tag) ||
101            enclosingBlock->hasTagName(h3Tag) ||
102            enclosingBlock->hasTagName(h4Tag) ||
103            enclosingBlock->hasTagName(h5Tag);
104 }
105 
doApply()106 void InsertParagraphSeparatorCommand::doApply()
107 {
108     bool splitText = false;
109     if (endingSelection().isNone())
110         return;
111 
112     Position insertionPosition = endingSelection().start();
113 
114     EAffinity affinity = endingSelection().affinity();
115 
116     // Delete the current selection.
117     if (endingSelection().isRange()) {
118         calculateStyleBeforeInsertion(insertionPosition);
119         deleteSelection(false, true);
120         insertionPosition = endingSelection().start();
121         affinity = endingSelection().affinity();
122     }
123 
124     // FIXME: The rangeCompliantEquivalent conversion needs to be moved into enclosingBlock.
125     Node* startBlockNode = enclosingBlock(rangeCompliantEquivalent(insertionPosition).node());
126     Position canonicalPos = VisiblePosition(insertionPosition).deepEquivalent();
127     Element* startBlock = static_cast<Element*>(startBlockNode);
128     if (!startBlockNode
129             || !startBlockNode->isElementNode()
130             || !startBlock->parentNode()
131             || isTableCell(startBlock)
132             || startBlock->hasTagName(formTag)
133             || (canonicalPos.node()->renderer() && canonicalPos.node()->renderer()->isTable())
134             || canonicalPos.node()->hasTagName(hrTag)) {
135         applyCommandToComposite(InsertLineBreakCommand::create(document()));
136         return;
137     }
138 
139     // Use the leftmost candidate.
140     insertionPosition = insertionPosition.upstream();
141     if (!insertionPosition.isCandidate())
142         insertionPosition = insertionPosition.downstream();
143 
144     // Adjust the insertion position after the delete
145     insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition);
146     VisiblePosition visiblePos(insertionPosition, affinity);
147     calculateStyleBeforeInsertion(insertionPosition);
148 
149     //---------------------------------------------------------------------
150     // Handle special case of typing return on an empty list item
151     if (breakOutOfEmptyListItem())
152         return;
153 
154     //---------------------------------------------------------------------
155     // Prepare for more general cases.
156 
157     bool isFirstInBlock = isStartOfBlock(visiblePos);
158     bool isLastInBlock = isEndOfBlock(visiblePos);
159     bool nestNewBlock = false;
160 
161     // Create block to be inserted.
162     RefPtr<Element> blockToInsert;
163     if (startBlock == startBlock->rootEditableElement()) {
164         blockToInsert = createDefaultParagraphElement(document());
165         nestNewBlock = true;
166     } else if (shouldUseDefaultParagraphElement(startBlock))
167         blockToInsert = createDefaultParagraphElement(document());
168     else
169         blockToInsert = startBlock->cloneElementWithoutChildren();
170 
171     //---------------------------------------------------------------------
172     // Handle case when position is in the last visible position in its block,
173     // including when the block is empty.
174     if (isLastInBlock) {
175         bool shouldApplyStyleAfterInsertion = true;
176         if (nestNewBlock) {
177             if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) {
178                 // The block is empty.  Create an empty block to
179                 // represent the paragraph that we're leaving.
180                 RefPtr<Element> extraBlock = createDefaultParagraphElement(document());
181                 appendNode(extraBlock, startBlock);
182                 appendBlockPlaceholder(extraBlock);
183             }
184             appendNode(blockToInsert, startBlock);
185         } else {
186             // We can get here if we pasted a copied portion of a blockquote with a newline at the end and are trying to paste it
187             // into an unquoted area. We then don't want the newline within the blockquote or else it will also be quoted.
188             if (Node* highestBlockquote = highestEnclosingNodeOfType(canonicalPos, &isMailBlockquote)) {
189                 startBlock = static_cast<Element*>(highestBlockquote);
190                 // When inserting the newline after the blockquote, we don't want to apply the original style after the insertion
191                 shouldApplyStyleAfterInsertion = false;
192             }
193             insertNodeAfter(blockToInsert, startBlock);
194         }
195 
196         appendBlockPlaceholder(blockToInsert);
197         setEndingSelection(VisibleSelection(Position(blockToInsert.get(), 0), DOWNSTREAM));
198         if (shouldApplyStyleAfterInsertion)
199             applyStyleAfterInsertion(startBlock);
200         return;
201     }
202 
203     //---------------------------------------------------------------------
204     // Handle case when position is in the first visible position in its block, and
205     // similar case where previous position is in another, presumeably nested, block.
206     if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) {
207         Node *refNode;
208         if (isFirstInBlock && !nestNewBlock)
209             refNode = startBlock;
210         else if (insertionPosition.node() == startBlock && nestNewBlock) {
211             refNode = startBlock->childNode(insertionPosition.deprecatedEditingOffset());
212             ASSERT(refNode); // must be true or we'd be in the end of block case
213         } else
214             refNode = insertionPosition.node();
215 
216         // find ending selection position easily before inserting the paragraph
217         insertionPosition = insertionPosition.downstream();
218 
219         insertNodeBefore(blockToInsert, refNode);
220         appendBlockPlaceholder(blockToInsert.get());
221         setEndingSelection(VisibleSelection(Position(blockToInsert.get(), 0), DOWNSTREAM));
222         applyStyleAfterInsertion(startBlock);
223         setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM));
224         return;
225     }
226 
227     //---------------------------------------------------------------------
228     // Handle the (more complicated) general case,
229 
230     // All of the content in the current block after visiblePos is
231     // about to be wrapped in a new paragraph element.  Add a br before
232     // it if visiblePos is at the start of a paragraph so that the
233     // content will move down a line.
234     if (isStartOfParagraph(visiblePos)) {
235         RefPtr<Element> br = createBreakElement(document());
236         insertNodeAt(br.get(), insertionPosition);
237         insertionPosition = positionAfterNode(br.get());
238     }
239 
240     // Move downstream. Typing style code will take care of carrying along the
241     // style of the upstream position.
242     insertionPosition = insertionPosition.downstream();
243 
244     // At this point, the insertionPosition's node could be a container, and we want to make sure we include
245     // all of the correct nodes when building the ancestor list.  So this needs to be the deepest representation of the position
246     // before we walk the DOM tree.
247     insertionPosition = VisiblePosition(insertionPosition).deepEquivalent();
248 
249     // Build up list of ancestors in between the start node and the start block.
250     Vector<Element*> ancestors;
251     if (insertionPosition.node() != startBlock) {
252         for (Element* n = insertionPosition.node()->parentElement(); n && n != startBlock; n = n->parentElement())
253             ancestors.append(n);
254     }
255 
256     // Make sure we do not cause a rendered space to become unrendered.
257     // FIXME: We need the affinity for pos, but pos.downstream() does not give it
258     Position leadingWhitespace = insertionPosition.leadingWhitespacePosition(VP_DEFAULT_AFFINITY);
259     // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions
260     // after the preserved newline, causing the newline to be turned into a nbsp.
261     if (leadingWhitespace.isNotNull()) {
262         Text* textNode = static_cast<Text*>(leadingWhitespace.node());
263         ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace());
264         replaceTextInNode(textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString());
265     }
266 
267     // Split at pos if in the middle of a text node.
268     if (insertionPosition.node()->isTextNode()) {
269         Text* textNode = static_cast<Text*>(insertionPosition.node());
270         bool atEnd = (unsigned)insertionPosition.deprecatedEditingOffset() >= textNode->length();
271         if (insertionPosition.deprecatedEditingOffset() > 0 && !atEnd) {
272             splitTextNode(textNode, insertionPosition.deprecatedEditingOffset());
273             insertionPosition.moveToOffset(0);
274             visiblePos = VisiblePosition(insertionPosition);
275             splitText = true;
276         }
277     }
278 
279     // Put the added block in the tree.
280     if (nestNewBlock)
281         appendNode(blockToInsert.get(), startBlock);
282     else
283         insertNodeAfter(blockToInsert.get(), startBlock);
284 
285     updateLayout();
286 
287     // Make clones of ancestors in between the start node and the start block.
288     RefPtr<Element> parent = blockToInsert;
289     for (size_t i = ancestors.size(); i != 0; --i) {
290         RefPtr<Element> child = ancestors[i - 1]->cloneElementWithoutChildren();
291         appendNode(child, parent);
292         parent = child.release();
293     }
294 
295     // If the paragraph separator was inserted at the end of a paragraph, an empty line must be
296     // created.  All of the nodes, starting at visiblePos, are about to be added to the new paragraph
297     // element.  If the first node to be inserted won't be one that will hold an empty line open, add a br.
298     if (isEndOfParagraph(visiblePos) && !lineBreakExistsAtVisiblePosition(visiblePos))
299         appendNode(createBreakElement(document()).get(), blockToInsert.get());
300 
301     // Move the start node and the siblings of the start node.
302     if (insertionPosition.node() != startBlock) {
303         Node* n = insertionPosition.node();
304         if (insertionPosition.deprecatedEditingOffset() >= caretMaxOffset(n))
305             n = n->nextSibling();
306 
307         while (n && n != blockToInsert) {
308             Node *next = n->nextSibling();
309             removeNode(n);
310             appendNode(n, parent.get());
311             n = next;
312         }
313     }
314 
315     // Move everything after the start node.
316     if (!ancestors.isEmpty()) {
317         Element* leftParent = ancestors.first();
318         while (leftParent && leftParent != startBlock) {
319             parent = parent->parentElement();
320             if (!parent)
321                 break;
322             Node* n = leftParent->nextSibling();
323             while (n && n != blockToInsert) {
324                 Node* next = n->nextSibling();
325                 removeNode(n);
326                 appendNode(n, parent.get());
327                 n = next;
328             }
329             leftParent = leftParent->parentElement();
330         }
331     }
332 
333     // Handle whitespace that occurs after the split
334     if (splitText) {
335         updateLayout();
336         insertionPosition = Position(insertionPosition.node(), 0);
337         if (!insertionPosition.isRenderedCharacter()) {
338             // Clear out all whitespace and insert one non-breaking space
339             ASSERT(insertionPosition.node()->isTextNode());
340             ASSERT(!insertionPosition.node()->renderer() || insertionPosition.node()->renderer()->style()->collapseWhiteSpace());
341             deleteInsignificantTextDownstream(insertionPosition);
342             insertTextIntoNode(static_cast<Text*>(insertionPosition.node()), 0, nonBreakingSpaceString());
343         }
344     }
345 
346     setEndingSelection(VisibleSelection(Position(blockToInsert.get(), 0), DOWNSTREAM));
347     applyStyleAfterInsertion(startBlock);
348 }
349 
350 } // namespace WebCore
351