mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-07 09:54:42 +00:00
Bug 1886506 - Fix caret paint invalidation issues. r=sefeng
The caret position is in the DOM, and sometimes can get out of sync. While that is an issue, it's not new: The code before the regressing bug papered over it on a pre-pass before entering DL building. Instead, deal with it using MarkFramesForDisplay (just like we mark the caret frame itself, which has the same issue), and invalidate the old frame more precisely by tracking it in nsCaret directly. Also, add missing invalidation in PresShell::SetCaret, where the caret might change and the old caret might not be invalidated properly. Differential Revision: https://phabricator.services.mozilla.com/D205369
This commit is contained in:
parent
30c2d20f64
commit
169a5d539e
@ -2238,9 +2238,16 @@ PresShell::GetAccessibleCaretEventHub() const {
|
||||
return eventHub.forget();
|
||||
}
|
||||
|
||||
void PresShell::SetCaret(nsCaret* aNewCaret) { mCaret = aNewCaret; }
|
||||
void PresShell::SetCaret(nsCaret* aNewCaret) {
|
||||
if (mCaret == aNewCaret) {
|
||||
return;
|
||||
}
|
||||
mCaret->SchedulePaint();
|
||||
mCaret = aNewCaret;
|
||||
aNewCaret->SchedulePaint();
|
||||
}
|
||||
|
||||
void PresShell::RestoreCaret() { mCaret = mOriginalCaret; }
|
||||
void PresShell::RestoreCaret() { SetCaret(mOriginalCaret); }
|
||||
|
||||
NS_IMETHODIMP PresShell::SetCaretEnabled(bool aInEnable) {
|
||||
bool oldEnabled = mCaretEnabled;
|
||||
@ -5647,7 +5654,9 @@ void PresShell::SetRenderingState(const RenderingState& aState) {
|
||||
}
|
||||
|
||||
void PresShell::SynthesizeMouseMove(bool aFromScroll) {
|
||||
if (!StaticPrefs::layout_reflow_synthMouseMove()) return;
|
||||
if (!StaticPrefs::layout_reflow_synthMouseMove()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mPaintingSuppressed || !mIsActive || !mPresContext) {
|
||||
return;
|
||||
@ -5660,8 +5669,9 @@ void PresShell::SynthesizeMouseMove(bool aFromScroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE))
|
||||
if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!mSynthMouseMoveEvent.IsPending()) {
|
||||
RefPtr<nsSynthMouseMoveEvent> ev =
|
||||
|
@ -392,6 +392,10 @@ nsIFrame* nsCaret::GetGeometry(const Selection* aSelection, nsRect* aRect) {
|
||||
}
|
||||
|
||||
void nsCaret::SchedulePaint() {
|
||||
if (mLastPaintedFrame) {
|
||||
mLastPaintedFrame->SchedulePaint();
|
||||
mLastPaintedFrame = nullptr;
|
||||
}
|
||||
auto data = GetFrameAndOffset(mCaretPosition);
|
||||
if (!data.mFrame) {
|
||||
return;
|
||||
@ -416,9 +420,6 @@ void nsCaret::UpdateCaretPositionFromSelectionIfNeeded() {
|
||||
if (newPos == mCaretPosition) {
|
||||
return;
|
||||
}
|
||||
// First SchedulePaint call invalidates the old position. Second one the new
|
||||
// one.
|
||||
SchedulePaint();
|
||||
mCaretPosition = newPos;
|
||||
SchedulePaint();
|
||||
}
|
||||
@ -427,7 +428,6 @@ void nsCaret::SetCaretPosition(nsINode* aNode, int32_t aOffset) {
|
||||
// Schedule a paint with the old position to invalidate.
|
||||
mFixedCaretPosition = !!aNode;
|
||||
if (mFixedCaretPosition) {
|
||||
SchedulePaint();
|
||||
mCaretPosition = {aNode, aOffset};
|
||||
SchedulePaint();
|
||||
} else {
|
||||
|
@ -14,7 +14,6 @@
|
||||
#include "nsCoord.h"
|
||||
#include "nsIFrame.h"
|
||||
#include "nsISelectionListener.h"
|
||||
#include "nsIWeakReferenceUtils.h"
|
||||
#include "nsPoint.h"
|
||||
#include "nsRect.h"
|
||||
|
||||
@ -114,6 +113,9 @@ class nsCaret final : public nsISelectionListener {
|
||||
*/
|
||||
void SchedulePaint();
|
||||
|
||||
nsIFrame* GetLastPaintedFrame() { return mLastPaintedFrame; }
|
||||
void SetLastPaintedFrame(nsIFrame* aFrame) { mLastPaintedFrame = aFrame; }
|
||||
|
||||
/**
|
||||
* Returns a frame to paint in, and the bounds of the painted caret
|
||||
* relative to that frame.
|
||||
@ -122,6 +124,7 @@ class nsCaret final : public nsISelectionListener {
|
||||
* off).
|
||||
*/
|
||||
nsIFrame* GetPaintGeometry(nsRect* aRect);
|
||||
|
||||
/**
|
||||
* Same as the overload above, but returns the caret and hook rects
|
||||
* separately, and also computes the color if requested.
|
||||
@ -221,6 +224,9 @@ class nsCaret final : public nsISelectionListener {
|
||||
|
||||
CaretPosition mCaretPosition;
|
||||
|
||||
// The last frame we painted the caret in.
|
||||
WeakFrame mLastPaintedFrame;
|
||||
|
||||
/**
|
||||
* mBlinkCount is used to control the number of times to blink the caret
|
||||
* before stopping the blink. This is reset each time we reset the
|
||||
|
@ -1104,28 +1104,36 @@ void nsDisplayListBuilder::EnterPresShell(const nsIFrame* aReferenceFrame,
|
||||
return;
|
||||
}
|
||||
|
||||
RefPtr<nsCaret> caret = state->mPresShell->GetCaret();
|
||||
// This code run for each pres shell and caret->GetPaintGeometry
|
||||
// will return nullptr for invisible caret. So only one caret
|
||||
// can be painted at a time.
|
||||
state->mCaretFrame = caret->GetPaintGeometry(&mCaretRect);
|
||||
state->mCaretFrame = [&]() -> nsIFrame* {
|
||||
RefPtr<nsCaret> caret = state->mPresShell->GetCaret();
|
||||
nsIFrame* currentCaret = caret->GetPaintGeometry(&mCaretRect);
|
||||
|
||||
// Check if the display root for the caret matches the display
|
||||
// root that we're painting, and only use it if it matches. Likely
|
||||
// we only need this for popup.
|
||||
if (state->mCaretFrame &&
|
||||
nsLayoutUtils::GetDisplayRootFrame(state->mCaretFrame) !=
|
||||
nsLayoutUtils::GetDisplayRootFrame(aReferenceFrame)) {
|
||||
state->mCaretFrame = nullptr;
|
||||
}
|
||||
if (auto* oldCaret = caret->GetLastPaintedFrame();
|
||||
oldCaret && oldCaret != currentCaret) {
|
||||
// Make sure to rebuild the old caret if it has changed.
|
||||
MarkFrameForDisplay(oldCaret, aReferenceFrame);
|
||||
}
|
||||
|
||||
// Caret frames add visual area to their frame, but we don't update the
|
||||
// overflow area. Use flags to make sure we build display items for that frame
|
||||
// instead.
|
||||
if (state->mCaretFrame) {
|
||||
MOZ_ASSERT(state->mCaretFrame->PresShell() == state->mPresShell);
|
||||
MarkFrameForDisplay(state->mCaretFrame, aReferenceFrame);
|
||||
}
|
||||
if (!currentCaret) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Check if the display root for the caret matches the display root that
|
||||
// we're painting, and only use it if it matches. Likely we only need this
|
||||
// for popup.
|
||||
if (nsLayoutUtils::GetDisplayRootFrame(currentCaret) !=
|
||||
nsLayoutUtils::GetDisplayRootFrame(aReferenceFrame)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Caret frames add visual area to their frame, but we don't update the
|
||||
// overflow area. Use flags to make sure we build display items for that
|
||||
// frame instead.
|
||||
MOZ_ASSERT(currentCaret->PresShell() == state->mPresShell);
|
||||
MarkFrameForDisplay(currentCaret, aReferenceFrame);
|
||||
caret->SetLastPaintedFrame(currentCaret);
|
||||
return currentCaret;
|
||||
}();
|
||||
}
|
||||
|
||||
// A non-blank paint is a paint that does not just contain the canvas
|
||||
|
@ -0,0 +1,21 @@
|
||||
<html class="test-wait reftest-wait">
|
||||
<style>
|
||||
* {
|
||||
backdrop-filter: hue-rotate(0deg);
|
||||
offset: path('M 72 1 h 0 v 90') 0px 0rad;
|
||||
mask-image: url(#x)
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
function func_01() {
|
||||
document.execCommand("justifyCenter", false)
|
||||
document.getSelection().collapse(a)
|
||||
setTimeout(func_01, 0)
|
||||
// Ensure that we've painted at least once.
|
||||
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||
document.documentElement.classList = "";
|
||||
}));
|
||||
}
|
||||
document.addEventListener("DOMContentLoaded", func_01)
|
||||
</script>
|
||||
<meter id="a" contenteditable="true">AA</meter>
|
Loading…
Reference in New Issue
Block a user