Commit 2e1f1cbb authored by yoichio@chromium.org's avatar yoichio@chromium.org

FrameSelection::updateApperance for caret should not cause layout.

We do not need update caret rectangle synchronously because we just need 
updated caret in painting.
Thus this CL delays computing new caret rectangle.

Source/core/editing/FrameSelection.cpp:
- Add ResetCaretBlinkOption to updateAppearance(). The option is set 
to ResetCaretBlink when it is called from setSelection.
If ResetCaretBlink, we reset caret blinking.
If we need to repaint caret, set |m_caretRectDirty| flag.
If |m_caretRectDirty| is set, we call PageAnimator::scheduleVisualUpdate to 
trigger repaint.
For range, create VisibleSelection without validation like 
HTMLTextFormControlElement::setSelectionRange.
- Add the setCaretRectNeedsUpdate function to just set |m_caretRectDirty| flag
 and call new scheduleVisualUpdate function, which calls
PageAnimator::scheduleVisualUpdate.
- FrameSelection::invalidateCaretRect does
 1. Checks the dirty flag.
 2. Gets new caret rectangle and node which has the caret renderer.
 3. if caret is changed, invalidate the new caret rect and the old caret rect.
 4. Caches the new caret rect and node. Sets dirty flag off.
This function is similar to old recomputeCaretRect(deleted).
- Delete unused updateRenderTreeIfneeded() from notifyRenderOfSelectionChange().
In old days, notifyRenderOfSelectionChange() used renderer.
- Refactor FrameSelection::absoluteCaretBounds() to call updateCaretRect
 directly. Since we update layout, ASSERT that document's lifecycle is not in
InPaintInvalidation.


Source/core/editing/Caret.cpp:
- Remove document->updateRenderTreeIfNeeded() from CaretBase::updateCaretRect
 and call it from caller.
- In FrameSelection::paintCaret, call without updateRenderTreeIfNeeded because
the tree must be updated on the painting phase.
Source/core/frame/FrameView.cpp:
- FrameView::invalidateTreeIfNeeded calls FrameSelection::InvalidateCaretRect
to invalidate caret rect for each frame.

Source/core/html/HTMLTextFormControlElementTest.cpp:
- Add new test to confirm setSelectionRange with another start/end not cause 
layout.
- Delete FrameSelectionLocalCaretRectDoesNotCauseLayout because localCaretRect 
is removed.

Layouttests:
- This cl reduces redundant RenderObject::invalidatePaintRectangle calls so we 
need to delete lines from expected.txt but result images are still same.
 
BUG=382809, 397303, 403317
TEST=Layouttests/fast/forms/textarea-scrollbar.html, textarea-scrolled-type.html

Review URL: https://codereview.chromium.org/431983005

