Caret should respect text background color
History
I fixed a caret color issue in WebKit in 2013, but I found out there was a regression, which made me work this issue again and finally fixed it. o/ It was a bit hard for me because I had not worked on WebKit since Chromium forked from the WebKit project in 2013.
The source code has been changed and I almost forgot how to deal with layout test errors. My WebKit debugging skill is also rusty, but I tried to remind how to work on a patch by reading my own documents and old ccmmits.
Change the code
Here is an overview:
When I first fixed this issue, I just updated one line of code as follows:
+++ b/trunk/Source/WebCore/editing/FrameSelection.cpp
@@ -1470,5 +1470,6 @@
Color caretColor = Color::black;
ColorSpace colorSpace = ColorSpaceDeviceRGB;
- Element* element = node->rootEditableElement();
+ Element* element = node->isElementNode() ? toElement(node) : node->parentElement();
+
if (element && element->renderer()) {
caretColor = element->renderer()->style()->visitedDependentColor(CSSPropertyColor);
My patch allowed WebKit to get the caret color from the element containing the text, not the root editable element.
void CaretBase::paintCaret(Node* node, GraphicsContext* context, const LayoutPoint& paintOffset, const LayoutRect& clipRect) const
{
#if ENABLE(TEXT_CARET)
if (m_caretVisibility == Hidden)
return;
LayoutRect drawingRect = localCaretRectWithoutUpdate();
RenderObject* renderer = caretRenderer(node);
if (renderer && renderer->isBox())
toRenderBox(renderer)->flipForWritingMode(drawingRect);
drawingRect.moveBy(roundedIntPoint(paintOffset));
LayoutRect caret = intersection(drawingRect, clipRect);
if (caret.isEmpty())
return;
Color caretColor = Color::black;
ColorSpace colorSpace = ColorSpaceDeviceRGB;
Element* element = node->isElementNode() ? toElement(node) : node->parentElement();
if (element && element->renderer()) {
caretColor = element->renderer()->style()->visitedDependentColor(CSSPropertyColor);
colorSpace = element->renderer()->style()->colorSpace();
}
context->fillRect(caret, caretColor, colorSpace);
#else
UNUSED_PARAM(node);
UNUSED_PARAM(context);
UNUSED_PARAM(paintOffset);
UNUSED_PARAM(clipRect);
#endif
}
Index: trunk/Source/WebCore/editing/FrameSelection.cpp
===================================================================
--- a/trunk/Source/WebCore/editing/FrameSelection.cpp
After 8 years, the mainline code has been changed as follows:
Color CaretBase::computeCaretColor(const RenderStyle& elementStyle, const Node* node)
{
// On iOS, we want to fall back to the tintColor, and only override if CSS has explicitly specified a custom color.
#if PLATFORM(IOS_FAMILY) && !PLATFORM(MACCATALYST)
UNUSED_PARAM(node);
return elementStyle.caretColor();
#else
RefPtr parentElement = node ? node->parentElement() : nullptr;
auto* parentStyle = parentElement && parentElement->renderer() ? &parentElement->renderer()->style() : nullptr;
// CSS value "auto" is treated as an invalid color.
if (!elementStyle.caretColor().isValid() && parentStyle) {
auto parentBackgroundColor = parentStyle->visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);
auto elementBackgroundColor = elementStyle.visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);
auto disappearsIntoBackground = blendSourceOver(parentBackgroundColor, elementBackgroundColor) == parentBackgroundColor;
if (disappearsIntoBackground)
return parentStyle->visitedDependentColorWithColorFilter(CSSPropertyCaretColor);
}
return elementStyle.visitedDependentColorWithColorFilter(CSSPropertyCaretColor);
#endif
}
CaretBase::computeCaretColor() is separated from CaretBase::patinCaret(). C++ 11 features and smart pointer are used (https://webkit.org/blog/3172/webkit-and-cxx11/)
Here is my change:
Index: trunk/Source/WebCore/editing/FrameSelection.cpp
===================================================================
--- a/trunk/Source/WebCore/editing/FrameSelection.cpp
+++ b/trunk/Source/WebCore/editing/FrameSelection.cpp
@@ -1791,13 +1791,13 @@
return elementStyle.caretColor();
#else
- auto* rootEditableElement = node ? node->rootEditableElement() : nullptr;
- auto* rootEditableStyle = rootEditableElement && rootEditableElement->renderer() ? &rootEditableElement->renderer()->style() : nullptr;
+ RefPtr parentElement = node ? node->parentElement() : nullptr;
+ auto* parentStyle = parentElement && parentElement->renderer() ? &parentElement->renderer()->style() : nullptr;
// CSS value "auto" is treated as an invalid color.
- if (!elementStyle.caretColor().isValid() && rootEditableStyle) {
- auto rootEditableBackgroundColor = rootEditableStyle->visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);
+ if (!elementStyle.caretColor().isValid() && parentStyle) {
+ auto parentBackgroundColor = parentStyle->visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);
auto elementBackgroundColor = elementStyle.visitedDependentColorWithColorFilter(CSSPropertyBackgroundColor);
- auto disappearsIntoBackground = blendSourceOver(rootEditableBackgroundColor, elementBackgroundColor) == rootEditableBackgroundColor;
+ auto disappearsIntoBackground = blendSourceOver(parentBackgroundColor, elementBackgroundColor) == parentBackgroundColor;
if (disappearsIntoBackground)
- return rootEditableStyle->visitedDependentColorWithColorFilter(CSSPropertyCaretColor);
+ return parentStyle->visitedDependentColorWithColorFilter(CSSPropertyCaretColor);
}
return elementStyle.visitedDependentColorWithColorFilter(CSSPropertyCaretColor);
One of the reviewer asked me to use RefPtr(smart pointer) instead of auto so I changed this line as follows:
auto parentElement = node ? node->parentElement() : nullptr;
=>
RefPtr parentElement = node ? node->parentElement() : nullptr;
RefPtr is a class template that implements WebKit’s intrusive reference counting. It has to be used instead of raw pointers when contributors update the source code.
Steps to contribution of your code
Below are the steps I followed. You can find more details at https://webkit.org/contributing-code/
Build WebKit
$ Tools/Scripts/build-webkit
You don’t need to run every test case. Instead, you can only run the relevant layout tests as follows:
Layout test
$ Tools/Scripts/run-webkit-tests editing/caret
If you need pixel tests, just pass -p
You can find the test result in WebKitBuild/Debug/layout-test-results/results.html
Sometimes, we need to update ChangeLog
$ Tools/Scripts/prepare-ChangeLog --g HEAD
Finally, you can upload your patch:
$ Tools/Scripts/webkit-patch upload 117493
It would be good to add a message to your updated patch:
$ Tools/Scripts/webkit-patch upload 117493 -m “Updated ChnageLog”
If you don’t need a review now, pass –no-review.
$ Tools/Scripts/webkit-patch upload 117493 -m “Updated ChnageLog” --no-review
When your patch lands, “Reviewed by NOBODY (OOPS!)” line in ChangeLog is automatically filled with the name of the reviewer who approves your patch by setting review:+ and pushes it to the commit queue(AFAIK).
https://ews-build.webkit.org/#/
In my case, I had to add the reviewer name because I updated the patch several times after getting +r.
If you change the layout code, you may see layout test failures:
If you find any layout test errors, you can easily get *-actual.txt from the layout test result. Just overwrite *-expcted.txt with the *-actual.txt.
Then, add them to your patch and run webkit-patch command again. If you already have +R, just add your patch to the commit queue.
The committer status has been suspended due to my inactivity so I was not able to add my patch to the commit queue. However, one of the reviewers helped me land this patch. The next plan is to work on Bug 44862 - Make the caret more visible on any background again.