diff options
Diffstat (limited to 'Source/WebCore/editing/FrameSelection.cpp')
-rw-r--r-- | Source/WebCore/editing/FrameSelection.cpp | 1081 |
1 files changed, 668 insertions, 413 deletions
diff --git a/Source/WebCore/editing/FrameSelection.cpp b/Source/WebCore/editing/FrameSelection.cpp index c492cf8b7..847479fad 100644 --- a/Source/WebCore/editing/FrameSelection.cpp +++ b/Source/WebCore/editing/FrameSelection.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2004, 2008, 2009, 2010 Apple Inc. All rights reserved. + * Copyright (C) 2004, 2008, 2009, 2010, 2014-2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * 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 + * THIS SOFTWARE IS PROVIDED BY APPLE 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 + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE 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 @@ -26,6 +26,7 @@ #include "config.h" #include "FrameSelection.h" +#include "AXObjectCache.h" #include "CharacterData.h" #include "DeleteSelectionCommand.h" #include "Document.h" @@ -33,19 +34,20 @@ #include "EditorClient.h" #include "Element.h" #include "ElementIterator.h" -#include "EventHandler.h" -#include "ExceptionCode.h" +#include "Event.h" +#include "EventNames.h" #include "FloatQuad.h" #include "FocusController.h" #include "Frame.h" #include "FrameTree.h" #include "FrameView.h" #include "GraphicsContext.h" +#include "HTMLBodyElement.h" #include "HTMLFormElement.h" -#include "HTMLFrameElementBase.h" -#include "HTMLInputElement.h" -#include "HTMLSelectElement.h" +#include "HTMLFrameElement.h" +#include "HTMLIFrameElement.h" #include "HTMLNames.h" +#include "HTMLSelectElement.h" #include "HitTestRequest.h" #include "HitTestResult.h" #include "InlineTextBox.h" @@ -74,8 +76,6 @@ #include "RenderStyle.h" #endif -#define EDIT_DEBUG 0 - namespace WebCore { using namespace HTMLNames; @@ -110,17 +110,20 @@ FrameSelection::FrameSelection(Frame* frame) : m_frame(frame) , m_xPosForVerticalArrowNavigation(NoXPosForVerticalArrowNavigation()) , m_granularity(CharacterGranularity) - , m_caretBlinkTimer(this, &FrameSelection::caretBlinkTimerFired) + , m_caretBlinkTimer(*this, &FrameSelection::caretBlinkTimerFired) + , m_appearanceUpdateTimer(*this, &FrameSelection::appearanceUpdateTimerFired) + , m_caretInsidePositionFixed(false) , m_absCaretBoundsDirty(true) , m_caretPaint(true) , m_isCaretBlinkingSuspended(false) , m_focused(frame && frame->page() && frame->page()->focusController().focusedFrame() == frame) , m_shouldShowBlockCursor(false) + , m_pendingSelectionUpdate(false) + , m_shouldRevealSelection(false) + , m_alwaysAlignCursorOnScrollWhenRevealingSelection(false) #if PLATFORM(IOS) , m_updateAppearanceEnabled(false) , m_caretBlinks(true) - , m_closeTypingSuppressions(0) - , m_scrollingSuppressCount(0) #endif { if (shouldAlwaysUseDirectionalSelection(m_frame)) @@ -135,35 +138,40 @@ Element* FrameSelection::rootEditableElementOrDocumentElement() const void FrameSelection::moveTo(const VisiblePosition &pos, EUserTriggered userTriggered, CursorAlignOnScroll align) { - SetSelectionOptions options = CloseTyping | ClearTypingStyle | userTriggered; - setSelection(VisibleSelection(pos.deepEquivalent(), pos.deepEquivalent(), pos.affinity(), m_selection.isDirectional()), options, align); + setSelection(VisibleSelection(pos.deepEquivalent(), pos.deepEquivalent(), pos.affinity(), m_selection.isDirectional()), + defaultSetSelectionOptions(userTriggered), AXTextStateChangeIntent(), align); } void FrameSelection::moveTo(const VisiblePosition &base, const VisiblePosition &extent, EUserTriggered userTriggered) { const bool selectionHasDirection = true; - SetSelectionOptions options = CloseTyping | ClearTypingStyle | userTriggered; - setSelection(VisibleSelection(base.deepEquivalent(), extent.deepEquivalent(), base.affinity(), selectionHasDirection), options); + setSelection(VisibleSelection(base.deepEquivalent(), extent.deepEquivalent(), base.affinity(), selectionHasDirection), defaultSetSelectionOptions(userTriggered)); } void FrameSelection::moveTo(const Position &pos, EAffinity affinity, EUserTriggered userTriggered) { - SetSelectionOptions options = CloseTyping | ClearTypingStyle | userTriggered; - setSelection(VisibleSelection(pos, affinity, m_selection.isDirectional()), options); + setSelection(VisibleSelection(pos, affinity, m_selection.isDirectional()), defaultSetSelectionOptions(userTriggered)); } -void FrameSelection::moveTo(const Range *r, EAffinity affinity, EUserTriggered userTriggered) +void FrameSelection::moveTo(const Range* range) { - SetSelectionOptions options = CloseTyping | ClearTypingStyle | userTriggered; - VisibleSelection selection = r ? VisibleSelection(r->startPosition(), r->endPosition(), affinity) : VisibleSelection(Position(), Position(), affinity); - setSelection(selection, options); + VisibleSelection selection = range ? VisibleSelection(range->startPosition(), range->endPosition()) : VisibleSelection(); + setSelection(selection); } void FrameSelection::moveTo(const Position &base, const Position &extent, EAffinity affinity, EUserTriggered userTriggered) { const bool selectionHasDirection = true; - SetSelectionOptions options = CloseTyping | ClearTypingStyle | userTriggered; - setSelection(VisibleSelection(base, extent, affinity, selectionHasDirection), options); + setSelection(VisibleSelection(base, extent, affinity, selectionHasDirection), defaultSetSelectionOptions(userTriggered)); +} + +void FrameSelection::moveWithoutValidationTo(const Position& base, const Position& extent, bool selectionHasDirection, bool shouldSetFocus, const AXTextStateChangeIntent& intent) +{ + VisibleSelection newSelection; + newSelection.setWithoutValidation(base, extent); + newSelection.setIsDirectional(selectionHasDirection); + AXTextStateChangeIntent newIntent = intent.type == AXTextStateChangeTypeUnknown ? AXTextStateChangeIntent(AXTextStateChangeTypeSelectionMove, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown, false }) : intent; + setSelection(newSelection, defaultSetSelectionOptions() | (shouldSetFocus ? 0 : DoNotSetFocus), newIntent); } void DragCaretController::setCaretPosition(const VisiblePosition& position) @@ -172,7 +180,7 @@ void DragCaretController::setCaretPosition(const VisiblePosition& position) invalidateCaretRect(node); m_position = position; setCaretRectNeedsUpdate(); - Document* document = 0; + Document* document = nullptr; if (Node* node = m_position.deepEquivalent().deprecatedNode()) { invalidateCaretRect(node); document = &node->document(); @@ -220,7 +228,7 @@ static void adjustEndpointsAtBidiBoundary(VisiblePosition& visibleBase, VisibleP } } -void FrameSelection::setNonDirectionalSelectionIfNeeded(const VisibleSelection& passedNewSelection, TextGranularity granularity, +void FrameSelection::setSelectionByMouseIfDifferent(const VisibleSelection& passedNewSelection, TextGranularity granularity, EndPointsAdjustmentMode endpointsAdjustmentMode) { VisibleSelection newSelection = passedNewSelection; @@ -247,144 +255,196 @@ void FrameSelection::setNonDirectionalSelectionIfNeeded(const VisibleSelection& if (m_selection == newSelection || !shouldChangeSelection(newSelection)) return; - setSelection(newSelection, granularity); + + AXTextStateChangeIntent intent; + if (AXObjectCache::accessibilityEnabled() && newSelection.isCaret()) + intent = AXTextStateChangeIntent(AXTextStateChangeTypeSelectionMove, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown, false }); + else + intent = AXTextStateChangeIntent(); + setSelection(newSelection, defaultSetSelectionOptions() | FireSelectEvent, intent, AlignCursorOnScrollIfNeeded, granularity); } -void FrameSelection::setSelection(const VisibleSelection& newSelection, SetSelectionOptions options, CursorAlignOnScroll align, TextGranularity granularity) +bool FrameSelection::setSelectionWithoutUpdatingAppearance(const VisibleSelection& newSelectionPossiblyWithoutDirection, SetSelectionOptions options, CursorAlignOnScroll align, TextGranularity granularity) { bool closeTyping = options & CloseTyping; bool shouldClearTypingStyle = options & ClearTypingStyle; - EUserTriggered userTriggered = selectionOptionsToUserTriggered(options); - VisibleSelection s = newSelection; + VisibleSelection newSelection = newSelectionPossiblyWithoutDirection; if (shouldAlwaysUseDirectionalSelection(m_frame)) - s.setIsDirectional(true); + newSelection.setIsDirectional(true); if (!m_frame) { - m_selection = s; - return; + m_selection = newSelection; + return false; } // <http://bugs.webkit.org/show_bug.cgi?id=23464>: Infinite recursion at FrameSelection::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.frame() && document.frame() != m_frame && &document != m_frame->document()) { - RefPtr<Frame> guard = document.frame(); - document.frame()->selection().setSelection(s, options, align, granularity); - // It's possible that during the above set selection, this FrameSelection has been modified by - // selectFrameElementInParentIfFullySelected, but that the selection is no longer valid since - // the frame is about to be destroyed. If this is the case, clear our selection. - if (guard->hasOneRef() && !m_selection.isNonOrphanedCaretOrRange()) - clear(); - return; + if (Document* newSelectionDocument = newSelection.base().document()) { + if (RefPtr<Frame> newSelectionFrame = newSelectionDocument->frame()) { + if (newSelectionFrame != m_frame && newSelectionDocument != m_frame->document()) { + newSelectionFrame->selection().setSelection(newSelection, options, AXTextStateChangeIntent(), align, granularity); + // It's possible that during the above set selection, this FrameSelection has been modified by + // selectFrameElementInParentIfFullySelected, but that the selection is no longer valid since + // the frame is about to be destroyed. If this is the case, clear our selection. + if (newSelectionFrame->hasOneRef() && m_selection.isNoneOrOrphaned()) + clear(); + return false; + } } } m_granularity = granularity; -#if PLATFORM(IOS) - if (closeTyping && m_closeTypingSuppressions == 0) -#else if (closeTyping) -#endif TypingCommand::closeTyping(m_frame); if (shouldClearTypingStyle) clearTypingStyle(); - if (m_selection == s) { - // Even if selection was not changed, selection offsets may have been changed. - updateSelectionCachesIfSelectionIsInsideTextFormControl(userTriggered); - return; - } - VisibleSelection oldSelection = m_selection; + bool didMutateSelection = oldSelection != newSelection; + if (didMutateSelection) + m_frame->editor().selectionWillChange(); + + m_selection = newSelection; + + // Selection offsets should increase when LF is inserted before the caret in InsertLineBreakCommand. See <https://webkit.org/b/56061>. + if (HTMLTextFormControlElement* textControl = enclosingTextFormControl(newSelection.start())) + textControl->selectionChanged(options & FireSelectEvent); + + if (!didMutateSelection) + return false; - m_selection = s; setCaretRectNeedsUpdate(); - - if (!s.isNone() && !(options & DoNotSetFocus)) - setFocusedElementIfNeeded(); - if (!(options & DoNotUpdateAppearance)) { -#if ENABLE(TEXT_CARET) - m_frame->document()->updateLayoutIgnorePendingStylesheets(); -#else - m_frame->document()->updateStyleIfNeeded(); -#endif - updateAppearance(); - } + if (!newSelection.isNone() && !(options & DoNotSetFocus)) + setFocusedElementIfNeeded(); // 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(); - updateSelectionCachesIfSelectionIsInsideTextFormControl(userTriggered); m_frame->editor().respondToChangedSelection(oldSelection, options); - if (userTriggered == UserTriggered) { + m_frame->document()->enqueueDocumentEvent(Event::create(eventNames().selectionchangeEvent, false, false)); + + return true; +} + +void FrameSelection::setSelection(const VisibleSelection& selection, SetSelectionOptions options, AXTextStateChangeIntent intent, CursorAlignOnScroll align, TextGranularity granularity) +{ + RefPtr<Frame> protectedFrame(m_frame); + if (!setSelectionWithoutUpdatingAppearance(selection, options, align, granularity)) + return; + + Document* document = m_frame->document(); + if (!document) + return; + + m_shouldRevealSelection = options & RevealSelection; + m_alwaysAlignCursorOnScrollWhenRevealingSelection = align == AlignCursorOnScrollAlways; + + m_pendingSelectionUpdate = true; + + if (document->hasPendingStyleRecalc()) + return; + + FrameView* frameView = document->view(); + if (frameView && frameView->layoutPending()) + return; + + updateAndRevealSelection(intent); +} + +static void updateSelectionByUpdatingLayoutOrStyle(Frame& frame) +{ +#if ENABLE(TEXT_CARET) + frame.document()->updateLayoutIgnorePendingStylesheets(); +#else + frame.document()->updateStyleIfNeeded(); +#endif +} + +void FrameSelection::setNeedsSelectionUpdate() +{ + m_pendingSelectionUpdate = true; + if (RenderView* view = m_frame->contentRenderer()) + view->clearSelection(); +} + +void FrameSelection::updateAndRevealSelection(const AXTextStateChangeIntent& intent) +{ + if (!m_pendingSelectionUpdate) + return; + + m_pendingSelectionUpdate = false; + + updateAppearance(); + + if (m_shouldRevealSelection) { ScrollAlignment alignment; if (m_frame->editor().behavior().shouldCenterAlignWhenSelectionIsRevealed()) - alignment = (align == AlignCursorOnScrollAlways) ? ScrollAlignment::alignCenterAlways : ScrollAlignment::alignCenterIfNeeded; + alignment = m_alwaysAlignCursorOnScrollWhenRevealingSelection ? ScrollAlignment::alignCenterAlways : ScrollAlignment::alignCenterIfNeeded; else - alignment = (align == AlignCursorOnScrollAlways) ? ScrollAlignment::alignTopAlways : ScrollAlignment::alignToEdgeIfNeeded; + alignment = m_alwaysAlignCursorOnScrollWhenRevealingSelection ? ScrollAlignment::alignTopAlways : ScrollAlignment::alignToEdgeIfNeeded; - revealSelection(alignment, RevealExtent); + revealSelection(SelectionRevealMode::Reveal, alignment, RevealExtent); } -#if HAVE(ACCESSIBILITY) - notifyAccessibilityForSelectionChange(); + + notifyAccessibilityForSelectionChange(intent); + + if (auto* client = m_frame->editor().client()) + client->didChangeSelectionAndUpdateLayout(); +} + +void FrameSelection::updateDataDetectorsForSelection() +{ +#if ENABLE(TELEPHONE_NUMBER_DETECTION) && !PLATFORM(IOS) + m_frame->editor().scanSelectionForTelephoneNumbers(); #endif - m_frame->document()->enqueueDocumentEvent(Event::create(eventNames().selectionchangeEvent, false, false)); } -static bool removingNodeRemovesPosition(Node* node, const Position& position) +static bool removingNodeRemovesPosition(Node& node, const Position& position) { if (!position.anchorNode()) return false; - if (position.anchorNode() == node) + if (position.anchorNode() == &node) return true; - if (!node->isElementNode()) + if (!is<Element>(node)) return false; - Element* element = toElement(node); - return element->containsIncludingShadowDOM(position.anchorNode()); -} - -static void clearRenderViewSelection(const Position& position) -{ - Ref<Document> document(position.anchorNode()->document()); - document->updateStyleIfNeeded(); - if (RenderView* view = document->renderView()) - view->clearSelection(); + return downcast<Element>(node).containsIncludingShadowDOM(position.anchorNode()); } -void DragCaretController::nodeWillBeRemoved(Node* node) +void DragCaretController::nodeWillBeRemoved(Node& node) { - if (!hasCaret() || (node && !node->inDocument())) + if (!hasCaret() || !node.isConnected()) return; if (!removingNodeRemovesPosition(node, m_position.deepEquivalent())) return; - clearRenderViewSelection(m_position.deepEquivalent()); + if (RenderView* view = node.document().renderView()) + view->clearSelection(); + clear(); } -void FrameSelection::nodeWillBeRemoved(Node* node) +void FrameSelection::nodeWillBeRemoved(Node& node) { // 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 (isNone() || (node && !node->inDocument())) + if (isNone() || !node.isConnected()) return; respondToNodeModification(node, removingNodeRemovesPosition(node, m_selection.base()), removingNodeRemovesPosition(node, m_selection.extent()), removingNodeRemovesPosition(node, m_selection.start()), removingNodeRemovesPosition(node, m_selection.end())); } -void FrameSelection::respondToNodeModification(Node* node, bool baseRemoved, bool extentRemoved, bool startRemoved, bool endRemoved) +void FrameSelection::respondToNodeModification(Node& node, bool baseRemoved, bool extentRemoved, bool startRemoved, bool endRemoved) { bool clearRenderTreeSelection = false; bool clearDOMTreeSelection = false; @@ -416,19 +476,28 @@ void FrameSelection::respondToNodeModification(Node* node, bool baseRemoved, boo 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; + auto compareNodeResult = range->compareNode(node); + if (!compareNodeResult.hasException()) { + auto compareResult = compareNodeResult.releaseReturnValue(); + if (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) - clearRenderViewSelection(m_selection.start()); + if (clearRenderTreeSelection) { + if (auto* renderView = node.document().renderView()) { + renderView->clearSelection(); + + // Trigger a selection update so the selection will be set again. + m_pendingSelectionUpdate = true; + renderView->setNeedsLayout(); + } + } if (clearDOMTreeSelection) setSelection(VisibleSelection(), DoNotSetFocus); @@ -457,7 +526,7 @@ static void updatePositionAfterAdoptingTextReplacement(Position& position, Chara void FrameSelection::textWasReplaced(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 || !node->inDocument()) + if (isNone() || !node || !node->isConnected()) return; Position base = m_selection.base(); @@ -478,7 +547,6 @@ void FrameSelection::textWasReplaced(CharacterData* node, unsigned offset, unsig else newSelection.setWithoutValidation(start, end); - m_frame->document()->updateLayout(); setSelection(newSelection, DoNotSetFocus); } } @@ -490,8 +558,8 @@ TextDirection FrameSelection::directionOfEnclosingBlock() TextDirection FrameSelection::directionOfSelection() { - InlineBox* startBox = 0; - InlineBox* endBox = 0; + InlineBox* startBox = nullptr; + InlineBox* endBox = nullptr; int unusedOffset; // Cache the VisiblePositions because visibleStart() and visibleEnd() // can cause layout, which has the potential to invalidate lineboxes. @@ -558,14 +626,14 @@ void FrameSelection::willBeModified(EAlteration alter, SelectionDirection direct VisiblePosition FrameSelection::positionForPlatform(bool isGetStart) const { - if (m_frame && m_frame->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(); + if (m_frame && m_frame->editor().behavior().shouldAlwaysExtendSelectionFromExtentEndpoint()) + return m_selection.isBaseFirst() ? m_selection.visibleEnd() : m_selection.visibleStart(); + + return isGetStart ? m_selection.visibleStart() : m_selection.visibleEnd(); } VisiblePosition FrameSelection::startForPlatform() const @@ -643,11 +711,9 @@ VisiblePosition FrameSelection::modifyExtendingRight(TextGranularity granularity // FIXME: implement all of the above? pos = modifyExtendingForward(granularity); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif } #if ENABLE(USERSELECT_ALL) adjustPositionForUserSelectAll(pos, directionOfEnclosingBlock() == LTR); @@ -674,11 +740,9 @@ VisiblePosition FrameSelection::modifyExtendingForward(TextGranularity granulari case ParagraphGranularity: pos = nextParagraphPosition(pos, lineDirectionPointForBlockDirectionNavigation(EXTENT)); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif case SentenceBoundary: pos = endOfSentence(endForPlatform()); break; @@ -702,8 +766,10 @@ VisiblePosition FrameSelection::modifyExtendingForward(TextGranularity granulari return pos; } -VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity) +VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity, bool* reachedBoundary) { + if (reachedBoundary) + *reachedBoundary = false; VisiblePosition pos; switch (granularity) { case CharacterGranularity: @@ -713,13 +779,14 @@ VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity) else pos = VisiblePosition(m_selection.start(), m_selection.affinity()); } else - pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).right(true); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).right(true, reachedBoundary); break; case WordGranularity: { - // Visual word movement relies on isWordTextBreak which is not implemented in WinCE and QT. - // https://bugs.webkit.org/show_bug.cgi?id=81136. bool skipsSpaceWhenMovingRight = m_frame && m_frame->editor().behavior().shouldSkipSpaceWhenMovingRight(); - pos = rightWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity()), skipsSpaceWhenMovingRight); + VisiblePosition currentPosition(m_selection.extent(), m_selection.affinity()); + pos = rightWordPosition(currentPosition, skipsSpaceWhenMovingRight); + if (reachedBoundary) + *reachedBoundary = pos == currentPosition; break; } case SentenceGranularity: @@ -729,22 +796,38 @@ VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity) case ParagraphBoundary: case DocumentBoundary: // FIXME: Implement all of the above. - pos = modifyMovingForward(granularity); + pos = modifyMovingForward(granularity, reachedBoundary); break; case LineBoundary: - pos = rightBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock()); + pos = rightBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock(), reachedBoundary); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif } return pos; } -VisiblePosition FrameSelection::modifyMovingForward(TextGranularity granularity) +VisiblePosition FrameSelection::modifyMovingForward(TextGranularity granularity, bool* reachedBoundary) { + if (reachedBoundary) + *reachedBoundary = false; + VisiblePosition currentPosition; + switch (granularity) { + case WordGranularity: + case SentenceGranularity: + currentPosition = VisiblePosition(m_selection.extent(), m_selection.affinity()); + break; + case LineGranularity: + case ParagraphGranularity: + case SentenceBoundary: + case ParagraphBoundary: + case DocumentBoundary: + currentPosition = endForPlatform(); + break; + default: + break; + } VisiblePosition pos; // FIXME: Stay in editable content for the less common granularities. switch (granularity) { @@ -752,47 +835,59 @@ VisiblePosition FrameSelection::modifyMovingForward(TextGranularity granularity) if (isRange()) pos = VisiblePosition(m_selection.end(), m_selection.affinity()); else - pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).next(CannotCrossEditingBoundary); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).next(CannotCrossEditingBoundary, reachedBoundary); break; case WordGranularity: - pos = nextWordPositionForPlatform(VisiblePosition(m_selection.extent(), m_selection.affinity())); + pos = nextWordPositionForPlatform(currentPosition); break; case SentenceGranularity: - pos = nextSentencePosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); + pos = nextSentencePosition(currentPosition); 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(); + pos = currentPosition; if (!isRange() || !isStartOfLine(pos)) pos = nextLinePosition(pos, lineDirectionPointForBlockDirectionNavigation(START)); break; } case ParagraphGranularity: - pos = nextParagraphPosition(endForPlatform(), lineDirectionPointForBlockDirectionNavigation(START)); + pos = nextParagraphPosition(currentPosition, lineDirectionPointForBlockDirectionNavigation(START)); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif case SentenceBoundary: - pos = endOfSentence(endForPlatform()); + pos = endOfSentence(currentPosition); break; case LineBoundary: - pos = logicalEndOfLine(endForPlatform()); + pos = logicalEndOfLine(endForPlatform(), reachedBoundary); break; case ParagraphBoundary: - pos = endOfParagraph(endForPlatform()); + pos = endOfParagraph(currentPosition); break; case DocumentBoundary: - pos = endForPlatform(); + pos = currentPosition; if (isEditablePosition(pos.deepEquivalent())) pos = endOfEditableContent(pos); else pos = endOfDocument(pos); break; } + switch (granularity) { + case WordGranularity: + case SentenceGranularity: + case LineGranularity: + case ParagraphGranularity: + case SentenceBoundary: + case ParagraphBoundary: + case DocumentBoundary: + if (reachedBoundary) + *reachedBoundary = pos == currentPosition; + break; + default: + break; + } return pos; } @@ -832,11 +927,9 @@ VisiblePosition FrameSelection::modifyExtendingLeft(TextGranularity granularity) case DocumentBoundary: pos = modifyExtendingBackward(granularity); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif } #if ENABLE(USERSELECT_ALL) adjustPositionForUserSelectAll(pos, !(directionOfEnclosingBlock() == LTR)); @@ -884,11 +977,9 @@ VisiblePosition FrameSelection::modifyExtendingBackward(TextGranularity granular else pos = startOfDocument(pos); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif } #if ENABLE(USERSELECT_ALL) adjustPositionForUserSelectAll(pos, !(directionOfEnclosingBlock() == LTR)); @@ -896,8 +987,10 @@ VisiblePosition FrameSelection::modifyExtendingBackward(TextGranularity granular return pos; } -VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity) +VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity, bool* reachedBoundary) { + if (reachedBoundary) + *reachedBoundary = false; VisiblePosition pos; switch (granularity) { case CharacterGranularity: @@ -907,11 +1000,14 @@ VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity) else pos = VisiblePosition(m_selection.end(), m_selection.affinity()); else - pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).left(true); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).left(true, reachedBoundary); break; case WordGranularity: { bool skipsSpaceWhenMovingRight = m_frame && m_frame->editor().behavior().shouldSkipSpaceWhenMovingRight(); - pos = leftWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity()), skipsSpaceWhenMovingRight); + VisiblePosition currentPosition(m_selection.extent(), m_selection.affinity()); + pos = leftWordPosition(currentPosition, skipsSpaceWhenMovingRight); + if (reachedBoundary) + *reachedBoundary = pos == currentPosition; break; } case SentenceGranularity: @@ -921,63 +1017,91 @@ VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity) case ParagraphBoundary: case DocumentBoundary: // FIXME: Implement all of the above. - pos = modifyMovingBackward(granularity); + pos = modifyMovingBackward(granularity, reachedBoundary); break; case LineBoundary: - pos = leftBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock()); + pos = leftBoundaryOfLine(startForPlatform(), directionOfEnclosingBlock(), reachedBoundary); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif } return pos; } -VisiblePosition FrameSelection::modifyMovingBackward(TextGranularity granularity) +VisiblePosition FrameSelection::modifyMovingBackward(TextGranularity granularity, bool* reachedBoundary) { + if (reachedBoundary) + *reachedBoundary = false; + VisiblePosition currentPosition; + switch (granularity) { + case WordGranularity: + case SentenceGranularity: + currentPosition = VisiblePosition(m_selection.extent(), m_selection.affinity()); + break; + case LineGranularity: + case ParagraphGranularity: + case SentenceBoundary: + case ParagraphBoundary: + case DocumentBoundary: + currentPosition = startForPlatform(); + break; + default: + break; + } 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); + pos = VisiblePosition(m_selection.extent(), m_selection.affinity()).previous(CannotCrossEditingBoundary, reachedBoundary); break; case WordGranularity: - pos = previousWordPosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); + pos = previousWordPosition(currentPosition); break; case SentenceGranularity: - pos = previousSentencePosition(VisiblePosition(m_selection.extent(), m_selection.affinity())); + pos = previousSentencePosition(currentPosition); break; case LineGranularity: - pos = previousLinePosition(startForPlatform(), lineDirectionPointForBlockDirectionNavigation(START)); + pos = previousLinePosition(currentPosition, lineDirectionPointForBlockDirectionNavigation(START)); break; case ParagraphGranularity: - pos = previousParagraphPosition(startForPlatform(), lineDirectionPointForBlockDirectionNavigation(START)); + pos = previousParagraphPosition(currentPosition, lineDirectionPointForBlockDirectionNavigation(START)); break; case SentenceBoundary: - pos = startOfSentence(startForPlatform()); + pos = startOfSentence(currentPosition); break; case LineBoundary: - pos = logicalStartOfLine(startForPlatform()); + pos = logicalStartOfLine(startForPlatform(), reachedBoundary); break; case ParagraphBoundary: - pos = startOfParagraph(startForPlatform()); + pos = startOfParagraph(currentPosition); break; case DocumentBoundary: - pos = startForPlatform(); + pos = currentPosition; if (isEditablePosition(pos.deepEquivalent())) pos = startOfEditableContent(pos); else pos = startOfDocument(pos); break; -#if PLATFORM(IOS) case DocumentGranularity: ASSERT_NOT_REACHED(); break; -#endif + } + switch (granularity) { + case WordGranularity: + case SentenceGranularity: + case LineGranularity: + case ParagraphGranularity: + case SentenceBoundary: + case ParagraphBoundary: + case DocumentBoundary: + if (reachedBoundary) + *reachedBoundary = pos == currentPosition; + break; + default: + break; } return pos; } @@ -985,7 +1109,137 @@ VisiblePosition FrameSelection::modifyMovingBackward(TextGranularity granularity static bool isBoundary(TextGranularity granularity) { return granularity == LineBoundary || granularity == ParagraphBoundary || granularity == DocumentBoundary; -} +} + +AXTextStateChangeIntent FrameSelection::textSelectionIntent(EAlteration alter, SelectionDirection direction, TextGranularity granularity) +{ + AXTextStateChangeIntent intent = AXTextStateChangeIntent(); + bool flip = false; + if (alter == FrameSelection::AlterationMove) { + intent.type = AXTextStateChangeTypeSelectionMove; + flip = isRange() && directionOfSelection() == RTL; + } else + intent.type = AXTextStateChangeTypeSelectionExtend; + switch (granularity) { + case CharacterGranularity: + intent.selection.granularity = AXTextSelectionGranularityCharacter; + break; + case WordGranularity: + intent.selection.granularity = AXTextSelectionGranularityWord; + break; + case SentenceGranularity: + case SentenceBoundary: + intent.selection.granularity = AXTextSelectionGranularitySentence; + break; + case LineGranularity: + case LineBoundary: + intent.selection.granularity = AXTextSelectionGranularityLine; + break; + case ParagraphGranularity: + case ParagraphBoundary: + intent.selection.granularity = AXTextSelectionGranularityParagraph; + break; + case DocumentGranularity: + case DocumentBoundary: + intent.selection.granularity = AXTextSelectionGranularityDocument; + break; + } + bool boundary = false; + switch (granularity) { + case CharacterGranularity: + case WordGranularity: + case SentenceGranularity: + case LineGranularity: + case ParagraphGranularity: + case DocumentGranularity: + break; + case SentenceBoundary: + case LineBoundary: + case ParagraphBoundary: + case DocumentBoundary: + boundary = true; + break; + } + switch (direction) { + case DirectionRight: + case DirectionForward: + if (boundary) + intent.selection.direction = flip ? AXTextSelectionDirectionBeginning : AXTextSelectionDirectionEnd; + else + intent.selection.direction = flip ? AXTextSelectionDirectionPrevious : AXTextSelectionDirectionNext; + break; + case DirectionLeft: + case DirectionBackward: + if (boundary) + intent.selection.direction = flip ? AXTextSelectionDirectionEnd : AXTextSelectionDirectionBeginning; + else + intent.selection.direction = flip ? AXTextSelectionDirectionNext : AXTextSelectionDirectionPrevious; + break; + } + return intent; +} + +static AXTextSelection textSelectionWithDirectionAndGranularity(SelectionDirection direction, TextGranularity granularity) +{ + // FIXME: Account for BIDI in DirectionRight & DirectionLeft. (In a RTL block, Right would map to Previous/Beginning and Left to Next/End.) + AXTextSelectionDirection intentDirection = AXTextSelectionDirectionUnknown; + switch (direction) { + case DirectionForward: + intentDirection = AXTextSelectionDirectionNext; + break; + case DirectionRight: + intentDirection = AXTextSelectionDirectionNext; + break; + case DirectionBackward: + intentDirection = AXTextSelectionDirectionPrevious; + break; + case DirectionLeft: + intentDirection = AXTextSelectionDirectionPrevious; + break; + } + AXTextSelectionGranularity intentGranularity = AXTextSelectionGranularityUnknown; + switch (granularity) { + case CharacterGranularity: + intentGranularity = AXTextSelectionGranularityCharacter; + break; + case WordGranularity: + intentGranularity = AXTextSelectionGranularityWord; + break; + case SentenceGranularity: + case SentenceBoundary: // FIXME: Boundary should affect direction. + intentGranularity = AXTextSelectionGranularitySentence; + break; + case LineGranularity: + intentGranularity = AXTextSelectionGranularityLine; + break; + case ParagraphGranularity: + case ParagraphBoundary: // FIXME: Boundary should affect direction. + intentGranularity = AXTextSelectionGranularityParagraph; + break; + case DocumentGranularity: + case DocumentBoundary: // FIXME: Boundary should affect direction. + intentGranularity = AXTextSelectionGranularityDocument; + break; + case LineBoundary: + intentGranularity = AXTextSelectionGranularityLine; + switch (direction) { + case DirectionForward: + intentDirection = AXTextSelectionDirectionEnd; + break; + case DirectionRight: + intentDirection = AXTextSelectionDirectionEnd; + break; + case DirectionBackward: + intentDirection = AXTextSelectionDirectionBeginning; + break; + case DirectionLeft: + intentDirection = AXTextSelectionDirectionBeginning; + break; + } + break; + } + return { intentDirection, intentGranularity, false }; +} bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, TextGranularity granularity, EUserTriggered userTriggered) { @@ -1004,13 +1258,14 @@ bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, Tex willBeModified(alter, direction); + bool reachedBoundary = false; bool wasRange = m_selection.isRange(); Position originalStartPosition = m_selection.start(); VisiblePosition position; switch (direction) { case DirectionRight: if (alter == AlterationMove) - position = modifyMovingRight(granularity); + position = modifyMovingRight(granularity, &reachedBoundary); else position = modifyExtendingRight(granularity); break; @@ -1018,11 +1273,11 @@ bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, Tex if (alter == AlterationExtend) position = modifyExtendingForward(granularity); else - position = modifyMovingForward(granularity); + position = modifyMovingForward(granularity, &reachedBoundary); break; case DirectionLeft: if (alter == AlterationMove) - position = modifyMovingLeft(granularity); + position = modifyMovingLeft(granularity, &reachedBoundary); else position = modifyExtendingLeft(granularity); break; @@ -1030,10 +1285,15 @@ bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, Tex if (alter == AlterationExtend) position = modifyExtendingBackward(granularity); else - position = modifyMovingBackward(granularity); + position = modifyMovingBackward(granularity, &reachedBoundary); break; } + if (reachedBoundary && !isRange() && userTriggered == UserTriggered && m_frame && AXObjectCache::accessibilityEnabled()) { + notifyAccessibilityForSelectionChange({ AXTextStateChangeTypeSelectionBoundary, textSelectionWithDirectionAndGranularity(direction, granularity) }); + return true; + } + if (position.isNull()) return false; @@ -1041,6 +1301,11 @@ bool FrameSelection::modify(EAlteration alter, SelectionDirection direction, Tex if (!wasRange && alter == AlterationMove && position == originalStartPosition) return false; + if (m_frame && AXObjectCache::accessibilityEnabled()) { + if (AXObjectCache* cache = m_frame->document()->existingAXObjectCache()) + cache->setTextSelectionIntent(textSelectionIntent(alter, direction, granularity)); + } + // 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 @@ -1241,8 +1506,8 @@ void FrameSelection::prepareForDestruction() if (view) view->clearSelection(); - setSelection(VisibleSelection(), CloseTyping | ClearTypingStyle | DoNotUpdateAppearance); - m_previousCaretNode.clear(); + setSelectionWithoutUpdatingAppearance(VisibleSelection(), defaultSetSelectionOptions(), AlignCursorOnScrollIfNeeded, CharacterGranularity); + m_previousCaretNode = nullptr; } void FrameSelection::setStart(const VisiblePosition &pos, EUserTriggered trigger) @@ -1264,25 +1529,25 @@ void FrameSelection::setEnd(const VisiblePosition &pos, EUserTriggered trigger) void FrameSelection::setBase(const VisiblePosition &pos, EUserTriggered userTriggered) { const bool selectionHasDirection = true; - setSelection(VisibleSelection(pos.deepEquivalent(), m_selection.extent(), pos.affinity(), selectionHasDirection), CloseTyping | ClearTypingStyle | userTriggered); + setSelection(VisibleSelection(pos.deepEquivalent(), m_selection.extent(), pos.affinity(), selectionHasDirection), defaultSetSelectionOptions(userTriggered)); } void FrameSelection::setExtent(const VisiblePosition &pos, EUserTriggered userTriggered) { const bool selectionHasDirection = true; - setSelection(VisibleSelection(m_selection.base(), pos.deepEquivalent(), pos.affinity(), selectionHasDirection), CloseTyping | ClearTypingStyle | userTriggered); + setSelection(VisibleSelection(m_selection.base(), pos.deepEquivalent(), pos.affinity(), selectionHasDirection), defaultSetSelectionOptions(userTriggered)); } void FrameSelection::setBase(const Position &pos, EAffinity affinity, EUserTriggered userTriggered) { const bool selectionHasDirection = true; - setSelection(VisibleSelection(pos, m_selection.extent(), affinity, selectionHasDirection), CloseTyping | ClearTypingStyle | userTriggered); + setSelection(VisibleSelection(pos, m_selection.extent(), affinity, selectionHasDirection), defaultSetSelectionOptions(userTriggered)); } void FrameSelection::setExtent(const Position &pos, EAffinity affinity, EUserTriggered userTriggered) { const bool selectionHasDirection = true; - setSelection(VisibleSelection(m_selection.base(), pos, affinity, selectionHasDirection), CloseTyping | ClearTypingStyle | userTriggered); + setSelection(VisibleSelection(m_selection.base(), pos, affinity, selectionHasDirection), defaultSetSelectionOptions(userTriggered)); } void CaretBase::clearCaretRect() @@ -1290,71 +1555,23 @@ void CaretBase::clearCaretRect() m_caretLocalRect = LayoutRect(); } -static inline bool caretRendersInsideNode(Node* node) -{ - return node && !isTableElement(node) && !editingIgnoresContent(node); -} - -static RenderObject* caretRenderer(Node* node) -{ - 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->isRenderBlockFlow() && caretRendersInsideNode(node); - return paintedByBlock ? renderer : renderer->containingBlock(); -} - bool CaretBase::updateCaretRect(Document* document, const VisiblePosition& caretPosition) { - document->updateStyleIfNeeded(); - m_caretLocalRect = LayoutRect(); - + document->updateLayoutIgnorePendingStylesheets(); m_caretRectNeedsUpdate = false; - - if (caretPosition.isNull()) - return false; - - ASSERT(caretPosition.deepEquivalent().deprecatedNode()->renderer()); - - // First compute a rect local to the renderer at the selection start. - RenderObject* renderer; - LayoutRect localRect = caretPosition.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(caretPosition.deepEquivalent().deprecatedNode()); - - // 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_caretLocalRect = localRect; - - return true; + RenderBlock* renderer; + m_caretLocalRect = localCaretRectInRendererForCaretPainting(caretPosition, renderer); + return !m_caretLocalRect.isEmpty(); } -RenderObject* FrameSelection::caretRenderer() const +RenderBlock* FrameSelection::caretRendererWithoutUpdatingLayout() const { - return WebCore::caretRenderer(m_selection.start().deprecatedNode()); + return rendererForCaretPainting(m_selection.start().deprecatedNode()); } -RenderObject* DragCaretController::caretRenderer() const +RenderBlock* DragCaretController::caretRenderer() const { - return WebCore::caretRenderer(m_position.deepEquivalent().deprecatedNode()); + return rendererForCaretPainting(m_position.deepEquivalent().deprecatedNode()); } static bool isNonOrphanedCaret(const VisibleSelection& selection) @@ -1362,43 +1579,21 @@ static bool isNonOrphanedCaret(const VisibleSelection& selection) return selection.isCaret() && !selection.start().isOrphan() && !selection.end().isOrphan(); } -LayoutRect FrameSelection::localCaretRect() +IntRect FrameSelection::absoluteCaretBounds(bool* insideFixed) { - if (shouldUpdateCaretRect()) { - if (!isNonOrphanedCaret(m_selection)) - clearCaretRect(); - else if (updateCaretRect(m_frame->document(), VisiblePosition(m_selection.start(), m_selection.affinity()))) - m_absCaretBoundsDirty = true; - } - - return localCaretRectWithoutUpdate(); -} - -IntRect CaretBase::absoluteBoundsForLocalRect(Node* node, const LayoutRect& rect) const -{ - RenderObject* caretPainter = caretRenderer(node); - if (!caretPainter) + if (!m_frame) return IntRect(); - - LayoutRect localRect(rect); - if (caretPainter->isBox()) - toRenderBox(caretPainter)->flipForWritingMode(localRect); - return caretPainter->localToAbsoluteQuad(FloatRect(localRect)).enclosingBoundingBox(); -} - -IntRect FrameSelection::absoluteCaretBounds() -{ + updateSelectionByUpdatingLayoutOrStyle(*m_frame); recomputeCaretRect(); + if (insideFixed) + *insideFixed = m_caretInsidePositionFixed; return m_absCaretBounds; } static void repaintCaretForLocalRect(Node* node, const LayoutRect& rect) { - RenderObject* caretPainter = caretRenderer(node); - if (!caretPainter) - return; - - caretPainter->repaintRectangle(rect); + if (auto* caretPainter = rendererForCaretPainting(node)) + caretPainter->repaintRectangle(rect); } bool FrameSelection::recomputeCaretRect() @@ -1413,16 +1608,33 @@ bool FrameSelection::recomputeCaretRect() if (!v) return false; - Node* caretNode = m_selection.start().deprecatedNode(); - LayoutRect oldRect = localCaretRectWithoutUpdate(); - LayoutRect newRect = localCaretRect(); + + RefPtr<Node> caretNode = m_previousCaretNode; + if (shouldUpdateCaretRect()) { + if (!isNonOrphanedCaret(m_selection)) + clearCaretRect(); + else { + VisiblePosition visibleStart = m_selection.visibleStart(); + if (updateCaretRect(m_frame->document(), visibleStart)) { + caretNode = visibleStart.deepEquivalent().deprecatedNode(); + m_absCaretBoundsDirty = true; + } + } + } + LayoutRect newRect = localCaretRectWithoutUpdate(); if (caretNode == m_previousCaretNode && oldRect == newRect && !m_absCaretBoundsDirty) return false; IntRect oldAbsCaretBounds = m_absCaretBounds; - m_absCaretBounds = absoluteBoundsForLocalRect(caretNode, localCaretRectWithoutUpdate()); + bool isInsideFixed; + m_absCaretBounds = absoluteBoundsForLocalCaretRect(rendererForCaretPainting(caretNode.get()), newRect, &isInsideFixed); + m_caretInsidePositionFixed = isInsideFixed; + + if (m_absCaretBoundsDirty && m_selection.isCaret()) // We should be able to always assert this condition. + ASSERT(m_absCaretBounds == m_selection.visibleStart().absoluteCaretBounds()); + m_absCaretBoundsDirty = false; if (caretNode == m_previousCaretNode && oldAbsCaretBounds == m_absCaretBounds) @@ -1430,12 +1642,12 @@ bool FrameSelection::recomputeCaretRect() #if ENABLE(TEXT_CARET) if (RenderView* view = m_frame->document()->renderView()) { - bool previousOrNewCaretNodeIsContentEditable = isContentEditable() || (m_previousCaretNode && m_previousCaretNode->isContentEditable()); + bool previousOrNewCaretNodeIsContentEditable = m_selection.isContentEditable() || (m_previousCaretNode && m_previousCaretNode->isContentEditable()); if (shouldRepaintCaret(view, previousOrNewCaretNodeIsContentEditable)) { if (m_previousCaretNode) repaintCaretForLocalRect(m_previousCaretNode.get(), oldRect); m_previousCaretNode = caretNode; - repaintCaretForLocalRect(caretNode, newRect); + repaintCaretForLocalRect(caretNode.get(), newRect); } } #endif @@ -1477,42 +1689,59 @@ void CaretBase::invalidateCaretRect(Node* node, bool caretRectChanged) return; if (RenderView* view = node->document().renderView()) { - if (shouldRepaintCaret(view, node->isContentEditable(Node::UserSelectAllIsAlwaysNonEditable))) + if (shouldRepaintCaret(view, isEditableNode(*node))) repaintCaretForLocalRect(node, localCaretRectWithoutUpdate()); } } -void FrameSelection::paintCaret(GraphicsContext* context, const LayoutPoint& paintOffset, const LayoutRect& clipRect) +void FrameSelection::paintCaret(GraphicsContext& context, const LayoutPoint& paintOffset, const LayoutRect& clipRect) { if (m_selection.isCaret() && m_caretPaint) CaretBase::paintCaret(m_selection.start().deprecatedNode(), context, paintOffset, clipRect); } -void CaretBase::paintCaret(Node* node, GraphicsContext* context, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const +#if ENABLE(TEXT_CARET) +static inline bool disappearsIntoBackground(const Color& foreground, const Color& background) +{ + return background.blend(foreground) == background; +} +#endif + +void CaretBase::paintCaret(Node* node, GraphicsContext& context, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const { #if ENABLE(TEXT_CARET) if (m_caretVisibility == Hidden) return; LayoutRect drawingRect = localCaretRectWithoutUpdate(); - RenderObject* renderer = caretRenderer(node); - if (renderer && renderer->isBox()) - toRenderBox(renderer)->flipForWritingMode(drawingRect); + if (auto* renderer = rendererForCaretPainting(node)) + renderer->flipForWritingMode(drawingRect); drawingRect.moveBy(roundedIntPoint(paintOffset)); LayoutRect caret = intersection(drawingRect, clipRect); if (caret.isEmpty()) return; Color caretColor = Color::black; - ColorSpace colorSpace = ColorSpaceDeviceRGB; - Element* element = node->isElementNode() ? toElement(node) : node->parentElement(); + Element* element = is<Element>(*node) ? downcast<Element>(node) : node->parentElement(); + Element* rootEditableElement = node->rootEditableElement(); if (element && element->renderer()) { - caretColor = element->renderer()->style().visitedDependentColor(CSSPropertyColor); - colorSpace = element->renderer()->style().colorSpace(); + bool setToRootEditableElement = false; + if (rootEditableElement && rootEditableElement->renderer()) { + const auto& rootEditableStyle = rootEditableElement->renderer()->style(); + const auto& elementStyle = element->renderer()->style(); + auto rootEditableBGColor = rootEditableStyle.visitedDependentColor(CSSPropertyBackgroundColor); + auto elementBGColor = elementStyle.visitedDependentColor(CSSPropertyBackgroundColor); + if (disappearsIntoBackground(elementBGColor, rootEditableBGColor)) { + caretColor = rootEditableStyle.visitedDependentColor(CSSPropertyColor); + setToRootEditableElement = true; + } + } + if (!setToRootEditableElement) + caretColor = element->renderer()->style().visitedDependentColor(CSSPropertyColor); } - context->fillRect(caret, caretColor, colorSpace); + context.fillRect(caret, caretColor); #else UNUSED_PARAM(node); UNUSED_PARAM(context); @@ -1521,30 +1750,30 @@ void CaretBase::paintCaret(Node* node, GraphicsContext* context, const LayoutPoi #endif } -void FrameSelection::debugRenderer(RenderObject* r, bool selected) const +void FrameSelection::debugRenderer(RenderObject* renderer, bool selected) const { - if (r->node()->isElementNode()) { - Element* element = toElement(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()) { + if (is<Element>(*renderer->node())) { + Element& element = downcast<Element>(*renderer->node()); + fprintf(stderr, "%s%s\n", selected ? "==> " : " ", element.localName().string().utf8().data()); + } else if (is<RenderText>(*renderer)) { + RenderText& textRenderer = downcast<RenderText>(*renderer); + if (!textRenderer.textLength() || !textRenderer.firstTextBox()) { fprintf(stderr, "%s#text (empty)\n", selected ? "==> " : " "); return; } static const int max = 36; - String text = textRenderer->text(); + String text = textRenderer.text(); int textLength = text.length(); if (selected) { int offset = 0; - if (r->node() == m_selection.start().containerNode()) + if (renderer->node() == m_selection.start().containerNode()) offset = m_selection.start().computeOffsetInContainerNode(); - else if (r->node() == m_selection.end().containerNode()) + else if (renderer->node() == m_selection.end().containerNode()) offset = m_selection.end().computeOffsetInContainerNode(); int pos; - InlineTextBox* box = textRenderer->findNextInlineTextBox(offset, pos); + InlineTextBox* box = textRenderer.findNextInlineTextBox(offset, pos); text = text.substring(box->start(), box->len()); String show; @@ -1588,22 +1817,21 @@ void FrameSelection::debugRenderer(RenderObject* r, bool selected) const bool FrameSelection::contains(const LayoutPoint& point) { - Document* document = m_frame->document(); - // Treat a collapsed selection like no selection. if (!isRange()) return false; - if (!document->renderView()) + + RenderView* renderView = m_frame->contentRenderer(); + if (!renderView) return false; - HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowShadowContent); HitTestResult result(point); - document->renderView()->hitTest(request, result); + renderView->hitTest(HitTestRequest(), result); Node* innerNode = result.innerNode(); if (!innerNode || !innerNode->renderer()) return false; - VisiblePosition visiblePos(innerNode->renderer()->positionForPoint(result.localPoint())); + VisiblePosition visiblePos(innerNode->renderer()->positionForPoint(result.localPoint(), nullptr)); if (visiblePos.isNull()) return false; @@ -1653,7 +1881,7 @@ void FrameSelection::selectFrameElementInParentIfFullySelected() return; // Create compute positions before and after the element. - unsigned ownerElementNodeIndex = ownerElement->nodeIndex(); + unsigned ownerElementNodeIndex = ownerElement->computeNodeIndex(); VisiblePosition beforeOwnerElement(VisiblePosition(Position(ownerElementParent, ownerElementNodeIndex, Position::PositionIsOffsetInAnchor))); VisiblePosition afterOwnerElement(VisiblePosition(Position(ownerElementParent, ownerElementNodeIndex + 1, Position::PositionIsOffsetInAnchor), VP_UPSTREAM_IF_POSSIBLE)); @@ -1669,29 +1897,38 @@ void FrameSelection::selectAll() { Document* document = m_frame->document(); - if (document->focusedElement() && document->focusedElement()->hasTagName(selectTag)) { - HTMLSelectElement* selectElement = toHTMLSelectElement(document->focusedElement()); - if (selectElement->canSelectAll()) { - selectElement->selectAll(); + Element* focusedElement = document->focusedElement(); + if (is<HTMLSelectElement>(focusedElement)) { + HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*focusedElement); + if (selectElement.canSelectAll()) { + selectElement.selectAll(); return; } } - RefPtr<Node> root = 0; - Node* selectStartTarget = 0; - if (isContentEditable()) { + RefPtr<Node> root; + Node* selectStartTarget = nullptr; + if (m_selection.isContentEditable()) { root = highestEditableRoot(m_selection.start()); if (Node* shadowRoot = m_selection.nonBoundaryShadowTreeRootNode()) selectStartTarget = shadowRoot->shadowHost(); else selectStartTarget = root.get(); } else { - root = m_selection.nonBoundaryShadowTreeRootNode(); + if (m_selection.isNone() && focusedElement) { + if (is<HTMLTextFormControlElement>(*focusedElement)) { + downcast<HTMLTextFormControlElement>(*focusedElement).select(); + return; + } + root = focusedElement->nonBoundaryShadowTreeRootNode(); + } else + root = m_selection.nonBoundaryShadowTreeRootNode(); + if (root) selectStartTarget = root->shadowHost(); else { root = document->documentElement(); - selectStartTarget = document->body(); + selectStartTarget = document->bodyOrFrameset(); } } if (!root) @@ -1702,45 +1939,36 @@ void FrameSelection::selectAll() VisibleSelection newSelection(VisibleSelection::selectionFromContentsOfNode(root.get())); - if (shouldChangeSelection(newSelection)) - setSelection(newSelection); - - selectFrameElementInParentIfFullySelected(); - updateSelectionCachesIfSelectionIsInsideTextFormControl(UserTriggered); + if (shouldChangeSelection(newSelection)) { + AXTextStateChangeIntent intent(AXTextStateChangeTypeSelectionExtend, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityAll, false }); + setSelection(newSelection, defaultSetSelectionOptions() | FireSelectEvent, intent); + } } -bool FrameSelection::setSelectedRange(Range* range, EAffinity affinity, bool closeTyping) +bool FrameSelection::setSelectedRange(Range* range, EAffinity affinity, bool closeTyping, EUserTriggered userTriggered) { - if (!range || !range->startContainer() || !range->endContainer()) + if (!range) return false; - ASSERT(&range->startContainer()->document() == &range->endContainer()->document()); + ASSERT(&range->startContainer().document() == &range->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 - ExceptionCode ec = 0; - bool collapsed = range->collapsed(ec); - if (ec) - return false; + VisibleSelection newSelection(*range, affinity); - // FIXME: Can we provide extentAffinity? - VisiblePosition visibleStart(range->startPosition(), collapsed ? affinity : DOWNSTREAM); - VisiblePosition visibleEnd(range->endPosition(), SEL_DEFAULT_AFFINITY); #if PLATFORM(IOS) - if (range->startContainer() && visibleStart.isNull()) - return false; - if (range->endContainer() && visibleEnd.isNull()) + // FIXME: Why do we need this check only in iOS? + if (newSelection.isNone()) return false; #endif - setSelection(VisibleSelection(visibleStart, visibleEnd), ClearTypingStyle | (closeTyping ? CloseTyping : 0)); - return true; -} -bool FrameSelection::isInPasswordField() const -{ - HTMLTextFormControlElement* textControl = enclosingTextFormControl(start()); - return textControl && isHTMLInputElement(textControl) && toHTMLInputElement(textControl)->isPasswordField(); + if (userTriggered == UserTriggered) { + FrameSelection trialFrameSelection; + trialFrameSelection.setSelection(newSelection, ClearTypingStyle | (closeTyping ? CloseTyping : 0)); + + if (!shouldChangeSelection(trialFrameSelection.selection())) + return false; + } + + setSelection(newSelection, ClearTypingStyle | (closeTyping ? CloseTyping : 0)); + return true; } void FrameSelection::focusedOrActiveStateChanged() @@ -1767,17 +1995,14 @@ void FrameSelection::focusedOrActiveStateChanged() setSelectionFromNone(); setCaretVisibility(activeAndFocused ? Visible : Hidden); - // Update for caps lock state - m_frame->eventHandler().capsLockStateMayHaveChanged(); - // Because StyleResolver::checkOneSelector() and // RenderTheme::isFocused() check if the frame is active, we have to // update style and theme state that depended on those. if (Element* element = document->focusedElement()) { - element->setNeedsStyleRecalc(); + element->invalidateStyleForSubtree(); if (RenderObject* renderer = element->renderer()) if (renderer && renderer->style().hasAppearance()) - renderer->theme().stateChanged(renderer, FocusState); + renderer->theme().stateChanged(*renderer, ControlStates::FocusState); } #endif } @@ -1817,17 +2042,14 @@ void FrameSelection::updateAppearance() // Paint a block cursor instead of a caret in overtype mode unless the caret is at the end of a line (in this case // the FrameSelection will paint a blinking caret as usual). - VisiblePosition forwardPosition; - if (m_shouldShowBlockCursor && m_selection.isCaret()) { - forwardPosition = modifyExtendingForward(CharacterGranularity); - m_caretPaint = forwardPosition.isNull(); - } + VisibleSelection oldSelection = selection(); #if ENABLE(TEXT_CARET) + bool paintBlockCursor = m_shouldShowBlockCursor && m_selection.isCaret() && !isLogicalEndOfLine(m_selection.visibleEnd()); bool caretRectChangedOrCleared = recomputeCaretRect(); bool caretBrowsing = m_frame->settings().caretBrowsingEnabled(); - bool shouldBlink = caretIsVisible() && isCaret() && (isContentEditable() || caretBrowsing) && forwardPosition.isNull(); + bool shouldBlink = !paintBlockCursor && caretIsVisible() && isCaret() && (oldSelection.isContentEditable() || caretBrowsing); // If the caret moved, stop the blink timer so we can restart with a // black caret in the new location. @@ -1853,7 +2075,12 @@ void FrameSelection::updateAppearance() // Construct a new VisibleSolution, since m_selection is not necessarily valid, and the following steps // assume a valid selection. See <https://bugs.webkit.org/show_bug.cgi?id=69563> and <rdar://problem/10232866>. - VisibleSelection selection(m_selection.visibleStart(), forwardPosition.isNotNull() ? forwardPosition : m_selection.visibleEnd()); +#if ENABLE(TEXT_CARET) + VisiblePosition endVisiblePosition = paintBlockCursor ? modifyExtendingForward(CharacterGranularity) : oldSelection.visibleEnd(); + VisibleSelection selection(oldSelection.visibleStart(), endVisiblePosition); +#else + VisibleSelection selection(oldSelection.visibleStart(), oldSelection.visibleEnd()); +#endif if (!selection.isRange()) { view->clearSelection(); @@ -1877,8 +2104,11 @@ void FrameSelection::updateAppearance() // because we don't yet notify the FrameSelection of text removal. if (startPos.isNotNull() && endPos.isNotNull() && selection.visibleStart() != selection.visibleEnd()) { RenderObject* startRenderer = startPos.deprecatedNode()->renderer(); + int startOffset = startPos.deprecatedEditingOffset(); RenderObject* endRenderer = endPos.deprecatedNode()->renderer(); - view->setSelection(startRenderer, startPos.deprecatedEditingOffset(), endRenderer, endPos.deprecatedEditingOffset()); + int endOffset = endPos.deprecatedEditingOffset(); + ASSERT(startOffset >= 0 && endOffset >= 0); + view->setSelection(startRenderer, startOffset, endRenderer, endOffset); } } @@ -1887,21 +2117,22 @@ void FrameSelection::setCaretVisibility(CaretVisibility visibility) if (caretVisibility() == visibility) return; + // FIXME: We shouldn't trigger a synchronous layout here. + if (m_frame) + updateSelectionByUpdatingLayoutOrStyle(*m_frame); + #if ENABLE(TEXT_CARET) - m_frame->document()->updateLayoutIgnorePendingStylesheets(); if (m_caretPaint) { m_caretPaint = false; invalidateCaretRect(); } CaretBase::setCaretVisibility(visibility); -#else - m_frame->document()->updateStyleIfNeeded(); #endif updateAppearance(); } -void FrameSelection::caretBlinkTimerFired(Timer<FrameSelection>&) +void FrameSelection::caretBlinkTimerFired() { #if ENABLE(TEXT_CARET) ASSERT(caretIsVisible()); @@ -1914,12 +2145,6 @@ void FrameSelection::caretBlinkTimerFired(Timer<FrameSelection>&) #endif } -void FrameSelection::updateSelectionCachesIfSelectionIsInsideTextFormControl(EUserTriggered userTriggered) -{ - if (HTMLTextFormControlElement* textControl = enclosingTextFormControl(start())) - textControl->selectionChanged(userTriggered == 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) @@ -1927,9 +2152,9 @@ static bool isFrameElement(const Node* n) if (!n) return false; RenderObject* renderer = n->renderer(); - if (!renderer || !renderer->isWidget()) + if (!is<RenderWidget>(renderer)) return false; - Widget* widget = toRenderWidget(renderer)->widget(); + Widget* widget = downcast<RenderWidget>(*renderer).widget(); return widget && widget->isFrameView(); } @@ -1940,32 +2165,32 @@ void FrameSelection::setFocusedElementIfNeeded() bool caretBrowsing = m_frame->settings().caretBrowsingEnabled(); if (caretBrowsing) { - if (Element* anchor = enclosingAnchorElement(base())) { - m_frame->page()->focusController().setFocusedElement(anchor, m_frame); + if (Element* anchor = enclosingAnchorElement(m_selection.base())) { + m_frame->page()->focusController().setFocusedElement(anchor, *m_frame); return; } } - if (Element* target = rootEditableElement()) { + if (Element* target = m_selection.rootEditableElement()) { // Walk up the DOM tree to search for an element 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->isMouseFocusable() && !isFrameElement(target)) { - m_frame->page()->focusController().setFocusedElement(target, m_frame); + m_frame->page()->focusController().setFocusedElement(target, *m_frame); return; } target = target->parentOrShadowHostElement(); } - m_frame->document()->setFocusedElement(0); + m_frame->document()->setFocusedElement(nullptr); } if (caretBrowsing) - m_frame->page()->focusController().setFocusedElement(0, m_frame); + m_frame->page()->focusController().setFocusedElement(nullptr, *m_frame); } -void DragCaretController::paintDragCaret(Frame* frame, GraphicsContext* p, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const +void DragCaretController::paintDragCaret(Frame* frame, GraphicsContext& p, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const { #if ENABLE(TEXT_CARET) if (m_position.deepEquivalent().deprecatedNode()->document().frame() == frame) @@ -1994,12 +2219,12 @@ bool FrameSelection::shouldDeleteSelection(const VisibleSelection& selection) co return m_frame->editor().client()->shouldDeleteRange(selection.toNormalizedRange().get()); } -FloatRect FrameSelection::bounds(bool clipToVisibleContent) const +FloatRect FrameSelection::selectionBounds(bool clipToVisibleContent) const { if (!m_frame->document()) return LayoutRect(); - m_frame->document()->updateStyleIfNeeded(); + updateSelectionByUpdatingLayoutOrStyle(*m_frame); RenderView* root = m_frame->contentRenderer(); FrameView* view = m_frame->view(); if (!root || !view) @@ -2009,25 +2234,37 @@ FloatRect FrameSelection::bounds(bool clipToVisibleContent) const return clipToVisibleContent ? intersection(selectionRect, view->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect)) : selectionRect; } -void FrameSelection::getClippedVisibleTextRectangles(Vector<FloatRect>& rectangles) const +void FrameSelection::getClippedVisibleTextRectangles(Vector<FloatRect>& rectangles, TextRectangleHeight textRectHeight) const { RenderView* root = m_frame->contentRenderer(); if (!root) return; - FloatRect visibleContentRect = m_frame->view()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); + Vector<FloatRect> textRects; + getTextRectangles(textRects, textRectHeight); - Vector<FloatQuad> quads; - toNormalizedRange()->textQuads(quads, true); + FloatRect visibleContentRect = m_frame->view()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); - size_t size = quads.size(); - for (size_t i = 0; i < size; ++i) { - FloatRect intersectionRect = intersection(quads[i].enclosingBoundingBox(), visibleContentRect); + for (const auto& rect : textRects) { + FloatRect intersectionRect = intersection(rect, visibleContentRect); if (!intersectionRect.isEmpty()) rectangles.append(intersectionRect); } } +void FrameSelection::getTextRectangles(Vector<FloatRect>& rectangles, TextRectangleHeight textRectHeight) const +{ + RefPtr<Range> range = toNormalizedRange(); + if (!range) + return; + + Vector<FloatQuad> quads; + range->absoluteTextQuads(quads, textRectHeight == TextRectangleHeight::SelectionHeight); + + for (const auto& quad : quads) + rectangles.append(quad.boundingBox()); +} + // Scans logically forward from "start", including any child frames. static HTMLFormElement* scanForForm(Element* start) { @@ -2037,12 +2274,12 @@ static HTMLFormElement* scanForForm(Element* start) auto descendants = descendantsOfType<HTMLElement>(start->document()); for (auto it = descendants.from(*start), end = descendants.end(); it != end; ++it) { HTMLElement& element = *it; - if (isHTMLFormElement(&element)) - return toHTMLFormElement(&element); - if (isHTMLFormControlElement(element)) - return toHTMLFormControlElement(element).form(); - if (isHTMLFrameElementBase(element)) { - Document* contentDocument = toHTMLFrameElementBase(element).contentDocument(); + if (is<HTMLFormElement>(element)) + return &downcast<HTMLFormElement>(element); + if (is<HTMLFormControlElement>(element)) + return downcast<HTMLFormControlElement>(element).form(); + if (is<HTMLFrameElementBase>(element)) { + Document* contentDocument = downcast<HTMLFrameElementBase>(element).contentDocument(); if (!contentDocument) continue; if (HTMLFormElement* frameResult = scanForForm(contentDocument->documentElement())) @@ -2058,7 +2295,7 @@ HTMLFormElement* FrameSelection::currentForm() const // Start looking either at the active (first responder) node, or where the selection is. Element* start = m_frame->document()->focusedElement(); if (!start) - start = this->start().element(); + start = m_selection.start().element(); if (!start) return nullptr; @@ -2071,40 +2308,43 @@ HTMLFormElement* FrameSelection::currentForm() const return scanForForm(start); } -void FrameSelection::revealSelection(const ScrollAlignment& alignment, RevealExtentOption revealExtentOption) +void FrameSelection::revealSelection(SelectionRevealMode revealMode, const ScrollAlignment& alignment, RevealExtentOption revealExtentOption) { - LayoutRect rect; + if (revealMode == SelectionRevealMode::DoNotReveal) + return; - switch (selectionType()) { + LayoutRect rect; + bool insideFixed = false; + switch (m_selection.selectionType()) { case VisibleSelection::NoSelection: return; case VisibleSelection::CaretSelection: - rect = absoluteCaretBounds(); + rect = absoluteCaretBounds(&insideFixed); break; case VisibleSelection::RangeSelection: - rect = revealExtentOption == RevealExtent ? VisiblePosition(extent()).absoluteCaretBounds() : enclosingIntRect(bounds(false)); + rect = revealExtentOption == RevealExtent ? VisiblePosition(m_selection.extent()).absoluteCaretBounds() : enclosingIntRect(selectionBounds(false)); break; } - Position start = this->start(); + Position start = m_selection.start(); ASSERT(start.deprecatedNode()); if (start.deprecatedNode() && start.deprecatedNode()->renderer()) { #if PLATFORM(IOS) if (RenderLayer* layer = start.deprecatedNode()->renderer()->enclosingLayer()) { if (!m_scrollingSuppressCount) { layer->setAdjustForIOSCaretWhenScrolling(true); - layer->scrollRectToVisible(rect, alignment, alignment); + layer->scrollRectToVisible(revealMode, rect, insideFixed, alignment, alignment); layer->setAdjustForIOSCaretWhenScrolling(false); updateAppearance(); if (m_frame->page()) - m_frame->page()->chrome().client().notifyRevealedSelectionByScrollingFrame(m_frame); + m_frame->page()->chrome().client().notifyRevealedSelectionByScrollingFrame(*m_frame); } } #else // 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 (start.deprecatedNode()->renderer()->scrollRectToVisible(rect, alignment, alignment)) + if (start.deprecatedNode()->renderer()->scrollRectToVisible(revealMode, rect, insideFixed, alignment, alignment)) updateAppearance(); #endif } @@ -2121,15 +2361,12 @@ void FrameSelection::setSelectionFromNone() if (!isNone() || !(document->hasEditableStyle() || caretBrowsing)) return; #else - if (!document || !(isNone() || isStartOfDocument(VisiblePosition(selection().start(), selection().affinity()))) || !document->hasEditableStyle()) + if (!document || !(isNone() || isStartOfDocument(VisiblePosition(m_selection.start(), m_selection.affinity()))) || !document->hasEditableStyle()) return; #endif - Node* node = document->documentElement(); - while (node && !node->hasTagName(bodyTag)) - node = NodeTraversal::next(node); - if (node) - setSelection(VisibleSelection(firstPositionInOrBeforeNode(node), DOWNSTREAM)); + if (auto* body = document->body()) + setSelection(VisibleSelection(firstPositionInOrBeforeNode(body), DOWNSTREAM)); } bool FrameSelection::shouldChangeSelection(const VisibleSelection& newSelection) const @@ -2159,7 +2396,33 @@ void FrameSelection::setShouldShowBlockCursor(bool shouldShowBlockCursor) updateAppearance(); } -#ifndef NDEBUG +void FrameSelection::updateAppearanceAfterLayout() +{ + m_appearanceUpdateTimer.stop(); + updateAppearanceAfterLayoutOrStyleChange(); +} + +void FrameSelection::scheduleAppearanceUpdateAfterStyleChange() +{ + m_appearanceUpdateTimer.startOneShot(0_s); +} + +void FrameSelection::appearanceUpdateTimerFired() +{ + updateAppearanceAfterLayoutOrStyleChange(); +} + +void FrameSelection::updateAppearanceAfterLayoutOrStyleChange() +{ + if (auto* client = m_frame->editor().client()) + client->updateEditorStateAfterLayoutIfEditabilityChanged(); + + setCaretRectNeedsUpdate(); + updateAndRevealSelection(AXTextStateChangeIntent()); + updateDataDetectorsForSelection(); +} + +#if ENABLE(TREE_DEBUGGING) void FrameSelection::formatForDebugger(char* buffer, unsigned length) const { @@ -2179,7 +2442,7 @@ void FrameSelection::expandSelectionToElementContainingCaretSelection() RefPtr<Range> range = elementRangeContainingCaretSelection(); if (!range) return; - VisibleSelection selection(range.get(), DOWNSTREAM); + VisibleSelection selection(*range, DOWNSTREAM); setSelection(selection); } @@ -2202,7 +2465,7 @@ PassRefPtr<Range> FrameSelection::elementRangeContainingCaretSelection() const return nullptr; Position startPos = createLegacyEditingPosition(element, 0); - Position endPos = createLegacyEditingPosition(element, element->childNodeCount()); + Position endPos = createLegacyEditingPosition(element, element->countChildNodes()); VisiblePosition startVisiblePos(startPos, VP_DEFAULT_AFFINITY); VisiblePosition endVisiblePos(endPos, VP_DEFAULT_AFFINITY); @@ -2302,9 +2565,7 @@ int FrameSelection::wordOffsetInRange(const Range *range) const // FIXME: This will only work in cases where the selection remains in // the same node after it is expanded. Improve to handle more complicated // cases. - ExceptionCode ec = 0; - int result = selection.start().deprecatedEditingOffset() - range->startOffset(ec); - ASSERT(!ec); + int result = selection.start().deprecatedEditingOffset() - range->startOffset(); if (result < 0) result = 0; return result; @@ -2314,12 +2575,9 @@ bool FrameSelection::spaceFollowsWordInRange(const Range *range) const { if (!range) return false; - ExceptionCode ec = 0; - Node* node = range->endContainer(ec); - ASSERT(!ec); - int endOffset = range->endOffset(ec); - ASSERT(!ec); - VisiblePosition pos(createLegacyEditingPosition(node, endOffset), VP_DEFAULT_AFFINITY); + Node& node = range->endContainer(); + int endOffset = range->endOffset(); + VisiblePosition pos(createLegacyEditingPosition(&node, endOffset), VP_DEFAULT_AFFINITY); return isSpaceOrNewline(pos.characterAfter()); } @@ -2389,15 +2647,12 @@ PassRefPtr<Range> FrameSelection::rangeByExtendingCurrentSelection(int amount) c return rangeByAlteringCurrentSelection(AlterationExtend, amount); } -void FrameSelection::selectRangeOnElement(unsigned location, unsigned length, Node* node) +void FrameSelection::selectRangeOnElement(unsigned location, unsigned length, Node& node) { RefPtr<Range> resultRange = m_frame->document()->createRange(); - ExceptionCode ec = 0; - resultRange->setStart(node, location, ec); - ASSERT(!ec); - resultRange->setEnd(node, location + length, ec); - ASSERT(!ec); - VisibleSelection selection = VisibleSelection(resultRange.get(), SEL_DEFAULT_AFFINITY); + resultRange->setStart(node, location); + resultRange->setEnd(node, location + length); + VisibleSelection selection = VisibleSelection(*resultRange, SEL_DEFAULT_AFFINITY); setSelection(selection, true); } @@ -2433,7 +2688,7 @@ VisibleSelection FrameSelection::wordSelectionContainingCaretSelection(const Vis VisibleSelection newSelection = frameSelection.selection(); newSelection.expandUsingGranularity(WordGranularity); - frameSelection.setSelection(newSelection, frameSelection.granularity()); + frameSelection.setSelection(newSelection, defaultSetSelectionOptions(), AXTextStateChangeIntent(), AlignCursorOnScrollIfNeeded, frameSelection.granularity()); Position startPos(frameSelection.selection().start()); Position endPos(frameSelection.selection().end()); @@ -2598,7 +2853,7 @@ void FrameSelection::setCaretColor(const Color& caretColor) } -#ifndef NDEBUG +#if ENABLE(TREE_DEBUGGING) void showTree(const WebCore::FrameSelection& sel) { |