Bug 1663475 - Paint caret geometry from the containing block. r=mats

This matches other browsers and shouldn't be too terrible, though I'm
not quite a fan of it.

Differential Revision: https://phabricator.services.mozilla.com/D89483
This commit is contained in:
Emilio Cobos Álvarez 2020-10-06 00:06:30 +00:00
Родитель 253ba9aba0
Коммит 9d6f081a11
9 изменённых файлов: 184 добавлений и 32 удалений

Просмотреть файл

@ -19,6 +19,7 @@
#include "nsIFrame.h"
#include "nsIScrollableFrame.h"
#include "nsIContent.h"
#include "nsIFrameInlines.h"
#include "nsLayoutUtils.h"
#include "nsPresContext.h"
#include "nsBlockFrame.h"
@ -448,7 +449,27 @@ void nsCaret::CheckSelectionLanguageChange() {
}
}
nsIFrame* nsCaret::GetPaintGeometry(nsRect* aRect) {
// This ensures that the caret is not affected by clips on inlines and so forth.
[[nodiscard]] static nsIFrame* MapToContainingBlock(nsIFrame* aFrame,
nsRect* aCaretRect,
nsRect* aHookRect) {
if (aFrame->IsBlockOutside() || aFrame->IsBlockFrameOrSubclass()) {
return aFrame;
}
nsIFrame* containingBlock = aFrame->GetContainingBlock();
if (!containingBlock) {
return aFrame;
}
*aCaretRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, *aCaretRect,
containingBlock);
*aHookRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, *aHookRect,
containingBlock);
return containingBlock;
}
nsIFrame* nsCaret::GetPaintGeometry(nsRect* aCaretRect, nsRect* aHookRect,
nscolor* aCaretColor) {
// Return null if we should not be visible.
if (!IsVisible() || !mIsBlinkOn) {
return nullptr;
@ -491,43 +512,44 @@ nsIFrame* nsCaret::GetPaintGeometry(nsRect* aRect) {
return nullptr;
}
if (aCaretColor) {
*aCaretColor = frame->GetCaretColorAt(frameOffset);
}
ComputeCaretRects(frame, frameOffset, aCaretRect, aHookRect);
return MapToContainingBlock(frame, aCaretRect, aHookRect);
}
nsIFrame* nsCaret::GetPaintGeometry(nsRect* aRect) {
nsRect caretRect;
nsRect hookRect;
ComputeCaretRects(frame, frameOffset, &caretRect, &hookRect);
nsIFrame* frame = GetPaintGeometry(&caretRect, &hookRect);
aRect->UnionRect(caretRect, hookRect);
return frame;
}
nsIFrame* nsCaret::GetFrame(int32_t* aContentOffset) {
return GetFrameAndOffset(GetSelection(), mOverrideContent, mOverrideOffset,
aContentOffset);
}
void nsCaret::PaintCaret(DrawTarget& aDrawTarget, nsIFrame* aForFrame,
const nsPoint& aOffset) {
int32_t contentOffset;
nsIFrame* frame = GetFrame(&contentOffset);
nsRect caretRect;
nsRect hookRect;
nscolor color;
nsIFrame* frame = GetPaintGeometry(&caretRect, &hookRect, &color);
MOZ_ASSERT(frame == aForFrame, "We're referring different frame");
if (!frame) {
return;
}
NS_ASSERTION(frame == aForFrame, "We're referring different frame");
int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
nsRect caretRect;
nsRect hookRect;
ComputeCaretRects(frame, contentOffset, &caretRect, &hookRect);
Rect devPxCaretRect = NSRectToSnappedRect(caretRect + aOffset,
appUnitsPerDevPixel, aDrawTarget);
Rect devPxHookRect =
NSRectToSnappedRect(hookRect + aOffset, appUnitsPerDevPixel, aDrawTarget);
ColorPattern color(ToDeviceColor(frame->GetCaretColorAt(contentOffset)));
aDrawTarget.FillRect(devPxCaretRect, color);
ColorPattern pattern(ToDeviceColor(color));
aDrawTarget.FillRect(devPxCaretRect, pattern);
if (!hookRect.IsEmpty()) {
aDrawTarget.FillRect(devPxHookRect, color);
aDrawTarget.FillRect(devPxHookRect, pattern);
}
}

Просмотреть файл

@ -143,6 +143,12 @@ 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.
*/
nsIFrame* GetPaintGeometry(nsRect* aCaretRect, nsRect* aHookRect,
nscolor* aCaretColor = nullptr);
/**
* A simple wrapper around GetGeometry. Does not take any caret state into
* account other than the current selection.
@ -194,10 +200,6 @@ class nsCaret final : public nsISelectionListener {
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
nsIFrame* GetFrame(int32_t* aContentOffset);
void ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
nsRect* aCaretRect, nsRect* aHookRect);
protected:
static void CaretBlinkCallback(nsITimer* aTimer, void* aClosure);
@ -212,6 +214,8 @@ class nsCaret final : public nsISelectionListener {
};
static Metrics ComputeMetrics(nsIFrame* aFrame, int32_t aOffset,
nscoord aCaretHeight);
void ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
nsRect* aCaretRect, nsRect* aHookRect);
// Returns true if we should not draw the caret because of XUL menu popups.
// The caret should be hidden if:

Просмотреть файл

@ -0,0 +1,28 @@
<!doctype html>
<html class="reftest-wait">
<title>Caret is correctly painted over inline with clip</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<style>
div {
font: 16px/1 monospace;
outline: 2px solid blue;
caret-color: black;
}
span {
color: transparent;
}
</style>
<div contenteditable spellcheck="no">
<span>ab</span>c<span>de</span>
</div>
<script>
SimpleTest.waitForFocus(function() {
document.querySelector('[contenteditable]').focus();
requestAnimationFrame(function() {
// Position the caret between "a" and "b".
synthesizeKey("KEY_ArrowRight");
document.documentElement.removeAttribute("class");
});
});
</script>

Просмотреть файл

@ -0,0 +1,32 @@
<!doctype html>
<html class="reftest-wait">
<title>Caret is correctly painted over inline with clip</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<style>
div {
font: 16px/1 monospace;
caret-color: black;
}
div:focus {
outline: 2px solid blue;
}
span {
/* This should only leave the "c" letter visible, but the caret should
still be visible when between "a" and "b" */
clip-path: inset(0 2ch);
}
</style>
<div contenteditable spellcheck="no">
<span>abcde</span>
</div>
<script>
SimpleTest.waitForFocus(function() {
document.querySelector('[contenteditable]').focus();
requestAnimationFrame(function() {
// Position the caret between "a" and "b".
synthesizeKey("KEY_ArrowRight");
document.documentElement.removeAttribute("class");
});
});
</script>

