Skip to content

Commit bd74c28

Browse files
committed
Enable zooming outline: auto
https://bugs.webkit.org/show_bug.cgi?id=310434 rdar://173068660 Reviewed by Simon Fraser. The outline: auto focus ring would never change in size. This placed it outside the CSS coordinate system which could get awkward/ugly when combined with outline-offset, which does participate in the CSS coordinate system. To address this we need to account for two types of zoom, "page zoom" (Cmd+) and "page scale" (pinch). The changes in GraphicsContextCocoa take care these. We repurpose some of the logic from scaledBlurRadius() to handle rotation and skew correctly, but go for the larger value so the focus ring is sufficiently large in all directions. That's also why we go from 0 to 1: to ensure there's a focus ring visible. Most of the other changes are about forwarding the relevant "page zoom" factor to the graphics layer. Finally, the change in RenderStyle takes fixes a bug where we pass the zoomed outline-width value to platformFocusRingWidthOffset() which operates on un-zoomed values. This results in an outline snug to a border getting an ever larger gap as you page zoom. Tests: fast/css/mac/outline-auto-zoom.html fast/css/outline-auto-zoom-canvas.html Canonical link: https://commits.webkit.org/309926@main
1 parent 1e942cc commit bd74c28

37 files changed

Lines changed: 335 additions & 102 deletions
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
This test ensures that the canvas drawFocusIfNeeded focus ring scales with CSS zoom and canvas scale.
2+
3+
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
4+
5+
6+
PASS pixels2xZoom > pixels1x is true
7+
PASS pixels2xScale > pixels1x is true
8+
PASS successfullyParsed is true
9+
10+
TEST COMPLETE
11+
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
This test ensures that the canvas drawFocusIfNeeded focus ring scales correctly under a rotated non-uniform transform, exercising the singular value decomposition.
2+
3+
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
4+
5+
6+
PASS pixelsRotatedScale > pixelsIdentity is true
7+
PASS successfullyParsed is true
8+
9+
TEST COMPLETE
10+
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src="../../resources/js-test.js"></script>
5+
<style>
6+
canvas {
7+
display: block;
8+
}
9+
</style>
10+
</head>
11+
<body>
12+
<canvas id="canvas1" width="100" height="100">
13+
<button id="btn1" tabindex="0">X</button>
14+
</canvas>
15+
<canvas id="canvas2" width="100" height="100">
16+
<button id="btn2" tabindex="0">X</button>
17+
</canvas>
18+
<script>
19+
description("This test ensures that the canvas drawFocusIfNeeded focus ring scales correctly under a rotated non-uniform transform, exercising the singular value decomposition.");
20+
21+
function countFocusRingPixels(canvas, setupTransform) {
22+
var context = canvas.getContext("2d");
23+
context.clearRect(0, 0, canvas.width, canvas.height);
24+
25+
if (setupTransform) {
26+
context.save();
27+
setupTransform(context);
28+
}
29+
30+
var button = canvas.querySelector("button");
31+
context.beginPath();
32+
context.rect(10, 10, 20, 20);
33+
button.focus();
34+
context.drawFocusIfNeeded(button);
35+
button.blur();
36+
37+
if (setupTransform)
38+
context.restore();
39+
40+
var imageData = context.getImageData(0, 0, canvas.width, canvas.height).data;
41+
var count = 0;
42+
for (var i = 3; i < imageData.length; i += 4) {
43+
if (imageData[i] > 0)
44+
count++;
45+
}
46+
return count;
47+
}
48+
49+
var canvas1 = document.getElementById("canvas1");
50+
var canvas2 = document.getElementById("canvas2");
51+
52+
var pixelsIdentity = countFocusRingPixels(canvas1);
53+
54+
// Rotate 45 degrees then scale non-uniformly (2x, 1x). The largest singular
55+
// value is 2, but reading just the diagonal entries of the resulting matrix
56+
// would give sqrt(2.5) ≈ 1.58 — only the full eigenvalue decomposition
57+
// produces the correct value.
58+
var pixelsRotatedScale = countFocusRingPixels(canvas2, function(ctx) {
59+
ctx.rotate(Math.PI / 4);
60+
ctx.scale(2, 1);
61+
});
62+
63+
shouldBeTrue("pixelsRotatedScale > pixelsIdentity");
64+
</script>
65+
</body>
66+
</html>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src="../../resources/js-test.js"></script>
5+
<style>
6+
canvas {
7+
display: block;
8+
}
9+
</style>
10+
</head>
11+
<body>
12+
<canvas id="canvas1" width="100" height="100">
13+
<button id="btn1" tabindex="0">X</button>
14+
</canvas>
15+
<canvas id="canvas2" width="100" height="100" style="zoom: 2">
16+
<button id="btn2" tabindex="0">X</button>
17+
</canvas>
18+
<canvas id="canvas3" width="100" height="100">
19+
<button id="btn3" tabindex="0">X</button>
20+
</canvas>
21+
<script>
22+
description("This test ensures that the canvas drawFocusIfNeeded focus ring scales with CSS zoom and canvas scale.");
23+
24+
function countFocusRingPixels(canvas, scale) {
25+
var context = canvas.getContext("2d");
26+
context.clearRect(0, 0, canvas.width, canvas.height);
27+
28+
if (scale) {
29+
context.save();
30+
context.scale(scale, scale);
31+
}
32+
33+
var button = canvas.querySelector("button");
34+
context.beginPath();
35+
context.rect(10, 10, 30, 30);
36+
button.focus();
37+
context.drawFocusIfNeeded(button);
38+
button.blur();
39+
40+
if (scale)
41+
context.restore();
42+
43+
var imageData = context.getImageData(0, 0, canvas.width, canvas.height).data;
44+
var count = 0;
45+
for (var i = 3; i < imageData.length; i += 4) {
46+
if (imageData[i] > 0)
47+
count++;
48+
}
49+
return count;
50+
}
51+
52+
var canvas1 = document.getElementById("canvas1");
53+
var canvas2 = document.getElementById("canvas2");
54+
var canvas3 = document.getElementById("canvas3");
55+
56+
var pixels1x = countFocusRingPixels(canvas1);
57+
var pixels2xZoom = countFocusRingPixels(canvas2);
58+
var pixels2xScale = countFocusRingPixels(canvas3, 2);
59+
60+
// At 2x zoom the focus ring radius should be doubled, producing more painted
61+
// pixels. We check that the zoomed canvas has strictly more focus ring pixels.
62+
shouldBeTrue("pixels2xZoom > pixels1x");
63+
64+
// Similarly, ctx.scale(2, 2) should also produce a scaled
65+
// focus ring with more pixels than the unscaled version.
66+
shouldBeTrue("pixels2xScale > pixels1x");
67+
</script>
68+
</body>
69+
</html>
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<style>
5+
body { margin: 0 }
6+
#target {
7+
outline: auto;
8+
width: 100px;
9+
height: 100px;
10+
margin: 50px;
11+
zoom: 2;
12+
}
13+
</style>
14+
<script>
15+
window.onload = function() {
16+
document.getElementById("target").focus();
17+
}
18+
</script>
19+
</head>
20+
<body>
21+
<div id="target" tabindex="0"></div>
22+
</body>
23+
</html>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<style>
5+
body { margin: 0 }
6+
#target {
7+
outline: auto;
8+
width: 100px;
9+
height: 100px;
10+
margin: 50px;
11+
}
12+
</style>
13+
<script>
14+
window.onload = function() {
15+
document.getElementById("target").focus();
16+
if (window.internals)
17+
internals.setPageZoomFactor(2);
18+
}
19+
</script>
20+
</head>
21+
<body>
22+
<div id="target" tabindex="0"></div>
23+
</body>
24+
</html>

