RenderTheme does not need to be per-page
[WebKit-https.git] / Source / WebCore / editing / FrameSelection.cpp
index 9ee00f2..8bb00db 100644 (file)
 #include "CharacterData.h"
 #include "DeleteSelectionCommand.h"
 #include "Document.h"
+#include "Editing.h"
 #include "Editor.h"
 #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 "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"
@@ -63,7 +64,6 @@
 #include "StyleProperties.h"
 #include "TypingCommand.h"
 #include "VisibleUnits.h"
-#include "htmlediting.h"
 #include <stdio.h>
 #include <wtf/text/CString.h>
 
@@ -76,8 +76,6 @@
 #include "RenderStyle.h"
 #endif
 
-#define EDIT_DEBUG 0
-
 namespace WebCore {
 
 using namespace HTMLNames;
@@ -113,6 +111,7 @@ FrameSelection::FrameSelection(Frame* frame)
     , m_xPosForVerticalArrowNavigation(NoXPosForVerticalArrowNavigation())
     , m_granularity(CharacterGranularity)
     , m_caretBlinkTimer(*this, &FrameSelection::caretBlinkTimerFired)
+    , m_caretInsidePositionFixed(false)
     , m_absCaretBoundsDirty(true)
     , m_caretPaint(true)
     , m_isCaretBlinkingSuspended(false)
@@ -124,7 +123,6 @@ FrameSelection::FrameSelection(Frame* frame)
 #if PLATFORM(IOS)
     , m_updateAppearanceEnabled(false)
     , m_caretBlinks(true)
-    , m_scrollingSuppressCount(0)
 #endif
 {
     if (shouldAlwaysUseDirectionalSelection(m_frame))
@@ -166,13 +164,13 @@ void FrameSelection::moveTo(const Position &base, const Position &extent, EAffin
     setSelection(VisibleSelection(base, extent, affinity, selectionHasDirection), defaultSetSelectionOptions(userTriggered));
 }
 
-void FrameSelection::moveWithoutValidationTo(const Position& base, const Position& extent, bool selectionHasDirection, bool shouldSetFocus)
+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 intent(AXTextStateChangeTypeSelectionMove, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown });
-    setSelection(newSelection, defaultSetSelectionOptions() | (shouldSetFocus ? 0 : DoNotSetFocus), intent);
+    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)
@@ -181,7 +179,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();
@@ -259,7 +257,7 @@ void FrameSelection::setSelectionByMouseIfDifferent(const VisibleSelection& pass
     
     AXTextStateChangeIntent intent;
     if (AXObjectCache::accessibilityEnabled() && newSelection.isCaret())
-        intent = AXTextStateChangeIntent(AXTextStateChangeTypeSelectionMove, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown });
+        intent = AXTextStateChangeIntent(AXTextStateChangeTypeSelectionMove, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown, false });
     else
         intent = AXTextStateChangeIntent();
     setSelection(newSelection, defaultSetSelectionOptions() | FireSelectEvent, intent, AlignCursorOnScrollIfNeeded, granularity);
@@ -288,7 +286,7 @@ bool FrameSelection::setSelectionWithoutUpdatingAppearance(const VisibleSelectio
                 // 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.isNonOrphanedCaretOrRange())
+                if (newSelectionFrame->hasOneRef() && m_selection.isNoneOrOrphaned())
                     clear();
                 return false;
             }
