Skip to content

Commit

Permalink
Fix edge cases of keyboard navigations around anchor elements with …
Browse files Browse the repository at this point in the history
…`TabsToLink`

https://bugs.webkit.org/show_bug.cgi?id=270713

Reviewed by Ryosuke Niwa.

This patch is to fix edge cases in keyboard focus navigation around
`anchor` elements:

Partial Merge: https://chromium.googlesource.com/chromium/blink/+/53f8eba14b6362795bae47c02236ca93303264a6

By merging above, WebKit is trying to fix case around `empty anchor`
while `TabsToLinks` enabled. The test case was failing currently in
WebKit, so this fixes it.

Merge: https://chromium.googlesource.com/chromium/blink/+/7ef69ef071ba2b49ece747b93518b9a1218f9c45

In this merge, WebKit is aligning to allow keyboard navigation to anchors
with `TabsToLinks` being disabled if the anchors have explicit tabindex.

Beside following, this patch still retains `early return` if it is not focusable.

> Changes:
* Source/WebCore/html/HTMLAnchorElement.cpp:
(HTMLAnchorElement::isKeyboardFocusable):

> Test Cases & Expectations:
* LayoutTests/fast/events/anchor-empty-focus-expected.txt:
* LayoutTests/fast/events/anchor-empty-focus.html:
* LayoutTests/fast/events/frame-tab-focus-expected.txt:
* LayoutTests/fast/events/frame-tab-focus.html:
* LayoutTests/fast/events/tab-focus-anchor-expected.txt:
* LayoutTests/fast/events/tab-focus-anchor-tab-to-links-expected.txt:
* LayoutTests/fast/events/tab-focus-anchor-tab-to-links.html:
* LayoutTests/fast/events/tab-focus-anchor.html:
* LayoutTests/fast/forms/focus-on-control-with-zero-size.html:
* LayoutTests/fast/forms/focus-on-control-with-zero-size-expected.txt:

