Index: Source/core/editing/InsertListCommand.cpp |
diff --git a/Source/core/editing/InsertListCommand.cpp b/Source/core/editing/InsertListCommand.cpp |
deleted file mode 100644 |
index 794080e9e339d385f2a96628bbadb5f3e20248d2..0000000000000000000000000000000000000000 |
--- a/Source/core/editing/InsertListCommand.cpp |
+++ /dev/null |
@@ -1,429 +0,0 @@ |
-/* |
- * Copyright (C) 2006, 2010 Apple Inc. All rights reserved. |
- * |
- * Redistribution and use in source and binary forms, with or without |
- * modification, are permitted provided that the following conditions |
- * are met: |
- * 1. Redistributions of source code must retain the above copyright |
- * notice, this list of conditions and the following disclaimer. |
- * 2. Redistributions in binary form must reproduce the above copyright |
- * notice, this list of conditions and the following disclaimer in the |
- * documentation and/or other materials provided with the distribution. |
- * |
- * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY |
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
- * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR |
- * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
- * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
- * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
- * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
- * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
- */ |
- |
-#include "config.h" |
-#include "core/editing/InsertListCommand.h" |
- |
-#include "bindings/core/v8/ExceptionStatePlaceholder.h" |
-#include "core/HTMLNames.h" |
-#include "core/dom/Document.h" |
-#include "core/dom/Element.h" |
-#include "core/dom/ElementTraversal.h" |
-#include "core/editing/EditingUtilities.h" |
-#include "core/editing/VisibleUnits.h" |
-#include "core/editing/iterators/TextIterator.h" |
-#include "core/html/HTMLBRElement.h" |
-#include "core/html/HTMLElement.h" |
-#include "core/html/HTMLLIElement.h" |
-#include "core/html/HTMLUListElement.h" |
- |
-namespace blink { |
- |
-using namespace HTMLNames; |
- |
-static Node* enclosingListChild(Node* node, Node* listNode) |
-{ |
- Node* listChild = enclosingListChild(node); |
- while (listChild && enclosingList(listChild) != listNode) |
- listChild = enclosingListChild(listChild->parentNode()); |
- return listChild; |
-} |
- |
-HTMLUListElement* InsertListCommand::fixOrphanedListChild(Node* node) |
-{ |
- RefPtrWillBeRawPtr<HTMLUListElement> listElement = createUnorderedListElement(document()); |
- insertNodeBefore(listElement, node); |
- removeNode(node); |
- appendNode(node, listElement); |
- m_listElement = listElement; |
- return listElement.get(); |
-} |
- |
-PassRefPtrWillBeRawPtr<HTMLElement> InsertListCommand::mergeWithNeighboringLists(PassRefPtrWillBeRawPtr<HTMLElement> passedList) |
-{ |
- RefPtrWillBeRawPtr<HTMLElement> list = passedList; |
- Element* previousList = ElementTraversal::previousSibling(*list); |
- if (canMergeLists(previousList, list.get())) |
- mergeIdenticalElements(previousList, list); |
- |
- if (!list) |
- return nullptr; |
- |
- Element* nextSibling = ElementTraversal::nextSibling(*list); |
- if (!nextSibling || !nextSibling->isHTMLElement()) |
- return list.release(); |
- |
- RefPtrWillBeRawPtr<HTMLElement> nextList = toHTMLElement(nextSibling); |
- if (canMergeLists(list.get(), nextList.get())) { |
- mergeIdenticalElements(list, nextList); |
- return nextList.release(); |
- } |
- return list.release(); |
-} |
- |
-bool InsertListCommand::selectionHasListOfType(const VisibleSelection& selection, const HTMLQualifiedName& listTag) |
-{ |
- VisiblePosition start = selection.visibleStart(); |
- |
- if (!enclosingList(start.deepEquivalent().anchorNode())) |
- return false; |
- |
- VisiblePosition end = startOfParagraph(selection.visibleEnd()); |
- while (start.isNotNull() && start.deepEquivalent() != end.deepEquivalent()) { |
- HTMLElement* listElement = enclosingList(start.deepEquivalent().anchorNode()); |
- if (!listElement || !listElement->hasTagName(listTag)) |
- return false; |
- start = startOfNextParagraph(start); |
- } |
- |
- return true; |
-} |
- |
-InsertListCommand::InsertListCommand(Document& document, Type type) |
- : CompositeEditCommand(document), m_type(type) |
-{ |
-} |
- |
-void InsertListCommand::doApply() |
-{ |
- if (!endingSelection().isNonOrphanedCaretOrRange()) |
- return; |
- |
- if (!endingSelection().rootEditableElement()) |
- return; |
- |
- VisiblePosition visibleEnd = endingSelection().visibleEnd(); |
- VisiblePosition visibleStart = endingSelection().visibleStart(); |
- // When a selection ends at the start of a paragraph, we rarely paint |
- // the selection gap before that paragraph, because there often is no gap. |
- // In a case like this, it's not obvious to the user that the selection |
- // ends "inside" that paragraph, so it would be confusing if InsertUn{Ordered}List |
- // operated on that paragraph. |
- // FIXME: We paint the gap before some paragraphs that are indented with left |
- // margin/padding, but not others. We should make the gap painting more consistent and |
- // then use a left margin/padding rule here. |
- if (visibleEnd.deepEquivalent() != visibleStart.deepEquivalent() && isStartOfParagraph(visibleEnd, CanSkipOverEditingBoundary)) { |
- setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(CannotCrossEditingBoundary), endingSelection().isDirectional())); |
- if (!endingSelection().rootEditableElement()) |
- return; |
- } |
- |
- const HTMLQualifiedName& listTag = (m_type == OrderedList) ? olTag : ulTag; |
- if (endingSelection().isRange()) { |
- bool forceListCreation = false; |
- VisibleSelection selection = selectionForParagraphIteration(endingSelection()); |
- ASSERT(selection.isRange()); |
- VisiblePosition startOfSelection = selection.visibleStart(); |
- VisiblePosition endOfSelection = selection.visibleEnd(); |
- VisiblePosition startOfLastParagraph = startOfParagraph(endOfSelection, CanSkipOverEditingBoundary); |
- |
- RefPtrWillBeRawPtr<Range> currentSelection = endingSelection().firstRange(); |
- RefPtrWillBeRawPtr<ContainerNode> scopeForStartOfSelection = nullptr; |
- RefPtrWillBeRawPtr<ContainerNode> scopeForEndOfSelection = nullptr; |
- // FIXME: This is an inefficient way to keep selection alive because |
- // indexForVisiblePosition walks from the beginning of the document to the |
- // endOfSelection everytime this code is executed. But not using index is hard |
- // because there are so many ways we can los eselection inside doApplyForSingleParagraph. |
- int indexForStartOfSelection = indexForVisiblePosition(startOfSelection, scopeForStartOfSelection); |
- int indexForEndOfSelection = indexForVisiblePosition(endOfSelection, scopeForEndOfSelection); |
- |
- if (startOfParagraph(startOfSelection, CanSkipOverEditingBoundary).deepEquivalent() != startOfLastParagraph.deepEquivalent()) { |
- forceListCreation = !selectionHasListOfType(selection, listTag); |
- |
- VisiblePosition startOfCurrentParagraph = startOfSelection; |
- while (startOfCurrentParagraph.isNotNull() && !inSameParagraph(startOfCurrentParagraph, startOfLastParagraph, CanCrossEditingBoundary)) { |
- // doApply() may operate on and remove the last paragraph of the selection from the document |
- // if it's in the same list item as startOfCurrentParagraph. Return early to avoid an |
- // infinite loop and because there is no more work to be done. |
- // FIXME(<rdar://problem/5983974>): The endingSelection() may be incorrect here. Compute |
- // the new location of endOfSelection and use it as the end of the new selection. |
- if (!startOfLastParagraph.deepEquivalent().inDocument()) |
- return; |
- setEndingSelection(startOfCurrentParagraph); |
- |
- // Save and restore endOfSelection and startOfLastParagraph when necessary |
- // since moveParagraph and movePragraphWithClones can remove nodes. |
- if (!doApplyForSingleParagraph(forceListCreation, listTag, *currentSelection)) |
- break; |
- if (endOfSelection.isNull() || endOfSelection.isOrphan() || startOfLastParagraph.isNull() || startOfLastParagraph.isOrphan()) { |
- endOfSelection = visiblePositionForIndex(indexForEndOfSelection, scopeForEndOfSelection.get()); |
- // If endOfSelection is null, then some contents have been deleted from the document. |
- // This should never happen and if it did, exit early immediately because we've lost the loop invariant. |
- ASSERT(endOfSelection.isNotNull()); |
- if (endOfSelection.isNull()) |
- return; |
- startOfLastParagraph = startOfParagraph(endOfSelection, CanSkipOverEditingBoundary); |
- } |
- |
- startOfCurrentParagraph = startOfNextParagraph(endingSelection().visibleStart()); |
- } |
- setEndingSelection(endOfSelection); |
- } |
- doApplyForSingleParagraph(forceListCreation, listTag, *currentSelection); |
- // Fetch the end of the selection, for the reason mentioned above. |
- if (endOfSelection.isNull() || endOfSelection.isOrphan()) { |
- endOfSelection = visiblePositionForIndex(indexForEndOfSelection, scopeForEndOfSelection.get()); |
- if (endOfSelection.isNull()) |
- return; |
- } |
- if (startOfSelection.isNull() || startOfSelection.isOrphan()) { |
- startOfSelection = visiblePositionForIndex(indexForStartOfSelection, scopeForStartOfSelection.get()); |
- if (startOfSelection.isNull()) |
- return; |
- } |
- setEndingSelection(VisibleSelection(startOfSelection, endOfSelection, endingSelection().isDirectional())); |
- return; |
- } |
- |
- ASSERT(endingSelection().firstRange()); |
- doApplyForSingleParagraph(false, listTag, *endingSelection().firstRange()); |
-} |
- |
-bool InsertListCommand::doApplyForSingleParagraph(bool forceCreateList, const HTMLQualifiedName& listTag, Range& currentSelection) |
-{ |
- // FIXME: This will produce unexpected results for a selection that starts just before a |
- // table and ends inside the first cell, selectionForParagraphIteration should probably |
- // be renamed and deployed inside setEndingSelection(). |
- Node* selectionNode = endingSelection().start().anchorNode(); |
- Node* listChildNode = enclosingListChild(selectionNode); |
- bool switchListType = false; |
- if (listChildNode) { |
- if (!listChildNode->parentNode()->hasEditableStyle()) |
- return false; |
- // Remove the list child. |
- RefPtrWillBeRawPtr<HTMLElement> listElement = enclosingList(listChildNode); |
- if (!listElement) { |
- listElement = fixOrphanedListChild(listChildNode); |
- listElement = mergeWithNeighboringLists(listElement); |
- } |
- if (!listElement->hasTagName(listTag)) { |
- // |listChildNode| will be removed from the list and a list of type |
- // |m_type| will be created. |
- switchListType = true; |
- } |
- |
- // If the list is of the desired type, and we are not removing the list, |
- // then exit early. |
- if (!switchListType && forceCreateList) |
- return true; |
- |
- // If the entire list is selected, then convert the whole list. |
- if (switchListType && isNodeVisiblyContainedWithin(*listElement, currentSelection)) { |
- bool rangeStartIsInList = visiblePositionBeforeNode(*listElement).deepEquivalent() == VisiblePosition(currentSelection.startPosition()).deepEquivalent(); |
- bool rangeEndIsInList = visiblePositionAfterNode(*listElement).deepEquivalent() == VisiblePosition(currentSelection.endPosition()).deepEquivalent(); |
- |
- RefPtrWillBeRawPtr<HTMLElement> newList = createHTMLElement(document(), listTag); |
- insertNodeBefore(newList, listElement); |
- |
- Node* firstChildInList = enclosingListChild(VisiblePosition(firstPositionInNode(listElement.get())).deepEquivalent().anchorNode(), listElement.get()); |
- Element* outerBlock = firstChildInList && isBlockFlowElement(*firstChildInList) ? toElement(firstChildInList) : listElement.get(); |
- |
- moveParagraphWithClones(VisiblePosition(firstPositionInNode(listElement.get())), VisiblePosition(lastPositionInNode(listElement.get())), newList.get(), outerBlock); |
- |
- // Manually remove listNode because moveParagraphWithClones sometimes leaves it behind in the document. |
- // See the bug 33668 and editing/execCommand/insert-list-orphaned-item-with-nested-lists.html. |
- // FIXME: This might be a bug in moveParagraphWithClones or deleteSelection. |
- if (listElement && listElement->inDocument()) |
- removeNode(listElement); |
- |
- newList = mergeWithNeighboringLists(newList); |
- |
- // Restore the start and the end of current selection if they started inside listNode |
- // because moveParagraphWithClones could have removed them. |
- if (rangeStartIsInList && newList) |
- currentSelection.setStart(newList, 0, IGNORE_EXCEPTION); |
- if (rangeEndIsInList && newList) |
- currentSelection.setEnd(newList, lastOffsetInNode(newList.get()), IGNORE_EXCEPTION); |
- |
- setEndingSelection(VisiblePosition(firstPositionInNode(newList.get()))); |
- |
- return true; |
- } |
- |
- unlistifyParagraph(endingSelection().visibleStart(), listElement.get(), listChildNode); |
- } |
- |
- if (!listChildNode || switchListType || forceCreateList) |
- m_listElement = listifyParagraph(endingSelection().visibleStart(), listTag); |
- |
- return true; |
-} |
- |
-void InsertListCommand::unlistifyParagraph(const VisiblePosition& originalStart, HTMLElement* listElement, Node* listChildNode) |
-{ |
- Node* nextListChild; |
- Node* previousListChild; |
- VisiblePosition start; |
- VisiblePosition end; |
- ASSERT(listChildNode); |
- if (isHTMLLIElement(*listChildNode)) { |
- start = VisiblePosition(firstPositionInNode(listChildNode)); |
- end = VisiblePosition(lastPositionInNode(listChildNode)); |
- nextListChild = listChildNode->nextSibling(); |
- previousListChild = listChildNode->previousSibling(); |
- } else { |
- // A paragraph is visually a list item minus a list marker. The paragraph will be moved. |
- start = startOfParagraph(originalStart, CanSkipOverEditingBoundary); |
- end = endOfParagraph(start, CanSkipOverEditingBoundary); |
- nextListChild = enclosingListChild(end.next().deepEquivalent().anchorNode(), listElement); |
- ASSERT(nextListChild != listChildNode); |
- previousListChild = enclosingListChild(start.previous().deepEquivalent().anchorNode(), listElement); |
- ASSERT(previousListChild != listChildNode); |
- } |
- // When removing a list, we must always create a placeholder to act as a point of insertion |
- // for the list content being removed. |
- RefPtrWillBeRawPtr<HTMLBRElement> placeholder = createBreakElement(document()); |
- RefPtrWillBeRawPtr<HTMLElement> elementToInsert = placeholder; |
- // If the content of the list item will be moved into another list, put it in a list item |
- // so that we don't create an orphaned list child. |
- if (enclosingList(listElement)) { |
- elementToInsert = createListItemElement(document()); |
- appendNode(placeholder, elementToInsert); |
- } |
- |
- if (nextListChild && previousListChild) { |
- // We want to pull listChildNode out of listNode, and place it before nextListChild |
- // and after previousListChild, so we split listNode and insert it between the two lists. |
- // But to split listNode, we must first split ancestors of listChildNode between it and listNode, |
- // if any exist. |
- // FIXME: We appear to split at nextListChild as opposed to listChildNode so that when we remove |
- // listChildNode below in moveParagraphs, previousListChild will be removed along with it if it is |
- // unrendered. But we ought to remove nextListChild too, if it is unrendered. |
- splitElement(listElement, splitTreeToNode(nextListChild, listElement)); |
- insertNodeBefore(elementToInsert, listElement); |
- } else if (nextListChild || listChildNode->parentNode() != listElement) { |
- // Just because listChildNode has no previousListChild doesn't mean there isn't any content |
- // in listNode that comes before listChildNode, as listChildNode could have ancestors |
- // between it and listNode. So, we split up to listNode before inserting the placeholder |
- // where we're about to move listChildNode to. |
- if (listChildNode->parentNode() != listElement) |
- splitElement(listElement, splitTreeToNode(listChildNode, listElement).get()); |
- insertNodeBefore(elementToInsert, listElement); |
- } else { |
- insertNodeAfter(elementToInsert, listElement); |
- } |
- |
- VisiblePosition insertionPoint = VisiblePosition(positionBeforeNode(placeholder.get())); |
- moveParagraphs(start, end, insertionPoint, /* preserveSelection */ true, /* preserveStyle */ true, listChildNode); |
-} |
- |
-static HTMLElement* adjacentEnclosingList(const VisiblePosition& pos, const VisiblePosition& adjacentPos, const HTMLQualifiedName& listTag) |
-{ |
- HTMLElement* listElement = outermostEnclosingList(adjacentPos.deepEquivalent().anchorNode()); |
- |
- if (!listElement) |
- return 0; |
- |
- Element* previousCell = enclosingTableCell(pos.deepEquivalent()); |
- Element* currentCell = enclosingTableCell(adjacentPos.deepEquivalent()); |
- |
- if (!listElement->hasTagName(listTag) |
- || listElement->contains(pos.deepEquivalent().anchorNode()) |
- || previousCell != currentCell |
- || enclosingList(listElement) != enclosingList(pos.deepEquivalent().anchorNode())) |
- return 0; |
- |
- return listElement; |
-} |
- |
-PassRefPtrWillBeRawPtr<HTMLElement> InsertListCommand::listifyParagraph(const VisiblePosition& originalStart, const HTMLQualifiedName& listTag) |
-{ |
- VisiblePosition start = startOfParagraph(originalStart, CanSkipOverEditingBoundary); |
- VisiblePosition end = endOfParagraph(start, CanSkipOverEditingBoundary); |
- |
- if (start.isNull() || end.isNull()) |
- return nullptr; |
- |
- // Check for adjoining lists. |
- RefPtrWillBeRawPtr<HTMLElement> listItemElement = createListItemElement(document()); |
- RefPtrWillBeRawPtr<HTMLBRElement> placeholder = createBreakElement(document()); |
- appendNode(placeholder, listItemElement); |
- |
- // Place list item into adjoining lists. |
- HTMLElement* previousList = adjacentEnclosingList(start, start.previous(CannotCrossEditingBoundary), listTag); |
- HTMLElement* nextList = adjacentEnclosingList(start, end.next(CannotCrossEditingBoundary), listTag); |
- RefPtrWillBeRawPtr<HTMLElement> listElement = nullptr; |
- if (previousList) { |
- appendNode(listItemElement, previousList); |
- } else if (nextList) { |
- insertNodeAt(listItemElement, positionBeforeNode(nextList)); |
- } else { |
- // Create the list. |
- listElement = createHTMLElement(document(), listTag); |
- appendNode(listItemElement, listElement); |
- |
- if (start.deepEquivalent() == end.deepEquivalent() && isBlock(start.deepEquivalent().anchorNode())) { |
- // Inserting the list into an empty paragraph that isn't held open |
- // by a br or a '\n', will invalidate start and end. Insert |
- // a placeholder and then recompute start and end. |
- RefPtrWillBeRawPtr<HTMLBRElement> placeholder = insertBlockPlaceholder(start.deepEquivalent()); |
- start = VisiblePosition(positionBeforeNode(placeholder.get())); |
- end = start; |
- } |
- |
- // Insert the list at a position visually equivalent to start of the |
- // paragraph that is being moved into the list. |
- // Try to avoid inserting it somewhere where it will be surrounded by |
- // inline ancestors of start, since it is easier for editing to produce |
- // clean markup when inline elements are pushed down as far as possible. |
- Position insertionPos(start.deepEquivalent().upstream()); |
- // Also avoid the containing list item. |
- Node* listChild = enclosingListChild(insertionPos.anchorNode()); |
- if (isHTMLLIElement(listChild)) |
- insertionPos = positionInParentBeforeNode(*listChild); |
- |
- insertNodeAt(listElement, insertionPos); |
- |
- // We inserted the list at the start of the content we're about to move |
- // Update the start of content, so we don't try to move the list into itself. bug 19066 |
- // Layout is necessary since start's node's inline layoutObjects may have been destroyed by the insertion |
- // The end of the content may have changed after the insertion and layout so update it as well. |
- if (insertionPos == start.deepEquivalent()) |
- start = originalStart; |
- } |
- |
- // Inserting list element and list item list may change start of pargraph |
- // to move. We calculate start of paragraph again. |
- document().updateLayoutIgnorePendingStylesheets(); |
- start = startOfParagraph(start, CanSkipOverEditingBoundary); |
- end = endOfParagraph(start, CanSkipOverEditingBoundary); |
- moveParagraph(start, end, VisiblePosition(positionBeforeNode(placeholder.get())), true); |
- |
- if (listElement) |
- return mergeWithNeighboringLists(listElement); |
- |
- if (canMergeLists(previousList, nextList)) |
- mergeIdenticalElements(previousList, nextList); |
- |
- return listElement; |
-} |
- |
-DEFINE_TRACE(InsertListCommand) |
-{ |
- visitor->trace(m_listElement); |
- CompositeEditCommand::trace(visitor); |
-} |
- |
-} |