@@ -304,13 +302,17 @@ bool FrameSelection::setSelectionWithoutUpdatingAppearance(const VisibleSelectio
         clearTypingStyle();
 
     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 (oldSelection == newSelection)
+    if (!didMutateSelection)
         return false;
 
     setCaretRectNeedsUpdate();
@@ -385,7 +387,7 @@ void FrameSelection::updateAndRevealSelection(const AXTextStateChangeIntent& int
         else
             alignment = m_alwaysAlignCursorOnScrollWhenRevealingSelection ? ScrollAlignment::alignTopAlways : ScrollAlignment::alignToEdgeIfNeeded;
 
-        revealSelection(alignment, RevealExtent);
+        revealSelection(SelectionRevealMode::Reveal, alignment, RevealExtent);
     }
 
     notifyAccessibilityForSelectionChange(intent);
@@ -417,7 +419,7 @@ static bool removingNodeRemovesPosition(Node& node, const Position& position)
 
 void DragCaretController::nodeWillBeRemoved(Node& node)
 {
-    if (!hasCaret() || !node.inDocument())
+    if (!hasCaret() || !node.isConnected())
         return;
 
     if (!removingNodeRemovesPosition(node, m_position.deepEquivalent()))
@@ -433,7 +435,7 @@ 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.inDocument())
+    if (isNone() || !node.isConnected())
         return;
 
     respondToNodeModification(node, removingNodeRemovesPosition(node, m_selection.base()), removingNodeRemovesPosition(node, m_selection.extent()),
@@ -472,14 +474,16 @@ 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;
+            }
         }
     }
 
@@ -520,7 +524,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();
@@ -552,8 +556,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.
@@ -620,14 +624,14 @@ void FrameSelection::willBeModified(EAlteration alter, SelectionDirection direct
 
 VisiblePosition FrameSelection::positionForPlatform(bool isGetStart) const
 {
-    if (m_frame && (m_frame->settings().editingBehaviorType() == EditingMacBehavior || m_frame->settings().editingBehaviorType() == EditingIOSBehavior))
-        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
@@ -760,8 +764,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:
@@ -771,11 +777,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: {
         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:
@@ -785,10 +794,10 @@ 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;
     case DocumentGranularity:
         ASSERT_NOT_REACHED();
@@ -797,8 +806,26 @@ VisiblePosition FrameSelection::modifyMovingRight(TextGranularity granularity)
     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) {
@@ -806,45 +833,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;
     case DocumentGranularity:
         ASSERT_NOT_REACHED();
         break;
     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;
 }
 
@@ -944,8 +985,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:
@@ -955,11 +998,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:
@@ -969,10 +1015,10 @@ 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;
     case DocumentGranularity:
         ASSERT_NOT_REACHED();
@@ -981,39 +1027,57 @@ VisiblePosition FrameSelection::modifyMovingLeft(TextGranularity granularity)
     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
@@ -1023,6 +1087,20 @@ VisiblePosition FrameSelection::modifyMovingBackward(TextGranularity granularity
         ASSERT_NOT_REACHED();
         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;
 }
 
@@ -1099,6 +1177,68 @@ AXTextStateChangeIntent FrameSelection::textSelectionIntent(EAlteration alter, S
     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)
 {
     if (userTriggered == UserTriggered) {
@@ -1116,13 +1256,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;
@@ -1130,11 +1271,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;
@@ -1142,10 +1283,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;
 
@@ -1359,7 +1505,7 @@ void FrameSelection::prepareForDestruction()
         view->clearSelection();
 
     setSelectionWithoutUpdatingAppearance(VisibleSelection(), defaultSetSelectionOptions(), AlignCursorOnScrollIfNeeded, CharacterGranularity);
-    m_previousCaretNode.clear();
+    m_previousCaretNode = nullptr;
 }
 
 void FrameSelection::setStart(const VisiblePosition &pos, EUserTriggered trigger)
@@ -1431,12 +1577,14 @@ static bool isNonOrphanedCaret(const VisibleSelection& selection)
     return selection.isCaret() && !selection.start().isOrphan() && !selection.end().isOrphan();
 }
 
-IntRect FrameSelection::absoluteCaretBounds()
+IntRect FrameSelection::absoluteCaretBounds(bool* insideFixed)
 {
     if (!m_frame)
         return IntRect();
     updateSelectionByUpdatingLayoutOrStyle(*m_frame);
     recomputeCaretRect();
+    if (insideFixed)
+        *insideFixed = m_caretInsidePositionFixed;
     return m_absCaretBounds;
 }
 
@@ -1478,7 +1626,9 @@ bool FrameSelection::recomputeCaretRect()
         return false;
 
     IntRect oldAbsCaretBounds = m_absCaretBounds;
-    m_absCaretBounds = absoluteBoundsForLocalCaretRect(rendererForCaretPainting(caretNode.get()), newRect);
+    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());
@@ -1542,20 +1692,20 @@ void CaretBase::invalidateCaretRect(Node* node, bool caretRectChanged)
     }
 }
 
