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

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-18T14:20:58 Rebase for merging code style fixes 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
223 // |m_type| will be created.
224 switchListType = true;
225 }
226
227 // If the list is of the desired type, and we are not removing the list,
228 // then exit early.
229 if (!switchListType && forceCreateList)
230 return true;
231
232 // If the entire list is selected, then convert the whole list.
233 if (switchListType && isNodeVisiblyContainedWithin(*listElement, current Selection)) {
234 bool rangeStartIsInList = visiblePositionBeforeNode(*listElement).de epEquivalent() == VisiblePosition(currentSelection.startPosition()).deepEquivale nt();
235 bool rangeEndIsInList = visiblePositionAfterNode(*listElement).deepE quivalent() == VisiblePosition(currentSelection.endPosition()).deepEquivalent();
236
237 RefPtrWillBeRawPtr<HTMLElement> newList = createHTMLElement(document (), listTag);
238 insertNodeBefore(newList, listElement);
239
240 Node* firstChildInList = enclosingListChild(VisiblePosition(firstPos itionInNode(listElement.get())).deepEquivalent().anchorNode(), listElement.get() );
241 Element* outerBlock = firstChildInList && isBlockFlowElement(*firstC hildInList) ? toElement(firstChildInList) : listElement.get();
242
243 moveParagraphWithClones(VisiblePosition(firstPositionInNode(listElem ent.get())), VisiblePosition(lastPositionInNode(listElement.get())), newList.get (), outerBlock);
244
245 // Manually remove listNode because moveParagraphWithClones sometime s leaves it behind in the document.
246 // See the bug 33668 and editing/execCommand/insert-list-orphaned-it em-with-nested-lists.html.
247 // FIXME: This might be a bug in moveParagraphWithClones or deleteSe lection.
248 if (listElement && listElement->inDocument())
249 removeNode(listElement);
250
251 newList = mergeWithNeighboringLists(newList);
252
253 // Restore the start and the end of current selection if they starte d inside listNode
254 // because moveParagraphWithClones could have removed them.
255 if (rangeStartIsInList && newList)
256 currentSelection.setStart(newList, 0, IGNORE_EXCEPTION);
257 if (rangeEndIsInList && newList)
258 currentSelection.setEnd(newList, lastOffsetInNode(newList.get()) , IGNORE_EXCEPTION);
259
260 setEndingSelection(VisiblePosition(firstPositionInNode(newList.get() )));
261
262 return true;
263 }
264
265 unlistifyParagraph(endingSelection().visibleStart(), listElement.get(), listChildNode);
266 }
267
268 if (!listChildNode || switchListType || forceCreateList)
269 m_listElement = listifyParagraph(endingSelection().visibleStart(), listT ag);
270
271 return true;
272 }
273
274 void InsertListCommand::unlistifyParagraph(const VisiblePosition& originalStart, HTMLElement* listElement, Node* listChildNode)
275 {
276 Node* nextListChild;
277 Node* previousListChild;
278 VisiblePosition start;
279 VisiblePosition end;
280 ASSERT(listChildNode);
281 if (isHTMLLIElement(*listChildNode)) {
282 start = VisiblePosition(firstPositionInNode(listChildNode));
283 end = VisiblePosition(lastPositionInNode(listChildNode));
284 nextListChild = listChildNode->nextSibling();
285 previousListChild = listChildNode->previousSibling();
286 } else {
287 // A paragraph is visually a list item minus a list marker. The paragra ph will be moved.
288 start = startOfParagraph(originalStart, CanSkipOverEditingBoundary);
289 end = endOfParagraph(start, CanSkipOverEditingBoundary);
290 nextListChild = enclosingListChild(end.next().deepEquivalent().anchorNod e(), listElement);
291 ASSERT(nextListChild != listChildNode);
292 previousListChild = enclosingListChild(start.previous().deepEquivalent() .anchorNode(), listElement);
293 ASSERT(previousListChild != listChildNode);
294 }
295 // When removing a list, we must always create a placeholder to act as a poi nt of insertion
296 // for the list content being removed.
297 RefPtrWillBeRawPtr<HTMLBRElement> placeholder = createBreakElement(document( ));
298 RefPtrWillBeRawPtr<HTMLElement> elementToInsert = placeholder;
299 // If the content of the list item will be moved into another list, put it i n a list item
300 // so that we don't create an orphaned list child.
301 if (enclosingList(listElement)) {
302 elementToInsert = createListItemElement(document());
303 appendNode(placeholder, elementToInsert);
304 }
305
306 if (nextListChild && previousListChild) {
307 // We want to pull listChildNode out of listNode, and place it before ne xtListChild
308 // and after previousListChild, so we split listNode and insert it betwe en the two lists.
309 // But to split listNode, we must first split ancestors of listChildNode between it and listNode,
310 // if any exist.
311 // FIXME: We appear to split at nextListChild as opposed to listChildNod e so that when we remove
312 // listChildNode below in moveParagraphs, previousListChild will be remo ved along with it if it is
313 // unrendered. But we ought to remove nextListChild too, if it is unrend ered.
314 splitElement(listElement, splitTreeToNode(nextListChild, listElement));
315 insertNodeBefore(elementToInsert, listElement);
316 } else if (nextListChild || listChildNode->parentNode() != listElement) {
317 // Just because listChildNode has no previousListChild doesn't mean ther e isn't any content
318 // in listNode that comes before listChildNode, as listChildNode could h ave ancestors
319 // between it and listNode. So, we split up to listNode before inserting the placeholder
320 // where we're about to move listChildNode to.
321 if (listChildNode->parentNode() != listElement)
322 splitElement(listElement, splitTreeToNode(listChildNode, listElement ).get());
323 insertNodeBefore(elementToInsert, listElement);
324 } else {
325 insertNodeAfter(elementToInsert, listElement);
326 }
327
328 VisiblePosition insertionPoint = VisiblePosition(positionBeforeNode(placehol der.get()));
329 moveParagraphs(start, end, insertionPoint, /* preserveSelection */ true, /* preserveStyle */ true, listChildNode);
330 }
331
332 static HTMLElement* adjacentEnclosingList(const VisiblePosition& pos, const Visi blePosition& adjacentPos, const HTMLQualifiedName& listTag)
333 {
334 HTMLElement* listElement = outermostEnclosingList(adjacentPos.deepEquivalent ().anchorNode());
335
336 if (!listElement)
337 return 0;
338
339 Element* previousCell = enclosingTableCell(pos.deepEquivalent());
340 Element* currentCell = enclosingTableCell(adjacentPos.deepEquivalent());
341
342 if (!listElement->hasTagName(listTag)
343 || listElement->contains(pos.deepEquivalent().anchorNode())
344 || previousCell != currentCell
345 || enclosingList(listElement) != enclosingList(pos.deepEquivalent().anch orNode()))
346 return 0;
347
348 return listElement;
349 }
350
351 PassRefPtrWillBeRawPtr<HTMLElement> InsertListCommand::listifyParagraph(const Vi siblePosition& originalStart, const HTMLQualifiedName& listTag)
352 {
353 VisiblePosition start = startOfParagraph(originalStart, CanSkipOverEditingBo undary);
354 VisiblePosition end = endOfParagraph(start, CanSkipOverEditingBoundary);
355
356 if (start.isNull() || end.isNull())
357 return nullptr;
358
359 // Check for adjoining lists.
360 RefPtrWillBeRawPtr<HTMLElement> listItemElement = createListItemElement(docu ment());
361 RefPtrWillBeRawPtr<HTMLBRElement> placeholder = createBreakElement(document( ));
362 appendNode(placeholder, listItemElement);
363
364 // Place list item into adjoining lists.
365 HTMLElement* previousList = adjacentEnclosingList(start, start.previous(Cann otCrossEditingBoundary), listTag);
366 HTMLElement* nextList = adjacentEnclosingList(start, end.next(CannotCrossEdi tingBoundary), listTag);
367 RefPtrWillBeRawPtr<HTMLElement> listElement = nullptr;
368 if (previousList) {
369 appendNode(listItemElement, previousList);
370 } else if (nextList) {
371 insertNodeAt(listItemElement, positionBeforeNode(nextList));
372 } else {
373 // Create the list.
374 listElement = createHTMLElement(document(), listTag);
375 appendNode(listItemElement, listElement);
376
377 if (start.deepEquivalent() == end.deepEquivalent() && isBlock(start.deep Equivalent().anchorNode())) {
378 // Inserting the list into an empty paragraph that isn't held open
379 // by a br or a '\n', will invalidate start and end. Insert
380 // a placeholder and then recompute start and end.
381 RefPtrWillBeRawPtr<HTMLBRElement> placeholder = insertBlockPlacehold er(start.deepEquivalent());
382 start = VisiblePosition(positionBeforeNode(placeholder.get()));
383 end = start;
384 }
385
386 // Insert the list at a position visually equivalent to start of the
387 // paragraph that is being moved into the list.
388 // Try to avoid inserting it somewhere where it will be surrounded by
389 // inline ancestors of start, since it is easier for editing to produce
390 // clean markup when inline elements are pushed down as far as possible.
391 Position insertionPos(start.deepEquivalent().upstream());
392 // Also avoid the containing list item.
393 Node* listChild = enclosingListChild(insertionPos.anchorNode());
394 if (isHTMLLIElement(listChild))
395 insertionPos = positionInParentBeforeNode(*listChild);
396
397 insertNodeAt(listElement, insertionPos);
398
399 // We inserted the list at the start of the content we're about to move
400 // Update the start of content, so we don't try to move the list into it self. bug 19066
401 // Layout is necessary since start's node's inline layoutObjects may hav e been destroyed by the insertion
402 // The end of the content may have changed after the insertion and layou t so update it as well.
403 if (insertionPos == start.deepEquivalent())
404 start = originalStart;
405 }
406
407 // Inserting list element and list item list may change start of pargraph
408 // to move. We calculate start of paragraph again.
409 document().updateLayoutIgnorePendingStylesheets();
410 start = startOfParagraph(start, CanSkipOverEditingBoundary);
411 end = endOfParagraph(start, CanSkipOverEditingBoundary);
412 moveParagraph(start, end, VisiblePosition(positionBeforeNode(placeholder.get ())), true);
413
414 if (listElement)
415 return mergeWithNeighboringLists(listElement);
416
417 if (canMergeLists(previousList, nextList))
418 mergeIdenticalElements(previousList, nextList);
419
420 return listElement;
421 }
422
423 DEFINE_TRACE(InsertListCommand)
424 {
425 visitor->trace(m_listElement);
426 CompositeEditCommand::trace(visitor);
427 }
428
429 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698