RenderTheme does not need to be per-page
[WebKit-https.git] / Source / WebCore / editing / FrameSelection.cpp
index be58f00..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 "Event.h"
-#include "EventHandler.h"
-#include "ExceptionCode.h"
+#include "EventNames.h"
 #include "FloatQuad.h"
 #include "FocusController.h"
 #include "Frame.h"
@@ -47,7 +47,6 @@
 #include "HTMLFormElement.h"
 #include "HTMLFrameElement.h"
 #include "HTMLIFrameElement.h"
-#include "HTMLInputElement.h"
 #include "HTMLNames.h"
 #include "HTMLSelectElement.h"
 #include "HitTestRequest.h"
@@ -65,7 +64,6 @@
 #include "StyleProperties.h"
 #include "TypingCommand.h"
 #include "VisibleUnits.h"
-#include "htmlediting.h"
 #include <stdio.h>
 #include <wtf/text/CString.h>
 
@@ -78,8 +76,6 @@
 #include "RenderStyle.h"
 #endif
 
-#define EDIT_DEBUG 0
-
 namespace WebCore {
 
 using namespace HTMLNames;
@@ -115,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)
@@ -126,7 +123,6 @@ FrameSelection::FrameSelection(Frame* frame)
 #if PLATFORM(IOS)
     , m_updateAppearanceEnabled(false)
     , m_caretBlinks(true)
-    , m_scrollingSuppressCount(0)
 #endif
 {
     if (shouldAlwaysUseDirectionalSelection(m_frame))
@@ -290,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;
             }
@@ -306,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();
@@ -387,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);
@@ -419,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()))
@@ -435,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()),
@@ -474,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;
+            }
         }
     }
 
@@ -522,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();
@@ -1575,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;
 }
 
@@ -1622,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());
@@ -1693,7 +1699,7 @@ void FrameSelection::paintCaret(GraphicsContext& context, const LayoutPoint& pai
 }
 
 #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;
 }
@@ -1807,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())
@@ -1937,7 +1943,7 @@ void FrameSelection::selectAll()
     }
 }
 
-bool FrameSelection::setSelectedRange(Range* range, EAffinity affinity, bool closeTyping)
+bool FrameSelection::setSelectedRange(Range* range, EAffinity affinity, bool closeTyping, EUserTriggered userTriggered)
 {
     if (!range)
         return false;
@@ -1951,6 +1957,14 @@ bool FrameSelection::setSelectedRange(Range* range, EAffinity affinity, bool clo
         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;
 }
@@ -1983,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);
@@ -2043,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) {
@@ -2088,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);
     }
 }
 
@@ -2098,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);
 
@@ -2147,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;
         }
     }
@@ -2159,16 +2176,16 @@ 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
@@ -2184,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();
 }
 
@@ -2289,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));
@@ -2311,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
     }
@@ -2376,6 +2396,9 @@ void FrameSelection::setShouldShowBlockCursor(bool shouldShowBlockCursor)
 
 void FrameSelection::updateAppearanceAfterLayout()
 {
+    if (auto* client = m_frame->editor().client())
+        client->updateEditorStateAfterLayoutIfEditabilityChanged();
+
     setCaretRectNeedsUpdate();
     updateAndRevealSelection(AXTextStateChangeIntent());
     updateDataDetectorsForSelection();
@@ -2405,7 +2428,7 @@ void FrameSelection::expandSelectionToElementContainingCaretSelection()
     setSelection(selection);
 }
 
-PassRefPtr<Range> FrameSelection::elementRangeContainingCaretSelection() const
+RefPtr<Range> FrameSelection::elementRangeContainingCaretSelection() const
 {
     if (m_selection.isNone())
         return nullptr;
@@ -2444,7 +2467,7 @@ void FrameSelection::expandSelectionToWordContainingCaretSelection()
         setSelection(selection);
 }
 
-PassRefPtr<Range> FrameSelection::wordRangeContainingCaretSelection()
+RefPtr<Range> FrameSelection::wordRangeContainingCaretSelection()
 {
     return wordSelectionContainingCaretSelection(m_selection).toNormalizedRange();
 }
@@ -2596,12 +2619,12 @@ 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);
 }
@@ -2609,11 +2632,8 @@ PassRefPtr<Range> FrameSelection::rangeByExtendingCurrentSelection(int amount) c
 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);
 }
@@ -2765,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;