Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(126)

Side by Side Diff: Source/core/editing/InsertListCommand.cpp

Issue 1294543005: Move execCommand related files in core/editing/ related files into core/editing/commands/ (Closed) Base URL: https://chromium.googlesource.com/chromium/blink.git@master
Patch Set: 2015-08-17T17:57:33 Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /*
2 * Copyright (C) 2006, 2010 Apple 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/InsertListCommand.h"
28
29 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
30 #include "core/HTMLNames.h"
31 #include "core/dom/Document.h"
32 #include "core/dom/Element.h"
33 #include "core/dom/ElementTraversal.h"
34 #include "core/editing/EditingUtilities.h"
35 #include "core/editing/VisibleUnits.h"
36 #include "core/editing/iterators/TextIterator.h"
37 #include "core/html/HTMLBRElement.h"
38 #include "core/html/HTMLElement.h"
39 #include "core/html/HTMLLIElement.h"
40 #include "core/html/HTMLUListElement.h"
41
42 namespace blink {
43
44 using namespace HTMLNames;
45
46 static Node* enclosingListChild(Node* node, Node* listNode)
47 {
48 Node* listChild = enclosingListChild(node);
49 while (listChild && enclosingList(listChild) != listNode)
50 listChild = enclosingListChild(listChild->parentNode());
51 return listChild;
52 }
53
54 HTMLUListElement* InsertListCommand::fixOrphanedListChild(Node* node)
55 {
56 RefPtrWillBeRawPtr<HTMLUListElement> listElement = createUnorderedListElemen t(document());
57 insertNodeBefore(listElement, node);
58 removeNode(node);
59 appendNode(node, listElement);
60 m_listElement = listElement;
61 return listElement.get();
62 }
63
64 PassRefPtrWillBeRawPtr<HTMLElement> InsertListCommand::mergeWithNeighboringLists (PassRefPtrWillBeRawPtr<HTMLElement> passedList)
65 {
66 RefPtrWillBeRawPtr<HTMLElement> list = passedList;
67 Element* previousList = ElementTraversal::previousSibling(*list);
68 if (canMergeLists(previousList, list.get()))
69 mergeIdenticalElements(previousList, list);
70
71 if (!list)
72 return nullptr;
73
74 Element* nextSibling = ElementTraversal::nextSibling(*list);
75 if (!nextSibling || !nextSibling->isHTMLElement())
76 return list.release();
77
78 RefPtrWillBeRawPtr<HTMLElement> nextList = toHTMLElement(nextSibling);
79 if (canMergeLists(list.get(), nextList.get())) {
80 mergeIdenticalElements(list, nextList);
81 return nextList.release();
82 }
83 return list.release();
84 }
85
86 bool InsertListCommand::selectionHasListOfType(const VisibleSelection& selection , const HTMLQualifiedName& listTag)
87 {
88 VisiblePosition start = selection.visibleStart();
89
90 if (!enclosingList(start.deepEquivalent().anchorNode()))
91 return false;
92
93 VisiblePosition end = startOfParagraph(selection.visibleEnd());
94 while (start.isNotNull() && start.deepEquivalent() != end.deepEquivalent()) {
95 HTMLElement* listElement = enclosingList(start.deepEquivalent().anchorNo de());
96 if (!listElement || !listElement->hasTagName(listTag))
97 return false;
98 start = startOfNextParagraph(start);
99 }
100
101 return true;
102 }
103
104 InsertListCommand::InsertListCommand(Document& document, Type type)
105 : CompositeEditCommand(document), m_type(type)
106 {
107 }
108
109 void InsertListCommand::doApply()
110 {
111 if (!endingSelection().isNonOrphanedCaretOrRange())
112 return;
113
114 if (!endingSelection().rootEditableElement())
115 return;
116
117 VisiblePosition visibleEnd = endingSelection().visibleEnd();
118 VisiblePosition visibleStart = endingSelection().visibleStart();
119 // When a selection ends at the start of a paragraph, we rarely paint
120 // the selection gap before that paragraph, because there often is no gap.
121 // In a case like this, it's not obvious to the user that the selection
122 // ends "inside" that paragraph, so it would be confusing if InsertUn{Ordere d}List
123 // operated on that paragraph.
124 // FIXME: We paint the gap before some paragraphs that are indented with lef t
125 // margin/padding, but not others. We should make the gap painting more con sistent and
126 // then use a left margin/padding rule here.
127 if (visibleEnd.deepEquivalent() != visibleStart.deepEquivalent() && isStartO fParagraph(visibleEnd, CanSkipOverEditingBoundary)) {
128 setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(Ca nnotCrossEditingBoundary), endingSelection().isDirectional()));
129 if (!endingSelection().rootEditableElement())
130 return;
131 }
132
133 const HTMLQualifiedName& listTag = (m_type == OrderedList) ? olTag : ulTag;
134 if (endingSelection().isRange()) {
135 bool forceListCreation = false;
136 VisibleSelection selection = selectionForParagraphIteration(endingSelect ion());
137 ASSERT(selection.isRange());
138 VisiblePosition startOfSelection = selection.visibleStart();
139 VisiblePosition endOfSelection = selection.visibleEnd();
140 VisiblePosition startOfLastParagraph = startOfParagraph(endOfSelection, CanSkipOverEditingBoundary);
141
142 RefPtrWillBeRawPtr<Range> currentSelection = endingSelection().firstRang e();
143 RefPtrWillBeRawPtr<ContainerNode> scopeForStartOfSelection = nullptr;
144 RefPtrWillBeRawPtr<ContainerNode> scopeForEndOfSelection = nullptr;
145 // FIXME: This is an inefficient way to keep selection alive because
146 // indexForVisiblePosition walks from the beginning of the document to t he
147 // endOfSelection everytime this code is executed. But not using index i s hard
148 // because there are so many ways we can los eselection inside doApplyFo rSingleParagraph.
149 int indexForStartOfSelection = indexForVisiblePosition(startOfSelection, scopeForStartOfSelection);
150 int indexForEndOfSelection = indexForVisiblePosition(endOfSelection, sco peForEndOfSelection);
151
152 if (startOfParagraph(startOfSelection, CanSkipOverEditingBoundary).deepE quivalent() != startOfLastParagraph.deepEquivalent()) {
153 forceListCreation = !selectionHasListOfType(selection, listTag);
154
155 VisiblePosition startOfCurrentParagraph = startOfSelection;
156 while (startOfCurrentParagraph.isNotNull() && !inSameParagraph(start OfCurrentParagraph, startOfLastParagraph, CanCrossEditingBoundary)) {
157 // doApply() may operate on and remove the last paragraph of the selection from the document
158 // if it's in the same list item as startOfCurrentParagraph. Re turn early to avoid an
159 // infinite loop and because there is no more work to be done.
160 // FIXME(<rdar://problem/5983974>): The endingSelection() may be incorrect here. Compute
161 // the new location of endOfSelection and use it as the end of t he new selection.
162 if (!startOfLastParagraph.deepEquivalent().inDocument())
163 return;
164 setEndingSelection(startOfCurrentParagraph);
165
166 // Save and restore endOfSelection and startOfLastParagraph when necessary
167 // since moveParagraph and movePragraphWithClones can remove nod es.
168 if (!doApplyForSingleParagraph(forceListCreation, listTag, *curr entSelection))
169 break;
170 if (endOfSelection.isNull() || endOfSelection.isOrphan() || star tOfLastParagraph.isNull() || startOfLastParagraph.isOrphan()) {
171 endOfSelection = visiblePositionForIndex(indexForEndOfSelect ion, scopeForEndOfSelection.get());
172 // If endOfSelection is null, then some contents have been d eleted from the document.
173 // This should never happen and if it did, exit early immedi ately because we've lost the loop invariant.
174 ASSERT(endOfSelection.isNotNull());
175 if (endOfSelection.isNull())
176 return;
177 startOfLastParagraph = startOfParagraph(endOfSelection, CanS kipOverEditingBoundary);
178 }
179
180 startOfCurrentParagraph = startOfNextParagraph(endingSelection() .visibleStart());
181 }
182 setEndingSelection(endOfSelection);
183 }
184 doApplyForSingleParagraph(forceListCreation, listTag, *currentSelection) ;
185 // Fetch the end of the selection, for the reason mentioned above.
186 if (endOfSelection.isNull() || endOfSelection.isOrphan()) {
187 endOfSelection = visiblePositionForIndex(indexForEndOfSelection, sco peForEndOfSelection.get());
188 if (endOfSelection.isNull())
189 return;
190 }
191 if (startOfSelection.isNull() || startOfSelection.isOrphan()) {
192 startOfSelection = visiblePositionForIndex(indexForStartOfSelection, scopeForStartOfSelection.get());
193 if (startOfSelection.isNull())
194 return;
195 }
196 setEndingSelection(VisibleSelection(startOfSelection, endOfSelection, en dingSelection().isDirectional()));
197 return;
198 }
199
200 ASSERT(endingSelection().firstRange());
201 doApplyForSingleParagraph(false, listTag, *endingSelection().firstRange());
202 }
203
204 bool InsertListCommand::doApplyForSingleParagraph(bool forceCreateList, const HT MLQualifiedName& listTag, Range& currentSelection)
205 {
206 // FIXME: This will produce unexpected results for a selection that starts j ust before a
207 // table and ends inside the first cell, selectionForParagraphIteration shou ld probably
208 // be renamed and deployed inside setEndingSelection().
209 Node* selectionNode = endingSelection().start().anchorNode();
210 Node* listChildNode = enclosingListChild(selectionNode);
211 bool switchListType = false;
212 if (listChildNode) {
213 if (!listChildNode->parentNode()->hasEditableStyle())
214 return false;
215 // Remove the list child.
216 RefPtrWillBeRawPtr<HTMLElement> listElement = enclosingList(listChildNod e);
217 if (!listElement) {
218 listElement = fixOrphanedListChild(listChildNode);
219 listElement = mergeWithNeighboringLists(listElement);
220 }
221 if (!listElement->hasTagName(listTag))
222 // listChildNode will be removed from the list and a list of type m_ type will be created.
223 switchListType = true;
224
225 // If the list is of the desired type, and we are not removing the list, then exit early.
226 if (!switchListType && forceCreateList)
227 return true;
228
229 // If the entire list is selected, then convert the whole list.
230 if (switchListType && isNodeVisiblyContainedWithin(*listElement, current Selection)) {
231 bool rangeStartIsInList = visiblePositionBeforeNode(*listElement).de epEquivalent() == VisiblePosition(currentSelection.startPosition()).deepEquivale nt();
232 bool rangeEndIsInList = visiblePositionAfterNode(*listElement).deepE quivalent() == VisiblePosition(currentSelection.endPosition()).deepEquivalent();
233
234 RefPtrWillBeRawPtr<HTMLElement> newList = createHTMLElement(document (), listTag);
235 insertNodeBefore(newList, listElement);
236
237 Node* firstChildInList = enclosingListChild(VisiblePosition(firstPos itionInNode(listElement.get())).deepEquivalent().anchorNode(), listElement.get() );
238 Element* outerBlock = firstChildInList && isBlockFlowElement(*firstC hildInList) ? toElement(firstChildInList) : listElement.get();
239
240 moveParagraphWithClones(VisiblePosition(firstPositionInNode(listElem ent.get())), VisiblePosition(lastPositionInNode(listElement.get())), newList.get (), outerBlock);
241
242 // Manually remove listNode because moveParagraphWithClones sometime s leaves it behind in the document.
243 // See the bug 33668 and editing/execCommand/insert-list-orphaned-it em-with-nested-lists.html.
244 // FIXME: This might be a bug in moveParagraphWithClones or deleteSe lection.
245 if (listElement && listElement->inDocument())
246 removeNode(listElement);
247
248 newList = mergeWithNeighboringLists(newList);
249
250 // Restore the start and the end of current selection if they starte d inside listNode
251 // because moveParagraphWithClones could have removed them.
252 if (rangeStartIsInList && newList)
253 currentSelection.setStart(newList, 0, IGNORE_EXCEPTION);
254 if (rangeEndIsInList && newList)
255 currentSelection.setEnd(newList, lastOffsetInNode(newList.get()) , IGNORE_EXCEPTION);
256
257 setEndingSelection(VisiblePosition(firstPositionInNode(newList.get() )));
258
259 return true;
260 }
261
262 unlistifyParagraph(endingSelection().visibleStart(), listElement.get(), listChildNode);
263 }
264
265 if (!listChildNode || switchListType || forceCreateList)
266 m_listElement = listifyParagraph(endingSelection().visibleStart(), listT ag);
267
268 return true;
269 }
270
271 void InsertListCommand::unlistifyParagraph(const VisiblePosition& originalStart, HTMLElement* listElement, Node* listChildNode)
272 {
273 Node* nextListChild;
274 Node* previousListChild;
275 VisiblePosition start;
276 VisiblePosition end;
277 ASSERT(listChildNode);
278 if (isHTMLLIElement(*listChildNode)) {
279 start = VisiblePosition(firstPositionInNode(listChildNode));
280 end = VisiblePosition(lastPositionInNode(listChildNode));
281 nextListChild = listChildNode->nextSibling();
282 previousListChild = listChildNode->previousSibling();
283 } else {
284 // A paragraph is visually a list item minus a list marker. The paragra ph will be moved.
285 start = startOfParagraph(originalStart, CanSkipOverEditingBoundary);
286 end = endOfParagraph(start, CanSkipOverEditingBoundary);
287 nextListChild = enclosingListChild(end.next().deepEquivalent().anchorNod e(), listElement);
288 ASSERT(nextListChild != listChildNode);
289 previousListChild = enclosingListChild(start.previous().deepEquivalent() .anchorNode(), listElement);
290 ASSERT(previousListChild != listChildNode);
291 }
292 // When removing a list, we must always create a placeholder to act as a poi nt of insertion
293 // for the list content being removed.
294 RefPtrWillBeRawPtr<HTMLBRElement> placeholder = createBreakElement(document( ));
295 RefPtrWillBeRawPtr<HTMLElement> elementToInsert = placeholder;
296 // If the content of the list item will be moved into another list, put it i n a list item
297 // so that we don't create an orphaned list child.
298 if (enclosingList(listElement)) {
299 elementToInsert = createListItemElement(document());
300 appendNode(placeholder, elementToInsert);
301 }
302
303 if (nextListChild && previousListChild) {
304 // We want to pull listChildNode out of listNode, and place it before ne xtListChild
305 // and after previousListChild, so we split listNode and insert it betwe en the two lists.
306 // But to split listNode, we must first split ancestors of listChildNode between it and listNode,
307 // if any exist.
308 // FIXME: We appear to split at nextListChild as opposed to listChildNod e so that when we remove
309 // listChildNode below in moveParagraphs, previousListChild will be remo ved along with it if it is
310 // unrendered. But we ought to remove nextListChild too, if it is unrend ered.
311 splitElement(listElement, splitTreeToNode(nextListChild, listElement));
312 insertNodeBefore(elementToInsert, listElement);
313 } else if (nextListChild || listChildNode->parentNode() != listElement) {
314 // Just because listChildNode has no previousListChild doesn't mean ther e isn't any content
315 // in listNode that comes before listChildNode, as listChildNode could h ave ancestors
316 // between it and listNode. So, we split up to listNode before inserting the placeholder
317 // where we're about to move listChildNode to.
318 if (listChildNode->parentNode() != listElement)
319 splitElement(listElement, splitTreeToNode(listChildNode, listElement ).get());
320 insertNodeBefore(elementToInsert, listElement);
321 } else {
322 insertNodeAfter(elementToInsert, listElement);
323 }
324
325 VisiblePosition insertionPoint = VisiblePosition(positionBeforeNode(placehol der.get()));
326 moveParagraphs(start, end, insertionPoint, /* preserveSelection */ true, /* preserveStyle */ true, listChildNode);
327 }
328
329 static HTMLElement* adjacentEnclosingList(const VisiblePosition& pos, const Visi blePosition& adjacentPos, const HTMLQualifiedName& listTag)
330 {
331 HTMLElement* listElement = outermostEnclosingList(adjacentPos.deepEquivalent ().anchorNode());
332
333 if (!listElement)
334 return 0;
335
336 Element* previousCell = enclosingTableCell(pos.deepEquivalent());
337 Element* currentCell = enclosingTableCell(adjacentPos.deepEquivalent());
338
339 if (!listElement->hasTagName(listTag)
340 || listElement->contains(pos.deepEquivalent().anchorNode())
341 || previousCell != currentCell
342 || enclosingList(listElement) != enclosingList(pos.deepEquivalent().anch orNode()))
343 return 0;
344
345 return listElement;
346 }
347
348 PassRefPtrWillBeRawPtr<HTMLElement> InsertListCommand::listifyParagraph(const Vi siblePosition& originalStart, const HTMLQualifiedName& listTag)
349 {
350 VisiblePosition start = startOfParagraph(originalStart, CanSkipOverEditingBo undary);
351 VisiblePosition end = endOfParagraph(start, CanSkipOverEditingBoundary);
352
353 if (start.isNull() || end.isNull())
354 return nullptr;
355
356 // Check for adjoining lists.
357 RefPtrWillBeRawPtr<HTMLElement> listItemElement = createListItemElement(docu ment());
358 RefPtrWillBeRawPtr<HTMLBRElement> placeholder = createBreakElement(document( ));
359 appendNode(placeholder, listItemElement);
360
361 // Place list item into adjoining lists.
362 HTMLElement* previousList = adjacentEnclosingList(start, start.previous(Cann otCrossEditingBoundary), listTag);
363 HTMLElement* nextList = adjacentEnclosingList(start, end.next(CannotCrossEdi tingBoundary), listTag);
364 RefPtrWillBeRawPtr<HTMLElement> listElement = nullptr;
365 if (previousList)
366 appendNode(listItemElement, previousList);
367 else if (nextList)
368 insertNodeAt(listItemElement, positionBeforeNode(nextList));
369 else {
370 // Create the list.
371 listElement = createHTMLElement(document(), listTag);
372 appendNode(listItemElement, listElement);
373
374 if (start.deepEquivalent() == end.deepEquivalent() && isBlock(start.deep Equivalent().anchorNode())) {
375 // Inserting the list into an empty paragraph that isn't held open
376 // by a br or a '\n', will invalidate start and end. Insert
377 // a placeholder and then recompute start and end.
378 RefPtrWillBeRawPtr<HTMLBRElement> placeholder = insertBlockPlacehold er(start.deepEquivalent());
379 start = VisiblePosition(positionBeforeNode(placeholder.get()));
380 end = start;
381 }
382
383 // Insert the list at a position visually equivalent to start of the
384 // paragraph that is being moved into the list.
385 // Try to avoid inserting it somewhere where it will be surrounded by
386 // inline ancestors of start, since it is easier for editing to produce
387 // clean markup when inline elements are pushed down as far as possible.
388 Position insertionPos(start.deepEquivalent().upstream());
389 // Also avoid the containing list item.
390 Node* listChild = enclosingListChild(insertionPos.anchorNode());
391 if (isHTMLLIElement(listChild))
392 insertionPos = positionInParentBeforeNode(*listChild);
393
394 insertNodeAt(listElement, insertionPos);
395
396 // We inserted the list at the start of the content we're about to move
397 // Update the start of content, so we don't try to move the list into it self. bug 19066
398 // Layout is necessary since start's node's inline layoutObjects may hav e been destroyed by the insertion
399 // The end of the content may have changed after the insertion and layou t so update it as well.
400 if (insertionPos == start.deepEquivalent())
401 start = originalStart;
402 }
403
404 // Inserting list element and list item list may change start of pargraph
405 // to move. We calculate start of paragraph again.
406 document().updateLayoutIgnorePendingStylesheets();
407 start = startOfParagraph(start, CanSkipOverEditingBoundary);
408 end = endOfParagraph(start, CanSkipOverEditingBoundary);
409 moveParagraph(start, end, VisiblePosition(positionBeforeNode(placeholder.get ())), true);
410
411 if (listElement)
412 return mergeWithNeighboringLists(listElement);
413
414 if (canMergeLists(previousList, nextList))
415 mergeIdenticalElements(previousList, nextList);
416
417 return listElement;
418 }
419
420 DEFINE_TRACE(InsertListCommand)
421 {
422 visitor->trace(m_listElement);
423 CompositeEditCommand::trace(visitor);
424 }
425
426 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698