summaryrefslogtreecommitdiff
path: root/Source/WebCore/editing/FrameSelection.cpp
diff options
context:
space:
mode:
authorLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
committerLorry Tar Creator <lorry-tar-importer@lorry>2017-06-27 06:07:23 +0000
commit1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch)
tree46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/editing/FrameSelection.cpp
parent32761a6cee1d0dee366b885b7b9c777e67885688 (diff)
downloadWebKitGtk-tarball-master.tar.gz
Diffstat (limited to 'Source/WebCore/editing/FrameSelection.cpp')
-rw-r--r--Source/WebCore/editing/FrameSelection.cpp1081
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)
{