• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 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 "Element.h"
28 #include "InsertListCommand.h"
29 #include "DocumentFragment.h"
30 #include "htmlediting.h"
31 #include "HTMLElement.h"
32 #include "HTMLNames.h"
33 #include "TextIterator.h"
34 #include "visible_units.h"
35 
36 namespace WebCore {
37 
38 using namespace HTMLNames;
39 
insertList(Document * document,Type type)40 PassRefPtr<HTMLElement> InsertListCommand::insertList(Document* document, Type type)
41 {
42     RefPtr<InsertListCommand> insertCommand = new InsertListCommand(document, type);
43     insertCommand->apply();
44     return insertCommand->m_listElement;
45 }
46 
fixOrphanedListChild(Node * node)47 HTMLElement* InsertListCommand::fixOrphanedListChild(Node* node)
48 {
49     RefPtr<HTMLElement> listElement = createUnorderedListElement(document());
50     insertNodeBefore(listElement, node);
51     removeNode(node);
52     appendNode(node, listElement);
53     m_listElement = listElement;
54     return listElement.get();
55 }
56 
InsertListCommand(Document * document,Type type)57 InsertListCommand::InsertListCommand(Document* document, Type type)
58     : CompositeEditCommand(document), m_type(type), m_forceCreateList(false)
59 {
60 }
61 
modifyRange()62 bool InsertListCommand::modifyRange()
63 {
64     VisibleSelection selection = selectionForParagraphIteration(endingSelection());
65     ASSERT(selection.isRange());
66     VisiblePosition startOfSelection = selection.visibleStart();
67     VisiblePosition endOfSelection = selection.visibleEnd();
68     VisiblePosition startOfLastParagraph = startOfParagraph(endOfSelection);
69 
70     if (startOfParagraph(startOfSelection) == startOfLastParagraph)
71         return false;
72 
73     Node* startList = enclosingList(startOfSelection.deepEquivalent().node());
74     Node* endList = enclosingList(endOfSelection.deepEquivalent().node());
75     if (!startList || startList != endList)
76         m_forceCreateList = true;
77 
78     setEndingSelection(startOfSelection);
79     doApply();
80     // Fetch the start of the selection after moving the first paragraph,
81     // because moving the paragraph will invalidate the original start.
82     // We'll use the new start to restore the original selection after
83     // we modified all selected paragraphs.
84     startOfSelection = endingSelection().visibleStart();
85     VisiblePosition startOfCurrentParagraph = startOfNextParagraph(startOfSelection);
86     while (startOfCurrentParagraph != startOfLastParagraph) {
87         // doApply() may operate on and remove the last paragraph of the selection from the document
88         // if it's in the same list item as startOfCurrentParagraph.  Return early to avoid an
89         // infinite loop and because there is no more work to be done.
90         // FIXME(<rdar://problem/5983974>): The endingSelection() may be incorrect here.  Compute
91         // the new location of endOfSelection and use it as the end of the new selection.
92         if (!startOfLastParagraph.deepEquivalent().node()->inDocument())
93             return true;
94         setEndingSelection(startOfCurrentParagraph);
95         doApply();
96         startOfCurrentParagraph = startOfNextParagraph(endingSelection().visibleStart());
97     }
98     setEndingSelection(endOfSelection);
99     doApply();
100     // Fetch the end of the selection, for the reason mentioned above.
101     endOfSelection = endingSelection().visibleEnd();
102     setEndingSelection(VisibleSelection(startOfSelection, endOfSelection));
103     m_forceCreateList = false;
104     return true;
105 }
106 
doApply()107 void InsertListCommand::doApply()
108 {
109     if (endingSelection().isNone())
110         return;
111 
112     if (!endingSelection().rootEditableElement())
113         return;
114 
115     VisiblePosition visibleEnd = endingSelection().visibleEnd();
116     VisiblePosition visibleStart = endingSelection().visibleStart();
117     // When a selection ends at the start of a paragraph, we rarely paint
118     // the selection gap before that paragraph, because there often is no gap.
119     // In a case like this, it's not obvious to the user that the selection
120     // ends "inside" that paragraph, so it would be confusing if InsertUn{Ordered}List
121     // operated on that paragraph.
122     // FIXME: We paint the gap before some paragraphs that are indented with left
123     // margin/padding, but not others.  We should make the gap painting more consistent and
124     // then use a left margin/padding rule here.
125     if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd))
126         setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true)));
127 
128     if (endingSelection().isRange() && modifyRange())
129         return;
130 
131     // FIXME: This will produce unexpected results for a selection that starts just before a
132     // table and ends inside the first cell, selectionForParagraphIteration should probably
133     // be renamed and deployed inside setEndingSelection().
134     Node* selectionNode = endingSelection().start().node();
135     const QualifiedName listTag = (m_type == OrderedList) ? olTag : ulTag;
136     Node* listChildNode = enclosingListChild(selectionNode);
137     bool switchListType = false;
138     if (listChildNode) {
139         // Remove the list chlild.
140         HTMLElement* listNode = enclosingList(listChildNode);
141         if (!listNode)
142             listNode = fixOrphanedListChild(listChildNode);
143         if (!listNode->hasTagName(listTag))
144             // listChildNode will be removed from the list and a list of type m_type will be created.
145             switchListType = true;
146         Node* nextListChild;
147         Node* previousListChild;
148         VisiblePosition start;
149         VisiblePosition end;
150         if (listChildNode->hasTagName(liTag)) {
151             start = firstDeepEditingPositionForNode(listChildNode);
152             end = lastDeepEditingPositionForNode(listChildNode);
153             nextListChild = listChildNode->nextSibling();
154             previousListChild = listChildNode->previousSibling();
155         } else {
156             // A paragraph is visually a list item minus a list marker.  The paragraph will be moved.
157             start = startOfParagraph(endingSelection().visibleStart());
158             end = endOfParagraph(endingSelection().visibleEnd());
159             nextListChild = enclosingListChild(end.next().deepEquivalent().node());
160             ASSERT(nextListChild != listChildNode);
161             if (enclosingList(nextListChild) != listNode)
162                 nextListChild = 0;
163             previousListChild = enclosingListChild(start.previous().deepEquivalent().node());
164             ASSERT(previousListChild != listChildNode);
165             if (enclosingList(previousListChild) != listNode)
166                 previousListChild = 0;
167         }
168         // When removing a list, we must always create a placeholder to act as a point of insertion
169         // for the list content being removed.
170         RefPtr<Element> placeholder = createBreakElement(document());
171         RefPtr<Element> nodeToInsert = placeholder;
172         // If the content of the list item will be moved into another list, put it in a list item
173         // so that we don't create an orphaned list child.
174         if (enclosingList(listNode)) {
175             nodeToInsert = createListItemElement(document());
176             appendNode(placeholder, nodeToInsert);
177         }
178 
179         if (nextListChild && previousListChild) {
180             // We want to pull listChildNode out of listNode, and place it before nextListChild
181             // and after previousListChild, so we split listNode and insert it between the two lists.
182             // But to split listNode, we must first split ancestors of listChildNode between it and listNode,
183             // if any exist.
184             // FIXME: We appear to split at nextListChild as opposed to listChildNode so that when we remove
185             // listChildNode below in moveParagraphs, previousListChild will be removed along with it if it is
186             // unrendered. But we ought to remove nextListChild too, if it is unrendered.
187             splitElement(listNode, splitTreeToNode(nextListChild, listNode));
188             insertNodeBefore(nodeToInsert, listNode);
189         } else if (nextListChild || listChildNode->parentNode() != listNode) {
190             // Just because listChildNode has no previousListChild doesn't mean there isn't any content
191             // in listNode that comes before listChildNode, as listChildNode could have ancestors
192             // between it and listNode. So, we split up to listNode before inserting the placeholder
193             // where we're about to move listChildNode to.
194             if (listChildNode->parentNode() != listNode)
195                 splitElement(listNode, splitTreeToNode(listChildNode, listNode).get());
196             insertNodeBefore(nodeToInsert, listNode);
197         } else
198             insertNodeAfter(nodeToInsert, listNode);
199 
200         VisiblePosition insertionPoint = VisiblePosition(Position(placeholder.get(), 0));
201         moveParagraphs(start, end, insertionPoint, true);
202     }
203     if (!listChildNode || switchListType || m_forceCreateList) {
204         // Create list.
205         VisiblePosition originalStart = endingSelection().visibleStart();
206         VisiblePosition start = startOfParagraph(originalStart);
207         VisiblePosition end = endOfParagraph(endingSelection().visibleEnd());
208 
209         // Check for adjoining lists.
210         VisiblePosition previousPosition = start.previous(true);
211         VisiblePosition nextPosition = end.next(true);
212         RefPtr<HTMLElement> listItemElement = createListItemElement(document());
213         RefPtr<HTMLElement> placeholder = createBreakElement(document());
214         appendNode(placeholder, listItemElement);
215         Element* previousList = outermostEnclosingList(previousPosition.deepEquivalent().node());
216         Element* nextList = outermostEnclosingList(nextPosition.deepEquivalent().node());
217         Node* startNode = start.deepEquivalent().node();
218         Node* previousCell = enclosingTableCell(previousPosition.deepEquivalent());
219         Node* nextCell = enclosingTableCell(nextPosition.deepEquivalent());
220         Node* currentCell = enclosingTableCell(start.deepEquivalent());
221         if (previousList && (!previousList->hasTagName(listTag) || startNode->isDescendantOf(previousList) || previousCell != currentCell))
222             previousList = 0;
223         if (nextList && (!nextList->hasTagName(listTag) || startNode->isDescendantOf(nextList) || nextCell != currentCell))
224             nextList = 0;
225         // Place list item into adjoining lists.
226         if (previousList)
227             appendNode(listItemElement, previousList);
228         else if (nextList)
229             insertNodeAt(listItemElement, Position(nextList, 0));
230         else {
231             // Create the list.
232             RefPtr<HTMLElement> listElement = m_type == OrderedList ? createOrderedListElement(document()) : createUnorderedListElement(document());
233             m_listElement = listElement;
234             appendNode(listItemElement, listElement);
235 
236             if (start == end && isBlock(start.deepEquivalent().node())) {
237                 // Inserting the list into an empty paragraph that isn't held open
238                 // by a br or a '\n', will invalidate start and end.  Insert
239                 // a placeholder and then recompute start and end.
240                 RefPtr<Node> placeholder = insertBlockPlaceholder(start.deepEquivalent());
241                 start = VisiblePosition(Position(placeholder.get(), 0));
242                 end = start;
243             }
244 
245             // Insert the list at a position visually equivalent to start of the
246             // paragraph that is being moved into the list.
247             // Try to avoid inserting it somewhere where it will be surrounded by
248             // inline ancestors of start, since it is easier for editing to produce
249             // clean markup when inline elements are pushed down as far as possible.
250             Position insertionPos(start.deepEquivalent().upstream());
251             // Also avoid the containing list item.
252             Node* listChild = enclosingListChild(insertionPos.node());
253             if (listChild && listChild->hasTagName(liTag))
254                 insertionPos = positionBeforeNode(listChild);
255 
256             insertNodeAt(listElement, insertionPos);
257 
258             // We inserted the list at the start of the content we're about to move
259             // Update the start of content, so we don't try to move the list into itself.  bug 19066
260             if (insertionPos == start.deepEquivalent())
261                 start = startOfParagraph(originalStart);
262         }
263         moveParagraph(start, end, VisiblePosition(Position(placeholder.get(), 0)), true);
264         if (nextList && previousList)
265             mergeIdenticalElements(previousList, nextList);
266     }
267 }
268 
269 }
270