📄 replaceselectioncommand.cpp
字号:
RefPtr<CSSMutableStyleDeclaration> styleAtInsertionPos = rangeCompliantEquivalent(insertionPos).computedStyle()->copyInheritableProperties(); String styleText = styleAtInsertionPos->cssText(); if (styleText == static_cast<Element*>(sourceDocumentStyleSpan)->getAttribute(styleAttr)) { fragment.removeNodePreservingChildren(sourceDocumentStyleSpan); if (!isStyleSpan(copiedRangeStyleSpan.get())) return true; } if (isStyleSpan(copiedRangeStyleSpan.get()) && styleText == static_cast<Element*>(copiedRangeStyleSpan.get())->getAttribute(styleAttr)) { fragment.removeNodePreservingChildren(copiedRangeStyleSpan.get()); return true; } return false;}// At copy time, WebKit wraps copied content in a span that contains the source document's // default styles. If the copied Range inherits any other styles from its ancestors, we put // those styles on a second span.// This function removes redundant styles from those spans, and removes the spans if all their // styles are redundant. // We should remove the Apple-style-span class when we're done, see <rdar://problem/5685600>.// We should remove styles from spans that are overridden by all of their children, either here// or at copy time.void ReplaceSelectionCommand::handleStyleSpans(){ Node* sourceDocumentStyleSpan = 0; Node* copiedRangeStyleSpan = 0; // The style span that contains the source document's default style should be at // the top of the fragment, but Mail sometimes adds a wrapper (for Paste As Quotation), // so search for the top level style span instead of assuming it's at the top. for (Node* node = m_firstNodeInserted.get(); node; node = node->traverseNextNode()) { if (isStyleSpan(node)) { sourceDocumentStyleSpan = node; // If the copied Range's common ancestor had user applied inheritable styles // on it, they'll be on a second style span, just below the one that holds the // document defaults. if (isStyleSpan(node->firstChild())) copiedRangeStyleSpan = node->firstChild(); break; } } // There might not be any style spans if we're pasting from another application or if // we are here because of a document.execCommand("InsertHTML", ...) call. if (!sourceDocumentStyleSpan) return; RefPtr<CSSMutableStyleDeclaration> sourceDocumentStyle = static_cast<HTMLElement*>(sourceDocumentStyleSpan)->getInlineStyleDecl()->copy(); Node* context = sourceDocumentStyleSpan->parentNode(); // If Mail wraps the fragment with a Paste as Quotation blockquote, styles from that element are // allowed to override those from the source document, see <rdar://problem/4930986>. if (isMailPasteAsQuotationNode(context)) { RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = computedStyle(context)->copyInheritableProperties(); RefPtr<CSSMutableStyleDeclaration> parentStyle = computedStyle(context->parentNode())->copyInheritableProperties(); parentStyle->diff(blockquoteStyle.get()); CSSMutableStyleDeclaration::const_iterator end = blockquoteStyle->end(); for (CSSMutableStyleDeclaration::const_iterator it = blockquoteStyle->begin(); it != end; ++it) { const CSSProperty& property = *it; sourceDocumentStyle->removeProperty(property.id()); } context = context->parentNode(); } RefPtr<CSSMutableStyleDeclaration> contextStyle = computedStyle(context)->copyInheritableProperties(); contextStyle->diff(sourceDocumentStyle.get()); // Remove block properties in the span's style. This prevents properties that probably have no effect // currently from affecting blocks later if the style is cloned for a new block element during a future // editing operation. // FIXME: They *can* have an effect currently if blocks beneath the style span aren't individually marked // with block styles by the editing engine used to style them. WebKit doesn't do this, but others might. sourceDocumentStyle->removeBlockProperties(); // The styles on sourceDocumentStyleSpan are all redundant, and there is no copiedRangeStyleSpan // to consider. We're finished. if (sourceDocumentStyle->length() == 0 && !copiedRangeStyleSpan) { removeNodePreservingChildren(sourceDocumentStyleSpan); return; } // There are non-redundant styles on sourceDocumentStyleSpan, but there is no // copiedRangeStyleSpan. Clear the redundant styles from sourceDocumentStyleSpan // and return. if (sourceDocumentStyle->length() > 0 && !copiedRangeStyleSpan) { setNodeAttribute(static_cast<Element*>(sourceDocumentStyleSpan), styleAttr, sourceDocumentStyle->cssText()); return; } RefPtr<CSSMutableStyleDeclaration> copiedRangeStyle = static_cast<HTMLElement*>(copiedRangeStyleSpan)->getInlineStyleDecl()->copy(); // We're going to put sourceDocumentStyleSpan's non-redundant styles onto copiedRangeStyleSpan, // as long as they aren't overridden by ones on copiedRangeStyleSpan. sourceDocumentStyle->merge(copiedRangeStyle.get(), true); copiedRangeStyle = sourceDocumentStyle; removeNodePreservingChildren(sourceDocumentStyleSpan); // Remove redundant styles. context = copiedRangeStyleSpan->parentNode(); contextStyle = computedStyle(context)->copyInheritableProperties(); contextStyle->diff(copiedRangeStyle.get()); // See the comments above about removing block properties. copiedRangeStyle->removeBlockProperties(); // All the styles on copiedRangeStyleSpan are redundant, remove it. if (copiedRangeStyle->length() == 0) { removeNodePreservingChildren(copiedRangeStyleSpan); return; } // Clear the redundant styles from the span's style attribute. // FIXME: If font-family:-webkit-monospace is non-redundant, then the font-size should stay, even if it // appears redundant. setNodeAttribute(static_cast<Element*>(copiedRangeStyleSpan), styleAttr, copiedRangeStyle->cssText());}void ReplaceSelectionCommand::mergeEndIfNeeded(){ if (!m_shouldMergeEnd) return; VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent()); VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent()); // Bail to avoid infinite recursion. if (m_movingParagraph) { ASSERT_NOT_REACHED(); return; } // Merging two paragraphs will destroy the moved one's block styles. Always move the end of inserted forward // to preserve the block style of the paragraph already in the document, unless the paragraph to move would // include the what was the start of the selection that was pasted into, so that we preserve that paragraph's // block styles. bool mergeForward = !(inSameParagraph(startOfInsertedContent, endOfInsertedContent) && !isStartOfParagraph(startOfInsertedContent)); VisiblePosition destination = mergeForward ? endOfInsertedContent.next() : endOfInsertedContent; VisiblePosition startOfParagraphToMove = mergeForward ? startOfParagraph(endOfInsertedContent) : endOfInsertedContent.next(); moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination); // Merging forward will remove m_lastLeafInserted from the document. // FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes. The nodes are // only ever used to create positions where inserted content starts/ends. if (mergeForward) { m_lastLeafInserted = destination.previous().deepEquivalent().node(); if (!m_firstNodeInserted->inDocument()) m_firstNodeInserted = endingSelection().visibleStart().deepEquivalent().node(); }}void ReplaceSelectionCommand::doApply(){ VisibleSelection selection = endingSelection(); ASSERT(selection.isCaretOrRange()); ASSERT(selection.start().node()); if (selection.isNone() || !selection.start().node()) return; bool selectionIsPlainText = !selection.isContentRichlyEditable(); Element* currentRoot = selection.rootEditableElement(); ReplacementFragment fragment(document(), m_documentFragment.get(), m_matchStyle, selection); if (m_matchStyle) m_insertionStyle = styleAtPosition(selection.start()); VisiblePosition visibleStart = selection.visibleStart(); VisiblePosition visibleEnd = selection.visibleEnd(); bool selectionEndWasEndOfParagraph = isEndOfParagraph(visibleEnd); bool selectionStartWasStartOfParagraph = isStartOfParagraph(visibleStart); Node* startBlock = enclosingBlock(visibleStart.deepEquivalent().node()); Position insertionPos = selection.start(); bool startIsInsideMailBlockquote = nearestMailBlockquote(insertionPos.node()); if (selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !startIsInsideMailBlockquote || startBlock == currentRoot || startBlock && startBlock->renderer() && startBlock->renderer()->isListItem() || selectionIsPlainText) m_preventNesting = false; if (selection.isRange()) { // When the end of the selection being pasted into is at the end of a paragraph, and that selection // spans multiple blocks, not merging may leave an empty line. // When the start of the selection being pasted into is at the start of a block, not merging // will leave hanging block(s). // Merge blocks if the start of the selection was in a Mail blockquote, since we handle // that case specially to prevent nesting. bool mergeBlocksAfterDelete = startIsInsideMailBlockquote || isEndOfParagraph(visibleEnd) || isStartOfBlock(visibleStart); // FIXME: We should only expand to include fully selected special elements if we are copying a // selection and pasting it on top of itself. deleteSelection(false, mergeBlocksAfterDelete, true, false); visibleStart = endingSelection().visibleStart(); if (fragment.hasInterchangeNewlineAtStart()) { if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) { if (!isEndOfDocument(visibleStart)) setEndingSelection(visibleStart.next()); } else insertParagraphSeparator(); } insertionPos = endingSelection().start(); } else { ASSERT(selection.isCaret()); if (fragment.hasInterchangeNewlineAtStart()) { VisiblePosition next = visibleStart.next(true); if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart) && next.isNotNull()) setEndingSelection(next); else insertParagraphSeparator(); } // We split the current paragraph in two to avoid nesting the blocks from the fragment inside the current block. // For example paste <div>foo</div><div>bar</div><div>baz</div> into <div>x^x</div>, where ^ is the caret. // As long as the div styles are the same, visually you'd expect: <div>xbar</div><div>bar</div><div>bazx</div>, // not <div>xbar<div>bar</div><div>bazx</div></div>. // Don't do this if the selection started in a Mail blockquote. if (m_preventNesting && !startIsInsideMailBlockquote && !isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart)) { insertParagraphSeparator(); setEndingSelection(endingSelection().visibleStart().previous()); } insertionPos = endingSelection().start(); } if (startIsInsideMailBlockquote && m_preventNesting) { // We don't want any of the pasted content to end up nested in a Mail blockquote, so first break // out of any surrounding Mail blockquotes. applyCommandToComposite(BreakBlockquoteCommand::create(document())); // This will leave a br between the split. Node* br = endingSelection().start().node(); ASSERT(br->hasTagName(brTag)); // Insert content between the two blockquotes, but remove the br (since it was just a placeholder). insertionPos = positionBeforeNode(br); removeNode(br); } // Inserting content could cause whitespace to collapse, e.g. inserting <div>foo</div> into hello^ world. prepareWhitespaceAtPositionForSplit(insertionPos); // NOTE: This would be an incorrect usage of downstream() if downstream() were changed to mean the last position after // p that maps to the same visible position as p (since in the case where a br is at the end of a block and collapsed // away, there are positions after the br which map to the same visible position as [br, 0]). Node* endBR = insertionPos.downstream().node()->hasTagName(brTag) ? insertionPos.downstream().node() : 0; VisiblePosition originalVisPosBeforeEndBR; if (endBR) originalVisPosBeforeEndBR = VisiblePosition(endBR, 0, DOWNSTREAM).previous(); startBlock = enclosingBlock(insertionPos.node()); // Adjust insertionPos to prevent nesting. // If the start was in a Mail blockquote, we will have already handled adjusting insertionPos above. if (m_preventNesting && startBlock && !startIsInsideMailBlockquote) { ASSERT(startBlock != currentRoot); VisiblePosition visibleInsertionPos(insertionPos); if (isEndOfBlock(visibleInsertionPos) && !(isStartOfBlock(visibleInsertionPos) && fragment.hasInterchangeNewlineAtEnd())) insertionPos = positionAfterNode(startBlock); else if (isStartOfBlock(visibleInsertionPos)) insertionPos = positionBeforeNode(startBlock); } // Paste into run of tabs splits the tab span. insertionPos = positionOutsideTabSpan(insertionPos);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -