diff options
Diffstat (limited to 'Source/WebCore/editing')
47 files changed, 563 insertions, 594 deletions
diff --git a/Source/WebCore/editing/AppendNodeCommand.cpp b/Source/WebCore/editing/AppendNodeCommand.cpp index 58f7fa6..9395968 100644 --- a/Source/WebCore/editing/AppendNodeCommand.cpp +++ b/Source/WebCore/editing/AppendNodeCommand.cpp @@ -31,7 +31,7 @@ namespace WebCore { -AppendNodeCommand::AppendNodeCommand(PassRefPtr<Element> parent, PassRefPtr<Node> node) +AppendNodeCommand::AppendNodeCommand(PassRefPtr<ContainerNode> parent, PassRefPtr<Node> node) : SimpleEditCommand(parent->document()) , m_parent(parent) , m_node(node) @@ -40,7 +40,7 @@ AppendNodeCommand::AppendNodeCommand(PassRefPtr<Element> parent, PassRefPtr<Node ASSERT(m_node); ASSERT(!m_node->parentNode()); - ASSERT(m_parent->isContentEditable() || !m_parent->attached()); + ASSERT(m_parent->rendererIsEditable() || !m_parent->attached()); } static void sendAXTextChangedIgnoringLineBreaks(Node* node, AXObjectCache::AXTextChange textChange) @@ -56,7 +56,7 @@ static void sendAXTextChangedIgnoringLineBreaks(Node* node, AXObjectCache::AXTex void AppendNodeCommand::doApply() { - if (!m_parent->isContentEditable() && m_parent->attached()) + if (!m_parent->rendererIsEditable() && m_parent->attached()) return; ExceptionCode ec; @@ -68,7 +68,7 @@ void AppendNodeCommand::doApply() void AppendNodeCommand::doUnapply() { - if (!m_parent->isContentEditable()) + if (!m_parent->rendererIsEditable()) return; // Need to notify this before actually deleting the text diff --git a/Source/WebCore/editing/AppendNodeCommand.h b/Source/WebCore/editing/AppendNodeCommand.h index 5ffb881..87a8cd2 100644 --- a/Source/WebCore/editing/AppendNodeCommand.h +++ b/Source/WebCore/editing/AppendNodeCommand.h @@ -32,18 +32,18 @@ namespace WebCore { class AppendNodeCommand : public SimpleEditCommand { public: - static PassRefPtr<AppendNodeCommand> create(PassRefPtr<Element> parent, PassRefPtr<Node> node) + static PassRefPtr<AppendNodeCommand> create(PassRefPtr<ContainerNode> parent, PassRefPtr<Node> node) { return adoptRef(new AppendNodeCommand(parent, node)); } private: - AppendNodeCommand(PassRefPtr<Element> parent, PassRefPtr<Node> node); + AppendNodeCommand(PassRefPtr<ContainerNode> parent, PassRefPtr<Node>); virtual void doApply(); virtual void doUnapply(); - RefPtr<Element> m_parent; + RefPtr<ContainerNode> m_parent; RefPtr<Node> m_node; }; diff --git a/Source/WebCore/editing/ApplyBlockElementCommand.cpp b/Source/WebCore/editing/ApplyBlockElementCommand.cpp index 7e20acc..c8f2161 100644 --- a/Source/WebCore/editing/ApplyBlockElementCommand.cpp +++ b/Source/WebCore/editing/ApplyBlockElementCommand.cpp @@ -72,7 +72,7 @@ void ApplyBlockElementCommand::doApply() // margin/padding, but not others. We should make the gap painting more consistent and // then use a left margin/padding rule here. if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd)) - setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true))); + setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(CannotCrossEditingBoundary))); VisibleSelection selection = selectionForParagraphIteration(endingSelection()); VisiblePosition startOfSelection = selection.visibleStart(); @@ -193,7 +193,7 @@ void ApplyBlockElementCommand::rangeForParagraphSplittingTextNodesIfNeeded(const if (!startStyle->collapseWhiteSpace() && start.offsetInContainerNode() > 0) { int startOffset = start.offsetInContainerNode(); splitTextNode(static_cast<Text*>(start.deprecatedNode()), startOffset); - start = positionBeforeNode(start.deprecatedNode()); + start = firstPositionInOrBeforeNode(start.deprecatedNode()); if (isStartAndEndOnSameNode) { ASSERT(end.offsetInContainerNode() >= startOffset); end = Position(end.deprecatedNode(), end.offsetInContainerNode() - startOffset, Position::PositionIsOffsetInAnchor); @@ -224,7 +224,7 @@ void ApplyBlockElementCommand::rangeForParagraphSplittingTextNodesIfNeeded(const && end.offsetInContainerNode() < end.containerNode()->maxCharacterOffset()) { splitTextNode(static_cast<Text*>(end.deprecatedNode()), end.offsetInContainerNode()); if (isStartAndEndOnSameNode) - start = positionBeforeNode(end.deprecatedNode()->previousSibling()); + start = firstPositionInOrBeforeNode(end.deprecatedNode()->previousSibling()); if (isEndAndEndOfLastParagraphOnSameNode) { if (m_endOfLastParagraph.offsetInContainerNode() == end.offsetInContainerNode()) m_endOfLastParagraph = lastPositionInNode(end.deprecatedNode()->previousSibling()); diff --git a/Source/WebCore/editing/ApplyStyleCommand.cpp b/Source/WebCore/editing/ApplyStyleCommand.cpp index f9ed18e..59540ec 100644 --- a/Source/WebCore/editing/ApplyStyleCommand.cpp +++ b/Source/WebCore/editing/ApplyStyleCommand.cpp @@ -57,11 +57,9 @@ using namespace HTMLNames; static RGBA32 getRGBAFontColor(CSSStyleDeclaration* style) { RefPtr<CSSValue> colorValue = style->getPropertyCSSValue(CSSPropertyColor); - if (!colorValue) + if (!colorValue || !colorValue->isPrimitiveValue()) return Color::transparent; - ASSERT(colorValue->isPrimitiveValue()); - CSSPrimitiveValue* primitiveColor = static_cast<CSSPrimitiveValue*>(colorValue.get()); RGBA32 rgba = 0; if (primitiveColor->primitiveType() != CSSPrimitiveValue::CSS_RGBCOLOR) { @@ -76,7 +74,7 @@ static RGBA32 getRGBAFontColor(CSSStyleDeclaration* style) class StyleChange { public: - explicit StyleChange(CSSStyleDeclaration*, const Position&); + StyleChange(EditingStyle*, const Position&); String cssStyle() const { return m_cssStyle; } bool applyBold() const { return m_applyBold; } @@ -111,7 +109,7 @@ public: return !(*this == other); } private: - void init(PassRefPtr<CSSStyleDeclaration>, const Position&); + void init(EditingStyle*, const Position&); void reconcileTextDecorationProperties(CSSMutableStyleDeclaration*); void extractTextStyles(Document*, CSSMutableStyleDeclaration*, bool shouldUseFixedFontDefaultSize); @@ -128,7 +126,7 @@ private: }; -StyleChange::StyleChange(CSSStyleDeclaration* style, const Position& position) +StyleChange::StyleChange(EditingStyle* style, const Position& position) : m_applyBold(false) , m_applyItalic(false) , m_applyUnderline(false) @@ -139,14 +137,14 @@ StyleChange::StyleChange(CSSStyleDeclaration* style, const Position& position) init(style, position); } -void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position& position) +void StyleChange::init(EditingStyle* style, const Position& position) { Document* document = position.anchorNode() ? position.anchorNode()->document() : 0; - if (!style || !document || !document->frame()) + if (!style || !style->style() || !document || !document->frame()) return; RefPtr<CSSComputedStyleDeclaration> computedStyle = position.computedStyle(); - RefPtr<CSSMutableStyleDeclaration> mutableStyle = getPropertiesNotIn(style.get(), computedStyle.get()); + RefPtr<CSSMutableStyleDeclaration> mutableStyle = getPropertiesNotIn(style->style(), computedStyle.get()); reconcileTextDecorationProperties(mutableStyle.get()); if (!document->frame()->editor()->shouldStyleWithCSS()) @@ -158,8 +156,8 @@ void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position& po // If unicode-bidi is present in mutableStyle and direction is not, then add direction to mutableStyle. // FIXME: Shouldn't this be done in getPropertiesNotIn? - if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->getPropertyCSSValue(CSSPropertyDirection)) - mutableStyle->setProperty(CSSPropertyDirection, style->getPropertyValue(CSSPropertyDirection)); + if (mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi) && !style->style()->getPropertyCSSValue(CSSPropertyDirection)) + mutableStyle->setProperty(CSSPropertyDirection, style->style()->getPropertyValue(CSSPropertyDirection)); // Save the result for later m_cssStyle = mutableStyle->cssText().stripWhiteSpace(); @@ -591,7 +589,7 @@ void ApplyStyleCommand::applyBlockStyle(EditingStyle *style) VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next()); VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next()); while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) { - StyleChange styleChange(style->style(), paragraphStart.deepEquivalent()); + StyleChange styleChange(style, paragraphStart.deepEquivalent()); if (styleChange.cssStyle().length() || m_removeOnly) { RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().deprecatedNode()); if (!m_removeOnly) { @@ -1017,12 +1015,12 @@ void ApplyStyleCommand::fixRangeAndApplyInlineStyle(EditingStyle* style, const P static bool containsNonEditableRegion(Node* node) { - if (!node->isContentEditable()) + if (!node->rendererIsEditable()) return true; Node* sibling = node->traverseNextSibling(); for (Node* descendent = node->firstChild(); descendent && descendent != sibling; descendent = descendent->traverseNextNode()) { - if (!descendent->isContentEditable()) + if (!descendent->rendererIsEditable()) return true; } @@ -1037,10 +1035,10 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, Node* n for (RefPtr<Node> next; node && node != pastEndNode; node = next.get()) { next = node->traverseNextNode(); - if (!node->renderer() || !node->isContentEditable()) + if (!node->renderer() || !node->rendererIsEditable()) continue; - if (!node->isContentRichlyEditable() && node->isHTMLElement()) { + if (!node->rendererIsRichlyEditable() && node->isHTMLElement()) { // This is a plaintext-only region. Only proceed if it's fully selected. // pastEndNode is the node after the last fully selected node, so if it's inside node then // node isn't fully selected. @@ -1059,7 +1057,7 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, Node* n continue; if (node->childNodeCount()) { - if (node->contains(pastEndNode) || containsNonEditableRegion(node) || !node->parentNode()->isContentEditable()) + if (node->contains(pastEndNode) || containsNonEditableRegion(node) || !node->parentNode()->rendererIsEditable()) continue; if (editingIgnoresContent(node)) { next = node->traverseNextSibling(); @@ -1080,7 +1078,7 @@ void ApplyStyleCommand::applyInlineStyleToNodeRange(EditingStyle* style, Node* n if (!removeStyleFromRunBeforeApplyingStyle(style, runStart, runEnd)) continue; - addInlineStyleIfNeeded(style->style(), runStart.get(), runEnd.get(), AddStyledElement); + addInlineStyleIfNeeded(style, runStart.get(), runEnd.get(), AddStyledElement); } } @@ -1099,7 +1097,7 @@ bool ApplyStyleCommand::removeStyleFromRunBeforeApplyingStyle(EditingStyle* styl if (node->childNodeCount()) continue; // We don't consider m_isInlineElementToRemoveFunction here because we never apply style when m_isInlineElementToRemoveFunction is specified - if ((!style->isEmpty() && getPropertiesNotIn(style->style(), computedStyle(node).get())->length()) + if (!style->styleIsPresentInComputedStyleOfNode(node) || (m_styledInlineElement && !enclosingNodeWithTag(positionBeforeNode(node), m_styledInlineElement->tagQName()))) { needToApplyStyle = true; break; @@ -1134,26 +1132,18 @@ bool ApplyStyleCommand::removeInlineStyleFromElement(EditingStyle* style, PassRe { ASSERT(element); - if (!element->parentNode() || !element->parentNode()->isContentEditable()) + if (!element->parentNode() || !element->parentNode()->rendererIsEditable()) return false; if (isStyledInlineElementToRemove(element.get())) { if (mode == RemoveNone) return true; ASSERT(extractedStyle); - if (element->inlineStyleDecl()) { - if (extractedStyle->style()) - extractedStyle->style()->merge(element->inlineStyleDecl()); - else - extractedStyle->setStyle(element->inlineStyleDecl()->copy()); - } + extractedStyle->mergeInlineStyleOfElement(element.get()); removeNodePreservingChildren(element); return true; } - if (!style->style()) - return false; - bool removed = false; if (removeImplicitlyStyledElement(style, element.get(), mode, extractedStyle)) removed = true; @@ -1296,7 +1286,7 @@ void ApplyStyleCommand::applyInlineStyleToPushDown(Node* node, EditingStyle* sty // We can't wrap node with the styled element here because new styled element will never be removed if we did. // If we modified the child pointer in pushDownInlineStyleAroundNode to point to new style element // then we fall into an infinite loop where we keep removing and adding styled element wrapping node. - addInlineStyleIfNeeded(newInlineStyle->style(), node, node, DoNotAddStyledElement); + addInlineStyleIfNeeded(newInlineStyle.get(), node, node, DoNotAddStyledElement); } void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* targetNode) @@ -1312,7 +1302,6 @@ void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* Vector<RefPtr<Element> > elementsToPushDown; while (current != targetNode) { ASSERT(current); - ASSERT(current->isHTMLElement()); ASSERT(current->contains(targetNode)); Node* child = current->firstChild(); Node* lastChild = current->lastChild(); @@ -1321,8 +1310,10 @@ void ApplyStyleCommand::pushDownInlineStyleAroundNode(EditingStyle* style, Node* styledElement = static_cast<StyledElement*>(current); elementsToPushDown.append(styledElement); } + RefPtr<EditingStyle> styleToPushDown = EditingStyle::create(); - removeInlineStyleFromElement(style, toHTMLElement(current), RemoveIfNeeded, styleToPushDown.get()); + if (current->isHTMLElement()) + removeInlineStyleFromElement(style, toHTMLElement(current), RemoveIfNeeded, styleToPushDown.get()); // The inner loop will go through children on each level // FIXME: we should aggregate inline child elements together so that we don't wrap each child separately. @@ -1659,13 +1650,13 @@ void ApplyStyleCommand::surroundNodeRangeWithElement(PassRefPtr<Node> passedStar RefPtr<Node> nextSibling = element->nextSibling(); RefPtr<Node> previousSibling = element->previousSibling(); - if (nextSibling && nextSibling->isElementNode() && nextSibling->isContentEditable() + if (nextSibling && nextSibling->isElementNode() && nextSibling->rendererIsEditable() && areIdenticalElements(element.get(), static_cast<Element*>(nextSibling.get()))) mergeIdenticalElements(element.get(), static_cast<Element*>(nextSibling.get())); - if (previousSibling && previousSibling->isElementNode() && previousSibling->isContentEditable()) { + if (previousSibling && previousSibling->isElementNode() && previousSibling->rendererIsEditable()) { Node* mergedElement = previousSibling->nextSibling(); - if (mergedElement->isElementNode() && mergedElement->isContentEditable() + if (mergedElement->isElementNode() && mergedElement->rendererIsEditable() && areIdenticalElements(static_cast<Element*>(previousSibling.get()), static_cast<Element*>(mergedElement))) mergeIdenticalElements(static_cast<Element*>(previousSibling.get()), static_cast<Element*>(mergedElement)); } @@ -1689,7 +1680,7 @@ void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElemen setNodeAttribute(block, styleAttr, cssText); } -void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style, PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, EAddStyledElement addStyledElement) +void ApplyStyleCommand::addInlineStyleIfNeeded(EditingStyle* style, PassRefPtr<Node> passedStart, PassRefPtr<Node> passedEnd, EAddStyledElement addStyledElement) { if (!passedStart || !passedEnd || !passedStart->inDocument() || !passedEnd->inDocument()) return; @@ -1704,7 +1695,7 @@ void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style insertNodeAt(dummyElement, positionBeforeNode(startNode.get())); positionForStyleComparison = positionBeforeNode(dummyElement.get()); } else - positionForStyleComparison = firstPositionInNode(startNode.get()); + positionForStyleComparison = firstPositionInOrBeforeNode(startNode.get()); StyleChange styleChange(style, positionForStyleComparison); diff --git a/Source/WebCore/editing/ApplyStyleCommand.h b/Source/WebCore/editing/ApplyStyleCommand.h index d4444e4..dc2217e 100644 --- a/Source/WebCore/editing/ApplyStyleCommand.h +++ b/Source/WebCore/editing/ApplyStyleCommand.h @@ -97,7 +97,7 @@ private: void fixRangeAndApplyInlineStyle(EditingStyle*, const Position& start, const Position& end); void applyInlineStyleToNodeRange(EditingStyle*, Node* startNode, Node* pastEndNode); void addBlockStyle(const StyleChange&, HTMLElement*); - void addInlineStyleIfNeeded(CSSMutableStyleDeclaration*, PassRefPtr<Node> start, PassRefPtr<Node> end, EAddStyledElement addStyledElement = AddStyledElement); + void addInlineStyleIfNeeded(EditingStyle*, PassRefPtr<Node> start, PassRefPtr<Node> end, EAddStyledElement = AddStyledElement); void splitTextAtStart(const Position& start, const Position& end); void splitTextAtEnd(const Position& start, const Position& end); void splitTextElementAtStart(const Position& start, const Position& end); diff --git a/Source/WebCore/editing/BreakBlockquoteCommand.cpp b/Source/WebCore/editing/BreakBlockquoteCommand.cpp index 011a787..91f496c 100644 --- a/Source/WebCore/editing/BreakBlockquoteCommand.cpp +++ b/Source/WebCore/editing/BreakBlockquoteCommand.cpp @@ -66,12 +66,8 @@ void BreakBlockquoteCommand::doApply() Position pos = endingSelection().start().downstream(); // Find the top-most blockquote from the start. - Element* topBlockquote = 0; - for (ContainerNode* node = pos.deprecatedNode()->parentNode(); node; node = node->parentNode()) { - if (isMailBlockquote(node)) - topBlockquote = static_cast<Element*>(node); - } - if (!topBlockquote || !topBlockquote->parentNode()) + Node* topBlockquote = highestEnclosingNodeOfType(pos, isMailBlockquote); + if (!topBlockquote || !topBlockquote->parentNode() || !topBlockquote->isElementNode()) return; RefPtr<Element> breakNode = createBreakElement(document()); @@ -103,7 +99,7 @@ void BreakBlockquoteCommand::doApply() pos = pos.next(); // Adjust the position so we don't split at the beginning of a quote. - while (isFirstVisiblePositionInNode(VisiblePosition(pos), nearestMailBlockquote(pos.deprecatedNode()))) + while (isFirstVisiblePositionInNode(VisiblePosition(pos), enclosingNodeOfType(pos, isMailBlockquote))) pos = pos.previous(); // startNode is the first node that we need to move to the new blockquote. @@ -135,7 +131,7 @@ void BreakBlockquoteCommand::doApply() ancestors.append(node); // Insert a clone of the top blockquote after the break. - RefPtr<Element> clonedBlockquote = topBlockquote->cloneElementWithoutChildren(); + RefPtr<Element> clonedBlockquote = static_cast<Element*>(topBlockquote)->cloneElementWithoutChildren(); insertNodeAfter(clonedBlockquote.get(), breakNode.get()); // Clone startNode's ancestors into the cloned blockquote. @@ -152,7 +148,7 @@ void BreakBlockquoteCommand::doApply() // find the first one so that we know where to start numbering. while (listChildNode && !listChildNode->hasTagName(liTag)) listChildNode = listChildNode->nextSibling(); - if (listChildNode && listChildNode->renderer()) + if (listChildNode && listChildNode->renderer() && listChildNode->renderer()->isListItem()) setNodeAttribute(static_cast<Element*>(clonedChild.get()), startAttr, String::number(toRenderListItem(listChildNode->renderer())->value())); } diff --git a/Source/WebCore/editing/CompositeEditCommand.cpp b/Source/WebCore/editing/CompositeEditCommand.cpp index b7672ee..2a69fb7 100644 --- a/Source/WebCore/editing/CompositeEditCommand.cpp +++ b/Source/WebCore/editing/CompositeEditCommand.cpp @@ -144,7 +144,7 @@ void CompositeEditCommand::insertNodeAfter(PassRefPtr<Node> insertChild, PassRef ASSERT(insertChild); ASSERT(refChild); ASSERT(!refChild->hasTagName(bodyTag)); - Element* parent = refChild->parentElement(); + ContainerNode* parent = refChild->parentNode(); ASSERT(parent); if (parent->lastChild() == refChild) appendNode(insertChild, parent); @@ -184,7 +184,7 @@ void CompositeEditCommand::insertNodeAt(PassRefPtr<Node> insertChild, const Posi insertNodeAfter(insertChild, refChild); } -void CompositeEditCommand::appendNode(PassRefPtr<Node> node, PassRefPtr<Element> parent) +void CompositeEditCommand::appendNode(PassRefPtr<Node> node, PassRefPtr<ContainerNode> parent) { ASSERT(canHaveChildrenForEditing(parent.get())); applyCommandToComposite(AppendNodeCommand::create(parent, node)); @@ -830,10 +830,10 @@ void CompositeEditCommand::cloneParagraphUnderNewElement(Position& start, Positi // Deleting a paragraph will leave a placeholder. Remove it (and prune // empty or unrendered parents). -void CompositeEditCommand::cleanupAfterDeletion() +void CompositeEditCommand::cleanupAfterDeletion(VisiblePosition destination) { VisiblePosition caretAfterDelete = endingSelection().visibleStart(); - if (isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) { + if (caretAfterDelete != destination && isStartOfParagraph(caretAfterDelete) && isEndOfParagraph(caretAfterDelete)) { // Note: We want the rightmost candidate. Position position = caretAfterDelete.deepEquivalent().downstream(); Node* node = position.deprecatedNode(); @@ -947,8 +947,8 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap } } - VisiblePosition beforeParagraph = startOfParagraphToMove.previous(); - VisiblePosition afterParagraph(endOfParagraphToMove.next()); + VisiblePosition beforeParagraph = startOfParagraphToMove.previous(CannotCrossEditingBoundary); + VisiblePosition afterParagraph(endOfParagraphToMove.next(CannotCrossEditingBoundary)); // 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. @@ -985,8 +985,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap deleteSelection(false, false, false, false); ASSERT(destination.deepEquivalent().anchorNode()->inDocument()); - - cleanupAfterDeletion(); + cleanupAfterDeletion(destination); ASSERT(destination.deepEquivalent().anchorNode()->inDocument()); // Add a br if pruning an empty block level element caused a collapse. For example: @@ -1049,7 +1048,7 @@ bool CompositeEditCommand::breakOutOfEmptyListItem() // FIXME: Can't we do something better when the immediate parent wasn't a list node? if (!listNode || (!listNode->hasTagName(ulTag) && !listNode->hasTagName(olTag)) - || !listNode->isContentEditable() + || !listNode->rendererIsEditable() || listNode == emptyListItem->rootEditableElement()) return false; @@ -1114,7 +1113,7 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() if (!isStartOfParagraph(caret) || !isEndOfParagraph(caret)) return false; - VisiblePosition previous(caret.previous(true)); + VisiblePosition previous(caret.previous(CannotCrossEditingBoundary)); // 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; @@ -1173,8 +1172,8 @@ Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Posi // Don't avoid block level anchors, because that would insert content into the wrong paragraph. if (enclosingAnchor && !isBlock(enclosingAnchor)) { - VisiblePosition firstInAnchor(firstDeepEditingPositionForNode(enclosingAnchor)); - VisiblePosition lastInAnchor(lastDeepEditingPositionForNode(enclosingAnchor)); + VisiblePosition firstInAnchor(firstPositionInNode(enclosingAnchor)); + VisiblePosition lastInAnchor(lastPositionInNode(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) { diff --git a/Source/WebCore/editing/CompositeEditCommand.h b/Source/WebCore/editing/CompositeEditCommand.h index 9066b65..4b96d8f 100644 --- a/Source/WebCore/editing/CompositeEditCommand.h +++ b/Source/WebCore/editing/CompositeEditCommand.h @@ -32,7 +32,6 @@ namespace WebCore { -class CSSStyleDeclaration; class EditingStyle; class HTMLElement; class StyledElement; @@ -50,7 +49,7 @@ protected: // // sugary-sweet convenience functions to help create and apply edit commands in composite commands // - void appendNode(PassRefPtr<Node>, PassRefPtr<Element> parent); + void appendNode(PassRefPtr<Node>, PassRefPtr<ContainerNode> parent); void applyCommandToComposite(PassRefPtr<EditCommand>); void applyStyle(const EditingStyle*, EditAction = EditActionChangeAttributes); void applyStyle(const EditingStyle*, const Position& start, const Position& end, EditAction = EditActionChangeAttributes); @@ -110,7 +109,7 @@ protected: void moveParagraphs(const VisiblePosition&, const VisiblePosition&, const VisiblePosition&, bool preserveSelection = false, bool preserveStyle = true); void moveParagraphWithClones(const VisiblePosition& startOfParagraphToMove, const VisiblePosition& endOfParagraphToMove, Element* blockElement, Node* outerNode); void cloneParagraphUnderNewElement(Position& start, Position& end, Node* outerNode, Element* blockElement); - void cleanupAfterDeletion(); + void cleanupAfterDeletion(VisiblePosition destination = VisiblePosition()); bool breakOutOfEmptyListItem(); bool breakOutOfEmptyMailBlockquotedParagraph(); diff --git a/Source/WebCore/editing/DeleteButtonController.cpp b/Source/WebCore/editing/DeleteButtonController.cpp index 75b9a96..332e68f 100644 --- a/Source/WebCore/editing/DeleteButtonController.cpp +++ b/Source/WebCore/editing/DeleteButtonController.cpp @@ -63,7 +63,7 @@ DeleteButtonController::DeleteButtonController(Frame* frame) static bool isDeletableElement(const Node* node) { - if (!node || !node->isHTMLElement() || !node->inDocument() || !node->isContentEditable()) + if (!node || !node->isHTMLElement() || !node->inDocument() || !node->rendererIsEditable()) return false; // In general we want to only draw the UI around object of a certain area, but we still keep the min width/height to @@ -156,15 +156,11 @@ static HTMLElement* enclosingDeletableElement(const VisibleSelection& selection) // The enclosingNodeOfType function only works on nodes that are editable // (which is strange, given its name). - if (!container->isContentEditable()) + if (!container->rendererIsEditable()) return 0; Node* element = enclosingNodeOfType(firstPositionInNode(container), &isDeletableElement); - if (!element) - return 0; - - ASSERT(element->isHTMLElement()); - return toHTMLElement(element); + return element && element->isHTMLElement() ? toHTMLElement(element) : 0; } void DeleteButtonController::respondToChangedSelection(const VisibleSelection& oldSelection) diff --git a/Source/WebCore/editing/DeleteFromTextNodeCommand.cpp b/Source/WebCore/editing/DeleteFromTextNodeCommand.cpp index fe572e1..8ee28a1 100644 --- a/Source/WebCore/editing/DeleteFromTextNodeCommand.cpp +++ b/Source/WebCore/editing/DeleteFromTextNodeCommand.cpp @@ -46,7 +46,7 @@ void DeleteFromTextNodeCommand::doApply() { ASSERT(m_node); - if (!m_node->isContentEditable()) + if (!m_node->rendererIsEditable()) return; ExceptionCode ec = 0; @@ -65,7 +65,7 @@ void DeleteFromTextNodeCommand::doUnapply() { ASSERT(m_node); - if (!m_node->isContentEditable()) + if (!m_node->rendererIsEditable()) return; ExceptionCode ec; diff --git a/Source/WebCore/editing/DeleteSelectionCommand.cpp b/Source/WebCore/editing/DeleteSelectionCommand.cpp index 897c305..cbebe54 100644 --- a/Source/WebCore/editing/DeleteSelectionCommand.cpp +++ b/Source/WebCore/editing/DeleteSelectionCommand.cpp @@ -52,7 +52,7 @@ static bool isTableRow(const Node* node) static bool isTableCellEmpty(Node* cell) { ASSERT(isTableCell(cell)); - return VisiblePosition(firstDeepEditingPositionForNode(cell)) == VisiblePosition(lastDeepEditingPositionForNode(cell)); + return VisiblePosition(firstPositionInNode(cell)) == VisiblePosition(lastPositionInNode(cell)); } static bool isTableRowEmpty(Node* row) @@ -72,6 +72,7 @@ DeleteSelectionCommand::DeleteSelectionCommand(Document *document, bool smartDel m_hasSelectionToDelete(false), m_smartDelete(smartDelete), m_mergeBlocksAfterDelete(mergeBlocksAfterDelete), + m_needPlaceholder(false), m_replace(replace), m_expandForSpecialElements(expandForSpecialElements), m_pruneStartBlockIfNecessary(false), @@ -88,6 +89,7 @@ DeleteSelectionCommand::DeleteSelectionCommand(const VisibleSelection& selection m_hasSelectionToDelete(true), m_smartDelete(smartDelete), m_mergeBlocksAfterDelete(mergeBlocksAfterDelete), + m_needPlaceholder(false), m_replace(replace), m_expandForSpecialElements(expandForSpecialElements), m_pruneStartBlockIfNecessary(false), @@ -188,8 +190,8 @@ void DeleteSelectionCommand::initializePositionData() // Don't move content out of a table cell. // If the cell is non-editable, enclosingNodeOfType won't return it by default, so // tell that function that we don't care if it returns non-editable nodes. - Node* startCell = enclosingNodeOfType(m_upstreamStart, &isTableCell, false); - Node* endCell = enclosingNodeOfType(m_downstreamEnd, &isTableCell, false); + Node* startCell = enclosingNodeOfType(m_upstreamStart, &isTableCell, CanCrossEditingBoundary); + Node* endCell = enclosingNodeOfType(m_downstreamEnd, &isTableCell, CanCrossEditingBoundary); // FIXME: This isn't right. A borderless table with two rows and a single column would appear as two paragraphs. if (endCell && endCell != startCell) m_mergeBlocksAfterDelete = false; @@ -262,8 +264,8 @@ void DeleteSelectionCommand::initializePositionData() // like the one below, since editing functions should obviously accept editing positions. // FIXME: Passing false to enclosingNodeOfType tells it that it's OK to return a non-editable // node. This was done to match existing behavior, but it seems wrong. - m_startBlock = enclosingNodeOfType(m_downstreamStart.parentAnchoredEquivalent(), &isBlock, false); - m_endBlock = enclosingNodeOfType(m_upstreamEnd.parentAnchoredEquivalent(), &isBlock, false); + m_startBlock = enclosingNodeOfType(m_downstreamStart.parentAnchoredEquivalent(), &isBlock, CanCrossEditingBoundary); + m_endBlock = enclosingNodeOfType(m_upstreamEnd.parentAnchoredEquivalent(), &isBlock, CanCrossEditingBoundary); } void DeleteSelectionCommand::saveTypingStyleState() @@ -284,7 +286,7 @@ void DeleteSelectionCommand::saveTypingStyleState() // If we're deleting into a Mail blockquote, save the style at end() instead of start() // We'll use this later in computeTypingStyleAfterDelete if we end up outside of a Mail blockquote - if (nearestMailBlockquote(m_selectionToDelete.start().deprecatedNode())) + if (enclosingNodeOfType(m_selectionToDelete.start(), isMailBlockquote)) m_deleteIntoBlockquoteStyle = EditingStyle::create(m_selectionToDelete.end()); else m_deleteIntoBlockquoteStyle = 0; @@ -340,7 +342,7 @@ void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node) if (m_startRoot != m_endRoot && !(node->isDescendantOf(m_startRoot.get()) && node->isDescendantOf(m_endRoot.get()))) { // If a node is not in both the start and end editable roots, remove it only if its inside an editable region. - if (!node->parentNode()->isContentEditable()) { + if (!node->parentNode()->rendererIsEditable()) { // Don't remove non-editable atomic nodes. if (!node->firstChild()) return; @@ -378,9 +380,9 @@ void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node) return; } - if (node == m_startBlock && !isEndOfBlock(VisiblePosition(firstDeepEditingPositionForNode(m_startBlock.get())).previous())) + if (node == m_startBlock && !isEndOfBlock(VisiblePosition(firstPositionInNode(m_startBlock.get())).previous())) m_needPlaceholder = true; - else if (node == m_endBlock && !isStartOfBlock(VisiblePosition(lastDeepEditingPositionForNode(m_startBlock.get())).next())) + else if (node == m_endBlock && !isStartOfBlock(VisiblePosition(lastPositionInNode(m_startBlock.get())).next())) m_needPlaceholder = true; // FIXME: Update the endpoints of the range being deleted. @@ -592,7 +594,7 @@ void DeleteSelectionCommand::mergeParagraphs() } // We need to merge into m_upstreamStart's block, but it's been emptied out and collapsed by deletion. - if (!mergeDestination.deepEquivalent().deprecatedNode() || !mergeDestination.deepEquivalent().deprecatedNode()->isDescendantOf(m_upstreamStart.deprecatedNode()->enclosingBlockFlowElement()) || m_startsAtEmptyLine) { + if (!mergeDestination.deepEquivalent().deprecatedNode() || !mergeDestination.deepEquivalent().deprecatedNode()->isDescendantOf(enclosingBlock(m_upstreamStart.containerNode())) || m_startsAtEmptyLine) { insertNodeAt(createBreakElement(document()).get(), m_upstreamStart); mergeDestination = VisiblePosition(m_upstreamStart); } @@ -687,7 +689,7 @@ void DeleteSelectionCommand::calculateTypingStyleAfterDelete() // has completed. // If we deleted into a blockquote, but are now no longer in a blockquote, use the alternate typing style - if (m_deleteIntoBlockquoteStyle && !nearestMailBlockquote(m_endingPosition.deprecatedNode())) + if (m_deleteIntoBlockquoteStyle && !enclosingNodeOfType(m_endingPosition, isMailBlockquote, CanCrossEditingBoundary)) m_typingStyle = m_deleteIntoBlockquoteStyle; m_deleteIntoBlockquoteStyle = 0; diff --git a/Source/WebCore/editing/EditingStyle.cpp b/Source/WebCore/editing/EditingStyle.cpp index de71fb7..668c943 100644 --- a/Source/WebCore/editing/EditingStyle.cpp +++ b/Source/WebCore/editing/EditingStyle.cpp @@ -377,15 +377,13 @@ bool EditingStyle::textDirection(WritingDirection& writingDirection) const return false; RefPtr<CSSValue> unicodeBidi = m_mutableStyle->getPropertyCSSValue(CSSPropertyUnicodeBidi); - if (!unicodeBidi) + if (!unicodeBidi || !unicodeBidi->isPrimitiveValue()) return false; - ASSERT(unicodeBidi->isPrimitiveValue()); int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent(); if (unicodeBidiValue == CSSValueEmbed) { RefPtr<CSSValue> direction = m_mutableStyle->getPropertyCSSValue(CSSPropertyDirection); - ASSERT(!direction || direction->isPrimitiveValue()); - if (!direction) + if (!direction || !direction->isPrimitiveValue()) return false; writingDirection = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent() == CSSValueLtr ? LeftToRightWritingDirection : RightToLeftWritingDirection; @@ -675,6 +673,11 @@ bool EditingStyle::extractConflictingImplicitStyleOfAttributes(HTMLElement* elem return removed; } +bool EditingStyle::styleIsPresentInComputedStyleOfNode(Node* node) const +{ + return !m_mutableStyle || !getPropertiesNotIn(m_mutableStyle.get(), computedStyle(node).get())->length(); +} + void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWritingDirection shouldPreserveWritingDirection) { if (!m_mutableStyle) @@ -702,13 +705,10 @@ void EditingStyle::prepareToApplyAt(const Position& position, ShouldPreserveWrit m_mutableStyle->removeProperty(CSSPropertyBackgroundColor, ec); } - if (unicodeBidi) { - ASSERT(unicodeBidi->isPrimitiveValue()); + if (unicodeBidi && unicodeBidi->isPrimitiveValue()) { m_mutableStyle->setProperty(CSSPropertyUnicodeBidi, static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent()); - if (direction) { - ASSERT(direction->isPrimitiveValue()); + if (direction && direction->isPrimitiveValue()) m_mutableStyle->setProperty(CSSPropertyDirection, static_cast<CSSPrimitiveValue*>(direction.get())->getIdent()); - } } } diff --git a/Source/WebCore/editing/EditingStyle.h b/Source/WebCore/editing/EditingStyle.h index aa310ac..37bfea7 100644 --- a/Source/WebCore/editing/EditingStyle.h +++ b/Source/WebCore/editing/EditingStyle.h @@ -113,6 +113,7 @@ public: bool conflictsWithImplicitStyleOfAttributes(HTMLElement*) const; bool extractConflictingImplicitStyleOfAttributes(HTMLElement*, ShouldPreserveWritingDirection, EditingStyle* extractedStyle, Vector<QualifiedName>& conflictingAttributes, ShouldExtractMatchingStyle) const; + bool styleIsPresentInComputedStyleOfNode(Node*) const; void prepareToApplyAt(const Position&, ShouldPreserveWritingDirection = DoNotPreserveWritingDirection); void mergeTypingStyle(Document*); void mergeInlineStyleOfElement(StyledElement*); diff --git a/Source/WebCore/editing/Editor.cpp b/Source/WebCore/editing/Editor.cpp index 8807965..a793e0b 100644 --- a/Source/WebCore/editing/Editor.cpp +++ b/Source/WebCore/editing/Editor.cpp @@ -289,7 +289,7 @@ bool Editor::canDeleteRange(Range* range) const if (!startContainer || !endContainer) return false; - if (!startContainer->isContentEditable() || !endContainer->isContentEditable()) + if (!startContainer->rendererIsEditable() || !endContainer->rendererIsEditable()) return false; if (range->collapsed(ec)) { @@ -653,10 +653,9 @@ WritingDirection Editor::textDirectionForSelection(bool& hasNestedOrMultipleEmbe RefPtr<CSSComputedStyleDeclaration> style = computedStyle(n); RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi); - if (!unicodeBidi) + if (!unicodeBidi || !unicodeBidi->isPrimitiveValue()) continue; - ASSERT(unicodeBidi->isPrimitiveValue()); int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent(); if (unicodeBidiValue == CSSValueEmbed || unicodeBidiValue == CSSValueBidiOverride) return NaturalWritingDirection; @@ -684,10 +683,9 @@ WritingDirection Editor::textDirectionForSelection(bool& hasNestedOrMultipleEmbe RefPtr<CSSComputedStyleDeclaration> style = computedStyle(node); RefPtr<CSSValue> unicodeBidi = style->getPropertyCSSValue(CSSPropertyUnicodeBidi); - if (!unicodeBidi) + if (!unicodeBidi || !unicodeBidi->isPrimitiveValue()) continue; - ASSERT(unicodeBidi->isPrimitiveValue()); int unicodeBidiValue = static_cast<CSSPrimitiveValue*>(unicodeBidi.get())->getIdent(); if (unicodeBidiValue == CSSValueNormal) continue; @@ -697,10 +695,9 @@ WritingDirection Editor::textDirectionForSelection(bool& hasNestedOrMultipleEmbe ASSERT(unicodeBidiValue == CSSValueEmbed); RefPtr<CSSValue> direction = style->getPropertyCSSValue(CSSPropertyDirection); - if (!direction) + if (!direction || !direction->isPrimitiveValue()) continue; - ASSERT(direction->isPrimitiveValue()); int directionValue = static_cast<CSSPrimitiveValue*>(direction.get())->getIdent(); if (directionValue != CSSValueLtr && directionValue != CSSValueRtl) continue; @@ -1371,7 +1368,7 @@ int Editor::spellCheckerDocumentTag() return client() ? client()->spellCheckerDocumentTag() : 0; } -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) +#if USE(AUTOMATIC_TEXT_REPLACEMENT) void Editor::uppercaseWord() { @@ -1823,14 +1820,16 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) int searchEndOffsetAfterWrap = spellingSearchRange->endOffset(ec); int misspellingOffset = 0; -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) - RefPtr<Range> grammarSearchRange = spellingSearchRange->cloneRange(ec); - String misspelledWord; - String badGrammarPhrase; + GrammarDetail grammarDetail; int grammarPhraseOffset = 0; + RefPtr<Range> grammarSearchRange; + String badGrammarPhrase; + String misspelledWord; + +#if USE(UNIFIED_TEXT_CHECKING) + grammarSearchRange = spellingSearchRange->cloneRange(ec); bool isSpelling = true; int foundOffset = 0; - GrammarDetail grammarDetail; String foundItem = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); if (isSpelling) { misspelledWord = foundItem; @@ -1841,15 +1840,11 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) } #else RefPtr<Range> firstMisspellingRange; - String misspelledWord = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); - String badGrammarPhrase; - -#ifndef BUILDING_ON_TIGER - int grammarPhraseOffset = 0; - GrammarDetail grammarDetail; + misspelledWord = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); +#if USE(GRAMMAR_CHECKING) // Search for bad grammar that occurs prior to the next misspelled word (if any) - RefPtr<Range> grammarSearchRange = spellingSearchRange->cloneRange(ec); + grammarSearchRange = spellingSearchRange->cloneRange(ec); if (!misspelledWord.isEmpty()) { // Stop looking at start of next misspelled word CharacterIterator chars(grammarSearchRange.get()); @@ -1869,7 +1864,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) // going until the end of the very first chunk we tested is far enough spellingSearchRange->setEnd(searchEndNodeAfterWrap, searchEndOffsetAfterWrap, ec); -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) +#if USE(UNIFIED_TEXT_CHECKING) grammarSearchRange = spellingSearchRange->cloneRange(ec); foundItem = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspellingOrBadGrammar(isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); if (isSpelling) { @@ -1882,7 +1877,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) #else misspelledWord = TextCheckingHelper(client(), spellingSearchRange).findFirstMisspelling(misspellingOffset, false, firstMisspellingRange); -#ifndef BUILDING_ON_TIGER +#if USE(GRAMMAR_CHECKING) grammarSearchRange = spellingSearchRange->cloneRange(ec); if (!misspelledWord.isEmpty()) { // Stop looking at start of next misspelled word @@ -1898,9 +1893,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) } if (!badGrammarPhrase.isEmpty()) { -#ifdef BUILDING_ON_TIGER - ASSERT_NOT_REACHED(); -#else + ASSERT(WTF_USE_GRAMMAR_CHECKING); // We found bad grammar. Since we only searched for bad grammar up to the first misspelled word, the bad grammar // takes precedence and we ignore any potential misspelled word. Select the grammar detail, update the spelling // panel, and store a marker so we draw the green squiggle later. @@ -1915,7 +1908,6 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) client()->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail); frame()->document()->markers()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, grammarDetail.userDescription); -#endif } else if (!misspelledWord.isEmpty()) { // We found a misspelling, but not any earlier bad grammar. Select the misspelling, update the spelling panel, and store // a marker so we draw the red squiggle later. @@ -1958,23 +1950,23 @@ bool Editor::isSelectionMisspelled() bool Editor::isSelectionUngrammatical() { -#ifdef BUILDING_ON_TIGER - return false; -#else +#if USE(GRAMMAR_CHECKING) Vector<String> ignoredGuesses; return TextCheckingHelper(client(), frame()->selection()->toNormalizedRange()).isUngrammatical(ignoredGuesses); +#else + return false; #endif } Vector<String> Editor::guessesForUngrammaticalSelection() { -#ifdef BUILDING_ON_TIGER - return Vector<String>(); -#else +#if USE(GRAMMAR_CHECKING) Vector<String> guesses; // Ignore the result of isUngrammatical; we just want the guesses, whether or not there are any TextCheckingHelper(client(), frame()->selection()->toNormalizedRange()).isUngrammatical(guesses); return guesses; +#else + return Vector<String>(); #endif } @@ -1991,7 +1983,7 @@ Vector<String> Editor::guessesForMisspelledSelection() Vector<String> Editor::guessesForMisspelledOrUngrammaticalSelection(bool& misspelled, bool& ungrammatical) { -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) +#if USE(UNIFIED_TEXT_CHECKING) return TextCheckingHelper(client(), frame()->selection()->toNormalizedRange()).guessesForMisspelledOrUngrammaticalRange(isGrammarCheckingEnabled(), misspelled, ungrammatical); #else misspelled = isSelectionMisspelled(); @@ -2060,7 +2052,7 @@ void Editor::markMisspellingsAndBadGrammar(const VisibleSelection &movingSelecti void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, const VisibleSelection& selectionAfterTyping) { -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) +#if USE(UNIFIED_TEXT_CHECKING) #if SUPPORT_AUTOCORRECTION_PANEL // Apply pending autocorrection before next round of spell checking. bool doApplyCorrection = true; @@ -2072,7 +2064,7 @@ void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, doApplyCorrection = false; } if (doApplyCorrection) - dismissCorrectionPanel(ReasonForDismissingCorrectionPanelAccepted); + handleCorrectionPanelResult(dismissCorrectionPanelSoon(ReasonForDismissingCorrectionPanelAccepted)); else m_correctionPanelInfo.rangeToBeReplaced.clear(); #else @@ -2082,12 +2074,14 @@ void Editor::markMisspellingsAfterTypingToWord(const VisiblePosition &wordStart, if (isContinuousSpellCheckingEnabled()) textCheckingOptions |= MarkSpelling; +#if USE(AUTOMATIC_TEXT_REPLACEMENT) if (isAutomaticQuoteSubstitutionEnabled() || isAutomaticLinkDetectionEnabled() || isAutomaticDashSubstitutionEnabled() || isAutomaticTextReplacementEnabled() || ((textCheckingOptions & MarkSpelling) && isAutomaticSpellingCorrectionEnabled())) textCheckingOptions |= PerformReplacement; +#endif if (!textCheckingOptions & (MarkSpelling | PerformReplacement)) return; @@ -2162,7 +2156,7 @@ void Editor::markMisspellingsOrBadGrammar(const VisibleSelection& selection, boo // If we're not in an editable node, bail. Node* editableNode = searchRange->startContainer(); - if (!editableNode || !editableNode->isContentEditable()) + if (!editableNode || !editableNode->rendererIsEditable()) return; if (!isSpellCheckingEnabledFor(editableNode)) @@ -2176,12 +2170,9 @@ void Editor::markMisspellingsOrBadGrammar(const VisibleSelection& selection, boo if (checkSpelling) checker.markAllMisspellings(firstMisspellingRange); else { -#ifdef BUILDING_ON_TIGER - ASSERT_NOT_REACHED(); -#else + ASSERT(WTF_USE_GRAMMAR_CHECKING); if (isGrammarCheckingEnabled()) checker.markAllBadGrammar(); -#endif } } @@ -2207,17 +2198,14 @@ void Editor::markMisspellings(const VisibleSelection& selection, RefPtr<Range>& void Editor::markBadGrammar(const VisibleSelection& selection) { -#ifndef BUILDING_ON_TIGER + ASSERT(WTF_USE_GRAMMAR_CHECKING); RefPtr<Range> firstMisspellingRange; markMisspellingsOrBadGrammar(selection, false, firstMisspellingRange); -#else - UNUSED_PARAM(selection); -#endif } -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCheckingOptions, Range* spellingRange, Range* grammarRange) { +#if USE(UNIFIED_TEXT_CHECKING) // There shouldn't be pending autocorrection at this moment. ASSERT(!m_correctionPanelInfo.rangeToBeReplaced); @@ -2233,7 +2221,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh // If we're not in an editable node, bail. Node* editableNode = spellingRange->startContainer(); - if (!editableNode || !editableNode->isContentEditable()) + if (!editableNode || !editableNode->rendererIsEditable()) return; if (!isSpellCheckingEnabledFor(editableNode)) @@ -2248,7 +2236,6 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh TextCheckingParagraph spellingParagraph(spellingRange); TextCheckingParagraph grammarParagraph(shouldMarkGrammar ? grammarRange : 0); - TextCheckingParagraph& paragraph = shouldMarkGrammar ? grammarParagraph : spellingParagraph; if (shouldMarkGrammar ? (spellingParagraph.isRangeEmpty() && grammarParagraph.isEmpty()) : spellingParagraph.isEmpty()) return; @@ -2257,13 +2244,13 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh if (m_frame->selection()->selectionType() == VisibleSelection::CaretSelection) { // Attempt to save the caret position so we can restore it later if needed Position caretPosition = m_frame->selection()->end(); - int offset = paragraph.offsetTo(caretPosition, ec); + int offset = spellingParagraph.offsetTo(caretPosition, ec); if (!ec) { selectionOffset = offset; restoreSelectionAfterChange = true; - if (selectionOffset > 0 && (selectionOffset > paragraph.textLength() || paragraph.textCharAt(selectionOffset - 1) == newlineCharacter)) + if (selectionOffset > 0 && (selectionOffset > spellingParagraph.textLength() || spellingParagraph.textCharAt(selectionOffset - 1) == newlineCharacter)) adjustSelectionForParagraphBoundaries = true; - if (selectionOffset > 0 && selectionOffset <= paragraph.textLength() && isAmbiguousBoundaryCharacter(paragraph.textCharAt(selectionOffset - 1))) + if (selectionOffset > 0 && selectionOffset <= spellingParagraph.textLength() && isAmbiguousBoundaryCharacter(spellingParagraph.textCharAt(selectionOffset - 1))) ambiguousBoundaryOffset = selectionOffset - 1; } } @@ -2278,6 +2265,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh if (shouldShowCorrectionPanel) checkingTypes |= TextCheckingTypeCorrection; if (shouldPerformReplacement) { +#if USE(AUTOMATIC_TEXT_REPLACEMENT) if (isAutomaticLinkDetectionEnabled()) checkingTypes |= TextCheckingTypeLink; if (isAutomaticQuoteSubstitutionEnabled()) @@ -2288,8 +2276,13 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh checkingTypes |= TextCheckingTypeReplacement; if (shouldMarkSpelling && isAutomaticSpellingCorrectionEnabled()) checkingTypes |= TextCheckingTypeCorrection; +#endif } - textChecker()->checkTextOfParagraph(paragraph.textCharacters(), paragraph.textLength(), checkingTypes, results); + if (shouldMarkGrammar) + textChecker()->checkTextOfParagraph(grammarParagraph.textCharacters(), grammarParagraph.textLength(), checkingTypes, results); + else + textChecker()->checkTextOfParagraph(spellingParagraph.textCharacters(), spellingParagraph.textLength(), checkingTypes, results); + #if SUPPORT_AUTOCORRECTION_PANEL // If this checking is only for showing correction panel, we shouldn't bother to mark misspellings. @@ -2348,7 +2341,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh // 2. The result doesn't end at an ambiguous boundary. // (FIXME: this is required until 6853027 is fixed and text checking can do this for us bool doReplacement = replacementLength > 0 && !resultEndsAtAmbiguousBoundary; - RefPtr<Range> rangeToReplace = paragraph.subrange(resultLocation, resultLength); + RefPtr<Range> rangeToReplace = spellingParagraph.subrange(resultLocation, resultLength); VisibleSelection selectionToReplace(rangeToReplace.get(), DOWNSTREAM); // adding links should be done only immediately after they are typed @@ -2387,7 +2380,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh m_correctionPanelInfo.replacedString = plainText(rangeToReplace.get()); m_correctionPanelInfo.replacementString = result->replacement; m_correctionPanelInfo.isActive = true; - client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, result->replacement, Vector<String>(), this); + client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, result->replacement, Vector<String>()); break; } // If this function is called for showing correction panel, we ignore other correction or replacement. @@ -2433,7 +2426,7 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh if (result->type == TextCheckingTypeCorrection) { // Add a marker so that corrections can easily be undone and won't be re-corrected. - RefPtr<Range> replacedRange = paragraph.subrange(resultLocation, replacementLength); + RefPtr<Range> replacedRange = spellingParagraph.subrange(resultLocation, replacementLength); replacedRange->startContainer()->document()->markers()->addMarker(replacedRange.get(), DocumentMarker::Replacement, replacedString); replacedRange->startContainer()->document()->markers()->addMarker(replacedRange.get(), DocumentMarker::CorrectionIndicator); replacedRange->startContainer()->document()->markers()->addMarker(replacedRange.get(), DocumentMarker::SpellCheckingExemption); @@ -2444,9 +2437,9 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh if (selectionChanged) { // Restore the caret position if we have made any replacements - paragraph.expandRangeToNextEnd(); - if (restoreSelectionAfterChange && selectionOffset >= 0 && selectionOffset <= paragraph.rangeLength()) { - RefPtr<Range> selectionRange = paragraph.subrange(0, selectionOffset); + spellingParagraph.expandRangeToNextEnd(); + if (restoreSelectionAfterChange && selectionOffset >= 0 && selectionOffset <= spellingParagraph.rangeLength()) { + RefPtr<Range> selectionRange = spellingParagraph.subrange(0, selectionOffset); m_frame->selection()->moveTo(selectionRange->endPosition(), DOWNSTREAM); if (adjustSelectionForParagraphBoundaries) m_frame->selection()->modify(SelectionController::AlterationMove, DirectionForward, CharacterGranularity); @@ -2456,10 +2449,17 @@ void Editor::markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions textCh m_frame->selection()->modify(SelectionController::AlterationMove, DirectionForward, CharacterGranularity); } } +#else + ASSERT_NOT_REACHED(); + UNUSED_PARAM(textCheckingOptions); + UNUSED_PARAM(spellingRange); + UNUSED_PARAM(grammarRange); +#endif // USE(UNIFIED_TEXT_CHECKING) } void Editor::changeBackToReplacedString(const String& replacedString) { +#if USE(UNIFIED_TEXT_CHECKING) if (replacedString.isEmpty()) return; @@ -2478,13 +2478,16 @@ void Editor::changeBackToReplacedString(const String& replacedString) #if SUPPORT_AUTOCORRECTION_PANEL changedRange->startContainer()->document()->markers()->addMarker(changedRange.get(), DocumentMarker::SpellCheckingExemption); #endif +#else + ASSERT_NOT_REACHED(); + UNUSED_PARAM(replacedString); +#endif // USE(UNIFIED_TEXT_CHECKING) } -#endif void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection) { -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) +#if USE(UNIFIED_TEXT_CHECKING) if (!isContinuousSpellCheckingEnabled()) return; TextCheckingOptions textCheckingOptions = MarkSpelling; @@ -2517,7 +2520,7 @@ void Editor::correctionPanelTimerFired(Timer<Editor>*) m_correctionPanelInfo.replacedString = plainText(m_correctionPanelInfo.rangeToBeReplaced.get()); FloatRect boundingBox = windowRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get()); if (!boundingBox.isEmpty()) - client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, m_correctionPanelInfo.replacementString, Vector<String>(), this); + client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, m_correctionPanelInfo.replacementString, Vector<String>()); } break; case CorrectionPanelInfo::PanelTypeSpellingSuggestions: { @@ -2535,7 +2538,7 @@ void Editor::correctionPanelTimerFired(Timer<Editor>*) m_correctionPanelInfo.isActive = true; FloatRect boundingBox = windowRectForRange(m_correctionPanelInfo.rangeToBeReplaced.get()); if (!boundingBox.isEmpty()) - client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, topSuggestion, suggestions, this); + client()->showCorrectionPanel(m_correctionPanelInfo.panelType, boundingBox, m_correctionPanelInfo.replacedString, topSuggestion, suggestions); } break; } @@ -2601,39 +2604,36 @@ void Editor::stopCorrectionPanelTimer() #endif } -void Editor::handleCancelOperation() +void Editor::dismissCorrectionPanel(ReasonForDismissingCorrectionPanel reasonForDismissing) { #if SUPPORT_AUTOCORRECTION_PANEL if (!m_correctionPanelInfo.isActive) return; m_correctionPanelInfo.isActive = false; + m_correctionPanelIsDismissedByEditor = true; if (client()) - client()->dismissCorrectionPanel(ReasonForDismissingCorrectionPanelCancelled); -#endif -} - -bool Editor::isShowingCorrectionPanel() -{ -#if SUPPORT_AUTOCORRECTION_PANEL - if (client()) - return client()->isShowingCorrectionPanel(); + client()->dismissCorrectionPanel(reasonForDismissing); +#else + UNUSED_PARAM(reasonForDismissing); #endif - return false; } -void Editor::dismissCorrectionPanel(ReasonForDismissingCorrectionPanel reasonForDismissing) +String Editor::dismissCorrectionPanelSoon(ReasonForDismissingCorrectionPanel reasonForDismissing) { #if SUPPORT_AUTOCORRECTION_PANEL if (!m_correctionPanelInfo.isActive) - return; + return String(); m_correctionPanelInfo.isActive = false; m_correctionPanelIsDismissedByEditor = true; - if (client()) - client()->dismissCorrectionPanel(reasonForDismissing); + if (!client()) + return String(); + return client()->dismissCorrectionPanelSoon(reasonForDismissing); #else UNUSED_PARAM(reasonForDismissing); + return String(); #endif } + void Editor::removeSpellAndCorrectionMarkersFromWordsToBeEdited(bool doNotRemoveIfSelectionAtWordBoundary) { // We want to remove the markers from a word if an editing command will change the word. This can happen in one of @@ -2768,7 +2768,7 @@ bool Editor::applyAutocorrectionBeforeTypingIfAppropriate() Position caretPosition = m_frame->selection()->selection().start(); if (m_correctionPanelInfo.rangeToBeReplaced->endPosition() == caretPosition) { - dismissCorrectionPanel(ReasonForDismissingCorrectionPanelAccepted); + handleCorrectionPanelResult(dismissCorrectionPanelSoon(ReasonForDismissingCorrectionPanelAccepted)); return true; } @@ -3572,7 +3572,7 @@ static Node* findFirstMarkable(Node* node) return 0; } -bool Editor::selectionStartHasSpellingMarkerFor(int from, int length) const +bool Editor::selectionStartHasMarkerFor(DocumentMarker::MarkerType markerType, int from, int length) const { Node* node = findFirstMarkable(m_frame->selection()->start().deprecatedNode()); if (!node) @@ -3583,7 +3583,7 @@ bool Editor::selectionStartHasSpellingMarkerFor(int from, int length) const Vector<DocumentMarker> markers = m_frame->document()->markers()->markersForNode(node); for (size_t i = 0; i < markers.size(); ++i) { DocumentMarker marker = markers[i]; - if (marker.startOffset <= startOffset && endOffset <= marker.endOffset && marker.type == DocumentMarker::Spelling) + if (marker.startOffset <= startOffset && endOffset <= marker.endOffset && marker.type == markerType) return true; } diff --git a/Source/WebCore/editing/Editor.h b/Source/WebCore/editing/Editor.h index e1a7119..c723ddf 100644 --- a/Source/WebCore/editing/Editor.h +++ b/Source/WebCore/editing/Editor.h @@ -36,6 +36,7 @@ #include "EditorInsertAction.h" #include "FindOptions.h" #include "SelectionController.h" +#include "TextChecking.h" #include "Timer.h" #include "VisibleSelection.h" #include "WritingDirection.h" @@ -222,7 +223,7 @@ public: void markMisspellings(const VisibleSelection&, RefPtr<Range>& firstMisspellingRange); void markBadGrammar(const VisibleSelection&); void markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection); -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) +#if USE(AUTOMATIC_TEXT_REPLACEMENT) void uppercaseWord(); void lowercaseWord(); void capitalizeWord(); @@ -239,6 +240,8 @@ public: void toggleAutomaticTextReplacement(); bool isAutomaticSpellingCorrectionEnabled(); void toggleAutomaticSpellingCorrection(); +#endif + enum TextCheckingOptionFlags { MarkSpelling = 1 << 0, MarkGrammar = 1 << 1, @@ -249,7 +252,7 @@ public: void markAllMisspellingsAndBadGrammarInRanges(TextCheckingOptions, Range* spellingRange, Range* grammarRange); void changeBackToReplacedString(const String& replacedString); -#endif + void advanceToNextMisspelling(bool startBeforeSelection = false); void showSpellingGuessPanel(); bool spellingPanelIsShowing(); @@ -320,11 +323,9 @@ public: void addToKillRing(Range*, bool prepend); - void handleCancelOperation(); void startCorrectionPanelTimer(CorrectionPanelInfo::PanelType); // If user confirmed a correction in the correction panel, correction has non-zero length, otherwise it means that user has dismissed the panel. void handleCorrectionPanelResult(const String& correction); - bool isShowingCorrectionPanel(); void pasteAsFragment(PassRefPtr<DocumentFragment>, bool smartReplace, bool matchStyle); void pasteAsPlainText(const String&, bool smartReplace); @@ -376,9 +377,10 @@ public: bool canCopyExcludingStandaloneImages(); void takeFindStringFromSelection(); void writeSelectionToPasteboard(const String& pasteboardName, const Vector<String>& pasteboardTypes); + void readSelectionFromPasteboard(const String& pasteboardName); #endif - bool selectionStartHasSpellingMarkerFor(int from, int length) const; + bool selectionStartHasMarkerFor(DocumentMarker::MarkerType, int from, int length) const; void removeSpellAndCorrectionMarkersFromWordsToBeEdited(bool doNotRemoveIfSelectionAtWordBoundary); private: @@ -425,6 +427,7 @@ private: Node* findEventTargetFromSelection() const; void stopCorrectionPanelTimer(); void dismissCorrectionPanel(ReasonForDismissingCorrectionPanel); + String dismissCorrectionPanelSoon(ReasonForDismissingCorrectionPanel); void applyCorrectionPanelInfo(const Vector<DocumentMarker::MarkerType>& markerTypesToAdd); // Return true if correction was applied, false otherwise. bool applyAutocorrectionBeforeTypingIfAppropriate(); diff --git a/Source/WebCore/editing/EditorCommand.cpp b/Source/WebCore/editing/EditorCommand.cpp index 6ea9954..8ea37bb 100644 --- a/Source/WebCore/editing/EditorCommand.cpp +++ b/Source/WebCore/editing/EditorCommand.cpp @@ -56,6 +56,7 @@ #include "Sound.h" #include "TypingCommand.h" #include "UnlinkCommand.h" +#include "UserTypingGestureIndicator.h" #include "htmlediting.h" #include "markup.h" #include <wtf/text/AtomicString.h> @@ -254,7 +255,7 @@ static int verticalScrollDistance(Frame* frame) RenderStyle* style = renderer->style(); if (!style) return 0; - if (!(style->overflowY() == OSCROLL || style->overflowY() == OAUTO || focusedNode->isContentEditable())) + if (!(style->overflowY() == OSCROLL || style->overflowY() == OAUTO || focusedNode->rendererIsEditable())) return 0; int height = std::min<int>(toRenderBox(renderer)->clientHeight(), frame->view()->visibleHeight()); @@ -294,19 +295,25 @@ static bool executeCreateLink(Frame* frame, Event*, EditorCommandSource, const S return true; } -static bool executeCut(Frame* frame, Event*, EditorCommandSource, const String&) +static bool executeCut(Frame* frame, Event*, EditorCommandSource source, const String&) { - frame->editor()->cut(); + if (source == CommandFromMenuOrKeyBinding) { + UserTypingGestureIndicator typingGestureIndicator(frame); + frame->editor()->cut(); + } else + frame->editor()->cut(); return true; } static bool executeDelete(Frame* frame, Event*, EditorCommandSource source, const String&) { switch (source) { - case CommandFromMenuOrKeyBinding: + case CommandFromMenuOrKeyBinding: { // Doesn't modify the text if the current selection isn't a range. + UserTypingGestureIndicator typingGestureIndicator(frame); frame->editor()->performDelete(); return true; + } case CommandFromDOM: case CommandFromDOMWithUserInterface: // If the current selection is a caret, delete the preceding character. IE performs forwardDelete, but we currently side with Firefox. @@ -883,21 +890,33 @@ static bool executeOutdent(Frame* frame, Event*, EditorCommandSource, const Stri return true; } -static bool executePaste(Frame* frame, Event*, EditorCommandSource, const String&) +static bool executePaste(Frame* frame, Event*, EditorCommandSource source, const String&) { - frame->editor()->paste(); + if (source == CommandFromMenuOrKeyBinding) { + UserTypingGestureIndicator typingGestureIndicator(frame); + frame->editor()->paste(); + } else + frame->editor()->paste(); return true; } -static bool executePasteAndMatchStyle(Frame* frame, Event*, EditorCommandSource, const String&) +static bool executePasteAndMatchStyle(Frame* frame, Event*, EditorCommandSource source, const String&) { - frame->editor()->pasteAsPlainText(); + if (source == CommandFromMenuOrKeyBinding) { + UserTypingGestureIndicator typingGestureIndicator(frame); + frame->editor()->pasteAsPlainText(); + } else + frame->editor()->pasteAsPlainText(); return true; } -static bool executePasteAsPlainText(Frame* frame, Event*, EditorCommandSource, const String&) +static bool executePasteAsPlainText(Frame* frame, Event*, EditorCommandSource source, const String&) { - frame->editor()->pasteAsPlainText(); + if (source == CommandFromMenuOrKeyBinding) { + UserTypingGestureIndicator typingGestureIndicator(frame); + frame->editor()->pasteAsPlainText(); + } else + frame->editor()->pasteAsPlainText(); return true; } @@ -1091,14 +1110,6 @@ static bool executeYankAndSelect(Frame* frame, Event*, EditorCommandSource, cons return true; } -#if SUPPORT_AUTOCORRECTION_PANEL -static bool executeCancelOperation(Frame* frame, Event*, EditorCommandSource, const String&) -{ - frame->editor()->handleCancelOperation(); - return true; -} -#endif - // Supported functions static bool supported(Frame*) @@ -1249,13 +1260,6 @@ static bool enabledUndo(Frame* frame, Event*, EditorCommandSource) return frame->editor()->canUndo(); } -#if SUPPORT_AUTOCORRECTION_PANEL -static bool enabledDismissCorrectionPanel(Frame* frame, Event*, EditorCommandSource) -{ - return frame->editor()->isShowingCorrectionPanel(); -} -#endif - // State functions static TriState stateNone(Frame*, Event*) @@ -1532,10 +1536,6 @@ static const CommandMap& createCommandMap() #if PLATFORM(MAC) { "TakeFindStringFromSelection", { executeTakeFindStringFromSelection, supportedFromMenuOrKeyBinding, enabledTakeFindStringFromSelection, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, #endif - -#if SUPPORT_AUTOCORRECTION_PANEL - { "CancelOperation", { executeCancelOperation, supportedFromMenuOrKeyBinding, enabledDismissCorrectionPanel, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, -#endif }; // These unsupported commands are listed here since they appear in the Microsoft diff --git a/Source/WebCore/editing/EditorInsertAction.h b/Source/WebCore/editing/EditorInsertAction.h index 5b732dc..b8b137d 100644 --- a/Source/WebCore/editing/EditorInsertAction.h +++ b/Source/WebCore/editing/EditorInsertAction.h @@ -32,7 +32,7 @@ namespace WebCore { enum EditorInsertAction { EditorInsertActionTyped, EditorInsertActionPasted, - EditorInsertActionDropped, + EditorInsertActionDropped }; } // namespace diff --git a/Source/WebCore/editing/FormatBlockCommand.cpp b/Source/WebCore/editing/FormatBlockCommand.cpp index 9d90a1e..759ca31 100644 --- a/Source/WebCore/editing/FormatBlockCommand.cpp +++ b/Source/WebCore/editing/FormatBlockCommand.cpp @@ -112,8 +112,7 @@ Element* FormatBlockCommand::elementForFormatBlockCommand(Range* range) if (!rootEditableElement || commonAncestor->contains(rootEditableElement)) return 0; - ASSERT(commonAncestor->isElementNode()); - return static_cast<Element*>(commonAncestor); + return commonAncestor->isElementNode() ? toElement(commonAncestor) : 0; } bool isElementForFormatBlock(const QualifiedName& tagName) @@ -149,14 +148,14 @@ Node* enclosingBlockToSplitTreeTo(Node* startNode) { Node* lastBlock = startNode; for (Node* n = startNode; n; n = n->parentNode()) { - if (!n->isContentEditable()) + if (!n->rendererIsEditable()) return lastBlock; - if (isTableCell(n) || n->hasTagName(bodyTag) || !n->parentNode() || !n->parentNode()->isContentEditable() || isElementForFormatBlock(n)) + if (isTableCell(n) || n->hasTagName(bodyTag) || !n->parentNode() || !n->parentNode()->rendererIsEditable() || isElementForFormatBlock(n)) return n; if (isBlock(n)) lastBlock = n; if (isListElement(n)) - return n->parentNode()->isContentEditable() ? n->parentNode() : n; + return n->parentNode()->rendererIsEditable() ? n->parentNode() : n; } return lastBlock; } diff --git a/Source/WebCore/editing/IndentOutdentCommand.cpp b/Source/WebCore/editing/IndentOutdentCommand.cpp index 82bec06..0229e85 100644 --- a/Source/WebCore/editing/IndentOutdentCommand.cpp +++ b/Source/WebCore/editing/IndentOutdentCommand.cpp @@ -120,7 +120,7 @@ void IndentOutdentCommand::outdentParagraph() VisiblePosition visibleEndOfParagraph = endOfParagraph(visibleStartOfParagraph); Node* enclosingNode = enclosingNodeOfType(visibleStartOfParagraph.deepEquivalent(), &isListOrIndentBlockquote); - if (!enclosingNode || !enclosingNode->parentNode()->isContentEditable()) // We can't outdent if there is no place to go! + if (!enclosingNode || !enclosingNode->parentNode()->rendererIsEditable()) // We can't outdent if there is no place to go! return; // Use InsertListCommand to remove the selection from the list @@ -151,7 +151,7 @@ void IndentOutdentCommand::outdentParagraph() if (ContainerNode* splitPointParent = splitPoint->parentNode()) { if (splitPointParent->hasTagName(blockquoteTag) && !splitPoint->hasTagName(blockquoteTag) - && splitPointParent->parentNode()->isContentEditable()) // We can't outdent if there is no place to go! + && splitPointParent->parentNode()->rendererIsEditable()) // We can't outdent if there is no place to go! splitElement(static_cast<Element*>(splitPointParent), splitPoint); } } diff --git a/Source/WebCore/editing/InsertIntoTextNodeCommand.cpp b/Source/WebCore/editing/InsertIntoTextNodeCommand.cpp index 9b7761c..b1a455b 100644 --- a/Source/WebCore/editing/InsertIntoTextNodeCommand.cpp +++ b/Source/WebCore/editing/InsertIntoTextNodeCommand.cpp @@ -44,7 +44,7 @@ InsertIntoTextNodeCommand::InsertIntoTextNodeCommand(PassRefPtr<Text> node, unsi void InsertIntoTextNodeCommand::doApply() { - if (!m_node->isContentEditable()) + if (!m_node->rendererIsEditable()) return; ExceptionCode ec; @@ -56,7 +56,7 @@ void InsertIntoTextNodeCommand::doApply() void InsertIntoTextNodeCommand::doUnapply() { - if (!m_node->isContentEditable()) + if (!m_node->rendererIsEditable()) return; // Need to notify this before actually deleting the text diff --git a/Source/WebCore/editing/InsertLineBreakCommand.cpp b/Source/WebCore/editing/InsertLineBreakCommand.cpp index 2260a00..76c9052 100644 --- a/Source/WebCore/editing/InsertLineBreakCommand.cpp +++ b/Source/WebCore/editing/InsertLineBreakCommand.cpp @@ -171,7 +171,7 @@ void InsertLineBreakCommand::doApply() // leaves and then comes back, new input will have the right style. // FIXME: We shouldn't always apply the typing style to the line break here, // see <rdar://problem/5794462>. - applyStyle(typingStyle.get(), firstDeepEditingPositionForNode(nodeToInsert.get()), lastDeepEditingPositionForNode(nodeToInsert.get())); + applyStyle(typingStyle.get(), firstPositionInOrBeforeNode(nodeToInsert.get()), lastPositionInOrAfterNode(nodeToInsert.get())); // Even though this applyStyle operates on a Range, it still sets an endingSelection(). // It tries to set a VisibleSelection around the content it operated on. So, that VisibleSelection // will either (a) select the line break we inserted, or it will (b) be a caret just diff --git a/Source/WebCore/editing/InsertListCommand.cpp b/Source/WebCore/editing/InsertListCommand.cpp index 68661b4..4585b2e 100644 --- a/Source/WebCore/editing/InsertListCommand.cpp +++ b/Source/WebCore/editing/InsertListCommand.cpp @@ -121,8 +121,8 @@ void InsertListCommand::doApply() // FIXME: We paint the gap before some paragraphs that are indented with left // margin/padding, but not others. We should make the gap painting more consistent and // then use a left margin/padding rule here. - if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd)) - setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true))); + if (visibleEnd != visibleStart && isStartOfParagraph(visibleEnd, CanSkipOverEditingBoundary)) + setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(CannotCrossEditingBoundary))); const QualifiedName& listTag = (m_type == OrderedList) ? olTag : ulTag; if (endingSelection().isRange()) { @@ -130,14 +130,14 @@ void InsertListCommand::doApply() ASSERT(selection.isRange()); VisiblePosition startOfSelection = selection.visibleStart(); VisiblePosition endOfSelection = selection.visibleEnd(); - VisiblePosition startOfLastParagraph = startOfParagraph(endOfSelection); + VisiblePosition startOfLastParagraph = startOfParagraph(endOfSelection, CanSkipOverEditingBoundary); - if (startOfParagraph(startOfSelection) != startOfLastParagraph) { + if (startOfParagraph(startOfSelection, CanSkipOverEditingBoundary) != startOfLastParagraph) { bool forceCreateList = !selectionHasListOfType(selection, listTag); RefPtr<Range> currentSelection = endingSelection().firstRange(); VisiblePosition startOfCurrentParagraph = startOfSelection; - while (startOfCurrentParagraph != startOfLastParagraph) { + while (!inSameParagraph(startOfCurrentParagraph, startOfLastParagraph, CanCrossEditingBoundary)) { // doApply() may operate on and remove the last paragraph of the selection from the document // if it's in the same list item as startOfCurrentParagraph. Return early to avoid an // infinite loop and because there is no more work to be done. @@ -162,7 +162,7 @@ void InsertListCommand::doApply() if (!lastSelectionRange) return; endOfSelection = lastSelectionRange->startPosition(); - startOfLastParagraph = startOfParagraph(endOfSelection); + startOfLastParagraph = startOfParagraph(endOfSelection, CanSkipOverEditingBoundary); } // Fetch the start of the selection after moving the first paragraph, @@ -257,14 +257,14 @@ void InsertListCommand::unlistifyParagraph(const VisiblePosition& originalStart, VisiblePosition start; VisiblePosition end; if (listChildNode->hasTagName(liTag)) { - start = firstDeepEditingPositionForNode(listChildNode); - end = lastDeepEditingPositionForNode(listChildNode); + start = firstPositionInNode(listChildNode); + end = lastPositionInNode(listChildNode); nextListChild = listChildNode->nextSibling(); previousListChild = listChildNode->previousSibling(); } else { // A paragraph is visually a list item minus a list marker. The paragraph will be moved. - start = startOfParagraph(originalStart); - end = endOfParagraph(start); + 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); @@ -327,8 +327,8 @@ static Element* adjacentEnclosingList(const VisiblePosition& pos, const VisibleP PassRefPtr<HTMLElement> InsertListCommand::listifyParagraph(const VisiblePosition& originalStart, const QualifiedName& listTag) { - VisiblePosition start = startOfParagraph(originalStart); - VisiblePosition end = endOfParagraph(start); + VisiblePosition start = startOfParagraph(originalStart, CanSkipOverEditingBoundary); + VisiblePosition end = endOfParagraph(start, CanSkipOverEditingBoundary); if (start.isNull() || end.isNull()) return 0; @@ -339,8 +339,8 @@ PassRefPtr<HTMLElement> InsertListCommand::listifyParagraph(const VisiblePositio appendNode(placeholder, listItemElement); // Place list item into adjoining lists. - Element* previousList = adjacentEnclosingList(start.deepEquivalent(), start.previous(true), listTag); - Element* nextList = adjacentEnclosingList(start.deepEquivalent(), end.next(true), listTag); + Element* previousList = adjacentEnclosingList(start.deepEquivalent(), start.previous(CannotCrossEditingBoundary), listTag); + Element* nextList = adjacentEnclosingList(start.deepEquivalent(), end.next(CannotCrossEditingBoundary), listTag); RefPtr<HTMLElement> listElement; if (previousList) appendNode(listItemElement, previousList); @@ -375,8 +375,11 @@ PassRefPtr<HTMLElement> InsertListCommand::listifyParagraph(const VisiblePositio // We inserted the list at the start of the content we're about to move // Update the start of content, so we don't try to move the list into itself. bug 19066 - if (insertionPos == start.deepEquivalent()) - start = startOfParagraph(originalStart); + // Layout is necessary since start's node's inline renderers may have been destroyed by the insertion + if (insertionPos == start.deepEquivalent()) { + listElement->document()->updateLayoutIgnorePendingStylesheets(); + start = startOfParagraph(originalStart, CanSkipOverEditingBoundary); + } } moveParagraph(start, end, positionBeforeNode(placeholder.get()), true); diff --git a/Source/WebCore/editing/InsertNodeBeforeCommand.cpp b/Source/WebCore/editing/InsertNodeBeforeCommand.cpp index 5fae45e..4b028e7 100644 --- a/Source/WebCore/editing/InsertNodeBeforeCommand.cpp +++ b/Source/WebCore/editing/InsertNodeBeforeCommand.cpp @@ -41,13 +41,13 @@ InsertNodeBeforeCommand::InsertNodeBeforeCommand(PassRefPtr<Node> insertChild, P ASSERT(m_refChild); ASSERT(m_refChild->parentNode()); - ASSERT(m_refChild->parentNode()->isContentEditable() || !m_refChild->parentNode()->attached()); + ASSERT(m_refChild->parentNode()->rendererIsEditable() || !m_refChild->parentNode()->attached()); } void InsertNodeBeforeCommand::doApply() { ContainerNode* parent = m_refChild->parentNode(); - if (!parent || !parent->isContentEditable()) + if (!parent || !parent->rendererIsEditable()) return; ExceptionCode ec; @@ -59,7 +59,7 @@ void InsertNodeBeforeCommand::doApply() void InsertNodeBeforeCommand::doUnapply() { - if (!m_insertChild->isContentEditable()) + if (!m_insertChild->rendererIsEditable()) return; // Need to notify this before actually deleting the text diff --git a/Source/WebCore/editing/JoinTextNodesCommand.cpp b/Source/WebCore/editing/JoinTextNodesCommand.cpp index 2766b84..86dd381 100644 --- a/Source/WebCore/editing/JoinTextNodesCommand.cpp +++ b/Source/WebCore/editing/JoinTextNodesCommand.cpp @@ -46,7 +46,7 @@ void JoinTextNodesCommand::doApply() return; ContainerNode* parent = m_text2->parentNode(); - if (!parent || !parent->isContentEditable()) + if (!parent || !parent->rendererIsEditable()) return; ExceptionCode ec = 0; @@ -63,7 +63,7 @@ void JoinTextNodesCommand::doUnapply() return; ContainerNode* parent = m_text2->parentNode(); - if (!parent || !parent->isContentEditable()) + if (!parent || !parent->rendererIsEditable()) return; ExceptionCode ec = 0; diff --git a/Source/WebCore/editing/MergeIdenticalElementsCommand.cpp b/Source/WebCore/editing/MergeIdenticalElementsCommand.cpp index ff59f49..4ee9436 100644 --- a/Source/WebCore/editing/MergeIdenticalElementsCommand.cpp +++ b/Source/WebCore/editing/MergeIdenticalElementsCommand.cpp @@ -42,7 +42,7 @@ MergeIdenticalElementsCommand::MergeIdenticalElementsCommand(PassRefPtr<Element> void MergeIdenticalElementsCommand::doApply() { - if (m_element1->nextSibling() != m_element2 || !m_element1->isContentEditable() || !m_element2->isContentEditable()) + if (m_element1->nextSibling() != m_element2 || !m_element1->rendererIsEditable() || !m_element2->rendererIsEditable()) return; m_atChild = m_element2->firstChild(); @@ -68,7 +68,7 @@ void MergeIdenticalElementsCommand::doUnapply() RefPtr<Node> atChild = m_atChild.release(); ContainerNode* parent = m_element2->parentNode(); - if (!parent || !parent->isContentEditable()) + if (!parent || !parent->rendererIsEditable()) return; ExceptionCode ec = 0; diff --git a/Source/WebCore/editing/RemoveNodeCommand.cpp b/Source/WebCore/editing/RemoveNodeCommand.cpp index 94e3e62..4a52492 100644 --- a/Source/WebCore/editing/RemoveNodeCommand.cpp +++ b/Source/WebCore/editing/RemoveNodeCommand.cpp @@ -42,7 +42,7 @@ RemoveNodeCommand::RemoveNodeCommand(PassRefPtr<Node> node) void RemoveNodeCommand::doApply() { ContainerNode* parent = m_node->parentNode(); - if (!parent || !parent->isContentEditable()) + if (!parent || !parent->rendererIsEditable()) return; m_parent = parent; @@ -56,7 +56,7 @@ void RemoveNodeCommand::doUnapply() { RefPtr<ContainerNode> parent = m_parent.release(); RefPtr<Node> refChild = m_refChild.release(); - if (!parent || !parent->isContentEditable()) + if (!parent || !parent->rendererIsEditable()) return; ExceptionCode ec; diff --git a/Source/WebCore/editing/ReplaceSelectionCommand.cpp b/Source/WebCore/editing/ReplaceSelectionCommand.cpp index b0a2d68..94531a6 100644 --- a/Source/WebCore/editing/ReplaceSelectionCommand.cpp +++ b/Source/WebCore/editing/ReplaceSelectionCommand.cpp @@ -43,6 +43,7 @@ #include "HTMLInputElement.h" #include "HTMLInterchange.h" #include "HTMLNames.h" +#include "NodeList.h" #include "SelectionController.h" #include "SmartReplace.h" #include "TextIterator.h" @@ -110,7 +111,7 @@ static bool isInterchangeConvertedSpaceSpan(const Node *node) static Position positionAvoidingPrecedingNodes(Position pos) { // If we're already on a break, it's probably a placeholder and we shouldn't change our position. - if (pos.deprecatedNode()->hasTagName(brTag)) + if (editingIgnoresContent(pos.deprecatedNode())) return pos; // We also stop when changing block flow elements because even though the visual position is the @@ -147,7 +148,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f if (!editableRoot->getAttributeEventListener(eventNames().webkitBeforeTextInsertedEvent) && // FIXME: Remove these checks once textareas and textfields actually register an event handler. !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextControl()) && - editableRoot->isContentRichlyEditable()) { + editableRoot->rendererIsRichlyEditable()) { removeInterchangeNodes(m_fragment.get()); return; } @@ -162,7 +163,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f ExceptionCode ec = 0; editableRoot->dispatchEvent(evt, ec); ASSERT(ec == 0); - if (text != evt->text() || !editableRoot->isContentRichlyEditable()) { + if (text != evt->text() || !editableRoot->rendererIsRichlyEditable()) { restoreTestRenderingNodesToFragment(holder.get()); removeNode(holder); @@ -357,7 +358,7 @@ static bool hasMatchingQuoteLevel(VisiblePosition endOfExistingContent, VisibleP { Position existing = endOfExistingContent.deepEquivalent(); Position inserted = endOfInsertedContent.deepEquivalent(); - bool isInsideMailBlockquote = nearestMailBlockquote(inserted.deprecatedNode()); + bool isInsideMailBlockquote = enclosingNodeOfType(inserted, isMailBlockquote, CanCrossEditingBoundary); return isInsideMailBlockquote && (numEnclosingMailBlockquotes(existing) == numEnclosingMailBlockquotes(inserted)); } @@ -367,7 +368,7 @@ bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfPara return false; VisiblePosition startOfInsertedContent(positionAtStartOfInsertedContent()); - VisiblePosition prev = startOfInsertedContent.previous(true); + VisiblePosition prev = startOfInsertedContent.previous(CannotCrossEditingBoundary); if (prev.isNull()) return false; @@ -389,7 +390,7 @@ bool ReplaceSelectionCommand::shouldMergeStart(bool selectionStartWasStartOfPara bool ReplaceSelectionCommand::shouldMergeEnd(bool selectionEndWasEndOfParagraph) { VisiblePosition endOfInsertedContent(positionAtEndOfInsertedContent()); - VisiblePosition next = endOfInsertedContent.next(true); + VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary); if (next.isNull()) return false; @@ -536,10 +537,10 @@ VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent() { Node* lastNode = m_lastLeafInserted.get(); // FIXME: Why is this hack here? What's special about <select> tags? - Node* enclosingSelect = enclosingNodeWithTag(firstDeepEditingPositionForNode(lastNode), selectTag); + Node* enclosingSelect = enclosingNodeWithTag(firstPositionInOrBeforeNode(lastNode), selectTag); if (enclosingSelect) lastNode = enclosingSelect; - return lastDeepEditingPositionForNode(lastNode); + return lastPositionInOrAfterNode(lastNode); } VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent() @@ -556,7 +557,7 @@ static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const // Handling the case where we are doing Paste as Quotation or pasting into quoted content is more complicated (see handleStyleSpans) // and doesn't receive the optimization. - if (isMailPasteAsQuotationNode(topNode) || nearestMailBlockquote(topNode)) + if (isMailPasteAsQuotationNode(topNode) || enclosingNodeOfType(firstPositionInOrBeforeNode(topNode), isMailBlockquote, CanCrossEditingBoundary)) return false; // Either there are no style spans in the fragment or a WebKit client has added content to the fragment @@ -623,7 +624,7 @@ void ReplaceSelectionCommand::handleStyleSpans() // If Mail wraps the fragment with a Paste as Quotation blockquote, or if you're pasting into a quoted region, // styles from blockquoteNode are allowed to override those from the source document, see <rdar://problem/4930986> and <rdar://problem/5089327>. - Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context : nearestMailBlockquote(context); + Node* blockquoteNode = isMailPasteAsQuotationNode(context) ? context : enclosingNodeOfType(firstPositionInNode(context), isMailBlockquote, CanCrossEditingBoundary); if (blockquoteNode) { sourceDocumentStyle->removeStyleConflictingWithStyleOfNode(blockquoteNode); context = blockquoteNode->parentNode(); @@ -776,6 +777,34 @@ static Node* enclosingInline(Node* node) return node; } +static bool isInlineNodeWithStyle(const Node* node) +{ + // We don't want to skip over any block elements. + if (!node->renderer() || !node->renderer()->isInline()) + return false; + + if (!node->isHTMLElement()) + return false; + + // We can skip over elements whose class attribute is + // one of our internal classes. + const HTMLElement* element = static_cast<const HTMLElement*>(node); + AtomicString classAttributeValue = element->getAttribute(classAttr); + if (classAttributeValue == AppleStyleSpanClass + || classAttributeValue == AppleTabSpanClass + || classAttributeValue == AppleConvertedSpace + || classAttributeValue == ApplePasteAsQuotation) + return true; + + // We can skip inline elements that don't have attributes or whose only + // attribute is the style attribute. + const NamedNodeMap* attributeMap = element->attributeMap(); + if (!attributeMap || attributeMap->isEmpty() || (attributeMap->length() == 1 && element->hasAttribute(styleAttr))) + return true; + + return false; +} + void ReplaceSelectionCommand::doApply() { VisibleSelection selection = endingSelection(); @@ -811,8 +840,8 @@ void ReplaceSelectionCommand::doApply() Node* startBlock = enclosingBlock(visibleStart.deepEquivalent().deprecatedNode()); Position insertionPos = selection.start(); - bool startIsInsideMailBlockquote = nearestMailBlockquote(insertionPos.deprecatedNode()); - + bool startIsInsideMailBlockquote = enclosingNodeOfType(insertionPos, isMailBlockquote, CanCrossEditingBoundary); + if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !startIsInsideMailBlockquote) || startBlock == currentRoot || isListItem(startBlock) || selectionIsPlainText) m_preventNesting = false; @@ -840,7 +869,7 @@ void ReplaceSelectionCommand::doApply() } else { ASSERT(selection.isCaret()); if (fragment.hasInterchangeNewlineAtStart()) { - VisiblePosition next = visibleStart.next(true); + VisiblePosition next = visibleStart.next(CannotCrossEditingBoundary); if (isEndOfParagraph(visibleStart) && !isStartOfParagraph(visibleStart) && next.isNotNull()) setEndingSelection(next); else @@ -917,6 +946,29 @@ void ReplaceSelectionCommand::doApply() // outside of preceding tags. insertionPos = positionAvoidingPrecedingNodes(insertionPos); + // If we are not trying to match the destination style we prefer a position + // that is outside inline elements that provide style. + // This way we can produce a less verbose markup. + // We can skip this optimization for fragments not wrapped in one of + // our style spans and for positions inside list items + // since insertAsListItems already does the right thing. + if (!m_matchStyle && !enclosingList(insertionPos.anchorNode()) && isStyleSpan(fragment.firstChild())) { + Node* parentNode = insertionPos.anchorNode()->parentNode(); + while (parentNode && parentNode->renderer() && isInlineNodeWithStyle(parentNode)) { + // If we are in the middle of a text node, we need to split it before we can + // move the insertion position. + if (insertionPos.anchorNode()->isTextNode() && insertionPos.anchorType() == Position::PositionIsOffsetInAnchor && insertionPos.offsetInContainerNode() && !insertionPos.atLastEditingPositionForNode()) + splitTextNodeContainingElement(static_cast<Text*>(insertionPos.anchorNode()), insertionPos.offsetInContainerNode()); + + // If the style element has more than one child, we need to split it. + if (parentNode->firstChild()->nextSibling()) + splitElement(static_cast<Element*>(parentNode), insertionPos.computeNodeAfterPosition()); + + insertionPos = positionInParentBeforeNode(parentNode); + parentNode = parentNode->parentNode(); + } + } + // FIXME: When pasting rich content we're often prevented from heading down the fast path by style spans. Try // again here if they've been removed. @@ -1036,7 +1088,7 @@ void ReplaceSelectionCommand::doApply() startOfInsertedContent = positionAtStartOfInsertedContent(); if (interchangeNewlineAtEnd) { - VisiblePosition next = endOfInsertedContent.next(true); + VisiblePosition next = endOfInsertedContent.next(CannotCrossEditingBoundary); if (selectionEndWasEndOfParagraph || !isEndOfParagraph(endOfInsertedContent) || next.isNull()) { if (!isStartOfParagraph(endOfInsertedContent)) { diff --git a/Source/WebCore/editing/SelectionController.cpp b/Source/WebCore/editing/SelectionController.cpp index 65f9062..698ba2c 100644 --- a/Source/WebCore/editing/SelectionController.cpp +++ b/Source/WebCore/editing/SelectionController.cpp @@ -153,16 +153,14 @@ void SelectionController::setSelection(const VisibleSelection& s, SetSelectionOp return; } - Node* baseNode = s.base().deprecatedNode(); - Document* document = 0; - if (baseNode) - document = baseNode->document(); - // <http://bugs.webkit.org/show_bug.cgi?id=23464>: Infinite recursion at SelectionController::setSelection // if document->frame() == m_frame we can get into an infinite loop - if (document && document->frame() && document->frame() != m_frame && document != m_frame->document()) { - document->frame()->selection()->setSelection(s, options); - return; + if (s.base().anchorNode()) { + Document* document = s.base().anchorNode()->document(); + if (document && document->frame() && document->frame() != m_frame && document != m_frame->document()) { + document->frame()->selection()->setSelection(s, options); + return; + } } if (closeTyping) @@ -170,10 +168,13 @@ void SelectionController::setSelection(const VisibleSelection& s, SetSelectionOp if (shouldClearTypingStyle) clearTypingStyle(); - - if (m_selection == s) + + if (m_selection == s) { + // Even if selection was not changed, selection offsets may have been changed. + notifyRendererOfSelectionChange(userTriggered); return; - + } + VisibleSelection oldSelection = m_selection; m_selection = s; @@ -208,17 +209,17 @@ void SelectionController::setSelection(const VisibleSelection& s, SetSelectionOp static bool removingNodeRemovesPosition(Node* node, const Position& position) { - if (!position.deprecatedNode()) + if (!position.anchorNode()) return false; - if (position.deprecatedNode() == node) + if (position.anchorNode() == node) return true; if (!node->isElementNode()) return false; Element* element = static_cast<Element*>(node); - return element->contains(position.deprecatedNode()) || element->contains(position.deprecatedNode()->shadowAncestorNode()); + return element->contains(position.anchorNode()) || element->contains(position.anchorNode()->shadowAncestorNode()); } void SelectionController::nodeWillBeRemoved(Node *node) @@ -336,6 +337,11 @@ void SelectionController::setIsDirectional(bool isDirectional) m_isDirectional = !m_frame || m_frame->editor()->behavior().shouldConsiderSelectionAsDirectional() || isDirectional; } +TextDirection SelectionController::directionOfEnclosingBlock() +{ + return WebCore::directionOfEnclosingBlock(m_selection.extent()); +} + void SelectionController::willBeModified(EAlteration alter, SelectionDirection direction) { if (alter != AlterationExtend) @@ -385,17 +391,6 @@ void SelectionController::willBeModified(EAlteration alter, SelectionDirection d } } -TextDirection SelectionController::directionOfEnclosingBlock() -{ - Node* enclosingBlockNode = enclosingBlock(m_selection.extent().deprecatedNode()); - if (!enclosingBlockNode) - return LTR; - RenderObject* renderer = enclosingBlockNode->renderer(); - if (renderer) - return renderer->style()->direction(); - return LTR; -} - VisiblePosition SelectionController::positionForPlatform(bool isGetStart) const { Settings* settings = m_frame ? m_frame->settings() : 0; @@ -431,9 +426,9 @@ VisiblePosition SelectionController::modifyExtendingRight(TextGranularity granul switch (granularity) { case CharacterGranularity: if (directionOfEnclosingBlock() == LTR) - pos = pos.next(true); + pos = pos.next(CannotCrossEditingBoundary); else - pos = pos.previous(true); + pos = pos.previous(CannotCrossEditingBoundary); break; case WordGranularity: if (directionOfEnclosingBlock() == LTR) @@ -464,7 +459,7 @@ VisiblePosition SelectionController::modifyExtendingForward(TextGranularity gran VisiblePosition pos(m_selection.extent(), m_selection.affinity()); switch (granularity) { case CharacterGranularity: - pos = pos.next(true); + pos = pos.next(CannotCrossEditingBoundary); break; case WordGranularity: pos = nextWordPosition(pos); @@ -538,7 +533,7 @@ VisiblePosition SelectionController::modifyMovingForward(TextGranularity granula if (isRange()) pos = VisiblePosition(m_selection.end(), m_selection.affinity()); else - pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).next(true); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).next(CannotCrossEditingBoundary); break; case WordGranularity: pos = nextWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); @@ -589,9 +584,9 @@ VisiblePosition SelectionController::modifyExtendingLeft(TextGranularity granula switch (granularity) { case CharacterGranularity: if (directionOfEnclosingBlock() == LTR) - pos = pos.previous(true); + pos = pos.previous(CannotCrossEditingBoundary); else - pos = pos.next(true); + pos = pos.next(CannotCrossEditingBoundary); break; case WordGranularity: if (directionOfEnclosingBlock() == LTR) @@ -626,7 +621,7 @@ VisiblePosition SelectionController::modifyExtendingBackward(TextGranularity gra // over everything. switch (granularity) { case CharacterGranularity: - pos = pos.previous(true); + pos = pos.previous(CannotCrossEditingBoundary); break; case WordGranularity: pos = previousWordPosition(pos); @@ -698,7 +693,7 @@ VisiblePosition SelectionController::modifyMovingBackward(TextGranularity granul if (isRange()) pos = VisiblePosition(m_selection.start(), m_selection.affinity()); else - pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).previous(true); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).previous(CannotCrossEditingBoundary); break; case WordGranularity: pos = previousWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); @@ -1256,11 +1251,11 @@ void SelectionController::debugRenderer(RenderObject *r, bool selected) const int textLength = text.length(); if (selected) { int offset = 0; - if (r->node() == m_selection.start().deprecatedNode()) - offset = m_selection.start().deprecatedEditingOffset(); - else if (r->node() == m_selection.end().deprecatedNode()) - offset = m_selection.end().deprecatedEditingOffset(); - + if (r->node() == m_selection.start().containerNode()) + offset = m_selection.start().computeOffsetInContainerNode(); + else if (r->node() == m_selection.end().containerNode()) + offset = m_selection.end().computeOffsetInContainerNode(); + int pos; InlineTextBox* box = textRenderer->findNextInlineTextBox(offset, pos); text = text.substring(box->start(), box->len()); @@ -1368,7 +1363,7 @@ void SelectionController::selectFrameElementInParentIfFullySelected() return; // This method's purpose is it to make it easier to select iframes (in order to delete them). Don't do anything if the iframe isn't deletable. - if (!ownerElementParent->isContentEditable()) + if (!ownerElementParent->rendererIsEditable()) return; // Create compute positions before and after the element. @@ -1456,7 +1451,9 @@ bool SelectionController::setSelectedRange(Range* range, EAffinity affinity, boo bool SelectionController::isInPasswordField() const { - Node* startNode = start().deprecatedNode(); + ASSERT(start().isNull() || start().anchorType() == Position::PositionIsOffsetInAnchor + || start().containerNode() || !start().anchorNode()->shadowAncestorNode()); + Node* startNode = start().containerNode(); if (!startNode) return false; @@ -1828,7 +1825,7 @@ void SelectionController::setSelectionFromNone() Document* document = m_frame->document(); bool caretBrowsing = m_frame->settings() && m_frame->settings()->caretBrowsingEnabled(); - if (!isNone() || !(document->inDesignMode() || caretBrowsing)) + if (!isNone() || !(document->rendererIsEditable() || caretBrowsing)) return; Node* node = document->documentElement(); diff --git a/Source/WebCore/editing/SelectionController.h b/Source/WebCore/editing/SelectionController.h index d6a9dff..2e3a6cd 100644 --- a/Source/WebCore/editing/SelectionController.h +++ b/Source/WebCore/editing/SelectionController.h @@ -127,7 +127,7 @@ public: bool isRange() const { return m_selection.isRange(); } bool isCaretOrRange() const { return m_selection.isCaretOrRange(); } bool isInPasswordField() const; - bool isAll(StayInEditableContent stayInEditableContent = MustStayInEditableContent) const { return m_selection.isAll(stayInEditableContent); } + bool isAll(EditingBoundaryCrossingRule rule = CannotCrossEditingBoundary) const { return m_selection.isAll(rule); } PassRefPtr<Range> toNormalizedRange() const { return m_selection.toNormalizedRange(); } diff --git a/Source/WebCore/editing/SplitElementCommand.cpp b/Source/WebCore/editing/SplitElementCommand.cpp index 888c45f..fc135f7 100644 --- a/Source/WebCore/editing/SplitElementCommand.cpp +++ b/Source/WebCore/editing/SplitElementCommand.cpp @@ -54,7 +54,7 @@ void SplitElementCommand::executeApply() ExceptionCode ec = 0; ContainerNode* parent = m_element2->parentNode(); - if (!parent || !parent->isContentEditable()) + if (!parent || !parent->rendererIsEditable()) return; parent->insertBefore(m_element1.get(), m_element2.get(), ec); if (ec) @@ -78,7 +78,7 @@ void SplitElementCommand::doApply() void SplitElementCommand::doUnapply() { - if (!m_element1 || !m_element1->isContentEditable() || !m_element2->isContentEditable()) + if (!m_element1 || !m_element1->rendererIsEditable() || !m_element2->rendererIsEditable()) return; Vector<RefPtr<Node> > children; diff --git a/Source/WebCore/editing/SplitTextNodeCommand.cpp b/Source/WebCore/editing/SplitTextNodeCommand.cpp index aea36b9..5cf7ac8 100644 --- a/Source/WebCore/editing/SplitTextNodeCommand.cpp +++ b/Source/WebCore/editing/SplitTextNodeCommand.cpp @@ -51,7 +51,7 @@ SplitTextNodeCommand::SplitTextNodeCommand(PassRefPtr<Text> text, int offset) void SplitTextNodeCommand::doApply() { ContainerNode* parent = m_text2->parentNode(); - if (!parent || !parent->isContentEditable()) + if (!parent || !parent->rendererIsEditable()) return; ExceptionCode ec = 0; @@ -68,7 +68,7 @@ void SplitTextNodeCommand::doApply() void SplitTextNodeCommand::doUnapply() { - if (!m_text1 || !m_text1->isContentEditable()) + if (!m_text1 || !m_text1->rendererIsEditable()) return; ASSERT(m_text1->document() == document()); @@ -89,7 +89,7 @@ void SplitTextNodeCommand::doReapply() return; ContainerNode* parent = m_text2->parentNode(); - if (!parent || !parent->isContentEditable()) + if (!parent || !parent->rendererIsEditable()) return; insertText1AndTrimText2(); diff --git a/Source/WebCore/editing/SplitTextNodeContainingElementCommand.cpp b/Source/WebCore/editing/SplitTextNodeContainingElementCommand.cpp index 8c90fb0..7b9fd2a 100644 --- a/Source/WebCore/editing/SplitTextNodeContainingElementCommand.cpp +++ b/Source/WebCore/editing/SplitTextNodeContainingElementCommand.cpp @@ -48,7 +48,7 @@ void SplitTextNodeContainingElementCommand::doApply() splitTextNode(m_text.get(), m_offset); Element* parent = m_text->parentElement(); - if (!parent || !parent->parentElement() || !parent->parentElement()->isContentEditable()) + if (!parent || !parent->parentElement() || !parent->parentElement()->rendererIsEditable()) return; RenderObject* parentRenderer = parent->renderer(); diff --git a/Source/WebCore/editing/TextCheckingHelper.cpp b/Source/WebCore/editing/TextCheckingHelper.cpp index e5553fd..009c807 100644 --- a/Source/WebCore/editing/TextCheckingHelper.cpp +++ b/Source/WebCore/editing/TextCheckingHelper.cpp @@ -224,7 +224,7 @@ String TextCheckingHelper::findFirstMisspelling(int& firstMisspellingOffset, boo String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail) { -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) +#if USE(UNIFIED_TEXT_CHECKING) String firstFoundItem; String misspelledWord; String badGrammarPhrase; @@ -350,12 +350,12 @@ String TextCheckingHelper::findFirstMisspellingOrBadGrammar(bool checkGrammar, b UNUSED_PARAM(outFirstFoundOffset); UNUSED_PARAM(outGrammarDetail); return ""; -#endif +#endif // USE(UNIFIED_TEXT_CHECKING) } int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& grammarDetails, int badGrammarPhraseLocation, int /*badGrammarPhraseLength*/, int startOffset, int endOffset, bool markAll) { -#ifndef BUILDING_ON_TIGER +#if USE(GRAMMAR_CHECKING) // Found some bad grammar. Find the earliest detail range that starts in our search range (if any). // Optionally add a DocumentMarker for each detail in the range. int earliestDetailLocationSoFar = -1; @@ -402,7 +402,7 @@ int TextCheckingHelper::findFirstGrammarDetail(const Vector<GrammarDetail>& gram String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, int& outGrammarPhraseOffset, bool markAll) { -#ifndef BUILDING_ON_TIGER + ASSERT(WTF_USE_GRAMMAR_CHECKING); // Initialize out parameters; these will be updated if we find something to return. outGrammarDetail.location = -1; outGrammarDetail.length = 0; @@ -458,18 +458,12 @@ String TextCheckingHelper::findFirstBadGrammar(GrammarDetail& outGrammarDetail, } return firstBadGrammarPhrase; -#else - ASSERT_NOT_REACHED(); - UNUSED_PARAM(outGrammarDetail); - UNUSED_PARAM(outGrammarPhraseOffset); - UNUSED_PARAM(markAll); -#endif } bool TextCheckingHelper::isUngrammatical(Vector<String>& guessesVector) const { -#ifndef BUILDING_ON_TIGER + ASSERT(WTF_USE_GRAMMAR_CHECKING); if (!m_client) return false; @@ -511,16 +505,11 @@ bool TextCheckingHelper::isUngrammatical(Vector<String>& guessesVector) const m_client->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail); return true; -#else - ASSERT_NOT_REACHED(); - UNUSED_PARAM(guessesVector); - return true; -#endif } Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool checkGrammar, bool& misspelled, bool& ungrammatical) const { -#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) +#if USE(UNIFIED_TEXT_CHECKING) Vector<String> guesses; ExceptionCode ec; misspelled = false; @@ -578,7 +567,7 @@ Vector<String> TextCheckingHelper::guessesForMisspelledOrUngrammaticalRange(bool UNUSED_PARAM(misspelled); UNUSED_PARAM(ungrammatical); return Vector<String>(); -#endif +#endif // USE(UNIFIED_TEXT_CHECKING) } @@ -592,15 +581,12 @@ void TextCheckingHelper::markAllMisspellings(RefPtr<Range>& firstMisspellingRang void TextCheckingHelper::markAllBadGrammar() { -#ifndef BUILDING_ON_TIGER - // Use the "markAll" feature of findFirstBadGrammar. Ignore the return value and "out parameters"; all we need to + ASSERT(WTF_USE_GRAMMAR_CHECKING); + // Use the "markAll" feature of ofindFirstBadGrammar. Ignore the return value and "out parameters"; all we need to // do is mark every instance. GrammarDetail ignoredGrammarDetail; int ignoredOffset; findFirstBadGrammar(ignoredGrammarDetail, ignoredOffset, true); -#else - ASSERT_NOT_REACHED(); -#endif } } diff --git a/Source/WebCore/editing/TextIterator.cpp b/Source/WebCore/editing/TextIterator.cpp index a9546b8..1b25c87 100644 --- a/Source/WebCore/editing/TextIterator.cpp +++ b/Source/WebCore/editing/TextIterator.cpp @@ -2320,7 +2320,7 @@ PassRefPtr<Range> TextIterator::rangeFromLocationAndLength(Element* scope, int r Position runEnd = VisiblePosition(runStart).next().deepEquivalent(); if (runEnd.isNotNull()) { ExceptionCode ec = 0; - textRunRange->setEnd(runEnd.deprecatedNode(), runEnd.deprecatedEditingOffset(), ec); + textRunRange->setEnd(runEnd.containerNode(), runEnd.computeOffsetInContainerNode(), ec); ASSERT(!ec); } } @@ -2512,11 +2512,6 @@ tryAgain: return matchLength; } -PassRefPtr<Range> findPlainText(const Range* range, const String& target, bool forward, bool caseSensitive) -{ - return findPlainText(range, target, (forward ? 0 : Backwards) | (caseSensitive ? 0 : CaseInsensitive)); -} - PassRefPtr<Range> findPlainText(const Range* range, const String& target, FindOptions options) { // First, find the text. diff --git a/Source/WebCore/editing/TextIterator.h b/Source/WebCore/editing/TextIterator.h index b0c310a..0f1a6fd 100644 --- a/Source/WebCore/editing/TextIterator.h +++ b/Source/WebCore/editing/TextIterator.h @@ -60,8 +60,6 @@ inline bool isCollapsibleWhitespace(UChar c) String plainText(const Range*, TextIteratorBehavior defaultBehavior = TextIteratorDefaultBehavior); UChar* plainTextToMallocAllocatedBuffer(const Range*, unsigned& bufferLength, bool isDisplayString, TextIteratorBehavior = TextIteratorDefaultBehavior); PassRefPtr<Range> findPlainText(const Range*, const String&, FindOptions); -// FIXME: Switch callers over to the FindOptions version and retire this one. -PassRefPtr<Range> findPlainText(const Range*, const String&, bool forward, bool caseSensitive); class BitStack { public: diff --git a/Source/WebCore/editing/TypingCommand.cpp b/Source/WebCore/editing/TypingCommand.cpp index 3b42915..aedda31 100644 --- a/Source/WebCore/editing/TypingCommand.cpp +++ b/Source/WebCore/editing/TypingCommand.cpp @@ -47,6 +47,14 @@ namespace WebCore { using namespace HTMLNames; +static bool canAppendNewLineFeed(const VisibleSelection& selection) +{ + ExceptionCode ec = 0; + RefPtr<BeforeTextInsertedEvent> event = BeforeTextInsertedEvent::create(String("\n")); + selection.rootEditableElement()->dispatchEvent(event, ec); + return event->text().length(); +} + TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, const String &textToInsert, TypingCommandOptions options, TextGranularity granularity, TextCompositionType compositionType) : CompositeEditCommand(document) , m_commandType(commandType) @@ -403,12 +411,18 @@ void TypingCommand::insertTextRunWithoutNewlines(const String &text, bool select void TypingCommand::insertLineBreak() { + if (!canAppendNewLineFeed(endingSelection())) + return; + applyCommandToComposite(InsertLineBreakCommand::create(document())); typingAddedToOpenCommand(InsertLineBreak); } void TypingCommand::insertParagraphSeparator() { + if (!canAppendNewLineFeed(endingSelection())) + return; + applyCommandToComposite(InsertParagraphSeparatorCommand::create(document())); typingAddedToOpenCommand(InsertParagraphSeparator); } @@ -429,7 +443,7 @@ void TypingCommand::insertParagraphSeparatorInQuotedContent() bool TypingCommand::makeEditableRootEmpty() { Element* root = endingSelection().rootEditableElement(); - if (!root->firstChild()) + if (!root || !root->firstChild()) return false; if (root->firstChild() == root->lastChild() && root->firstElementChild() && root->firstElementChild()->hasTagName(brTag)) { @@ -474,14 +488,14 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) if (killRing && selection.isCaret() && granularity != CharacterGranularity) selection.modify(SelectionController::AlterationExtend, DirectionBackward, CharacterGranularity); - if (endingSelection().visibleStart().previous(true).isNull()) { + if (endingSelection().visibleStart().previous(CannotCrossEditingBoundary).isNull()) { // When the caret is at the start of the editable area in an empty list item, break out of the list item. if (breakOutOfEmptyListItem()) { typingAddedToOpenCommand(DeleteKey); return; } // When there are no visible positions in the editing root, delete its entire contents. - if (endingSelection().visibleStart().next(true).isNull() && makeEditableRootEmpty()) { + if (endingSelection().visibleStart().next(CannotCrossEditingBoundary).isNull() && makeEditableRootEmpty()) { typingAddedToOpenCommand(DeleteKey); return; } @@ -493,7 +507,7 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) return; // If the caret is at the start of a paragraph after a table, move content into the last table cell. - if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(true))) { + if (isStartOfParagraph(visibleStart) && isFirstPositionAfterTable(visibleStart.previous(CannotCrossEditingBoundary))) { // Unless the caret is just before a table. We don't want to move a table into the last table cell. if (isLastPositionBeforeTable(visibleStart)) return; @@ -574,10 +588,10 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki Position downstreamEnd = endingSelection().end().downstream(); VisiblePosition visibleEnd = endingSelection().visibleEnd(); if (visibleEnd == endOfParagraph(visibleEnd)) - downstreamEnd = visibleEnd.next(true).deepEquivalent().downstream(); + downstreamEnd = visibleEnd.next(CannotCrossEditingBoundary).deepEquivalent().downstream(); // When deleting tables: Select the table first, then perform the deletion if (downstreamEnd.deprecatedNode() && downstreamEnd.deprecatedNode()->renderer() && downstreamEnd.deprecatedNode()->renderer()->isTable() && !downstreamEnd.deprecatedEditingOffset()) { - setEndingSelection(VisibleSelection(endingSelection().end(), lastDeepEditingPositionForNode(downstreamEnd.deprecatedNode()), DOWNSTREAM)); + setEndingSelection(VisibleSelection(endingSelection().end(), positionAfterNode(downstreamEnd.deprecatedNode()), DOWNSTREAM)); typingAddedToOpenCommand(ForwardDeleteKey); return; } diff --git a/Source/WebCore/editing/VisiblePosition.cpp b/Source/WebCore/editing/VisiblePosition.cpp index 10798ad..c3c05fd 100644 --- a/Source/WebCore/editing/VisiblePosition.cpp +++ b/Source/WebCore/editing/VisiblePosition.cpp @@ -59,18 +59,22 @@ void VisiblePosition::init(const Position& position, EAffinity affinity) m_affinity = DOWNSTREAM; } -VisiblePosition VisiblePosition::next(bool stayInEditableContent) const +VisiblePosition VisiblePosition::next(EditingBoundaryCrossingRule rule) const { + // FIXME: Support CanSkipEditingBoundary + ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary); VisiblePosition next(nextVisuallyDistinctCandidate(m_deepPosition), m_affinity); - - if (!stayInEditableContent) + + if (rule == CanCrossEditingBoundary) return next; - + return honorEditableBoundaryAtOrAfter(next); } -VisiblePosition VisiblePosition::previous(bool stayInEditableContent) const +VisiblePosition VisiblePosition::previous(EditingBoundaryCrossingRule rule) const { + // FIXME: Support CanSkipEditingBoundary + ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary); // find first previous DOM position that is visible Position pos = previousVisuallyDistinctCandidate(m_deepPosition); @@ -91,7 +95,7 @@ VisiblePosition VisiblePosition::previous(bool stayInEditableContent) const } #endif - if (!stayInEditableContent) + if (rule == CanCrossEditingBoundary) return prev; return honorEditableBoundaryAtOrBefore(prev); @@ -469,7 +473,7 @@ Position VisiblePosition::canonicalPosition(const Position& passedPosition) // The new position must be in the same editable element. Enforce that first. // Unless the descent is from a non-editable html element to an editable body. - if (node && node->hasTagName(htmlTag) && !node->isContentEditable() && node->document()->body() && node->document()->body()->isContentEditable()) + if (node && node->hasTagName(htmlTag) && !node->rendererIsEditable() && node->document()->body() && node->document()->body()->rendererIsEditable()) return next.isNotNull() ? next : prev; Node* editingRoot = editableRootForPosition(position); diff --git a/Source/WebCore/editing/VisiblePosition.h b/Source/WebCore/editing/VisiblePosition.h index 008d676..505c914 100644 --- a/Source/WebCore/editing/VisiblePosition.h +++ b/Source/WebCore/editing/VisiblePosition.h @@ -26,6 +26,7 @@ #ifndef VisiblePosition_h #define VisiblePosition_h +#include "EditingBoundary.h" #include "Node.h" #include "Position.h" #include "TextDirection.h" @@ -47,8 +48,6 @@ namespace WebCore { class InlineBox; -enum StayInEditableContent { MayLeaveEditableContent, MustStayInEditableContent }; - class VisiblePosition { public: // NOTE: UPSTREAM affinity will be used only if pos is at end of a wrapped line, @@ -69,8 +68,8 @@ public: // FIXME: Change the following functions' parameter from a boolean to StayInEditableContent. // next() and previous() will increment/decrement by a character cluster. - VisiblePosition next(bool stayInEditableContent = false) const; - VisiblePosition previous(bool stayInEditableContent = false) const; + VisiblePosition next(EditingBoundaryCrossingRule = CanCrossEditingBoundary) const; + VisiblePosition previous(EditingBoundaryCrossingRule = CanCrossEditingBoundary) const; VisiblePosition honorEditableBoundaryAtOrBefore(const VisiblePosition&) const; VisiblePosition honorEditableBoundaryAtOrAfter(const VisiblePosition&) const; diff --git a/Source/WebCore/editing/VisibleSelection.cpp b/Source/WebCore/editing/VisibleSelection.cpp index 75531ca..5633c90 100644 --- a/Source/WebCore/editing/VisibleSelection.cpp +++ b/Source/WebCore/editing/VisibleSelection.cpp @@ -89,7 +89,8 @@ VisibleSelection::VisibleSelection(const Range* range, EAffinity affinity) VisibleSelection VisibleSelection::selectionFromContentsOfNode(Node* node) { - return VisibleSelection(firstDeepEditingPositionForNode(node), lastDeepEditingPositionForNode(node), DOWNSTREAM); + ASSERT(!editingIgnoresContent(node)); + return VisibleSelection(firstPositionInNode(node), lastPositionInNode(node), DOWNSTREAM); } void VisibleSelection::setBase(const Position& position) @@ -172,8 +173,8 @@ PassRefPtr<Range> VisibleSelection::toNormalizedRange() const s = s.parentAnchoredEquivalent(); e = e.parentAnchoredEquivalent(); } - - if (s.isNull() || e.isNull()) + + if (!s.containerNode() || !e.containerNode()) return 0; // VisibleSelections are supposed to always be valid. This constructor will ASSERT @@ -208,7 +209,7 @@ static PassRefPtr<Range> makeSearchRange(const Position& pos) Position start(pos.parentAnchoredEquivalent()); searchRange->selectNodeContents(boundary, ec); - searchRange->setStart(start.deprecatedNode(), start.deprecatedEditingOffset(), ec); + searchRange->setStart(start.containerNode(), start.offsetInContainerNode(), ec); ASSERT(!ec); if (ec) @@ -217,9 +218,9 @@ static PassRefPtr<Range> makeSearchRange(const Position& pos) return searchRange.release(); } -bool VisibleSelection::isAll(StayInEditableContent stayInEditableContent) const +bool VisibleSelection::isAll(EditingBoundaryCrossingRule rule) const { - return !shadowTreeRootNode() && visibleStart().previous(stayInEditableContent).isNull() && visibleEnd().next(stayInEditableContent).isNull(); + return !shadowTreeRootNode() && visibleStart().previous(rule).isNull() && visibleEnd().next(rule).isNull(); } void VisibleSelection::appendTrailingWhitespace() @@ -305,7 +306,7 @@ void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(Text // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table. if (isBlock(table)) - end = end.next(true); + end = end.next(CannotCrossEditingBoundary); else end = wordEnd; } @@ -355,7 +356,7 @@ void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity(Text // The paragraph break after the last paragraph in the last cell of a block table ends // at the start of the paragraph after the table, not at the position just after the table. if (isBlock(table)) - end = end.next(true); + end = end.next(CannotCrossEditingBoundary); // There is no parargraph break after the last paragraph in the last cell of an inline table. else end = visibleParagraphEnd; @@ -457,7 +458,7 @@ void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries() Node* startRoot = highestEditableRoot(m_start); Node* endRoot = highestEditableRoot(m_end); - Node* baseEditableAncestor = lowestEditableAncestor(m_base.deprecatedNode()); + Node* baseEditableAncestor = lowestEditableAncestor(m_base.containerNode()); // The base, start and end are all in the same region. No adjustment necessary. if (baseRoot == startRoot && baseRoot == endRoot) @@ -492,19 +493,19 @@ void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries() // The selection ends in editable content or non-editable content inside a different editable ancestor, // move backward until non-editable content inside the same lowest editable ancestor is reached. - Node* endEditableAncestor = lowestEditableAncestor(m_end.deprecatedNode()); + Node* endEditableAncestor = lowestEditableAncestor(m_end.containerNode()); if (endRoot || endEditableAncestor != baseEditableAncestor) { Position p = previousVisuallyDistinctCandidate(m_end); Node* shadowAncestor = endRoot ? endRoot->shadowAncestorNode() : 0; if (p.isNull() && endRoot && (shadowAncestor != endRoot)) - p = lastDeepEditingPositionForNode(shadowAncestor); - while (p.isNotNull() && !(lowestEditableAncestor(p.deprecatedNode()) == baseEditableAncestor && !isEditablePosition(p))) { + p = positionAfterNode(shadowAncestor); + while (p.isNotNull() && !(lowestEditableAncestor(p.containerNode()) == baseEditableAncestor && !isEditablePosition(p))) { Node* root = editableRootForPosition(p); shadowAncestor = root ? root->shadowAncestorNode() : 0; - p = isAtomicNode(p.deprecatedNode()) ? positionInParentBeforeNode(p.deprecatedNode()) : previousVisuallyDistinctCandidate(p); + p = isAtomicNode(p.containerNode()) ? positionInParentBeforeNode(p.containerNode()) : previousVisuallyDistinctCandidate(p); if (p.isNull() && (shadowAncestor != root)) - p = lastDeepEditingPositionForNode(shadowAncestor); + p = positionAfterNode(shadowAncestor); } VisiblePosition previous(p); @@ -522,16 +523,16 @@ void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries() // The selection starts in editable content or non-editable content inside a different editable ancestor, // move forward until non-editable content inside the same lowest editable ancestor is reached. - Node* startEditableAncestor = lowestEditableAncestor(m_start.deprecatedNode()); + Node* startEditableAncestor = lowestEditableAncestor(m_start.containerNode()); if (startRoot || startEditableAncestor != baseEditableAncestor) { Position p = nextVisuallyDistinctCandidate(m_start); Node* shadowAncestor = startRoot ? startRoot->shadowAncestorNode() : 0; if (p.isNull() && startRoot && (shadowAncestor != startRoot)) p = positionBeforeNode(shadowAncestor); - while (p.isNotNull() && !(lowestEditableAncestor(p.deprecatedNode()) == baseEditableAncestor && !isEditablePosition(p))) { + while (p.isNotNull() && !(lowestEditableAncestor(p.containerNode()) == baseEditableAncestor && !isEditablePosition(p))) { Node* root = editableRootForPosition(p); shadowAncestor = root ? root->shadowAncestorNode() : 0; - p = isAtomicNode(p.deprecatedNode()) ? positionInParentAfterNode(p.deprecatedNode()) : nextVisuallyDistinctCandidate(p); + p = isAtomicNode(p.containerNode()) ? positionInParentAfterNode(p.containerNode()) : nextVisuallyDistinctCandidate(p); if (p.isNull() && (shadowAncestor != root)) p = positionBeforeNode(shadowAncestor); } @@ -551,7 +552,7 @@ void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries() } // Correct the extent if necessary. - if (baseEditableAncestor != lowestEditableAncestor(m_extent.deprecatedNode())) + if (baseEditableAncestor != lowestEditableAncestor(m_extent.containerNode())) m_extent = m_baseIsFirst ? m_end : m_start; } diff --git a/Source/WebCore/editing/VisibleSelection.h b/Source/WebCore/editing/VisibleSelection.h index 5d52a08..a352c20 100644 --- a/Source/WebCore/editing/VisibleSelection.h +++ b/Source/WebCore/editing/VisibleSelection.h @@ -79,7 +79,7 @@ public: bool isBaseFirst() const { return m_baseIsFirst; } - bool isAll(StayInEditableContent) const; + bool isAll(EditingBoundaryCrossingRule) const; void appendTrailingWhitespace(); diff --git a/Source/WebCore/editing/WrapContentsInDummySpanCommand.cpp b/Source/WebCore/editing/WrapContentsInDummySpanCommand.cpp index 5fa0b39..51f744c 100644 --- a/Source/WebCore/editing/WrapContentsInDummySpanCommand.cpp +++ b/Source/WebCore/editing/WrapContentsInDummySpanCommand.cpp @@ -64,7 +64,7 @@ void WrapContentsInDummySpanCommand::doUnapply() { ASSERT(m_element); - if (!m_dummySpan || !m_element->isContentEditable()) + if (!m_dummySpan || !m_element->rendererIsEditable()) return; Vector<RefPtr<Node> > children; @@ -84,7 +84,7 @@ void WrapContentsInDummySpanCommand::doReapply() { ASSERT(m_element); - if (!m_dummySpan || !m_element->isContentEditable()) + if (!m_dummySpan || !m_element->rendererIsEditable()) return; executeApply(); diff --git a/Source/WebCore/editing/htmlediting.cpp b/Source/WebCore/editing/htmlediting.cpp index a078d91..564eff6 100644 --- a/Source/WebCore/editing/htmlediting.cpp +++ b/Source/WebCore/editing/htmlediting.cpp @@ -34,6 +34,7 @@ #include "HTMLInterchange.h" #include "HTMLLIElement.h" #include "HTMLNames.h" +#include "HTMLObjectElement.h" #include "HTMLOListElement.h" #include "HTMLUListElement.h" #include "PositionIterator.h" @@ -73,22 +74,20 @@ bool editingIgnoresContent(const Node* node) bool canHaveChildrenForEditing(const Node* node) { - return !node->hasTagName(hrTag) && - !node->hasTagName(brTag) && - !node->hasTagName(imgTag) && - !node->hasTagName(buttonTag) && - !node->hasTagName(inputTag) && - !node->hasTagName(textareaTag) && - !node->hasTagName(objectTag) && - !node->hasTagName(iframeTag) && - !node->hasTagName(embedTag) && - !node->hasTagName(appletTag) && - !node->hasTagName(selectTag) && - !node->hasTagName(datagridTag) && + return !node->isTextNode() + && !node->hasTagName(brTag) + && !node->hasTagName(imgTag) + && !node->hasTagName(inputTag) + && !node->hasTagName(textareaTag) + && (!node->hasTagName(objectTag) || static_cast<const HTMLObjectElement*>(node)->useFallbackContent()) + && !node->hasTagName(iframeTag) + && !node->hasTagName(embedTag) + && !node->hasTagName(appletTag) + && !node->hasTagName(selectTag) #if ENABLE(WML) - !node->hasTagName(WMLNames::doTag) && + && !node->hasTagName(WMLNames::doTag) #endif - !node->isTextNode(); + && ((!node->hasTagName(hrTag) && !node->hasTagName(datagridTag)) || node->hasChildNodes()); } // Compare two positions, taking into account the possibility that one or both @@ -144,7 +143,7 @@ Node* highestEditableRoot(const Position& position) node = highestRoot; while (node) { - if (node->isContentEditable()) + if (node->rendererIsEditable()) highestRoot = node; if (node->hasTagName(bodyTag)) break; @@ -161,7 +160,7 @@ Node* lowestEditableAncestor(Node* node) Node *lowestRoot = 0; while (node) { - if (node->isContentEditable()) + if (node->rendererIsEditable()) return node->rootEditableElement(); if (node->hasTagName(bodyTag)) break; @@ -180,7 +179,7 @@ bool isEditablePosition(const Position& p) if (node->renderer() && node->renderer()->isTable()) node = node->parentNode(); - return node->isContentEditable(); + return node->rendererIsEditable(); } bool isAtUnsplittableElement(const Position& pos) @@ -199,7 +198,7 @@ bool isRichlyEditablePosition(const Position& p) if (node->renderer() && node->renderer()->isTable()) node = node->parentNode(); - return node->isContentRichlyEditable(); + return node->rendererIsRichlyEditable(); } Element* editableRootForPosition(const Position& p) @@ -221,7 +220,7 @@ Element* unsplittableElementForPosition(const Position& p) { // Since enclosingNodeOfType won't search beyond the highest root editable node, // this code works even if the closest table cell was outside of the root editable node. - Element* enclosingCell = static_cast<Element*>(enclosingNodeOfType(p, &isTableCell, true)); + Element* enclosingCell = static_cast<Element*>(enclosingNodeOfType(p, &isTableCell)); if (enclosingCell) return enclosingCell; @@ -277,14 +276,14 @@ Position previousVisuallyDistinctCandidate(const Position& position) VisiblePosition firstEditablePositionAfterPositionInRoot(const Position& position, Node* highestRoot) { // position falls before highestRoot. - if (comparePositions(position, firstDeepEditingPositionForNode(highestRoot)) == -1 && highestRoot->isContentEditable()) - return firstDeepEditingPositionForNode(highestRoot); + if (comparePositions(position, firstPositionInNode(highestRoot)) == -1 && highestRoot->rendererIsEditable()) + return firstPositionInNode(highestRoot); Position p = position; if (Node* shadowAncestor = p.deprecatedNode()->shadowAncestorNode()) if (shadowAncestor != p.deprecatedNode()) - p = lastDeepEditingPositionForNode(shadowAncestor); + p = positionAfterNode(shadowAncestor); while (p.deprecatedNode() && !isEditablePosition(p) && p.deprecatedNode()->isDescendantOf(highestRoot)) p = isAtomicNode(p.deprecatedNode()) ? positionInParentAfterNode(p.deprecatedNode()) : nextVisuallyDistinctCandidate(p); @@ -298,14 +297,15 @@ VisiblePosition firstEditablePositionAfterPositionInRoot(const Position& positio VisiblePosition lastEditablePositionBeforePositionInRoot(const Position& position, Node* highestRoot) { // When position falls after highestRoot, the result is easy to compute. - if (comparePositions(position, lastDeepEditingPositionForNode(highestRoot)) == 1) - return lastDeepEditingPositionForNode(highestRoot); + if (comparePositions(position, lastPositionInNode(highestRoot)) == 1) + return lastPositionInNode(highestRoot); Position p = position; - - if (Node* shadowAncestor = p.deprecatedNode()->shadowAncestorNode()) + + if (Node* shadowAncestor = p.deprecatedNode()->shadowAncestorNode()) { if (shadowAncestor != p.deprecatedNode()) - p = firstDeepEditingPositionForNode(shadowAncestor); + p = firstPositionInOrBeforeNode(shadowAncestor); + } while (p.deprecatedNode() && !isEditablePosition(p) && p.deprecatedNode()->isDescendantOf(highestRoot)) p = isAtomicNode(p.deprecatedNode()) ? positionInParentBeforeNode(p.deprecatedNode()) : previousVisuallyDistinctCandidate(p); @@ -327,9 +327,18 @@ bool isBlock(const Node* node) // FIXME: Pass a position to this function. The enclosing block of [table, x] for example, should be the // block that contains the table and not the table, and this function should be the only one responsible for // knowing about these kinds of special cases. -Node* enclosingBlock(Node* node) +Node* enclosingBlock(Node* node, EditingBoundaryCrossingRule rule) { - return static_cast<Element*>(enclosingNodeOfType(firstPositionInOrBeforeNode(node), isBlock)); + return static_cast<Element*>(enclosingNodeOfType(firstPositionInOrBeforeNode(node), isBlock, rule)); +} + +TextDirection directionOfEnclosingBlock(const Position& position) +{ + Node* enclosingBlockNode = enclosingBlock(position.containerNode()); + if (!enclosingBlockNode) + return LTR; + RenderObject* renderer = enclosingBlockNode->renderer(); + return renderer ? renderer->style()->direction() : LTR; } // This method is used to create positions in the DOM. It returns the maximum valid offset @@ -561,7 +570,7 @@ PassRefPtr<Range> extendRangeToWrappingNodes(PassRefPtr<Range> range, const Rang Node* ancestor = range->commonAncestorContainer(ec);// find the cloeset common ancestor Node* highestNode = 0; // traverse through ancestors as long as they are contained within the range, content-editable, and below rootNode (could be =0). - while (ancestor && ancestor->isContentEditable() && isNodeVisiblyContainedWithin(ancestor, maximumRange) && ancestor != rootNode) { + while (ancestor && ancestor->rendererIsEditable() && isNodeVisiblyContainedWithin(ancestor, maximumRange) && ancestor != rootNode) { highestNode = ancestor; ancestor = ancestor->parentNode(); } @@ -592,7 +601,7 @@ Node* enclosingNodeWithTag(const Position& p, const QualifiedName& tagName) Node* root = highestEditableRoot(p); for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) { - if (root && !n->isContentEditable()) + if (root && !n->rendererIsEditable()) continue; if (n->hasTagName(tagName)) return n; @@ -603,18 +612,20 @@ Node* enclosingNodeWithTag(const Position& p, const QualifiedName& tagName) return 0; } -Node* enclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), bool onlyReturnEditableNodes) +Node* enclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule) { + // FIXME: support CanSkipCrossEditingBoundary + ASSERT(rule == CanCrossEditingBoundary || rule == CannotCrossEditingBoundary); if (p.isNull()) return 0; - Node* root = highestEditableRoot(p); + Node* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(p) : 0; for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) { // Don't return a non-editable node if the input position was editable, since // the callers from editing will no doubt want to perform editing inside the returned node. - if (root && !n->isContentEditable() && onlyReturnEditableNodes) + if (root && !n->rendererIsEditable()) continue; - if ((*nodeIsOfType)(n)) + if (nodeIsOfType(n)) return n; if (n == root) return 0; @@ -623,12 +634,14 @@ Node* enclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), return 0; } -Node* highestEnclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*)) +Node* highestEnclosingNodeOfType(const Position& p, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule rule) { Node* highest = 0; - Node* root = highestEditableRoot(p); + Node* root = rule == CannotCrossEditingBoundary ? highestEditableRoot(p) : 0; for (Node* n = p.deprecatedNode(); n; n = n->parentNode()) { - if ((*nodeIsOfType)(n)) + if (root && !n->rendererIsEditable()) + continue; + if (nodeIsOfType(n)) highest = n; if (n == root) break; @@ -721,8 +734,8 @@ Node* enclosingEmptyListItem(const VisiblePosition& visiblePos) if (!listChildNode || !isStartOfParagraph(visiblePos) || !isEndOfParagraph(visiblePos)) return 0; - VisiblePosition firstInListChild(firstDeepEditingPositionForNode(listChildNode)); - VisiblePosition lastInListChild(lastDeepEditingPositionForNode(listChildNode)); + VisiblePosition firstInListChild(firstPositionInOrBeforeNode(listChildNode)); + VisiblePosition lastInListChild(lastPositionInOrAfterNode(listChildNode)); if (firstInListChild != visiblePos || lastInListChild != visiblePos) return 0; @@ -754,7 +767,7 @@ bool canMergeLists(Element* firstList, Element* secondList) return false; return firstList->hasTagName(secondList->tagQName())// make sure the list types match (ol vs. ul) - && firstList->isContentEditable() && secondList->isContentEditable()// both lists are editable + && firstList->rendererIsEditable() && secondList->rendererIsEditable() // both lists are editable && firstList->rootEditableElement() == secondList->rootEditableElement()// don't cross editing boundaries && isVisiblyAdjacent(positionInParentAfterNode(firstList), positionInParentBeforeNode(secondList)); // Make sure there is no visible content between this li and the previous list @@ -932,15 +945,6 @@ bool isNodeRendered(const Node *node) return renderer->style()->visibility() == VISIBLE; } -Node *nearestMailBlockquote(const Node *node) -{ - for (Node *n = const_cast<Node *>(node); n; n = n->parentNode()) { - if (isMailBlockquote(n)) - return n; - } - return 0; -} - unsigned numEnclosingMailBlockquotes(const Position& p) { unsigned num = 0; @@ -1016,7 +1020,7 @@ VisibleSelection selectionForParagraphIteration(const VisibleSelection& original // (a table is itself a paragraph). if (Node* table = isFirstPositionAfterTable(endOfSelection)) if (startOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(table)) - newSelection = VisibleSelection(startOfSelection, endOfSelection.previous(true)); + newSelection = VisibleSelection(startOfSelection, endOfSelection.previous(CannotCrossEditingBoundary)); // If the start of the selection to modify is just before a table, // and if the end of the selection is inside that table, then the first paragraph @@ -1024,7 +1028,7 @@ VisibleSelection selectionForParagraphIteration(const VisibleSelection& original // containing the table itself. if (Node* table = isLastPositionBeforeTable(startOfSelection)) if (endOfSelection.deepEquivalent().deprecatedNode()->isDescendantOf(table)) - newSelection = VisibleSelection(startOfSelection.next(true), endOfSelection); + newSelection = VisibleSelection(startOfSelection.next(CannotCrossEditingBoundary), endOfSelection); return newSelection; } diff --git a/Source/WebCore/editing/htmlediting.h b/Source/WebCore/editing/htmlediting.h index b71e879..cb2b2a4 100644 --- a/Source/WebCore/editing/htmlediting.h +++ b/Source/WebCore/editing/htmlediting.h @@ -26,9 +26,11 @@ #ifndef htmlediting_h #define htmlediting_h +#include "EditingBoundary.h" #include "ExceptionCode.h" #include "HTMLNames.h" #include "Position.h" +#include "TextDirection.h" #include <wtf/Forward.h> #include <wtf/unicode/CharacterNames.h> @@ -54,18 +56,17 @@ class VisibleSelection; Node* highestAncestor(Node*); Node* highestEditableRoot(const Position&); -Node* highestEnclosingNodeOfType(const Position&, bool (*nodeIsOfType)(const Node*)); +Node* highestEnclosingNodeOfType(const Position&, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule = CannotCrossEditingBoundary); Node* lowestEditableAncestor(Node*); -Node* enclosingBlock(Node*); +Node* enclosingBlock(Node*, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); Node* enclosingTableCell(const Position&); Node* enclosingEmptyListItem(const VisiblePosition&); Node* enclosingAnchorElement(const Position&); Node* enclosingNodeWithTag(const Position&, const QualifiedName&); -Node* enclosingNodeOfType(const Position&, bool (*nodeIsOfType)(const Node*), bool onlyReturnEditableNodes = true); +Node* enclosingNodeOfType(const Position&, bool (*nodeIsOfType)(const Node*), EditingBoundaryCrossingRule = CannotCrossEditingBoundary); Node* tabSpanNode(const Node*); -Node* nearestMailBlockquote(const Node*); Node* isLastPositionBeforeTable(const VisiblePosition&); Node* isFirstPositionAfterTable(const VisiblePosition&); @@ -96,6 +97,8 @@ bool isNodeVisiblyContainedWithin(Node*, const Range*); bool isRenderedAsNonInlineTableImageOrHR(const Node*); bool isNodeInTextFormControl(Node* node); +TextDirection directionOfEnclosingBlock(const Position&); + // ------------------------------------------------------------------------- // Position // ------------------------------------------------------------------------- @@ -115,34 +118,18 @@ Position positionOutsideContainingSpecialElement(const Position&, Node** contain inline Position firstPositionInOrBeforeNode(Node* node) { + if (!node) + return Position(); return editingIgnoresContent(node) ? positionBeforeNode(node) : firstPositionInNode(node); } inline Position lastPositionInOrAfterNode(Node* node) { + if (!node) + return Position(); return editingIgnoresContent(node) ? positionAfterNode(node) : lastPositionInNode(node); } -// Position creation functions are inline to prevent ref-churn. -// Other Position creation functions are in Position.h -// but these depend on lastOffsetForEditing which is defined in htmlediting.h. - -// NOTE: first/lastDeepEditingPositionForNode return legacy editing positions (like [img, 0]) -// for elements which editing ignores. The rest of the editing code will treat [img, 0] -// as "the last position before the img". -// New code should use the creation functions in Position.h instead. -inline Position firstDeepEditingPositionForNode(Node* anchorNode) -{ - ASSERT(anchorNode); - return Position(anchorNode, 0); -} - -inline Position lastDeepEditingPositionForNode(Node* anchorNode) -{ - ASSERT(anchorNode); - return Position(anchorNode, lastOffsetForEditing(anchorNode)); -} - // comparision functions on Position int comparePositions(const Position&, const Position&); @@ -237,7 +224,7 @@ inline bool isWhitespace(UChar c) { return c == noBreakSpace || c == ' ' || c == '\n' || c == '\t'; } -String stringWithRebalancedWhitespace(const String&, bool, bool); +String stringWithRebalancedWhitespace(const String&, bool startIsStartOfParagraph, bool endIsEndOfParagraph); const String& nonBreakingSpaceString(); } diff --git a/Source/WebCore/editing/mac/EditorMac.mm b/Source/WebCore/editing/mac/EditorMac.mm index 4c617c0..60bfe6c 100644 --- a/Source/WebCore/editing/mac/EditorMac.mm +++ b/Source/WebCore/editing/mac/EditorMac.mm @@ -216,4 +216,13 @@ void Editor::writeSelectionToPasteboard(const String& pasteboardName, const Vect Pasteboard::writeSelection([NSPasteboard pasteboardWithName:pasteboardName], types.get(), selectedRange().get(), true, m_frame); } +void Editor::readSelectionFromPasteboard(const String& pasteboardName) +{ + Pasteboard pasteboard([NSPasteboard pasteboardWithName:pasteboardName]); + if (m_frame->selection()->isContentRichlyEditable()) + pasteWithPasteboard(&pasteboard, true); + else + pasteAsPlainTextWithPasteboard(&pasteboard); +} + } // namespace WebCore diff --git a/Source/WebCore/editing/markup.cpp b/Source/WebCore/editing/markup.cpp index 9d97b3f..316cec7 100644 --- a/Source/WebCore/editing/markup.cpp +++ b/Source/WebCore/editing/markup.cpp @@ -488,10 +488,8 @@ static Node* highestAncestorToWrapMarkup(const Range* range, Node* fullySelected specialCommonAncestor = ancestorToRetainStructureAndAppearance(commonAncestor); // Retain the Mail quote level by including all ancestor mail block quotes. - for (Node* ancestor = range->firstNode(); ancestor; ancestor = ancestor->parentNode()) { - if (isMailBlockquote(ancestor)) - specialCommonAncestor = ancestor; - } + if (Node* highestMailBlockquote = highestEnclosingNodeOfType(firstPositionInOrBeforeNode(range->firstNode()), isMailBlockquote, CanCrossEditingBoundary)) + specialCommonAncestor = highestMailBlockquote; } Node* checkAncestor = specialCommonAncestor ? specialCommonAncestor : commonAncestor; @@ -634,7 +632,7 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc // Styles that Mail blockquotes contribute should only be placed on the Mail blockquote, to help // us differentiate those styles from ones that the user has applied. This helps us // get the color of content pasted into blockquotes right. - style->removeStyleAddedByNode(nearestMailBlockquote(parentOfLastClosed)); + style->removeStyleAddedByNode(enclosingNodeOfType(firstPositionInNode(parentOfLastClosed), isMailBlockquote, CanCrossEditingBoundary)); // Document default styles will be added on another wrapper span. if (document && document->documentElement()) diff --git a/Source/WebCore/editing/visible_units.cpp b/Source/WebCore/editing/visible_units.cpp index 0baacf2..c9ca8c0 100644 --- a/Source/WebCore/editing/visible_units.cpp +++ b/Source/WebCore/editing/visible_units.cpp @@ -126,6 +126,7 @@ static VisiblePosition previousBoundary(const VisiblePosition& c, BoundarySearch // Use the character iterator to translate the next value into a DOM position. BackwardsCharacterIterator charIt(searchRange.get()); charIt.advance(string.size() - suffixLength - next); + // FIXME: charIt can get out of shadow host. return VisiblePosition(charIt.range()->endPosition(), DOWNSTREAM); } @@ -377,8 +378,8 @@ static VisiblePosition startPositionForLine(const VisiblePosition& c) startBox = startBox->nextLeafChild(); } - VisiblePosition visPos = startBox->isInlineTextBox() ? VisiblePosition(Position(startNode, static_cast<InlineTextBox *>(startBox)->start(), Position::PositionIsOffsetInAnchor), DOWNSTREAM) - : VisiblePosition(positionBeforeNode(startNode), DOWNSTREAM); + VisiblePosition visPos = startNode->isTextNode() ? VisiblePosition(Position(startNode, static_cast<InlineTextBox *>(startBox)->start(), Position::PositionIsOffsetInAnchor), DOWNSTREAM) + : VisiblePosition(positionBeforeNode(startNode), DOWNSTREAM); return positionAvoidingFirstPositionInTable(visPos); } @@ -476,10 +477,10 @@ bool isEndOfLine(const VisiblePosition &p) // The first leaf before node that has the same editability as node. static Node* previousLeafWithSameEditability(Node* node) { - bool editable = node->isContentEditable(); + bool editable = node->rendererIsEditable(); Node* n = node->previousLeafNode(); while (n) { - if (editable == n->isContentEditable()) + if (editable == n->rendererIsEditable()) return n; n = n->previousLeafNode(); } @@ -571,18 +572,20 @@ VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int // Could not find a previous line. This means we must already be on the first line. // Move to the start of the content in this block, which effectively moves us // to the start of the line we're on. - Element* rootElement = node->isContentEditable() ? node->rootEditableElement() : node->document()->documentElement(); + Element* rootElement = node->rendererIsEditable() ? node->rootEditableElement() : node->document()->documentElement(); + if (!rootElement) + return VisiblePosition(); return VisiblePosition(firstPositionInNode(rootElement), DOWNSTREAM); } static Node* nextLeafWithSameEditability(Node* node, int offset) { - bool editable = node->isContentEditable(); + bool editable = node->rendererIsEditable(); ASSERT(offset >= 0); Node* child = node->childNode(offset); Node* n = child ? child->nextLeafNode() : node->lastDescendant()->nextLeafNode(); while (n) { - if (editable == n->isContentEditable()) + if (editable == n->rendererIsEditable()) return n; n = n->nextLeafNode(); } @@ -594,10 +597,10 @@ static Node* nextLeafWithSameEditability(Node* node) if (!node) return 0; - bool editable = node->isContentEditable(); + bool editable = node->rendererIsEditable(); Node* n = node->nextLeafNode(); while (n) { - if (editable == n->isContentEditable()) + if (editable == n->rendererIsEditable()) return n; n = n->nextLeafNode(); } @@ -676,7 +679,9 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int x) // Could not find a next line. This means we must already be on the last line. // Move to the end of the content in this block, which effectively moves us // to the end of the line we're on. - Element* rootElement = node->isContentEditable() ? node->rootEditableElement() : node->document()->documentElement(); + Element* rootElement = node->rendererIsEditable() ? node->rootEditableElement() : node->document()->documentElement(); + if (!rootElement) + return VisiblePosition(); return VisiblePosition(lastPositionInNode(rootElement), DOWNSTREAM); } @@ -743,18 +748,25 @@ VisiblePosition startOfParagraph(const VisiblePosition& c, EditingBoundaryCrossi return VisiblePosition(); if (isRenderedAsNonInlineTableImageOrHR(startNode)) - return firstDeepEditingPositionForNode(startNode); + return positionBeforeNode(startNode); Node* startBlock = enclosingBlock(startNode); - Node *node = startNode; + Node* node = startNode; + Node* highestRoot = highestEditableRoot(p); int offset = p.deprecatedEditingOffset(); Position::AnchorType type = p.anchorType(); - Node *n = startNode; + Node* n = startNode; while (n) { - if (boundaryCrossingRule == CannotCrossEditingBoundary && n->isContentEditable() != startNode->isContentEditable()) + if (boundaryCrossingRule == CannotCrossEditingBoundary && n->rendererIsEditable() != startNode->rendererIsEditable()) break; + if (boundaryCrossingRule == CanSkipOverEditingBoundary) { + while (n && n->rendererIsEditable() != startNode->rendererIsEditable()) + n = n->traversePreviousNodePostOrder(startBlock); + if (!n || !n->isDescendantOf(highestRoot)) + break; + } RenderObject *r = n->renderer(); if (!r) { n = n->traversePreviousNodePostOrder(startBlock); @@ -807,19 +819,27 @@ VisiblePosition endOfParagraph(const VisiblePosition &c, EditingBoundaryCrossing Node* startNode = p.deprecatedNode(); if (isRenderedAsNonInlineTableImageOrHR(startNode)) - return lastDeepEditingPositionForNode(startNode); + return positionAfterNode(startNode); Node* startBlock = enclosingBlock(startNode); - Node *stayInsideBlock = startBlock; + Node* stayInsideBlock = startBlock; - Node *node = startNode; + Node* node = startNode; + Node* highestRoot = highestEditableRoot(p); int offset = p.deprecatedEditingOffset(); Position::AnchorType type = p.anchorType(); - Node *n = startNode; + Node* n = startNode; while (n) { - if (boundaryCrossingRule == CannotCrossEditingBoundary && n->isContentEditable() != startNode->isContentEditable()) + if (boundaryCrossingRule == CannotCrossEditingBoundary && n->rendererIsEditable() != startNode->rendererIsEditable()) break; + if (boundaryCrossingRule == CanSkipOverEditingBoundary) { + while (n && n->rendererIsEditable() != startNode->rendererIsEditable()) + n = n->traverseNextNode(stayInsideBlock); + if (!n || !n->isDescendantOf(highestRoot)) + break; + } + RenderObject *r = n->renderer(); if (!r) { n = n->traverseNextNode(stayInsideBlock); @@ -862,20 +882,21 @@ VisiblePosition endOfParagraph(const VisiblePosition &c, EditingBoundaryCrossing return VisiblePosition(Position(node, type), DOWNSTREAM); } +// FIXME: isStartOfParagraph(startOfNextParagraph(pos)) is not always true VisiblePosition startOfNextParagraph(const VisiblePosition& visiblePosition) { - VisiblePosition paragraphEnd(endOfParagraph(visiblePosition)); - VisiblePosition afterParagraphEnd(paragraphEnd.next(true)); + VisiblePosition paragraphEnd(endOfParagraph(visiblePosition, CanSkipOverEditingBoundary)); + VisiblePosition afterParagraphEnd(paragraphEnd.next(CannotCrossEditingBoundary)); // The position after the last position in the last cell of a table // is not the start of the next paragraph. if (isFirstPositionAfterTable(afterParagraphEnd)) - return afterParagraphEnd.next(true); + return afterParagraphEnd.next(CannotCrossEditingBoundary); return afterParagraphEnd; } -bool inSameParagraph(const VisiblePosition &a, const VisiblePosition &b) +bool inSameParagraph(const VisiblePosition &a, const VisiblePosition &b, EditingBoundaryCrossingRule boundaryCrossingRule) { - return a.isNotNull() && startOfParagraph(a) == startOfParagraph(b); + return a.isNotNull() && startOfParagraph(a, boundaryCrossingRule) == startOfParagraph(b, boundaryCrossingRule); } bool isStartOfParagraph(const VisiblePosition &pos, EditingBoundaryCrossingRule boundaryCrossingRule) @@ -914,41 +935,37 @@ VisiblePosition nextParagraphPosition(const VisiblePosition& p, int x) // --------- -VisiblePosition startOfBlock(const VisiblePosition &c) +VisiblePosition startOfBlock(const VisiblePosition& visiblePosition, EditingBoundaryCrossingRule rule) { - Position p = c.deepEquivalent(); - Node* startNode = p.deprecatedNode(); - if (!startNode) + Position position = visiblePosition.deepEquivalent(); + Node* startBlock; + if (!position.containerNode() || !(startBlock = enclosingBlock(position.containerNode(), rule))) return VisiblePosition(); - return VisiblePosition(firstPositionInNode(startNode->enclosingBlockFlowElement()), DOWNSTREAM); + return firstPositionInNode(startBlock); } -VisiblePosition endOfBlock(const VisiblePosition &c) +VisiblePosition endOfBlock(const VisiblePosition& visiblePosition, EditingBoundaryCrossingRule rule) { - Position p = c.deepEquivalent(); - - Node* startNode = p.deprecatedNode(); - if (!startNode) + Position position = visiblePosition.deepEquivalent(); + Node* endBlock; + if (!position.containerNode() || !(endBlock = enclosingBlock(position.containerNode(), rule))) return VisiblePosition(); - - Node *startBlock = startNode->enclosingBlockFlowElement(); - - return VisiblePosition(lastPositionInNode(startBlock), VP_DEFAULT_AFFINITY); + return lastPositionInNode(endBlock); } bool inSameBlock(const VisiblePosition &a, const VisiblePosition &b) { - return !a.isNull() && enclosingBlockFlowElement(a) == enclosingBlockFlowElement(b); + return !a.isNull() && enclosingBlock(a.deepEquivalent().containerNode()) == enclosingBlock(b.deepEquivalent().containerNode()); } bool isStartOfBlock(const VisiblePosition &pos) { - return pos.isNotNull() && pos == startOfBlock(pos); + return pos.isNotNull() && pos == startOfBlock(pos, CanCrossEditingBoundary); } bool isEndOfBlock(const VisiblePosition &pos) { - return pos.isNotNull() && pos == endOfBlock(pos); + return pos.isNotNull() && pos == endOfBlock(pos, CanCrossEditingBoundary); } // --------- @@ -1012,7 +1029,7 @@ VisiblePosition startOfEditableContent(const VisiblePosition& visiblePosition) if (!highestRoot) return VisiblePosition(); - return firstDeepEditingPositionForNode(highestRoot); + return firstPositionInNode(highestRoot); } VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition) @@ -1021,88 +1038,7 @@ VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition) if (!highestRoot) return VisiblePosition(); - return lastDeepEditingPositionForNode(highestRoot); -} - -static void getLeafBoxesInLogicalOrder(RootInlineBox* rootBox, Vector<InlineBox*>& leafBoxesInLogicalOrder) -{ - unsigned char minLevel = 128; - unsigned char maxLevel = 0; - unsigned count = 0; - InlineBox* r = rootBox->firstLeafChild(); - // First find highest and lowest levels, - // and initialize leafBoxesInLogicalOrder with the leaf boxes in visual order. - while (r) { - if (r->bidiLevel() > maxLevel) - maxLevel = r->bidiLevel(); - if (r->bidiLevel() < minLevel) - minLevel = r->bidiLevel(); - leafBoxesInLogicalOrder.append(r); - r = r->nextLeafChild(); - ++count; - } - - if (rootBox->renderer()->style()->visuallyOrdered()) - return; - // Reverse of reordering of the line (L2 according to Bidi spec): - // L2. From the highest level found in the text to the lowest odd level on each line, - // reverse any contiguous sequence of characters that are at that level or higher. - - // Reversing the reordering of the line is only done up to the lowest odd level. - if (!(minLevel % 2)) - minLevel++; - - InlineBox** end = leafBoxesInLogicalOrder.end(); - while (minLevel <= maxLevel) { - InlineBox** iter = leafBoxesInLogicalOrder.begin(); - while (iter != end) { - while (iter != end) { - if ((*iter)->bidiLevel() >= minLevel) - break; - ++iter; - } - InlineBox** first = iter; - while (iter != end) { - if ((*iter)->bidiLevel() < minLevel) - break; - ++iter; - } - InlineBox** last = iter; - std::reverse(first, last); - } - ++minLevel; - } -} - -static void getLogicalStartBoxAndNode(RootInlineBox* rootBox, InlineBox*& startBox, Node*& startNode) -{ - Vector<InlineBox*> leafBoxesInLogicalOrder; - getLeafBoxesInLogicalOrder(rootBox, leafBoxesInLogicalOrder); - startBox = 0; - startNode = 0; - for (size_t i = 0; i < leafBoxesInLogicalOrder.size(); ++i) { - startBox = leafBoxesInLogicalOrder[i]; - startNode = startBox->renderer()->node(); - if (startNode) - return; - } -} - -static void getLogicalEndBoxAndNode(RootInlineBox* rootBox, InlineBox*& endBox, Node*& endNode) -{ - Vector<InlineBox*> leafBoxesInLogicalOrder; - getLeafBoxesInLogicalOrder(rootBox, leafBoxesInLogicalOrder); - endBox = 0; - endNode = 0; - // Generated content (e.g. list markers and CSS :before and :after - // pseudoelements) have no corresponding DOM element, and so cannot be - // represented by a VisiblePosition. Use whatever precedes instead. - for (size_t i = leafBoxesInLogicalOrder.size(); i > 0; --i) { - endBox = leafBoxesInLogicalOrder[i - 1]; - endNode = endBox->renderer()->node(); - if (endNode) - return; - } + return lastPositionInNode(highestRoot); } static VisiblePosition logicalStartPositionForLine(const VisiblePosition& c) @@ -1122,8 +1058,7 @@ static VisiblePosition logicalStartPositionForLine(const VisiblePosition& c) } InlineBox* logicalStartBox; - Node* logicalStartNode; - getLogicalStartBoxAndNode(rootBox, logicalStartBox, logicalStartNode); + Node* logicalStartNode = rootBox->getLogicalStartBoxWithNode(logicalStartBox); if (!logicalStartNode) return VisiblePosition(); @@ -1158,8 +1093,8 @@ static VisiblePosition logicalEndPositionForLine(const VisiblePosition& c) } InlineBox* logicalEndBox; - Node* logicalEndNode; - getLogicalEndBoxAndNode(rootBox, logicalEndBox, logicalEndNode); + Node* logicalEndNode = rootBox->getLogicalEndBoxWithNode(logicalEndBox); + if (!logicalEndNode) return VisiblePosition(); diff --git a/Source/WebCore/editing/visible_units.h b/Source/WebCore/editing/visible_units.h index 167bd2c..18b9665 100644 --- a/Source/WebCore/editing/visible_units.h +++ b/Source/WebCore/editing/visible_units.h @@ -32,6 +32,7 @@ namespace WebCore { +class RootInlineBox; class VisiblePosition; enum EWordSide { RightWordIfOnBoundary = false, LeftWordIfOnBoundary = true }; @@ -70,11 +71,11 @@ VisiblePosition previousParagraphPosition(const VisiblePosition &, int x); VisiblePosition nextParagraphPosition(const VisiblePosition &, int x); bool isStartOfParagraph(const VisiblePosition &, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); bool isEndOfParagraph(const VisiblePosition &, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); -bool inSameParagraph(const VisiblePosition &, const VisiblePosition &); +bool inSameParagraph(const VisiblePosition &, const VisiblePosition &, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); // blocks (true paragraphs; line break elements don't break blocks) -VisiblePosition startOfBlock(const VisiblePosition &); -VisiblePosition endOfBlock(const VisiblePosition &); +VisiblePosition startOfBlock(const VisiblePosition &, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); +VisiblePosition endOfBlock(const VisiblePosition &, EditingBoundaryCrossingRule = CannotCrossEditingBoundary); bool inSameBlock(const VisiblePosition &, const VisiblePosition &); bool isStartOfBlock(const VisiblePosition &); bool isEndOfBlock(const VisiblePosition &); |