git-svn-id: svn://svn.chromium.org/blink/trunk@180269 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 03437977
......@@ -1303,4 +1303,8 @@ crbug.com/401902 [ Win7 Debug ] virtual/gpu/fast/canvas/canvas-lose-restore-goo
crbug.com/402379 [ Win7 Debug ] storage/indexeddb/cursor-continue-validity.html [ Pass Slow ]
crbug.com/402379 [ Win7 Debug ] storage/indexeddb/mozilla/indexes.html [ Pass Slow ]
crbug.com/382809 editing/selection/repaint-rect-for-vertical-writing-mode-with-positioned-root.html [ NeedsRebaseline ]
crbug.com/382809 fast/repaint/caret-outside-block.html [ NeedsRebaseline ]
crbug.com/382809 fast/repaint/inline-outline-repaint.html [ NeedsRebaseline ]
crbug.com/402801 [ XP Release ] http/tests/misc/location-replace-crossdomain.html [ Pass Slow ]
......@@ -6,7 +6,6 @@
"contentsOpaque": true,
"drawsContent": true,
"repaintRects": [
[357, 199, 3, 22],
[357, 199, 3, 22]
]
}
......
......@@ -70,10 +70,12 @@ void DragCaretController::setCaretPosition(const VisiblePosition& position)
invalidateCaretRect(node);
document = &node->document();
}
if (m_position.isNull() || m_position.isOrphan())
if (m_position.isNull() || m_position.isOrphan()) {
clearCaretRect();
else
} else {
document->updateRenderTreeIfNeeded();
updateCaretRect(document, m_position);
}
}
static bool removingNodeRemovesPosition(Node& node, const Position& position)
......@@ -134,7 +136,6 @@ RenderBlock* CaretBase::caretRenderer(Node* node)
bool CaretBase::updateCaretRect(Document* document, const PositionWithAffinity& caretPosition)
{
document->updateRenderTreeIfNeeded();
m_caretLocalRect = LayoutRect();
m_caretRectNeedsUpdate = false;
......
......@@ -96,7 +96,7 @@ FrameSelection::FrameSelection(LocalFrame* frame)
, 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)
......@@ -272,11 +272,9 @@ void FrameSelection::setSelection(const VisibleSelection& newSelection, SetSelec
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.
......@@ -1230,70 +1228,68 @@ static bool isTextFormControl(const VisibleSelection& selection)
return enclosingTextFormControl(selection.start());
}
LayoutRect FrameSelection::localCaretRect()
IntRect FrameSelection::absoluteCaretBounds()
{
if (shouldUpdateCaretRect()) {
if (!isNonOrphanedCaret(m_selection))
clearCaretRect();
else if (isTextFormControl(m_selection))
m_absCaretBoundsDirty |= updateCaretRect(m_frame->document(), PositionWithAffinity(m_selection.start().isCandidate() ? m_selection.start() : Position(), m_selection.affinity()));
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
m_absCaretBoundsDirty |= updateCaretRect(m_frame->document(), VisiblePosition(m_selection.start(), m_selection.affinity()));
updateCaretRect(m_frame->document(), VisiblePosition(m_selection.start(), m_selection.affinity()));
}
return localCaretRectWithoutUpdate();
return absoluteBoundsForLocalRect(m_selection.start().deprecatedNode(), localCaretRectWithoutUpdate());
}
IntRect FrameSelection::absoluteCaretBounds()
static LayoutRect localCaretRect(const VisibleSelection& m_selection, const PositionWithAffinity& caretPosition, RenderObject*& renderer)
{
recomputeCaretRect();
return m_absCaretBounds;
renderer = nullptr;
if (!isNonOrphanedCaret(m_selection))
return LayoutRect();
return localCaretRectOfPosition(caretPosition, renderer);
}
bool FrameSelection::recomputeCaretRect()
static void invalidateLocalCaretRect(RenderObject* renderer, const LayoutRect& caretRect)
{
if (!shouldUpdateCaretRect())
return false;
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 true;
// 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;
CaretBase::invalidateCaretRect(m_selection.start().deprecatedNode(), recomputeCaretRect());
if (!m_caretBlinkTimer.isActive() && newNode == m_previousCaretNode && newRect == m_previousCaretRect)
return;
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)
......@@ -1465,8 +1461,7 @@ void FrameSelection::notifyCompositorForSelectionChange()
if (!RuntimeEnabledFeatures::compositedSelectionUpdatesEnabled())
return;
if (Page* page = m_frame->page())
page->animator().scheduleVisualUpdate();
scheduleVisualUpdate();
}
void FrameSelection::focusedOrActiveStateChanged()
......@@ -1540,23 +1535,23 @@ inline static bool shouldStopBlinkingDueToTypingCommand(LocalFrame* frame)
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).
bool paintBlockCursor = m_shouldShowBlockCursor && m_selection.isCaret() && !isLogicalEndOfLine(m_selection.visibleEnd());
bool caretRectChangedOrCleared = recomputeCaretRect();
bool shouldBlink = !paintBlockCursor && shouldBlinkCaret();
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
......@@ -1565,20 +1560,28 @@ void FrameSelection::updateAppearance()
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>.
VisiblePosition endVisiblePosition = paintBlockCursor ? modifyExtendingForward(CharacterGranularity) : m_selection.visibleEnd();
VisibleSelection selection(m_selection.visibleStart(), endVisiblePosition);
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();
......@@ -1613,11 +1616,6 @@ void FrameSelection::setCaretVisibility(CaretVisibility visibility)
if (caretVisibility() == visibility)
return;
m_frame->document()->updateLayoutIgnorePendingStylesheets();
if (m_caretPaint) {
m_caretPaint = false;
invalidateCaretRect();
}
CaretBase::setCaretVisibility(visibility);
updateAppearance();
......@@ -1649,13 +1647,11 @@ void FrameSelection::caretBlinkTimerFired(Timer<FrameSelection>*)
if (isCaretBlinkingSuspended() && m_caretPaint)
return;
m_caretPaint = !m_caretPaint;
invalidateCaretRect();
setCaretRectNeedsUpdate();
}
void FrameSelection::notifyRendererOfSelectionChange(EUserTriggered userTriggered)
{
m_frame->document()->updateRenderTreeIfNeeded();
if (HTMLTextFormControlElement* textControl = enclosingTextFormControl(start()))
textControl->selectionChanged(userTriggered == UserTriggered);
}
......@@ -1915,6 +1911,21 @@ void FrameSelection::trace(Visitor* visitor)
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
......
......@@ -89,6 +89,10 @@ public:
NonDirectional,
Directional
};
enum ResetCaretBlinkOption {
None,
ResetCaretBlink
};
Element* rootEditableElement() const { return m_selection.rootEditableElement(); }
Element* rootEditableElementOrDocumentElement() const;
......@@ -139,13 +143,8 @@ public:
// Return the renderer that is responsible for painting the caret (in the selection start node)
RenderBlock* caretRenderer() const;
// Caret rect local to the caret's renderer
LayoutRect localCaretRect();
LayoutRect localCaretRectWithoutUpdateForTesting() const { return CaretBase::localCaretRectWithoutUpdate(); }
// Bounds of (possibly transformed) caret in absolute coords
IntRect absoluteCaretBounds();
void setCaretRectNeedsUpdate() { CaretBase::setCaretRectNeedsUpdate(); }
void didChangeFocus();
void willBeModified(EAlteration, SelectionDirection);
......@@ -168,8 +167,11 @@ public:
void didMergeTextNodes(const Text& oldNode, unsigned offset);
void didSplitTextNode(const Text& oldNode);
void updateAppearance(ResetCaretBlinkOption = None);
void setCaretVisible(bool caretIsVisible) { setCaretVisibility(caretIsVisible ? Visible : Hidden); }
bool recomputeCaretRect();
bool isCaretBoundsDirty() const { return m_caretRectDirty; }
void setCaretRectNeedsUpdate();
void scheduleVisualUpdate() const;
void invalidateCaretRect();
void paintCaret(GraphicsContext*, const LayoutPoint&, const LayoutRect& clipRect);
......@@ -183,9 +185,6 @@ public:
bool isFocusedAndActive() const;
void pageActivationChanged();
// Painting.
void updateAppearance();
void updateSecureKeyboardEntryIfActive();
#ifndef NDEBUG
......@@ -280,13 +279,13 @@ private:
RefPtrWillBeMember<Range> m_logicalRange;
RefPtrWillBeMember<Node> m_previousCaretNode; // The last node which painted the caret. Retained for clearing the old caret when it moves.
LayoutRect m_previousCaretRect;
RefPtrWillBeMember<EditingStyle> m_typingStyle;
Timer<FrameSelection> m_caretBlinkTimer;
// The painted bounds of the caret in absolute coordinates
IntRect m_absCaretBounds;
bool m_absCaretBoundsDirty : 1;
bool m_caretRectDirty : 1;
bool m_caretPaint : 1;
bool m_isCaretBlinkingSuspended : 1;
bool m_focused : 1;
......
......@@ -995,6 +995,9 @@ void FrameView::invalidateTreeIfNeeded()
#ifndef NDEBUG
renderView()->assertSubtreeClearedPaintInvalidationState();
#endif
if (m_frame->selection().isCaretBoundsDirty())
m_frame->selection().invalidateCaretRect();
}
DocumentLifecycle& FrameView::lifecycle() const
......
......@@ -20,6 +20,7 @@
#include "core/page/SpellCheckerClient.h"
#include "core/rendering/RenderTreeAsText.h"
#include "core/testing/DummyPageHolder.h"
#include "core/testing/UnitTestHelpers.h"
#include "wtf/OwnPtr.h"
#include <gtest/gtest.h>
......@@ -107,35 +108,29 @@ TEST_F(HTMLTextFormControlElementTest, SetSelectionRange)
EXPECT_EQ(3, textControl().selectionEnd());
}
TEST_F(HTMLTextFormControlElementTest, FrameSelectionLocalCaretRectDoesNotCauseLayout)
{
input().focus();
input().setValue("Hello, input form.");
FrameSelection& frameSelection = document().frame()->selection();
frameSelection.setCaretRectNeedsUpdate();
forceLayoutFlag();
int startLayoutCount = layoutCount();
frameSelection.localCaretRect();
EXPECT_EQ(startLayoutCount, layoutCount());
}
TEST_F(HTMLTextFormControlElementTest, SetSameSelectionRangeDoesNotCauseLayout)
TEST_F(HTMLTextFormControlElementTest, SetSelectionRangeDoesNotCauseLayout)
{
input().focus();
input().setValue("Hello, input form.");
input().setSelectionRange(1, 1);
FrameSelection& frameSelection = document().frame()->selection();
LayoutRect oldCaretRect = frameSelection.localCaretRectWithoutUpdateForTesting();
EXPECT_FALSE(oldCaretRect.isEmpty());
forceLayoutFlag();
LayoutRect oldCaretRect = frameSelection.absoluteCaretBounds();
EXPECT_FALSE(oldCaretRect.isEmpty());
int startLayoutCount = layoutCount();
input().setSelectionRange(1, 1);
EXPECT_EQ(startLayoutCount, layoutCount());
LayoutRect newCaretRect = frameSelection.localCaretRectWithoutUpdateForTesting();
LayoutRect newCaretRect = frameSelection.absoluteCaretBounds();
EXPECT_EQ(oldCaretRect, newCaretRect);
forceLayoutFlag();
oldCaretRect = frameSelection.absoluteCaretBounds();
EXPECT_FALSE(oldCaretRect.isEmpty());
startLayoutCount = layoutCount();
input().setSelectionRange(2, 2);
EXPECT_EQ(startLayoutCount, layoutCount());
newCaretRect = frameSelection.absoluteCaretBounds();
EXPECT_NE(oldCaretRect, newCaretRect);
}
typedef Position (*PositionFunction)(const Position&);
......@@ -164,7 +159,7 @@ void testBoundary(HTMLDocument& document, HTMLTextFormControlElement& textContro
for (unsigned i = 0; i < textControl.innerEditorValue().length(); i++) {
textControl.setSelectionRange(i, i);
Position position = document.frame()->selection().start();
SCOPED_TRACE(testing::Message() << "offset " << position.deprecatedEditingOffset() << " of " << nodePositionAsStringForTesting(position.deprecatedNode()).ascii().data());
SCOPED_TRACE(::testing::Message() << "offset " << position.deprecatedEditingOffset() << " of " << nodePositionAsStringForTesting(position.deprecatedNode()).ascii().data());
{
SCOPED_TRACE("HTMLTextFormControlElement::startOfWord");
testFunctionEquivalence(position, HTMLTextFormControlElement::startOfWord, startOfWord);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment