/* * Copyright (C) 2004, 2008, 2009, 2010 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "SelectionController.h" #include "CharacterData.h" #include "DeleteSelectionCommand.h" #include "Document.h" #include "Editor.h" #include "EditorClient.h" #include "Element.h" #include "EventHandler.h" #include "ExceptionCode.h" #include "FloatQuad.h" #include "FocusController.h" #include "Frame.h" #include "FrameTree.h" #include "FrameView.h" #include "GraphicsContext.h" #include "HTMLFormElement.h" #include "HTMLFrameElementBase.h" #include "HTMLInputElement.h" #include "HTMLNames.h" #include "HitTestRequest.h" #include "HitTestResult.h" #include "Page.h" #include "Range.h" #include "RenderLayer.h" #include "RenderTextControl.h" #include "RenderTheme.h" #include "RenderView.h" #include "RenderWidget.h" #include "SecureTextInput.h" #include "Settings.h" #include "TextIterator.h" #include "TypingCommand.h" #include "htmlediting.h" #include "visible_units.h" #ifdef ANDROID_ALLOW_TURNING_OFF_CARET #include "WebViewCore.h" #endif #include <stdio.h> #include <wtf/text/CString.h> #define EDIT_DEBUG 0 namespace WebCore { using namespace HTMLNames; const int NoXPosForVerticalArrowNavigation = INT_MIN; SelectionController::SelectionController(Frame* frame, bool isDragCaretController) : m_frame(frame) , m_xPosForVerticalArrowNavigation(NoXPosForVerticalArrowNavigation) , m_granularity(CharacterGranularity) , m_caretBlinkTimer(this, &SelectionController::caretBlinkTimerFired) , m_caretRectNeedsUpdate(true) , m_absCaretBoundsDirty(true) , m_isDragCaretController(isDragCaretController) , m_isCaretBlinkingSuspended(false) , m_focused(frame && frame->page() && frame->page()->focusController()->focusedFrame() == frame) , m_caretVisible(isDragCaretController) , m_caretPaint(true) { setIsDirectional(false); } void SelectionController::moveTo(const VisiblePosition &pos, bool userTriggered, CursorAlignOnScroll align) { SetSelectionOptions options = CloseTyping | ClearTypingStyle; if (userTriggered) options |= UserTriggered; setSelection(VisibleSelection(pos.deepEquivalent(), pos.deepEquivalent(), pos.affinity()), options, align); } void SelectionController::moveTo(const VisiblePosition &base, const VisiblePosition &extent, bool userTriggered) { SetSelectionOptions options = CloseTyping | ClearTypingStyle; if (userTriggered) options |= UserTriggered; setSelection(VisibleSelection(base.deepEquivalent(), extent.deepEquivalent(), base.affinity()), options); } void SelectionController::moveTo(const Position &pos, EAffinity affinity, bool userTriggered) { SetSelectionOptions options = CloseTyping | ClearTypingStyle; if (userTriggered) options |= UserTriggered; setSelection(VisibleSelection(pos, affinity), options); } void SelectionController::moveTo(const Range *r, EAffinity affinity, bool userTriggered) { SetSelectionOptions options = CloseTyping | ClearTypingStyle; if (userTriggered) options |= UserTriggered; VisibleSelection selection = r ? VisibleSelection(r->startPosition(), r->endPosition(), affinity) : VisibleSelection(Position(), Position(), affinity); setSelection(selection, options); } void SelectionController::moveTo(const Position &base, const Position &extent, EAffinity affinity, bool userTriggered) { SetSelectionOptions options = CloseTyping | ClearTypingStyle; if (userTriggered) options |= UserTriggered; setSelection(VisibleSelection(base, extent, affinity), options); } void SelectionController::setSelection(const VisibleSelection& s, SetSelectionOptions options, CursorAlignOnScroll align, TextGranularity granularity, DirectionalityPolicy directionalityPolicy) { m_granularity = granularity; bool closeTyping = options & CloseTyping; bool shouldClearTypingStyle = options & ClearTypingStyle; bool userTriggered = options & UserTriggered; setIsDirectional(directionalityPolicy == MakeDirectionalSelection); if (m_isDragCaretController) { invalidateCaretRect(); m_selection = s; m_caretRectNeedsUpdate = true; invalidateCaretRect(); updateCaretRect(); return; } if (!m_frame) { m_selection = s; return; } // <http://bugs.webkit.org/show_bug.cgi?id=23464>: Infinite recursion at SelectionController::setSelection // if document->frame() == m_frame we can get into an infinite loop if (s.base().anchorNode()) { Document* document = s.base().anchorNode()->document(); if (document && document->frame() && document->frame() != m_frame && document != m_frame->document()) { document->frame()->selection()->setSelection(s, options); return; } } if (closeTyping) TypingCommand::closeTyping(m_frame->editor()->lastEditCommand()); if (shouldClearTypingStyle) clearTypingStyle(); if (m_selection == s) { // Even if selection was not changed, selection offsets may have been changed. notifyRendererOfSelectionChange(userTriggered); return; } VisibleSelection oldSelection = m_selection; m_selection = s; m_caretRectNeedsUpdate = true; if (!s.isNone()) setFocusedNodeIfNeeded(); updateAppearance(); // Always clear the x position used for vertical arrow navigation. // It will be restored by the vertical arrow navigation code if necessary. m_xPosForVerticalArrowNavigation = NoXPosForVerticalArrowNavigation; selectFrameElementInParentIfFullySelected(); notifyRendererOfSelectionChange(userTriggered); m_frame->editor()->respondToChangedSelection(oldSelection, options); if (userTriggered) { ScrollAlignment alignment; if (m_frame->editor()->behavior().shouldCenterAlignWhenSelectionIsRevealed()) alignment = (align == AlignCursorOnScrollAlways) ? ScrollAlignment::alignCenterAlways : ScrollAlignment::alignCenterIfNeeded; else alignment = (align == AlignCursorOnScrollAlways) ? ScrollAlignment::alignTopAlways : ScrollAlignment::alignToEdgeIfNeeded; revealSelection(alignment, true); } notifyAccessibilityForSelectionChange(); m_frame->document()->enqueueDocumentEvent(Event::create(eventNames().selectionchangeEvent, false, false)); } static bool removingNodeRemovesPosition(Node* node, const Position& position) { if (!position.anchorNode()) return false; if (position.anchorNode() == node) return true; if (!node->isElementNode()) return false; Element* element = static_cast<Element*>(node); return element->contains(position.anchorNode()) || element->contains(position.anchorNode()->shadowAncestorNode()); } void SelectionController::nodeWillBeRemoved(Node *node) { if (isNone()) return; // There can't be a selection inside a fragment, so if a fragment's node is being removed, // the selection in the document that created the fragment needs no adjustment. if (node && highestAncestor(node)->nodeType() == Node::DOCUMENT_FRAGMENT_NODE) return; respondToNodeModification(node, removingNodeRemovesPosition(node, m_selection.base()), removingNodeRemovesPosition(node, m_selection.extent()), removingNodeRemovesPosition(node, m_selection.start()), removingNodeRemovesPosition(node, m_selection.end())); } void SelectionController::respondToNodeModification(Node* node, bool baseRemoved, bool extentRemoved, bool startRemoved, bool endRemoved) { bool clearRenderTreeSelection = false; bool clearDOMTreeSelection = false; if (startRemoved || endRemoved) { // FIXME: When endpoints are removed, we should just alter the selection, instead of blowing it away. clearRenderTreeSelection = true; clearDOMTreeSelection = true; } else if (baseRemoved || extentRemoved) { // The base and/or extent are about to be removed, but the start and end aren't. // Change the base and extent to the start and end, but don't re-validate the // selection, since doing so could move the start and end into the node // that is about to be removed. if (m_selection.isBaseFirst()) m_selection.setWithoutValidation(m_selection.start(), m_selection.end()); else m_selection.setWithoutValidation(m_selection.end(), m_selection.start()); } else if (RefPtr<Range> range = m_selection.firstRange()) { ExceptionCode ec = 0; Range::CompareResults compareResult = range->compareNode(node, ec); if (!ec && (compareResult == Range::NODE_BEFORE_AND_AFTER || compareResult == Range::NODE_INSIDE)) { // If we did nothing here, when this node's renderer was destroyed, the rect that it // occupied would be invalidated, but, selection gaps that change as a result of // the removal wouldn't be invalidated. // FIXME: Don't do so much unnecessary invalidation. clearRenderTreeSelection = true; } } if (clearRenderTreeSelection) { RefPtr<Document> document = m_selection.start().anchorNode()->document(); document->updateStyleIfNeeded(); if (RenderView* view = toRenderView(document->renderer())) view->clearSelection(); } if (clearDOMTreeSelection) setSelection(VisibleSelection(), 0); } enum EndPointType { EndPointIsStart, EndPointIsEnd }; static bool shouldRemovePositionAfterAdoptingTextReplacement(Position& position, EndPointType type, CharacterData* node, unsigned offset, unsigned oldLength, unsigned newLength) { if (!position.anchorNode() || position.anchorNode() != node || position.anchorType() != Position::PositionIsOffsetInAnchor) return false; ASSERT(position.offsetInContainerNode() >= 0); unsigned positionOffset = static_cast<unsigned>(position.offsetInContainerNode()); if (positionOffset > offset && positionOffset < offset + oldLength) return true; // Adjust the offset if the position is after or at the end of the deleted contents (positionOffset >= offset + oldLength) // to avoid having a stale offset except when the position is the end of selection and nothing is deleted, in which case, // adjusting offset results in incorrectly extending the selection until the end of newly inserted contents. if ((positionOffset > offset + oldLength) || (positionOffset == offset + oldLength && (type == EndPointIsStart || oldLength))) position.moveToOffset(positionOffset - oldLength + newLength); return false; } void SelectionController::textWillBeReplaced(CharacterData* node, unsigned offset, unsigned oldLength, unsigned newLength) { // The fragment check is a performance optimization. See http://trac.webkit.org/changeset/30062. if (isNone() || !node || highestAncestor(node)->nodeType() == Node::DOCUMENT_FRAGMENT_NODE) return; Position base = m_selection.base(); Position extent = m_selection.extent(); Position start = m_selection.start(); Position end = m_selection.end(); bool shouldRemoveBase = shouldRemovePositionAfterAdoptingTextReplacement(base, m_selection.isBaseFirst() ? EndPointIsStart : EndPointIsEnd, node, offset, oldLength, newLength); bool shouldRemoveExtent = shouldRemovePositionAfterAdoptingTextReplacement(extent, m_selection.isBaseFirst() ? EndPointIsEnd : EndPointIsStart, node, offset, oldLength, newLength); bool shouldRemoveStart = shouldRemovePositionAfterAdoptingTextReplacement(start, EndPointIsStart, node, offset, oldLength, newLength); bool shouldRemoveEnd = shouldRemovePositionAfterAdoptingTextReplacement(end, EndPointIsEnd, node, offset, oldLength, newLength); if ((base != m_selection.base() || extent != m_selection.extent() || start != m_selection.start() || end != m_selection.end()) && !shouldRemoveStart && !shouldRemoveEnd) { VisibleSelection newSelection; if (!shouldRemoveBase && !shouldRemoveExtent) newSelection.setWithoutValidation(base, extent); else { if (newSelection.isBaseFirst()) newSelection.setWithoutValidation(start, end); else newSelection.setWithoutValidation(end, start); } m_frame->document()->updateLayout(); setSelection(newSelection, 0); return; } respondToNodeModification(node, shouldRemoveBase, shouldRemoveExtent, shouldRemoveStart, shouldRemoveEnd); } void SelectionController::setIsDirectional(bool isDirectional) { m_isDirectional = !m_frame || m_frame->editor()->behavior().shouldConsiderSelectionAsDirectional() || isDirectional; } TextDirection SelectionController::directionOfEnclosingBlock() { return WebCore::directionOfEnclosingBlock(m_selection.extent()); } void SelectionController::willBeModified(EAlteration alter, SelectionDirection direction) { if (alter != AlterationExtend) return; Position start = m_selection.start(); Position end = m_selection.end(); bool baseIsStart = true; if (m_isDirectional) { // Make base and extent match start and end so we extend the user-visible selection. // This only matters for cases where base and extend point to different positions than // start and end (e.g. after a double-click to select a word). if (m_selection.isBaseFirst()) baseIsStart = true; else baseIsStart = false; } else { switch (direction) { case DirectionRight: if (directionOfEnclosingBlock() == LTR) baseIsStart = true; else baseIsStart = false; break; case DirectionForward: baseIsStart = true; break; case DirectionLeft: if (directionOfEnclosingBlock() == LTR) baseIsStart = false; else baseIsStart = true; break; case DirectionBackward: baseIsStart = false; break; } } if (baseIsStart) { m_selection.setBase(start); m_selection.setExtent(end); } else { m_selection.setBase(end); m_selection.setExtent(start); } } VisiblePosition SelectionController::positionForPlatform(bool isGetStart) const { Settings* settings = m_frame ? m_frame->settings() : 0; if (settings && settings->editingBehaviorType() == EditingMacBehavior) return isGetStart ? m_selection.visibleStart() : m_selection.visibleEnd(); // Linux and Windows always extend selections from the extent endpoint. // FIXME: VisibleSelection should be fixed to ensure as an invariant that // base/extent always point to the same nodes as start/end, but which points // to which depends on the value of isBaseFirst. Then this can be changed // to just return m_sel.extent(). return m_selection.isBaseFirst() ? m_selection.visibleEnd() : m_selection.visibleStart(); } VisiblePosition SelectionController::startForPlatform() const { return positionForPlatform(true); } VisiblePosition SelectionController::endForPlatform() const { return positionForPlatform(false); } VisiblePosition SelectionController::modifyExtendingRight(TextGranularity granularity) { VisiblePosition pos(m_selection.extent(), m_selection.affinity()); // The difference between modifyExtendingRight and modifyExtendingForward is: // modifyExtendingForward always extends forward logically. // modifyExtendingRight behaves the same as modifyExtendingForward except for extending character or word, // it extends forward logically if the enclosing block is LTR direction, // but it extends backward logically if the enclosing block is RTL direction. switch (granularity) { case CharacterGranularity: if (directionOfEnclosingBlock() == LTR) pos = pos.next(CannotCrossEditingBoundary); else pos = pos.previous(CannotCrossEditingBoundary); break; case WordGranularity: if (directionOfEnclosingBlock() == LTR) pos = nextWordPosition(pos); else pos = previousWordPosition(pos); break; case LineBoundary: if (directionOfEnclosingBlock() == LTR) pos = modifyExtendingForward(granularity); else pos = modifyExtendingBackward(granularity); break; case SentenceGranularity: case LineGranularity: case ParagraphGranularity: case SentenceBoundary: case ParagraphBoundary: case DocumentBoundary: // FIXME: implement all of the above? pos = modifyExtendingForward(granularity); break; case WebKitVisualWordGranularity: break; } return pos; } VisiblePosition SelectionController::modifyExtendingForward(TextGranularity granularity) { VisiblePosition pos(m_selection.extent(), m_selection.affinity()); switch (granularity) { case CharacterGranularity: pos = pos.next(CannotCrossEditingBoundary); break; case WordGranularity: pos = nextWordPosition(pos); break; case SentenceGranularity: pos = nextSentencePosition(pos); break; case LineGranularity: pos = nextLinePosition(pos, xPosForVerticalArrowNavigation(EXTENT)); break; case ParagraphGranularity: pos = nextParagraphPosition(pos, xPosForVerticalArrowNavigation(EXTENT)); break; case SentenceBoundary: pos = endOfSentence(endForPlatform()); break; case LineBoundary: pos = logicalEndOfLine(endForPlatform()); break; case ParagraphBoundary: pos = endOfParagraph(endForPlatform()); break; case DocumentBoundary: pos = endForPlatform(); if (isEditablePosition(pos.deepEquivalent())) pos = endOfEditableContent(pos); else pos = endOfDocument(pos); break; case WebKitVisualWordGranularity: break; } return pos; } VisiblePosition SelectionController::modifyMovingRight(TextGranularity granularity) { VisiblePosition pos; switch (granularity) { case CharacterGranularity: if (isRange()) { if (directionOfEnclosingBlock() == LTR) pos = VisiblePosition(m_selection.end(), m_selection.affinity()); else pos = VisiblePosition(m_selection.start(), m_selection.affinity()); } else pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).right(true); break; case WordGranularity: case SentenceGranularity: case LineGranularity: case ParagraphGranularity: case SentenceBoundary: case ParagraphBoundary: case DocumentBoundary: // FIXME: Implement all of the above. pos = modifyMovingForward(granularity); break; case LineBoundary: pos = rightBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock()); break; case WebKitVisualWordGranularity: pos = rightWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); break; } return pos; } VisiblePosition SelectionController::modifyMovingForward(TextGranularity granularity) { VisiblePosition pos; // FIXME: Stay in editable content for the less common granularities. switch (granularity) { case CharacterGranularity: if (isRange()) pos = VisiblePosition(m_selection.end(), m_selection.affinity()); else pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).next(CannotCrossEditingBoundary); break; case WordGranularity: pos = nextWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); break; case SentenceGranularity: pos = nextSentencePosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); break; case LineGranularity: { // down-arrowing from a range selection that ends at the start of a line needs // to leave the selection at that line start (no need to call nextLinePosition!) pos = endForPlatform(); if (!isRange() || !isStartOfLine(pos)) pos = nextLinePosition(pos, xPosForVerticalArrowNavigation(START)); break; } case ParagraphGranularity: pos = nextParagraphPosition(endForPlatform(), xPosForVerticalArrowNavigation(START)); break; case SentenceBoundary: pos = endOfSentence(endForPlatform()); break; case LineBoundary: pos = logicalEndOfLine(endForPlatform()); break; case ParagraphBoundary: pos = endOfParagraph(endForPlatform()); break; case DocumentBoundary: pos = endForPlatform(); if (isEditablePosition(pos.deepEquivalent())) pos = endOfEditableContent(pos); else pos = endOfDocument(pos); break; case WebKitVisualWordGranularity: break; } return pos; } VisiblePosition SelectionController::modifyExtendingLeft(TextGranularity granularity) { VisiblePosition pos(m_selection.extent(), m_selection.affinity()); // The difference between modifyExtendingLeft and modifyExtendingBackward is: // modifyExtendingBackward always extends backward logically. // modifyExtendingLeft behaves the same as modifyExtendingBackward except for extending character or word, // it extends backward logically if the enclosing block is LTR direction, // but it extends forward logically if the enclosing block is RTL direction. switch (granularity) { case CharacterGranularity: if (directionOfEnclosingBlock() == LTR) pos = pos.previous(CannotCrossEditingBoundary); else pos = pos.next(CannotCrossEditingBoundary); break; case WordGranularity: if (directionOfEnclosingBlock() == LTR) pos = previousWordPosition(pos); else pos = nextWordPosition(pos); break; case LineBoundary: if (directionOfEnclosingBlock() == LTR) pos = modifyExtendingBackward(granularity); else pos = modifyExtendingForward(granularity); break; case SentenceGranularity: case LineGranularity: case ParagraphGranularity: case SentenceBoundary: case ParagraphBoundary: case DocumentBoundary: pos = modifyExtendingBackward(granularity); break; case WebKitVisualWordGranularity: break; } return pos; } VisiblePosition SelectionController::modifyExtendingBackward(TextGranularity granularity) { VisiblePosition pos(m_selection.extent(), m_selection.affinity()); // Extending a selection backward by word or character from just after a table selects // the table. This "makes sense" from the user perspective, esp. when deleting. // It was done here instead of in VisiblePosition because we want VPs to iterate // over everything. switch (granularity) { case CharacterGranularity: pos = pos.previous(CannotCrossEditingBoundary); break; case WordGranularity: pos = previousWordPosition(pos); break; case SentenceGranularity: pos = previousSentencePosition(pos); break; case LineGranularity: pos = previousLinePosition(pos, xPosForVerticalArrowNavigation(EXTENT)); break; case ParagraphGranularity: pos = previousParagraphPosition(pos, xPosForVerticalArrowNavigation(EXTENT)); break; case SentenceBoundary: pos = startOfSentence(startForPlatform()); break; case LineBoundary: pos = logicalStartOfLine(startForPlatform()); break; case ParagraphBoundary: pos = startOfParagraph(startForPlatform()); break; case DocumentBoundary: pos = startForPlatform(); if (isEditablePosition(pos.deepEquivalent())) pos = startOfEditableContent(pos); else pos = startOfDocument(pos); break; case WebKitVisualWordGranularity: break; } return pos; } VisiblePosition SelectionController::modifyMovingLeft(TextGranularity granularity) { VisiblePosition pos; switch (granularity) { case CharacterGranularity: if (isRange()) if (directionOfEnclosingBlock() == LTR) pos = VisiblePosition(m_selection.start(), m_selection.affinity()); else pos = VisiblePosition(m_selection.end(), m_selection.affinity()); else pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).left(true); break; case WordGranularity: case SentenceGranularity: case LineGranularity: case ParagraphGranularity: case SentenceBoundary: case ParagraphBoundary: case DocumentBoundary: // FIXME: Implement all of the above. pos = modifyMovingBackward(granularity); break; case LineBoundary: pos = leftBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock()); break; case WebKitVisualWordGranularity: pos = leftWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); break; } return pos; } VisiblePosition SelectionController::modifyMovingBackward(TextGranularity granularity) { VisiblePosition pos; switch (granularity) { case CharacterGranularity: if (isRange()) pos = VisiblePosition(m_selection.start(), m_selection.affinity()); else pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).previous(CannotCrossEditingBoundary); break; case WordGranularity: pos = previousWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); break; case SentenceGranularity: pos = previousSentencePosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); break; case LineGranularity: pos = previousLinePosition(startForPlatform(), xPosForVerticalArrowNavigation(START)); break; case ParagraphGranularity: pos = previousParagraphPosition(startForPlatform(), xPosForVerticalArrowNavigation(START)); break; case SentenceBoundary: pos = startOfSentence(startForPlatform()); break; case LineBoundary: pos = logicalStartOfLine(startForPlatform()); break; case ParagraphBoundary: pos = startOfParagraph(startForPlatform()); break; case DocumentBoundary: pos = startForPlatform(); if (isEditablePosition(pos.deepEquivalent())) pos = startOfEditableContent(pos); else pos = startOfDocument(pos); break; case WebKitVisualWordGranularity: break; } return pos; } static bool isBoundary(TextGranularity granularity) { return granularity == LineBoundary || granularity == ParagraphBoundary || granularity == DocumentBoundary; } bool SelectionController::modify(EAlteration alter, SelectionDirection direction, TextGranularity granularity, bool userTriggered) { if (userTriggered) { SelectionController trialSelectionController; trialSelectionController.setSelection(m_selection); trialSelectionController.setIsDirectional(m_isDirectional); trialSelectionController.modify(alter, direction, granularity, false); bool change = shouldChangeSelection(trialSelectionController.selection()); if (!change) return false; } willBeModified(alter, direction); bool wasRange = m_selection.isRange(); Position originalStartPosition = m_selection.start(); VisiblePosition position; switch (direction) { case DirectionRight: if (alter == AlterationMove) position = modifyMovingRight(granularity); else position = modifyExtendingRight(granularity); break; case DirectionForward: if (alter == AlterationExtend) position = modifyExtendingForward(granularity); else position = modifyMovingForward(granularity); break; case DirectionLeft: if (alter == AlterationMove) position = modifyMovingLeft(granularity); else position = modifyExtendingLeft(granularity); break; case DirectionBackward: if (alter == AlterationExtend) position = modifyExtendingBackward(granularity); else position = modifyMovingBackward(granularity); break; } if (position.isNull()) return false; if (isSpatialNavigationEnabled(m_frame)) if (!wasRange && alter == AlterationMove && position == originalStartPosition) return false; // Some of the above operations set an xPosForVerticalArrowNavigation. // Setting a selection will clear it, so save it to possibly restore later. // Note: the START position type is arbitrary because it is unused, it would be // the requested position type if there were no xPosForVerticalArrowNavigation set. int x = xPosForVerticalArrowNavigation(START); switch (alter) { case AlterationMove: moveTo(position, userTriggered); break; case AlterationExtend: // Standard Mac behavior when extending to a boundary is grow the selection rather than leaving the // base in place and moving the extent. Matches NSTextView. if (!m_frame || !m_frame->editor()->behavior().shouldAlwaysGrowSelectionWhenExtendingToBoundary() || m_selection.isCaret() || !isBoundary(granularity)) setExtent(position, userTriggered); else { TextDirection textDirection = directionOfEnclosingBlock(); if (direction == DirectionForward || (textDirection == LTR && direction == DirectionRight) || (textDirection == RTL && direction == DirectionLeft)) setEnd(position, userTriggered); else setStart(position, userTriggered); } break; } if (granularity == LineGranularity || granularity == ParagraphGranularity) m_xPosForVerticalArrowNavigation = x; if (userTriggered) m_granularity = CharacterGranularity; setCaretRectNeedsUpdate(); setIsDirectional(alter == AlterationExtend); return true; } // FIXME: Maybe baseline would be better? static bool absoluteCaretY(const VisiblePosition &c, int &y) { IntRect rect = c.absoluteCaretBounds(); if (rect.isEmpty()) return false; y = rect.y() + rect.height() / 2; return true; } bool SelectionController::modify(EAlteration alter, int verticalDistance, bool userTriggered, CursorAlignOnScroll align) { if (!verticalDistance) return false; if (userTriggered) { SelectionController trialSelectionController; trialSelectionController.setSelection(m_selection); trialSelectionController.setIsDirectional(m_isDirectional); trialSelectionController.modify(alter, verticalDistance, false); bool change = shouldChangeSelection(trialSelectionController.selection()); if (!change) return false; } bool up = verticalDistance < 0; if (up) verticalDistance = -verticalDistance; willBeModified(alter, up ? DirectionBackward : DirectionForward); VisiblePosition pos; int xPos = 0; switch (alter) { case AlterationMove: pos = VisiblePosition(up ? m_selection.start() : m_selection.end(), m_selection.affinity()); xPos = xPosForVerticalArrowNavigation(up ? START : END); m_selection.setAffinity(up ? UPSTREAM : DOWNSTREAM); break; case AlterationExtend: pos = VisiblePosition(m_selection.extent(), m_selection.affinity()); xPos = xPosForVerticalArrowNavigation(EXTENT); m_selection.setAffinity(DOWNSTREAM); break; } int startY; if (!absoluteCaretY(pos, startY)) return false; if (up) startY = -startY; int lastY = startY; VisiblePosition result; VisiblePosition next; for (VisiblePosition p = pos; ; p = next) { next = (up ? previousLinePosition : nextLinePosition)(p, xPos); if (next.isNull() || next == p) break; int nextY; if (!absoluteCaretY(next, nextY)) break; if (up) nextY = -nextY; if (nextY - startY > verticalDistance) break; if (nextY >= lastY) { lastY = nextY; result = next; } } if (result.isNull()) return false; switch (alter) { case AlterationMove: moveTo(result, userTriggered, align); break; case AlterationExtend: setExtent(result, userTriggered); break; } if (userTriggered) m_granularity = CharacterGranularity; setIsDirectional(alter == AlterationExtend); return true; } int SelectionController::xPosForVerticalArrowNavigation(EPositionType type) { int x = 0; if (isNone()) return x; Position pos; switch (type) { case START: pos = m_selection.start(); break; case END: pos = m_selection.end(); break; case BASE: pos = m_selection.base(); break; case EXTENT: pos = m_selection.extent(); break; } Frame* frame = pos.anchorNode()->document()->frame(); if (!frame) return x; if (m_xPosForVerticalArrowNavigation == NoXPosForVerticalArrowNavigation) { VisiblePosition visiblePosition(pos, m_selection.affinity()); // VisiblePosition creation can fail here if a node containing the selection becomes visibility:hidden // after the selection is created and before this function is called. x = visiblePosition.isNotNull() ? visiblePosition.xOffsetForVerticalNavigation() : 0; m_xPosForVerticalArrowNavigation = x; } else x = m_xPosForVerticalArrowNavigation; return x; } void SelectionController::clear() { m_granularity = CharacterGranularity; setSelection(VisibleSelection()); } void SelectionController::setStart(const VisiblePosition &pos, bool userTriggered) { if (m_selection.isBaseFirst()) setBase(pos, userTriggered); else setExtent(pos, userTriggered); } void SelectionController::setEnd(const VisiblePosition &pos, bool userTriggered) { if (m_selection.isBaseFirst()) setExtent(pos, userTriggered); else setBase(pos, userTriggered); } void SelectionController::setBase(const VisiblePosition &pos, bool userTriggered) { SetSelectionOptions options = CloseTyping | ClearTypingStyle; if (userTriggered) options |= UserTriggered; setSelection(VisibleSelection(pos.deepEquivalent(), m_selection.extent(), pos.affinity()), options); } void SelectionController::setExtent(const VisiblePosition &pos, bool userTriggered) { SetSelectionOptions options = CloseTyping | ClearTypingStyle; if (userTriggered) options |= UserTriggered; setSelection(VisibleSelection(m_selection.base(), pos.deepEquivalent(), pos.affinity()), options); } void SelectionController::setBase(const Position &pos, EAffinity affinity, bool userTriggered) { SetSelectionOptions options = CloseTyping | ClearTypingStyle; if (userTriggered) options |= UserTriggered; setSelection(VisibleSelection(pos, m_selection.extent(), affinity), options); } void SelectionController::setExtent(const Position &pos, EAffinity affinity, bool userTriggered) { SetSelectionOptions options = CloseTyping | ClearTypingStyle; if (userTriggered) options |= UserTriggered; setSelection(VisibleSelection(m_selection.base(), pos, affinity), options); } void SelectionController::setCaretRectNeedsUpdate(bool flag) { m_caretRectNeedsUpdate = flag; } void SelectionController::updateCaretRect() { if (isNone() || !m_selection.start().anchorNode()->inDocument() || !m_selection.end().anchorNode()->inDocument()) { m_caretRect = IntRect(); return; } m_selection.start().anchorNode()->document()->updateStyleIfNeeded(); m_caretRect = IntRect(); if (isCaret()) { VisiblePosition pos(m_selection.start(), m_selection.affinity()); if (pos.isNotNull()) { ASSERT(pos.deepEquivalent().deprecatedNode()->renderer()); // First compute a rect local to the renderer at the selection start RenderObject* renderer; IntRect localRect = pos.localCaretRect(renderer); // Get the renderer that will be responsible for painting the caret (which // is either the renderer we just found, or one of its containers) RenderObject* caretPainter = caretRenderer(); // Compute an offset between the renderer and the caretPainter bool unrooted = false; while (renderer != caretPainter) { RenderObject* containerObject = renderer->container(); if (!containerObject) { unrooted = true; break; } localRect.move(renderer->offsetFromContainer(containerObject, localRect.location())); renderer = containerObject; } if (!unrooted) m_caretRect = localRect; m_absCaretBoundsDirty = true; } } m_caretRectNeedsUpdate = false; } RenderObject* SelectionController::caretRenderer() const { Node* node = m_selection.start().deprecatedNode(); if (!node) return 0; RenderObject* renderer = node->renderer(); if (!renderer) return 0; // if caretNode is a block and caret is inside it then caret should be painted by that block bool paintedByBlock = renderer->isBlockFlow() && caretRendersInsideNode(node); return paintedByBlock ? renderer : renderer->containingBlock(); } IntRect SelectionController::localCaretRect() { if (m_caretRectNeedsUpdate) updateCaretRect(); return m_caretRect; } IntRect SelectionController::absoluteBoundsForLocalRect(const IntRect& rect) const { RenderObject* caretPainter = caretRenderer(); if (!caretPainter) return IntRect(); IntRect localRect(rect); if (caretPainter->isBox()) toRenderBox(caretPainter)->flipForWritingMode(localRect); return caretPainter->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox(); } IntRect SelectionController::absoluteCaretBounds() { recomputeCaretRect(); return m_absCaretBounds; } static IntRect repaintRectForCaret(IntRect caret) { if (caret.isEmpty()) return IntRect(); // Ensure that the dirty rect intersects the block that paints the caret even in the case where // the caret itself is just outside the block. See <https://bugs.webkit.org/show_bug.cgi?id=19086>. caret.inflateX(1); return caret; } IntRect SelectionController::caretRepaintRect() const { return absoluteBoundsForLocalRect(repaintRectForCaret(localCaretRectForPainting())); } bool SelectionController::recomputeCaretRect() { if (!m_caretRectNeedsUpdate) return false; if (!m_frame) return false; FrameView* v = m_frame->document()->view(); if (!v) return false; IntRect oldRect = m_caretRect; IntRect newRect = localCaretRect(); if (oldRect == newRect && !m_absCaretBoundsDirty) return false; IntRect oldAbsCaretBounds = m_absCaretBounds; // FIXME: Rename m_caretRect to m_localCaretRect. m_absCaretBounds = absoluteBoundsForLocalRect(m_caretRect); m_absCaretBoundsDirty = false; if (oldAbsCaretBounds == m_absCaretBounds) return false; IntRect oldAbsoluteCaretRepaintBounds = m_absoluteCaretRepaintBounds; // We believe that we need to inflate the local rect before transforming it to obtain the repaint bounds. m_absoluteCaretRepaintBounds = caretRepaintRect(); #if ENABLE(TEXT_CARET) if (RenderView* view = toRenderView(m_frame->document()->renderer())) { // FIXME: make caret repainting container-aware. view->repaintRectangleInViewAndCompositedLayers(oldAbsoluteCaretRepaintBounds, false); if (shouldRepaintCaret(view)) view->repaintRectangleInViewAndCompositedLayers(m_absoluteCaretRepaintBounds, false); } #endif return true; } bool SelectionController::shouldRepaintCaret(const RenderView* view) const { ASSERT(view); Frame* frame = view->frameView() ? view->frameView()->frame() : 0; // The frame where the selection started. bool caretBrowsing = frame && frame->settings() && frame->settings()->caretBrowsingEnabled(); return (caretBrowsing || isContentEditable()); } void SelectionController::invalidateCaretRect() { if (!isCaret()) return; Document* d = m_selection.start().anchorNode()->document(); // recomputeCaretRect will always return false for the drag caret, // because its m_frame is always 0. bool caretRectChanged = recomputeCaretRect(); // EDIT FIXME: This is an unfortunate hack. // Basically, we can't trust this layout position since we // can't guarantee that the check to see if we are in unrendered // content will work at this point. We may have to wait for // a layout and re-render of the document to happen. So, resetting this // flag will cause another caret layout to happen the first time // that we try to paint the caret after this call. That one will work since // it happens after the document has accounted for any editing // changes which may have been done. // And, we need to leave this layout here so the caret moves right // away after clicking. m_caretRectNeedsUpdate = true; if (!caretRectChanged) { RenderView* view = toRenderView(d->renderer()); if (view && shouldRepaintCaret(view)) view->repaintRectangleInViewAndCompositedLayers(caretRepaintRect(), false); } } void SelectionController::paintCaret(GraphicsContext* context, int tx, int ty, const IntRect& clipRect) { #ifdef ANDROID_ALLOW_TURNING_OFF_CARET if (m_frame && !android::WebViewCore::getWebViewCore(m_frame->view())->shouldPaintCaret()) return; #endif #if ENABLE(TEXT_CARET) if (!m_caretVisible) return; if (!m_caretPaint) return; if (!m_selection.isCaret()) return; IntRect drawingRect = localCaretRectForPainting(); if (caretRenderer() && caretRenderer()->isBox()) toRenderBox(caretRenderer())->flipForWritingMode(drawingRect); drawingRect.move(tx, ty); IntRect caret = intersection(drawingRect, clipRect); if (caret.isEmpty()) return; Color caretColor = Color::black; ColorSpace colorSpace = ColorSpaceDeviceRGB; Element* element = rootEditableElement(); if (element && element->renderer()) { caretColor = element->renderer()->style()->visitedDependentColor(CSSPropertyColor); colorSpace = element->renderer()->style()->colorSpace(); } context->fillRect(caret, caretColor, colorSpace); #else UNUSED_PARAM(context); UNUSED_PARAM(tx); UNUSED_PARAM(ty); UNUSED_PARAM(clipRect); #endif } void SelectionController::debugRenderer(RenderObject *r, bool selected) const { if (r->node()->isElementNode()) { Element* element = static_cast<Element *>(r->node()); fprintf(stderr, "%s%s\n", selected ? "==> " : " ", element->localName().string().utf8().data()); } else if (r->isText()) { RenderText* textRenderer = toRenderText(r); if (!textRenderer->textLength() || !textRenderer->firstTextBox()) { fprintf(stderr, "%s#text (empty)\n", selected ? "==> " : " "); return; } static const int max = 36; String text = textRenderer->text(); int textLength = text.length(); if (selected) { int offset = 0; if (r->node() == m_selection.start().containerNode()) offset = m_selection.start().computeOffsetInContainerNode(); else if (r->node() == m_selection.end().containerNode()) offset = m_selection.end().computeOffsetInContainerNode(); int pos; InlineTextBox* box = textRenderer->findNextInlineTextBox(offset, pos); text = text.substring(box->start(), box->len()); String show; int mid = max / 2; int caret = 0; // text is shorter than max if (textLength < max) { show = text; caret = pos; } else if (pos - mid < 0) { // too few characters to left show = text.left(max - 3) + "..."; caret = pos; } else if (pos - mid >= 0 && pos + mid <= textLength) { // enough characters on each side show = "..." + text.substring(pos - mid + 3, max - 6) + "..."; caret = mid; } else { // too few characters on right show = "..." + text.right(max - 3); caret = pos - (textLength - show.length()); } show.replace('\n', ' '); show.replace('\r', ' '); fprintf(stderr, "==> #text : \"%s\" at offset %d\n", show.utf8().data(), pos); fprintf(stderr, " "); for (int i = 0; i < caret; i++) fprintf(stderr, " "); fprintf(stderr, "^\n"); } else { if ((int)text.length() > max) text = text.left(max - 3) + "..."; else text = text.left(max); fprintf(stderr, " #text : \"%s\"\n", text.utf8().data()); } } } bool SelectionController::contains(const IntPoint& point) { Document* document = m_frame->document(); // Treat a collapsed selection like no selection. if (!isRange()) return false; if (!document->renderer()) return false; HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active); HitTestResult result(point); document->renderView()->layer()->hitTest(request, result); Node* innerNode = result.innerNode(); if (!innerNode || !innerNode->renderer()) return false; VisiblePosition visiblePos(innerNode->renderer()->positionForPoint(result.localPoint())); if (visiblePos.isNull()) return false; if (m_selection.visibleStart().isNull() || m_selection.visibleEnd().isNull()) return false; Position start(m_selection.visibleStart().deepEquivalent()); Position end(m_selection.visibleEnd().deepEquivalent()); Position p(visiblePos.deepEquivalent()); return comparePositions(start, p) <= 0 && comparePositions(p, end) <= 0; } // Workaround for the fact that it's hard to delete a frame. // Call this after doing user-triggered selections to make it easy to delete the frame you entirely selected. // Can't do this implicitly as part of every setSelection call because in some contexts it might not be good // for the focus to move to another frame. So instead we call it from places where we are selecting with the // mouse or the keyboard after setting the selection. void SelectionController::selectFrameElementInParentIfFullySelected() { // Find the parent frame; if there is none, then we have nothing to do. Frame* parent = m_frame->tree()->parent(); if (!parent) return; Page* page = m_frame->page(); if (!page) return; // Check if the selection contains the entire frame contents; if not, then there is nothing to do. if (!isRange()) return; if (!isStartOfDocument(selection().visibleStart())) return; if (!isEndOfDocument(selection().visibleEnd())) return; // Get to the <iframe> or <frame> (or even <object>) element in the parent frame. Element* ownerElement = m_frame->ownerElement(); if (!ownerElement) return; ContainerNode* ownerElementParent = ownerElement->parentNode(); if (!ownerElementParent) return; // This method's purpose is it to make it easier to select iframes (in order to delete them). Don't do anything if the iframe isn't deletable. if (!ownerElementParent->rendererIsEditable()) return; // Create compute positions before and after the element. unsigned ownerElementNodeIndex = ownerElement->nodeIndex(); VisiblePosition beforeOwnerElement(VisiblePosition(Position(ownerElementParent, ownerElementNodeIndex, Position::PositionIsOffsetInAnchor))); VisiblePosition afterOwnerElement(VisiblePosition(Position(ownerElementParent, ownerElementNodeIndex + 1, Position::PositionIsOffsetInAnchor), VP_UPSTREAM_IF_POSSIBLE)); // Focus on the parent frame, and then select from before this element to after. VisibleSelection newSelection(beforeOwnerElement, afterOwnerElement); if (parent->selection()->shouldChangeSelection(newSelection)) { page->focusController()->setFocusedFrame(parent); parent->selection()->setSelection(newSelection); } } void SelectionController::selectAll() { Document* document = m_frame->document(); if (document->focusedNode() && document->focusedNode()->canSelectAll()) { document->focusedNode()->selectAll(); return; } Node* root = 0; if (isContentEditable()) root = highestEditableRoot(m_selection.start()); else { root = shadowTreeRootNode(); if (!root) root = document->documentElement(); } if (!root) return; VisibleSelection newSelection(VisibleSelection::selectionFromContentsOfNode(root)); if (shouldChangeSelection(newSelection)) setSelection(newSelection); selectFrameElementInParentIfFullySelected(); notifyRendererOfSelectionChange(true); } bool SelectionController::setSelectedRange(Range* range, EAffinity affinity, bool closeTyping) { if (!range) return false; ExceptionCode ec = 0; Node* startContainer = range->startContainer(ec); if (ec) return false; Node* endContainer = range->endContainer(ec); if (ec) return false; ASSERT(startContainer); ASSERT(endContainer); ASSERT(startContainer->document() == endContainer->document()); m_frame->document()->updateLayoutIgnorePendingStylesheets(); // Non-collapsed ranges are not allowed to start at the end of a line that is wrapped, // they start at the beginning of the next line instead bool collapsed = range->collapsed(ec); if (ec) return false; int startOffset = range->startOffset(ec); if (ec) return false; int endOffset = range->endOffset(ec); if (ec) return false; // FIXME: Can we provide extentAffinity? VisiblePosition visibleStart(Position(startContainer, startOffset, Position::PositionIsOffsetInAnchor), collapsed ? affinity : DOWNSTREAM); VisiblePosition visibleEnd(Position(endContainer, endOffset, Position::PositionIsOffsetInAnchor), SEL_DEFAULT_AFFINITY); SetSelectionOptions options = ClearTypingStyle; if (closeTyping) options |= CloseTyping; setSelection(VisibleSelection(visibleStart, visibleEnd), options); return true; } bool SelectionController::isInPasswordField() const { ASSERT(start().isNull() || start().anchorType() == Position::PositionIsOffsetInAnchor || start().containerNode() || !start().anchorNode()->shadowAncestorNode()); Node* startNode = start().containerNode(); if (!startNode) return false; startNode = startNode->shadowAncestorNode(); if (!startNode) return false; if (!startNode->hasTagName(inputTag)) return false; return static_cast<HTMLInputElement*>(startNode)->isPasswordField(); } bool SelectionController::caretRendersInsideNode(Node* node) const { if (!node) return false; return !isTableElement(node) && !editingIgnoresContent(node); } void SelectionController::focusedOrActiveStateChanged() { bool activeAndFocused = isFocusedAndActive(); // Because RenderObject::selectionBackgroundColor() and // RenderObject::selectionForegroundColor() check if the frame is active, // we have to update places those colors were painted. if (RenderView* view = toRenderView(m_frame->document()->renderer())) view->repaintRectangleInViewAndCompositedLayers(enclosingIntRect(bounds())); // Caret appears in the active frame. if (activeAndFocused) setSelectionFromNone(); setCaretVisible(activeAndFocused); // Update for caps lock state m_frame->eventHandler()->capsLockStateMayHaveChanged(); // Because CSSStyleSelector::checkOneSelector() and // RenderTheme::isFocused() check if the frame is active, we have to // update style and theme state that depended on those. if (Node* node = m_frame->document()->focusedNode()) { node->setNeedsStyleRecalc(); if (RenderObject* renderer = node->renderer()) if (renderer && renderer->style()->hasAppearance()) renderer->theme()->stateChanged(renderer, FocusState); } // Secure keyboard entry is set by the active frame. if (m_frame->document()->useSecureKeyboardEntryWhenActive()) setUseSecureKeyboardEntry(activeAndFocused); } void SelectionController::pageActivationChanged() { focusedOrActiveStateChanged(); } void SelectionController::updateSecureKeyboardEntryIfActive() { if (m_frame->document() && isFocusedAndActive()) setUseSecureKeyboardEntry(m_frame->document()->useSecureKeyboardEntryWhenActive()); } void SelectionController::setUseSecureKeyboardEntry(bool enable) { if (enable) enableSecureTextInput(); else disableSecureTextInput(); } void SelectionController::setFocused(bool flag) { if (m_focused == flag) return; m_focused = flag; focusedOrActiveStateChanged(); } bool SelectionController::isFocusedAndActive() const { return m_focused && m_frame->page() && m_frame->page()->focusController()->isActive(); } void SelectionController::updateAppearance() { ASSERT(!m_isDragCaretController); #if ENABLE(TEXT_CARET) bool caretRectChanged = recomputeCaretRect(); bool caretBrowsing = m_frame->settings() && m_frame->settings()->caretBrowsingEnabled(); bool shouldBlink = m_caretVisible && isCaret() && (isContentEditable() || caretBrowsing); // If the caret moved, stop the blink timer so we can restart with a // black caret in the new location. if (caretRectChanged || !shouldBlink) m_caretBlinkTimer.stop(); // Start blinking with a black caret. Be sure not to restart if we're // already blinking in the right location. if (shouldBlink && !m_caretBlinkTimer.isActive()) { if (double blinkInterval = m_frame->page()->theme()->caretBlinkInterval()) m_caretBlinkTimer.startRepeating(blinkInterval); if (!m_caretPaint) { m_caretPaint = true; invalidateCaretRect(); } } #endif // We need to update style in case the node containing the selection is made display:none. m_frame->document()->updateStyleIfNeeded(); RenderView* view = m_frame->contentRenderer(); if (!view) return; VisibleSelection selection = this->selection(); if (!selection.isRange()) { view->clearSelection(); return; } // Use the rightmost candidate for the start of the selection, and the leftmost candidate for the end of the selection. // Example: foo <a>bar</a>. Imagine that a line wrap occurs after 'foo', and that 'bar' is selected. If we pass [foo, 3] // as the start of the selection, the selection painting code will think that content on the line containing 'foo' is selected // and will fill the gap before 'bar'. Position startPos = selection.start(); Position candidate = startPos.downstream(); if (candidate.isCandidate()) startPos = candidate; Position endPos = selection.end(); candidate = endPos.upstream(); if (candidate.isCandidate()) endPos = candidate; // We can get into a state where the selection endpoints map to the same VisiblePosition when a selection is deleted // because we don't yet notify the SelectionController of text removal. if (startPos.isNotNull() && endPos.isNotNull() && selection.visibleStart() != selection.visibleEnd()) { RenderObject* startRenderer = startPos.deprecatedNode()->renderer(); RenderObject* endRenderer = endPos.deprecatedNode()->renderer(); view->setSelection(startRenderer, startPos.deprecatedEditingOffset(), endRenderer, endPos.deprecatedEditingOffset()); } } void SelectionController::setCaretVisible(bool flag) { if (m_caretVisible == flag) return; clearCaretRectIfNeeded(); m_caretVisible = flag; updateAppearance(); } void SelectionController::clearCaretRectIfNeeded() { #if ENABLE(TEXT_CARET) if (!m_caretPaint) return; m_caretPaint = false; invalidateCaretRect(); #endif } void SelectionController::caretBlinkTimerFired(Timer<SelectionController>*) { #if ENABLE(TEXT_CARET) ASSERT(m_caretVisible); ASSERT(isCaret()); bool caretPaint = m_caretPaint; if (isCaretBlinkingSuspended() && caretPaint) return; m_caretPaint = !caretPaint; invalidateCaretRect(); #endif } void SelectionController::notifyRendererOfSelectionChange(bool userTriggered) { m_frame->document()->updateStyleIfNeeded(); if (!rootEditableElement()) return; RenderObject* renderer = rootEditableElement()->shadowAncestorNode()->renderer(); if (!renderer || !renderer->isTextControl()) return; toRenderTextControl(renderer)->selectionChanged(userTriggered); } // Helper function that tells whether a particular node is an element that has an entire // Frame and FrameView, a <frame>, <iframe>, or <object>. static bool isFrameElement(const Node* n) { if (!n) return false; RenderObject* renderer = n->renderer(); if (!renderer || !renderer->isWidget()) return false; Widget* widget = toRenderWidget(renderer)->widget(); return widget && widget->isFrameView(); } void SelectionController::setFocusedNodeIfNeeded() { if (isNone() || !isFocused()) return; bool caretBrowsing = m_frame->settings() && m_frame->settings()->caretBrowsingEnabled(); if (caretBrowsing) { if (Node* anchor = enclosingAnchorElement(base())) { m_frame->page()->focusController()->setFocusedNode(anchor, m_frame); return; } } if (Node* target = rootEditableElement()) { // Walk up the DOM tree to search for a node to focus. while (target) { // We don't want to set focus on a subframe when selecting in a parent frame, // so add the !isFrameElement check here. There's probably a better way to make this // work in the long term, but this is the safest fix at this time. if (target && target->isMouseFocusable() && !isFrameElement(target)) { m_frame->page()->focusController()->setFocusedNode(target, m_frame); return; } target = target->parentOrHostNode(); } m_frame->document()->setFocusedNode(0); } if (caretBrowsing) m_frame->page()->focusController()->setFocusedNode(0, m_frame); } void SelectionController::paintDragCaret(GraphicsContext* p, int tx, int ty, const IntRect& clipRect) const { #if ENABLE(TEXT_CARET) SelectionController* dragCaretController = m_frame->page()->dragCaretController(); ASSERT(dragCaretController->selection().isCaret()); if (dragCaretController->selection().start().anchorNode()->document()->frame() == m_frame) dragCaretController->paintCaret(p, tx, ty, clipRect); #else UNUSED_PARAM(p); UNUSED_PARAM(tx); UNUSED_PARAM(ty); UNUSED_PARAM(clipRect); #endif } PassRefPtr<CSSMutableStyleDeclaration> SelectionController::copyTypingStyle() const { if (!m_typingStyle || !m_typingStyle->style()) return 0; return m_typingStyle->style()->copy(); } bool SelectionController::shouldDeleteSelection(const VisibleSelection& selection) const { return m_frame->editor()->client()->shouldDeleteRange(selection.toNormalizedRange().get()); } FloatRect SelectionController::bounds(bool clipToVisibleContent) const { RenderView* root = m_frame->contentRenderer(); FrameView* view = m_frame->view(); if (!root || !view) return IntRect(); IntRect selectionRect = root->selectionBounds(clipToVisibleContent); return clipToVisibleContent ? intersection(selectionRect, view->visibleContentRect()) : selectionRect; } void SelectionController::getClippedVisibleTextRectangles(Vector<FloatRect>& rectangles) const { RenderView* root = m_frame->contentRenderer(); if (!root) return; FloatRect visibleContentRect = m_frame->view()->visibleContentRect(); Vector<FloatQuad> quads; toNormalizedRange()->textQuads(quads, true); // FIXME: We are appending empty rectangles to the list for those that fall outside visibleContentRect. // It might be better to omit those rectangles entirely. size_t size = quads.size(); for (size_t i = 0; i < size; ++i) rectangles.append(intersection(quads[i].enclosingBoundingBox(), visibleContentRect)); } // Scans logically forward from "start", including any child frames. static HTMLFormElement* scanForForm(Node* start) { for (Node* node = start; node; node = node->traverseNextNode()) { if (node->hasTagName(formTag)) return static_cast<HTMLFormElement*>(node); if (node->isHTMLElement() && toHTMLElement(node)->isFormControlElement()) return static_cast<HTMLFormControlElement*>(node)->form(); if (node->hasTagName(frameTag) || node->hasTagName(iframeTag)) { Node* childDocument = static_cast<HTMLFrameElementBase*>(node)->contentDocument(); if (HTMLFormElement* frameResult = scanForForm(childDocument)) return frameResult; } } return 0; } // We look for either the form containing the current focus, or for one immediately after it HTMLFormElement* SelectionController::currentForm() const { // Start looking either at the active (first responder) node, or where the selection is. Node* start = m_frame->document()->focusedNode(); if (!start) start = this->start().deprecatedNode(); // Try walking up the node tree to find a form element. Node* node; for (node = start; node; node = node->parentNode()) { if (node->hasTagName(formTag)) return static_cast<HTMLFormElement*>(node); if (node->isHTMLElement() && toHTMLElement(node)->isFormControlElement()) return static_cast<HTMLFormControlElement*>(node)->form(); } // Try walking forward in the node tree to find a form element. return scanForForm(start); } void SelectionController::revealSelection(const ScrollAlignment& alignment, bool revealExtent) { IntRect rect; switch (selectionType()) { case VisibleSelection::NoSelection: return; case VisibleSelection::CaretSelection: rect = absoluteCaretBounds(); break; case VisibleSelection::RangeSelection: rect = revealExtent ? VisiblePosition(extent()).absoluteCaretBounds() : enclosingIntRect(bounds(false)); break; } Position start = this->start(); ASSERT(start.deprecatedNode()); if (start.deprecatedNode() && start.deprecatedNode()->renderer()) { // FIXME: This code only handles scrolling the startContainer's layer, but // the selection rect could intersect more than just that. // See <rdar://problem/4799899>. if (RenderLayer* layer = start.deprecatedNode()->renderer()->enclosingLayer()) { layer->scrollRectToVisible(rect, false, alignment, alignment); updateAppearance(); } } } void SelectionController::setSelectionFromNone() { // Put a caret inside the body if the entire frame is editable (either the // entire WebView is editable or designMode is on for this document). Document* document = m_frame->document(); bool caretBrowsing = m_frame->settings() && m_frame->settings()->caretBrowsingEnabled(); if (!isNone() || !(document->rendererIsEditable() || caretBrowsing)) return; Node* node = document->documentElement(); while (node && !node->hasTagName(bodyTag)) node = node->traverseNextNode(); if (node) setSelection(VisibleSelection(firstPositionInOrBeforeNode(node), DOWNSTREAM)); } bool SelectionController::shouldChangeSelection(const VisibleSelection& newSelection) const { return m_frame->editor()->shouldChangeSelection(selection(), newSelection, newSelection.affinity(), false); } #ifndef NDEBUG void SelectionController::formatForDebugger(char* buffer, unsigned length) const { m_selection.formatForDebugger(buffer, length); } void SelectionController::showTreeForThis() const { m_selection.showTreeForThis(); } #endif } #ifndef NDEBUG void showTree(const WebCore::SelectionController& sel) { sel.showTreeForThis(); } void showTree(const WebCore::SelectionController* sel) { if (sel) sel->showTreeForThis(); } #endif