#include "config.h"
#include "core/editing/FrameSelection.h"
-#include <stdio.h>
-#include "bindings/v8/ExceptionState.h"
+#include "bindings/core/v8/ExceptionState.h"
#include "core/HTMLNames.h"
#include "core/accessibility/AXObjectCache.h"
#include "core/css/StylePropertySet.h"
#include "core/editing/TypingCommand.h"
#include "core/editing/VisibleUnits.h"
#include "core/editing/htmlediting.h"
+#include "core/events/Event.h"
#include "core/frame/LocalDOMWindow.h"
#include "core/frame/LocalFrame.h"
#include "core/html/HTMLBodyElement.h"
#include "platform/geometry/FloatQuad.h"
#include "platform/graphics/GraphicsContext.h"
#include "wtf/text/CString.h"
+#include <stdio.h>
#define EDIT_DEBUG 0
-namespace WebCore {
+namespace blink {
using namespace HTMLNames;
, m_observingVisibleSelection(false)
, m_granularity(CharacterGranularity)
, m_caretBlinkTimer(this, &FrameSelection::caretBlinkTimerFired)
- , m_absCaretBoundsDirty(true)
+ , m_caretRectDirty(true)
, m_caretPaint(true)
, m_isCaretBlinkingSuspended(false)
, m_focused(frame && frame->page() && frame->page()->focusController().focusedFrame() == frame)
return selectionRoot ? selectionRoot : m_frame->document()->documentElement();
}
-Node* FrameSelection::rootEditableElementOrTreeScopeRootNode() const
+ContainerNode* FrameSelection::rootEditableElementOrTreeScopeRootNode() const
{
Element* selectionRoot = m_selection.rootEditableElement();
if (selectionRoot)
setFocusedNodeIfNeeded();
if (!(options & DoNotUpdateAppearance)) {
- m_frame->document()->updateLayoutIgnorePendingStylesheets();
-
// Hits in compositing/overflow/do-not-paint-outline-into-composited-scrolling-contents.html
DisableCompositingQueryAsserts disabler;
- updateAppearance();
+ updateAppearance(ResetCaretBlink);
}
// Always clear the x position used for vertical arrow navigation.
}
notifyAccessibilityForSelectionChange();
+ notifyCompositorForSelectionChange();
m_frame->domWindow()->enqueueDocumentEvent(Event::create(EventTypeNames::selectionchange));
}
if (base == m_selection.base() && extent == m_selection.extent() && start == m_selection.start() && end == m_selection.end())
return;
VisibleSelection newSelection;
- newSelection.setWithoutValidation(base, extent);
+ if (m_selection.isBaseFirst())
+ newSelection.setWithoutValidation(start, end);
+ else
+ newSelection.setWithoutValidation(end, start);
setSelection(newSelection, DoNotSetFocus);
}
TextDirection FrameSelection::directionOfEnclosingBlock()
{
- return WebCore::directionOfEnclosingBlock(m_selection.extent());
+ return blink::directionOfEnclosingBlock(m_selection.extent());
}
TextDirection FrameSelection::directionOfSelection()
setSelection(VisibleSelection(m_selection.base(), pos.deepEquivalent(), pos.affinity(), selectionHasDirection), CloseTyping | ClearTypingStyle | userTriggered);
}
-RenderObject* FrameSelection::caretRenderer() const
+RenderBlock* FrameSelection::caretRenderer() const
{
return CaretBase::caretRenderer(m_selection.start().deprecatedNode());
}
return selection.isCaret() && !selection.start().isOrphan() && !selection.end().isOrphan();
}
-LayoutRect FrameSelection::localCaretRect()
+static bool isTextFormControl(const VisibleSelection& selection)
{
- 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();
+ return enclosingTextFormControl(selection.start());
}
IntRect FrameSelection::absoluteCaretBounds()
{
- recomputeCaretRect();
- return m_absCaretBounds;
+ ASSERT(m_frame->document()->lifecycle().state() != DocumentLifecycle::InPaintInvalidation);
+ m_frame->document()->updateLayoutIgnorePendingStylesheets();
+ if (!isNonOrphanedCaret(m_selection)) {
+ clearCaretRect();
+ } else {
+ if (isTextFormControl(m_selection))
+ updateCaretRect(m_frame->document(), PositionWithAffinity(m_selection.start().isCandidate() ? m_selection.start() : Position(), m_selection.affinity()));
+ else
+ updateCaretRect(m_frame->document(), VisiblePosition(m_selection.start(), m_selection.affinity()));
+ }
+ return absoluteBoundsForLocalRect(m_selection.start().deprecatedNode(), localCaretRectWithoutUpdate());
}
-bool FrameSelection::recomputeCaretRect()
+static LayoutRect localCaretRect(const VisibleSelection& m_selection, const PositionWithAffinity& caretPosition, RenderObject*& renderer)
{
- if (!shouldUpdateCaretRect())
- return false;
+ renderer = nullptr;
+ if (!isNonOrphanedCaret(m_selection))
+ return LayoutRect();
- if (!m_frame || !m_frame->document()->view())
- return false;
-
- LayoutRect oldRect = localCaretRectWithoutUpdate();
- LayoutRect newRect = localCaretRect();
- if (oldRect == newRect && !m_absCaretBoundsDirty)
- return false;
-
- IntRect oldAbsCaretBounds = m_absCaretBounds;
- m_absCaretBounds = absoluteBoundsForLocalRect(m_selection.start().deprecatedNode(), localCaretRectWithoutUpdate());
- m_absCaretBoundsDirty = false;
-
- if (oldAbsCaretBounds == m_absCaretBounds)
- return false;
-
- if (RenderView* view = m_frame->document()->renderView()) {
- if (m_previousCaretNode && shouldRepaintCaret(view, m_previousCaretNode->isContentEditable()))
- repaintCaretForLocalRect(m_previousCaretNode.get(), oldRect);
- Node* node = m_selection.start().deprecatedNode();
- m_previousCaretNode = node;
- if (shouldRepaintCaret(view, isContentEditable()))
- repaintCaretForLocalRect(node, newRect);
- }
+ return localCaretRectOfPosition(caretPosition, renderer);
+}
- return true;
+static void invalidateLocalCaretRect(RenderObject* renderer, const LayoutRect& caretRect)
+{
+ // FIXME: Need to over-paint 1 pixel to workaround some rounding problems.
+ // https://bugs.webkit.org/show_bug.cgi?id=108283
+ LayoutRect inflatedRect = caretRect;
+ inflatedRect.inflate(1);
+ renderer->invalidatePaintRectangle(inflatedRect);
}
void FrameSelection::invalidateCaretRect()
{
- if (!isCaret())
+ if (!m_caretRectDirty)
+ return;
+ m_caretRectDirty = false;
+
+ RenderObject* renderer = nullptr;
+ LayoutRect newRect = localCaretRect(m_selection, PositionWithAffinity(m_selection.start(), m_selection.affinity()), renderer);
+ Node* newNode = renderer ? renderer->node() : nullptr;
+
+ if (!m_caretBlinkTimer.isActive() && newNode == m_previousCaretNode && newRect == m_previousCaretRect)
return;
- CaretBase::invalidateCaretRect(m_selection.start().deprecatedNode(), recomputeCaretRect());
+ RenderView* view = m_frame->document()->renderView();
+ if (m_previousCaretNode && shouldRepaintCaret(view, m_previousCaretNode->isContentEditable()))
+ invalidateLocalCaretRect(m_previousCaretNode->renderer(), m_previousCaretRect);
+ if (newNode && shouldRepaintCaret(view, newNode->isContentEditable()))
+ invalidateLocalCaretRect(newNode->renderer(), newRect);
+
+ m_previousCaretNode = newNode;
+ m_previousCaretRect = newRect;
}
void FrameSelection::paintCaret(GraphicsContext* context, const LayoutPoint& paintOffset, const LayoutRect& clipRect)
{
- if (m_selection.isCaret() && m_caretPaint)
+ if (m_selection.isCaret() && m_caretPaint) {
+ updateCaretRect(m_frame->document(), m_selection.visibleStart());
CaretBase::paintCaret(m_selection.start().deprecatedNode(), context, paintOffset, clipRect);
+ }
}
bool FrameSelection::contains(const LayoutPoint& point)
if (!document->renderView())
return false;
- HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent);
+ HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active);
HitTestResult result(point);
document->renderView()->hitTest(request, result);
Node* innerNode = result.innerNode();
// Get to the <iframe> or <frame> (or even <object>) element in the parent frame.
// FIXME: Doesn't work for OOPI.
- Element* ownerElement = m_frame->deprecatedLocalOwner();
+ HTMLFrameOwnerElement* ownerElement = m_frame->deprecatedLocalOwner();
if (!ownerElement)
return;
ContainerNode* ownerElementParent = ownerElement->parentNode();
return;
// This method's purpose is it to make it easier to select iframes (in order to delete them). Don't do anything if the iframe isn't deletable.
- if (!ownerElementParent->rendererIsEditable())
+ if (!ownerElementParent->hasEditableStyle())
return;
// Create compute positions before and after the element.
}
}
+void FrameSelection::notifyCompositorForSelectionChange()
+{
+ if (!RuntimeEnabledFeatures::compositedSelectionUpdatesEnabled())
+ return;
+
+ scheduleVisualUpdate();
+}
+
void FrameSelection::focusedOrActiveStateChanged()
{
bool activeAndFocused = isFocusedAndActive();
// RenderObject::selectionForegroundColor() check if the frame is active,
// we have to update places those colors were painted.
if (RenderView* view = document->renderView())
- view->repaintSelection();
+ view->invalidatePaintForSelection();
// Caret appears in the active frame.
if (activeAndFocused)
return frame->editor().lastEditCommand() && frame->editor().lastEditCommand()->shouldStopCaretBlinking();
}
-void FrameSelection::updateAppearance()
+void FrameSelection::updateAppearance(ResetCaretBlinkOption option)
{
// 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();
- }
+ bool paintBlockCursor = m_shouldShowBlockCursor && m_selection.isCaret() && !isLogicalEndOfLine(m_selection.visibleEnd());
+
+ bool shouldBlink = !paintBlockCursor && shouldBlinkCaret();
- bool caretRectChangedOrCleared = recomputeCaretRect();
- bool shouldBlink = shouldBlinkCaret() && forwardPosition.isNull();
+ bool willNeedCaretRectUpdate = false;
// If the caret moved, stop the blink timer so we can restart with a
// black caret in the new location.
- if (caretRectChangedOrCleared || !shouldBlink || shouldStopBlinkingDueToTypingCommand(m_frame)) {
+ if (option == ResetCaretBlink || !shouldBlink || shouldStopBlinkingDueToTypingCommand(m_frame)) {
m_caretBlinkTimer.stop();
- if (!shouldBlink && m_caretPaint) {
- m_caretPaint = false;
- invalidateCaretRect();
- }
+
+ m_caretPaint = false;
+ willNeedCaretRectUpdate = true;
}
// Start blinking with a black caret. Be sure not to restart if we're
if (double blinkInterval = RenderTheme::theme().caretBlinkInterval())
m_caretBlinkTimer.startRepeating(blinkInterval, FROM_HERE);
- if (!m_caretPaint) {
- m_caretPaint = true;
- invalidateCaretRect();
- }
+ m_caretPaint = true;
+ willNeedCaretRectUpdate = true;
}
+ if (willNeedCaretRectUpdate)
+ setCaretRectNeedsUpdate();
+
RenderView* view = m_frame->contentRenderer();
if (!view)
return;
// 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());
+
+ VisibleSelection selection;
+ if (isTextFormControl(m_selection)) {
+ Position endPosition = paintBlockCursor ? m_selection.extent().next() : m_selection.end();
+ selection.setWithoutValidation(m_selection.start(), endPosition);
+ } else {
+ VisiblePosition endVisiblePosition = paintBlockCursor ? modifyExtendingForward(CharacterGranularity) : m_selection.visibleEnd();
+ selection = VisibleSelection(m_selection.visibleStart(), endVisiblePosition);
+ }
if (!selection.isRange()) {
view->clearSelection();
if (caretVisibility() == visibility)
return;
- m_frame->document()->updateLayoutIgnorePendingStylesheets();
- if (m_caretPaint) {
- m_caretPaint = false;
- invalidateCaretRect();
- }
CaretBase::setCaretVisibility(visibility);
updateAppearance();
if (m_frame->settings() && m_frame->settings()->caretBrowsingEnabled())
return false;
- Node* root = rootEditableElement();
+ Element* root = rootEditableElement();
if (!root)
return false;
{
ASSERT(caretIsVisible());
ASSERT(isCaret());
- bool caretPaint = m_caretPaint;
- if (isCaretBlinkingSuspended() && caretPaint)
+ if (isCaretBlinkingSuspended() && m_caretPaint)
return;
- m_caretPaint = !caretPaint;
- invalidateCaretRect();
+ m_caretPaint = !m_caretPaint;
+ setCaretRectNeedsUpdate();
}
void FrameSelection::notifyRendererOfSelectionChange(EUserTriggered userTriggered)
{
- m_frame->document()->updateRenderTreeIfNeeded();
-
if (HTMLTextFormControlElement* textControl = enclosingTextFormControl(start()))
textControl->selectionChanged(userTriggered == UserTriggered);
}
Document* document = m_frame->document();
bool caretBrowsing = m_frame->settings() && m_frame->settings()->caretBrowsingEnabled();
- if (!isNone() || !(document->rendererIsEditable() || caretBrowsing))
+ if (!isNone() || !(document->hasEditableStyle() || caretBrowsing))
return;
- Node* node = document->documentElement();
- if (!node)
+ Element* documentElement = document->documentElement();
+ if (!documentElement)
return;
- Node* body = isHTMLBodyElement(*node) ? node : Traversal<HTMLBodyElement>::next(*node);
- if (body)
+ if (HTMLBodyElement* body = Traversal<HTMLBodyElement>::firstChild(*documentElement))
setSelection(VisibleSelection(firstPositionInOrBeforeNode(body), DOWNSTREAM));
}
VisibleSelection::ChangeObserver::trace(visitor);
}
+void FrameSelection::setCaretRectNeedsUpdate()
+{
+ m_caretRectDirty = true;
+
+ scheduleVisualUpdate();
+}
+
+void FrameSelection::scheduleVisualUpdate() const
+{
+ if (!m_frame)
+ return;
+ if (Page* page = m_frame->page())
+ page->animator().scheduleVisualUpdate();
+}
+
}
#ifndef NDEBUG
-void showTree(const WebCore::FrameSelection& sel)
+void showTree(const blink::FrameSelection& sel)
{
sel.showTreeForThis();
}
-void showTree(const WebCore::FrameSelection* sel)
+void showTree(const blink::FrameSelection* sel)
{
if (sel)
sel->showTreeForThis();