Skip to content

Commit

Permalink
Fix keyboard selection of RenderListBox in vertical writing mode
Browse files Browse the repository at this point in the history
https://bugs.webkit.org/show_bug.cgi?id=261804
rdar://115766451

Reviewed by Wenson Hsieh.

In horizontal writing mode, the down and up arrow keys can be used to select the
next and previous option respectively. Adjust the arrow keys used for vertical
writing mode.

The added test will pass once <select> is enabled under the vertical form controls feature.

* LayoutTests/imported/w3c/web-platform-tests/css/css-writing-modes/forms/select-multiple-keyboard-selection.optional-expected.txt: Added.
* LayoutTests/imported/w3c/web-platform-tests/css/css-writing-modes/forms/select-multiple-keyboard-selection.optional.html: Added.
* LayoutTests/platform/ios/TestExpectations:

`RenderListBox` is not used on iOS.

* Source/WebCore/html/HTMLSelectElement.cpp:
(WebCore::HTMLSelectElement::listBoxDefaultEventHandler):

`vertical-lr`: Right and left arrow keys can be used for next and previous respectively.
`vertical-rl`: Left and right arrow keys can be used for next and previous respectively.
Canonical link: https://commits.webkit.org/268926@main
  • Loading branch information
pxlcoder committed Oct 5, 2023
1 parent db838b4 commit 8b80fb7
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@


PASS select[multiple][style="writing-mode: horizontal-tb"] supports keyboard navigation
FAIL select[multiple][style="writing-mode: vertical-lr"] supports keyboard navigation assert_equals: expected "Option 2" but got "Option 1"
FAIL select[multiple][style="writing-mode: vertical-rl"] supports keyboard navigation assert_equals: expected "Option 2" but got "Option 1"
FAIL select[multiple][style="writing-mode: sideways-lr"] supports keyboard navigation assert_equals: expected "Option 2" but got "Option 1"
FAIL select[multiple][style="writing-mode: sideways-rl"] supports keyboard navigation assert_equals: expected "Option 2" but got "Option 1"

Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<!doctype html>
<link rel="author" title="Aditya Keerthi" href="https://github.com/pxlcoder">
<link rel="help" href="https://html.spec.whatwg.org/#the-select-element">
<link rel="help" href="https://drafts.csswg.org/css-writing-modes-4/#block-flow">
<title>Test &lt;select&gt; multiple keyboard selection</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-actions.js"></script>
<script src="/resources/testdriver-vendor.js"></script>

<select multiple size="4"></select>
<script>
const select = document.querySelector("select");
for (let i = 0; i < 5; i++) {
const option = document.createElement("option");
option.textContent = `Option ${i + 1}`;
select.appendChild(option);
}

const arrow_left = "\uE012";
const arrow_up = "\uE013";
const arrow_right = "\uE014";
const arrow_down = "\uE015";

for (const writingMode of ["horizontal-tb", "vertical-lr", "vertical-rl", "sideways-lr", "sideways-rl"]) {
if (!CSS.supports(`writing-mode: ${writingMode}`))
continue;

const isHorizontal = writingMode === "horizontal-tb";
const isReversedBlockFlowDirection = writingMode.endsWith("-rl");
const scrollBlockAxis = isHorizontal ? "scrollTop" : "scrollLeft";

let nextKey = isHorizontal ? arrow_down : arrow_right;
let previousKey = isHorizontal ? arrow_up : arrow_left;

if (isReversedBlockFlowDirection) {
[nextKey, previousKey] = [previousKey, nextKey];
}

promise_test(async function() {
select.selectedIndex = 0;
select.style.writingMode = writingMode;
this.add_cleanup(() => {
select.scrollTop = 0;
select.scrollLeft = 0;
select.removeAttribute("style");
});

assert_equals(select.value, "Option 1");
assert_equals(select[scrollBlockAxis], 0);

select.focus();

assert_equals(document.activeElement, select);

await test_driver.send_keys(document.activeElement, previousKey);
assert_equals(select.value, "Option 1");
assert_equals(select[scrollBlockAxis], 0);

await test_driver.send_keys(document.activeElement, nextKey);
assert_equals(select.value, "Option 2");
assert_equals(select[scrollBlockAxis], 0);

for (let i = 0; i < select.size - 1; i++) {
await test_driver.send_keys(document.activeElement, nextKey);
}

assert_equals(select.value, "Option 5");

if (!isReversedBlockFlowDirection) {
assert_true(select[scrollBlockAxis] > 0);
} else {
assert_true(select[scrollBlockAxis] < 0);
}

await test_driver.send_keys(document.activeElement, previousKey);
assert_equals(select.value, "Option 4");

if (!isReversedBlockFlowDirection) {
assert_true(select[scrollBlockAxis] > 0);
} else {
assert_true(select[scrollBlockAxis] < 0);
}
}, `select[multiple][style="writing-mode: ${writingMode}"] supports keyboard navigation`);
};
</script>
1 change: 1 addition & 0 deletions LayoutTests/platform/ios/TestExpectations
Original file line number Diff line number Diff line change
Expand Up @@ -3943,6 +3943,7 @@ imported/w3c/web-platform-tests/css/css-writing-modes/forms/color-input-appearan
imported/w3c/web-platform-tests/css/css-writing-modes/forms/select-multiple-scrolling.optional.html [ Failure ]
imported/w3c/web-platform-tests/css/css-writing-modes/forms/select-size-scrolling-and-sizing.optional.html [ Failure ]
imported/w3c/web-platform-tests/css/css-writing-modes/forms/select-multiple-options-visual-order.html [ Failure ]
imported/w3c/web-platform-tests/css/css-writing-modes/forms/select-multiple-keyboard-selection.optional.html [ Failure ]
fast/forms/select/multiselect-in-listbox-mouse-release-outside.html [ Skip ]

