// For the moment, this is SPI and the only client (Mail.app) is satisfied. // Here are two things to re-evaluate when making into API. // 1. Currently, InheritedListType uses clones whereas OrderedList and // UnorderedList create a new list node of the specified type. That is // inconsistent wrt style. If that is not OK, here are some alternatives: // - new nodes always inherit style (probably the best choice) // - new nodes have always have no style // - new nodes of the same type inherit style // 2. Currently, the node we return may be either a pre-existing one or // a new one. Is it confusing to return the pre-existing one without // somehow indicating that it is not new? If so, here are some alternatives: // - only return the list node if we created it // - indicate whether the list node is new or pre-existing // - (silly) client specifies whether to return pre-existing list nodes void IncreaseSelectionListLevelCommand::doApply() { Node* startListChild; Node* endListChild; if (!canIncreaseListLevel(endingSelection(), startListChild, endListChild)) return; Node* previousItem = startListChild->renderer()->previousSibling()->node(); if (isListHTMLElement(previousItem)) { // move nodes up into preceding list appendSiblingNodeRange(startListChild, endListChild, downcast<Element>(previousItem)); m_listElement = previousItem; } else { // create a sublist for the preceding element and move nodes there RefPtr<Element> newParent; switch (m_listType) { case InheritedListType: newParent = startListChild->parentElement(); if (newParent) newParent = newParent->cloneElementWithoutChildren(document()); break; case OrderedList: newParent = HTMLOListElement::create(document()); break; case UnorderedList: newParent = HTMLUListElement::create(document()); break; } insertNodeBefore(newParent, startListChild); appendSiblingNodeRange(startListChild, endListChild, newParent.get()); m_listElement = WTFMove(newParent); } }
Position InsertTextCommand::prepareForTextInsertion(const Position& pos) { // Prepare for text input by looking at the specified position. // It may be necessary to insert a text node to receive characters. // FIXME: What is the rootEditable() check about? Seems like it // assumes that the content before (or after) pos.node() is editable // (i.e. pos is at an editable/non-editable boundary). That seems // like a bad assumption. if (!pos.node()->isTextNode()) { RefPtr<Node> textNode = document()->createEditingTextNode(""); // Now insert the node in the right place if (pos.node()->rootEditableElement() != NULL) { insertNodeAt(textNode.get(), pos.node(), pos.offset()); } else if (pos.node()->caretMinOffset() == pos.offset()) { insertNodeBefore(textNode.get(), pos.node()); } else if (pos.node()->caretMaxOffset() == pos.offset()) { insertNodeAfter(textNode.get(), pos.node()); } else ASSERT_NOT_REACHED(); return Position(textNode.get(), 0); } if (isTabSpanTextNode(pos.node())) { RefPtr<Node> textNode = document()->createEditingTextNode(""); insertNodeAtTabSpanPosition(textNode.get(), pos); return Position(textNode.get(), 0); } return pos; }
// For the moment, this is SPI and the only client (Mail.app) is satisfied. // Here are two things to re-evaluate when making into API. // 1. Currently, InheritedListType uses clones whereas OrderedList and // UnorderedList create a new list node of the specified type. That is // inconsistent wrt style. If that is not OK, here are some alternatives: // - new nodes always inherit style (probably the best choice) // - new nodes have always have no style // - new nodes of the same type inherit style // 2. Currently, the node we return may be either a pre-existing one or // a new one. Is it confusing to return the pre-existing one without // somehow indicating that it is not new? If so, here are some alternatives: // - only return the list node if we created it // - indicate whether the list node is new or pre-existing // - (silly) client specifies whether to return pre-existing list nodes void IncreaseSelectionListLevelCommand::doApply() { Node* startListChild; Node* endListChild; if (!canIncreaseListLevel(endingSelection(), startListChild, endListChild)) return; Node* previousItem = startListChild->renderer()->previousSibling()->element(); if (isListElement(previousItem)) { // move nodes up into preceding list appendSiblingNodeRange(startListChild, endListChild, previousItem); m_listElement = previousItem; } else { // create a sublist for the preceding element and move nodes there RefPtr<Node> newParent; switch (m_listType) { case InheritedListType: newParent = startListChild->parentNode()->cloneNode(false); break; case OrderedList: newParent = createOrderedListElement(document()); break; case UnorderedList: newParent = createUnorderedListElement(document()); break; } insertNodeBefore(newParent.get(), startListChild); appendSiblingNodeRange(startListChild, endListChild, newParent.get()); m_listElement = newParent.get(); } }
static __inline void insertNodeAtStart(LinkList *list, listnode_t *newnode) { if(list->headptr == NULL) { list->headptr = list->tailptr = newnode; if(list->state->flags & LCF_CIRCULAR) newnode->prev = newnode->next = newnode; else newnode->prev = newnode->next = NULL; } else insertNodeBefore(list, list->headptr, newnode); }
void RemoveNodePreservingChildrenCommand::doApply() { Vector<RefPtr<Node> > children; for (Node* child = m_node->firstChild(); child; child = child->nextSibling()) children.append(child); size_t size = children.size(); for (size_t i = 0; i < size; ++i) { RefPtr<Node> child = children[i].release(); removeNode(child, m_shouldAssumeContentIsAlwaysEditable); insertNodeBefore(child.release(), m_node, m_shouldAssumeContentIsAlwaysEditable); } removeNode(m_node, m_shouldAssumeContentIsAlwaysEditable); }
void ModifySelectionListLevelCommand::insertSiblingNodeRangeBefore(Node* startNode, Node* endNode, Node* refNode) { Node* node = startNode; while (1) { Node* next = node->nextSibling(); removeNode(node); insertNodeBefore(node, refNode); if (node == endNode) break; node = next; } }
static void insertNodeAtStart(CBuffer* cb, cbnode_t* newnode) { assert(cb && newnode); if(!cb->head) { cb->head = newnode; cb->tail = newnode; newnode->prev = NULL; newnode->next = NULL; cb->indexGood = false; // it will be updated when needed. return; } insertNodeBefore(cb, cb->head, newnode); }
void RemoveNodePreservingChildrenCommand::doApply(EditingState* editingState) { ABORT_EDITING_COMMAND_IF(!m_node->parentNode()); ABORT_EDITING_COMMAND_IF(!hasEditableStyle(*m_node->parentNode())); if (m_node->isContainerNode()) { NodeVector children; getChildNodes(toContainerNode(*m_node), children); for (auto& currentChild : children) { Node* child = currentChild.release(); removeNode(child, editingState, m_shouldAssumeContentIsAlwaysEditable); if (editingState->isAborted()) return; insertNodeBefore(child, m_node, editingState, m_shouldAssumeContentIsAlwaysEditable); if (editingState->isAborted()) return; } } removeNode(m_node, editingState, m_shouldAssumeContentIsAlwaysEditable); }
Position InsertTextCommand::insertTab(const Position& pos) { Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent(); if (insertPos.isNull()) return pos; Node* node = insertPos.containerNode(); unsigned offset = node->isTextNode() ? insertPos.offsetInContainerNode() : 0; // keep tabs coalesced in tab span if (isTabSpanTextNode(node)) { RefPtrWillBeRawPtr<Text> textNode = toText(node); insertTextIntoNode(textNode, offset, "\t"); return Position(textNode.release(), offset + 1); } // create new tab span RefPtrWillBeRawPtr<Element> spanNode = createTabSpanElement(document()); // place it if (!node->isTextNode()) { insertNodeAt(spanNode.get(), insertPos); } else { RefPtrWillBeRawPtr<Text> textNode = toText(node); if (offset >= textNode->length()) insertNodeAfter(spanNode, textNode.release()); else { // split node to make room for the span // NOTE: splitTextNode uses textNode for the // second node in the split, so we need to // insert the span before it. if (offset > 0) splitTextNode(textNode, offset); insertNodeBefore(spanNode, textNode.release()); } } // return the position following the new tab return lastPositionInNode(spanNode.get()); }
int SimplifyMarkupCommand::pruneSubsequentAncestorsToRemove(WillBeHeapVector<RefPtrWillBeMember<ContainerNode>>& nodesToRemove, size_t startNodeIndex) { size_t pastLastNodeToRemove = startNodeIndex + 1; for (; pastLastNodeToRemove < nodesToRemove.size(); ++pastLastNodeToRemove) { if (nodesToRemove[pastLastNodeToRemove - 1]->parentNode() != nodesToRemove[pastLastNodeToRemove]) break; ASSERT(nodesToRemove[pastLastNodeToRemove]->firstChild() == nodesToRemove[pastLastNodeToRemove]->lastChild()); } ContainerNode* highestAncestorToRemove = nodesToRemove[pastLastNodeToRemove - 1].get(); RefPtrWillBeRawPtr<ContainerNode> parent = highestAncestorToRemove->parentNode(); if (!parent) // Parent has already been removed. return -1; if (pastLastNodeToRemove == startNodeIndex + 1) return 0; removeNode(nodesToRemove[startNodeIndex], AssumeContentIsAlwaysEditable); insertNodeBefore(nodesToRemove[startNodeIndex], highestAncestorToRemove, AssumeContentIsAlwaysEditable); removeNode(highestAncestorToRemove, AssumeContentIsAlwaysEditable); return pastLastNodeToRemove - startNodeIndex - 1; }
Position InsertTextCommand::insertTab(const Position& pos) { Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent(); Node *node = insertPos.node(); unsigned int offset = insertPos.offset(); // keep tabs coalesced in tab span if (isTabSpanTextNode(node)) { insertTextIntoNode(static_cast<Text *>(node), offset, "\t"); return Position(node, offset + 1); } // create new tab span RefPtr<Element> spanNode = createTabSpanElement(document()); // place it if (!node->isTextNode()) { insertNodeAt(spanNode.get(), node, offset); } else { Text *textNode = static_cast<Text *>(node); if (offset >= textNode->length()) { insertNodeAfter(spanNode.get(), textNode); } else { // split node to make room for the span // NOTE: splitTextNode uses textNode for the // second node in the split, so we need to // insert the span before it. if (offset > 0) splitTextNode(textNode, offset); insertNodeBefore(spanNode.get(), textNode); } } // return the position following the new tab return Position(spanNode->lastChild(), spanNode->lastChild()->caretMaxOffset()); }
void InsertParagraphSeparatorCommand::doApply() { if (!endingSelection().isNonOrphanedCaretOrRange()) return; Position insertionPosition = endingSelection().start(); EAffinity affinity = endingSelection().affinity(); // Delete the current selection. if (endingSelection().isRange()) { calculateStyleBeforeInsertion(insertionPosition); deleteSelection(false, true); insertionPosition = endingSelection().start(); affinity = endingSelection().affinity(); } // FIXME: The parentAnchoredEquivalent conversion needs to be moved into enclosingBlock. RefPtr<Element> startBlock = enclosingBlock(insertionPosition.parentAnchoredEquivalent().containerNode()); Position canonicalPos = VisiblePosition(insertionPosition).deepEquivalent(); if (!startBlock || !startBlock->nonShadowBoundaryParentNode() // FIXME: If the node is hidden, we don't have a canonical position so we will do the wrong thing for tables and <hr>. https://bugs.webkit.org/show_bug.cgi?id=40342 || (!canonicalPos.isNull() && isRenderedTableElement(canonicalPos.deprecatedNode()))) { applyCommandToComposite(InsertLineBreakCommand::create(document())); return; } // Use the leftmost candidate. insertionPosition = insertionPosition.upstream(); if (!insertionPosition.isCandidate()) insertionPosition = insertionPosition.downstream(); // Adjust the insertion position after the delete insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition); VisiblePosition visiblePos(insertionPosition, affinity); calculateStyleBeforeInsertion(insertionPosition); //--------------------------------------------------------------------- // Handle special case of typing return on an empty list item if (breakOutOfEmptyListItem()) return; //--------------------------------------------------------------------- // Prepare for more general cases. bool isFirstInBlock = isStartOfBlock(visiblePos); bool isLastInBlock = isEndOfBlock(visiblePos); bool nestNewBlock = false; // Create block to be inserted. RefPtr<Element> blockToInsert = nullptr; if (startBlock->isRootEditableElement()) { blockToInsert = createDefaultParagraphElement(document()); nestNewBlock = true; } else if (shouldUseDefaultParagraphElement(startBlock.get())) { blockToInsert = createDefaultParagraphElement(document()); } else { blockToInsert = startBlock->cloneElementWithoutChildren(); } //--------------------------------------------------------------------- // Handle case when position is in the last visible position in its block, // including when the block is empty. if (isLastInBlock) { if (nestNewBlock) { if (isFirstInBlock && !lineBreakExistsAtVisiblePosition(visiblePos)) { // The block is empty. Create an empty block to // represent the paragraph that we're leaving. RefPtr<HTMLElement> extraBlock = createDefaultParagraphElement(document()); appendNode(extraBlock, startBlock); } appendNode(blockToInsert, startBlock); } else { // Most of the time we want to stay at the nesting level of the startBlock (e.g., when nesting within lists). However, // for div nodes, this can result in nested div tags that are hard to break out of. Element* siblingElement = startBlock.get(); insertNodeAfter(blockToInsert, siblingElement); } // Recreate the same structure in the new paragraph. Vector<RefPtr<Element> > ancestors; getAncestorsInsideBlock(positionOutsideTabSpan(insertionPosition).deprecatedNode(), startBlock.get(), ancestors); RefPtr<Element> parent = cloneHierarchyUnderNewBlock(ancestors, blockToInsert); setEndingSelection(VisibleSelection(firstPositionInNode(parent.get()), DOWNSTREAM, endingSelection().isDirectional())); return; } //--------------------------------------------------------------------- // Handle case when position is in the first visible position in its block, and // similar case where previous position is in another, presumeably nested, block. if (isFirstInBlock || !inSameBlock(visiblePos, visiblePos.previous())) { Node* refNode = 0; insertionPosition = positionOutsideTabSpan(insertionPosition); if (isFirstInBlock && !nestNewBlock) { refNode = startBlock.get(); } else if (isFirstInBlock && nestNewBlock) { // startBlock should always have children, otherwise isLastInBlock would be true and it's handled above. ASSERT(startBlock->hasChildren()); refNode = startBlock->firstChild(); } else if (insertionPosition.deprecatedNode() == startBlock && nestNewBlock) { refNode = NodeTraversal::childAt(*startBlock, insertionPosition.deprecatedEditingOffset()); ASSERT(refNode); // must be true or we'd be in the end of block case } else refNode = insertionPosition.deprecatedNode(); // find ending selection position easily before inserting the paragraph insertionPosition = insertionPosition.downstream(); if (refNode) insertNodeBefore(blockToInsert, refNode); // Recreate the same structure in the new paragraph. Vector<RefPtr<Element> > ancestors; getAncestorsInsideBlock(positionAvoidingSpecialElementBoundary(positionOutsideTabSpan(insertionPosition)).deprecatedNode(), startBlock.get(), ancestors); // In this case, we need to set the new ending selection. setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM, endingSelection().isDirectional())); return; } //--------------------------------------------------------------------- // Handle the (more complicated) general case, // Move downstream. Typing style code will take care of carrying along the // style of the upstream position. insertionPosition = insertionPosition.downstream(); // At this point, the insertionPosition's node could be a container, and we want to make sure we include // all of the correct nodes when building the ancestor list. So this needs to be the deepest representation of the position // before we walk the DOM tree. insertionPosition = positionOutsideTabSpan(VisiblePosition(insertionPosition).deepEquivalent()); // If the returned position lies either at the end or at the start of an element that is ignored by editing // we should move to its upstream or downstream position. if (editingIgnoresContent(insertionPosition.deprecatedNode())) { if (insertionPosition.atLastEditingPositionForNode()) insertionPosition = insertionPosition.downstream(); else if (insertionPosition.atFirstEditingPositionForNode()) insertionPosition = insertionPosition.upstream(); } // Make sure we do not cause a rendered space to become unrendered. // FIXME: We need the affinity for pos, but pos.downstream() does not give it Position leadingWhitespace = leadingWhitespacePosition(insertionPosition, VP_DEFAULT_AFFINITY); // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions // after the preserved newline, causing the newline to be turned into a nbsp. if (leadingWhitespace.isNotNull() && leadingWhitespace.deprecatedNode()->isTextNode()) { Text* textNode = toText(leadingWhitespace.deprecatedNode()); ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace()); replaceTextInNodePreservingMarkers(textNode, leadingWhitespace.deprecatedEditingOffset(), 1, nonBreakingSpaceString()); } // Split at pos if in the middle of a text node. Position positionAfterSplit; if (insertionPosition.anchorType() == Position::PositionIsOffsetInAnchor && insertionPosition.containerNode()->isTextNode()) { RefPtr<Text> textNode = toText(insertionPosition.containerNode()); bool atEnd = static_cast<unsigned>(insertionPosition.offsetInContainerNode()) >= textNode->length(); if (insertionPosition.deprecatedEditingOffset() > 0 && !atEnd) { splitTextNode(textNode, insertionPosition.offsetInContainerNode()); positionAfterSplit = firstPositionInNode(textNode.get()); insertionPosition.moveToPosition(textNode->previousSibling(), insertionPosition.offsetInContainerNode()); visiblePos = VisiblePosition(insertionPosition); } } // If we got detached due to mutation events, just bail out. if (!startBlock->parentNode()) return; // Put the added block in the tree. if (nestNewBlock) { appendNode(blockToInsert.get(), startBlock); } else { insertNodeAfter(blockToInsert.get(), startBlock); } document().updateLayoutIgnorePendingStylesheets(); // Move the start node and the siblings of the start node. if (VisiblePosition(insertionPosition) != VisiblePosition(positionBeforeNode(blockToInsert.get()))) { Node* n; if (insertionPosition.containerNode() == startBlock) n = insertionPosition.computeNodeAfterPosition(); else { Node* splitTo = insertionPosition.containerNode(); if (splitTo->isTextNode() && insertionPosition.offsetInContainerNode() >= caretMaxOffset(splitTo)) splitTo = NodeTraversal::next(*splitTo, startBlock.get()); ASSERT(splitTo); splitTreeToNode(splitTo, startBlock.get()); for (n = startBlock->firstChild(); n; n = n->nextSibling()) { VisiblePosition beforeNodePosition(positionBeforeNode(n)); if (!beforeNodePosition.isNull() && comparePositions(VisiblePosition(insertionPosition), beforeNodePosition) <= 0) break; } } moveRemainingSiblingsToNewParent(n, blockToInsert.get(), blockToInsert); } // Handle whitespace that occurs after the split if (positionAfterSplit.isNotNull()) { document().updateLayoutIgnorePendingStylesheets(); if (!positionAfterSplit.isRenderedCharacter()) { // Clear out all whitespace and insert one non-breaking space ASSERT(!positionAfterSplit.containerNode()->renderer() || positionAfterSplit.containerNode()->renderer()->style()->collapseWhiteSpace()); deleteInsignificantTextDownstream(positionAfterSplit); if (positionAfterSplit.deprecatedNode()->isTextNode()) insertTextIntoNode(toText(positionAfterSplit.containerNode()), 0, nonBreakingSpaceString()); } } setEndingSelection(VisibleSelection(firstPositionInNode(blockToInsert.get()), DOWNSTREAM, endingSelection().isDirectional())); }
void insertBefore(struct Node *node, double data) { struct Node* newNode = createNode(data); insertNodeBefore(node, newNode); }