Просмотреть файл

@ -0,0 +1,29 @@
<!doctype html>
<html class="reftest-wait">
<title>Caret is correctly painted over inline with clip</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<style>
span[contenteditable] {
font: 16px/1 monospace;
caret-color: black;
display: inline-block;
outline: 2px solid blue;
}
span > span {
color: transparent;
}
</style>
<span contenteditable spellcheck="no">
<span>ab</span>c<span>de</span>
</span>
<script>
SimpleTest.waitForFocus(function() {
document.querySelector('[contenteditable]').focus();
requestAnimationFrame(function() {
// Position the caret between "a" and "b".
synthesizeKey("KEY_ArrowRight");
document.documentElement.removeAttribute("class");
});
});
</script>

Просмотреть файл

@ -0,0 +1,33 @@
<!doctype html>
<html class="reftest-wait">
<title>Caret is correctly painted over inline with clip</title>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<style>
span[contenteditable] {
font: 16px/1 monospace;
caret-color: black;
display: inline-block;
}
span[contenteditable]:focus {
outline: 2px solid blue;
}
span > span {
/* This should only leave the "c" letter visible, but the caret should
still be visible when between "a" and "b" */
clip-path: inset(0 2ch);
}
</style>
<span contenteditable spellcheck="no">
<span>abcde</span>
</span>
<script>
SimpleTest.waitForFocus(function() {
document.querySelector('[contenteditable]').focus();
requestAnimationFrame(function() {
// Position the caret between "a" and "b".
synthesizeKey("KEY_ArrowRight");
document.documentElement.removeAttribute("class");
});
});
</script>

Просмотреть файл

@ -375,6 +375,10 @@ support-files =
bug1637476-2-ref.html
bug1637476-3.html
bug1637476-3-ref.html
bug1663475-1.html
bug1663475-1-ref.html
bug1663475-2.html
bug1663475-2-ref.html
image_rgrg-256x256.png
input-invalid-ref.html
input-maxlength-invalid-change.html

Просмотреть файл

@ -274,6 +274,8 @@ var tests = [
[ 'bug1637476-1.html' , 'bug1637476-1-ref.html' ] ,
[ 'bug1637476-2.html' , 'bug1637476-2-ref.html' ] ,
[ 'bug1637476-3.html' , 'bug1637476-3-ref.html' ] ,
[ 'bug1663475-1.html' , 'bug1663475-1-ref.html' ] ,
[ 'bug1663475-2.html' , 'bug1663475-2-ref.html' ] ,
function() {SpecialPowers.pushPrefEnv({'clear': [['layout.accessiblecaret.enabled_on_touch']]}, nextTest);} ,
function() {SpecialPowers.pushPrefEnv({'set': [['accessibility.browsewithcaret', true]]}, nextTest);} ,
[ 'bug1529492-1.html' , 'bug1529492-1-ref.html' ] ,

Просмотреть файл

@ -4935,20 +4935,18 @@ bool nsDisplayCaret::CreateWebRenderCommands(
mozilla::layers::RenderRootStateManager* aManager,
nsDisplayListBuilder* aDisplayListBuilder) {
using namespace mozilla::layers;
int32_t contentOffset;
nsIFrame* frame = mCaret->GetFrame(&contentOffset);
nsRect caretRect;
nsRect hookRect;
nscolor caretColor;
nsIFrame* frame =
mCaret->GetPaintGeometry(&caretRect, &hookRect, &caretColor);
MOZ_ASSERT(frame == mFrame, "We're referring different frame");
if (!frame) {
return true;
}
NS_ASSERTION(frame == mFrame, "We're referring different frame");
int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
nsRect caretRect;
nsRect hookRect;
mCaret->ComputeCaretRects(frame, contentOffset, &caretRect, &hookRect);
gfx::DeviceColor color = ToDeviceColor(frame->GetCaretColorAt(contentOffset));
gfx::DeviceColor color = ToDeviceColor(caretColor);
LayoutDeviceRect devCaretRect = LayoutDeviceRect::FromAppUnits(
caretRect + ToReferenceFrame(), appUnitsPerDevPixel);
LayoutDeviceRect devHookRect = LayoutDeviceRect::FromAppUnits(