-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);
 }
 
 #if ENABLE(TEXT_CARET)
-static inline bool disappearsIntoBackground(Color foreground, Color background)
+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
+void CaretBase::paintCaret(Node* node, GraphicsContext& context, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const
 {
 #if ENABLE(TEXT_CARET)
     if (m_caretVisibility == Hidden)
@@ -1570,7 +1720,6 @@ void CaretBase::paintCaret(Node* node, GraphicsContext* context, const LayoutPoi
         return;
 
     Color caretColor = Color::black;
-    ColorSpace colorSpace = ColorSpaceDeviceRGB;
     Element* element = is<Element>(*node) ? downcast<Element>(node) : node->parentElement();
     Element* rootEditableElement = node->rootEditableElement();
 
@@ -1583,17 +1732,14 @@ void CaretBase::paintCaret(Node* node, GraphicsContext* context, const LayoutPoi
             auto elementBGColor = elementStyle.visitedDependentColor(CSSPropertyBackgroundColor);
             if (disappearsIntoBackground(elementBGColor, rootEditableBGColor)) {
                 caretColor = rootEditableStyle.visitedDependentColor(CSSPropertyColor);
-                colorSpace = rootEditableStyle.colorSpace();
                 setToRootEditableElement = true;
             }
         }
-        if (!setToRootEditableElement) {
+        if (!setToRootEditableElement)
             caretColor = element->renderer()->style().visitedDependentColor(CSSPropertyColor);
-            colorSpace = element->renderer()->style().colorSpace();
-        }
     }
 
-    context->fillRect(caret, caretColor, colorSpace);
+    context.fillRect(caret, caretColor);
 #else
     UNUSED_PARAM(node);
     UNUSED_PARAM(context);
@@ -1667,7 +1813,7 @@ void FrameSelection::debugRenderer(RenderObject* renderer, bool selected) const
     }
 }
 
-bool FrameSelection::contains(const LayoutPoint& point)
+bool FrameSelection::contains(const LayoutPoint& point) const
 {
     // Treat a collapsed selection like no selection.
     if (!isRange())
@@ -1792,25 +1938,33 @@ void FrameSelection::selectAll()
     VisibleSelection newSelection(VisibleSelection::selectionFromContentsOfNode(root.get()));
 
     if (shouldChangeSelection(newSelection)) {
-        AXTextStateChangeIntent intent(AXTextStateChangeTypeSelectionExtend, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityAll });
+        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());
 
     VisibleSelection newSelection(*range, affinity);
 
 #if PLATFORM(IOS)
     // FIXME: Why do we need this check only in iOS?
-    if (range->startContainer() && range->endContainer() && newSelection.isNone())
+    if (newSelection.isNone())
         return false;
 #endif
 
+    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;
 }
@@ -1843,7 +1997,7 @@ void FrameSelection::focusedOrActiveStateChanged()
     // 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, ControlStates::FocusState);
@@ -1903,7 +2057,7 @@ void FrameSelection::updateAppearance()
     // Start blinking with a black caret. Be sure not to restart if we're
     // already blinking in the right location.
     if (shouldBlink && !m_caretBlinkTimer.isActive()) {
-        if (double blinkInterval = m_frame->page()->theme().caretBlinkInterval())
+        if (Seconds blinkInterval = RenderTheme::singleton().caretBlinkInterval())
             m_caretBlinkTimer.startRepeating(blinkInterval);
 
         if (!m_caretPaint) {
@@ -1948,8 +2102,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);
     }
 }
 
@@ -1958,7 +2115,7 @@ void FrameSelection::setCaretVisibility(CaretVisibility visibility)
     if (caretVisibility() == visibility)
         return;
 
-    // FIXME: We shouldn't trigger a synchrnously layout here.
+    // FIXME: We shouldn't trigger a synchronous layout here.
     if (m_frame)
         updateSelectionByUpdatingLayoutOrStyle(*m_frame);
 
