diff options
author | Feng Qian <> | 2009-04-10 18:11:29 -0700 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-04-10 18:11:29 -0700 |
commit | 8f72e70a9fd78eec56623b3a62e68f16b7b27e28 (patch) | |
tree | 181bf9a400c30a1bf34ea6d72560e8d00111d549 /WebCore/editing | |
parent | 7ed56f225e0ade046e1c2178977f72b2d896f196 (diff) | |
download | external_webkit-8f72e70a9fd78eec56623b3a62e68f16b7b27e28.zip external_webkit-8f72e70a9fd78eec56623b3a62e68f16b7b27e28.tar.gz external_webkit-8f72e70a9fd78eec56623b3a62e68f16b7b27e28.tar.bz2 |
AI 145796: Land the WebKit merge @r42026.
Automated import of CL 145796
Diffstat (limited to 'WebCore/editing')
42 files changed, 1482 insertions, 868 deletions
diff --git a/WebCore/editing/ApplyStyleCommand.cpp b/WebCore/editing/ApplyStyleCommand.cpp index eca2999..d43cc81 100644 --- a/WebCore/editing/ApplyStyleCommand.cpp +++ b/WebCore/editing/ApplyStyleCommand.cpp @@ -32,6 +32,8 @@ #include "CSSPropertyNames.h" #include "CSSValueKeywords.h" #include "Document.h" +#include "Editor.h" +#include "Frame.h" #include "HTMLElement.h" #include "HTMLInterchange.h" #include "HTMLNames.h" @@ -50,12 +52,7 @@ using namespace HTMLNames; class StyleChange { public: - enum ELegacyHTMLStyles { DoNotUseLegacyHTMLStyles, UseLegacyHTMLStyles }; - - explicit StyleChange(CSSStyleDeclaration*, ELegacyHTMLStyles usesLegacyStyles=UseLegacyHTMLStyles); - StyleChange(CSSStyleDeclaration*, const Position&, ELegacyHTMLStyles usesLegacyStyles=UseLegacyHTMLStyles); - - static ELegacyHTMLStyles styleModeForParseMode(bool); + explicit StyleChange(CSSStyleDeclaration*, const Position&); String cssStyle() const { return m_cssStyle; } bool applyBold() const { return m_applyBold; } @@ -70,8 +67,6 @@ public: String fontFace() { return m_applyFontFace; } String fontSize() { return m_applyFontSize; } - bool usesLegacyStyles() const { return m_usesLegacyStyles; } - private: void init(PassRefPtr<CSSStyleDeclaration>, const Position&); bool checkForLegacyHTMLStyleChange(const CSSProperty*); @@ -85,33 +80,26 @@ private: String m_applyFontColor; String m_applyFontFace; String m_applyFontSize; - bool m_usesLegacyStyles; }; - -StyleChange::StyleChange(CSSStyleDeclaration* style, ELegacyHTMLStyles usesLegacyStyles) +StyleChange::StyleChange(CSSStyleDeclaration* style, const Position& position) : m_applyBold(false) , m_applyItalic(false) , m_applySubscript(false) , m_applySuperscript(false) - , m_usesLegacyStyles(usesLegacyStyles) -{ - init(style, Position()); -} - -StyleChange::StyleChange(CSSStyleDeclaration* style, const Position& position, ELegacyHTMLStyles usesLegacyStyles) - : m_applyBold(false) - , m_applyItalic(false) - , m_applySubscript(false) - , m_applySuperscript(false) - , m_usesLegacyStyles(usesLegacyStyles) { init(style, position); } -void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position &position) +void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position& position) { + Document* document = position.node() ? position.node()->document() : 0; + if (!document || !document->frame()) + return; + + bool useHTMLFormattingTags = !document->frame()->editor()->shouldStyleWithCSS(); + RefPtr<CSSMutableStyleDeclaration> mutableStyle = style->makeMutable(); String styleText(""); @@ -131,7 +119,7 @@ void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position &po continue; // If needed, figure out if this change is a legacy HTML style change. - if (m_usesLegacyStyles && checkForLegacyHTMLStyleChange(property)) + if (useHTMLFormattingTags && checkForLegacyHTMLStyleChange(property)) continue; if (property->id() == CSSPropertyDirection) { @@ -160,11 +148,6 @@ void StyleChange::init(PassRefPtr<CSSStyleDeclaration> style, const Position &po m_cssStyle = styleText.stripWhiteSpace(); } -StyleChange::ELegacyHTMLStyles StyleChange::styleModeForParseMode(bool isQuirksMode) -{ - return isQuirksMode ? UseLegacyHTMLStyles : DoNotUseLegacyHTMLStyles; -} - // This function is the mapping from CSS styles to styling tags (like font-weight: bold to <b>) bool StyleChange::checkForLegacyHTMLStyleChange(const CSSProperty* property) { @@ -359,7 +342,7 @@ void ApplyStyleCommand::updateStartEnd(const Position& newStart, const Position& if (!m_useEndingSelection && (newStart != m_start || newEnd != m_end)) m_useEndingSelection = true; - setEndingSelection(Selection(newStart, newEnd, VP_DEFAULT_AFFINITY)); + setEndingSelection(VisibleSelection(newStart, newEnd, VP_DEFAULT_AFFINITY)); m_start = newStart; m_end = newEnd; } @@ -442,7 +425,7 @@ void ApplyStyleCommand::applyBlockStyle(CSSMutableStyleDeclaration *style) VisiblePosition nextParagraphStart(endOfParagraph(paragraphStart).next()); VisiblePosition beyondEnd(endOfParagraph(visibleEnd).next()); while (paragraphStart.isNotNull() && paragraphStart != beyondEnd) { - StyleChange styleChange(style, paragraphStart.deepEquivalent(), StyleChange::styleModeForParseMode(document()->inCompatMode())); + StyleChange styleChange(style, paragraphStart.deepEquivalent()); if (styleChange.cssStyle().length() > 0 || m_removeOnly) { RefPtr<Node> block = enclosingBlock(paragraphStart.deepEquivalent().node()); RefPtr<Node> newBlock = moveParagraphContentsToNewBlockIfNecessary(paragraphStart.deepEquivalent()); @@ -537,7 +520,7 @@ void ApplyStyleCommand::applyRelativeFontStyleChange(CSSMutableStyleDeclaration start = start.upstream(); // Move upstream to ensure we do not add redundant spans. Node *startNode = start.node(); - if (startNode->isTextNode() && start.offset() >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters. + if (startNode->isTextNode() && start.m_offset >= caretMaxOffset(startNode)) // Move out of text node if range does not include its characters. startNode = startNode->traverseNextNode(); // Store away font size before making any changes to the document. @@ -885,17 +868,17 @@ void ApplyStyleCommand::applyInlineStyleToRange(CSSMutableStyleDeclaration* styl bool rangeIsEmpty = false; - if (start.offset() >= caretMaxOffset(start.node())) { + if (start.m_offset >= caretMaxOffset(start.node())) { node = node->traverseNextNode(); Position newStart = Position(node, 0); - if (Range::compareBoundaryPoints(end, newStart) < 0) + if (!node || Range::compareBoundaryPoints(end, newStart) < 0) rangeIsEmpty = true; } if (!rangeIsEmpty) { // pastEndNode is the node after the last fully selected node. Node* pastEndNode = end.node(); - if (end.offset() >= caretMaxOffset(end.node())) + if (end.m_offset >= caretMaxOffset(end.node())) pastEndNode = end.node()->traverseNextSibling(); // FIXME: Callers should perform this operation on a Range that includes the br // if they want style applied to the empty line. @@ -958,7 +941,8 @@ bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclaration* style, HTMLE for (CSSMutableStyleDeclaration::const_iterator it = style->begin(); it != end; ++it) { switch ((*it).id()) { case CSSPropertyFontWeight: - if (elem->hasLocalName(bTag)) + // IE inserts "strong" tags for execCommand("bold"), so we remove them, even though they're not strictly presentational + if (elem->hasLocalName(bTag) || elem->hasLocalName(strongTag)) return true; break; case CSSPropertyVerticalAlign: @@ -966,7 +950,8 @@ bool ApplyStyleCommand::isHTMLStyleNode(CSSMutableStyleDeclaration* style, HTMLE return true; break; case CSSPropertyFontStyle: - if (elem->hasLocalName(iTag)) + // IE inserts "em" tags for execCommand("italic"), so we remove them, even though they're not strictly presentational + if (elem->hasLocalName(iTag) || elem->hasLocalName(emTag)) return true; } } @@ -1145,7 +1130,7 @@ void ApplyStyleCommand::applyTextDecorationStyle(Node *node, CSSMutableStyleDecl HTMLElement *element = static_cast<HTMLElement *>(node); - StyleChange styleChange(style, Position(element, 0), StyleChange::styleModeForParseMode(document()->inCompatMode())); + StyleChange styleChange(style, Position(element, 0)); if (styleChange.cssStyle().length() > 0) { String cssText = styleChange.cssStyle(); CSSMutableStyleDeclaration *decl = element->inlineStyleDecl(); @@ -1234,13 +1219,13 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> Position s = start; Position e = end; - Node *node = start.node(); + Node* node = start.node(); while (node) { - Node *next = node->traverseNextNode(); + Node* next = node->traverseNextNode(); if (node->isHTMLElement() && nodeFullySelected(node, start, end)) { - HTMLElement *elem = static_cast<HTMLElement *>(node); - Node *prev = elem->traversePreviousNodePostOrder(); - Node *next = elem->traverseNextNode(); + HTMLElement* elem = static_cast<HTMLElement*>(node); + Node* prev = elem->traversePreviousNodePostOrder(); + Node* next = elem->traverseNextNode(); if (m_styledInlineElement && elem->hasTagName(m_styledInlineElement->tagQName())) removeNodePreservingChildren(elem); if (isHTMLStyleNode(style.get(), elem)) @@ -1254,14 +1239,14 @@ void ApplyStyleCommand::removeInlineStyle(PassRefPtr<CSSMutableStyleDeclaration> if (s.node() == elem) { // Since elem must have been fully selected, and it is at the start // of the selection, it is clear we can set the new s offset to 0. - ASSERT(s.offset() <= caretMinOffset(s.node())); + ASSERT(s.m_offset <= caretMinOffset(s.node())); s = Position(next, 0); } if (e.node() == elem) { // Since elem must have been fully selected, and it is at the end // of the selection, it is clear we can set the new e offset to // the max range offset of prev. - ASSERT(e.offset() >= maxRangeOffset(e.node())); + ASSERT(e.m_offset >= maxRangeOffset(e.node())); e = Position(prev, maxRangeOffset(prev)); } } @@ -1282,7 +1267,7 @@ bool ApplyStyleCommand::nodeFullySelected(Node *node, const Position &start, con ASSERT(node->isElementNode()); Position pos = Position(node, node->childNodeCount()).upstream(); - return Range::compareBoundaryPoints(node, 0, start.node(), start.offset()) >= 0 && + return Range::compareBoundaryPoints(node, 0, start.node(), start.m_offset) >= 0 && Range::compareBoundaryPoints(pos, end) <= 0; } @@ -1293,7 +1278,7 @@ bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, c Position pos = Position(node, node->childNodeCount()).upstream(); bool isFullyBeforeStart = Range::compareBoundaryPoints(pos, start) < 0; - bool isFullyAfterEnd = Range::compareBoundaryPoints(node, 0, end.node(), end.offset()) > 0; + bool isFullyAfterEnd = Range::compareBoundaryPoints(node, 0, end.node(), end.m_offset) > 0; return isFullyBeforeStart || isFullyAfterEnd; } @@ -1301,11 +1286,11 @@ bool ApplyStyleCommand::nodeFullyUnselected(Node *node, const Position &start, c bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Position &end) { - if (start.node()->isTextNode() && start.offset() > caretMinOffset(start.node()) && start.offset() < caretMaxOffset(start.node())) { - int endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0; + if (start.node()->isTextNode() && start.m_offset > caretMinOffset(start.node()) && start.m_offset < caretMaxOffset(start.node())) { + int endOffsetAdjustment = start.node() == end.node() ? start.m_offset : 0; Text *text = static_cast<Text *>(start.node()); - splitTextNode(text, start.offset()); - updateStartEnd(Position(start.node(), 0), Position(end.node(), end.offset() - endOffsetAdjustment)); + splitTextNode(text, start.m_offset); + updateStartEnd(Position(start.node(), 0), Position(end.node(), end.m_offset - endOffsetAdjustment)); return true; } return false; @@ -1313,15 +1298,15 @@ bool ApplyStyleCommand::splitTextAtStartIfNeeded(const Position &start, const Po bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Position &end) { - if (end.node()->isTextNode() && end.offset() > caretMinOffset(end.node()) && end.offset() < caretMaxOffset(end.node())) { + if (end.node()->isTextNode() && end.m_offset > caretMinOffset(end.node()) && end.m_offset < caretMaxOffset(end.node())) { Text *text = static_cast<Text *>(end.node()); - splitTextNode(text, end.offset()); + splitTextNode(text, end.m_offset); Node *prevNode = text->previousSibling(); ASSERT(prevNode); Node *startNode = start.node() == end.node() ? prevNode : start.node(); ASSERT(startNode); - updateStartEnd(Position(startNode, start.offset()), Position(prevNode, caretMaxOffset(prevNode))); + updateStartEnd(Position(startNode, start.m_offset), Position(prevNode, caretMaxOffset(prevNode))); return true; } return false; @@ -1329,12 +1314,12 @@ bool ApplyStyleCommand::splitTextAtEndIfNeeded(const Position &start, const Posi bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, const Position &end) { - if (start.node()->isTextNode() && start.offset() > caretMinOffset(start.node()) && start.offset() < caretMaxOffset(start.node())) { - int endOffsetAdjustment = start.node() == end.node() ? start.offset() : 0; + if (start.node()->isTextNode() && start.m_offset > caretMinOffset(start.node()) && start.m_offset < caretMaxOffset(start.node())) { + int endOffsetAdjustment = start.node() == end.node() ? start.m_offset : 0; Text *text = static_cast<Text *>(start.node()); - splitTextNodeContainingElement(text, start.offset()); + splitTextNodeContainingElement(text, start.m_offset); - updateStartEnd(Position(start.node()->parentNode(), start.node()->nodeIndex()), Position(end.node(), end.offset() - endOffsetAdjustment)); + updateStartEnd(Position(start.node()->parentNode(), start.node()->nodeIndex()), Position(end.node(), end.m_offset - endOffsetAdjustment)); return true; } return false; @@ -1342,15 +1327,15 @@ bool ApplyStyleCommand::splitTextElementAtStartIfNeeded(const Position &start, c bool ApplyStyleCommand::splitTextElementAtEndIfNeeded(const Position &start, const Position &end) { - if (end.node()->isTextNode() && end.offset() > caretMinOffset(end.node()) && end.offset() < caretMaxOffset(end.node())) { + if (end.node()->isTextNode() && end.m_offset > caretMinOffset(end.node()) && end.m_offset < caretMaxOffset(end.node())) { Text *text = static_cast<Text *>(end.node()); - splitTextNodeContainingElement(text, end.offset()); + splitTextNodeContainingElement(text, end.m_offset); Node *prevNode = text->parent()->previousSibling()->lastChild(); ASSERT(prevNode); Node *startNode = start.node() == end.node() ? prevNode : start.node(); ASSERT(startNode); - updateStartEnd(Position(startNode, start.offset()), Position(prevNode->parent(), prevNode->nodeIndex() + 1)); + updateStartEnd(Position(startNode, start.m_offset), Position(prevNode->parent(), prevNode->nodeIndex() + 1)); return true; } return false; @@ -1394,10 +1379,10 @@ static bool areIdenticalElements(Node *first, Node *second) bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, const Position &end) { Node *startNode = start.node(); - int startOffset = start.offset(); + int startOffset = start.m_offset; if (isAtomicNode(start.node())) { - if (start.offset() != 0) + if (start.m_offset != 0) return false; // note: prior siblings could be unrendered elements. it's silly to miss the @@ -1426,7 +1411,7 @@ bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, int startOffsetAdjustment = startChild->nodeIndex(); int endOffsetAdjustment = startNode == end.node() ? startOffsetAdjustment : 0; - updateStartEnd(Position(startNode, startOffsetAdjustment), Position(end.node(), end.offset() + endOffsetAdjustment)); + updateStartEnd(Position(startNode, startOffsetAdjustment), Position(end.node(), end.m_offset + endOffsetAdjustment)); return true; } @@ -1436,7 +1421,7 @@ bool ApplyStyleCommand::mergeStartWithPreviousIfIdentical(const Position &start, bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const Position &end) { Node *endNode = end.node(); - int endOffset = end.offset(); + int endOffset = end.m_offset; if (isAtomicNode(endNode)) { if (endOffset < caretMaxOffset(endNode)) @@ -1466,7 +1451,7 @@ bool ApplyStyleCommand::mergeEndWithNextIfIdentical(const Position &start, const ASSERT(startNode); int endOffset = nextChild ? nextChild->nodeIndex() : nextElement->childNodes()->length(); - updateStartEnd(Position(startNode, start.offset()), Position(nextElement, endOffset)); + updateStartEnd(Position(startNode, start.m_offset), Position(nextElement, endOffset)); return true; } @@ -1495,7 +1480,7 @@ void ApplyStyleCommand::surroundNodeRangeWithElement(Node* startNode, Node* endN } // FIXME: We should probably call updateStartEnd if the start or end was in the node // range so that the endingSelection() is canonicalized. See the comments at the end of - // Selection::validate(). + // VisibleSelection::validate(). } void ApplyStyleCommand::addBlockStyle(const StyleChange& styleChange, HTMLElement* block) @@ -1544,7 +1529,7 @@ void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style if (m_removeOnly) return; - StyleChange styleChange(style, Position(startNode, 0), StyleChange::styleModeForParseMode(document()->inCompatMode())); + StyleChange styleChange(style, Position(startNode, 0)); // // Font tags need to go outside of CSS so that CSS font sizes override leagcy font sizes. @@ -1587,7 +1572,7 @@ void ApplyStyleCommand::addInlineStyleIfNeeded(CSSMutableStyleDeclaration *style surroundNodeRangeWithElement(startNode, endNode, createHTMLElement(document(), supTag)); if (m_styledInlineElement) - surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElement()); + surroundNodeRangeWithElement(startNode, endNode, m_styledInlineElement->cloneElementWithoutChildren()); } float ApplyStyleCommand::computedFontSize(const Node *node) @@ -1622,9 +1607,9 @@ void ApplyStyleCommand::joinChildTextNodes(Node *node, const Position &start, co Text *childText = static_cast<Text *>(child); Text *nextText = static_cast<Text *>(next); if (next == start.node()) - newStart = Position(childText, childText->length() + start.offset()); + newStart = Position(childText, childText->length() + start.m_offset); if (next == end.node()) - newEnd = Position(childText, childText->length() + end.offset()); + newEnd = Position(childText, childText->length() + end.m_offset); String textToMove = nextText->data(); insertTextIntoNode(childText, childText->length(), textToMove); removeNode(next); diff --git a/WebCore/editing/BreakBlockquoteCommand.cpp b/WebCore/editing/BreakBlockquoteCommand.cpp index 76a0890..2a513a5 100644 --- a/WebCore/editing/BreakBlockquoteCommand.cpp +++ b/WebCore/editing/BreakBlockquoteCommand.cpp @@ -72,7 +72,7 @@ void BreakBlockquoteCommand::doApply() insertNodeAfter(breakNode.get(), topBlockquote); if (isLastVisiblePositionInNode(visiblePos, topBlockquote)) { - setEndingSelection(Selection(Position(breakNode.get(), 0), DOWNSTREAM)); + setEndingSelection(VisibleSelection(Position(breakNode.get(), 0), DOWNSTREAM)); rebalanceWhitespace(); return; } @@ -85,19 +85,19 @@ void BreakBlockquoteCommand::doApply() // Split at pos if in the middle of a text node. if (startNode->isTextNode()) { Text* textNode = static_cast<Text*>(startNode); - if ((unsigned)pos.offset() >= textNode->length()) { + if ((unsigned)pos.m_offset >= textNode->length()) { startNode = startNode->traverseNextNode(); ASSERT(startNode); - } else if (pos.offset() > 0) - splitTextNode(textNode, pos.offset()); - } else if (pos.offset() > 0) { + } else if (pos.m_offset > 0) + splitTextNode(textNode, pos.m_offset); + } else if (pos.m_offset > 0) { startNode = startNode->traverseNextNode(); ASSERT(startNode); } // If there's nothing inside topBlockquote to move, we're finished. if (!startNode->isDescendantOf(topBlockquote)) { - setEndingSelection(Selection(VisiblePosition(Position(startNode, 0)))); + setEndingSelection(VisibleSelection(VisiblePosition(Position(startNode, 0)))); return; } @@ -107,7 +107,7 @@ void BreakBlockquoteCommand::doApply() ancestors.append(node); // Insert a clone of the top blockquote after the break. - RefPtr<Element> clonedBlockquote = topBlockquote->cloneElement(); + RefPtr<Element> clonedBlockquote = topBlockquote->cloneElementWithoutChildren(); insertNodeAfter(clonedBlockquote.get(), breakNode.get()); // Clone startNode's ancestors into the cloned blockquote. @@ -116,7 +116,7 @@ void BreakBlockquoteCommand::doApply() // or clonedBlockquote if ancestors is empty). RefPtr<Element> clonedAncestor = clonedBlockquote; for (size_t i = ancestors.size(); i != 0; --i) { - RefPtr<Element> clonedChild = ancestors[i - 1]->cloneElement(); // shallow clone + RefPtr<Element> clonedChild = ancestors[i - 1]->cloneElementWithoutChildren(); // Preserve list item numbering in cloned lists. if (clonedChild->isElementNode() && clonedChild->hasTagName(olTag)) { Node* listChildNode = i > 1 ? ancestors[i - 2] : startNode; @@ -168,7 +168,7 @@ void BreakBlockquoteCommand::doApply() addBlockPlaceholderIfNeeded(clonedBlockquote.get()); // Put the selection right before the break. - setEndingSelection(Selection(Position(breakNode.get(), 0), DOWNSTREAM)); + setEndingSelection(VisibleSelection(Position(breakNode.get(), 0), DOWNSTREAM)); rebalanceWhitespace(); } diff --git a/WebCore/editing/CompositeEditCommand.cpp b/WebCore/editing/CompositeEditCommand.cpp index 335f77e..9052582 100644 --- a/WebCore/editing/CompositeEditCommand.cpp +++ b/WebCore/editing/CompositeEditCommand.cpp @@ -157,7 +157,7 @@ void CompositeEditCommand::insertNodeAt(PassRefPtr<Node> insertChild, const Posi // likewise for replaced elements, brs, etc. Position p = rangeCompliantEquivalent(editingPosition); Node* refChild = p.node(); - int offset = p.offset(); + int offset = p.m_offset; if (canHaveChildrenForEditing(refChild)) { Node* child = refChild->firstChild(); @@ -171,6 +171,10 @@ void CompositeEditCommand::insertNodeAt(PassRefPtr<Node> insertChild, const Posi insertNodeBefore(insertChild, refChild); else if (refChild->isTextNode() && caretMaxOffset(refChild) > offset) { splitTextNode(static_cast<Text *>(refChild), offset); + + // Mutation events (bug 22634) from the text node insertion may have removed the refChild + if (!refChild->inDocument()) + return; insertNodeBefore(insertChild, refChild); } else insertNodeAfter(insertChild, refChild); @@ -296,7 +300,7 @@ void CompositeEditCommand::inputText(const String& text, bool selectInsertedText if (selectInsertedText) { RefPtr<Range> selectedRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, length); - setEndingSelection(Selection(selectedRange.get())); + setEndingSelection(VisibleSelection(selectedRange.get())); } } @@ -323,13 +327,13 @@ Position CompositeEditCommand::positionOutsideTabSpan(const Position& pos) Node* tabSpan = tabSpanNode(pos.node()); - if (pos.offset() <= caretMinOffset(pos.node())) + if (pos.m_offset <= caretMinOffset(pos.node())) return positionBeforeNode(tabSpan); - if (pos.offset() >= caretMaxOffset(pos.node())) + if (pos.m_offset >= caretMaxOffset(pos.node())) return positionAfterNode(tabSpan); - splitTextNodeContainingElement(static_cast<Text *>(pos.node()), pos.offset()); + splitTextNodeContainingElement(static_cast<Text *>(pos.node()), pos.m_offset); return positionBeforeNode(tabSpan); } @@ -345,7 +349,7 @@ void CompositeEditCommand::deleteSelection(bool smartDelete, bool mergeBlocksAft applyCommandToComposite(DeleteSelectionCommand::create(document(), smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements)); } -void CompositeEditCommand::deleteSelection(const Selection &selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements) +void CompositeEditCommand::deleteSelection(const VisibleSelection &selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements) { if (selection.isRange()) applyCommandToComposite(DeleteSelectionCommand::create(selection, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements)); @@ -388,7 +392,7 @@ void CompositeEditCommand::rebalanceWhitespaceAt(const Position& position) String text = textNode->data(); ASSERT(!text.isEmpty()); - int offset = position.offset(); + int offset = position.m_offset; // If neither text[offset] nor text[offset - 1] are some form of whitespace, do nothing. if (!isWhitespace(text[offset])) { offset--; @@ -445,14 +449,14 @@ void CompositeEditCommand::prepareWhitespaceAtPositionForSplit(Position& positio Position previous(previousVisiblePos.deepEquivalent()); if (isCollapsibleWhitespace(previousVisiblePos.characterAfter()) && previous.node()->isTextNode() && !previous.node()->hasTagName(brTag)) - replaceTextInNode(static_cast<Text*>(previous.node()), previous.offset(), 1, nonBreakingSpaceString()); + replaceTextInNode(static_cast<Text*>(previous.node()), previous.m_offset, 1, nonBreakingSpaceString()); if (isCollapsibleWhitespace(visiblePos.characterAfter()) && position.node()->isTextNode() && !position.node()->hasTagName(brTag)) - replaceTextInNode(static_cast<Text*>(position.node()), position.offset(), 1, nonBreakingSpaceString()); + replaceTextInNode(static_cast<Text*>(position.node()), position.m_offset, 1, nonBreakingSpaceString()); } void CompositeEditCommand::rebalanceWhitespace() { - Selection selection = endingSelection(); + VisibleSelection selection = endingSelection(); if (selection.isNone()) return; @@ -538,8 +542,8 @@ void CompositeEditCommand::deleteInsignificantText(const Position& start, const next = node->traverseNextNode(); if (node->isTextNode()) { Text* textNode = static_cast<Text*>(node); - int startOffset = node == start.node() ? start.offset() : 0; - int endOffset = node == end.node() ? end.offset() : textNode->length(); + int startOffset = node == start.node() ? start.m_offset : 0; + int endOffset = node == end.node() ? end.m_offset : textNode->length(); deleteInsignificantText(textNode, startOffset, endOffset); } if (node == end.node()) @@ -592,7 +596,7 @@ PassRefPtr<Node> CompositeEditCommand::addBlockPlaceholderIfNeeded(Element* cont // append the placeholder to make sure it follows // any unrendered blocks - RenderBlock* block = static_cast<RenderBlock*>(renderer); + RenderBlock* block = toRenderBlock(renderer); if (block->height() == 0 || (block->isListItem() && block->isEmpty())) return appendBlockPlaceholder(container); @@ -612,10 +616,10 @@ void CompositeEditCommand::removePlaceholderAt(const VisiblePosition& visiblePos // the start of a paragraph will render it superfluous. // FIXME: This doesn't remove placeholders at the end of anonymous blocks. if (isEndOfBlock(visiblePosition) && isStartOfParagraph(visiblePosition)) { - if (p.node()->hasTagName(brTag) && p.offset() == 0) + if (p.node()->hasTagName(brTag) && p.m_offset == 0) removeNode(p.node()); else if (lineBreakExistsAtPosition(visiblePosition)) - deleteTextFromNode(static_cast<Text*>(p.node()), p.offset(), 1); + deleteTextFromNode(static_cast<Text*>(p.node()), p.m_offset, 1); } } @@ -655,11 +659,10 @@ PassRefPtr<Node> CompositeEditCommand::moveParagraphContentsToNewBlockIfNecessar // Perform some checks to see if we need to perform work in this function. if (isBlock(upstreamStart.node())) { - // If the block is the body element, always move content to a new block, so that - // we avoid adding styles to the body element, since Mail's Make Plain Text feature - // can't handle those. - if (upstreamStart.node()->hasTagName(bodyTag)) { - // If the block is the body element and there is nothing insde of it, create a new + // If the block is the root editable element, always move content to a new block, + // since it is illegal to modify attributes on the root editable element for editing. + if (upstreamStart.node() == editableRootForPosition(upstreamStart)) { + // If the block is the root editable element and there is nothing insde of it, create a new // block but don't try and move content into it, since there's nothing to move. if (upstreamStart == upstreamEnd) return insertNewDefaultParagraphElementAt(upstreamStart); @@ -696,7 +699,7 @@ void CompositeEditCommand::pushAnchorElementDown(Node* anchorNode) ASSERT(anchorNode->isLink()); - setEndingSelection(Selection::selectionFromContentsOfNode(anchorNode)); + setEndingSelection(VisibleSelection::selectionFromContentsOfNode(anchorNode)); applyStyledElement(static_cast<Element*>(anchorNode)); // Clones of anchorNode have been pushed down, now remove it. if (anchorNode->inDocument()) @@ -709,7 +712,7 @@ void CompositeEditCommand::pushAnchorElementDown(Node* anchorNode) // Anchors cannot be nested. void CompositeEditCommand::pushPartiallySelectedAnchorElementsDown() { - Selection originalSelection = endingSelection(); + VisibleSelection originalSelection = endingSelection(); VisiblePosition visibleStart(originalSelection.start()); VisiblePosition visibleEnd(originalSelection.end()); @@ -779,7 +782,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap // start and end can't be used directly to create a Range; they are "editing positions" Position startRangeCompliant = rangeCompliantEquivalent(start); Position endRangeCompliant = rangeCompliantEquivalent(end); - RefPtr<Range> range = Range::create(document(), startRangeCompliant.node(), startRangeCompliant.offset(), endRangeCompliant.node(), endRangeCompliant.offset()); + RefPtr<Range> range = Range::create(document(), startRangeCompliant.node(), startRangeCompliant.m_offset, endRangeCompliant.node(), endRangeCompliant.m_offset); // FIXME: This is an inefficient way to preserve style on nodes in the paragraph to move. It // shouldn't matter though, since moved paragraphs will usually be quite small. @@ -797,7 +800,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap // FIXME (5098931): We should add a new insert action "WebViewInsertActionMoved" and call shouldInsertFragment here. - setEndingSelection(Selection(start, end, DOWNSTREAM)); + setEndingSelection(VisibleSelection(start, end, DOWNSTREAM)); deleteSelection(false, false, false, false); ASSERT(destination.deepEquivalent().node()->inDocument()); @@ -828,7 +831,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap if (textNode->length() == 1) removeNodeAndPruneAncestors(node); else - deleteTextFromNode(textNode, position.offset(), 1); + deleteTextFromNode(textNode, position.m_offset, 1); } } @@ -866,7 +869,7 @@ void CompositeEditCommand::moveParagraphs(const VisiblePosition& startOfParagrap RefPtr<Range> start = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + startIndex, 0, true); RefPtr<Range> end = TextIterator::rangeFromLocationAndLength(document()->documentElement(), destinationIndex + endIndex, 0, true); if (start && end) - setEndingSelection(Selection(start->startPosition(), end->startPosition(), DOWNSTREAM)); + setEndingSelection(VisibleSelection(start->startPosition(), end->startPosition(), DOWNSTREAM)); } } @@ -897,7 +900,7 @@ bool CompositeEditCommand::breakOutOfEmptyListItem() } appendBlockPlaceholder(newBlock); - setEndingSelection(Selection(Position(newBlock.get(), 0), DOWNSTREAM)); + setEndingSelection(VisibleSelection(Position(newBlock.get(), 0), DOWNSTREAM)); computedStyle(endingSelection().start().node())->diff(style.get()); if (style->length() > 0) @@ -935,7 +938,7 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() // a second one. if (!isStartOfParagraph(atBR)) insertNodeBefore(createBreakElement(document()), br); - setEndingSelection(Selection(atBR)); + setEndingSelection(VisibleSelection(atBR)); // If this is an empty paragraph there must be a line break here. if (!lineBreakExistsAtPosition(caret)) @@ -950,7 +953,7 @@ bool CompositeEditCommand::breakOutOfEmptyMailBlockquotedParagraph() removeNode(caretPos.node()); prune(beforeBR.node()); } else { - ASSERT(caretPos.offset() == 0); + ASSERT(caretPos.m_offset == 0); Text* textNode = static_cast<Text*>(caretPos.node()); Node* parentNode = textNode->parentNode(); // The preserved newline must be the first thing in the node, since otherwise the previous @@ -976,8 +979,8 @@ Position CompositeEditCommand::positionAvoidingSpecialElementBoundary(const Posi Position result = original; // Don't avoid block level anchors, because that would insert content into the wrong paragraph. if (enclosingAnchor && !isBlock(enclosingAnchor)) { - VisiblePosition firstInAnchor(Position(enclosingAnchor, 0)); - VisiblePosition lastInAnchor(Position(enclosingAnchor, maxDeepOffset(enclosingAnchor))); + VisiblePosition firstInAnchor(firstDeepEditingPositionForNode(enclosingAnchor)); + VisiblePosition lastInAnchor(lastDeepEditingPositionForNode(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) { @@ -1034,9 +1037,7 @@ PassRefPtr<Node> CompositeEditCommand::splitTreeToNode(Node* start, Node* end, b PassRefPtr<Element> createBlockPlaceholderElement(Document* document) { - ExceptionCode ec = 0; - RefPtr<Element> breakNode = document->createElementNS(xhtmlNamespaceURI, "br", ec); - ASSERT(ec == 0); + RefPtr<Element> breakNode = document->createElement(brTag, false); return breakNode.release(); } diff --git a/WebCore/editing/CompositeEditCommand.h b/WebCore/editing/CompositeEditCommand.h index 6bb142b..4a3defd 100644 --- a/WebCore/editing/CompositeEditCommand.h +++ b/WebCore/editing/CompositeEditCommand.h @@ -52,7 +52,7 @@ protected: void applyStyledElement(PassRefPtr<Element>); void removeStyledElement(PassRefPtr<Element>); void deleteSelection(bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = true); - void deleteSelection(const Selection&, bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = true); + void deleteSelection(const VisibleSelection&, bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = true); virtual void deleteTextFromNode(PassRefPtr<Text>, unsigned offset, unsigned count); void inputText(const String&, bool selectInsertedText = false); void insertNodeAfter(PassRefPtr<Node>, PassRefPtr<Node> refChild); diff --git a/WebCore/editing/CreateLinkCommand.cpp b/WebCore/editing/CreateLinkCommand.cpp index c5d68dd..263feab 100644 --- a/WebCore/editing/CreateLinkCommand.cpp +++ b/WebCore/editing/CreateLinkCommand.cpp @@ -53,7 +53,7 @@ void CreateLinkCommand::doApply() insertNodeAt(anchorElement.get(), endingSelection().start()); RefPtr<Text> textNode = new Text(document(), m_url); appendNode(textNode.get(), anchorElement.get()); - setEndingSelection(Selection(positionBeforeNode(anchorElement.get()), positionAfterNode(anchorElement.get()), DOWNSTREAM)); + setEndingSelection(VisibleSelection(positionBeforeNode(anchorElement.get()), positionAfterNode(anchorElement.get()), DOWNSTREAM)); } } diff --git a/WebCore/editing/DeleteButtonController.cpp b/WebCore/editing/DeleteButtonController.cpp index 0516e0c..c0775e3 100644 --- a/WebCore/editing/DeleteButtonController.cpp +++ b/WebCore/editing/DeleteButtonController.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2006, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2006, 2008, 2009 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -101,12 +101,12 @@ static bool isDeletableElement(const Node* node) return false; } -static HTMLElement* enclosingDeletableElement(const Selection& selection) +static HTMLElement* enclosingDeletableElement(const VisibleSelection& selection) { if (!selection.isContentEditable()) return 0; - RefPtr<Range> range = selection.toRange(); + RefPtr<Range> range = selection.toNormalizedRange(); if (!range) return 0; @@ -128,7 +128,7 @@ static HTMLElement* enclosingDeletableElement(const Selection& selection) return static_cast<HTMLElement*>(element); } -void DeleteButtonController::respondToChangedSelection(const Selection& oldSelection) +void DeleteButtonController::respondToChangedSelection(const VisibleSelection& oldSelection) { if (!enabled()) return; @@ -154,6 +154,13 @@ void DeleteButtonController::createDeletionUI() style->setProperty(CSSPropertyWebkitUserDrag, CSSValueNone); style->setProperty(CSSPropertyWebkitUserSelect, CSSValueNone); style->setProperty(CSSPropertyWebkitUserModify, CSSValueNone); + style->setProperty(CSSPropertyVisibility, CSSValueHidden); + style->setProperty(CSSPropertyPosition, CSSValueAbsolute); + style->setProperty(CSSPropertyCursor, CSSValueDefault); + style->setProperty(CSSPropertyTop, "0"); + style->setProperty(CSSPropertyRight, "0"); + style->setProperty(CSSPropertyBottom, "0"); + style->setProperty(CSSPropertyLeft, "0"); RefPtr<HTMLDivElement> outline = new HTMLDivElement(divTag, m_target->document()); outline->setId(outlineElementIdentifier); @@ -163,10 +170,6 @@ void DeleteButtonController::createDeletionUI() style = outline->getInlineStyleDecl(); style->setProperty(CSSPropertyPosition, CSSValueAbsolute); - style->setProperty(CSSPropertyCursor, CSSValueDefault); - style->setProperty(CSSPropertyWebkitUserDrag, CSSValueNone); - style->setProperty(CSSPropertyWebkitUserSelect, CSSValueNone); - style->setProperty(CSSPropertyWebkitUserModify, CSSValueNone); style->setProperty(CSSPropertyZIndex, String::number(-1000000)); style->setProperty(CSSPropertyTop, String::number(-borderWidth - m_target->renderBox()->borderTop()) + "px"); style->setProperty(CSSPropertyRight, String::number(-borderWidth - m_target->renderBox()->borderRight()) + "px"); @@ -174,6 +177,7 @@ void DeleteButtonController::createDeletionUI() style->setProperty(CSSPropertyLeft, String::number(-borderWidth - m_target->renderBox()->borderLeft()) + "px"); style->setProperty(CSSPropertyBorder, String::number(borderWidth) + "px solid rgba(0, 0, 0, 0.6)"); style->setProperty(CSSPropertyWebkitBorderRadius, String::number(borderRadius) + "px"); + style->setProperty(CSSPropertyVisibility, CSSValueVisible); ExceptionCode ec = 0; container->appendChild(outline.get(), ec); @@ -190,15 +194,12 @@ void DeleteButtonController::createDeletionUI() style = button->getInlineStyleDecl(); style->setProperty(CSSPropertyPosition, CSSValueAbsolute); - style->setProperty(CSSPropertyCursor, CSSValueDefault); - style->setProperty(CSSPropertyWebkitUserDrag, CSSValueNone); - style->setProperty(CSSPropertyWebkitUserSelect, CSSValueNone); - style->setProperty(CSSPropertyWebkitUserModify, CSSValueNone); style->setProperty(CSSPropertyZIndex, String::number(1000000)); style->setProperty(CSSPropertyTop, String::number((-buttonHeight / 2) - m_target->renderBox()->borderTop() - (borderWidth / 2) + buttonBottomShadowOffset) + "px"); style->setProperty(CSSPropertyLeft, String::number((-buttonWidth / 2) - m_target->renderBox()->borderLeft() - (borderWidth / 2)) + "px"); style->setProperty(CSSPropertyWidth, String::number(buttonWidth) + "px"); style->setProperty(CSSPropertyHeight, String::number(buttonHeight) + "px"); + style->setProperty(CSSPropertyVisibility, CSSValueVisible); RefPtr<Image> buttonImage = Image::loadPlatformResource("deleteButton"); if (buttonImage->isNull()) @@ -283,8 +284,13 @@ void DeleteButtonController::enable() ASSERT(m_disableStack > 0); if (m_disableStack > 0) m_disableStack--; - if (enabled()) + if (enabled()) { + // Determining if the element is deletable currently depends on style + // because whether something is editable depends on style, so we need + // to recalculate style before calling enclosingDeletableElement. + m_frame->document()->updateRendering(); show(enclosingDeletableElement(m_frame->selection()->selection())); + } } void DeleteButtonController::disable() diff --git a/WebCore/editing/DeleteButtonController.h b/WebCore/editing/DeleteButtonController.h index ab2d0b0..713ae8b 100644 --- a/WebCore/editing/DeleteButtonController.h +++ b/WebCore/editing/DeleteButtonController.h @@ -34,7 +34,7 @@ class DeleteButton; class Frame; class HTMLElement; class RenderObject; -class Selection; +class VisibleSelection; class DeleteButtonController { public: @@ -45,7 +45,7 @@ public: HTMLElement* target() const { return m_target.get(); } HTMLElement* containerElement() const { return m_containerElement.get(); } - void respondToChangedSelection(const Selection& oldSelection); + void respondToChangedSelection(const VisibleSelection& oldSelection); void show(HTMLElement*); void hide(); diff --git a/WebCore/editing/DeleteSelectionCommand.cpp b/WebCore/editing/DeleteSelectionCommand.cpp index 21b597e..09288ee 100644 --- a/WebCore/editing/DeleteSelectionCommand.cpp +++ b/WebCore/editing/DeleteSelectionCommand.cpp @@ -56,9 +56,7 @@ static bool isTableRow(const Node* node) static bool isTableCellEmpty(Node* cell) { ASSERT(isTableCell(cell)); - VisiblePosition firstInCell(Position(cell, 0)); - VisiblePosition lastInCell(Position(cell, maxDeepOffset(cell))); - return firstInCell == lastInCell; + return VisiblePosition(firstDeepEditingPositionForNode(cell)) == VisiblePosition(lastDeepEditingPositionForNode(cell)); } static bool isTableRowEmpty(Node* row) @@ -88,7 +86,7 @@ DeleteSelectionCommand::DeleteSelectionCommand(Document *document, bool smartDel { } -DeleteSelectionCommand::DeleteSelectionCommand(const Selection& selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements) +DeleteSelectionCommand::DeleteSelectionCommand(const VisibleSelection& selection, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements) : CompositeEditCommand(selection.start().node()->document()), m_hasSelectionToDelete(true), m_smartDelete(smartDelete), @@ -312,8 +310,8 @@ static void updatePositionForNodeRemoval(Node* node, Position& position) { if (position.isNull()) return; - if (node->parent() == position.node() && node->nodeIndex() < (unsigned)position.offset()) - position = Position(position.node(), position.offset() - 1); + if (node->parent() == position.node() && node->nodeIndex() < (unsigned)position.m_offset) + position = Position(position.node(), position.m_offset - 1); if (position.node() == node || position.node()->isDescendantOf(node)) position = positionBeforeNode(node); } @@ -363,9 +361,9 @@ void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node) return; } - if (node == m_startBlock && !isEndOfBlock(VisiblePosition(m_startBlock.get(), 0, DOWNSTREAM).previous())) + if (node == m_startBlock && !isEndOfBlock(VisiblePosition(firstDeepEditingPositionForNode(m_startBlock.get())).previous())) m_needPlaceholder = true; - else if (node == m_endBlock && !isStartOfBlock(VisiblePosition(m_endBlock.get(), maxDeepOffset(m_endBlock.get()), DOWNSTREAM).next())) + else if (node == m_endBlock && !isStartOfBlock(VisiblePosition(lastDeepEditingPositionForNode(m_startBlock.get())).next())) m_needPlaceholder = true; // FIXME: Update the endpoints of the range being deleted. @@ -379,9 +377,9 @@ void DeleteSelectionCommand::removeNode(PassRefPtr<Node> node) static void updatePositionForTextRemoval(Node* node, int offset, int count, Position& position) { if (position.node() == node) { - if (position.offset() > offset + count) - position = Position(position.node(), position.offset() - count); - else if (position.offset() > offset) + if (position.m_offset > offset + count) + position = Position(position.node(), position.m_offset - count); + else if (position.m_offset > offset) position = Position(position.node(), offset); } } @@ -398,7 +396,7 @@ void DeleteSelectionCommand::deleteTextFromNode(PassRefPtr<Text> node, unsigned void DeleteSelectionCommand::handleGeneralDelete() { - int startOffset = m_upstreamStart.offset(); + int startOffset = m_upstreamStart.m_offset; Node* startNode = m_upstreamStart.node(); // Never remove the start block unless it's a table, in which case we won't merge content in. @@ -413,7 +411,7 @@ void DeleteSelectionCommand::handleGeneralDelete() deleteTextFromNode(text, caretMaxOffset(startNode), text->length() - caretMaxOffset(startNode)); } - if (startOffset >= maxDeepOffset(startNode)) { + if (startOffset >= lastOffsetForEditing(startNode)) { startNode = startNode->traverseNextSibling(); startOffset = 0; } @@ -424,17 +422,16 @@ void DeleteSelectionCommand::handleGeneralDelete() if (startNode == m_downstreamEnd.node()) { // The selection to delete is all in one node. - if (!startNode->renderer() || - (startOffset == 0 && m_downstreamEnd.offset() >= maxDeepOffset(startNode))) { + if (!startNode->renderer() || (startOffset == 0 && m_downstreamEnd.atLastEditingPositionForNode())) { // just delete removeNode(startNode); - } else if (m_downstreamEnd.offset() - startOffset > 0) { + } else if (m_downstreamEnd.m_offset - startOffset > 0) { if (startNode->isTextNode()) { // in a text node that needs to be trimmed - Text *text = static_cast<Text *>(startNode); - deleteTextFromNode(text, startOffset, m_downstreamEnd.offset() - startOffset); + Text* text = static_cast<Text*>(startNode); + deleteTextFromNode(text, startOffset, m_downstreamEnd.m_offset - startOffset); } else { - removeChildrenInRange(startNode, startOffset, m_downstreamEnd.offset()); + removeChildrenInRange(startNode, startOffset, m_downstreamEnd.m_offset); m_endingPosition = m_upstreamStart; } } @@ -465,14 +462,14 @@ void DeleteSelectionCommand::handleGeneralDelete() // if we just removed a node from the end container, update end position so the // check above will work if (node->parentNode() == m_downstreamEnd.node()) { - ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.offset()); - m_downstreamEnd = Position(m_downstreamEnd.node(), m_downstreamEnd.offset() - 1); + ASSERT(node->nodeIndex() < (unsigned)m_downstreamEnd.m_offset); + m_downstreamEnd = Position(m_downstreamEnd.node(), m_downstreamEnd.m_offset - 1); } removeNode(node.get()); node = nextNode.get(); } else { Node* n = node->lastDescendant(); - if (m_downstreamEnd.node() == n && m_downstreamEnd.offset() >= caretMaxOffset(n)) { + if (m_downstreamEnd.node() == n && m_downstreamEnd.m_offset >= caretMaxOffset(n)) { removeNode(node.get()); node = 0; } else @@ -480,16 +477,16 @@ void DeleteSelectionCommand::handleGeneralDelete() } } - if (m_downstreamEnd.node() != startNode && !m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.offset() >= caretMinOffset(m_downstreamEnd.node())) { - if (m_downstreamEnd.offset() >= maxDeepOffset(m_downstreamEnd.node()) && !canHaveChildrenForEditing(m_downstreamEnd.node())) { + if (m_downstreamEnd.node() != startNode && !m_upstreamStart.node()->isDescendantOf(m_downstreamEnd.node()) && m_downstreamEnd.node()->inDocument() && m_downstreamEnd.m_offset >= caretMinOffset(m_downstreamEnd.node())) { + if (m_downstreamEnd.atLastEditingPositionForNode() && !canHaveChildrenForEditing(m_downstreamEnd.node())) { // The node itself is fully selected, not just its contents. Delete it. removeNode(m_downstreamEnd.node()); } else { if (m_downstreamEnd.node()->isTextNode()) { // in a text node that needs to be trimmed Text *text = static_cast<Text *>(m_downstreamEnd.node()); - if (m_downstreamEnd.offset() > 0) { - deleteTextFromNode(text, 0, m_downstreamEnd.offset()); + if (m_downstreamEnd.m_offset > 0) { + deleteTextFromNode(text, 0, m_downstreamEnd.m_offset); m_downstreamEnd = Position(text, 0); } // Remove children of m_downstreamEnd.node() that come after m_upstreamStart. @@ -507,7 +504,7 @@ void DeleteSelectionCommand::handleGeneralDelete() if (n) offset = n->nodeIndex() + 1; } - removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.offset()); + removeChildrenInRange(m_downstreamEnd.node(), offset, m_downstreamEnd.m_offset); m_downstreamEnd = Position(m_downstreamEnd.node(), offset); } } @@ -522,12 +519,12 @@ void DeleteSelectionCommand::fixupWhitespace() if (m_leadingWhitespace.isNotNull() && !m_leadingWhitespace.isRenderedCharacter()) { Text* textNode = static_cast<Text*>(m_leadingWhitespace.node()); ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace()); - replaceTextInNode(textNode, m_leadingWhitespace.offset(), 1, nonBreakingSpaceString()); + replaceTextInNode(textNode, m_leadingWhitespace.m_offset, 1, nonBreakingSpaceString()); } if (m_trailingWhitespace.isNotNull() && !m_trailingWhitespace.isRenderedCharacter()) { Text* textNode = static_cast<Text*>(m_trailingWhitespace.node()); ASSERT(!textNode->renderer() ||textNode->renderer()->style()->collapseWhiteSpace()); - replaceTextInNode(textNode, m_trailingWhitespace.offset(), 1, nonBreakingSpaceString()); + replaceTextInNode(textNode, m_trailingWhitespace.m_offset, 1, nonBreakingSpaceString()); } } @@ -696,7 +693,7 @@ void DeleteSelectionCommand::calculateTypingStyleAfterDelete() void DeleteSelectionCommand::clearTransientState() { - m_selectionToDelete = Selection(); + m_selectionToDelete = VisibleSelection(); m_upstreamStart.clear(); m_downstreamStart.clear(); m_upstreamEnd.clear(); @@ -755,7 +752,7 @@ void DeleteSelectionCommand::doApply() // want to replace it with a placeholder BR! if (handleSpecialCaseBRDelete()) { calculateTypingStyleAfterDelete(); - setEndingSelection(Selection(m_endingPosition, affinity)); + setEndingSelection(VisibleSelection(m_endingPosition, affinity)); clearTransientState(); rebalanceWhitespace(); return; @@ -778,7 +775,7 @@ void DeleteSelectionCommand::doApply() calculateTypingStyleAfterDelete(); - setEndingSelection(Selection(m_endingPosition, affinity)); + setEndingSelection(VisibleSelection(m_endingPosition, affinity)); clearTransientState(); } diff --git a/WebCore/editing/DeleteSelectionCommand.h b/WebCore/editing/DeleteSelectionCommand.h index 0f9f2f7..640c549 100644 --- a/WebCore/editing/DeleteSelectionCommand.h +++ b/WebCore/editing/DeleteSelectionCommand.h @@ -36,14 +36,14 @@ public: { return adoptRef(new DeleteSelectionCommand(document, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements)); } - static PassRefPtr<DeleteSelectionCommand> create(const Selection& selection, bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = false) + static PassRefPtr<DeleteSelectionCommand> create(const VisibleSelection& selection, bool smartDelete = false, bool mergeBlocksAfterDelete = true, bool replace = false, bool expandForSpecialElements = false) { return adoptRef(new DeleteSelectionCommand(selection, smartDelete, mergeBlocksAfterDelete, replace, expandForSpecialElements)); } private: DeleteSelectionCommand(Document*, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements); - DeleteSelectionCommand(const Selection&, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements); + DeleteSelectionCommand(const VisibleSelection&, bool smartDelete, bool mergeBlocksAfterDelete, bool replace, bool expandForSpecialElements); virtual void doApply(); virtual EditAction editingAction() const; @@ -74,7 +74,7 @@ private: bool m_pruneStartBlockIfNecessary; // This data is transient and should be cleared at the end of the doApply function. - Selection m_selectionToDelete; + VisibleSelection m_selectionToDelete; Position m_upstreamStart; Position m_downstreamStart; Position m_upstreamEnd; diff --git a/WebCore/editing/EditCommand.cpp b/WebCore/editing/EditCommand.cpp index 7ec0c19..fefe658 100644 --- a/WebCore/editing/EditCommand.cpp +++ b/WebCore/editing/EditCommand.cpp @@ -47,8 +47,7 @@ EditCommand::EditCommand(Document* document) { ASSERT(m_document); ASSERT(m_document->frame()); - DeleteButtonController* deleteButton = m_document->frame()->editor()->deleteButtonController(); - setStartingSelection(avoidIntersectionWithNode(m_document->frame()->selection()->selection(), deleteButton ? deleteButton->containerElement() : 0)); + setStartingSelection(avoidIntersectionWithNode(m_document->frame()->selection()->selection(), m_document->frame()->editor()->deleteButtonController()->containerElement())); setEndingSelection(m_startingSelection); } @@ -94,7 +93,10 @@ void EditCommand::apply() if (!m_parent) { updateLayout(); - frame->editor()->appliedEditing(this); + // Only need to call appliedEditing for top-level commands, and TypingCommands do it on their + // own (see TypingCommand::typingAddedToOpenCommand). + if (!isTypingCommand()) + frame->editor()->appliedEditing(this); } } @@ -158,7 +160,7 @@ EditAction EditCommand::editingAction() const return EditActionUnspecified; } -void EditCommand::setStartingSelection(const Selection& s) +void EditCommand::setStartingSelection(const VisibleSelection& s) { Element* root = s.rootEditableElement(); for (EditCommand* cmd = this; ; cmd = cmd->m_parent) { @@ -169,7 +171,7 @@ void EditCommand::setStartingSelection(const Selection& s) } } -void EditCommand::setEndingSelection(const Selection &s) +void EditCommand::setEndingSelection(const VisibleSelection &s) { Element* root = s.rootEditableElement(); for (EditCommand* cmd = this; cmd; cmd = cmd->m_parent) { diff --git a/WebCore/editing/EditCommand.h b/WebCore/editing/EditCommand.h index 78490f8..c4969f6 100644 --- a/WebCore/editing/EditCommand.h +++ b/WebCore/editing/EditCommand.h @@ -28,7 +28,7 @@ #include "EditAction.h" #include "Element.h" -#include "Selection.h" +#include "VisibleSelection.h" namespace WebCore { @@ -47,8 +47,8 @@ public: virtual EditAction editingAction() const; - const Selection& startingSelection() const { return m_startingSelection; } - const Selection& endingSelection() const { return m_endingSelection; } + const VisibleSelection& startingSelection() const { return m_startingSelection; } + const VisibleSelection& endingSelection() const { return m_endingSelection; } Element* startingRootEditableElement() const { return m_startingRootEditableElement.get(); } Element* endingRootEditableElement() const { return m_endingRootEditableElement.get(); } @@ -63,8 +63,8 @@ protected: Document* document() const { return m_document.get(); } - void setStartingSelection(const Selection&); - void setEndingSelection(const Selection&); + void setStartingSelection(const VisibleSelection&); + void setEndingSelection(const VisibleSelection&); PassRefPtr<CSSMutableStyleDeclaration> styleAtPosition(const Position&); void updateLayout() const; @@ -75,8 +75,8 @@ private: virtual void doReapply(); // calls doApply() RefPtr<Document> m_document; - Selection m_startingSelection; - Selection m_endingSelection; + VisibleSelection m_startingSelection; + VisibleSelection m_endingSelection; RefPtr<Element> m_startingRootEditableElement; RefPtr<Element> m_endingRootEditableElement; CompositeEditCommand* m_parent; diff --git a/WebCore/editing/Editor.cpp b/WebCore/editing/Editor.cpp index 510b910..f44449b 100644 --- a/WebCore/editing/Editor.cpp +++ b/WebCore/editing/Editor.cpp @@ -74,9 +74,9 @@ using namespace HTMLNames; // When an event handler has moved the selection outside of a text control // we should use the target control's selection for this editing operation. -Selection Editor::selectionForCommand(Event* event) +VisibleSelection Editor::selectionForCommand(Event* event) { - Selection selection = m_frame->selection()->selection(); + VisibleSelection selection = m_frame->selection()->selection(); if (!event) return selection; // If the target is a text control, and the current selection is outside of its shadow tree, @@ -222,7 +222,7 @@ bool Editor::isSelectTrailingWhitespaceEnabled() bool Editor::deleteWithDirection(SelectionController::EDirection direction, TextGranularity granularity, bool killRing, bool isTypingAction) { - if (!canEdit() || !m_frame->document()) + if (!canEdit()) return false; if (m_frame->selection()->isRange()) { @@ -317,7 +317,7 @@ PassRefPtr<Range> Editor::selectedRange() { if (!m_frame) return 0; - return m_frame->selection()->toRange(); + return m_frame->selection()->toNormalizedRange(); } bool Editor::shouldDeleteRange(Range* range) const @@ -376,14 +376,14 @@ bool Editor::shouldShowDeleteInterface(HTMLElement* element) const return client() && client()->shouldShowDeleteInterface(element); } -void Editor::respondToChangedSelection(const Selection& oldSelection) +void Editor::respondToChangedSelection(const VisibleSelection& oldSelection) { if (client()) client()->respondToChangedSelection(); m_deleteButtonController->respondToChangedSelection(oldSelection); } -void Editor::respondToChangedContents(const Selection& endingSelection) +void Editor::respondToChangedContents(const VisibleSelection& endingSelection) { if (AXObjectCache::accessibilityEnabled()) { Node* node = endingSelection.start().node(); @@ -419,7 +419,7 @@ const SimpleFontData* Editor::fontForSelection(bool& hasMultipleFonts) const const SimpleFontData* font = 0; - RefPtr<Range> range = m_frame->selection()->toRange(); + RefPtr<Range> range = m_frame->selection()->toNormalizedRange(); Node* startNode = range->editingStartPosition().node(); if (startNode) { Node* pastEnd = range->pastLastNode(); @@ -572,7 +572,7 @@ bool Editor::hasBidiSelection() const if (style->direction() == RTL) return true; - return static_cast<RenderBlock*>(renderer)->containsNonZeroBidiLevel(); + return toRenderBlock(renderer)->containsNonZeroBidiLevel(); } TriState Editor::selectionUnorderedListState() const @@ -689,7 +689,7 @@ void Editor::clearLastEditCommand() bool Editor::dispatchCPPEvent(const AtomicString &eventType, ClipboardAccessPolicy policy) { Node* target = m_frame->selection()->start().element(); - if (!target && m_frame->document()) + if (!target) target = m_frame->document()->body(); if (!target) return true; @@ -699,7 +699,7 @@ bool Editor::dispatchCPPEvent(const AtomicString &eventType, ClipboardAccessPoli ExceptionCode ec = 0; RefPtr<Event> evt = ClipboardEvent::create(eventType, true, true, clipboard); - EventTargetNodeCast(target)->dispatchEvent(evt, ec); + target->dispatchEvent(evt, ec); bool noDefaultProcessing = evt->defaultPrevented(); // invalidate clipboard here for security @@ -710,15 +710,15 @@ bool Editor::dispatchCPPEvent(const AtomicString &eventType, ClipboardAccessPoli void Editor::applyStyle(CSSStyleDeclaration* style, EditAction editingAction) { - switch (m_frame->selection()->state()) { - case Selection::NONE: + switch (m_frame->selection()->selectionType()) { + case VisibleSelection::NoSelection: // do nothing break; - case Selection::CARET: + case VisibleSelection::CaretSelection: m_frame->computeAndSetTypingStyle(style, editingAction); break; - case Selection::RANGE: - if (m_frame->document() && style) + case VisibleSelection::RangeSelection: + if (style) applyCommand(ApplyStyleCommand::create(m_frame->document(), style, editingAction)); break; } @@ -731,13 +731,13 @@ bool Editor::shouldApplyStyle(CSSStyleDeclaration* style, Range* range) void Editor::applyParagraphStyle(CSSStyleDeclaration* style, EditAction editingAction) { - switch (m_frame->selection()->state()) { - case Selection::NONE: + switch (m_frame->selection()->selectionType()) { + case VisibleSelection::NoSelection: // do nothing break; - case Selection::CARET: - case Selection::RANGE: - if (m_frame->document() && style) + case VisibleSelection::CaretSelection: + case VisibleSelection::RangeSelection: + if (style) applyCommand(ApplyStyleCommand::create(m_frame->document(), style, editingAction, ApplyStyleCommand::ForceBlockProperties)); break; } @@ -748,7 +748,7 @@ void Editor::applyStyleToSelection(CSSStyleDeclaration* style, EditAction editin if (!style || style->length() == 0 || !canEditRichly()) return; - if (client() && client()->shouldApplyStyle(style, m_frame->selection()->toRange().get())) + if (client() && client()->shouldApplyStyle(style, m_frame->selection()->toNormalizedRange().get())) applyStyle(style, editingAction); } @@ -757,7 +757,7 @@ void Editor::applyParagraphStyleToSelection(CSSStyleDeclaration* style, EditActi if (!style || style->length() == 0 || !canEditRichly()) return; - if (client() && client()->shouldApplyStyle(style, m_frame->selection()->toRange().get())) + if (client() && client()->shouldApplyStyle(style, m_frame->selection()->toNormalizedRange().get())) applyParagraphStyle(style, editingAction); } @@ -870,14 +870,9 @@ void Editor::appliedEditing(PassRefPtr<EditCommand> cmd) { dispatchEditableContentChangedEvents(*cmd); - Selection newSelection(cmd->endingSelection()); - // If there is no selection change, don't bother sending shouldChangeSelection, but still call setSelection, - // because there is work that it must do in this situation. - // The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls. - // See <rdar://problem/5729315> Some shouldChangeSelectedDOMRange contain Ranges for selections that are no longer valid + VisibleSelection newSelection(cmd->endingSelection()); // Don't clear the typing style with this selection change. We do those things elsewhere if necessary. - if (newSelection == m_frame->selection()->selection() || m_frame->shouldChangeSelection(newSelection)) - m_frame->selection()->setSelection(newSelection, false, false); + changeSelectionAfterCommand(newSelection, false, false, cmd.get()); if (!cmd->preservesTypingStyle()) m_frame->setTypingStyle(0); @@ -899,13 +894,8 @@ void Editor::unappliedEditing(PassRefPtr<EditCommand> cmd) { dispatchEditableContentChangedEvents(*cmd); - Selection newSelection(cmd->startingSelection()); - // If there is no selection change, don't bother sending shouldChangeSelection, but still call setSelection, - // because there is work that it must do in this situation. - // The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls. - // See <rdar://problem/5729315> Some shouldChangeSelectedDOMRange contain Ranges for selections that are no longer valid - if (newSelection == m_frame->selection()->selection() || m_frame->shouldChangeSelection(newSelection)) - m_frame->selection()->setSelection(newSelection, true); + VisibleSelection newSelection(cmd->startingSelection()); + changeSelectionAfterCommand(newSelection, true, true, cmd.get()); m_lastEditCommand = 0; if (client()) @@ -917,13 +907,8 @@ void Editor::reappliedEditing(PassRefPtr<EditCommand> cmd) { dispatchEditableContentChangedEvents(*cmd); - Selection newSelection(cmd->endingSelection()); - // If there is no selection change, don't bother sending shouldChangeSelection, but still call setSelection, - // because there is work that it must do in this situation. - // The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls. - // See <rdar://problem/5729315> Some shouldChangeSelectedDOMRange contain Ranges for selections that are no longer valid - if (newSelection == m_frame->selection()->selection() || m_frame->shouldChangeSelection(newSelection)) - m_frame->selection()->setSelection(newSelection, true); + VisibleSelection newSelection(cmd->endingSelection()); + changeSelectionAfterCommand(newSelection, true, true, cmd.get()); m_lastEditCommand = 0; if (client()) @@ -936,6 +921,8 @@ Editor::Editor(Frame* frame) , m_deleteButtonController(new DeleteButtonController(frame)) , m_ignoreCompositionSelectionChange(false) , m_shouldStartNewKillRingSequence(false) + // This is off by default, since most editors want this behavior (this matches IE but not FF). + , m_shouldStyleWithCSS(false) { } @@ -947,6 +934,7 @@ void Editor::clear() { m_compositionNode = 0; m_customCompositionUnderlines.clear(); + m_shouldStyleWithCSS = false; } bool Editor::insertText(const String& text, Event* triggeringEvent) @@ -959,10 +947,10 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn if (text.isEmpty()) return false; - Selection selection = selectionForCommand(triggeringEvent); + VisibleSelection selection = selectionForCommand(triggeringEvent); if (!selection.isContentEditable()) return false; - RefPtr<Range> range = selection.toRange(); + RefPtr<Range> range = selection.toNormalizedRange(); if (!shouldInsertText(text, range.get(), EditorInsertActionTyped)) return true; @@ -981,7 +969,7 @@ bool Editor::insertTextWithoutSendingTextEvent(const String& text, bool selectIn // Reveal the current selection if (Frame* editedFrame = document->frame()) if (Page* page = editedFrame->page()) - page->focusController()->focusedOrMainFrame()->revealSelection(RenderLayer::gAlignToEdgeIfNeeded); + page->focusController()->focusedOrMainFrame()->revealSelection(ScrollAlignment::alignToEdgeIfNeeded); } } @@ -993,7 +981,7 @@ bool Editor::insertLineBreak() if (!canEdit()) return false; - if (!shouldInsertText("\n", m_frame->selection()->toRange().get(), EditorInsertActionTyped)) + if (!shouldInsertText("\n", m_frame->selection()->toNormalizedRange().get(), EditorInsertActionTyped)) return true; TypingCommand::insertLineBreak(m_frame->document()); @@ -1009,7 +997,7 @@ bool Editor::insertParagraphSeparator() if (!canEditRichly()) return insertLineBreak(); - if (!shouldInsertText("\n", m_frame->selection()->toRange().get(), EditorInsertActionTyped)) + if (!shouldInsertText("\n", m_frame->selection()->toNormalizedRange().get(), EditorInsertActionTyped)) return true; TypingCommand::insertParagraphSeparator(m_frame->document()); @@ -1204,8 +1192,8 @@ void Editor::setBaseWritingDirection(WritingDirection direction) { Node* focusedNode = frame()->document()->focusedNode(); if (focusedNode && (focusedNode->hasTagName(textareaTag) - || focusedNode->hasTagName(inputTag) && (static_cast<HTMLInputElement*>(focusedNode)->inputType() == HTMLInputElement::TEXT - || static_cast<HTMLInputElement*>(focusedNode)->inputType() == HTMLInputElement::SEARCH))) { + || (focusedNode->hasTagName(inputTag) && (static_cast<HTMLInputElement*>(focusedNode)->inputType() == HTMLInputElement::TEXT + || static_cast<HTMLInputElement*>(focusedNode)->inputType() == HTMLInputElement::SEARCH)))) { if (direction == NaturalWritingDirection) return; static_cast<HTMLElement*>(focusedNode)->setAttribute(dirAttr, direction == LeftToRightWritingDirection ? "ltr" : "rtl"); @@ -1226,7 +1214,7 @@ void Editor::selectComposition() // The composition can start inside a composed character sequence, so we have to override checks. // See <http://bugs.webkit.org/show_bug.cgi?id=15781> - Selection selection; + VisibleSelection selection; selection.setWithoutValidation(range->startPosition(), range->endPosition()); m_frame->selection()->setSelection(selection, false, false); } @@ -1254,7 +1242,7 @@ void Editor::confirmComposition(const String& text, bool preserveSelection) { setIgnoreCompositionSelectionChange(true); - Selection oldSelection = m_frame->selection()->selection(); + VisibleSelection oldSelection = m_frame->selection()->selection(); selectComposition(); @@ -1302,9 +1290,9 @@ void Editor::setComposition(const String& text, const Vector<CompositionUnderlin TypingCommand::insertText(m_frame->document(), text, true, true); Node* baseNode = m_frame->selection()->base().node(); - unsigned baseOffset = m_frame->selection()->base().offset(); + unsigned baseOffset = m_frame->selection()->base().m_offset; Node* extentNode = m_frame->selection()->extent().node(); - unsigned extentOffset = m_frame->selection()->extent().offset(); + unsigned extentOffset = m_frame->selection()->extent().m_offset; if (baseNode && baseNode == extentNode && baseNode->isTextNode() && baseOffset + text.length() == extentOffset) { m_compositionNode = static_cast<Text*>(baseNode); @@ -1334,7 +1322,7 @@ void Editor::ignoreSpelling() if (!client()) return; - RefPtr<Range> selectedRange = frame()->selection()->toRange(); + RefPtr<Range> selectedRange = frame()->selection()->toNormalizedRange(); if (selectedRange) frame()->document()->removeMarkers(selectedRange.get(), DocumentMarker::Spelling); @@ -1548,6 +1536,130 @@ static String findFirstBadGrammarInRange(EditorClient* client, Range* searchRang #endif /* not BUILDING_ON_TIGER */ +#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + +static String findFirstMisspellingOrBadGrammarInRange(EditorClient* client, Range* searchRange, bool checkGrammar, bool& outIsSpelling, int& outFirstFoundOffset, GrammarDetail& outGrammarDetail) +{ + ASSERT_ARG(client, client); + ASSERT_ARG(searchRange, searchRange); + + String firstFoundItem; + String misspelledWord; + String badGrammarPhrase; + ExceptionCode ec = 0; + + // Initialize out parameters; these will be updated if we find something to return. + outIsSpelling = true; + outFirstFoundOffset = 0; + outGrammarDetail.location = -1; + outGrammarDetail.length = 0; + outGrammarDetail.guesses.clear(); + outGrammarDetail.userDescription = ""; + + // Expand the search range to encompass entire paragraphs, since text checking needs that much context. + // Determine the character offset from the start of the paragraph to the start of the original search range, + // since we will want to ignore results in this area. + RefPtr<Range> paragraphRange = searchRange->cloneRange(ec); + setStart(paragraphRange.get(), startOfParagraph(searchRange->startPosition())); + int totalRangeLength = TextIterator::rangeLength(paragraphRange.get()); + setEnd(paragraphRange.get(), endOfParagraph(searchRange->startPosition())); + + RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), searchRange->startPosition()); + int searchRangeStartOffset = TextIterator::rangeLength(offsetAsRange.get()); + int totalLengthProcessed = 0; + + bool firstIteration = true; + bool lastIteration = false; + while (totalLengthProcessed < totalRangeLength) { + // Iterate through the search range by paragraphs, checking each one for spelling and grammar. + int currentLength = TextIterator::rangeLength(paragraphRange.get()); + int currentStartOffset = firstIteration ? searchRangeStartOffset : 0; + int currentEndOffset = currentLength; + if (inSameParagraph(paragraphRange->startPosition(), searchRange->endPosition())) { + // Determine the character offset from the end of the original search range to the end of the paragraph, + // since we will want to ignore results in this area. + RefPtr<Range> endOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), searchRange->endPosition()); + currentEndOffset = TextIterator::rangeLength(endOffsetAsRange.get()); + lastIteration = true; + } + if (currentStartOffset < currentEndOffset) { + String paragraphString = plainText(paragraphRange.get()); + if (paragraphString.length() > 0) { + bool foundGrammar = false; + int spellingLocation = 0; + int grammarPhraseLocation = 0; + int grammarDetailLocation = 0; + unsigned grammarDetailIndex = 0; + + Vector<TextCheckingResult> results; + client->checkSpellingAndGrammarOfParagraph(paragraphString.characters(), paragraphString.length(), checkGrammar, results); + + for (unsigned i = 0; i < results.size(); i++) { + const TextCheckingResult* result = &results[i]; + if (result->resultType == 1 && result->location >= currentStartOffset && result->location + result->length <= currentEndOffset) { + ASSERT(result->length > 0 && result->location >= 0); + spellingLocation = result->location; + misspelledWord = paragraphString.substring(result->location, result->length); + ASSERT(misspelledWord.length() != 0); + break; + } else if (checkGrammar && result->resultType == 2 && result->location < currentEndOffset && result->location + result->length > currentStartOffset) { + ASSERT(result->length > 0 && result->location >= 0); + // We can't stop after the first grammar result, since there might still be a spelling result after + // it begins but before the first detail in it, but we can stop if we find a second grammar result. + if (foundGrammar) break; + for (unsigned j = 0; j < result->details.size(); j++) { + const GrammarDetail* detail = &result->details[j]; + ASSERT(detail->length > 0 && detail->location >= 0); + if (result->location + detail->location >= currentStartOffset && result->location + detail->location + detail->length <= currentEndOffset && (!foundGrammar || result->location + detail->location < grammarDetailLocation)) { + grammarDetailIndex = j; + grammarDetailLocation = result->location + detail->location; + foundGrammar = true; + } + } + if (foundGrammar) { + grammarPhraseLocation = result->location; + outGrammarDetail = result->details[grammarDetailIndex]; + badGrammarPhrase = paragraphString.substring(result->location, result->length); + ASSERT(badGrammarPhrase.length() != 0); + } + } + } + + if (!misspelledWord.isEmpty() && (!checkGrammar || badGrammarPhrase.isEmpty() || spellingLocation <= grammarDetailLocation)) { + int spellingOffset = spellingLocation - currentStartOffset; + if (!firstIteration) { + RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), searchRange->startPosition(), paragraphRange->startPosition()); + spellingOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get()); + } + outIsSpelling = true; + outFirstFoundOffset = spellingOffset; + firstFoundItem = misspelledWord; + break; + } else if (checkGrammar && !badGrammarPhrase.isEmpty()) { + int grammarPhraseOffset = grammarPhraseLocation - currentStartOffset; + if (!firstIteration) { + RefPtr<Range> paragraphOffsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), searchRange->startPosition(), paragraphRange->startPosition()); + grammarPhraseOffset += TextIterator::rangeLength(paragraphOffsetAsRange.get()); + } + outIsSpelling = false; + outFirstFoundOffset = grammarPhraseOffset; + firstFoundItem = badGrammarPhrase; + break; + } + } + } + if (lastIteration || totalLengthProcessed + currentLength >= totalRangeLength) + break; + setStart(paragraphRange.get(), startOfNextParagraph(paragraphRange->endPosition())); + setEnd(paragraphRange.get(), endOfParagraph(paragraphRange->startPosition())); + firstIteration = false; + totalLengthProcessed += currentLength; + } + return firstFoundItem; +} + +#endif + void Editor::advanceToNextMisspelling(bool startBeforeSelection) { ExceptionCode ec = 0; @@ -1557,7 +1669,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) // Start at the end of the selection, search to edge of document. Starting at the selection end makes // repeated "check spelling" commands work. - Selection selection(frame()->selection()->selection()); + VisibleSelection selection(frame()->selection()->selection()); RefPtr<Range> spellingSearchRange(rangeOfContents(frame()->document())); bool startedWithSelection = false; if (selection.start().node()) { @@ -1584,13 +1696,14 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) return; Position rangeCompliantPosition = rangeCompliantEquivalent(position); - spellingSearchRange->setStart(rangeCompliantPosition.node(), rangeCompliantPosition.offset(), ec); + spellingSearchRange->setStart(rangeCompliantPosition.node(), rangeCompliantPosition.m_offset, ec); startedWithSelection = false; // won't need to wrap } // topNode defines the whole range we want to operate on Node* topNode = highestEditableRoot(position); - spellingSearchRange->setEnd(topNode, maxDeepOffset(topNode), ec); + // FIXME: lastOffsetForEditing() is wrong here if editingIgnoresContent(highestEditableRoot()) returns true (e.g. a <table>) + spellingSearchRange->setEnd(topNode, lastOffsetForEditing(topNode), ec); // If spellingSearchRange starts in the middle of a word, advance to the next word so we start checking // at a word boundary. Going back by one char and then forward by a word does the trick. @@ -1614,7 +1727,24 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) Node *searchEndNodeAfterWrap = spellingSearchRange->endContainer(ec); int searchEndOffsetAfterWrap = spellingSearchRange->endOffset(ec); - int misspellingOffset; + int misspellingOffset = 0; +#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + RefPtr<Range> grammarSearchRange = spellingSearchRange->cloneRange(ec); + String misspelledWord; + String badGrammarPhrase; + int grammarPhraseOffset = 0; + bool isSpelling = true; + int foundOffset = 0; + GrammarDetail grammarDetail; + String foundItem = findFirstMisspellingOrBadGrammarInRange(client(), spellingSearchRange.get(), isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); + if (isSpelling) { + misspelledWord = foundItem; + misspellingOffset = foundOffset; + } else { + badGrammarPhrase = foundItem; + grammarPhraseOffset = foundOffset; + } +#else String misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false); String badGrammarPhrase; @@ -1635,6 +1765,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) if (isGrammarCheckingEnabled()) badGrammarPhrase = findFirstBadGrammarInRange(client(), grammarSearchRange.get(), grammarDetail, grammarPhraseOffset, false); #endif +#endif // If we found neither bad grammar nor a misspelled word, wrap and try again (but don't bother if we started at the beginning of the // block rather than at a selection). @@ -1643,6 +1774,17 @@ 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) + grammarSearchRange = spellingSearchRange->cloneRange(ec); + foundItem = findFirstMisspellingOrBadGrammarInRange(client(), spellingSearchRange.get(), isGrammarCheckingEnabled(), isSpelling, foundOffset, grammarDetail); + if (isSpelling) { + misspelledWord = foundItem; + misspellingOffset = foundOffset; + } else { + badGrammarPhrase = foundItem; + grammarPhraseOffset = foundOffset; + } +#else misspelledWord = findFirstMisspellingInRange(client(), spellingSearchRange.get(), misspellingOffset, false); #ifndef BUILDING_ON_TIGER @@ -1656,6 +1798,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) if (isGrammarCheckingEnabled()) badGrammarPhrase = findFirstBadGrammarInRange(client(), grammarSearchRange.get(), grammarDetail, grammarPhraseOffset, false); #endif +#endif } if (!badGrammarPhrase.isEmpty()) { @@ -1671,7 +1814,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) // FIXME 4859190: This gets confused with doubled punctuation at the end of a paragraph RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarSearchRange.get(), grammarPhraseOffset + grammarDetail.location, grammarDetail.length); - frame()->selection()->setSelection(Selection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY)); + frame()->selection()->setSelection(VisibleSelection(badGrammarRange.get(), SEL_DEFAULT_AFFINITY)); frame()->revealSelection(); client()->updateSpellingUIWithGrammarString(badGrammarPhrase, grammarDetail); @@ -1682,7 +1825,7 @@ void Editor::advanceToNextMisspelling(bool startBeforeSelection) // a marker so we draw the red squiggle later. RefPtr<Range> misspellingRange = TextIterator::subrange(spellingSearchRange.get(), misspellingOffset, misspelledWord.length()); - frame()->selection()->setSelection(Selection(misspellingRange.get(), DOWNSTREAM)); + frame()->selection()->setSelection(VisibleSelection(misspellingRange.get(), DOWNSTREAM)); frame()->revealSelection(); client()->updateSpellingUIWithMisspelledWord(misspelledWord); @@ -1770,7 +1913,7 @@ bool Editor::isSelectionUngrammatical() return false; #else Vector<String> ignoredGuesses; - return isRangeUngrammatical(client(), frame()->selection()->toRange().get(), ignoredGuesses); + return isRangeUngrammatical(client(), frame()->selection()->toNormalizedRange().get(), ignoredGuesses); #endif } @@ -1781,7 +1924,7 @@ Vector<String> Editor::guessesForUngrammaticalSelection() #else Vector<String> guesses; // Ignore the result of isRangeUngrammatical; we just want the guesses, whether or not there are any - isRangeUngrammatical(client(), frame()->selection()->toRange().get(), guesses); + isRangeUngrammatical(client(), frame()->selection()->toNormalizedRange().get(), guesses); return guesses; #endif } @@ -1797,6 +1940,86 @@ Vector<String> Editor::guessesForMisspelledSelection() return guesses; } +#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + +static Vector<String> guessesForMisspelledOrUngrammaticalRange(EditorClient* client, Range *range, bool checkGrammar, bool& misspelled, bool& ungrammatical) +{ + Vector<String> guesses; + ExceptionCode ec; + misspelled = false; + ungrammatical = false; + + if (!client || !range || range->collapsed(ec)) + return guesses; + + // Expand the range to encompass entire paragraphs, since text checking needs that much context. + int rangeStartOffset; + String paragraphString; + RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(range, rangeStartOffset, paragraphString); + int rangeLength = TextIterator::rangeLength(range); + if (rangeLength == 0 || paragraphString.length() == 0) + return guesses; + + Vector<TextCheckingResult> results; + client->checkSpellingAndGrammarOfParagraph(paragraphString.characters(), paragraphString.length(), checkGrammar, results); + + for (unsigned i = 0; i < results.size(); i++) { + const TextCheckingResult* result = &results[i]; + if (result->resultType == 1 && result->location == rangeStartOffset && result->length == rangeLength) { + String misspelledWord = paragraphString.substring(rangeStartOffset, rangeLength); + ASSERT(misspelledWord.length() != 0); + client->getGuessesForWord(misspelledWord, guesses); + client->updateSpellingUIWithMisspelledWord(misspelledWord); + misspelled = true; + return guesses; + } + } + + if (!checkGrammar) + return guesses; + + for (unsigned i = 0; i < results.size(); i++) { + const TextCheckingResult* result = &results[i]; + if (result->resultType == 2 && result->location <= rangeStartOffset && result->location + result->length >= rangeStartOffset + rangeLength) { + for (unsigned j = 0; j < result->details.size(); j++) { + const GrammarDetail* detail = &result->details[j]; + ASSERT(detail->length > 0 && detail->location >= 0); + if (result->location + detail->location == rangeStartOffset && detail->length == rangeLength) { + String badGrammarPhrase = paragraphString.substring(result->location, result->length); + ASSERT(badGrammarPhrase.length() != 0); + for (unsigned k = 0; k < detail->guesses.size(); k++) + guesses.append(detail->guesses[k]); + client->updateSpellingUIWithGrammarString(badGrammarPhrase, *detail); + ungrammatical = true; + return guesses; + } + } + } + } + return guesses; +} + +#endif + +Vector<String> Editor::guessesForMisspelledOrUngrammaticalSelection(bool& misspelled, bool& ungrammatical) +{ +#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + return guessesForMisspelledOrUngrammaticalRange(client(), frame()->selection()->toNormalizedRange().get(), isGrammarCheckingEnabled(), misspelled, ungrammatical); +#else + misspelled = isSelectionMisspelled(); + if (misspelled) { + ungrammatical = false; + return guessesForMisspelledSelection(); + } + if (isGrammarCheckingEnabled() && isSelectionUngrammatical()) { + ungrammatical = true; + return guessesForUngrammaticalSelection(); + } + ungrammatical = false; + return Vector<String>(); +#endif +} + void Editor::showSpellingGuessPanel() { if (!client()) { @@ -1829,14 +2052,24 @@ void Editor::markMisspellingsAfterTypingToPosition(const VisiblePosition &p) if (!isContinuousSpellCheckingEnabled()) return; +#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + VisibleSelection adjacentWords = VisibleSelection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary)); + if (isGrammarCheckingEnabled()) { + VisibleSelection selectedSentence = VisibleSelection(startOfSentence(p), endOfSentence(p)); + markMisspellingsAndBadGrammar(adjacentWords, true, selectedSentence); + } else { + markMisspellingsAndBadGrammar(adjacentWords, false, adjacentWords); + } +#else // Check spelling of one word - markMisspellings(Selection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary))); + markMisspellings(VisibleSelection(startOfWord(p, LeftWordIfOnBoundary), endOfWord(p, RightWordIfOnBoundary))); if (!isGrammarCheckingEnabled()) return; // Check grammar of entire sentence - markBadGrammar(Selection(startOfSentence(p), endOfSentence(p))); + markBadGrammar(VisibleSelection(startOfSentence(p), endOfSentence(p))); +#endif } static void markAllMisspellingsInRange(EditorClient* client, Range* searchRange) @@ -1858,7 +2091,7 @@ static void markAllBadGrammarInRange(EditorClient* client, Range* searchRange) } #endif -static void markMisspellingsOrBadGrammar(Editor* editor, const Selection& selection, bool checkSpelling) +static void markMisspellingsOrBadGrammar(Editor* editor, const VisibleSelection& selection, bool checkSpelling) { // This function is called with a selection already expanded to word boundaries. // Might be nice to assert that here. @@ -1868,7 +2101,7 @@ static void markMisspellingsOrBadGrammar(Editor* editor, const Selection& select if (!editor->isContinuousSpellCheckingEnabled()) return; - RefPtr<Range> searchRange(selection.toRange()); + RefPtr<Range> searchRange(selection.toNormalizedRange()); if (!searchRange) return; @@ -1893,12 +2126,12 @@ static void markMisspellingsOrBadGrammar(Editor* editor, const Selection& select } } -void Editor::markMisspellings(const Selection& selection) +void Editor::markMisspellings(const VisibleSelection& selection) { markMisspellingsOrBadGrammar(this, selection, true); } -void Editor::markBadGrammar(const Selection& selection) +void Editor::markBadGrammar(const VisibleSelection& selection) { #ifndef BUILDING_ON_TIGER markMisspellingsOrBadGrammar(this, selection, false); @@ -1907,6 +2140,78 @@ void Editor::markBadGrammar(const Selection& selection) #endif } +#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + +static void markAllMisspellingsAndBadGrammarInRanges(EditorClient* client, Range *spellingRange, bool markGrammar, Range *grammarRange) +{ + // This function is called with selections already expanded to word boundaries. + ExceptionCode ec; + if (!client || !spellingRange || (markGrammar && !grammarRange)) + return; + + // If we're not in an editable node, bail. + Node* editableNode = spellingRange->startContainer(); + if (!editableNode || !editableNode->isContentEditable()) + return; + + // Expand the range to encompass entire paragraphs, since text checking needs that much context. + int spellingRangeStartOffset = 0; + int spellingRangeEndOffset = 0; + int grammarRangeStartOffset = 0; + int grammarRangeEndOffset = 0; + String paragraphString; + + if (markGrammar) { + // The spelling range should be contained in the paragraph-aligned extension of the grammar range. + RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(grammarRange, grammarRangeStartOffset, paragraphString); + RefPtr<Range> offsetAsRange = Range::create(paragraphRange->startContainer(ec)->document(), paragraphRange->startPosition(), spellingRange->startPosition()); + spellingRangeStartOffset = TextIterator::rangeLength(offsetAsRange.get()); + grammarRangeEndOffset = grammarRangeStartOffset + TextIterator::rangeLength(grammarRange); + } else { + RefPtr<Range> paragraphRange = paragraphAlignedRangeForRange(spellingRange, spellingRangeStartOffset, paragraphString); + } + spellingRangeEndOffset = spellingRangeStartOffset + TextIterator::rangeLength(spellingRange); + if (paragraphString.length() == 0 || (spellingRangeStartOffset >= spellingRangeEndOffset && (!markGrammar || grammarRangeStartOffset >= grammarRangeEndOffset))) + return; + + Vector<TextCheckingResult> results; + client->checkSpellingAndGrammarOfParagraph(paragraphString.characters(), paragraphString.length(), markGrammar, results); + + for (unsigned i = 0; i < results.size(); i++) { + const TextCheckingResult* result = &results[i]; + if (result->resultType == 1 && result->location >= spellingRangeStartOffset && result->location + result->length <= spellingRangeEndOffset) { + ASSERT(result->length > 0 && result->location >= 0); + RefPtr<Range> misspellingRange = TextIterator::subrange(spellingRange, result->location - spellingRangeStartOffset, result->length); + misspellingRange->startContainer(ec)->document()->addMarker(misspellingRange.get(), DocumentMarker::Spelling); + } else if (markGrammar && result->resultType == 2 && result->location < grammarRangeEndOffset && result->location + result->length > grammarRangeStartOffset) { + ASSERT(result->length > 0 && result->location >= 0); + for (unsigned j = 0; j < result->details.size(); j++) { + const GrammarDetail* detail = &result->details[j]; + ASSERT(detail->length > 0 && detail->location >= 0); + if (result->location + detail->location >= grammarRangeStartOffset && result->location + detail->location + detail->length <= grammarRangeEndOffset) { + RefPtr<Range> badGrammarRange = TextIterator::subrange(grammarRange, result->location + detail->location - grammarRangeStartOffset, detail->length); + grammarRange->startContainer(ec)->document()->addMarker(badGrammarRange.get(), DocumentMarker::Grammar, detail->userDescription); + } + } + } + } +} + +#endif + +void Editor::markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection) +{ +#if PLATFORM(MAC) && !defined(BUILDING_ON_TIGER) && !defined(BUILDING_ON_LEOPARD) + if (!isContinuousSpellCheckingEnabled()) + return; + markAllMisspellingsAndBadGrammarInRanges(client(), spellingSelection.toNormalizedRange().get(), markGrammar && isGrammarCheckingEnabled(), grammarSelection.toNormalizedRange().get()); +#else + markMisspellings(spellingSelection); + if (markGrammar) + markBadGrammar(grammarSelection); +#endif +} + PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint) { Document* document = m_frame->documentAtPoint(windowPoint); @@ -1919,8 +2224,8 @@ PassRefPtr<Range> Editor::rangeForPoint(const IntPoint& windowPoint) if (!frameView) return 0; IntPoint framePoint = frameView->windowToContents(windowPoint); - Selection selection(frame->visiblePositionForPoint(framePoint)); - return avoidIntersectionWithNode(selection.toRange().get(), deleteButtonController() ? deleteButtonController()->containerElement() : 0); + VisibleSelection selection(frame->visiblePositionForPoint(framePoint)); + return avoidIntersectionWithNode(selection.toNormalizedRange().get(), m_deleteButtonController->containerElement()); } void Editor::revealSelectionAfterEditingOperation() @@ -1928,7 +2233,7 @@ void Editor::revealSelectionAfterEditingOperation() if (m_ignoreCompositionSelectionChange) return; - m_frame->revealSelection(RenderLayer::gAlignToEdgeIfNeeded); + m_frame->revealSelection(ScrollAlignment::alignToEdgeIfNeeded); } void Editor::setIgnoreCompositionSelectionChange(bool ignore) @@ -1964,13 +2269,13 @@ bool Editor::getCompositionSelection(unsigned& selectionStart, unsigned& selecti if (end.node() != m_compositionNode) return false; - if (static_cast<unsigned>(start.offset()) < m_compositionStart) + if (static_cast<unsigned>(start.m_offset) < m_compositionStart) return false; - if (static_cast<unsigned>(end.offset()) > m_compositionEnd) + if (static_cast<unsigned>(end.m_offset) > m_compositionEnd) return false; - selectionStart = start.offset() - m_compositionStart; - selectionEnd = start.offset() - m_compositionEnd; + selectionStart = start.m_offset - m_compositionStart; + selectionEnd = start.m_offset - m_compositionEnd; return true; } @@ -1979,7 +2284,7 @@ void Editor::transpose() if (!canEdit()) return; - Selection selection = m_frame->selection()->selection(); + VisibleSelection selection = m_frame->selection()->selection(); if (!selection.isCaret()) return; @@ -1995,7 +2300,7 @@ void Editor::transpose() RefPtr<Range> range = makeRange(previous, next); if (!range) return; - Selection newSelection(range.get(), DOWNSTREAM); + VisibleSelection newSelection(range.get(), DOWNSTREAM); // Transpose the two characters. String text = plainText(range.get()); @@ -2070,7 +2375,7 @@ bool Editor::insideVisibleArea(const IntPoint& point) const if (!(container->style()->overflowX() == OHIDDEN || container->style()->overflowY() == OHIDDEN)) return true; - IntRect rectInPageCoords = container->getOverflowClipRect(0, 0); + IntRect rectInPageCoords = container->overflowClipRect(0, 0); IntRect rectInFrameCoords = IntRect(renderer->x() * -1, renderer->y() * -1, rectInPageCoords.width(), rectInPageCoords.height()); @@ -2096,7 +2401,7 @@ bool Editor::insideVisibleArea(Range* range) const if (!(container->style()->overflowX() == OHIDDEN || container->style()->overflowY() == OHIDDEN)) return true; - IntRect rectInPageCoords = container->getOverflowClipRect(0, 0); + IntRect rectInPageCoords = container->overflowClipRect(0, 0); IntRect rectInFrameCoords = IntRect(renderer->x() * -1, renderer->y() * -1, rectInPageCoords.width(), rectInPageCoords.height()); IntRect resultRect = range->boundingBox(); @@ -2186,4 +2491,25 @@ PassRefPtr<Range> Editor::nextVisibleRange(Range* currentRange, const String& ta return lastVisibleRange(target, caseFlag); } +void Editor::changeSelectionAfterCommand(const VisibleSelection& newSelection, bool closeTyping, bool clearTypingStyle, EditCommand* cmd) +{ + // If there is no selection change, don't bother sending shouldChangeSelection, but still call setSelection, + // because there is work that it must do in this situation. + // The old selection can be invalid here and calling shouldChangeSelection can produce some strange calls. + // See <rdar://problem/5729315> Some shouldChangeSelectedDOMRange contain Ranges for selections that are no longer valid + bool selectionDidNotChangeDOMPosition = newSelection == m_frame->selection()->selection(); + if (selectionDidNotChangeDOMPosition || m_frame->shouldChangeSelection(newSelection)) + m_frame->selection()->setSelection(newSelection, closeTyping, clearTypingStyle); + + // Some kinds of deletes and line break insertions change the selection's position within the document without + // changing its position within the DOM. For example when you press return in the following (the caret is marked by ^): + // <div contentEditable="true"><div>^Hello</div></div> + // WebCore inserts <div><br></div> *before* the current block, which correctly moves the paragraph down but which doesn't + // change the caret's DOM position (["hello", 0]). In these situations the above SelectionController::setSelection call + // does not call EditorClient::respondToChangedSelection(), which, on the Mac, sends selection change notifications and + // starts a new kill ring sequence, but we want to do these things (matches AppKit). + if (selectionDidNotChangeDOMPosition && cmd->isTypingCommand()) + client()->respondToChangedSelection(); +} + } // namespace WebCore diff --git a/WebCore/editing/Editor.h b/WebCore/editing/Editor.h index d97f670..ce8fa0f 100644 --- a/WebCore/editing/Editor.h +++ b/WebCore/editing/Editor.h @@ -110,8 +110,8 @@ public: bool shouldDeleteRange(Range*) const; bool shouldApplyStyle(CSSStyleDeclaration*, Range*); - void respondToChangedSelection(const Selection& oldSelection); - void respondToChangedContents(const Selection& endingSelection); + void respondToChangedSelection(const VisibleSelection& oldSelection); + void respondToChangedContents(const VisibleSelection& endingSelection); TriState selectionHasStyle(CSSStyleDeclaration*) const; const SimpleFontData* fontForSelection(bool&) const; @@ -151,6 +151,9 @@ public: bool selectionStartHasStyle(CSSStyleDeclaration*) const; bool clientIsEditable() const; + + void setShouldStyleWithCSS(bool flag) { m_shouldStyleWithCSS = flag; } + bool shouldStyleWithCSS() const { return m_shouldStyleWithCSS; } class Command { public: @@ -192,9 +195,11 @@ public: bool isSelectionMisspelled(); Vector<String> guessesForMisspelledSelection(); Vector<String> guessesForUngrammaticalSelection(); + Vector<String> guessesForMisspelledOrUngrammaticalSelection(bool& misspelled, bool& ungrammatical); void markMisspellingsAfterTypingToPosition(const VisiblePosition&); - void markMisspellings(const Selection&); - void markBadGrammar(const Selection&); + void markMisspellings(const VisibleSelection&); + void markBadGrammar(const VisibleSelection&); + void markMisspellingsAndBadGrammar(const VisibleSelection& spellingSelection, bool markGrammar, const VisibleSelection& grammarSelection); void advanceToNextMisspelling(bool startBeforeSelection = false); void showSpellingGuessPanel(); bool spellingPanelIsShowing(); @@ -250,7 +255,7 @@ public: void clear(); - Selection selectionForCommand(Event*); + VisibleSelection selectionForCommand(Event*); void appendToKillRing(const String&); void prependToKillRing(const String&); @@ -278,6 +283,7 @@ private: Vector<CompositionUnderline> m_customCompositionUnderlines; bool m_ignoreCompositionSelectionChange; bool m_shouldStartNewKillRingSequence; + bool m_shouldStyleWithCSS; bool canDeleteRange(Range*) const; bool canSmartReplaceWithPasteboard(Pasteboard*); @@ -295,6 +301,8 @@ private: PassRefPtr<Range> firstVisibleRange(const String&, bool caseFlag); PassRefPtr<Range> lastVisibleRange(const String&, bool caseFlag); + + void changeSelectionAfterCommand(const VisibleSelection& newSelection, bool closeTyping, bool clearTypingStyle, EditCommand*); }; inline void Editor::setStartNewKillRingSequence(bool flag) diff --git a/WebCore/editing/EditorCommand.cpp b/WebCore/editing/EditorCommand.cpp index 54c73b9..0090df7 100644 --- a/WebCore/editing/EditorCommand.cpp +++ b/WebCore/editing/EditorCommand.cpp @@ -35,13 +35,14 @@ #include "EditorClient.h" #include "Event.h" #include "EventHandler.h" -#include "Frame.h" #include "FormatBlockCommand.h" +#include "Frame.h" #include "HTMLFontElement.h" #include "HTMLImageElement.h" #include "IndentOutdentCommand.h" #include "InsertListCommand.h" #include "Page.h" +#include "RenderBox.h" #include "ReplaceSelectionCommand.h" #include "Scrollbar.h" #include "Settings.h" @@ -184,15 +185,15 @@ static bool executeInsertNode(Frame* frame, PassRefPtr<Node> content) static bool expandSelectionToGranularity(Frame* frame, TextGranularity granularity) { - Selection selection = frame->selection()->selection(); + VisibleSelection selection = frame->selection()->selection(); selection.expandUsingGranularity(granularity); - RefPtr<Range> newRange = selection.toRange(); + RefPtr<Range> newRange = selection.toNormalizedRange(); if (!newRange) return false; ExceptionCode ec = 0; if (newRange->collapsed(ec)) return false; - RefPtr<Range> oldRange = frame->selection()->selection().toRange(); + RefPtr<Range> oldRange = frame->selection()->selection().toNormalizedRange(); EAffinity affinity = frame->selection()->affinity(); if (!frame->editor()->client()->shouldChangeSelectedRange(oldRange.get(), newRange.get(), affinity, false)) return false; @@ -342,7 +343,7 @@ static bool executeDeleteToEndOfParagraph(Frame* frame, Event*, EditorCommandSou static bool executeDeleteToMark(Frame* frame, Event*, EditorCommandSource, const String&) { - RefPtr<Range> mark = frame->mark().toRange(); + RefPtr<Range> mark = frame->mark().toNormalizedRange(); if (mark) { SelectionController* selection = frame->selection(); bool selected = selection->setSelectedRange(unionDOMRanges(mark.get(), frame->editor()->selectedRange().get()).get(), DOWNSTREAM, true); @@ -491,9 +492,9 @@ static bool executeInsertNewlineInQuotedContent(Frame* frame, Event*, EditorComm return true; } -static bool executeInsertOrderedList(Frame* frame, Event*, EditorCommandSource, const String& value) +static bool executeInsertOrderedList(Frame* frame, Event*, EditorCommandSource, const String&) { - applyCommand(InsertListCommand::create(frame->document(), InsertListCommand::OrderedList, value)); + applyCommand(InsertListCommand::create(frame->document(), InsertListCommand::OrderedList)); return true; } @@ -514,9 +515,9 @@ static bool executeInsertText(Frame* frame, Event*, EditorCommandSource, const S return true; } -static bool executeInsertUnorderedList(Frame* frame, Event*, EditorCommandSource, const String& value) +static bool executeInsertUnorderedList(Frame* frame, Event*, EditorCommandSource, const String&) { - applyCommand(InsertListCommand::create(frame->document(), InsertListCommand::UnorderedList, value)); + applyCommand(InsertListCommand::create(frame->document(), InsertListCommand::UnorderedList)); return true; } @@ -826,6 +827,30 @@ static bool executeMoveWordRightAndModifySelection(Frame* frame, Event*, EditorC return true; } +static bool executeMoveToLeftEndOfLine(Frame* frame, Event*, EditorCommandSource, const String&) +{ + frame->selection()->modify(SelectionController::MOVE, SelectionController::LEFT, LineBoundary, true); + return true; +} + +static bool executeMoveToLeftEndOfLineAndModifySelection(Frame* frame, Event*, EditorCommandSource, const String&) +{ + frame->selection()->modify(SelectionController::EXTEND, SelectionController::LEFT, LineBoundary, true); + return true; +} + +static bool executeMoveToRightEndOfLine(Frame* frame, Event*, EditorCommandSource, const String&) +{ + frame->selection()->modify(SelectionController::MOVE, SelectionController::RIGHT, LineBoundary, true); + return true; +} + +static bool executeMoveToRightEndOfLineAndModifySelection(Frame* frame, Event*, EditorCommandSource, const String&) +{ + frame->selection()->modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary, true); + return true; +} + static bool executeOutdent(Frame* frame, Event*, EditorCommandSource, const String&) { applyCommand(IndentOutdentCommand::create(frame->document(), IndentOutdentCommand::Outdent)); @@ -888,7 +913,7 @@ static bool executeSelectSentence(Frame* frame, Event*, EditorCommandSource, con static bool executeSelectToMark(Frame* frame, Event*, EditorCommandSource, const String&) { - RefPtr<Range> mark = frame->mark().toRange(); + RefPtr<Range> mark = frame->mark().toNormalizedRange(); RefPtr<Range> selection = frame->editor()->selectedRange(); if (!mark || !selection) { systemBeep(); @@ -914,6 +939,15 @@ static bool executeStrikethrough(Frame* frame, Event*, EditorCommandSource sourc return executeToggleStyle(frame, source, EditActionChangeAttributes, CSSPropertyWebkitTextDecorationsInEffect, "none", "line-through"); } +static bool executeStyleWithCSS(Frame* frame, Event*, EditorCommandSource, const String& value) +{ + if (value != "false" && value != "true") + return false; + + frame->editor()->setShouldStyleWithCSS(value == "true" ? true : false); + return true; +} + static bool executeSubscript(Frame* frame, Event*, EditorCommandSource source, const String&) { return executeToggleStyle(frame, source, EditActionSubscript, CSSPropertyVerticalAlign, "baseline", "sub"); @@ -926,8 +960,8 @@ static bool executeSuperscript(Frame* frame, Event*, EditorCommandSource source, static bool executeSwapWithMark(Frame* frame, Event*, EditorCommandSource, const String&) { - const Selection& mark = frame->mark(); - const Selection& selection = frame->selection()->selection(); + const VisibleSelection& mark = frame->mark(); + const VisibleSelection& selection = frame->selection()->selection(); if (mark.isNone() || selection.isNone()) { systemBeep(); return false; @@ -1033,20 +1067,20 @@ static bool enabled(Frame*, Event*, EditorCommandSource) static bool enabledVisibleSelection(Frame* frame, Event* event, EditorCommandSource) { // The term "visible" here includes a caret in editable text or a range in any text. - const Selection& selection = frame->editor()->selectionForCommand(event); + const VisibleSelection& selection = frame->editor()->selectionForCommand(event); return (selection.isCaret() && selection.isContentEditable()) || selection.isRange(); } static bool enabledVisibleSelectionAndMark(Frame* frame, Event* event, EditorCommandSource) { - const Selection& selection = frame->editor()->selectionForCommand(event); + const VisibleSelection& selection = frame->editor()->selectionForCommand(event); return ((selection.isCaret() && selection.isContentEditable()) || selection.isRange()) && frame->mark().isCaretOrRange(); } static bool enableCaretInEditableText(Frame* frame, Event* event, EditorCommandSource) { - const Selection& selection = frame->editor()->selectionForCommand(event); + const VisibleSelection& selection = frame->editor()->selectionForCommand(event); return selection.isCaret() && selection.isContentEditable(); } @@ -1138,6 +1172,11 @@ static TriState stateStrikethrough(Frame* frame, Event*) return stateStyle(frame, CSSPropertyTextDecoration, "line-through"); } +static TriState stateStyleWithCSS(Frame* frame, Event*) +{ + return frame->editor()->shouldStyleWithCSS() ? TrueTriState : FalseTriState; +} + static TriState stateSubscript(Frame* frame, Event*) { return stateStyle(frame, CSSPropertyVerticalAlign, "sub"); @@ -1244,8 +1283,8 @@ static const CommandMap& createCommandMap() { "IgnoreSpelling", { executeIgnoreSpelling, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "Indent", { executeIndent, supported, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "InsertBacktab", { executeInsertBacktab, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, isTextInsertion, doNotAllowExecutionWhenDisabled } }, - { "InsertHorizontalRule", { executeInsertHorizontalRule, supported, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "InsertHTML", { executeInsertHTML, supported, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, + { "InsertHorizontalRule", { executeInsertHorizontalRule, supported, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "InsertImage", { executeInsertImage, supported, enabledInRichlyEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "InsertLineBreak", { executeInsertLineBreak, supported, enabledInEditableText, stateNone, valueNull, isTextInsertion, doNotAllowExecutionWhenDisabled } }, { "InsertNewline", { executeInsertNewline, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, isTextInsertion, doNotAllowExecutionWhenDisabled } }, @@ -1296,6 +1335,10 @@ static const CommandMap& createCommandMap() { "MoveToEndOfParagraphAndModifySelection", { executeMoveToEndOfParagraphAndModifySelection, supportedFromMenuOrKeyBinding, enabledVisibleSelection, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "MoveToEndOfSentence", { executeMoveToEndOfSentence, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "MoveToEndOfSentenceAndModifySelection", { executeMoveToEndOfSentenceAndModifySelection, supportedFromMenuOrKeyBinding, enabledVisibleSelection, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, + { "MoveToLeftEndOfLine", { executeMoveToLeftEndOfLine, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, + { "MoveToLeftEndOfLineAndModifySelection", { executeMoveToLeftEndOfLineAndModifySelection, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, + { "MoveToRightEndOfLine", { executeMoveToRightEndOfLine, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, + { "MoveToRightEndOfLineAndModifySelection", { executeMoveToRightEndOfLineAndModifySelection, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "MoveUp", { executeMoveUp, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "MoveUpAndModifySelection", { executeMoveUpAndModifySelection, supportedFromMenuOrKeyBinding, enabledVisibleSelection, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "MoveWordBackward", { executeMoveWordBackward, supportedFromMenuOrKeyBinding, enabledInEditableText, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, @@ -1320,6 +1363,7 @@ static const CommandMap& createCommandMap() { "SelectWord", { executeSelectWord, supportedFromMenuOrKeyBinding, enabledVisibleSelection, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "SetMark", { executeSetMark, supportedFromMenuOrKeyBinding, enabledVisibleSelection, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "Strikethrough", { executeStrikethrough, supported, enabledInRichlyEditableText, stateStrikethrough, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, + { "StyleWithCSS", { executeStyleWithCSS, supported, enabledInRichlyEditableText, stateStyleWithCSS, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "Subscript", { executeSubscript, supported, enabledInRichlyEditableText, stateSubscript, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "Superscript", { executeSuperscript, supported, enabledInRichlyEditableText, stateSuperscript, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, { "SwapWithMark", { executeSwapWithMark, supportedFromMenuOrKeyBinding, enabledVisibleSelectionAndMark, stateNone, valueNull, notTextInsertion, doNotAllowExecutionWhenDisabled } }, @@ -1428,7 +1472,7 @@ bool Editor::Command::execute(const String& parameter, Event* triggeringEvent) c { if (!isEnabled(triggeringEvent)) { // Let certain commands be executed when performed explicitly even if they are disabled. - if (!isSupported() || !m_frame || !m_frame->document() || !m_command->allowExecutionWhenDisabled) + if (!isSupported() || !m_frame || !m_command->allowExecutionWhenDisabled) return false; } m_frame->document()->updateLayoutIgnorePendingStylesheets(); @@ -1447,21 +1491,21 @@ bool Editor::Command::isSupported() const bool Editor::Command::isEnabled(Event* triggeringEvent) const { - if (!isSupported() || !m_frame || !m_frame->document()) + if (!isSupported() || !m_frame) return false; return m_command->isEnabled(m_frame.get(), triggeringEvent, m_source); } TriState Editor::Command::state(Event* triggeringEvent) const { - if (!isSupported() || !m_frame || !m_frame->document()) + if (!isSupported() || !m_frame) return FalseTriState; return m_command->state(m_frame.get(), triggeringEvent); } String Editor::Command::value(Event* triggeringEvent) const { - if (!isSupported() || !m_frame || !m_frame->document()) + if (!isSupported() || !m_frame) return String(); return m_command->value(m_frame.get(), triggeringEvent); } diff --git a/WebCore/editing/FormatBlockCommand.cpp b/WebCore/editing/FormatBlockCommand.cpp index 6a4ee7e..88169be 100644 --- a/WebCore/editing/FormatBlockCommand.cpp +++ b/WebCore/editing/FormatBlockCommand.cpp @@ -63,7 +63,7 @@ bool FormatBlockCommand::modifyRange() setEndingSelection(visibleEnd); doApply(); visibleEnd = endingSelection().visibleEnd(); - setEndingSelection(Selection(visibleStart.deepEquivalent(), visibleEnd.deepEquivalent(), DOWNSTREAM)); + setEndingSelection(VisibleSelection(visibleStart.deepEquivalent(), visibleEnd.deepEquivalent(), DOWNSTREAM)); return true; } @@ -87,7 +87,7 @@ void FormatBlockCommand::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(Selection(visibleStart, visibleEnd.previous(true))); + setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true))); if (endingSelection().isRange() && modifyRange()) return; diff --git a/WebCore/editing/IndentOutdentCommand.cpp b/WebCore/editing/IndentOutdentCommand.cpp index 242aeb0..9444b11 100644 --- a/WebCore/editing/IndentOutdentCommand.cpp +++ b/WebCore/editing/IndentOutdentCommand.cpp @@ -109,7 +109,7 @@ PassRefPtr<Element> IndentOutdentCommand::prepareBlockquoteLevelForInsertion(Vis void IndentOutdentCommand::indentRegion() { - Selection selection = selectionForParagraphIteration(endingSelection()); + VisibleSelection selection = selectionForParagraphIteration(endingSelection()); VisiblePosition startOfSelection = selection.visibleStart(); VisiblePosition endOfSelection = selection.visibleEnd(); int startIndex = indexForVisiblePosition(startOfSelection); @@ -126,7 +126,7 @@ void IndentOutdentCommand::indentRegion() insertNodeAt(blockquote, start); RefPtr<Element> placeholder = createBreakElement(document()); appendNode(placeholder, blockquote); - setEndingSelection(Selection(Position(placeholder.get(), 0), DOWNSTREAM)); + setEndingSelection(VisibleSelection(Position(placeholder.get(), 0), DOWNSTREAM)); return; } @@ -151,7 +151,7 @@ void IndentOutdentCommand::indentRegion() appendNode(placeholder, listItem); } else { // Clone the list element, insert it before the current paragraph, and move the paragraph into it. - RefPtr<Element> clonedList = listNode->cloneElement(); + RefPtr<Element> clonedList = listNode->cloneElementWithoutChildren(); insertNodeBefore(clonedList, enclosingListChild(endOfCurrentParagraph.deepEquivalent().node())); appendNode(listItem, clonedList); appendNode(placeholder, listItem); @@ -191,7 +191,7 @@ void IndentOutdentCommand::indentRegion() RefPtr<Range> startRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), startIndex, 0, true); RefPtr<Range> endRange = TextIterator::rangeFromLocationAndLength(document()->documentElement(), endIndex, 0, true); if (startRange && endRange) - setEndingSelection(Selection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM)); + setEndingSelection(VisibleSelection(startRange->startPosition(), endRange->startPosition(), DOWNSTREAM)); } void IndentOutdentCommand::outdentParagraph() @@ -205,11 +205,11 @@ void IndentOutdentCommand::outdentParagraph() // Use InsertListCommand to remove the selection from the list if (enclosingNode->hasTagName(olTag)) { - applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::OrderedList, "")); + applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::OrderedList)); return; } if (enclosingNode->hasTagName(ulTag)) { - applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::UnorderedList, "")); + applyCommandToComposite(InsertListCommand::create(document(), InsertListCommand::UnorderedList)); return; } @@ -262,13 +262,13 @@ void IndentOutdentCommand::outdentRegion() while (endOfCurrentParagraph != endAfterSelection) { VisiblePosition endOfNextParagraph = endOfParagraph(endOfCurrentParagraph.next()); if (endOfCurrentParagraph == endOfLastParagraph) - setEndingSelection(Selection(originalSelectionEnd, DOWNSTREAM)); + setEndingSelection(VisibleSelection(originalSelectionEnd, DOWNSTREAM)); else setEndingSelection(endOfCurrentParagraph); outdentParagraph(); endOfCurrentParagraph = endOfNextParagraph; } - setEndingSelection(Selection(originalSelectionStart, endingSelection().end(), DOWNSTREAM)); + setEndingSelection(VisibleSelection(originalSelectionStart, endingSelection().end(), DOWNSTREAM)); } void IndentOutdentCommand::doApply() @@ -290,7 +290,7 @@ void IndentOutdentCommand::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(Selection(visibleStart, visibleEnd.previous(true))); + setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true))); if (m_typeOfAction == Indent) indentRegion(); diff --git a/WebCore/editing/InsertLineBreakCommand.cpp b/WebCore/editing/InsertLineBreakCommand.cpp index 2ec71af..a5c588d 100644 --- a/WebCore/editing/InsertLineBreakCommand.cpp +++ b/WebCore/editing/InsertLineBreakCommand.cpp @@ -30,11 +30,12 @@ #include "Document.h" #include "Element.h" #include "Frame.h" +#include "HTMLNames.h" +#include "Range.h" +#include "RenderObject.h" #include "Text.h" #include "VisiblePosition.h" -#include "Range.h" #include "htmlediting.h" -#include "HTMLNames.h" #include "visible_units.h" namespace WebCore { @@ -88,7 +89,7 @@ bool InsertLineBreakCommand::shouldUseBreakElement(const Position& insertionPos) void InsertLineBreakCommand::doApply() { deleteSelection(); - Selection selection = endingSelection(); + VisibleSelection selection = endingSelection(); if (selection.isNone()) return; @@ -116,30 +117,27 @@ void InsertLineBreakCommand::doApply() insertNodeBefore(nodeToInsert->cloneNode(false), nodeToInsert); VisiblePosition endingPosition(Position(nodeToInsert.get(), 0)); - setEndingSelection(Selection(endingPosition)); - } else if (pos.offset() <= caretMinOffset(pos.node())) { + setEndingSelection(VisibleSelection(endingPosition)); + } else if (pos.m_offset <= caretMinOffset(pos.node())) { insertNodeAt(nodeToInsert.get(), pos); // Insert an extra br or '\n' if the just inserted one collapsed. if (!isStartOfParagraph(VisiblePosition(Position(nodeToInsert.get(), 0)))) insertNodeBefore(nodeToInsert->cloneNode(false).get(), nodeToInsert.get()); - setEndingSelection(Selection(positionAfterNode(nodeToInsert.get()), DOWNSTREAM)); + setEndingSelection(VisibleSelection(positionAfterNode(nodeToInsert.get()), DOWNSTREAM)); // If we're inserting after all of the rendered text in a text node, or into a non-text node, // a simple insertion is sufficient. - } else if (pos.offset() >= caretMaxOffset(pos.node()) || !pos.node()->isTextNode()) { + } else if (pos.m_offset >= caretMaxOffset(pos.node()) || !pos.node()->isTextNode()) { insertNodeAt(nodeToInsert.get(), pos); - setEndingSelection(Selection(positionAfterNode(nodeToInsert.get()), DOWNSTREAM)); + setEndingSelection(VisibleSelection(positionAfterNode(nodeToInsert.get()), DOWNSTREAM)); } else { // Split a text node ASSERT(pos.node()->isTextNode()); // Do the split - ExceptionCode ec = 0; Text* textNode = static_cast<Text*>(pos.node()); - RefPtr<Text> textBeforeNode = document()->createTextNode(textNode->substringData(0, selection.start().offset(), ec)); - deleteTextFromNode(textNode, 0, pos.offset()); - insertNodeBefore(textBeforeNode, textNode); + splitTextNode(textNode, pos.m_offset); insertNodeBefore(nodeToInsert, textNode); Position endingPosition = Position(textNode, 0); @@ -160,7 +158,7 @@ void InsertLineBreakCommand::doApply() } } - setEndingSelection(Selection(endingPosition, DOWNSTREAM)); + setEndingSelection(VisibleSelection(endingPosition, DOWNSTREAM)); } // Handle the case where there is a typing style. @@ -172,10 +170,9 @@ 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, Position(nodeToInsert.get(), 0), - Position(nodeToInsert.get(), maxDeepOffset(nodeToInsert.get()))); + applyStyle(typingStyle, firstDeepEditingPositionForNode(nodeToInsert.get()), lastDeepEditingPositionForNode(nodeToInsert.get())); // Even though this applyStyle operates on a Range, it still sets an endingSelection(). - // It tries to set a Selection around the content it operated on. So, that Selection + // 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 // before the line break (if the line break is at the end of a block it isn't selectable). // So, this next call sets the endingSelection() to a caret just after the line break diff --git a/WebCore/editing/InsertListCommand.cpp b/WebCore/editing/InsertListCommand.cpp index 20c63b8..aa48761 100644 --- a/WebCore/editing/InsertListCommand.cpp +++ b/WebCore/editing/InsertListCommand.cpp @@ -39,7 +39,7 @@ using namespace HTMLNames; PassRefPtr<HTMLElement> InsertListCommand::insertList(Document* document, Type type) { - RefPtr<InsertListCommand> insertCommand = new InsertListCommand(document, type, ""); + RefPtr<InsertListCommand> insertCommand = new InsertListCommand(document, type); insertCommand->apply(); return insertCommand->m_listElement; } @@ -54,14 +54,14 @@ HTMLElement* InsertListCommand::fixOrphanedListChild(Node* node) return listElement.get(); } -InsertListCommand::InsertListCommand(Document* document, Type type, const String& id) - : CompositeEditCommand(document), m_type(type), m_id(id), m_forceCreateList(false) +InsertListCommand::InsertListCommand(Document* document, Type type) + : CompositeEditCommand(document), m_type(type), m_forceCreateList(false) { } bool InsertListCommand::modifyRange() { - Selection selection = selectionForParagraphIteration(endingSelection()); + VisibleSelection selection = selectionForParagraphIteration(endingSelection()); ASSERT(selection.isRange()); VisiblePosition startOfSelection = selection.visibleStart(); VisiblePosition endOfSelection = selection.visibleEnd(); @@ -99,7 +99,7 @@ bool InsertListCommand::modifyRange() doApply(); // Fetch the end of the selection, for the reason mentioned above. endOfSelection = endingSelection().visibleEnd(); - setEndingSelection(Selection(startOfSelection, endOfSelection)); + setEndingSelection(VisibleSelection(startOfSelection, endOfSelection)); m_forceCreateList = false; return true; } @@ -123,7 +123,7 @@ void InsertListCommand::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(Selection(visibleStart, visibleEnd.previous(true))); + setEndingSelection(VisibleSelection(visibleStart, visibleEnd.previous(true))); if (endingSelection().isRange() && modifyRange()) return; @@ -148,8 +148,8 @@ void InsertListCommand::doApply() VisiblePosition start; VisiblePosition end; if (listChildNode->hasTagName(liTag)) { - start = VisiblePosition(Position(listChildNode, 0)); - end = VisiblePosition(Position(listChildNode, maxDeepOffset(listChildNode))); + start = firstDeepEditingPositionForNode(listChildNode); + end = lastDeepEditingPositionForNode(listChildNode); nextListChild = listChildNode->nextSibling(); previousListChild = listChildNode->previousSibling(); } else { @@ -230,8 +230,6 @@ void InsertListCommand::doApply() // Create the list. RefPtr<HTMLElement> listElement = m_type == OrderedList ? createOrderedListElement(document()) : createUnorderedListElement(document()); m_listElement = listElement; - if (!m_id.isEmpty()) - listElement->setId(m_id); appendNode(listItemElement, listElement); if (start == end && isBlock(start.deepEquivalent().node())) { diff --git a/WebCore/editing/InsertListCommand.h b/WebCore/editing/InsertListCommand.h index b39f2b5..ecdd2cf 100644 --- a/WebCore/editing/InsertListCommand.h +++ b/WebCore/editing/InsertListCommand.h @@ -36,9 +36,9 @@ class InsertListCommand : public CompositeEditCommand { public: enum Type { OrderedList, UnorderedList }; - static PassRefPtr<InsertListCommand> create(Document* document, Type listType, const String& listID) + static PassRefPtr<InsertListCommand> create(Document* document, Type listType) { - return adoptRef(new InsertListCommand(document, listType, listID)); + return adoptRef(new InsertListCommand(document, listType)); } static PassRefPtr<HTMLElement> insertList(Document*, Type); @@ -46,7 +46,7 @@ public: virtual bool preservesTypingStyle() const { return true; } private: - InsertListCommand(Document*, Type, const String&); + InsertListCommand(Document*, Type); virtual void doApply(); virtual EditAction editingAction() const { return EditActionInsertList; } @@ -55,7 +55,6 @@ private: bool modifyRange(); RefPtr<HTMLElement> m_listElement; Type m_type; - String m_id; bool m_forceCreateList; }; diff --git a/WebCore/editing/InsertParagraphSeparatorCommand.cpp b/WebCore/editing/InsertParagraphSeparatorCommand.cpp index 30e006e..0e9c291 100644 --- a/WebCore/editing/InsertParagraphSeparatorCommand.cpp +++ b/WebCore/editing/InsertParagraphSeparatorCommand.cpp @@ -106,42 +106,42 @@ void InsertParagraphSeparatorCommand::doApply() if (endingSelection().isNone()) return; - Position pos = endingSelection().start(); + Position insertionPosition = endingSelection().start(); EAffinity affinity = endingSelection().affinity(); // Delete the current selection. if (endingSelection().isRange()) { - calculateStyleBeforeInsertion(pos); + calculateStyleBeforeInsertion(insertionPosition); deleteSelection(false, true); - pos = endingSelection().start(); + insertionPosition = endingSelection().start(); affinity = endingSelection().affinity(); } // FIXME: The rangeCompliantEquivalent conversion needs to be moved into enclosingBlock. - Node* startBlockNode = enclosingBlock(rangeCompliantEquivalent(pos).node()); - Position canonicalPos = VisiblePosition(pos).deepEquivalent(); + Node* startBlockNode = enclosingBlock(rangeCompliantEquivalent(insertionPosition).node()); + Position canonicalPos = VisiblePosition(insertionPosition).deepEquivalent(); Element* startBlock = static_cast<Element*>(startBlockNode); if (!startBlockNode || !startBlockNode->isElementNode() || !startBlock->parentNode() || isTableCell(startBlock) || startBlock->hasTagName(formTag) - || canonicalPos.node()->renderer() && canonicalPos.node()->renderer()->isTable() + || (canonicalPos.node()->renderer() && canonicalPos.node()->renderer()->isTable()) || canonicalPos.node()->hasTagName(hrTag)) { applyCommandToComposite(InsertLineBreakCommand::create(document())); return; } // Use the leftmost candidate. - pos = pos.upstream(); - if (!pos.isCandidate()) - pos = pos.downstream(); + insertionPosition = insertionPosition.upstream(); + if (!insertionPosition.isCandidate()) + insertionPosition = insertionPosition.downstream(); // Adjust the insertion position after the delete - pos = positionAvoidingSpecialElementBoundary(pos); - VisiblePosition visiblePos(pos, affinity); - calculateStyleBeforeInsertion(pos); + insertionPosition = positionAvoidingSpecialElementBoundary(insertionPosition); + VisiblePosition visiblePos(insertionPosition, affinity); + calculateStyleBeforeInsertion(insertionPosition); //--------------------------------------------------------------------- // Handle special case of typing return on an empty list item @@ -150,10 +150,6 @@ void InsertParagraphSeparatorCommand::doApply() //--------------------------------------------------------------------- // Prepare for more general cases. - // FIXME: We shouldn't peel off the node here because then we lose track of - // the fact that it's the node that belongs to an editing position and - // not a rangeCompliantEquivalent. - Node *startNode = pos.node(); bool isFirstInBlock = isStartOfBlock(visiblePos); bool isLastInBlock = isEndOfBlock(visiblePos); @@ -167,7 +163,7 @@ void InsertParagraphSeparatorCommand::doApply() } else if (shouldUseDefaultParagraphElement(startBlock)) blockToInsert = createDefaultParagraphElement(document()); else - blockToInsert = startBlock->cloneElement(); + blockToInsert = startBlock->cloneElementWithoutChildren(); //--------------------------------------------------------------------- // Handle case when position is in the last visible position in its block, @@ -186,7 +182,7 @@ void InsertParagraphSeparatorCommand::doApply() insertNodeAfter(blockToInsert, startBlock); appendBlockPlaceholder(blockToInsert); - setEndingSelection(Selection(Position(blockToInsert.get(), 0), DOWNSTREAM)); + setEndingSelection(VisibleSelection(Position(blockToInsert.get(), 0), DOWNSTREAM)); applyStyleAfterInsertion(startBlock); return; } @@ -198,20 +194,20 @@ void InsertParagraphSeparatorCommand::doApply() Node *refNode; if (isFirstInBlock && !nestNewBlock) refNode = startBlock; - else if (pos.node() == startBlock && nestNewBlock) { - refNode = startBlock->childNode(pos.offset()); + else if (insertionPosition.node() == startBlock && nestNewBlock) { + refNode = startBlock->childNode(insertionPosition.m_offset); ASSERT(refNode); // must be true or we'd be in the end of block case } else - refNode = pos.node(); + refNode = insertionPosition.node(); // find ending selection position easily before inserting the paragraph - pos = pos.downstream(); + insertionPosition = insertionPosition.downstream(); insertNodeBefore(blockToInsert, refNode); appendBlockPlaceholder(blockToInsert.get()); - setEndingSelection(Selection(Position(blockToInsert.get(), 0), DOWNSTREAM)); + setEndingSelection(VisibleSelection(Position(blockToInsert.get(), 0), DOWNSTREAM)); applyStyleAfterInsertion(startBlock); - setEndingSelection(Selection(pos, DOWNSTREAM)); + setEndingSelection(VisibleSelection(insertionPosition, DOWNSTREAM)); return; } @@ -224,41 +220,45 @@ void InsertParagraphSeparatorCommand::doApply() // content will move down a line. if (isStartOfParagraph(visiblePos)) { RefPtr<Element> br = createBreakElement(document()); - insertNodeAt(br.get(), pos); - pos = positionAfterNode(br.get()); + insertNodeAt(br.get(), insertionPosition); + insertionPosition = positionAfterNode(br.get()); } // Move downstream. Typing style code will take care of carrying along the // style of the upstream position. - pos = pos.downstream(); - startNode = pos.node(); + insertionPosition = insertionPosition.downstream(); + + // At this point, the insertionPosition's node could be a container, and we want to make sure we include + // all of the correct nodes when building the ancestor list. So this needs to be the deepest representation of the position + // before we walk the DOM tree. + insertionPosition = VisiblePosition(insertionPosition).deepEquivalent(); // Build up list of ancestors in between the start node and the start block. Vector<Element*> ancestors; - if (startNode != startBlock) { - for (Element* n = startNode->parentElement(); n && n != startBlock; n = n->parentElement()) + if (insertionPosition.node() != startBlock) { + for (Element* n = insertionPosition.node()->parentElement(); n && n != startBlock; n = n->parentElement()) ancestors.append(n); } // Make sure we do not cause a rendered space to become unrendered. // FIXME: We need the affinity for pos, but pos.downstream() does not give it - Position leadingWhitespace = pos.leadingWhitespacePosition(VP_DEFAULT_AFFINITY); + Position leadingWhitespace = insertionPosition.leadingWhitespacePosition(VP_DEFAULT_AFFINITY); // FIXME: leadingWhitespacePosition is returning the position before preserved newlines for positions // after the preserved newline, causing the newline to be turned into a nbsp. if (leadingWhitespace.isNotNull()) { Text* textNode = static_cast<Text*>(leadingWhitespace.node()); ASSERT(!textNode->renderer() || textNode->renderer()->style()->collapseWhiteSpace()); - replaceTextInNode(textNode, leadingWhitespace.offset(), 1, nonBreakingSpaceString()); + replaceTextInNode(textNode, leadingWhitespace.m_offset, 1, nonBreakingSpaceString()); } // Split at pos if in the middle of a text node. - if (startNode->isTextNode()) { - Text *textNode = static_cast<Text *>(startNode); - bool atEnd = (unsigned)pos.offset() >= textNode->length(); - if (pos.offset() > 0 && !atEnd) { - splitTextNode(textNode, pos.offset()); - pos = Position(startNode, 0); - visiblePos = VisiblePosition(pos); + if (insertionPosition.node()->isTextNode()) { + Text* textNode = static_cast<Text*>(insertionPosition.node()); + bool atEnd = (unsigned)insertionPosition.m_offset >= textNode->length(); + if (insertionPosition.m_offset > 0 && !atEnd) { + splitTextNode(textNode, insertionPosition.m_offset); + insertionPosition.m_offset = 0; + visiblePos = VisiblePosition(insertionPosition); splitText = true; } } @@ -274,7 +274,7 @@ void InsertParagraphSeparatorCommand::doApply() // Make clones of ancestors in between the start node and the start block. RefPtr<Element> parent = blockToInsert; for (size_t i = ancestors.size(); i != 0; --i) { - RefPtr<Element> child = ancestors[i - 1]->cloneElement(); // shallow clone + RefPtr<Element> child = ancestors[i - 1]->cloneElementWithoutChildren(); appendNode(child, parent); parent = child.release(); } @@ -286,10 +286,10 @@ void InsertParagraphSeparatorCommand::doApply() appendNode(createBreakElement(document()).get(), blockToInsert.get()); // Move the start node and the siblings of the start node. - if (startNode != startBlock) { - Node *n = startNode; - if (pos.offset() >= caretMaxOffset(startNode)) - n = startNode->nextSibling(); + if (insertionPosition.node() != startBlock) { + Node* n = insertionPosition.node(); + if (insertionPosition.m_offset >= caretMaxOffset(n)) + n = n->nextSibling(); while (n && n != blockToInsert) { Node *next = n->nextSibling(); @@ -320,18 +320,17 @@ void InsertParagraphSeparatorCommand::doApply() // Handle whitespace that occurs after the split if (splitText) { updateLayout(); - pos = Position(startNode, 0); - if (!pos.isRenderedCharacter()) { + insertionPosition = Position(insertionPosition.node(), 0); + if (!insertionPosition.isRenderedCharacter()) { // Clear out all whitespace and insert one non-breaking space - ASSERT(startNode); - ASSERT(startNode->isTextNode()); - ASSERT(!startNode->renderer() || startNode->renderer()->style()->collapseWhiteSpace()); - deleteInsignificantTextDownstream(pos); - insertTextIntoNode(static_cast<Text*>(startNode), 0, nonBreakingSpaceString()); + ASSERT(insertionPosition.node()->isTextNode()); + ASSERT(!insertionPosition.node()->renderer() || insertionPosition.node()->renderer()->style()->collapseWhiteSpace()); + deleteInsignificantTextDownstream(insertionPosition); + insertTextIntoNode(static_cast<Text*>(insertionPosition.node()), 0, nonBreakingSpaceString()); } } - setEndingSelection(Selection(Position(blockToInsert.get(), 0), DOWNSTREAM)); + setEndingSelection(VisibleSelection(Position(blockToInsert.get(), 0), DOWNSTREAM)); applyStyleAfterInsertion(startBlock); } diff --git a/WebCore/editing/InsertTextCommand.cpp b/WebCore/editing/InsertTextCommand.cpp index 46e9a94..52da5d0 100644 --- a/WebCore/editing/InsertTextCommand.cpp +++ b/WebCore/editing/InsertTextCommand.cpp @@ -89,19 +89,19 @@ bool InsertTextCommand::performTrivialReplace(const String& text, bool selectIns if (start.node() != end.node() || !start.node()->isTextNode() || isTabSpanTextNode(start.node())) return false; - replaceTextInNode(static_cast<Text*>(start.node()), start.offset(), end.offset() - start.offset(), text); + replaceTextInNode(static_cast<Text*>(start.node()), start.m_offset, end.m_offset - start.m_offset, text); - Position endPosition(start.node(), start.offset() + text.length()); + Position endPosition(start.node(), start.m_offset + text.length()); // We could have inserted a part of composed character sequence, // so we are basically treating ending selection as a range to avoid validation. // <http://bugs.webkit.org/show_bug.cgi?id=15781> - Selection forcedEndingSelection; + VisibleSelection forcedEndingSelection; forcedEndingSelection.setWithoutValidation(start, endPosition); setEndingSelection(forcedEndingSelection); if (!selectInsertedText) - setEndingSelection(Selection(endingSelection().visibleEnd())); + setEndingSelection(VisibleSelection(endingSelection().visibleEnd())); return true; } @@ -155,7 +155,7 @@ void InsertTextCommand::input(const String& originalText, bool selectInsertedTex startPosition = prepareForTextInsertion(startPosition); removePlaceholderAt(VisiblePosition(startPosition)); Text *textNode = static_cast<Text *>(startPosition.node()); - int offset = startPosition.offset(); + int offset = startPosition.m_offset; insertTextIntoNode(textNode, offset, text); endPosition = Position(textNode, offset + text.length()); @@ -172,7 +172,7 @@ void InsertTextCommand::input(const String& originalText, bool selectInsertedTex // We could have inserted a part of composed character sequence, // so we are basically treating ending selection as a range to avoid validation. // <http://bugs.webkit.org/show_bug.cgi?id=15781> - Selection forcedEndingSelection; + VisibleSelection forcedEndingSelection; forcedEndingSelection.setWithoutValidation(startPosition, endPosition); setEndingSelection(forcedEndingSelection); @@ -199,7 +199,7 @@ void InsertTextCommand::input(const String& originalText, bool selectInsertedTex applyStyle(typingStyle); if (!selectInsertedText) - setEndingSelection(Selection(endingSelection().end(), endingSelection().affinity())); + setEndingSelection(VisibleSelection(endingSelection().end(), endingSelection().affinity())); } Position InsertTextCommand::insertTab(const Position& pos) @@ -207,7 +207,7 @@ Position InsertTextCommand::insertTab(const Position& pos) Position insertPos = VisiblePosition(pos, DOWNSTREAM).deepEquivalent(); Node *node = insertPos.node(); - unsigned int offset = insertPos.offset(); + unsigned int offset = insertPos.m_offset; // keep tabs coalesced in tab span if (isTabSpanTextNode(node)) { diff --git a/WebCore/editing/ModifySelectionListLevel.cpp b/WebCore/editing/ModifySelectionListLevel.cpp index 9bc73c6..5ea658c 100644 --- a/WebCore/editing/ModifySelectionListLevel.cpp +++ b/WebCore/editing/ModifySelectionListLevel.cpp @@ -46,7 +46,7 @@ bool ModifySelectionListLevelCommand::preservesTypingStyle() const } // This needs to be static so it can be called by canIncreaseSelectionListLevel and canDecreaseSelectionListLevel -static bool getStartEndListChildren(const Selection& selection, Node*& start, Node*& end) +static bool getStartEndListChildren(const VisibleSelection& selection, Node*& start, Node*& end) { if (selection.isNone()) return false; @@ -79,8 +79,8 @@ static bool getStartEndListChildren(const Selection& selection, Node*& start, No // if the selection ends on a list item with a sublist, include the entire sublist if (endListChild->renderer()->isListItem()) { RenderObject* r = endListChild->renderer()->nextSibling(); - if (r && isListElement(r->element())) - endListChild = r->element(); + if (r && isListElement(r->node())) + endListChild = r->node(); } start = startListChild; @@ -141,7 +141,7 @@ IncreaseSelectionListLevelCommand::IncreaseSelectionListLevelCommand(Document* d } // This needs to be static so it can be called by canIncreaseSelectionListLevel -static bool canIncreaseListLevel(const Selection& selection, Node*& start, Node*& end) +static bool canIncreaseListLevel(const VisibleSelection& selection, Node*& start, Node*& end) { if (!getStartEndListChildren(selection, start, end)) return false; @@ -175,7 +175,7 @@ void IncreaseSelectionListLevelCommand::doApply() if (!canIncreaseListLevel(endingSelection(), startListChild, endListChild)) return; - Node* previousItem = startListChild->renderer()->previousSibling()->element(); + Node* previousItem = startListChild->renderer()->previousSibling()->node(); if (isListElement(previousItem)) { // move nodes up into preceding list appendSiblingNodeRange(startListChild, endListChild, static_cast<Element*>(previousItem)); @@ -187,7 +187,7 @@ void IncreaseSelectionListLevelCommand::doApply() case InheritedListType: newParent = startListChild->parentElement(); if (newParent) - newParent = newParent->cloneElement(); + newParent = newParent->cloneElementWithoutChildren(); break; case OrderedList: newParent = createOrderedListElement(document()); @@ -239,7 +239,7 @@ DecreaseSelectionListLevelCommand::DecreaseSelectionListLevelCommand(Document* d } // This needs to be static so it can be called by canDecreaseSelectionListLevel -static bool canDecreaseListLevel(const Selection& selection, Node*& start, Node*& end) +static bool canDecreaseListLevel(const VisibleSelection& selection, Node*& start, Node*& end) { if (!getStartEndListChildren(selection, start, end)) return false; @@ -258,8 +258,8 @@ void DecreaseSelectionListLevelCommand::doApply() if (!canDecreaseListLevel(endingSelection(), startListChild, endListChild)) return; - Node* previousItem = startListChild->renderer()->previousSibling() ? startListChild->renderer()->previousSibling()->element() : 0; - Node* nextItem = endListChild->renderer()->nextSibling() ? endListChild->renderer()->nextSibling()->element() : 0; + Node* previousItem = startListChild->renderer()->previousSibling() ? startListChild->renderer()->previousSibling()->node() : 0; + Node* nextItem = endListChild->renderer()->nextSibling() ? endListChild->renderer()->nextSibling()->node() : 0; Element* listNode = startListChild->parentElement(); if (!previousItem) { diff --git a/WebCore/editing/MoveSelectionCommand.cpp b/WebCore/editing/MoveSelectionCommand.cpp index 08587cb..2c656e7 100644 --- a/WebCore/editing/MoveSelectionCommand.cpp +++ b/WebCore/editing/MoveSelectionCommand.cpp @@ -39,7 +39,7 @@ MoveSelectionCommand::MoveSelectionCommand(PassRefPtr<DocumentFragment> fragment void MoveSelectionCommand::doApply() { - Selection selection = endingSelection(); + VisibleSelection selection = endingSelection(); ASSERT(selection.isRange()); Position pos = m_position; @@ -48,14 +48,14 @@ void MoveSelectionCommand::doApply() // Update the position otherwise it may become invalid after the selection is deleted. Node *positionNode = m_position.node(); - int positionOffset = m_position.offset(); + int positionOffset = m_position.m_offset; Position selectionEnd = selection.end(); - int selectionEndOffset = selectionEnd.offset(); + int selectionEndOffset = selectionEnd.m_offset; if (selectionEnd.node() == positionNode && selectionEndOffset < positionOffset) { positionOffset -= selectionEndOffset; Position selectionStart = selection.start(); if (selectionStart.node() == positionNode) { - positionOffset += selectionStart.offset(); + positionOffset += selectionStart.m_offset; } pos = Position(positionNode, positionOffset); } @@ -69,7 +69,11 @@ void MoveSelectionCommand::doApply() if (!pos.node()->inDocument()) pos = endingSelection().start(); - setEndingSelection(Selection(pos, endingSelection().affinity())); + setEndingSelection(VisibleSelection(pos, endingSelection().affinity())); + if (!positionNode->inDocument()) { + // Document was modified out from under us. + return; + } applyCommandToComposite(ReplaceSelectionCommand::create(positionNode->document(), m_fragment, true, m_smartMove)); } diff --git a/WebCore/editing/RemoveFormatCommand.cpp b/WebCore/editing/RemoveFormatCommand.cpp index 80e0ee7..609ab0e 100644 --- a/WebCore/editing/RemoveFormatCommand.cpp +++ b/WebCore/editing/RemoveFormatCommand.cpp @@ -30,7 +30,7 @@ #include "Editor.h" #include "Frame.h" #include "HTMLNames.h" -#include "Selection.h" +#include "VisibleSelection.h" #include "SelectionController.h" #include "TextIterator.h" #include "TypingCommand.h" @@ -49,7 +49,7 @@ void RemoveFormatCommand::doApply() Frame* frame = document()->frame(); // Make a plain text string from the selection to remove formatting like tables and lists. - String string = plainText(frame->selection()->selection().toRange().get()); + String string = plainText(frame->selection()->selection().toNormalizedRange().get()); // Get the default style for this editable root, it's the style that we'll give the // content that we're operating on. diff --git a/WebCore/editing/ReplaceSelectionCommand.cpp b/WebCore/editing/ReplaceSelectionCommand.cpp index 15fc736..09ad985 100644 --- a/WebCore/editing/ReplaceSelectionCommand.cpp +++ b/WebCore/editing/ReplaceSelectionCommand.cpp @@ -61,7 +61,7 @@ enum EFragmentType { EmptyFragment, SingleTextNodeFragment, TreeFragment }; class ReplacementFragment : Noncopyable { public: - ReplacementFragment(Document*, DocumentFragment*, bool matchStyle, const Selection&); + ReplacementFragment(Document*, DocumentFragment*, bool matchStyle, const VisibleSelection&); Node* firstChild() const; Node* lastChild() const; @@ -103,7 +103,7 @@ static bool isInterchangeConvertedSpaceSpan(const Node *node) static_cast<const HTMLElement *>(node)->getAttribute(classAttr) == convertedSpaceSpanClassString; } -ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* fragment, bool matchStyle, const Selection& selection) +ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* fragment, bool matchStyle, const VisibleSelection& selection) : m_document(document), m_fragment(fragment), m_matchStyle(matchStyle), @@ -126,8 +126,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f if (!editableRoot->inlineEventListenerForType(eventNames().webkitBeforeTextInsertedEvent) && // FIXME: Remove these checks once textareas and textfields actually register an event handler. - !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextField()) && - !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextArea()) && + !(shadowAncestorNode && shadowAncestorNode->renderer() && shadowAncestorNode->renderer()->isTextControl()) && editableRoot->isContentRichlyEditable()) { removeInterchangeNodes(m_fragment.get()); return; @@ -136,7 +135,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f Node* styleNode = selection.base().node(); RefPtr<Node> holder = insertFragmentForTestRendering(styleNode); - RefPtr<Range> range = Selection::selectionFromContentsOfNode(holder.get()).toRange(); + RefPtr<Range> range = VisibleSelection::selectionFromContentsOfNode(holder.get()).toNormalizedRange(); String text = plainText(range.get()); // Give the root a chance to change the text. RefPtr<BeforeTextInsertedEvent> evt = BeforeTextInsertedEvent::create(text); @@ -147,7 +146,7 @@ ReplacementFragment::ReplacementFragment(Document* document, DocumentFragment* f restoreTestRenderingNodesToFragment(holder.get()); removeNode(holder); - m_fragment = createFragmentFromText(selection.toRange().get(), evt->text()); + m_fragment = createFragmentFromText(selection.toNormalizedRange().get(), evt->text()); if (!m_fragment->firstChild()) return; holder = insertFragmentForTestRendering(styleNode); @@ -406,6 +405,23 @@ void ReplaceSelectionCommand::removeNodeAndPruneAncestors(Node* node) m_firstNodeInserted = m_lastLeafInserted && m_lastLeafInserted->inDocument() ? afterFirst : 0; } +static bool isHeaderElement(Node* a) +{ + if (!a) + return false; + + return a->hasTagName(h1Tag) || + a->hasTagName(h2Tag) || + a->hasTagName(h3Tag) || + a->hasTagName(h4Tag) || + a->hasTagName(h5Tag); +} + +static bool haveSameTagName(Node* a, Node* b) +{ + return a && b && a->isElementNode() && b->isElementNode() && static_cast<Element*>(a)->tagName() == static_cast<Element*>(b)->tagName(); +} + bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& source, const VisiblePosition& destination) { if (source.isNull() || destination.isNull()) @@ -414,10 +430,12 @@ bool ReplaceSelectionCommand::shouldMerge(const VisiblePosition& source, const V Node* sourceNode = source.deepEquivalent().node(); Node* destinationNode = destination.deepEquivalent().node(); Node* sourceBlock = enclosingBlock(sourceNode); + Node* destinationBlock = enclosingBlock(destinationNode); return !enclosingNodeOfType(source.deepEquivalent(), &isMailPasteAsQuotationNode) && sourceBlock && (!sourceBlock->hasTagName(blockquoteTag) || isMailBlockquote(sourceBlock)) && enclosingListChild(sourceBlock) == enclosingListChild(destinationNode) && enclosingTableCell(source.deepEquivalent()) == enclosingTableCell(destination.deepEquivalent()) && + (!isHeaderElement(sourceBlock) || haveSameTagName(sourceBlock, destinationBlock)) && // Don't merge to or from a position before or after a block because it would // be a no-op and cause infinite recursion. !isBlock(sourceNode) && !isBlock(destinationNode); @@ -491,10 +509,11 @@ void ReplaceSelectionCommand::handlePasteAsQuotationNode() VisiblePosition ReplaceSelectionCommand::positionAtEndOfInsertedContent() { Node* lastNode = m_lastLeafInserted.get(); - Node* enclosingSelect = enclosingNodeWithTag(Position(lastNode, 0), selectTag); + // FIXME: Why is this hack here? What's special about <select> tags? + Node* enclosingSelect = enclosingNodeWithTag(firstDeepEditingPositionForNode(lastNode), selectTag); if (enclosingSelect) lastNode = enclosingSelect; - return VisiblePosition(Position(lastNode, maxDeepOffset(lastNode))); + return lastDeepEditingPositionForNode(lastNode); } VisiblePosition ReplaceSelectionCommand::positionAtStartOfInsertedContent() @@ -509,8 +528,9 @@ static bool handleStyleSpansBeforeInsertion(ReplacementFragment& fragment, const { Node* topNode = fragment.firstChild(); - // Handling this case is more complicated (see handleStyleSpans) and doesn't receive the optimization. - if (isMailPasteAsQuotationNode(topNode)) + // 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)) return false; // Either there are no style spans in the fragment or a WebKit client has added content to the fragment @@ -573,11 +593,12 @@ void ReplaceSelectionCommand::handleStyleSpans() RefPtr<CSSMutableStyleDeclaration> sourceDocumentStyle = static_cast<HTMLElement*>(sourceDocumentStyleSpan)->getInlineStyleDecl()->copy(); Node* context = sourceDocumentStyleSpan->parentNode(); - // If Mail wraps the fragment with a Paste as Quotation blockquote, styles from that element are - // allowed to override those from the source document, see <rdar://problem/4930986>. - if (isMailPasteAsQuotationNode(context)) { - RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = computedStyle(context)->copyInheritableProperties(); - RefPtr<CSSMutableStyleDeclaration> parentStyle = computedStyle(context->parentNode())->copyInheritableProperties(); + // 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); + if (blockquoteNode) { + RefPtr<CSSMutableStyleDeclaration> blockquoteStyle = computedStyle(blockquoteNode)->copyInheritableProperties(); + RefPtr<CSSMutableStyleDeclaration> parentStyle = computedStyle(blockquoteNode->parentNode())->copyInheritableProperties(); parentStyle->diff(blockquoteStyle.get()); CSSMutableStyleDeclaration::const_iterator end = blockquoteStyle->end(); @@ -586,7 +607,7 @@ void ReplaceSelectionCommand::handleStyleSpans() sourceDocumentStyle->removeProperty(property.id()); } - context = context->parentNode(); + context = blockquoteNode->parentNode(); } RefPtr<CSSMutableStyleDeclaration> contextStyle = computedStyle(context)->copyInheritableProperties(); @@ -679,7 +700,7 @@ void ReplaceSelectionCommand::mergeEndIfNeeded() void ReplaceSelectionCommand::doApply() { - Selection selection = endingSelection(); + VisibleSelection selection = endingSelection(); ASSERT(selection.isCaretOrRange()); ASSERT(selection.start().node()); if (selection.isNone() || !selection.start().node()) @@ -704,9 +725,9 @@ void ReplaceSelectionCommand::doApply() Position insertionPos = selection.start(); bool startIsInsideMailBlockquote = nearestMailBlockquote(insertionPos.node()); - if (selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !startIsInsideMailBlockquote || + if ((selectionStartWasStartOfParagraph && selectionEndWasEndOfParagraph && !startIsInsideMailBlockquote) || startBlock == currentRoot || - startBlock && startBlock->renderer() && startBlock->renderer()->isListItem() || + (startBlock && startBlock->renderer() && startBlock->renderer()->isListItem()) || selectionIsPlainText) m_preventNesting = false; @@ -821,11 +842,20 @@ void ReplaceSelectionCommand::doApply() fragment.removeNode(refNode); insertNodeAtAndUpdateNodesInserted(refNode, insertionPos); - + + // Mutation events (bug 22634) may have already removed the inserted content + if (!refNode->inDocument()) + return; + while (node) { Node* next = node->nextSibling(); fragment.removeNode(node); insertNodeAfterAndUpdateNodesInserted(node, refNode.get()); + + // Mutation events (bug 22634) may have already removed the inserted content + if (!node->inDocument()) + return; + refNode = node; node = next; } @@ -846,7 +876,7 @@ void ReplaceSelectionCommand::doApply() // We inserted before the startBlock to prevent nesting, and the content before the startBlock wasn't in its own block and // didn't have a br after it, so the inserted content ended up in the same paragraph. - if (startBlock && insertionPos.node() == startBlock->parentNode() && (unsigned)insertionPos.offset() < startBlock->nodeIndex() && !isStartOfParagraph(startOfInsertedContent)) + if (startBlock && insertionPos.node() == startBlock->parentNode() && (unsigned)insertionPos.m_offset < startBlock->nodeIndex() && !isStartOfParagraph(startOfInsertedContent)) insertNodeAt(createBreakElement(document()).get(), startOfInsertedContent.deepEquivalent()); Position lastPositionToSelect; @@ -878,9 +908,13 @@ void ReplaceSelectionCommand::doApply() // Insert a line break just after the inserted content to separate it from what // comes after and prevent that from happening. VisiblePosition endOfInsertedContent = positionAtEndOfInsertedContent(); - if (startOfParagraph(endOfInsertedContent) == startOfParagraphToMove) + if (startOfParagraph(endOfInsertedContent) == startOfParagraphToMove) { insertNodeAt(createBreakElement(document()).get(), endOfInsertedContent.deepEquivalent()); - + // Mutation events (bug 22634) triggered by inserting the <br> might have removed the content we're about to move + if (!startOfParagraphToMove.deepEquivalent().node()->inDocument()) + return; + } + // FIXME: Maintain positions for the start and end of inserted content instead of keeping nodes. The nodes are // only ever used to create positions where inserted content starts/ends. moveParagraph(startOfParagraphToMove, endOfParagraph(startOfParagraphToMove), destination); @@ -1013,9 +1047,9 @@ void ReplaceSelectionCommand::completeHTMLReplacement(const Position &lastPositi return; if (m_selectReplacement) - setEndingSelection(Selection(start, end, SEL_DEFAULT_AFFINITY)); + setEndingSelection(VisibleSelection(start, end, SEL_DEFAULT_AFFINITY)); else - setEndingSelection(Selection(end, SEL_DEFAULT_AFFINITY)); + setEndingSelection(VisibleSelection(end, SEL_DEFAULT_AFFINITY)); } EditAction ReplaceSelectionCommand::editingAction() const diff --git a/WebCore/editing/SelectionController.cpp b/WebCore/editing/SelectionController.cpp index 67f675e..d606891 100644 --- a/WebCore/editing/SelectionController.cpp +++ b/WebCore/editing/SelectionController.cpp @@ -76,30 +76,30 @@ SelectionController::SelectionController(Frame* frame, bool isDragCaretControlle void SelectionController::moveTo(const VisiblePosition &pos, bool userTriggered) { - setSelection(Selection(pos.deepEquivalent(), pos.deepEquivalent(), pos.affinity()), true, true, userTriggered); + setSelection(VisibleSelection(pos.deepEquivalent(), pos.deepEquivalent(), pos.affinity()), true, true, userTriggered); } void SelectionController::moveTo(const VisiblePosition &base, const VisiblePosition &extent, bool userTriggered) { - setSelection(Selection(base.deepEquivalent(), extent.deepEquivalent(), base.affinity()), true, true, userTriggered); + setSelection(VisibleSelection(base.deepEquivalent(), extent.deepEquivalent(), base.affinity()), true, true, userTriggered); } void SelectionController::moveTo(const Position &pos, EAffinity affinity, bool userTriggered) { - setSelection(Selection(pos, affinity), true, true, userTriggered); + setSelection(VisibleSelection(pos, affinity), true, true, userTriggered); } void SelectionController::moveTo(const Range *r, EAffinity affinity, bool userTriggered) { - setSelection(Selection(startPosition(r), endPosition(r), affinity), true, true, userTriggered); + setSelection(VisibleSelection(startPosition(r), endPosition(r), affinity), true, true, userTriggered); } void SelectionController::moveTo(const Position &base, const Position &extent, EAffinity affinity, bool userTriggered) { - setSelection(Selection(base, extent, affinity), true, true, userTriggered); + setSelection(VisibleSelection(base, extent, affinity), true, true, userTriggered); } -void SelectionController::setSelection(const Selection& s, bool closeTyping, bool clearTypingStyle, bool userTriggered) +void SelectionController::setSelection(const VisibleSelection& s, bool closeTyping, bool clearTypingStyle, bool userTriggered) { if (m_isDragCaretController) { invalidateCaretRect(); @@ -134,7 +134,7 @@ void SelectionController::setSelection(const Selection& s, bool closeTyping, boo if (m_sel == s) return; - Selection oldSelection = m_sel; + VisibleSelection oldSelection = m_sel; m_sel = s; @@ -151,7 +151,7 @@ void SelectionController::setSelection(const Selection& s, bool closeTyping, boo m_frame->notifyRendererOfSelectionChange(userTriggered); m_frame->respondToChangedSelection(oldSelection, closeTyping); if (userTriggered) - m_frame->revealCaret(RenderLayer::gAlignToEdgeIfNeeded); + m_frame->revealSelection(ScrollAlignment::alignToEdgeIfNeeded, true); notifyAccessibilityForSelectionChange(); } @@ -215,12 +215,12 @@ void SelectionController::nodeWillBeRemoved(Node *node) if (clearRenderTreeSelection) { RefPtr<Document> document = m_sel.start().node()->document(); document->updateRendering(); - if (RenderView* view = static_cast<RenderView*>(document->renderer())) + if (RenderView* view = toRenderView(document->renderer())) view->clearSelection(); } if (clearDOMTreeSelection) - setSelection(Selection(), false, false); + setSelection(VisibleSelection(), false, false); } void SelectionController::willBeModified(EAlteration alter, EDirection direction) @@ -700,27 +700,27 @@ int SelectionController::xPosForVerticalArrowNavigation(EPositionType type) void SelectionController::clear() { - setSelection(Selection()); + setSelection(VisibleSelection()); } void SelectionController::setBase(const VisiblePosition &pos, bool userTriggered) { - setSelection(Selection(pos.deepEquivalent(), m_sel.extent(), pos.affinity()), true, true, userTriggered); + setSelection(VisibleSelection(pos.deepEquivalent(), m_sel.extent(), pos.affinity()), true, true, userTriggered); } void SelectionController::setExtent(const VisiblePosition &pos, bool userTriggered) { - setSelection(Selection(m_sel.base(), pos.deepEquivalent(), pos.affinity()), true, true, userTriggered); + setSelection(VisibleSelection(m_sel.base(), pos.deepEquivalent(), pos.affinity()), true, true, userTriggered); } void SelectionController::setBase(const Position &pos, EAffinity affinity, bool userTriggered) { - setSelection(Selection(pos, m_sel.extent(), affinity), true, true, userTriggered); + setSelection(VisibleSelection(pos, m_sel.extent(), affinity), true, true, userTriggered); } void SelectionController::setExtent(const Position &pos, EAffinity affinity, bool userTriggered) { - setSelection(Selection(m_sel.base(), pos, affinity), true, true, userTriggered); + setSelection(VisibleSelection(m_sel.base(), pos, affinity), true, true, userTriggered); } void SelectionController::setNeedsLayout(bool flag) @@ -830,7 +830,7 @@ IntRect SelectionController::caretRepaintRect() const bool SelectionController::recomputeCaretRect() { - if (!m_frame || !m_frame->document()) + if (!m_frame) return false; FrameView* v = m_frame->document()->view(); @@ -853,9 +853,10 @@ bool SelectionController::recomputeCaretRect() if (oldAbsRepaintRect == m_absCaretBounds) return false; - if (RenderView* view = static_cast<RenderView*>(m_frame->document()->renderer())) { - view->repaintViewRectangle(oldAbsRepaintRect, false); - view->repaintViewRectangle(m_absCaretBounds, false); + if (RenderView* view = toRenderView(m_frame->document()->renderer())) { + // FIXME: make caret repainting container-aware. + view->repaintRectangleInViewAndCompositedLayers(oldAbsRepaintRect, false); + view->repaintRectangleInViewAndCompositedLayers(m_absCaretBounds, false); } return true; @@ -886,8 +887,8 @@ void SelectionController::invalidateCaretRect() m_needsLayout = true; if (!caretRectChanged) { - if (RenderView* view = static_cast<RenderView*>(d->renderer())) - view->repaintViewRectangle(caretRepaintRect(), false); + if (RenderView* view = toRenderView(d->renderer())) + view->repaintRectangleInViewAndCompositedLayers(caretRepaintRect(), false); } } @@ -931,9 +932,9 @@ void SelectionController::debugRenderer(RenderObject *r, bool selected) const if (selected) { int offset = 0; if (r->node() == m_sel.start().node()) - offset = m_sel.start().offset(); + offset = m_sel.start().m_offset; else if (r->node() == m_sel.end().node()) - offset = m_sel.end().offset(); + offset = m_sel.end().m_offset; int pos; InlineTextBox *box = textRenderer->findNextInlineTextBox(offset, pos); @@ -995,7 +996,8 @@ bool SelectionController::contains(const IntPoint& point) if (!document->renderer()) return false; - HitTestRequest request(true, true); + HitTestRequest request(HitTestRequest::ReadOnly | + HitTestRequest::Active); HitTestResult result(point); document->renderView()->layer()->hitTest(request, result); Node* innerNode = result.innerNode(); @@ -1041,8 +1043,6 @@ void SelectionController::selectFrameElementInParentIfFullySelected() // Get to the <iframe> or <frame> (or even <object>) element in the parent frame. Document* doc = m_frame->document(); - if (!doc) - return; Element* ownerElement = doc->ownerElement(); if (!ownerElement) return; @@ -1060,7 +1060,7 @@ void SelectionController::selectFrameElementInParentIfFullySelected() VisiblePosition afterOwnerElement(VisiblePosition(ownerElementParent, ownerElementNodeIndex + 1, VP_UPSTREAM_IF_POSSIBLE)); // Focus on the parent frame, and then select from before this element to after. - Selection newSelection(beforeOwnerElement, afterOwnerElement); + VisibleSelection newSelection(beforeOwnerElement, afterOwnerElement); if (parent->shouldChangeSelection(newSelection)) { page->focusController()->setFocusedFrame(parent); parent->selection()->setSelection(newSelection); @@ -1070,8 +1070,6 @@ void SelectionController::selectFrameElementInParentIfFullySelected() void SelectionController::selectAll() { Document* document = m_frame->document(); - if (!document) - return; if (document->focusedNode() && document->focusedNode()->canSelectAll()) { document->focusedNode()->selectAll(); @@ -1088,7 +1086,7 @@ void SelectionController::selectAll() } if (!root) return; - Selection newSelection(Selection::selectionFromContentsOfNode(root)); + VisibleSelection newSelection(VisibleSelection::selectionFromContentsOfNode(root)); if (m_frame->shouldChangeSelection(newSelection)) setSelection(newSelection); selectFrameElementInParentIfFullySelected(); @@ -1132,7 +1130,7 @@ bool SelectionController::setSelectedRange(Range* range, EAffinity affinity, boo // FIXME: Can we provide extentAffinity? VisiblePosition visibleStart(startContainer, startOffset, collapsed ? affinity : DOWNSTREAM); VisiblePosition visibleEnd(endContainer, endOffset, SEL_DEFAULT_AFFINITY); - setSelection(Selection(visibleStart, visibleEnd), closeTyping); + setSelection(VisibleSelection(visibleStart, visibleEnd), closeTyping); return true; } @@ -1166,8 +1164,8 @@ void SelectionController::focusedOrActiveStateChanged() // Because RenderObject::selectionBackgroundColor() and // RenderObject::selectionForegroundColor() check if the frame is active, // we have to update places those colors were painted. - if (RenderView* view = static_cast<RenderView*>(m_frame->document()->renderer())) - view->repaintViewRectangle(enclosingIntRect(m_frame->selectionBounds())); + if (RenderView* view = toRenderView(m_frame->document()->renderer())) + view->repaintRectangleInViewAndCompositedLayers(enclosingIntRect(m_frame->selectionBounds())); // Caret appears in the active frame. if (activeAndFocused) @@ -1205,8 +1203,7 @@ void SelectionController::setFocused(bool flag) focusedOrActiveStateChanged(); - if (Document* doc = m_frame->document()) - doc->dispatchWindowEvent(flag ? eventNames().focusEvent : eventNames().blurEvent, false, false); + m_frame->document()->dispatchWindowEvent(flag ? eventNames().focusEvent : eventNames().blurEvent, false, false); } bool SelectionController::isFocusedAndActive() const diff --git a/WebCore/editing/SelectionController.h b/WebCore/editing/SelectionController.h index fe5e90d..21e849d 100644 --- a/WebCore/editing/SelectionController.h +++ b/WebCore/editing/SelectionController.h @@ -27,8 +27,8 @@ #define SelectionController_h #include "IntRect.h" -#include "Selection.h" #include "Range.h" +#include "VisibleSelection.h" #include <wtf/Noncopyable.h> namespace WebCore { @@ -56,8 +56,8 @@ public: void moveTo(const Position&, EAffinity, bool userTriggered = false); void moveTo(const Position&, const Position&, EAffinity, bool userTriggered = false); - const Selection& selection() const { return m_sel; } - void setSelection(const Selection&, bool closeTyping = true, bool clearTypingStyle = true, bool userTriggered = false); + const VisibleSelection& selection() const { return m_sel; } + void setSelection(const VisibleSelection&, bool closeTyping = true, bool clearTypingStyle = true, bool userTriggered = false); bool setSelectedRange(Range*, EAffinity, bool closeTyping); void selectAll(); void clear(); @@ -67,7 +67,7 @@ public: bool contains(const IntPoint&); - Selection::EState state() const { return m_sel.state(); } + VisibleSelection::SelectionType selectionType() const { return m_sel.selectionType(); } EAffinity affinity() const { return m_sel.affinity(); } @@ -103,7 +103,7 @@ public: bool isCaretOrRange() const { return m_sel.isCaretOrRange(); } bool isInPasswordField() const; - PassRefPtr<Range> toRange() const { return m_sel.toRange(); } + PassRefPtr<Range> toNormalizedRange() const { return m_sel.toNormalizedRange(); } void debugRenderer(RenderObject*, bool selected) const; @@ -154,7 +154,7 @@ private: Frame* m_frame; int m_xPosForVerticalArrowNavigation; - Selection m_sel; + VisibleSelection m_sel; IntRect m_caretRect; // caret rect in coords local to the renderer responsible for painting the caret IntRect m_absCaretBounds; // absolute bounding rect for the caret diff --git a/WebCore/editing/SmartReplaceCF.cpp b/WebCore/editing/SmartReplaceCF.cpp index f2fd985..c5fa9a8 100644 --- a/WebCore/editing/SmartReplaceCF.cpp +++ b/WebCore/editing/SmartReplaceCF.cpp @@ -30,6 +30,7 @@ #include "SmartReplace.h" #include <CoreFoundation/CFCharacterSet.h> +#include <CoreFoundation/CFString.h> namespace WebCore { diff --git a/WebCore/editing/SplitElementCommand.cpp b/WebCore/editing/SplitElementCommand.cpp index 69447d4..35dfc6f 100644 --- a/WebCore/editing/SplitElementCommand.cpp +++ b/WebCore/editing/SplitElementCommand.cpp @@ -43,7 +43,7 @@ SplitElementCommand::SplitElementCommand(PassRefPtr<Element> element, PassRefPtr void SplitElementCommand::doApply() { - RefPtr<Element> prefixElement = m_element2->cloneElement(); + RefPtr<Element> prefixElement = m_element2->cloneElementWithoutChildren(); if (m_atChild->parentNode() != m_element2) return; diff --git a/WebCore/editing/TextIterator.cpp b/WebCore/editing/TextIterator.cpp index f617889..2d7e641 100644 --- a/WebCore/editing/TextIterator.cpp +++ b/WebCore/editing/TextIterator.cpp @@ -224,7 +224,9 @@ void TextIterator::advance() if (!m_handledNode) { if (renderer->isText() && m_node->nodeType() == Node::TEXT_NODE) // FIXME: What about CDATA_SECTION_NODE? m_handledNode = handleTextNode(); - else if (renderer && (renderer->isImage() || renderer->isWidget() || (renderer->element() && renderer->element()->isControl()))) + else if (renderer && (renderer->isImage() || renderer->isWidget() || + (renderer->node() && renderer->node()->isElementNode() && + static_cast<Element*>(renderer->node())->isFormControlElement()))) m_handledNode = handleReplacedElement(); else m_handledNode = handleNonTextNode(); @@ -247,7 +249,7 @@ void TextIterator::advance() parentNode = m_node->shadowParentNode(); } while (!next && parentNode) { - if (pastEnd && parentNode == m_endContainer || m_endContainer->isDescendantOf(parentNode)) + if ((pastEnd && parentNode == m_endContainer) || m_endContainer->isDescendantOf(parentNode)) return; bool haveRenderer = m_node->renderer(); m_node = parentNode; @@ -414,8 +416,8 @@ bool TextIterator::handleReplacedElement() return false; } - if (m_enterTextControls && (renderer->isTextArea() || renderer->isTextField())) { - m_node = static_cast<RenderTextControl*>(renderer)->innerTextElement(); + if (m_enterTextControls && renderer->isTextControl()) { + m_node = toRenderTextControl(renderer)->innerTextElement(); m_offset = 0; m_inShadowContent = true; return false; @@ -858,7 +860,7 @@ void SimplifiedBackwardsTextIterator::advance() if (!m_handledNode && canHaveChildrenForEditing(m_node) && m_node->parentNode() && - (!m_node->lastChild() || m_node == m_endNode && m_endOffset == 0)) { + (!m_node->lastChild() || (m_node == m_endNode && m_endOffset == 0))) { exitNode(); if (m_positionNode) { m_handledNode = true; @@ -1057,7 +1059,7 @@ void CharacterIterator::advance(int count) String CharacterIterator::string(int numChars) { Vector<UChar> result; - result.reserveCapacity(numChars); + result.reserveInitialCapacity(numChars); while (numChars > 0 && !atEnd()) { int runSize = min(numChars, length()); result.append(characters(), runSize); @@ -1081,6 +1083,81 @@ static PassRefPtr<Range> characterSubrange(CharacterIterator& it, int offset, in end->endContainer(), end->endOffset()); } +BackwardsCharacterIterator::BackwardsCharacterIterator() + : m_offset(0) + , m_runOffset(0) + , m_atBreak(true) +{ +} + +BackwardsCharacterIterator::BackwardsCharacterIterator(const Range* range) + : m_offset(0) + , m_runOffset(0) + , m_atBreak(true) + , m_textIterator(range) +{ + while (!atEnd() && !m_textIterator.length()) + m_textIterator.advance(); +} + +PassRefPtr<Range> BackwardsCharacterIterator::range() const +{ + RefPtr<Range> r = m_textIterator.range(); + if (!m_textIterator.atEnd()) { + if (m_textIterator.length() <= 1) + ASSERT(m_runOffset == 0); + else { + Node* n = r->startContainer(); + ASSERT(n == r->endContainer()); + int offset = r->endOffset() - m_runOffset; + ExceptionCode ec = 0; + r->setStart(n, offset - 1, ec); + r->setEnd(n, offset, ec); + ASSERT(!ec); + } + } + return r.release(); +} + +void BackwardsCharacterIterator::advance(int count) +{ + if (count <= 0) { + ASSERT(!count); + return; + } + + m_atBreak = false; + + int remaining = m_textIterator.length() - m_runOffset; + if (count < remaining) { + m_runOffset += count; + m_offset += count; + return; + } + + count -= remaining; + m_offset += remaining; + + for (m_textIterator.advance(); !atEnd(); m_textIterator.advance()) { + int runLength = m_textIterator.length(); + if (runLength == 0) + m_atBreak = true; + else { + if (count < runLength) { + m_runOffset = count; + m_offset += count; + return; + } + + count -= runLength; + m_offset += runLength; + } + } + + m_atBreak = true; + m_runOffset = 0; +} + // -------- WordAwareIterator::WordAwareIterator() @@ -1225,7 +1302,7 @@ inline SearchBuffer::SearchBuffer(const String& target, bool isCaseSensitive) ASSERT(!m_target.isEmpty()); size_t targetLength = target.length(); - m_buffer.reserveCapacity(max(targetLength * 8, minimumSearchBufferSize)); + m_buffer.reserveInitialCapacity(max(targetLength * 8, minimumSearchBufferSize)); m_overlap = m_buffer.capacity() / 4; // Grab the single global searcher. @@ -1481,7 +1558,7 @@ PassRefPtr<Range> TextIterator::rangeFromLocationAndLength(Element *scope, int r Position runEnd = VisiblePosition(runStart).next().deepEquivalent(); if (runEnd.isNotNull()) { ExceptionCode ec = 0; - textRunRange->setEnd(runEnd.node(), runEnd.offset(), ec); + textRunRange->setEnd(runEnd.node(), runEnd.m_offset, ec); ASSERT(!ec); } } @@ -1542,7 +1619,7 @@ UChar* plainTextToMallocAllocatedBuffer(const Range* r, unsigned& bufferLength, typedef pair<UChar*, unsigned> TextSegment; Vector<TextSegment>* textSegments = 0; Vector<UChar> textBuffer; - textBuffer.reserveCapacity(cMaxSegmentSize); + textBuffer.reserveInitialCapacity(cMaxSegmentSize); for (TextIterator it(r); !it.atEnd(); it.advance()) { if (textBuffer.size() && textBuffer.size() + it.length() > cMaxSegmentSize) { UChar* newSegmentBuffer = static_cast<UChar*>(malloc(textBuffer.size() * sizeof(UChar))); diff --git a/WebCore/editing/TextIterator.h b/WebCore/editing/TextIterator.h index 308ffc9..f00bea4 100644 --- a/WebCore/editing/TextIterator.h +++ b/WebCore/editing/TextIterator.h @@ -216,6 +216,25 @@ private: TextIterator m_textIterator; }; +class BackwardsCharacterIterator { +public: + BackwardsCharacterIterator(); + explicit BackwardsCharacterIterator(const Range*); + + void advance(int); + + bool atEnd() const { return m_textIterator.atEnd(); } + + PassRefPtr<Range> range() const; + +private: + int m_offset; + int m_runOffset; + bool m_atBreak; + + SimplifiedBackwardsTextIterator m_textIterator; +}; + // Very similar to the TextIterator, except that the chunks of text returned are "well behaved", // meaning they never end split up a word. This is useful for spellcheck or (perhaps one day) searching. class WordAwareIterator { diff --git a/WebCore/editing/TypingCommand.cpp b/WebCore/editing/TypingCommand.cpp index 414680f..6235f7a 100644 --- a/WebCore/editing/TypingCommand.cpp +++ b/WebCore/editing/TypingCommand.cpp @@ -36,6 +36,7 @@ #include "InsertLineBreakCommand.h" #include "InsertParagraphSeparatorCommand.h" #include "InsertTextCommand.h" +#include "RenderObject.h" #include "SelectionController.h" #include "VisiblePosition.h" #include "htmlediting.h" @@ -48,7 +49,6 @@ TypingCommand::TypingCommand(Document *document, ETypingCommand commandType, con m_commandType(commandType), m_textToInsert(textToInsert), m_openForMoreTyping(true), - m_applyEditing(false), m_selectInsertedText(selectInsertedText), m_smartDelete(false), m_granularity(granularity), @@ -125,14 +125,14 @@ void TypingCommand::insertText(Document* document, const String& text, bool sele insertText(document, text, frame->selection()->selection(), selectInsertedText, insertedTextIsComposition); } -void TypingCommand::insertText(Document* document, const String& text, const Selection& selectionForInsertion, bool selectInsertedText, bool insertedTextIsComposition) +void TypingCommand::insertText(Document* document, const String& text, const VisibleSelection& selectionForInsertion, bool selectInsertedText, bool insertedTextIsComposition) { ASSERT(document); RefPtr<Frame> frame = document->frame(); ASSERT(frame); - Selection currentSelection = frame->selection()->selection(); + VisibleSelection currentSelection = frame->selection()->selection(); bool changeSelection = currentSelection != selectionForInsertion; String newText = text; @@ -300,13 +300,7 @@ void TypingCommand::markMisspellingsAfterTyping() void TypingCommand::typingAddedToOpenCommand() { markMisspellingsAfterTyping(); - // Do not apply editing to the frame on the first time through. - // The frame will get told in the same way as all other commands. - // But since this command stays open and is used for additional typing, - // we need to tell the frame here as other commands are added. - if (m_applyEditing) - document()->frame()->editor()->appliedEditing(this); - m_applyEditing = true; + document()->frame()->editor()->appliedEditing(this); } void TypingCommand::insertText(const String &text, bool selectInsertedText) @@ -370,19 +364,19 @@ void TypingCommand::insertParagraphSeparatorInQuotedContent() void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) { - Selection selectionToDelete; - Selection selectionAfterUndo; + VisibleSelection selectionToDelete; + VisibleSelection selectionAfterUndo; - switch (endingSelection().state()) { - case Selection::RANGE: + switch (endingSelection().selectionType()) { + case VisibleSelection::RangeSelection: selectionToDelete = endingSelection(); selectionAfterUndo = selectionToDelete; break; - case Selection::CARET: { - if (breakOutOfEmptyMailBlockquotedParagraph()) { + case VisibleSelection::CaretSelection: { + // After breaking out of an empty mail blockquote, we still want continue with the deletion + // so actual content will get deleted, and not just the quote style. + if (breakOutOfEmptyMailBlockquotedParagraph()) typingAddedToOpenCommand(); - return; - } m_smartDelete = false; @@ -410,63 +404,62 @@ void TypingCommand::deleteKeyPressed(TextGranularity granularity, bool killRing) selection.modify(SelectionController::EXTEND, SelectionController::BACKWARD, granularity); // If the caret is just after a table, select the table and don't delete anything. } else if (Node* table = isFirstPositionAfterTable(visibleStart)) { - setEndingSelection(Selection(Position(table, 0), endingSelection().start(), DOWNSTREAM)); + setEndingSelection(VisibleSelection(Position(table, 0), endingSelection().start(), DOWNSTREAM)); typingAddedToOpenCommand(); return; } selectionToDelete = selection.selection(); - if (granularity == CharacterGranularity && selectionToDelete.end().offset() - selectionToDelete.start().offset() > 1) { - // When we delete a ligature consisting of multiple Unicode characters with a backspace key, - // we should not delete the ligature but delete only its last characeter. To check we are deleting - // a ligature, we retrieve the previous position of the caret and count the number of - // characters to be deleted. - // To prevent from calculating the previous position every time when pressing a backspace key, - // we retrieve the previous position only when the given selection consists of two or more characters. - if (selectionToDelete.end().offset() - selectionToDelete.end().previous(UsingComposedCharacters).offset() > 1) - selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(NotUsingComposedCharacters)); + if (granularity == CharacterGranularity && selectionToDelete.end().node() == selectionToDelete.start().node() && selectionToDelete.end().m_offset - selectionToDelete.start().m_offset > 1) { + // If there are multiple Unicode code points to be deleted, adjust the range to match platform conventions. + selectionToDelete.setWithoutValidation(selectionToDelete.end(), selectionToDelete.end().previous(BackwardDeletion)); } if (!startingSelection().isRange() || selectionToDelete.base() != startingSelection().start()) selectionAfterUndo = selectionToDelete; else // It's a little tricky to compute what the starting selection would have been in the original document. - // We can't let the Selection class's validation kick in or it'll adjust for us based on + // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on // the current state of the document and we'll get the wrong result. selectionAfterUndo.setWithoutValidation(startingSelection().end(), selectionToDelete.extent()); break; } - case Selection::NONE: + case VisibleSelection::NoSelection: ASSERT_NOT_REACHED(); break; } - if (selectionToDelete.isCaretOrRange() && document()->frame()->shouldDeleteSelection(selectionToDelete)) { - if (killRing) - document()->frame()->editor()->addToKillRing(selectionToDelete.toRange().get(), false); - // Make undo select everything that has been deleted, unless an undo will undo more than just this deletion. - // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete - // more text than you insert. In that case all of the text that was around originally should be selected. - if (m_openedByBackwardDelete) - setStartingSelection(selectionAfterUndo); - CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); - setSmartDelete(false); - typingAddedToOpenCommand(); - } + ASSERT(!selectionToDelete.isNone()); + if (selectionToDelete.isNone()) + return; + + if (selectionToDelete.isCaret() || !document()->frame()->shouldDeleteSelection(selectionToDelete)) + return; + + if (killRing) + document()->frame()->editor()->addToKillRing(selectionToDelete.toNormalizedRange().get(), false); + // Make undo select everything that has been deleted, unless an undo will undo more than just this deletion. + // FIXME: This behaves like TextEdit except for the case where you open with text insertion and then delete + // more text than you insert. In that case all of the text that was around originally should be selected. + if (m_openedByBackwardDelete) + setStartingSelection(selectionAfterUndo); + CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); + setSmartDelete(false); + typingAddedToOpenCommand(); } void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool killRing) { - Selection selectionToDelete; - Selection selectionAfterUndo; + VisibleSelection selectionToDelete; + VisibleSelection selectionAfterUndo; - switch (endingSelection().state()) { - case Selection::RANGE: + switch (endingSelection().selectionType()) { + case VisibleSelection::RangeSelection: selectionToDelete = endingSelection(); selectionAfterUndo = selectionToDelete; break; - case Selection::CARET: { + case VisibleSelection::CaretSelection: { m_smartDelete = false; // Handle delete at beginning-of-block case. @@ -483,8 +476,8 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki if (visibleEnd == endOfParagraph(visibleEnd)) downstreamEnd = visibleEnd.next(true).deepEquivalent().downstream(); // When deleting tables: Select the table first, then perform the deletion - if (downstreamEnd.node() && downstreamEnd.node()->renderer() && downstreamEnd.node()->renderer()->isTable() && downstreamEnd.offset() == 0) { - setEndingSelection(Selection(endingSelection().end(), Position(downstreamEnd.node(), maxDeepOffset(downstreamEnd.node())), DOWNSTREAM)); + if (downstreamEnd.node() && downstreamEnd.node()->renderer() && downstreamEnd.node()->renderer()->isTable() && downstreamEnd.m_offset == 0) { + setEndingSelection(VisibleSelection(endingSelection().end(), lastDeepEditingPositionForNode(downstreamEnd.node()), DOWNSTREAM)); typingAddedToOpenCommand(); return; } @@ -498,7 +491,7 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki selectionAfterUndo = selectionToDelete; else { // It's a little tricky to compute what the starting selection would have been in the original document. - // We can't let the Selection class's validation kick in or it'll adjust for us based on + // We can't let the VisibleSelection class's validation kick in or it'll adjust for us based on // the current state of the document and we'll get the wrong result. Position extent = startingSelection().end(); if (extent.node() != selectionToDelete.end().node()) @@ -506,29 +499,34 @@ void TypingCommand::forwardDeleteKeyPressed(TextGranularity granularity, bool ki else { int extraCharacters; if (selectionToDelete.start().node() == selectionToDelete.end().node()) - extraCharacters = selectionToDelete.end().offset() - selectionToDelete.start().offset(); + extraCharacters = selectionToDelete.end().m_offset - selectionToDelete.start().m_offset; else - extraCharacters = selectionToDelete.end().offset(); - extent = Position(extent.node(), extent.offset() + extraCharacters); + extraCharacters = selectionToDelete.end().m_offset; + extent = Position(extent.node(), extent.m_offset + extraCharacters); } selectionAfterUndo.setWithoutValidation(startingSelection().start(), extent); } break; } - case Selection::NONE: + case VisibleSelection::NoSelection: ASSERT_NOT_REACHED(); break; } - if (selectionToDelete.isCaretOrRange() && document()->frame()->shouldDeleteSelection(selectionToDelete)) { - if (killRing) - document()->frame()->editor()->addToKillRing(selectionToDelete.toRange().get(), false); - // make undo select what was deleted - setStartingSelection(selectionAfterUndo); - CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); - setSmartDelete(false); - typingAddedToOpenCommand(); - } + ASSERT(!selectionToDelete.isNone()); + if (selectionToDelete.isNone()) + return; + + if (selectionToDelete.isCaret() || !document()->frame()->shouldDeleteSelection(selectionToDelete)) + return; + + if (killRing) + document()->frame()->editor()->addToKillRing(selectionToDelete.toNormalizedRange().get(), false); + // make undo select what was deleted + setStartingSelection(selectionAfterUndo); + CompositeEditCommand::deleteSelection(selectionToDelete, m_smartDelete); + setSmartDelete(false); + typingAddedToOpenCommand(); } void TypingCommand::deleteSelection(bool smartDelete) diff --git a/WebCore/editing/TypingCommand.h b/WebCore/editing/TypingCommand.h index b4e1091..bf588be 100644 --- a/WebCore/editing/TypingCommand.h +++ b/WebCore/editing/TypingCommand.h @@ -46,7 +46,7 @@ public: static void deleteKeyPressed(Document*, bool smartDelete = false, TextGranularity = CharacterGranularity, bool killRing = false); static void forwardDeleteKeyPressed(Document*, bool smartDelete = false, TextGranularity = CharacterGranularity, bool killRing = false); static void insertText(Document*, const String&, bool selectInsertedText = false, bool insertedTextIsComposition = false); - static void insertText(Document*, const String&, const Selection&, bool selectInsertedText = false, bool insertedTextIsComposition = false); + static void insertText(Document*, const String&, const VisibleSelection&, bool selectInsertedText = false, bool insertedTextIsComposition = false); static void insertLineBreak(Document*); static void insertParagraphSeparator(Document*); static void insertParagraphSeparatorInQuotedContent(Document*); @@ -87,7 +87,6 @@ private: ETypingCommand m_commandType; String m_textToInsert; bool m_openForMoreTyping; - bool m_applyEditing; bool m_selectInsertedText; bool m_smartDelete; TextGranularity m_granularity; diff --git a/WebCore/editing/VisiblePosition.cpp b/WebCore/editing/VisiblePosition.cpp index 3d771c6..27ee146 100644 --- a/WebCore/editing/VisiblePosition.cpp +++ b/WebCore/editing/VisiblePosition.cpp @@ -81,7 +81,7 @@ VisiblePosition VisiblePosition::previous(bool stayInEditableContent) const Position pos = previousVisuallyDistinctCandidate(m_deepPosition); // return null visible position if there is no previous visible position - if (pos.atStart()) + if (pos.atStartOfTree()) return VisiblePosition(); VisiblePosition prev = VisiblePosition(pos, DOWNSTREAM); @@ -125,7 +125,7 @@ Position VisiblePosition::leftVisuallyDistinctCandidate() const if (!box) return primaryDirection == LTR ? previousVisuallyDistinctCandidate(m_deepPosition) : nextVisuallyDistinctCandidate(m_deepPosition); - RenderObject* renderer = box->object(); + RenderObject* renderer = box->renderer(); while (true) { if ((renderer->isReplaced() || renderer->isBR()) && offset == box->caretRightmostOffset()) @@ -147,7 +147,7 @@ Position VisiblePosition::leftVisuallyDistinctCandidate() const // Reposition at the other logical position corresponding to our edge's visual position and go for another round. box = prevBox; - renderer = box->object(); + renderer = box->renderer(); offset = prevBox->caretRightmostOffset(); continue; } @@ -176,7 +176,7 @@ Position VisiblePosition::leftVisuallyDistinctCandidate() const break; box = prevBox; } - renderer = box->object(); + renderer = box->renderer(); offset = box->caretRightmostOffset(); if (box->direction() == primaryDirection) break; @@ -185,7 +185,7 @@ Position VisiblePosition::leftVisuallyDistinctCandidate() const if (prevBox) { box = prevBox; - renderer = box->object(); + renderer = box->renderer(); offset = box->caretRightmostOffset(); if (box->bidiLevel() > level) { do { @@ -215,15 +215,15 @@ Position VisiblePosition::leftVisuallyDistinctCandidate() const break; level = box->bidiLevel(); } - renderer = box->object(); + renderer = box->renderer(); offset = primaryDirection == LTR ? box->caretMinOffset() : box->caretMaxOffset(); } break; } - p = Position(renderer->element(), offset); + p = Position(renderer->node(), offset); - if (p.isCandidate() && p.downstream() != downstreamStart || p.atStart() || p.atEnd()) + if ((p.isCandidate() && p.downstream() != downstreamStart) || p.atStartOfTree() || p.atEndOfTree()) return p; } } @@ -231,7 +231,8 @@ Position VisiblePosition::leftVisuallyDistinctCandidate() const VisiblePosition VisiblePosition::left(bool stayInEditableContent) const { Position pos = leftVisuallyDistinctCandidate(); - if (pos.atStart() || pos.atEnd()) + // FIXME: Why can't we move left from the last position in a tree? + if (pos.atStartOfTree() || pos.atEndOfTree()) return VisiblePosition(); VisiblePosition left = VisiblePosition(pos, DOWNSTREAM); @@ -266,7 +267,7 @@ Position VisiblePosition::rightVisuallyDistinctCandidate() const if (!box) return primaryDirection == LTR ? nextVisuallyDistinctCandidate(m_deepPosition) : previousVisuallyDistinctCandidate(m_deepPosition); - RenderObject* renderer = box->object(); + RenderObject* renderer = box->renderer(); while (true) { if ((renderer->isReplaced() || renderer->isBR()) && offset == box->caretLeftmostOffset()) @@ -288,7 +289,7 @@ Position VisiblePosition::rightVisuallyDistinctCandidate() const // Reposition at the other logical position corresponding to our edge's visual position and go for another round. box = nextBox; - renderer = box->object(); + renderer = box->renderer(); offset = nextBox->caretLeftmostOffset(); continue; } @@ -318,7 +319,7 @@ Position VisiblePosition::rightVisuallyDistinctCandidate() const break; box = nextBox; } - renderer = box->object(); + renderer = box->renderer(); offset = box->caretLeftmostOffset(); if (box->direction() == primaryDirection) break; @@ -327,7 +328,7 @@ Position VisiblePosition::rightVisuallyDistinctCandidate() const if (nextBox) { box = nextBox; - renderer = box->object(); + renderer = box->renderer(); offset = box->caretLeftmostOffset(); if (box->bidiLevel() > level) { do { @@ -357,15 +358,15 @@ Position VisiblePosition::rightVisuallyDistinctCandidate() const break; level = box->bidiLevel(); } - renderer = box->object(); + renderer = box->renderer(); offset = primaryDirection == LTR ? box->caretMaxOffset() : box->caretMinOffset(); } break; } - p = Position(renderer->element(), offset); + p = Position(renderer->node(), offset); - if (p.isCandidate() && p.downstream() != downstreamStart || p.atStart() || p.atEnd()) + if ((p.isCandidate() && p.downstream() != downstreamStart) || p.atStartOfTree() || p.atEndOfTree()) return p; } } @@ -373,7 +374,8 @@ Position VisiblePosition::rightVisuallyDistinctCandidate() const VisiblePosition VisiblePosition::right(bool stayInEditableContent) const { Position pos = rightVisuallyDistinctCandidate(); - if (pos.atStart() || pos.atEnd()) + // FIXME: Why can't we move left from the last position in a tree? + if (pos.atStartOfTree() || pos.atEndOfTree()) return VisiblePosition(); VisiblePosition right = VisiblePosition(pos, DOWNSTREAM); @@ -399,7 +401,7 @@ VisiblePosition VisiblePosition::honorEditableBoundaryAtOrBefore(const VisiblePo // Return pos itself if the two are from the very same editable region, or both are non-editable // FIXME: In the non-editable case, just because the new position is non-editable doesn't mean movement - // to it is allowed. Selection::adjustForEditableContent has this problem too. + // to it is allowed. VisibleSelection::adjustForEditableContent has this problem too. if (highestEditableRoot(pos.deepEquivalent()) == highestRoot) return pos; @@ -425,7 +427,7 @@ VisiblePosition VisiblePosition::honorEditableBoundaryAtOrAfter(const VisiblePos // Return pos itself if the two are from the very same editable region, or both are non-editable // FIXME: In the non-editable case, just because the new position is non-editable doesn't mean movement - // to it is allowed. Selection::adjustForEditableContent has this problem too. + // to it is allowed. VisibleSelection::adjustForEditableContent has this problem too. if (highestEditableRoot(pos.deepEquivalent()) == highestRoot) return pos; @@ -478,24 +480,24 @@ Position VisiblePosition::canonicalPosition(const Position& position) // 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->hasTagName(htmlTag) && !node->isContentEditable()) + if (node->hasTagName(htmlTag) && !node->isContentEditable() && node->document()->body() && node->document()->body()->isContentEditable()) return next.isNotNull() ? next : prev; Node* editingRoot = editableRootForPosition(position); // If the html element is editable, descending into its body will look like a descent // from non-editable to editable content since rootEditableElement() always stops at the body. - if (editingRoot && editingRoot->hasTagName(htmlTag) || position.node()->isDocumentNode()) + if ((editingRoot && editingRoot->hasTagName(htmlTag)) || position.node()->isDocumentNode()) return next.isNotNull() ? next : prev; bool prevIsInSameEditableElement = prevNode && editableRootForPosition(prev) == editingRoot; bool nextIsInSameEditableElement = nextNode && editableRootForPosition(next) == editingRoot; if (prevIsInSameEditableElement && !nextIsInSameEditableElement) return prev; - + if (nextIsInSameEditableElement && !prevIsInSameEditableElement) return next; - + if (!nextIsInSameEditableElement && !prevIsInSameEditableElement) return Position(); @@ -518,7 +520,7 @@ UChar VisiblePosition::characterAfter() const if (!node || !node->isTextNode()) return 0; Text* textNode = static_cast<Text*>(pos.node()); - int offset = pos.offset(); + int offset = pos.m_offset; if ((unsigned)offset >= textNode->length()) return 0; return textNode->data()[offset]; @@ -541,7 +543,7 @@ IntRect VisiblePosition::localCaretRect(RenderObject*& renderer) const getInlineBoxAndOffset(inlineBox, caretOffset); if (inlineBox) - renderer = inlineBox->object(); + renderer = inlineBox->renderer(); return renderer->localCaretRect(inlineBox, caretOffset); } @@ -574,7 +576,7 @@ void VisiblePosition::debugPosition(const char* msg) const if (isNull()) fprintf(stderr, "Position [%s]: null\n", msg); else - fprintf(stderr, "Position [%s]: %s [%p] at %d\n", msg, m_deepPosition.node()->nodeName().utf8().data(), m_deepPosition.node(), m_deepPosition.offset()); + fprintf(stderr, "Position [%s]: %s [%p] at %d\n", msg, m_deepPosition.node()->nodeName().utf8().data(), m_deepPosition.node(), m_deepPosition.m_offset); } #ifndef NDEBUG @@ -598,7 +600,7 @@ PassRefPtr<Range> makeRange(const VisiblePosition &start, const VisiblePosition Position s = rangeCompliantEquivalent(start); Position e = rangeCompliantEquivalent(end); - return Range::create(s.node()->document(), s.node(), s.offset(), e.node(), e.offset()); + return Range::create(s.node()->document(), s.node(), s.m_offset, e.node(), e.m_offset); } VisiblePosition startVisiblePosition(const Range *r, EAffinity affinity) @@ -619,7 +621,7 @@ bool setStart(Range *r, const VisiblePosition &visiblePosition) return false; Position p = rangeCompliantEquivalent(visiblePosition); int code = 0; - r->setStart(p.node(), p.offset(), code); + r->setStart(p.node(), p.m_offset, code); return code == 0; } @@ -629,7 +631,7 @@ bool setEnd(Range *r, const VisiblePosition &visiblePosition) return false; Position p = rangeCompliantEquivalent(visiblePosition); int code = 0; - r->setEnd(p.node(), p.offset(), code); + r->setEnd(p.node(), p.m_offset, code); return code == 0; } diff --git a/WebCore/editing/Selection.cpp b/WebCore/editing/VisibleSelection.cpp index 4fb3a54..279adf2 100644 --- a/WebCore/editing/Selection.cpp +++ b/WebCore/editing/VisibleSelection.cpp @@ -24,7 +24,7 @@ */ #include "config.h" -#include "Selection.h" +#include "VisibleSelection.h" #include "CharacterNames.h" #include "CString.h" @@ -35,20 +35,21 @@ #include "VisiblePosition.h" #include "visible_units.h" #include "Range.h" + #include <wtf/Assertions.h> #include <stdio.h> namespace WebCore { -Selection::Selection() +VisibleSelection::VisibleSelection() : m_affinity(DOWNSTREAM) , m_granularity(CharacterGranularity) - , m_state(NONE) + , m_selectionType(NoSelection) , m_baseIsFirst(true) { } -Selection::Selection(const Position& pos, EAffinity affinity) +VisibleSelection::VisibleSelection(const Position& pos, EAffinity affinity) : m_base(pos) , m_extent(pos) , m_affinity(affinity) @@ -57,7 +58,7 @@ Selection::Selection(const Position& pos, EAffinity affinity) validate(); } -Selection::Selection(const Position& base, const Position& extent, EAffinity affinity) +VisibleSelection::VisibleSelection(const Position& base, const Position& extent, EAffinity affinity) : m_base(base) , m_extent(extent) , m_affinity(affinity) @@ -66,7 +67,7 @@ Selection::Selection(const Position& base, const Position& extent, EAffinity aff validate(); } -Selection::Selection(const VisiblePosition& pos) +VisibleSelection::VisibleSelection(const VisiblePosition& pos) : m_base(pos.deepEquivalent()) , m_extent(pos.deepEquivalent()) , m_affinity(pos.affinity()) @@ -75,7 +76,7 @@ Selection::Selection(const VisiblePosition& pos) validate(); } -Selection::Selection(const VisiblePosition& base, const VisiblePosition& extent) +VisibleSelection::VisibleSelection(const VisiblePosition& base, const VisiblePosition& extent) : m_base(base.deepEquivalent()) , m_extent(extent.deepEquivalent()) , m_affinity(base.affinity()) @@ -84,7 +85,7 @@ Selection::Selection(const VisiblePosition& base, const VisiblePosition& extent) validate(); } -Selection::Selection(const Range* range, EAffinity affinity) +VisibleSelection::VisibleSelection(const Range* range, EAffinity affinity) : m_base(range->startPosition()) , m_extent(range->endPosition()) , m_affinity(affinity) @@ -93,36 +94,45 @@ Selection::Selection(const Range* range, EAffinity affinity) validate(); } -Selection Selection::selectionFromContentsOfNode(Node* node) +VisibleSelection VisibleSelection::selectionFromContentsOfNode(Node* node) { - return Selection(Position(node, 0), Position(node, maxDeepOffset(node)), DOWNSTREAM); + return VisibleSelection(firstDeepEditingPositionForNode(node), lastDeepEditingPositionForNode(node), DOWNSTREAM); } -void Selection::setBase(const Position& position) +void VisibleSelection::setBase(const Position& position) { m_base = position; validate(); } -void Selection::setBase(const VisiblePosition& visiblePosition) +void VisibleSelection::setBase(const VisiblePosition& visiblePosition) { m_base = visiblePosition.deepEquivalent(); validate(); } -void Selection::setExtent(const Position& position) +void VisibleSelection::setExtent(const Position& position) { m_extent = position; validate(); } -void Selection::setExtent(const VisiblePosition& visiblePosition) +void VisibleSelection::setExtent(const VisiblePosition& visiblePosition) { m_extent = visiblePosition.deepEquivalent(); validate(); } -PassRefPtr<Range> Selection::toRange() const +PassRefPtr<Range> VisibleSelection::firstRange() const +{ + if (isNone()) + return 0; + Position start = rangeCompliantEquivalent(m_start); + Position end = rangeCompliantEquivalent(m_end); + return Range::create(start.node()->document(), start, end); +} + +PassRefPtr<Range> VisibleSelection::toNormalizedRange() const { if (isNone()) return 0; @@ -159,7 +169,7 @@ PassRefPtr<Range> Selection::toRange() const ASSERT(isRange()); s = m_start.downstream(); e = m_end.upstream(); - if (Range::compareBoundaryPoints(s.node(), s.offset(), e.node(), e.offset()) > 0) { + if (Range::compareBoundaryPoints(s.node(), s.m_offset, e.node(), e.m_offset) > 0) { // Make sure the start is before the end. // The end can wind up before the start if collapsed whitespace is the only thing selected. Position tmp = s; @@ -170,22 +180,12 @@ PassRefPtr<Range> Selection::toRange() const e = rangeCompliantEquivalent(e); } - ExceptionCode ec = 0; - RefPtr<Range> result(Range::create(s.node()->document())); - result->setStart(s.node(), s.offset(), ec); - if (ec) { - LOG_ERROR("Exception setting Range start from Selection: %d", ec); - return 0; - } - result->setEnd(e.node(), e.offset(), ec); - if (ec) { - LOG_ERROR("Exception setting Range end from Selection: %d", ec); - return 0; - } - return result.release(); + // VisibleSelections are supposed to always be valid. This constructor will ASSERT + // if a valid range could not be created, which is fine for this callsite. + return Range::create(s.node()->document(), s, e); } -bool Selection::expandUsingGranularity(TextGranularity granularity) +bool VisibleSelection::expandUsingGranularity(TextGranularity granularity) { if (isNone()) return false; @@ -213,7 +213,7 @@ static PassRefPtr<Range> makeSearchRange(const Position& pos) Position start(rangeCompliantEquivalent(pos)); searchRange->selectNodeContents(boundary, ec); - searchRange->setStart(start.node(), start.offset(), ec); + searchRange->setStart(start.node(), start.m_offset, ec); ASSERT(!ec); if (ec) @@ -222,7 +222,7 @@ static PassRefPtr<Range> makeSearchRange(const Position& pos) return searchRange.release(); } -void Selection::appendTrailingWhitespace() +void VisibleSelection::appendTrailingWhitespace() { RefPtr<Range> searchRange = makeSearchRange(m_end); if (!searchRange) @@ -238,7 +238,7 @@ void Selection::appendTrailingWhitespace() } } -void Selection::validate() +void VisibleSelection::setBaseAndExtentToDeepEquivalents() { // Move the selection to rendered positions, if possible. bool baseAndExtentEqual = m_base == m_extent; @@ -259,10 +259,12 @@ void Selection::validate() } else if (m_extent.isNull()) { m_extent = m_base; m_baseIsFirst = true; - } else { + } else m_baseIsFirst = comparePositions(m_base, m_extent) <= 0; - } +} +void VisibleSelection::setStartAndEndFromBaseAndExtentRespectingGranularity() +{ if (m_baseIsFirst) { m_start = m_base; m_end = m_extent; @@ -271,7 +273,6 @@ void Selection::validate() m_end = m_base; } - // Expand the selection if requested. switch (m_granularity) { case CharacterGranularity: // Don't do any expansion. @@ -385,26 +386,31 @@ void Selection::validate() m_start = m_end; if (m_end.isNull()) m_end = m_start; - - adjustForEditableContent(); +} - // adjust the state +void VisibleSelection::updateSelectionType() +{ if (m_start.isNull()) { ASSERT(m_end.isNull()); - m_state = NONE; - - // enforce downstream affinity if not caret, as affinity only - // makes sense for caret - m_affinity = DOWNSTREAM; + m_selectionType = NoSelection; } else if (m_start == m_end || m_start.upstream() == m_end.upstream()) { - m_state = CARET; - } else { - m_state = RANGE; + m_selectionType = CaretSelection; + } else + m_selectionType = RangeSelection; - // enforce downstream affinity if not caret, as affinity only - // makes sense for caret + // Affinity only makes sense for a caret + if (m_selectionType != CaretSelection) m_affinity = DOWNSTREAM; +} + +void VisibleSelection::validate() +{ + setBaseAndExtentToDeepEquivalents(); + setStartAndEndFromBaseAndExtentRespectingGranularity(); + adjustSelectionToAvoidCrossingEditingBoundaries(); + updateSelectionType(); + if (selectionType() == RangeSelection) { // "Constrain" the selection to be the smallest equivalent range of nodes. // This is a somewhat arbitrary choice, but experience shows that it is // useful to make to make the selection "canonical" (if only for @@ -419,12 +425,12 @@ void Selection::validate() } // FIXME: This function breaks the invariant of this class. -// But because we use Selection to store values in editing commands for use when +// But because we use VisibleSelection to store values in editing commands for use when // undoing the command, we need to be able to create a selection that while currently // invalid, will be valid once the changes are undone. This is a design problem. -// To fix it we either need to change the invariants of Selection or create a new +// To fix it we either need to change the invariants of VisibleSelection or create a new // class for editing to use that can manipulate selections that are not currently valid. -void Selection::setWithoutValidation(const Position& base, const Position& extent) +void VisibleSelection::setWithoutValidation(const Position& base, const Position& extent) { ASSERT(!base.isNull()); ASSERT(!extent.isNull()); @@ -441,10 +447,10 @@ void Selection::setWithoutValidation(const Position& base, const Position& exten m_start = extent; m_end = base; } - m_state = RANGE; + m_selectionType = RangeSelection; } -void Selection::adjustForEditableContent() +void VisibleSelection::adjustSelectionToAvoidCrossingEditingBoundaries() { if (m_base.isNull() || m_start.isNull() || m_end.isNull()) return; @@ -496,17 +502,19 @@ void Selection::adjustForEditableContent() Position p = previousVisuallyDistinctCandidate(m_end); Node* shadowAncestor = endRoot ? endRoot->shadowAncestorNode() : 0; if (p.isNull() && endRoot && (shadowAncestor != endRoot)) - p = Position(shadowAncestor, maxDeepOffset(shadowAncestor)); + p = lastDeepEditingPositionForNode(shadowAncestor); while (p.isNotNull() && !(lowestEditableAncestor(p.node()) == baseEditableAncestor && !isEditablePosition(p))) { Node* root = editableRootForPosition(p); shadowAncestor = root ? root->shadowAncestorNode() : 0; p = isAtomicNode(p.node()) ? positionBeforeNode(p.node()) : previousVisuallyDistinctCandidate(p); if (p.isNull() && (shadowAncestor != root)) - p = Position(shadowAncestor, maxDeepOffset(shadowAncestor)); + p = lastDeepEditingPositionForNode(shadowAncestor); } VisiblePosition previous(p); - + if (previous.isNull()) { + // The selection crosses an Editing boundary. This is a + // programmer error in the editing code. Happy debugging! ASSERT_NOT_REACHED(); m_base = Position(); m_extent = Position(); @@ -534,6 +542,8 @@ void Selection::adjustForEditableContent() VisiblePosition next(p); if (next.isNull()) { + // The selection crosses an Editing boundary. This is a + // programmer error in the editing code. Happy debugging! ASSERT_NOT_REACHED(); m_base = Position(); m_extent = Position(); @@ -549,42 +559,42 @@ void Selection::adjustForEditableContent() m_extent = m_baseIsFirst ? m_end : m_start; } -bool Selection::isContentEditable() const +bool VisibleSelection::isContentEditable() const { return isEditablePosition(start()); } -bool Selection::isContentRichlyEditable() const +bool VisibleSelection::isContentRichlyEditable() const { return isRichlyEditablePosition(start()); } -Element* Selection::rootEditableElement() const +Element* VisibleSelection::rootEditableElement() const { return editableRootForPosition(start()); } -Node* Selection::shadowTreeRootNode() const +Node* VisibleSelection::shadowTreeRootNode() const { return start().node() ? start().node()->shadowTreeRootNode() : 0; } -void Selection::debugPosition() const +void VisibleSelection::debugPosition() const { if (!m_start.node()) return; - fprintf(stderr, "Selection =================\n"); + fprintf(stderr, "VisibleSelection =================\n"); if (m_start == m_end) { Position pos = m_start; - fprintf(stderr, "pos: %s %p:%d\n", pos.node()->nodeName().utf8().data(), pos.node(), pos.offset()); + fprintf(stderr, "pos: %s %p:%d\n", pos.node()->nodeName().utf8().data(), pos.node(), pos.m_offset); } else { Position pos = m_start; - fprintf(stderr, "start: %s %p:%d\n", pos.node()->nodeName().utf8().data(), pos.node(), pos.offset()); + fprintf(stderr, "start: %s %p:%d\n", pos.node()->nodeName().utf8().data(), pos.node(), pos.m_offset); fprintf(stderr, "-----------------------------------\n"); pos = m_end; - fprintf(stderr, "end: %s %p:%d\n", pos.node()->nodeName().utf8().data(), pos.node(), pos.offset()); + fprintf(stderr, "end: %s %p:%d\n", pos.node()->nodeName().utf8().data(), pos.node(), pos.m_offset); fprintf(stderr, "-----------------------------------\n"); } @@ -593,7 +603,7 @@ void Selection::debugPosition() const #ifndef NDEBUG -void Selection::formatForDebugger(char* buffer, unsigned length) const +void VisibleSelection::formatForDebugger(char* buffer, unsigned length) const { String result; String s; @@ -614,11 +624,11 @@ void Selection::formatForDebugger(char* buffer, unsigned length) const strncpy(buffer, result.utf8().data(), length - 1); } -void Selection::showTreeForThis() const +void VisibleSelection::showTreeForThis() const { if (start().node()) { start().node()->showTreeAndMark(start().node(), "S", end().node(), "E"); - fprintf(stderr, "start offset: %d, end offset: %d\n", start().offset(), end().offset()); + fprintf(stderr, "start offset: %d, end offset: %d\n", start().m_offset, end().m_offset); } } @@ -628,12 +638,12 @@ void Selection::showTreeForThis() const #ifndef NDEBUG -void showTree(const WebCore::Selection& sel) +void showTree(const WebCore::VisibleSelection& sel) { sel.showTreeForThis(); } -void showTree(const WebCore::Selection* sel) +void showTree(const WebCore::VisibleSelection* sel) { if (sel) sel->showTreeForThis(); diff --git a/WebCore/editing/Selection.h b/WebCore/editing/VisibleSelection.h index 5e21701..ae2142d 100644 --- a/WebCore/editing/Selection.h +++ b/WebCore/editing/VisibleSelection.h @@ -23,8 +23,8 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#ifndef Selection_h -#define Selection_h +#ifndef VisibleSelection_h +#define VisibleSelection_h #include "TextGranularity.h" #include "VisiblePosition.h" @@ -35,24 +35,23 @@ class Position; const EAffinity SEL_DEFAULT_AFFINITY = DOWNSTREAM; -class Selection { +class VisibleSelection { public: - enum EState { NONE, CARET, RANGE }; - enum EDirection { FORWARD, BACKWARD, RIGHT, LEFT }; + enum SelectionType { NoSelection, CaretSelection, RangeSelection }; - Selection(); + VisibleSelection(); - Selection(const Position&, EAffinity); - Selection(const Position&, const Position&, EAffinity); + VisibleSelection(const Position&, EAffinity); + VisibleSelection(const Position&, const Position&, EAffinity); - Selection(const Range*, EAffinity = SEL_DEFAULT_AFFINITY); + VisibleSelection(const Range*, EAffinity = SEL_DEFAULT_AFFINITY); - Selection(const VisiblePosition&); - Selection(const VisiblePosition&, const VisiblePosition&); + VisibleSelection(const VisiblePosition&); + VisibleSelection(const VisiblePosition&, const VisiblePosition&); - static Selection selectionFromContentsOfNode(Node*); + static VisibleSelection selectionFromContentsOfNode(Node*); - EState state() const { return m_state; } + SelectionType selectionType() const { return m_selectionType; } void setAffinity(EAffinity affinity) { m_affinity = affinity; } EAffinity affinity() const { return m_affinity; } @@ -63,17 +62,17 @@ public: void setExtent(const VisiblePosition&); Position base() const { return m_base; } - Position extent() const { return m_extent; } + Position extent() const { return m_extent; } Position start() const { return m_start; } Position end() const { return m_end; } VisiblePosition visibleStart() const { return VisiblePosition(m_start, isRange() ? DOWNSTREAM : affinity()); } VisiblePosition visibleEnd() const { return VisiblePosition(m_end, isRange() ? UPSTREAM : affinity()); } - bool isNone() const { return state() == NONE; } - bool isCaret() const { return state() == CARET; } - bool isRange() const { return state() == RANGE; } - bool isCaretOrRange() const { return state() != NONE; } + bool isNone() const { return selectionType() == NoSelection; } + bool isCaret() const { return selectionType() == CaretSelection; } + bool isRange() const { return selectionType() == RangeSelection; } + bool isCaretOrRange() const { return selectionType() != NoSelection; } bool isBaseFirst() const { return m_baseIsFirst; } @@ -82,7 +81,13 @@ public: bool expandUsingGranularity(TextGranularity granularity); TextGranularity granularity() const { return m_granularity; } - PassRefPtr<Range> toRange() const; + // We don't yet support multi-range selections, so we only ever have one range to return. + PassRefPtr<Range> firstRange() const; + + // FIXME: Most callers probably don't want this function, but are using it + // for historical reasons. toNormalizedRange contracts the range around + // text, and moves the caret upstream before returning the range. + PassRefPtr<Range> toNormalizedRange() const; Element* rootEditableElement() const; bool isContentEditable() const; @@ -100,27 +105,33 @@ public: private: void validate(); - void adjustForEditableContent(); - Position m_base; // base position for the selection - Position m_extent; // extent position for the selection - Position m_start; // start position for the selection - Position m_end; // end position for the selection + // Support methods for validate() + void setBaseAndExtentToDeepEquivalents(); + void setStartAndEndFromBaseAndExtentRespectingGranularity(); + void adjustSelectionToAvoidCrossingEditingBoundaries(); + void updateSelectionType(); + + // FIXME: These should all be VisiblePositions + Position m_base; // Where the first click happened + Position m_extent; // Where the end click happened + Position m_start; // Leftmost position when expanded to respect granularity + Position m_end; // Rightmost position when expanded to respect granularity - EAffinity m_affinity; // the upstream/downstream affinity of the caret - TextGranularity m_granularity; // granularity of start/end selection + EAffinity m_affinity; // the upstream/downstream affinity of the caret + TextGranularity m_granularity; // granularity of start/end selection // these are cached, can be recalculated by validate() - EState m_state; // the state of the selection + SelectionType m_selectionType; // None, Caret, Range bool m_baseIsFirst; // true if base is before the extent }; -inline bool operator==(const Selection& a, const Selection& b) +inline bool operator==(const VisibleSelection& a, const VisibleSelection& b) { return a.start() == b.start() && a.end() == b.end() && a.affinity() == b.affinity() && a.granularity() == b.granularity() && a.isBaseFirst() == b.isBaseFirst(); } -inline bool operator!=(const Selection& a, const Selection& b) +inline bool operator!=(const VisibleSelection& a, const VisibleSelection& b) { return !(a == b); } @@ -129,8 +140,8 @@ inline bool operator!=(const Selection& a, const Selection& b) #ifndef NDEBUG // Outside the WebCore namespace for ease of invocation from gdb. -void showTree(const WebCore::Selection&); -void showTree(const WebCore::Selection*); +void showTree(const WebCore::VisibleSelection&); +void showTree(const WebCore::VisibleSelection*); #endif -#endif // Selection_h +#endif // VisibleSelection_h diff --git a/WebCore/editing/htmlediting.cpp b/WebCore/editing/htmlediting.cpp index ee41e69..055c3a7 100644 --- a/WebCore/editing/htmlediting.cpp +++ b/WebCore/editing/htmlediting.cpp @@ -40,7 +40,7 @@ #include "PositionIterator.h" #include "RenderObject.h" #include "Range.h" -#include "Selection.h" +#include "VisibleSelection.h" #include "Text.h" #include "TextIterator.h" #include "VisiblePosition.h" @@ -98,8 +98,8 @@ int comparePositions(const Position& a, const Position& b) ASSERT(nodeA); Node* nodeB = b.node(); ASSERT(nodeB); - int offsetA = a.offset(); - int offsetB = b.offset(); + int offsetA = a.m_offset; + int offsetB = b.m_offset; Node* shadowAncestorA = nodeA->shadowAncestorNode(); if (shadowAncestorA == nodeA) @@ -221,8 +221,8 @@ Position nextVisuallyDistinctCandidate(const Position& position) { Position p = position; Position downstreamStart = p.downstream(); - while (!p.atEnd()) { - p = p.next(UsingComposedCharacters); + while (!p.atEndOfTree()) { + p = p.next(Character); if (p.isCandidate() && p.downstream() != downstreamStart) return p; } @@ -244,8 +244,8 @@ Position previousVisuallyDistinctCandidate(const Position& position) { Position p = position; Position downstreamStart = p.downstream(); - while (!p.atStart()) { - p = p.previous(UsingComposedCharacters); + while (!p.atStartOfTree()) { + p = p.previous(Character); if (p.isCandidate() && p.downstream() != downstreamStart) return p; } @@ -255,14 +255,14 @@ Position previousVisuallyDistinctCandidate(const Position& position) VisiblePosition firstEditablePositionAfterPositionInRoot(const Position& position, Node* highestRoot) { // position falls before highestRoot. - if (comparePositions(position, Position(highestRoot, 0)) == -1 && highestRoot->isContentEditable()) - return VisiblePosition(Position(highestRoot, 0)); - + if (comparePositions(position, firstDeepEditingPositionForNode(highestRoot)) == -1 && highestRoot->isContentEditable()) + return firstDeepEditingPositionForNode(highestRoot); + Position p = position; if (Node* shadowAncestor = p.node()->shadowAncestorNode()) if (shadowAncestor != p.node()) - p = Position(shadowAncestor, maxDeepOffset(shadowAncestor)); + p = lastDeepEditingPositionForNode(shadowAncestor); while (p.node() && !isEditablePosition(p) && p.node()->isDescendantOf(highestRoot)) p = isAtomicNode(p.node()) ? positionAfterNode(p.node()) : nextVisuallyDistinctCandidate(p); @@ -276,14 +276,14 @@ 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, Position(highestRoot, maxDeepOffset(highestRoot))) == 1) - return VisiblePosition(Position(highestRoot, maxDeepOffset(highestRoot))); - + if (comparePositions(position, lastDeepEditingPositionForNode(highestRoot)) == 1) + return lastDeepEditingPositionForNode(highestRoot); + Position p = position; if (Node* shadowAncestor = p.node()->shadowAncestorNode()) if (shadowAncestor != p.node()) - p = Position(shadowAncestor, 0); + p = firstDeepEditingPositionForNode(shadowAncestor); while (p.node() && !isEditablePosition(p) && p.node()->isDescendantOf(highestRoot)) p = isAtomicNode(p.node()) ? positionBeforeNode(p.node()) : previousVisuallyDistinctCandidate(p); @@ -294,6 +294,7 @@ VisiblePosition lastEditablePositionBeforePositionInRoot(const Position& positio return VisiblePosition(p); } +// FIXME: The method name, comment, and code say three different things here! // Whether or not content before and after this node will collapse onto the same line as it. bool isBlock(const Node* node) { @@ -309,39 +310,44 @@ Node* enclosingBlock(Node* node) return static_cast<Element*>(enclosingNodeOfType(Position(node, 0), isBlock)); } +// Internally editing uses "invalid" positions for historical reasons. For +// example, in <div><img /></div>, Editing might use (img, 1) for the position +// after <img>, but we have to convert that to (div, 1) before handing the +// position to a Range object. Ideally all internal positions should +// be "range compliant" for simplicity. Position rangeCompliantEquivalent(const Position& pos) { if (pos.isNull()) return Position(); - Node *node = pos.node(); - - if (pos.offset() <= 0) { + Node* node = pos.node(); + + if (pos.m_offset <= 0) { if (node->parentNode() && (editingIgnoresContent(node) || isTableElement(node))) return positionBeforeNode(node); return Position(node, 0); } - + if (node->offsetInCharacters()) - return Position(node, min(node->maxCharacterOffset(), pos.offset())); - + return Position(node, min(node->maxCharacterOffset(), pos.m_offset)); + int maxCompliantOffset = node->childNodeCount(); - if (pos.offset() > maxCompliantOffset) { + if (pos.m_offset > maxCompliantOffset) { if (node->parentNode()) return positionAfterNode(node); - + // there is no other option at this point than to // use the highest allowed position in the node return Position(node, maxCompliantOffset); } // Editing should never generate positions like this. - if ((pos.offset() < maxCompliantOffset) && editingIgnoresContent(node)) { + if ((pos.m_offset < maxCompliantOffset) && editingIgnoresContent(node)) { ASSERT_NOT_REACHED(); return node->parentNode() ? positionBeforeNode(node) : Position(node, 0); } - if (pos.offset() == maxCompliantOffset && (editingIgnoresContent(node) || isTableElement(node))) + if (pos.m_offset == maxCompliantOffset && (editingIgnoresContent(node) || isTableElement(node))) return positionAfterNode(node); return Position(pos); @@ -356,7 +362,7 @@ Position rangeCompliantEquivalent(const VisiblePosition& vpos) // in a node. It returns 1 for some elements even though they do not have children, which // creates technically invalid DOM Positions. Be sure to call rangeCompliantEquivalent // on a Position before using it to create a DOM Range, or an exception will be thrown. -int maxDeepOffset(const Node *node) +int lastOffsetForEditing(const Node* node) { ASSERT(node); if (!node) @@ -536,7 +542,7 @@ Position positionOutsideContainingSpecialElement(const Position &pos, Node **con Node* isFirstPositionAfterTable(const VisiblePosition& visiblePosition) { Position upstream(visiblePosition.deepEquivalent().upstream()); - if (upstream.node() && upstream.node()->renderer() && upstream.node()->renderer()->isTable() && upstream.offset() == maxDeepOffset(upstream.node())) + if (upstream.node() && upstream.node()->renderer() && upstream.node()->renderer()->isTable() && upstream.atLastEditingPositionForNode()) return upstream.node(); return 0; @@ -545,19 +551,21 @@ Node* isFirstPositionAfterTable(const VisiblePosition& visiblePosition) Node* isLastPositionBeforeTable(const VisiblePosition& visiblePosition) { Position downstream(visiblePosition.deepEquivalent().downstream()); - if (downstream.node() && downstream.node()->renderer() && downstream.node()->renderer()->isTable() && downstream.offset() == 0) + if (downstream.node() && downstream.node()->renderer() && downstream.node()->renderer()->isTable() && downstream.atFirstEditingPositionForNode()) return downstream.node(); return 0; } -Position positionBeforeNode(const Node *node) +Position positionBeforeNode(const Node* node) { + // FIXME: This should ASSERT(node->parentNode()) return Position(node->parentNode(), node->nodeIndex()); } -Position positionAfterNode(const Node *node) +Position positionAfterNode(const Node* node) { + // FIXME: This should ASSERT(node->parentNode()) return Position(node->parentNode(), node->nodeIndex() + 1); } @@ -694,16 +702,17 @@ static Node* appendedSublist(Node* listItem) return 0; } +// FIXME: This method should not need to call isStartOfParagraph/isEndOfParagraph Node* enclosingEmptyListItem(const VisiblePosition& visiblePos) { // Check that position is on a line by itself inside a list item Node* listChildNode = enclosingListChild(visiblePos.deepEquivalent().node()); if (!listChildNode || !isStartOfParagraph(visiblePos) || !isEndOfParagraph(visiblePos)) return 0; - - VisiblePosition firstInListChild(Position(listChildNode, 0)); - VisiblePosition lastInListChild(Position(listChildNode, maxDeepOffset(listChildNode))); - + + VisiblePosition firstInListChild(firstDeepEditingPositionForNode(listChildNode)); + VisiblePosition lastInListChild(lastDeepEditingPositionForNode(listChildNode)); + if (firstInListChild != visiblePos || lastInListChild != visiblePos) return 0; @@ -814,16 +823,16 @@ Position positionBeforeTabSpan(const Position& pos) PassRefPtr<Element> createTabSpanElement(Document* document, PassRefPtr<Node> tabTextNode) { - // make the span to hold the tab - ExceptionCode ec = 0; - RefPtr<Element> spanElement = document->createElementNS(xhtmlNamespaceURI, "span", ec); - ASSERT(ec == 0); + // Make the span to hold the tab. + RefPtr<Element> spanElement = document->createElement(spanTag, false); spanElement->setAttribute(classAttr, AppleTabSpanClass); spanElement->setAttribute(styleAttr, "white-space:pre"); - // add tab text to that span + // Add tab text to that span. if (!tabTextNode) tabTextNode = document->createEditingTextNode("\t"); + + ExceptionCode ec = 0; spanElement->appendChild(tabTextNode, ec); ASSERT(ec == 0); @@ -873,7 +882,7 @@ unsigned numEnclosingMailBlockquotes(const Position& p) bool isMailBlockquote(const Node *node) { - if (!node || !node->isElementNode() && !node->hasTagName(blockquoteTag)) + if (!node || (!node->isElementNode() && !node->hasTagName(blockquoteTag))) return false; return static_cast<const Element *>(node)->getAttribute("type") == "cite"; @@ -894,7 +903,7 @@ int caretMaxOffset(const Node* n) if (n->isTextNode() && n->renderer()) return n->renderer()->caretMaxOffset(); // For containers return the number of children. For others do the same as above. - return maxDeepOffset(n); + return lastOffsetForEditing(n); } bool lineBreakExistsAtPosition(const VisiblePosition& visiblePosition) @@ -904,15 +913,15 @@ bool lineBreakExistsAtPosition(const VisiblePosition& visiblePosition) Position downstream(visiblePosition.deepEquivalent().downstream()); return downstream.node()->hasTagName(brTag) || - downstream.node()->isTextNode() && downstream.node()->renderer()->style()->preserveNewline() && visiblePosition.characterAfter() == '\n'; + (downstream.node()->isTextNode() && downstream.node()->renderer()->style()->preserveNewline() && visiblePosition.characterAfter() == '\n'); } // Modifies selections that have an end point at the edge of a table // that contains the other endpoint so that they don't confuse // code that iterates over selected paragraphs. -Selection selectionForParagraphIteration(const Selection& original) +VisibleSelection selectionForParagraphIteration(const VisibleSelection& original) { - Selection newSelection(original); + VisibleSelection newSelection(original); VisiblePosition startOfSelection(newSelection.visibleStart()); VisiblePosition endOfSelection(newSelection.visibleEnd()); @@ -922,7 +931,7 @@ Selection selectionForParagraphIteration(const Selection& original) // (a table is itself a paragraph). if (Node* table = isFirstPositionAfterTable(endOfSelection)) if (startOfSelection.deepEquivalent().node()->isDescendantOf(table)) - newSelection = Selection(startOfSelection, endOfSelection.previous(true)); + newSelection = VisibleSelection(startOfSelection, endOfSelection.previous(true)); // 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 @@ -930,7 +939,7 @@ Selection selectionForParagraphIteration(const Selection& original) // containing the table itself. if (Node* table = isLastPositionBeforeTable(startOfSelection)) if (endOfSelection.deepEquivalent().node()->isDescendantOf(table)) - newSelection = Selection(startOfSelection.next(true), endOfSelection); + newSelection = VisibleSelection(startOfSelection.next(true), endOfSelection); return newSelection; } @@ -976,12 +985,12 @@ PassRefPtr<Range> avoidIntersectionWithNode(const Range* range, Node* node) return Range::create(document, startContainer, startOffset, endContainer, endOffset); } -Selection avoidIntersectionWithNode(const Selection& selection, Node* node) +VisibleSelection avoidIntersectionWithNode(const VisibleSelection& selection, Node* node) { if (selection.isNone()) - return Selection(selection); + return VisibleSelection(selection); - Selection updatedSelection(selection); + VisibleSelection updatedSelection(selection); Node* base = selection.base().node(); Node* extent = selection.extent().node(); ASSERT(base); diff --git a/WebCore/editing/htmlediting.h b/WebCore/editing/htmlediting.h index a8bc0b4..ece5e29 100644 --- a/WebCore/editing/htmlediting.h +++ b/WebCore/editing/htmlediting.h @@ -37,13 +37,13 @@ class HTMLElement; class Node; class Position; class Range; -class Selection; +class VisibleSelection; class String; class VisiblePosition; Position rangeCompliantEquivalent(const Position&); Position rangeCompliantEquivalent(const VisiblePosition&); -int maxDeepOffset(const Node*); +int lastOffsetForEditing(const Node*); bool isAtomicNode(const Node*); bool editingIgnoresContent(const Node*); bool canHaveChildrenForEditing(const Node*); @@ -72,7 +72,7 @@ Position positionBeforeNode(const Node*); Position positionAfterNode(const Node*); PassRefPtr<Range> avoidIntersectionWithNode(const Range*, Node*); -Selection avoidIntersectionWithNode(const Selection&, Node*); +VisibleSelection avoidIntersectionWithNode(const VisibleSelection&, Node*); bool isSpecialElement(const Node*); bool validBlockTag(const String&); @@ -129,7 +129,7 @@ bool isTableCell(const Node*); bool lineBreakExistsAtPosition(const VisiblePosition&); -Selection selectionForParagraphIteration(const Selection&); +VisibleSelection selectionForParagraphIteration(const VisibleSelection&); int indexForVisiblePosition(VisiblePosition&); diff --git a/WebCore/editing/mac/SelectionControllerMac.mm b/WebCore/editing/mac/SelectionControllerMac.mm index 4125c3a..03e1051 100644 --- a/WebCore/editing/mac/SelectionControllerMac.mm +++ b/WebCore/editing/mac/SelectionControllerMac.mm @@ -27,42 +27,41 @@ #import "SelectionController.h" #import "AXObjectCache.h" -#import "Document.h" #import "Frame.h" -#import "FrameView.h" #import "RenderView.h" -#import "Selection.h" #import "WebCoreViewFactory.h" -#import <ApplicationServices/ApplicationServices.h> - namespace WebCore { void SelectionController::notifyAccessibilityForSelectionChange() { + Document* document = m_frame->document(); + if (AXObjectCache::accessibilityEnabled() && m_sel.start().isNotNull() && m_sel.end().isNotNull()) - m_frame->document()->axObjectCache()->postNotification(m_sel.start().node()->renderer(), "AXSelectedTextChanged"); - + document->axObjectCache()->postNotification(m_sel.start().node()->renderer(), "AXSelectedTextChanged"); + // if zoom feature is enabled, insertion point changes should update the zoom - if (UAZoomEnabled() && m_sel.isCaret() && m_sel.start().node()) { - RenderView *renderView = static_cast<RenderView*>(m_sel.start().node()->renderer()); - if (renderView) { - IntRect selectionRect = absoluteCaretBounds(); - IntRect viewRect = renderView->viewRect(); - FrameView* frameView = renderView->view()->frameView(); - if (frameView) { - selectionRect = frameView->contentsToScreen(selectionRect); - viewRect = frameView->contentsToScreen(viewRect); - CGRect cgCaretRect = CGRectMake(selectionRect.x(), selectionRect.y(), selectionRect.width(), selectionRect.height()); - CGRect cgViewRect = CGRectMake(viewRect.x(), viewRect.y(), viewRect.width(), viewRect.height()); - cgCaretRect = [[WebCoreViewFactory sharedFactory] accessibilityConvertScreenRect:cgCaretRect]; - cgViewRect = [[WebCoreViewFactory sharedFactory] accessibilityConvertScreenRect:cgViewRect]; + if (!UAZoomEnabled() || !m_sel.isCaret()) + return; - (void)UAZoomChangeFocus(&cgViewRect, &cgCaretRect, kUAZoomFocusTypeInsertionPoint); - } - } - } -} + RenderView* renderView = document->renderView(); + if (!renderView) + return; + FrameView* frameView = m_frame->view(); + if (!frameView) + return; + IntRect selectionRect = absoluteCaretBounds(); + IntRect viewRect = renderView->viewRect(); + + selectionRect = frameView->contentsToScreen(selectionRect); + viewRect = frameView->contentsToScreen(viewRect); + CGRect cgCaretRect = CGRectMake(selectionRect.x(), selectionRect.y(), selectionRect.width(), selectionRect.height()); + CGRect cgViewRect = CGRectMake(viewRect.x(), viewRect.y(), viewRect.width(), viewRect.height()); + cgCaretRect = [[WebCoreViewFactory sharedFactory] accessibilityConvertScreenRect:cgCaretRect]; + cgViewRect = [[WebCoreViewFactory sharedFactory] accessibilityConvertScreenRect:cgViewRect]; + + UAZoomChangeFocus(&cgViewRect, &cgCaretRect, kUAZoomFocusTypeInsertionPoint); +} } // namespace WebCore diff --git a/WebCore/editing/markup.cpp b/WebCore/editing/markup.cpp index 1352457..7de5287 100644 --- a/WebCore/editing/markup.cpp +++ b/WebCore/editing/markup.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004, 2005, 2006, 2007, 2008 Apple Inc. All rights reserved. + * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -52,7 +52,7 @@ #include "ProcessingInstruction.h" #include "QualifiedName.h" #include "Range.h" -#include "Selection.h" +#include "VisibleSelection.h" #include "TextIterator.h" #include "htmlediting.h" #include "visible_units.h" @@ -408,8 +408,7 @@ static void appendStartMarkup(Vector<UChar>& result, const Node *node, const Ran bool useRenderedText = !enclosingNodeWithTag(Position(const_cast<Node*>(node), 0), selectTag); String markup = escapeContentText(useRenderedText ? renderedText(node, range) : stringValueForRange(node, range), false); - if (annotate) - markup = convertHTMLTextToInterchangeFormat(markup, static_cast<const Text*>(node)); + markup = convertHTMLTextToInterchangeFormat(markup, static_cast<const Text*>(node)); append(result, markup); break; } @@ -438,7 +437,7 @@ static void appendStartMarkup(Vector<UChar>& result, const Node *node, const Ran case Node::ELEMENT_NODE: { result.append('<'); const Element* el = static_cast<const Element*>(node); - bool convert = convertBlocksToInlines & isBlock(const_cast<Node*>(node)); + bool convert = convertBlocksToInlines && isBlock(const_cast<Node*>(node)); append(result, el->nodeNamePreservingCase()); NamedAttrMap *attrs = el->attributes(); unsigned length = attrs->length(); @@ -587,26 +586,50 @@ static String getEndMarkup(const Node *node) return String::adopt(result); } -static void appendMarkup(Vector<UChar>& result, Node* startNode, bool onlyIncludeChildren, Vector<Node*>* nodes, const HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0) +class MarkupAccumulator { +public: + MarkupAccumulator(Node* nodeToSkip, Vector<Node*>* nodes) + : m_nodeToSkip(nodeToSkip) + , m_nodes(nodes) + { + } + + void appendMarkup(Node* startNode, EChildrenOnly, const HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces = 0); + + String takeResult() { return String::adopt(m_result); } + +private: + Vector<UChar> m_result; + Node* m_nodeToSkip; + Vector<Node*>* m_nodes; +}; + +// FIXME: Would be nice to do this in a non-recursive way. +void MarkupAccumulator::appendMarkup(Node* startNode, EChildrenOnly childrenOnly, const HashMap<AtomicStringImpl*, AtomicStringImpl*>* namespaces) { + if (startNode == m_nodeToSkip) + return; + HashMap<AtomicStringImpl*, AtomicStringImpl*> namespaceHash; if (namespaces) namespaceHash = *namespaces; - - if (!onlyIncludeChildren) { - if (nodes) - nodes->append(startNode); - - appendStartMarkup(result,startNode, 0, DoNotAnnotateForInterchange, false, &namespaceHash); + + // start tag + if (!childrenOnly) { + if (m_nodes) + m_nodes->append(startNode); + appendStartMarkup(m_result, startNode, 0, DoNotAnnotateForInterchange, false, &namespaceHash); } - // print children - if (!(startNode->document()->isHTMLDocument() && doesHTMLForbidEndTag(startNode))) + + // children + if (!(startNode->document()->isHTMLDocument() && doesHTMLForbidEndTag(startNode))) { for (Node* current = startNode->firstChild(); current; current = current->nextSibling()) - appendMarkup(result, current, false, nodes, &namespaceHash); - - // Print my ending tag - if (!onlyIncludeChildren) - appendEndMarkup(result, startNode); + appendMarkup(current, IncludeNode, &namespaceHash); + } + + // end tag + if (!childrenOnly) + appendEndMarkup(m_result, startNode); } static void completeURLs(Node* node, const String& baseURL) @@ -690,7 +713,7 @@ static String joinMarkups(const Vector<String>& preMarkups, const Vector<String> length += postMarkups[i].length(); Vector<UChar> result; - result.reserveCapacity(length); + result.reserveInitialCapacity(length); for (size_t i = preCount; i > 0; --i) append(result, preMarkups[i - 1]); @@ -701,6 +724,24 @@ static String joinMarkups(const Vector<String>& preMarkups, const Vector<String> return String::adopt(result); } +static bool isSpecialAncestorBlock(Node* node) +{ + if (!node || !isBlock(node)) + return false; + + return node->hasTagName(listingTag) || + node->hasTagName(olTag) || + node->hasTagName(preTag) || + node->hasTagName(tableTag) || + node->hasTagName(ulTag) || + node->hasTagName(xmpTag) || + node->hasTagName(h1Tag) || + node->hasTagName(h2Tag) || + node->hasTagName(h3Tag) || + node->hasTagName(h4Tag) || + node->hasTagName(h5Tag); +} + // FIXME: Shouldn't we omit style info when annotate == DoNotAnnotateForInterchange? // FIXME: At least, annotation and style info should probably not be included in range.markupString() String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterchange annotate, bool convertBlocksToInlines) @@ -849,12 +890,7 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc table = table->parentNode(); if (table) specialCommonAncestor = table; - } else if (commonAncestorBlock->hasTagName(listingTag) - || commonAncestorBlock->hasTagName(olTag) - || commonAncestorBlock->hasTagName(preTag) - || commonAncestorBlock->hasTagName(tableTag) - || commonAncestorBlock->hasTagName(ulTag) - || commonAncestorBlock->hasTagName(xmpTag)) + } else if (isSpecialAncestorBlock(commonAncestorBlock)) specialCommonAncestor = commonAncestorBlock; } @@ -888,7 +924,7 @@ String createMarkup(const Range* range, Vector<Node*>* nodes, EAnnotateForInterc // FIXME: Only include markup for a fully selected root (and ancestors of lastClosed up to that root) if // there are styles/attributes on those nodes that need to be included to preserve the appearance of the copied markup. // FIXME: Do this for all fully selected blocks, not just the body. - Node* fullySelectedRoot = body && *Selection::selectionFromContentsOfNode(body).toRange() == *updatedRange ? body : 0; + Node* fullySelectedRoot = body && *VisibleSelection::selectionFromContentsOfNode(body).toNormalizedRange() == *updatedRange ? body : 0; if (annotate && fullySelectedRoot) specialCommonAncestor = fullySelectedRoot; @@ -1004,30 +1040,21 @@ PassRefPtr<DocumentFragment> createFragmentFromMarkup(Document* document, const return fragment.release(); } -String createMarkup(const Node* node, EChildrenOnly includeChildren, Vector<Node*>* nodes) +String createMarkup(const Node* node, EChildrenOnly childrenOnly, Vector<Node*>* nodes) { - Vector<UChar> result; - if (!node) return ""; - Document* document = node->document(); - Frame* frame = document->frame(); - DeleteButtonController* deleteButton = frame ? frame->editor()->deleteButtonController() : 0; - - // disable the delete button so it's elements are not serialized into the markup - if (deleteButton) { - if (node->isDescendantOf(deleteButton->containerElement())) + HTMLElement* deleteButtonContainerElement = 0; + if (Frame* frame = node->document()->frame()) { + deleteButtonContainerElement = frame->editor()->deleteButtonController()->containerElement(); + if (node->isDescendantOf(deleteButtonContainerElement)) return ""; - deleteButton->disable(); } - appendMarkup(result, const_cast<Node*>(node), includeChildren, nodes); - - if (deleteButton) - deleteButton->enable(); - - return String::adopt(result); + MarkupAccumulator accumulator(deleteButtonContainerElement, nodes); + accumulator.appendMarkup(const_cast<Node*>(node), childrenOnly); + return accumulator.takeResult(); } static void fillContainerFromString(ContainerNode* paragraph, const String& string) @@ -1140,7 +1167,7 @@ PassRefPtr<DocumentFragment> createFragmentFromText(Range* context, const String element->setAttribute(classAttr, AppleInterchangeNewline); } else { if (useClonesOfEnclosingBlock) - element = block->cloneElement(); + element = block->cloneElementWithoutChildren(); else element = createDefaultParagraphElement(document); fillContainerFromString(element.get(), s); diff --git a/WebCore/editing/qt/EditorQt.cpp b/WebCore/editing/qt/EditorQt.cpp index 89ee78e..e74e2f3 100644 --- a/WebCore/editing/qt/EditorQt.cpp +++ b/WebCore/editing/qt/EditorQt.cpp @@ -31,7 +31,7 @@ #include "ClipboardQt.h" #include "Document.h" #include "Element.h" -#include "Selection.h" +#include "VisibleSelection.h" #include "SelectionController.h" #include "TextIterator.h" #include "htmlediting.h" diff --git a/WebCore/editing/visible_units.cpp b/WebCore/editing/visible_units.cpp index a50503d..1e8b05b 100644 --- a/WebCore/editing/visible_units.cpp +++ b/WebCore/editing/visible_units.cpp @@ -36,12 +36,32 @@ #include "TextIterator.h" #include "VisiblePosition.h" #include "htmlediting.h" +#include <wtf/unicode/Unicode.h> namespace WebCore { using namespace HTMLNames; +using namespace WTF::Unicode; -static VisiblePosition previousBoundary(const VisiblePosition &c, unsigned (*searchFunction)(const UChar *, unsigned)) +static int firstNonComplexContextLineBreak(const UChar* characters, int length) +{ + for (int i = 0; i < length; ++i) { + if (!hasLineBreakingPropertyComplexContext(characters[i])) + return i; + } + return length; +} + +static int lastNonComplexContextLineBreak(const UChar* characters, int length) +{ + for (int i = length - 1; i >= 0; --i) { + if (!hasLineBreakingPropertyComplexContext(characters[i])) + return i; + } + return -1; +} + +static VisiblePosition previousBoundary(const VisiblePosition &c, unsigned (*searchFunction)(const UChar *, unsigned, unsigned)) { Position pos = c.deepEquivalent(); Node *n = pos.node(); @@ -62,16 +82,35 @@ static VisiblePosition previousBoundary(const VisiblePosition &c, unsigned (*sea Position end = rangeCompliantEquivalent(pos); RefPtr<Range> searchRange = Range::create(d); - int exception = 0; - searchRange->setStart(start.node(), start.offset(), exception); - searchRange->setEnd(end.node(), end.offset(), exception); + Vector<UChar, 1024> string; + unsigned suffixLength = 0; + + ExceptionCode ec = 0; + if (hasLineBreakingPropertyComplexContext(c.characterBefore())) { + RefPtr<Range> forwardsScanRange(d->createRange()); + forwardsScanRange->setEndAfter(boundary, ec); + forwardsScanRange->setStart(end.node(), end.m_offset, ec); + TextIterator forwardsIterator(forwardsScanRange.get()); + while (!forwardsIterator.atEnd()) { + const UChar* characters = forwardsIterator.characters(); + int length = forwardsIterator.length(); + int i = firstNonComplexContextLineBreak(characters, length); + string.append(characters, i); + suffixLength += i; + if (i < length) + break; + forwardsIterator.advance(); + } + } + + searchRange->setStart(start.node(), start.m_offset, ec); + searchRange->setEnd(end.node(), end.m_offset, ec); - ASSERT(!exception); - if (exception) + ASSERT(!ec); + if (ec) return VisiblePosition(); - + SimplifiedBackwardsTextIterator it(searchRange.get()); - Vector<UChar, 1024> string; unsigned next = 0; bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->renderer()->style()->textSecurity() != TSNONE; while (!it.atEnd()) { @@ -85,7 +124,7 @@ static VisiblePosition previousBoundary(const VisiblePosition &c, unsigned (*sea string.prepend(iteratorString.characters(), iteratorString.length()); } - next = searchFunction(string.data(), string.size()); + next = searchFunction(string.data(), string.size(), string.size() - suffixLength); if (next != 0) break; it.advance(); @@ -94,26 +133,22 @@ static VisiblePosition previousBoundary(const VisiblePosition &c, unsigned (*sea if (it.atEnd() && next == 0) { pos = it.range()->startPosition(); } else if (next != 0) { - Node *node = it.range()->startContainer(exception); - if (node->isTextNode() || (node->renderer() && node->renderer()->isBR())) + Node *node = it.range()->startContainer(ec); + if ((node->isTextNode() && static_cast<int>(next) <= node->maxCharacterOffset()) || (node->renderer() && node->renderer()->isBR() && !next)) // The next variable contains a usable index into a text node pos = Position(node, next); else { - // Use the end of the found range, the start is not guaranteed to - // be correct. - Position end = it.range()->endPosition(); - VisiblePosition boundary(end); - unsigned i = it.length() - next; - while (i--) - boundary = boundary.previous(); - return boundary; + // Use the character iterator to translate the next value into a DOM position. + BackwardsCharacterIterator charIt(searchRange.get()); + charIt.advance(string.size() - suffixLength - next); + pos = charIt.range()->endPosition(); } } return VisiblePosition(pos, DOWNSTREAM); } -static VisiblePosition nextBoundary(const VisiblePosition &c, unsigned (*searchFunction)(const UChar *, unsigned)) +static VisiblePosition nextBoundary(const VisiblePosition &c, unsigned (*searchFunction)(const UChar *, unsigned, unsigned)) { Position pos = c.deepEquivalent(); Node *n = pos.node(); @@ -132,11 +167,30 @@ static VisiblePosition nextBoundary(const VisiblePosition &c, unsigned (*searchF RefPtr<Range> searchRange(d->createRange()); Position start(rangeCompliantEquivalent(pos)); + + Vector<UChar, 1024> string; + unsigned prefixLength = 0; + ExceptionCode ec = 0; + if (hasLineBreakingPropertyComplexContext(c.characterAfter())) { + RefPtr<Range> backwardsScanRange(d->createRange()); + backwardsScanRange->setEnd(start.node(), start.m_offset, ec); + SimplifiedBackwardsTextIterator backwardsIterator(backwardsScanRange.get()); + while (!backwardsIterator.atEnd()) { + const UChar* characters = backwardsIterator.characters(); + int length = backwardsIterator.length(); + int i = lastNonComplexContextLineBreak(characters, length); + string.prepend(characters + i + 1, length - i - 1); + prefixLength += length - i - 1; + if (i > -1) + break; + backwardsIterator.advance(); + } + } + searchRange->selectNodeContents(boundary, ec); - searchRange->setStart(start.node(), start.offset(), ec); + searchRange->setStart(start.node(), start.m_offset, ec); TextIterator it(searchRange.get(), true); - Vector<UChar, 1024> string; unsigned next = 0; bool inTextSecurityMode = start.node() && start.node()->renderer() && start.node()->renderer()->style()->textSecurity() != TSNONE; while (!it.atEnd()) { @@ -151,7 +205,7 @@ static VisiblePosition nextBoundary(const VisiblePosition &c, unsigned (*searchF string.append(iteratorString.characters(), iteratorString.length()); } - next = searchFunction(string.data(), string.size()); + next = searchFunction(string.data(), string.size(), prefixLength); if (next != string.size()) break; it.advance(); @@ -159,16 +213,18 @@ static VisiblePosition nextBoundary(const VisiblePosition &c, unsigned (*searchF if (it.atEnd() && next == string.size()) { pos = it.range()->startPosition(); - } else if (next != 0) { + } else if (next != prefixLength) { // Use the character iterator to translate the next value into a DOM position. CharacterIterator charIt(searchRange.get(), true); - charIt.advance(next - 1); + charIt.advance(next - prefixLength - 1); pos = charIt.range()->endPosition(); - // FIXME: workaround for collapsed range (where only start position is correct) emitted for some emitted newlines (see rdar://5192593) - VisiblePosition visPos = VisiblePosition(pos); - if (visPos == VisiblePosition(charIt.range()->startPosition())) - pos = visPos.next(true).deepEquivalent(); + if (*charIt.characters() == '\n') { + // FIXME: workaround for collapsed range (where only start position is correct) emitted for some emitted newlines (see rdar://5192593) + VisiblePosition visPos = VisiblePosition(pos); + if (visPos == VisiblePosition(charIt.range()->startPosition())) + pos = visPos.next(true).deepEquivalent(); + } } // generate VisiblePosition, use UPSTREAM affinity if possible @@ -177,10 +233,13 @@ static VisiblePosition nextBoundary(const VisiblePosition &c, unsigned (*searchF // --------- -static unsigned startWordBoundary(const UChar* characters, unsigned length) +static unsigned startWordBoundary(const UChar* characters, unsigned length, unsigned offset) { + ASSERT(offset); + if (lastNonComplexContextLineBreak(characters, offset) == -1) + return 0; int start, end; - findWordBoundary(characters, length, length, &start, &end); + findWordBoundary(characters, length, offset - 1, &start, &end); return start; } @@ -201,10 +260,13 @@ VisiblePosition startOfWord(const VisiblePosition &c, EWordSide side) return previousBoundary(p, startWordBoundary); } -static unsigned endWordBoundary(const UChar* characters, unsigned length) +static unsigned endWordBoundary(const UChar* characters, unsigned length, unsigned offset) { + ASSERT(offset <= length); + if (firstNonComplexContextLineBreak(characters + offset, length - offset) == static_cast<int>(length - offset)) + return length; int start, end; - findWordBoundary(characters, length, 0, &start, &end); + findWordBoundary(characters, length, offset, &start, &end); return end; } @@ -224,9 +286,11 @@ VisiblePosition endOfWord(const VisiblePosition &c, EWordSide side) return nextBoundary(p, endWordBoundary); } -static unsigned previousWordPositionBoundary(const UChar* characters, unsigned length) +static unsigned previousWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset) { - return findNextWordFromIndex(characters, length, length, false); + if (lastNonComplexContextLineBreak(characters, offset) == -1) + return 0; + return findNextWordFromIndex(characters, length, offset, false); } VisiblePosition previousWordPosition(const VisiblePosition &c) @@ -235,9 +299,11 @@ VisiblePosition previousWordPosition(const VisiblePosition &c) return c.honorEditableBoundaryAtOrAfter(prev); } -static unsigned nextWordPositionBoundary(const UChar* characters, unsigned length) +static unsigned nextWordPositionBoundary(const UChar* characters, unsigned length, unsigned offset) { - return findNextWordFromIndex(characters, length, 0, true); + if (firstNonComplexContextLineBreak(characters + offset, length - offset) == static_cast<int>(length - offset)) + return length; + return findNextWordFromIndex(characters, length, offset, true); } VisiblePosition nextWordPosition(const VisiblePosition &c) @@ -286,7 +352,7 @@ static VisiblePosition startPositionForLine(const VisiblePosition& c) // There are VisiblePositions at offset 0 in blocks without // RootInlineBoxes, like empty editable blocks and bordered blocks. Position p = c.deepEquivalent(); - if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.offset() == 0) + if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.m_offset == 0) return positionAvoidingFirstPositionInTable(c); return VisiblePosition(); @@ -301,11 +367,11 @@ static VisiblePosition startPositionForLine(const VisiblePosition& c) if (!startBox) return VisiblePosition(); - RenderObject *startRenderer = startBox->object(); + RenderObject *startRenderer = startBox->renderer(); if (!startRenderer) return VisiblePosition(); - startNode = startRenderer->element(); + startNode = startRenderer->node(); if (startNode) break; @@ -333,7 +399,7 @@ VisiblePosition startOfLine(const VisiblePosition& c) // greater than the input position. This fix is to account for the discrepancy between lines with webkit-line-break:after-white-space // style versus lines without that style, which would break before a space by default. Position p = visPos.deepEquivalent(); - if (p.offset() > c.deepEquivalent().offset() && p.node()->isSameNode(c.deepEquivalent().node())) { + if (p.m_offset > c.deepEquivalent().m_offset && p.node()->isSameNode(c.deepEquivalent().node())) { visPos = c.previous(); if (visPos.isNull()) return VisiblePosition(); @@ -354,7 +420,7 @@ static VisiblePosition endPositionForLine(const VisiblePosition& c) // There are VisiblePositions at offset 0 in blocks without // RootInlineBoxes, like empty editable blocks and bordered blocks. Position p = c.deepEquivalent(); - if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.offset() == 0) + if (p.node()->renderer() && p.node()->renderer()->isRenderBlock() && p.m_offset == 0) return c; return VisiblePosition(); } @@ -368,11 +434,11 @@ static VisiblePosition endPositionForLine(const VisiblePosition& c) if (!endBox) return VisiblePosition(); - RenderObject *endRenderer = endBox->object(); + RenderObject *endRenderer = endBox->renderer(); if (!endRenderer) return VisiblePosition(); - endNode = endRenderer->element(); + endNode = endRenderer->node(); if (endNode) break; @@ -498,7 +564,7 @@ VisiblePosition previousLinePosition(const VisiblePosition &visiblePosition, int FloatPoint absPos = containingBlock->localToAbsolute(FloatPoint()); if (containingBlock->hasOverflowClip()) absPos -= containingBlock->layer()->scrolledContentOffset(); - RenderObject *renderer = root->closestLeafChildForXPos(x - absPos.x(), isEditablePosition(p))->object(); + RenderObject* renderer = root->closestLeafChildForXPos(x - absPos.x(), isEditablePosition(p))->renderer(); Node* node = renderer->node(); if (editingIgnoresContent(node)) return Position(node->parent(), node->nodeIndex()); @@ -571,7 +637,7 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int x) // Need to move forward to next containing editable block in this root editable // block and find the first root line box in that block. Node* startBlock = enclosingBlock(node); - Node* n = nextLeafWithSameEditability(node, p.offset()); + Node* n = nextLeafWithSameEditability(node, p.m_offset); while (n && startBlock == enclosingBlock(n)) n = nextLeafWithSameEditability(n); while (n) { @@ -599,7 +665,7 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int x) FloatPoint absPos = containingBlock->localToAbsolute(FloatPoint()); if (containingBlock->hasOverflowClip()) absPos -= containingBlock->layer()->scrolledContentOffset(); - RenderObject *renderer = root->closestLeafChildForXPos(x - absPos.x(), isEditablePosition(p))->object(); + RenderObject* renderer = root->closestLeafChildForXPos(x - absPos.x(), isEditablePosition(p))->renderer(); Node* node = renderer->node(); if (editingIgnoresContent(node)) return Position(node->parent(), node->nodeIndex()); @@ -615,7 +681,7 @@ VisiblePosition nextLinePosition(const VisiblePosition &visiblePosition, int x) // --------- -static unsigned startSentenceBoundary(const UChar* characters, unsigned length) +static unsigned startSentenceBoundary(const UChar* characters, unsigned length, unsigned) { TextBreakIterator* iterator = sentenceBreakIterator(characters, length); // FIXME: The following function can return -1; we don't handle that. @@ -627,7 +693,7 @@ VisiblePosition startOfSentence(const VisiblePosition &c) return previousBoundary(c, startSentenceBoundary); } -static unsigned endSentenceBoundary(const UChar* characters, unsigned length) +static unsigned endSentenceBoundary(const UChar* characters, unsigned length, unsigned) { TextBreakIterator* iterator = sentenceBreakIterator(characters, length); return textBreakNext(iterator); @@ -639,7 +705,7 @@ VisiblePosition endOfSentence(const VisiblePosition &c) return nextBoundary(c, endSentenceBoundary); } -static unsigned previousSentencePositionBoundary(const UChar* characters, unsigned length) +static unsigned previousSentencePositionBoundary(const UChar* characters, unsigned length, unsigned) { // FIXME: This is identical to startSentenceBoundary. I'm pretty sure that's not right. TextBreakIterator* iterator = sentenceBreakIterator(characters, length); @@ -653,7 +719,7 @@ VisiblePosition previousSentencePosition(const VisiblePosition &c) return c.honorEditableBoundaryAtOrAfter(prev); } -static unsigned nextSentencePositionBoundary(const UChar* characters, unsigned length) +static unsigned nextSentencePositionBoundary(const UChar* characters, unsigned length, unsigned) { // FIXME: This is identical to endSentenceBoundary. This isn't right, it needs to // move to the equivlant position in the following sentence. @@ -667,8 +733,13 @@ VisiblePosition nextSentencePosition(const VisiblePosition &c) return c.honorEditableBoundaryAtOrBefore(next); } +static bool renderedAsNonInlineTableOrHR(RenderObject* renderer) +{ + return renderer && ((renderer->isTable() && !renderer->isInline()) || renderer->isHR()); +} + // FIXME: Broken for positions before/after images that aren't inline (5027702) -VisiblePosition startOfParagraph(const VisiblePosition &c) +VisiblePosition startOfParagraph(const VisiblePosition& c) { Position p = c.deepEquivalent(); Node *startNode = p.node(); @@ -676,16 +747,13 @@ VisiblePosition startOfParagraph(const VisiblePosition &c) if (!startNode) return VisiblePosition(); - if (startNode->renderer() - && ((startNode->renderer()->isTable() && !startNode->renderer()->isInline()) - || startNode->renderer()->isHR()) - && p.offset() == maxDeepOffset(startNode)) - return VisiblePosition(Position(startNode, 0)); + if (renderedAsNonInlineTableOrHR(startNode->renderer()) && p.atLastEditingPositionForNode()) + return firstDeepEditingPositionForNode(startNode); Node* startBlock = enclosingBlock(startNode); Node *node = startNode; - int offset = p.offset(); + int offset = p.m_offset; Node *n = startNode; while (n) { @@ -739,17 +807,14 @@ VisiblePosition endOfParagraph(const VisiblePosition &c) Position p = c.deepEquivalent(); Node* startNode = p.node(); - if (startNode->renderer() - && ((startNode->renderer()->isTable() && !startNode->renderer()->isInline()) - || startNode->renderer()->isHR()) - && p.offset() == 0) - return VisiblePosition(Position(startNode, maxDeepOffset(startNode))); + if (renderedAsNonInlineTableOrHR(startNode->renderer()) && p.atFirstEditingPositionForNode()) + return lastDeepEditingPositionForNode(startNode); Node* startBlock = enclosingBlock(startNode); Node *stayInsideBlock = startBlock; Node *node = startNode; - int offset = p.offset(); + int offset = p.m_offset; Node *n = startNode; while (n) { @@ -785,7 +850,7 @@ VisiblePosition endOfParagraph(const VisiblePosition &c) n = n->traverseNextNode(stayInsideBlock); } else if (editingIgnoresContent(n) || isTableElement(n)) { node = n; - offset = maxDeepOffset(n); + offset = lastOffsetForEditing(n); n = n->traverseNextSibling(stayInsideBlock); } else n = n->traverseNextNode(stayInsideBlock); @@ -820,25 +885,25 @@ bool isEndOfParagraph(const VisiblePosition &pos) return pos.isNotNull() && pos == endOfParagraph(pos); } -VisiblePosition previousParagraphPosition(const VisiblePosition &p, int x) +VisiblePosition previousParagraphPosition(const VisiblePosition& p, int x) { VisiblePosition pos = p; do { VisiblePosition n = previousLinePosition(pos, x); if (n.isNull() || n == pos) - return p; + break; pos = n; } while (inSameParagraph(p, pos)); return pos; } -VisiblePosition nextParagraphPosition(const VisiblePosition &p, int x) +VisiblePosition nextParagraphPosition(const VisiblePosition& p, int x) { VisiblePosition pos = p; do { VisiblePosition n = nextLinePosition(pos, x); if (n.isNull() || n == pos) - return p; + break; pos = n; } while (inSameParagraph(p, pos)); return pos; @@ -944,7 +1009,7 @@ VisiblePosition startOfEditableContent(const VisiblePosition& visiblePosition) if (!highestRoot) return VisiblePosition(); - return VisiblePosition(highestRoot, 0, DOWNSTREAM); + return firstDeepEditingPositionForNode(highestRoot); } VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition) @@ -953,7 +1018,7 @@ VisiblePosition endOfEditableContent(const VisiblePosition& visiblePosition) if (!highestRoot) return VisiblePosition(); - return VisiblePosition(highestRoot, maxDeepOffset(highestRoot), DOWNSTREAM); + return lastDeepEditingPositionForNode(highestRoot); } } |