📄 compositeeditcommand.cpp
字号:
ASSERT(anchorNode->isLink()); setEndingSelection(VisibleSelection::selectionFromContentsOfNode(anchorNode)); applyStyledElement(static_cast<Element*>(anchorNode)); // Clones of anchorNode have been pushed down, now remove it. if (anchorNode->inDocument()) removeNodePreservingChildren(anchorNode);}// We must push partially selected anchors down before creating or removing// links from a selection to create fully selected chunks that can be removed.// ApplyStyleCommand doesn't do this for us because styles can be nested.// Anchors cannot be nested.void CompositeEditCommand::pushPartiallySelectedAnchorElementsDown(){ VisibleSelection originalSelection = endingSelection(); VisiblePosition visibleStart(originalSelection.start()); VisiblePosition visibleEnd(originalSelection.end()); Node* startAnchor = enclosingAnchorElement(originalSelection.start()); VisiblePosition startOfStartAnchor(Position(startAnchor, 0)); if (startAnchor && startOfStartAnchor != visibleStart) pushAnchorElementDown(startAnchor); Node* endAnchor = enclosingAnchorElement(originalSelection.end()); VisiblePosition endOfEndAnchor(Position(endAnchor, 0)); if (endAnchor && endOfEndAnchor != visibleEnd) pushAnchorElementDown(endAnchor); ASSERT(originalSelection.start().node()->inDocument() && originalSelection.end().node()->inDocument()); setEndingSelection(originalSelection);}// This moves a paragraph preserving its style.void CompositeEditCommand::moveParagraph(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle){ ASSERT(isStartOfParagraph(startOfParagraphToMove)); ASSERT(isEndOfParagraph(endOfParagraphToMove)); moveParagraphs(startOfParagraphToMove, endOfParagraphToMove, destination, preserveSelection, preserveStyle);}void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, const VisiblePosition& destination, bool preserveSelection, bool preserveStyle){ if (startOfParagraphToMove == destination) return; int startIndex = -1; int endIndex = -1; int destinationIndex = -1; if (preserveSelection && !endingSelection().isNone()) { VisiblePosition visibleStart = endingSelection().visibleStart(); VisiblePosition visibleEnd = endingSelection().visibleEnd(); bool startAfterParagraph = Range::compareBoundaryPoints(visibleStart.deepEquivalent(), endOfParagraphToMove.deepEquivalent()) > 0; bool endBeforeParagraph = Range::compareBoundaryPoints(visibleEnd.deepEquivalent(), startOfParagraphToMove.deepEquivalent()) < 0; if (!startAfterParagraph && !endBeforeParagraph) { bool startInParagraph = Range::compareBoundaryPoints(visibleStart.deepEquivalent(), startOfParagraphToMove.deepEquivalent()) >= 0; bool endInParagraph = Range::compareBoundaryPoints(visibleEnd.deepEquivalent(), endOfParagraphToMove.deepEquivalent()) <= 0; startIndex = 0; if (startInParagraph) { RefPtr<Range> startRange = Range::create(document(), rangeCompliantEquivalent(startOfParagraphToMove.deepEquivalent()), rangeCompliantEquivalent(visibleStart.deepEquivalent())); startIndex = TextIterator::rangeLength(startRange.get(), true); } endIndex = 0; if (endInParagraph) { RefPtr<Range> endRange = Range::create(document(), rangeCompliantEquivalent(startOfParagraphToMove.deepEquivalent()), rangeCompliantEquivalent(visibleEnd.deepEquivalent())); endIndex = TextIterator::rangeLength(endRange.get(), true); } } } VisiblePosition beforeParagraph = startOfParagraphToMove.previous(); VisiblePosition afterParagraph(endOfParagraphToMove.next()); // We upstream() the end and downstream() the start so that we don't include collapsed whitespace in the move. // When we paste a fragment, spaces after the end and before the start are treated as though they were rendered. Position start = startOfParagraphToMove.deepEquivalent().downstream(); Position end = endOfParagraphToMove.deepEquivalent().upstream(); // start and end can't be used directly to create a Range; they are "editing positions" Position startRangeCompliant = rangeCompliantEquivalent(start); Position endRangeCompliant = rangeCompliantEquivalent(end); RefPtr<Range> range = Range::create(document(), startRangeCompliant.node(), startRangeCompliant.offset(), endRangeCompliant.node(), endRangeCompliant.offset()); // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It // shouldn't matter though, since moved paragraphs will usually be quite small. RefPtr<DocumentFragment> fragment = startOfParagraphToMove != endOfParagraphToMove ? createFragmentFromMarkup(document(), createMarkup(range.get(), 0, DoNotAnnotateForInterchange, true), "") : 0; // A non-empty paragraph's style is moved when we copy and move it. We don't move // anything if we're given an empty paragraph, but an empty paragraph can have style // too, <div><b><br></b></div> for example. Save it so that we can preserve it later. RefPtr<CSSMutableStyleDeclaration> styleInEmptyParagraph; if (startOfParagraphToMove == endOfParagraphToMove && preserveStyle) { styleInEmptyParagraph = styleAtPosition(startOfParagraphToMove.deepEquivalent()); // The moved paragraph should assume the block style of the destination. styleInEmptyParagraph->removeBlockProperties(); } // FIXME (5098931): We should add a new insert action "WebViewInsertActionMoved" and call shouldInsertFragment here. setEndingSelection(VisibleSelection(start, end, DOWNSTREAM)); deleteSelection(false, false, false, false); ASSERT(destination.deepEquivalent().node()->inDocument()); // There are bugs in deletion when it removes a fully selected table/list. // It expands and removes the entire table/list, but will let content // before and after the table/list collapse onto one line. // Deleting a paragraph will leave a placeholder. Remove it (and prune // empty or unrendered parents). VisiblePosition caretAfterDelete = endingSelection().visibleStart(); if (isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) { // Note: We want the rightmost candidate. Position position = caretAfterDelete.deepEquivalent().downstream(); Node* node = position.node(); // Normally deletion will leave a br as a placeholder. if (node->hasTagName(brTag)) removeNodeAndPruneAncestors(node); // If the selection to move was empty and in an empty block that // doesn't require a placeholder to prop itself open (like a bordered // div or an li), remove it during the move (the list removal code // expects this behavior). else if (isBlock(node)) removeNodeAndPruneAncestors(node); else if (lineBreakExistsAtPosition(caretAfterDelete)) { // There is a preserved '\n' at caretAfterDelete. Text* textNode = static_cast<Text*>(node); if (textNode->length() == 1) removeNodeAndPruneAncestors(node); else deleteTextFromNode(textNode, position.offset(), 1); } } // Add a br if pruning an empty block level element caused a collapse. For example: // foo^ // <div>bar</div> // baz // Imagine moving 'bar' to ^. 'bar' will be deleted and its div pruned. That would // cause 'baz' to collapse onto the line with 'foobar' unless we insert a br. // Must recononicalize these two VisiblePositions after the pruning above. beforeParagraph = VisiblePosition(beforeParagraph.deepEquivalent()); afterParagraph = VisiblePosition(afterParagraph.deepEquivalent()); if (beforeParagraph.isNotNull() && (!isEndOfParagraph(beforeParagraph) || beforeParagraph == afterParagraph)) { // FIXME: Trim text between beforeParagraph and afterParagraph if they aren't equal. insertNodeAt(createBreakElement(document()), beforeParagraph.deepEquivalent()); // Need an updateLayout here in case inserting the br has split a text node. updateLayout(); } RefPtr<Range> startToDestinationRange(Range::create(document(), Position(document(), 0), rangeCompliantEquivalent(destination.deepEquivalent()))); destinationIndex = TextIterator::rangeLength(startToDestinationRange.get(), true); setEndingSelection(destination); applyCommandToComposite(ReplaceSelectionCommand::create(document(), fragment, true, false, !preserveStyle, false, true)); // Restore styles from an empty paragraph to the new empty paragraph. if (styleInEmptyParagraph) applyStyle(styleInEmptyParagraph.get()); if (preserveSelection && startIndex != -1) { // Fragment creation (using createMarkup) incorrectly uses regular // spaces instead of nbsps for some spaces that were rendered (11475), which // causes spaces to be collapsed during the move operation. This results // in a call to rangeFromLocationAndLength with a location past the end // of the document (which will return null). RefPtr<Range> start = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + startIndex, 0, true); RefPtr<Range> end = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + endIndex, 0, true); if (start && end) setEndingSelection(VisibleSelection(start->startPosition(), end->startPosition(), DOWNSTREAM)); }}// FIXME: Send an appropriate shouldDeleteRange call.bool CompositeEditCommand::breakOutOfEmptyListItem(){ Node* emptyListItem = enclosingEmptyListItem(endingSelection().visibleStart()); if (!emptyListItem) return false; RefPtr<CSSMutableStyleDeclaration> style = styleAtPosition(endingSelection().start()); Node* listNode = emptyListItem->parentNode(); if (!listNode->isContentEditable()) return false; RefPtr<Element> newBlock = isListElement(listNode->parentNode()) ? createListItemElement(document()) : createDefaultParagraphElement(document()); if (emptyListItem->renderer()->nextSibling()) { if (emptyListItem->renderer()->previousSibling()) splitElement(static_cast<Element*>(listNode), emptyListItem); insertNodeBefore(newBlock, listNode); removeNode(emptyListItem); } else { insertNodeAfter(newBlock, listNode); removeNode(emptyListItem->renderer()->previousSibling() ? emptyListItem : listNode); } appendBlockPlaceholder(newBlock); setEndingSelection(VisibleSelection(Position(newBlock.get(), 0), DOWNSTREAM)); computedStyle(endingSelection().start().node())->diff(style.get()); if (style->length() > 0) applyStyle(style.get()); return true;}// If the caret is in an empty quoted paragraph, and either there is nothing before that// paragraph, or what is before is unquoted, and the user presses delete, unquote that paragraph.bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph(){ if (!endingSelection().isCaret()) return false; VisiblePosition caret(endingSelection().visibleStart()); Node* highestBlockquote = highestEnclosingNodeOfType(caret.deepEquivalent(), &isMailBlockquote); if (!highestBlockquote) return false; if (!isStartOfParagraph(caret) || !isEndOfParagraph(caret)) return false; VisiblePosition previous(caret.previous(true)); // Only move forward if there's nothing before the caret, or if there's unquoted content before it. if (enclosingNodeOfType(previous.deepEquivalent(), &isMailBlockquote)) return false; RefPtr<Node> br = createBreakElement(document()); // We want to replace this quoted paragraph with an unquoted one, so insert a br // to hold the caret before the highest blockquote. insertNodeBefore(br, highestBlockquote); VisiblePosition atBR(Position(br.get(), 0)); // If the br we inserted collapsed, for example foo<br><blockquote>...</blockquote>, insert // a second one. if (!isStartOfParagraph(atBR)) insertNodeBefore(createBreakElement(document()), br); setEndingSelection(VisibleSelection(atBR)); // If this is an empty paragraph there must be a line break here. if (!lineBreakExistsAtPosition(caret)) return false; Position caretPos(caret.deepEquivalent()); // A line break is either a br or a preserved newline. ASSERT(caretPos.node()->hasTagName(brTag) || caretPos.node()->isTextNode() && caretPos.node()->renderer()->style()->preserveNewline()); if (caretPos.node()->hasTagName(brTag)) { Position beforeBR(positionBeforeNode(caretPos.node())); removeNode(caretPos.node()); prune(beforeBR.node()); } else { ASSERT(caretPos.offset() == 0); Text* textNode = static_cast<Text*>(caretPos.node()); Node* parentNode = textNode->parentNode(); // The preserved newline must be the first thing in the node, since otherwise the previous // paragraph would be quoted, and we verified that it wasn't above. deleteTextFromNode(textNode, 0, 1); prune(parentNode); } return true;}// Operations use this function to avoid inserting content into an anchor when at the start or the end of // that anchor, as in NSTextView.// FIXME: This is only an approximation of NSTextViews insertion behavior, which varies depending on how// the caret was made. Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Position& original){ if (original.isNull()) return original; VisiblePosition visiblePos(original); Node* enclosingAnchor = enclosingAnchorElement(original); Position result = original; // Don't avoid block level anchors, because that would insert content into the wrong paragraph. if (enclosingAnchor && !isBlock(enclosingAnchor)) { VisiblePosition firstInAnchor(Position(enclosingAnchor, 0)); VisiblePosition lastInAnchor(Position(enclosingAnchor, maxDeepOffset(enclosingAnchor))); // If visually just after the anchor, insert *inside* the anchor unless it's the last // VisiblePosition in the document, to match NSTextView. if (visiblePos == lastInAnchor) { // Make sure anchors are pushed down before avoiding them so that we don't // also avoid structural elements like lists and blocks (5142012). if (original.node() != enclosingAnchor && original.node()->parentNode() != enclosingAnchor) { pushAnchorElementDown(enclosingAnchor); enclosingAnchor = enclosingAnchorElement(original); if (!enclosingAnchor) return original; } // Don't insert outside an anchor if doing so would skip over a line break. It would // probably be safe to move the line break so that we could still avoid the anchor here. Position downstream(visiblePos.deepEquivalent().downstream()); if (lineBreakExistsAtPosition(visiblePos) && downstream.node()->isDescendantOf(enclosingAnchor)) return original; result = positionAfterNode(enclosingAnchor); } // If visually just before an anchor, insert *outside* the anchor unless it's the first // VisiblePosition in a paragraph, to match NSTextView. if (visiblePos == firstInAnchor) { // Make sure anchors are pushed down before avoiding them so that we don't // also avoid structural elements like lists and blocks (5142012). if (original.node() != enclosingAnchor && original.node()->parentNode() != enclosingAnchor) { pushAnchorElementDown(enclosingAnchor); enclosingAnchor = enclosingAnchorElement(original); } result = positionBeforeNode(enclosingAnchor); } } if (result.isNull() || !editableRootForPosition(result)) result = original; return result;}// Splits the tree parent by parent until we reach the specified ancestor. We use VisiblePositions// to determine if the split is necessary. Returns the last split node.PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, bool splitAncestor){ RefPtr<Node> node; for (node = start; node && node->parent() != end; node = node->parent()) { VisiblePosition positionInParent(Position(node->parent(), 0), DOWNSTREAM); VisiblePosition positionInNode(Position(node, 0), DOWNSTREAM); if (positionInParent != positionInNode) applyCommandToComposite(SplitElementCommand::create(static_cast<Element*>(node->parent()), node)); } if (splitAncestor) return splitTreeToNode(end, end->parent()); return node.release();}PassRefPtr<Element> createBlockPlaceholderElement(Document* document){ RefPtr<Element> breakNode = document->createElement(brTag, false); return breakNode.release();}} // namespace WebCore
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -