void SplitTextNodeContainingElementCommand::doApply() { ASSERT(m_text); ASSERT(m_offset > 0); splitTextNode(m_text.get(), m_offset); Node *parentNode = m_text->parentNode(); if (!parentNode->renderer() || !parentNode->renderer()->isInline()) { wrapContentsInDummySpan(static_cast<Element *>(parentNode)); parentNode = parentNode->firstChild(); } splitElement(static_cast<Element *>(parentNode), m_text.get()); }
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()); }
void SplitTextNodeContainingElementCommand::doApply() { ASSERT(m_text); ASSERT(m_offset > 0); splitTextNode(m_text.get(), m_offset); Element* parent = m_text->parentElement(); if (!parent || !parent->parentElement() || !parent->parentElement()->hasEditableStyle()) return; LayoutObject* parentLayoutObject = parent->layoutObject(); if (!parentLayoutObject || !parentLayoutObject->isInline()) { wrapContentsInDummySpan(parent); Node* firstChild = parent->firstChild(); if (!firstChild || !firstChild->isElementNode()) return; parent = toElement(firstChild); } splitElement(parent, m_text.get()); }
void SplitTextNodeContainingElementCommand::doApply() { ASSERT(m_text); ASSERT(m_offset > 0); splitTextNode(m_text.get(), m_offset); Element* parent = m_text->parentElement(); if (!parent || !parent->parentElement() || !parent->parentElement()->isContentEditable()) return; RenderObject* parentRenderer = parent->renderer(); if (!parentRenderer || !parentRenderer->isInline()) { wrapContentsInDummySpan(parent); Node* firstChild = parent->firstChild(); if (!firstChild || !firstChild->isElementNode()) return; parent = static_cast<Element*>(firstChild); } splitElement(parent, m_text); }
void SplitTextNodeContainingElementCommand::doApply() { ASSERT(m_text); ASSERT(m_offset > 0); splitTextNode(m_text.get(), m_offset); Element* parent = m_text->parentElement(); if (!parent || !parent->parentElement() || !parent->parentElement()->hasEditableStyle()) return; RenderElement* parentRenderer = parent->renderer(); if (!parentRenderer || !parentRenderer->isInline()) { wrapContentsInDummySpan(*parent); Node* firstChild = parent->firstChild(); if (!is<Element>(firstChild)) return; parent = downcast<Element>(firstChild); } splitElement(parent, m_text); }
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())); }