@@ -2007,7 +2164,7 @@ void FrameSelection::setFocusedElementIfNeeded()
     bool caretBrowsing = m_frame->settings().caretBrowsingEnabled();
     if (caretBrowsing) {
         if (Element* anchor = enclosingAnchorElement(m_selection.base())) {
-            m_frame->page()->focusController().setFocusedElement(anchor, m_frame);
+            m_frame->page()->focusController().setFocusedElement(anchor, *m_frame);
             return;
         }
     }
@@ -2019,19 +2176,19 @@ void FrameSelection::setFocusedElementIfNeeded()
             // 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)
@@ -2044,10 +2201,10 @@ void DragCaretController::paintDragCaret(Frame* frame, GraphicsContext* p, const
 #endif
 }
 
-PassRefPtr<MutableStyleProperties> FrameSelection::copyTypingStyle() const
+RefPtr<MutableStyleProperties> FrameSelection::copyTypingStyle() const
 {
     if (!m_typingStyle || !m_typingStyle->style())
-        return 0;
+        return nullptr;
     return m_typingStyle->style()->mutableCopy();
 }
 
@@ -2075,25 +2232,37 @@ FloatRect FrameSelection::selectionBounds(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)
 {
@@ -2137,15 +2306,18 @@ 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;
 
+    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(m_selection.extent()).absoluteCaretBounds() : enclosingIntRect(selectionBounds(false));
@@ -2159,18 +2331,18 @@ void FrameSelection::revealSelection(const ScrollAlignment& alignment, RevealExt
         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
     }
@@ -2224,6 +2396,9 @@ void FrameSelection::setShouldShowBlockCursor(bool shouldShowBlockCursor)
 
 void FrameSelection::updateAppearanceAfterLayout()
 {
+    if (auto* client = m_frame->editor().client())
+        client->updateEditorStateAfterLayoutIfEditabilityChanged();
+
     setCaretRectNeedsUpdate();
     updateAndRevealSelection(AXTextStateChangeIntent());
     updateDataDetectorsForSelection();
@@ -2253,7 +2428,7 @@ void FrameSelection::expandSelectionToElementContainingCaretSelection()
     setSelection(selection);
 }
 
-PassRefPtr<Range> FrameSelection::elementRangeContainingCaretSelection() const
+RefPtr<Range> FrameSelection::elementRangeContainingCaretSelection() const
 {
     if (m_selection.isNone())
         return nullptr;
@@ -2292,7 +2467,7 @@ void FrameSelection::expandSelectionToWordContainingCaretSelection()
         setSelection(selection);
 }
 
-PassRefPtr<Range> FrameSelection::wordRangeContainingCaretSelection()
+RefPtr<Range> FrameSelection::wordRangeContainingCaretSelection()
 {
     return wordSelectionContainingCaretSelection(m_selection).toNormalizedRange();
 }
@@ -2372,9 +2547,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;
@@ -2384,12 +2557,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());
 }
 
@@ -2449,24 +2619,21 @@ bool FrameSelection::selectionAtWordStart() const
     return result;
 }
 
-PassRefPtr<Range> FrameSelection::rangeByMovingCurrentSelection(int amount) const
+RefPtr<Range> FrameSelection::rangeByMovingCurrentSelection(int amount) const
 {
     return rangeByAlteringCurrentSelection(AlterationMove, amount);
 }
 
-PassRefPtr<Range> FrameSelection::rangeByExtendingCurrentSelection(int amount) const
+RefPtr<Range> FrameSelection::rangeByExtendingCurrentSelection(int amount) const
 {
     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);
+    resultRange->setStart(node, location);
+    resultRange->setEnd(node, location + length);
     VisibleSelection selection = VisibleSelection(*resultRange, SEL_DEFAULT_AFFINITY);
     setSelection(selection, true);
 }
@@ -2618,7 +2785,7 @@ bool FrameSelection::actualSelectionAtSentenceStart(const VisibleSelection& sel)
     return result;
 }
 
-PassRefPtr<Range> FrameSelection::rangeByAlteringCurrentSelection(EAlteration alteration, int amount) const
+RefPtr<Range> FrameSelection::rangeByAlteringCurrentSelection(EAlteration alteration, int amount) const
 {
     if (m_selection.isNone())
         return nullptr;