> Platform Specific Test Expectation:
* LayoutTests/platform/ios/TestExpectations: `iOS` don't have 'EventSender` keydown support

Canonical link: https://commits.webkit.org/275976@main
  • Loading branch information
Ahmad Saleem committed Mar 12, 2024
1 parent 8764804 commit e39892e
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 59 deletions.
12 changes: 10 additions & 2 deletions LayoutTests/fast/events/anchor-empty-focus-expected.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
This tests that focus() works on an empty anchor.
This tests that focus() and sequential focus navigation work on empty anchors.

On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".


PASS document.activeElement is emptyAnchor1
PASS document.activeElement is emptyAnchor2
PASS successfullyParsed is true

TEST COMPLETE

TEST PASSED
42 changes: 24 additions & 18 deletions LayoutTests/fast/events/anchor-empty-focus.html
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
<!DOCTYPE html><!-- webkit-test-runner [ TabsToLinks=true ] -->
<html>
<head>
<script>
function test()
{
document.getElementById("anch").focus();
if (window.testRunner) {
testRunner.dumpAsText();
eventSender.keyDown('s');
}
}
</script>
</head>
<body onload="test()">
This tests that focus() works on an empty anchor.<br>
<a id="anch" href="#" onkeydown="document.getElementById('console').innerText = 'TEST PASSED'"></a>
<br>
<pre id="console">TEST FAILED</pre>
</body>
<head>
<script src="../../resources/js-test.js"></script>
</head>
<body onload="test()">
<a id="anch" href="#"></a>
<a id="anch2" href="#"></a>
<input>

<script>
description('This tests that focus() and sequential focus navigation work on empty anchors.');
jsTestIsAsync = true;
var emptyAnchor1 = document.getElementById("anch");
var emptyAnchor2 = document.getElementById("anch2");
function test() {
emptyAnchor1.focus();
shouldBe('document.activeElement', 'emptyAnchor1');

eventSender.keyDown('\t');
shouldBe('document.activeElement', 'emptyAnchor2');
finishJSTest();
}
</script>
</body>
</html>
38 changes: 14 additions & 24 deletions LayoutTests/fast/events/frame-tab-focus-expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ Tabbing forward...

main window: INPUT #2 (tabindex=2) focused
main window: INPUT #2 (tabindex=2) blurred
main window: A #2 (tabindex=2) focused
main window: A #2 (tabindex=2) blurred
main window: INPUT #1 (tabindex=3) focused
main window: INPUT #1 (tabindex=3) blurred
main window: A #1 (tabindex=3) focused
main window: A #1 (tabindex=3) blurred
main window: A #3 (tabindex=3) focused
main window: A #3 (tabindex=3) blurred
main window: window blurred
child: window focused
child: INPUT #0 (tabindex=0) focused
Expand All @@ -17,33 +23,9 @@ main window: INPUT #0 (tabindex=0) focused
main window: INPUT #0 (tabindex=0) blurred
main window: window blurred
empty-child: window focused
empty-child: window blurred
middle-child-1: window focused
middle-child-1: INPUT #0 (tabindex=0) focused
middle-child-1: INPUT #0 (tabindex=0) blurred
middle-child-1: window blurred
middle-child-2: window focused
middle-child-2: INPUT #0 (tabindex=0) focused
middle-child-2: INPUT #0 (tabindex=0) blurred
middle-child-2: window blurred
main window: window focused
main window: INPUT #3 (tabindex=0) focused
main window: INPUT #3 (tabindex=0) blurred

Tabbing backward...

main window: INPUT #3 (tabindex=0) focused
main window: INPUT #3 (tabindex=0) blurred
main window: window blurred
middle-child-2: window focused
middle-child-2: INPUT #0 (tabindex=0) focused
middle-child-2: INPUT #0 (tabindex=0) blurred
middle-child-2: window blurred
middle-child-1: window focused
middle-child-1: INPUT #0 (tabindex=0) focused
middle-child-1: INPUT #0 (tabindex=0) blurred
middle-child-1: window blurred
empty-child: window focused
empty-child: window blurred
main window: window focused
main window: INPUT #0 (tabindex=0) focused
Expand All @@ -54,10 +36,18 @@ child: INPUT #0 (tabindex=0) focused
child: INPUT #0 (tabindex=0) blurred
child: window blurred
main window: window focused
main window: A #3 (tabindex=3) focused
main window: A #3 (tabindex=3) blurred
main window: A #1 (tabindex=3) focused
main window: A #1 (tabindex=3) blurred
main window: INPUT #1 (tabindex=3) focused
main window: INPUT #1 (tabindex=3) blurred
main window: A #2 (tabindex=2) focused
main window: A #2 (tabindex=2) blurred
main window: INPUT #2 (tabindex=2) focused
main window: INPUT #2 (tabindex=2) blurred
main window: A #0 (tabindex=1) focused
main window: A #0 (tabindex=1) blurred

Option-tabbing forward...

Expand Down
1 change: 0 additions & 1 deletion LayoutTests/fast/events/frame-tab-focus.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
function windowFocused(win, name)
{
return function() {
win.document.body.style.background = "green";
log(name + ': window focused');
}
}
Expand Down
6 changes: 6 additions & 0 deletions LayoutTests/fast/events/tab-focus-anchor-expected.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@ This test ensures that we can tab to an a element with a tab index. Press tab to

Not focusable (given the WebKitTabToLinks pref is set to false)

Not focusable

Focusable

Focusable

Result

PASS
PASS

21 changes: 21 additions & 0 deletions LayoutTests/fast/events/tab-focus-anchor-tab-to-links-expected.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
This test ensures that we can tab to all anchor elements. Press tab three times to focus the elements below.

Focusable

Not focusable

Focusable

Focusable



Result

PASS gave focus to focusable element
PASS gave focus to focusable element
PASS gave focus to focusable element
PASS successfullyParsed is true

TEST COMPLETE

40 changes: 40 additions & 0 deletions LayoutTests/fast/events/tab-focus-anchor-tab-to-links.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!DOCTYPE html><!-- webkit-test-runner [ TabsToLinks=true ] -->
<html>
<head>
<link rel="help" href="http://www.w3.org/TR/DOM-Level-3-Events/#events-WheelEvent">
<script src="../../resources/js-test.js"></script>
<script>
function runTest()
{
if (!window.testRunner || !window.eventSender) {
debug("FAIL: This test requires window.testRunner and window.eventSender.");
finishJSTest();
return;
}

for (var i = 0; i < 3; i++)
eventSender.keyDown('\t');

finishJSTest();
};

window.addEventListener('load', runTest);
</script>
</head>
<body>

<p>This test ensures that we can tab to all anchor elements. Press tab three
times to focus the elements below.

<p><a onfocus="testPassed('gave focus to focusable element')" href="#">Focusable</a></p>
<p><a onfocus="testFailed('gave focus to unfocusable element')">Not focusable</a></p>
<p><a tabindex=0 onfocus="testPassed('gave focus to focusable element')" href="#">Focusable</a></p>
<p><a tabindex=0 onfocus="testPassed('gave focus to focusable element')">Focusable</a></p>
<p><input onfocus="testFailed('should have stopped testing before this element')"></p>

<p>Result

<div id="console"></div>

</body>
</html>
12 changes: 7 additions & 5 deletions LayoutTests/fast/events/tab-focus-anchor.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
function pass()
{
var el = document.querySelector('pre');
el.textContent = 'PASS';
el.textContent += 'PASS\n';
}

function fail()
{
var el = document.querySelector('pre');
el.textContent = 'FAIL';
el.textContent += 'FAIL\n';
}

if (window.testRunner) {
Expand All @@ -24,7 +24,7 @@
if (!window.testRunner)
return;

for (var i = 0; i < 1; i++) {
for (var i = 0; i < 2; i++) {
eventSender.keyDown('\t');
}
};
Expand All @@ -36,12 +36,14 @@
<p>This test ensures that we can tab to an a element with a tab index. Press tab
to focus the element below.

<p><a tabindex=0 onfocus="fail()" href="#">Not focusable (given the WebKitTabToLinks pref is set to false)</a>
<p><a onfocus="fail()" href="#">Not focusable (given the WebKitTabToLinks pref is set to false)</a>
<p><a onfocus="fail()">Not focusable</a>
<p><a tabindex=0 onfocus="pass()" href="#">Focusable</a>
<p><a tabindex=0 onfocus="pass()">Focusable</a>

<p>Result

<pre>FAIL</pre>
<pre id="out"></pre>

</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
Check if a control won't loose focus when it becomes 0-size.
PASS document.activeElement is input1
PASS input1.value is "01"
PASS document.activeElement is input1

Check if 0-size control can get focus.
PASS document.activeElement is input1
PASS successfullyParsed is true

TEST COMPLETE

55 changes: 55 additions & 0 deletions LayoutTests/fast/forms/focus-on-control-with-zero-size.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<!DOCTYPE html>
<body>
<script src="../../resources/js-test.js"></script>
<style>
input {
border: none;
padding: 0;
margin: 0;
}
</style>
<input id="input1">
<script>
function handleKeydown(event) {
event.target.style.width = '0px';
}

function handleBlur(event) {
testFailed('Event: blur');
event.target.style.width = '';
}

window.onload = function() {
debug('Check if a control won\'t loose focus when it becomes 0-size.');
input1.focus();
input1.addEventListener('keydown', handleKeydown, false);
input1.addEventListener('blur', handleBlur, false);
eventSender.keyDown('0');
event.target.removeEventListener('keydown', handleKeydown);
setTimeout(step2, 1);
};

function step2() {
// We need to check activeElement twice because
// FrameSelection::setFocusedNodeIfNeeded can change focus.
shouldBe('document.activeElement', 'input1');
eventSender.keyDown('1');
shouldBeEqualToString('input1.value', '01');
shouldBe('document.activeElement', 'input1');

debug('');
debug('Check if 0-size control can get focus.');
input1.removeEventListener('blur', handleBlur);
input1.blur();
shouldBe('document.activeElement', 'document.body', true);
input1.style.height = '0px';
shouldBe('input1.offsetHeight', '0', true);
input1.focus();
shouldBe('document.activeElement', 'input1');

finishJSTest();
}

jsTestIsAsync = true;
</script>
</body>
2 changes: 2 additions & 0 deletions LayoutTests/platform/ios/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -1011,6 +1011,7 @@ fast/events/select-element.html [ Skip ]
fast/events/selectstart-by-arrow-keys-prevent-default.html [ Skip ]
fast/events/selectstart-by-arrow-keys.html [ Skip ]
fast/events/tab-focus-anchor.html [ Skip ]
fast/events/tab-focus-anchor-tab-to-links.html [ Skip ]
fast/events/tab-focus-hidden.html [ Skip ]
fast/events/tab-focus-link-in-canvas.html [ Skip ]
fast/events/tab-imagemap.html [ Skip ]
Expand All @@ -1027,6 +1028,7 @@ fast/forms/empty-textarea-toggle-disabled.html [ Skip ]
fast/forms/enter-clicks-buttons.html [ Skip ]
fast/forms/focus-change-on-keypress.html [ Skip ]
fast/forms/focus-control-to-page.html [ Skip ]
fast/forms/focus-on-control-with-zero-size.html [ Skip ]
fast/forms/implicit-submission.html [ Skip ]
fast/forms/input-first-letter-edit.html [ Skip ]
fast/forms/input-image-submit.html [ Skip ]
Expand Down
15 changes: 6 additions & 9 deletions Source/WebCore/html/HTMLAnchorElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
* (C) 1999 Antti Koivisto (koivisto@kde.org)
* (C) 2000 Simon Hausmann <hausmann@kde.org>
* Copyright (C) 2003-2016 Apple Inc. All rights reserved.
* Copyright (C) 2013 Google Inc. All rights reserved.
* (C) 2006 Graham Dennis (graham.dennis@gmail.com)
*
* This library is free software; you can redistribute it and/or
Expand Down Expand Up @@ -140,20 +141,16 @@ static bool hasNonEmptyBox(RenderBoxModelObject* renderer)

bool HTMLAnchorElement::isKeyboardFocusable(KeyboardEvent* event) const
{
if (!isLink())
return HTMLElement::isKeyboardFocusable(event);

if (!isFocusable())
return false;

if (!document().frame())
return false;
// Anchor is focusable if the base element supports focus and is focusable.
if (isFocusable() && Element::supportsFocus())
return HTMLElement::isKeyboardFocusable(event);

if (!document().frame()->eventHandler().tabsToLinks(event))
if (isLink() && !document().frame()->eventHandler().tabsToLinks(event))
return false;

if (!renderer() && ancestorsOfType<HTMLCanvasElement>(*this).first())
return true;
return HTMLElement::isKeyboardFocusable(event);

return hasNonEmptyBox(renderBoxModelObject());
}
Expand Down

0 comments on commit e39892e

Please sign in to comment.