InsertListCommand.cpp [plain text]
#include "config.h"
#include "InsertListCommand.h"
#include "Editing.h"
#include "ElementTraversal.h"
#include "HTMLBRElement.h"
#include "HTMLLIElement.h"
#include "HTMLNames.h"
#include "HTMLUListElement.h"
#include "Range.h"
#include "VisibleUnits.h"
namespace WebCore {
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;
}
RefPtr<HTMLElement> InsertListCommand::insertList(Document& document, Type type)
{
RefPtr<InsertListCommand> insertCommand = create(document, type);
insertCommand->apply();
return insertCommand->m_listElement;
}
HTMLElement* InsertListCommand::fixOrphanedListChild(Node& node)
{
auto parentNode = makeRefPtr(node.parentNode());
if (parentNode && !parentNode->hasRichlyEditableStyle())
return nullptr;
auto listElement = HTMLUListElement::create(document());
insertNodeBefore(listElement.copyRef(), node);
if (!listElement->hasEditableStyle())
return nullptr;
removeNode(node);
appendNode(node, listElement.copyRef());
m_listElement = WTFMove(listElement);
return m_listElement.get();
}
Ref<HTMLElement> InsertListCommand::mergeWithNeighboringLists(HTMLElement& list)
{
Ref<HTMLElement> protectedList = list;
Element* previousList = list.previousElementSibling();
if (canMergeLists(previousList, &list))
mergeIdenticalElements(*previousList, list);
Element* sibling = ElementTraversal::nextSibling(list);
if (!is<HTMLElement>(sibling))
return protectedList;
Ref<HTMLElement> nextList = downcast<HTMLElement>(*sibling);
if (canMergeLists(&list, nextList.ptr())) {
mergeIdenticalElements(list, nextList);
return nextList;
}
return protectedList;
}
bool InsertListCommand::selectionHasListOfType(const VisibleSelection& selection, const QualifiedName& listTag)
{
VisiblePosition start = selection.visibleStart();
if (!enclosingList(start.deepEquivalent().deprecatedNode()))
return false;
VisiblePosition end = startOfParagraph(selection.visibleEnd());
while (start.isNotNull() && start != end) {
Element* listNode = enclosingList(start.deepEquivalent().deprecatedNode());
if (!listNode || !listNode->hasTagName(listTag))
return false;
start = startOfNextParagraph(start);
}
return true;
}
InsertListCommand::InsertListCommand(Document& document, Type type)
: CompositeEditCommand(document)
, m_type(type)
{
}
void InsertListCommand::doApply()
{
VisiblePosition visibleEnd = endingSelection().visibleEnd();
VisiblePosition visibleStart = endingSelection().visibleStart();
if (visibleEnd.isNull() || visibleStart.isNull() || !endingSelection().isContentRichlyEditable())
return;
if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd, CanSkipOverEditingBoundary)) {
setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(CannotCrossEditingBoundary), endingSelection().isDirectional()));
if (!endingSelection().rootEditableElement())
return;
}
auto& listTag = (m_type == Type::OrderedList) ? olTag : ulTag;
if (endingSelection().isRange()) {
VisibleSelection selection = selectionForParagraphIteration(endingSelection());
if (selection.isRange()) {
VisiblePosition startOfSelection = selection.visibleStart();
VisiblePosition endOfSelection = selection.visibleEnd();
VisiblePosition startOfLastParagraph = startOfParagraph(endOfSelection, CanSkipOverEditingBoundary);
if (startOfLastParagraph.isNotNull() && startOfParagraph(startOfSelection, CanSkipOverEditingBoundary) != startOfLastParagraph) {
bool forceCreateList = !selectionHasListOfType(selection, listTag);
auto currentSelection = *endingSelection().firstRange();
VisiblePosition startOfCurrentParagraph = startOfSelection;
while (startOfCurrentParagraph.isNotNull() && !inSameParagraph(startOfCurrentParagraph, startOfLastParagraph, CanCrossEditingBoundary)) {
if (!startOfLastParagraph.deepEquivalent().anchorNode()->isConnected())
return;
setEndingSelection(startOfCurrentParagraph);
RefPtr<ContainerNode> scope;
int indexForEndOfSelection = indexForVisiblePosition(endOfSelection, scope);
doApplyForSingleParagraph(forceCreateList, listTag, currentSelection);
if (endOfSelection.isNull() || endOfSelection.isOrphan() || startOfLastParagraph.isNull() || startOfLastParagraph.isOrphan()) {
endOfSelection = visiblePositionForIndex(indexForEndOfSelection, scope.get());
ASSERT(endOfSelection.isNotNull());
if (endOfSelection.isNull())
return;
startOfLastParagraph = startOfParagraph(endOfSelection, CanSkipOverEditingBoundary);
}
if (startOfCurrentParagraph == startOfSelection)
startOfSelection = endingSelection().visibleStart();
startOfCurrentParagraph = startOfNextParagraph(endingSelection().visibleStart());
}
setEndingSelection(endOfSelection);
doApplyForSingleParagraph(forceCreateList, listTag, currentSelection);
endOfSelection = endingSelection().visibleEnd();
setEndingSelection(VisibleSelection(startOfSelection, endOfSelection, endingSelection().isDirectional()));
return;
}
}
}
auto range = endingSelection().firstRange();
doApplyForSingleParagraph(false, listTag, *range);
}
EditAction InsertListCommand::editingAction() const
{
return m_type == Type::OrderedList ? EditAction::InsertOrderedList : EditAction::InsertUnorderedList;
}
void InsertListCommand::doApplyForSingleParagraph(bool forceCreateList, const HTMLQualifiedName& listTag, SimpleRange& currentSelection)
{
Node* selectionNode = endingSelection().start().deprecatedNode();
Node* listChildNode = enclosingListChild(selectionNode);
bool switchListType = false;
if (listChildNode) {
RefPtr<HTMLElement> listNode = enclosingList(listChildNode);
if (!listNode) {
RefPtr<HTMLElement> listElement = fixOrphanedListChild(*listChildNode);
if (!listElement || !listElement->isConnected())
return;
listNode = mergeWithNeighboringLists(*listElement);
}
if (!listNode->hasTagName(listTag)) {
switchListType = true;
}
if (!switchListType && forceCreateList)
return;
if (switchListType && isNodeVisiblyContainedWithin(*listNode, currentSelection)) {
bool rangeStartIsInList = visiblePositionBeforeNode(*listNode) == makeDeprecatedLegacyPosition(currentSelection.start);
bool rangeEndIsInList = visiblePositionAfterNode(*listNode) == makeDeprecatedLegacyPosition(currentSelection.end);
RefPtr<HTMLElement> newList = createHTMLElement(document(), listTag);
insertNodeBefore(*newList, *listNode);
if (!newList->hasEditableStyle())
return;
auto* firstChildInList = enclosingListChild(VisiblePosition(firstPositionInNode(listNode.get())).deepEquivalent().deprecatedNode(), listNode.get());
Node* outerBlock = firstChildInList && isBlockFlowElement(*firstChildInList) ? firstChildInList : listNode.get();
moveParagraphWithClones(firstPositionInNode(listNode.get()), lastPositionInNode(listNode.get()), newList.get(), outerBlock);
if (listNode && listNode->isConnected())
removeNode(*listNode);
newList = mergeWithNeighboringLists(*newList);
if (rangeStartIsInList && newList)
currentSelection.start = makeBoundaryPointBeforeNodeContents(*newList);
if (rangeEndIsInList && newList)
currentSelection.end = makeBoundaryPointAfterNodeContents(*newList);
setEndingSelection(VisiblePosition(firstPositionInNode(newList.get())));
return;
}
unlistifyParagraph(endingSelection().visibleStart(), listNode.get(), listChildNode);
}
if (!listChildNode || switchListType || forceCreateList)
m_listElement = listifyParagraph(endingSelection().visibleStart(), listTag);
}
void InsertListCommand::unlistifyParagraph(const VisiblePosition& originalStart, HTMLElement* listNode, Node* listChildNode)
{
Node* nextListChild;
Node* previousListChild;
VisiblePosition start;
VisiblePosition end;
if (!listNode->parentNode()->hasEditableStyle())
return;
if (listChildNode->hasTagName(liTag)) {
start = firstPositionInNode(listChildNode);
end = lastPositionInNode(listChildNode);
nextListChild = listChildNode->nextSibling();
previousListChild = listChildNode->previousSibling();
} else {
start = startOfParagraph(originalStart, CanSkipOverEditingBoundary);
end = endOfParagraph(start, CanSkipOverEditingBoundary);
nextListChild = enclosingListChild(end.next().deepEquivalent().deprecatedNode(), listNode);
ASSERT(nextListChild != listChildNode);
previousListChild = enclosingListChild(start.previous().deepEquivalent().deprecatedNode(), listNode);
ASSERT(previousListChild != listChildNode);
}
auto placeholder = HTMLBRElement::create(document());
RefPtr<Element> nodeToInsert = placeholder.copyRef();
if (enclosingList(listNode)) {
nodeToInsert = HTMLLIElement::create(document());
appendNode(placeholder.copyRef(), *nodeToInsert);
}
if (nextListChild && previousListChild) {
splitElement(*listNode, *splitTreeToNode(*nextListChild, *listNode));
insertNodeBefore(nodeToInsert.releaseNonNull(), *listNode);
} else if (nextListChild || listChildNode->parentNode() != listNode) {
if (auto listChildNodeParentNode = makeRefPtr(listChildNode->parentNode()); listChildNodeParentNode && listChildNodeParentNode != listNode)
splitElement(*listNode, *splitTreeToNode(*listChildNode, *listNode).get());
insertNodeBefore(nodeToInsert.releaseNonNull(), *listNode);
} else
insertNodeAfter(nodeToInsert.releaseNonNull(), *listNode);
VisiblePosition insertionPoint = VisiblePosition(positionBeforeNode(placeholder.ptr()));
moveParagraphs(start, end, insertionPoint, true);
}
static Element* adjacentEnclosingList(const VisiblePosition& pos, const VisiblePosition& adjacentPos, const QualifiedName& listTag)
{
Element* listNode = outermostEnclosingList(adjacentPos.deepEquivalent().deprecatedNode());
if (!listNode)
return 0;
Node* previousCell = enclosingTableCell(pos.deepEquivalent());
Node* currentCell = enclosingTableCell(adjacentPos.deepEquivalent());
if (!listNode->hasTagName(listTag)
|| listNode->contains(pos.deepEquivalent().deprecatedNode())
|| previousCell != currentCell
|| enclosingList(listNode) != enclosingList(pos.deepEquivalent().deprecatedNode()))
return 0;
return listNode;
}
RefPtr<HTMLElement> InsertListCommand::listifyParagraph(const VisiblePosition& originalStart, const QualifiedName& listTag)
{
VisiblePosition start = startOfParagraph(originalStart, CanSkipOverEditingBoundary);
VisiblePosition end = endOfParagraph(start, CanSkipOverEditingBoundary);
if (start.isNull() || end.isNull() || !start.deepEquivalent().containerNode()->hasEditableStyle() || !end.deepEquivalent().containerNode()->hasEditableStyle())
return 0;
auto listItemElement = HTMLLIElement::create(document());
auto placeholder = HTMLBRElement::create(document());
appendNode(placeholder.copyRef(), listItemElement.copyRef());
Element* previousList = adjacentEnclosingList(start.deepEquivalent(), start.previous(CannotCrossEditingBoundary), listTag);
Element* nextList = adjacentEnclosingList(start.deepEquivalent(), end.next(CannotCrossEditingBoundary), listTag);
RefPtr<HTMLElement> listElement;
if (previousList)
appendNode(WTFMove(listItemElement), *previousList);
else if (nextList)
insertNodeAt(WTFMove(listItemElement), positionBeforeNode(nextList));
else {
listElement = createHTMLElement(document(), listTag);
appendNode(WTFMove(listItemElement), *listElement);
if (start == end && isBlock(start.deepEquivalent().deprecatedNode())) {
auto blockPlaceholder = insertBlockPlaceholder(start.deepEquivalent());
start = positionBeforeNode(blockPlaceholder.get());
end = start;
}
Position insertionPos(start.deepEquivalent().upstream());
Node* listChild = enclosingListChild(insertionPos.deprecatedNode());
if (listChild && listChild->hasTagName(liTag))
insertionPos = positionInParentBeforeNode(listChild);
if (!isEditablePosition(insertionPos))
return 0;
insertNodeAt(*listElement, insertionPos);
if (insertionPos == start.deepEquivalent()) {
listElement->document().updateLayoutIgnorePendingStylesheets();
start = startOfParagraph(originalStart, CanSkipOverEditingBoundary);
end = endOfParagraph(start, CanSkipOverEditingBoundary);
}
}
moveParagraph(start, end, positionBeforeNode(placeholder.ptr()), true);
if (listElement)
return mergeWithNeighboringLists(*listElement);
if (canMergeLists(previousList, nextList))
mergeIdenticalElements(*previousList, *nextList);
return listElement;
}
}