# To investigate: might need to relax the tests, or adapt native thumb appearance.
Expand Down
23 changes: 16 additions & 7 deletions Source/WebCore/html/HTMLSelectElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1436,34 +1436,43 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event& event)
if (!is<KeyboardEvent>(event))
return;

CheckedPtr renderer = this->renderer();
bool isHorizontalWritingMode = renderer ? renderer->style().isHorizontalWritingMode() : true;
bool isFlippedBlocksWritingMode = renderer ? renderer->style().isFlippedBlocksWritingMode() : false;

auto nextKeyIdentifier = isHorizontalWritingMode ? "Down"_s : "Right"_s;
auto previousKeyIdentifier = isHorizontalWritingMode ? "Up"_s : "Left"_s;
if (isFlippedBlocksWritingMode)
std::swap(nextKeyIdentifier, previousKeyIdentifier);

KeyboardEvent& keyboardEvent = downcast<KeyboardEvent>(event);
const String& keyIdentifier = keyboardEvent.keyIdentifier();

bool handled = false;
int endIndex = 0;
if (m_activeSelectionEndIndex < 0) {
// Initialize the end index
if (keyIdentifier == "Down"_s || keyIdentifier == "PageDown"_s) {
if (keyIdentifier == nextKeyIdentifier || keyIdentifier == "PageDown"_s) {
int startIndex = lastSelectedListIndex();
handled = true;
if (keyIdentifier == "Down"_s)
if (keyIdentifier == nextKeyIdentifier)
endIndex = nextSelectableListIndex(startIndex);
else
endIndex = nextSelectableListIndexPageAway(startIndex, SkipForwards);
} else if (keyIdentifier == "Up"_s || keyIdentifier == "PageUp"_s) {
} else if (keyIdentifier == previousKeyIdentifier || keyIdentifier == "PageUp"_s) {
int startIndex = optionToListIndex(selectedIndex());
handled = true;
if (keyIdentifier == "Up"_s)
if (keyIdentifier == previousKeyIdentifier)
endIndex = previousSelectableListIndex(startIndex);
else
endIndex = nextSelectableListIndexPageAway(startIndex, SkipBackwards);
}
} else {
// Set the end index based on the current end index.
if (keyIdentifier == "Down"_s) {
if (keyIdentifier == nextKeyIdentifier) {
endIndex = nextSelectableListIndex(m_activeSelectionEndIndex);
handled = true;
} else if (keyIdentifier == "Up"_s) {
} else if (keyIdentifier == previousKeyIdentifier) {
endIndex = previousSelectableListIndex(m_activeSelectionEndIndex);
handled = true;
} else if (keyIdentifier == "PageDown"_s) {
Expand Down Expand Up @@ -1514,7 +1523,7 @@ void HTMLSelectElement::listBoxDefaultEventHandler(Event& event)
setActiveSelectionAnchorIndex(m_activeSelectionEndIndex);
}

downcast<RenderListBox>(*renderer()).scrollToRevealElementAtListIndex(endIndex);
downcast<RenderListBox>(*renderer).scrollToRevealElementAtListIndex(endIndex);
if (selectNewItem) {
updateListBoxSelection(deselectOthers);
listBoxOnChange();
Expand Down

0 comments on commit 8b80fb7

Please sign in to comment.