‎LayoutTests/fast/images/image-map-outline-with-scale-transform-expected.html‎

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,26 @@
33
<head>
44
<title>This tests that we paint area outline properly when the image's container is scaled.</title>
55
<style>
6-
img {
7-
position: absolute;
8-
top: 23px;
9-
left: 25px;
6+
.container {
7+
transform: scale(2);
8+
position: absolute;
9+
left: 50px;
10+
top: 50px;
1011
}
11-
12-
div {
13-
position: absolute;
14-
top: 33px;
15-
left: 35px;
16-
width: 40px;
17-
height: 40px;
18-
outline: auto;
12+
.outline {
13+
position: absolute;
14+
top: 5px;
15+
left: 5px;
16+
width: 20px;
17+
height: 20px;
18+
outline: auto;
1919
}
2020
</style>
2121
</head>
2222
<body>
23-
<img src="./resources/green.jpg" width=100 height=100>
24-
<div></div>
23+
<div class="container">
24+
<img src="./resources/green.jpg" width=50 height=50>
25+
<div class="outline"></div>
26+
</div>
2527
</body>
2628
</html>
Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
<html>
22
<head>
3-
<script>
4-
window.onload = function()
5-
{
6-
document.getElementById("area").focus();
7-
}
8-
</script>
3+
<style>
4+
img { zoom: 2.0; outline: auto }
5+
</style>
96
</head>
107
<body>
118
<p>Assuming the port-specific theme draws focus rings, this test ensures that a focus ring is drawn correctly for an imagemap when the zoom style is applied to the image. This test PASSED if a focus ring is drawn around the perimeter of the &lt;area&gt; in the imagemap (below).</p>
12-
<map name="imagemap">
13-
<area id="area" shape="rect" coords="0,0,256,256" href="#dummy" />
14-
</map>
15-
<img src="imagemap.jpg" width="256" height="256" usemap="#imagemap" ismap />
9+
<img src="imagemap.jpg" width="128" height="128">
1610
</body>
1711
</head>
1812
</html>

‎LayoutTests/platform/glib/TestExpectations‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5401,6 +5401,11 @@ webkit.org/b/310274 media/media-controller-time.html [ Pass Timeout ]
54015401

54025402
webkit.org/b/310278 media/video-pause-play-resolve.html [ Pass Timeout ]
54035403

5404+
webkit.org/b/310543 fast/css/outline-auto-zoom.html [ ImageOnlyFailure ]
5405+
webkit.org/b/310543 fast/css/outline-auto-zoom-canvas.html [ Failure ]
5406+
webkit.org/b/310543 fast/css/outline-auto-zoom-canvas-rotated-scale.html [ Failure ]
5407+
webkit.org/b/310543 fast/images/imagemap-focus-ring-zoom-style.html [ ImageOnlyFailure ]
5408+
webkit.org/b/310543 fast/images/image-map-outline-with-scale-transform.html [ ImageOnlyFailure ]
54045409
webkit.org/b/310543 fast/images/image-map-outline-with-paint-root-offset.html [ ImageOnlyFailure ]
54055410
webkit.org/b/310543 fast/inline/hidpi-outline-auto-with-border-radius-horizontal-ltr.html [ ImageOnlyFailure ]
54065411
webkit.org/b/310543 fast/inline/hidpi-outline-auto-with-border-radius-vertical-ltr.html [ ImageOnlyFailure ]

‎Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,9 @@ void CanvasRenderingContext2D::drawFocusIfNeededInternal(const Path& path, Eleme
165165
Ref canvas = this->canvas();
166166
if (!element.focused() || !hasInvertibleTransform() || path.isEmpty() || !element.isDescendantOf(canvas.get()) || !context)
167167
return;
168-
context->drawFocusRing(path, 1, RenderTheme::singleton().focusRingColor(protect(element.document())->styleColorOptions(canvas->computedStyle())));
168+
CheckedPtr canvasStyle = canvas->computedStyle();
169+
auto zoomFactor = canvasStyle ? canvasStyle->usedZoom() : 1.f;
170+
context->drawFocusRing(path, 1, RenderTheme::singleton().focusRingColor(protect(element.document())->styleColorOptions(canvasStyle)), zoomFactor);
169171
didDrawEntireCanvas();
170172
}
171173

0 commit comments

Comments
 (0)