Bug 1481400 - Don't dispatch a simulated click on keyup for default-prevented keydown. r=masayuki

I decided to go with a separate flag instead of reusing :active because
the :active handling WebKit uses for buttons is extremely weird: They
apply :active only to the button, not to the whole element chain like
mouse activeness does, and something like this keeps the :active state
in the button ~forever:

  <!doctype html>
  <style>
    :active { outline: 2px solid red; }
  </style>
  <button onkeyup="return false">ABC</button>

Differential Revision: https://phabricator.services.mozilla.com/D116585
This commit is contained in:
Emilio Cobos Álvarez 2021-06-03 08:15:24 +00:00
Родитель 88bb474e07
Коммит 21c786b30a
3 изменённых файлов: 74 добавлений и 8 удалений

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

@ -2439,17 +2439,26 @@ bool nsGenericHTMLElement::PerformAccesskey(bool aKeyCausesActivation,
void nsGenericHTMLElement::HandleKeyboardActivation(
EventChainPostVisitor& aVisitor) {
const auto message = aVisitor.mEvent->mMessage;
if (message != eKeyUp && message != eKeyPress) {
return;
}
if (nsEventStatus_eIgnore != aVisitor.mEventStatus) {
if (message != eKeyDown && message != eKeyUp && message != eKeyPress) {
return;
}
const WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
if (nsEventStatus_eIgnore != aVisitor.mEventStatus) {
if (message == eKeyUp && keyEvent->mKeyCode == NS_VK_SPACE) {
// Unset the flag even if the event is default-prevented or something.
UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
}
return;
}
bool shouldActivate = false;
switch (message) {
case eKeyDown:
if (keyEvent->mKeyCode == NS_VK_SPACE) {
SetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
}
return;
case eKeyPress:
shouldActivate = keyEvent->mKeyCode == NS_VK_RETURN;
if (keyEvent->mKeyCode == NS_VK_SPACE) {
@ -2458,7 +2467,11 @@ void nsGenericHTMLElement::HandleKeyboardActivation(
}
break;
case eKeyUp:
shouldActivate = keyEvent->mKeyCode == NS_VK_SPACE;
shouldActivate = keyEvent->mKeyCode == NS_VK_SPACE &&
HasFlag(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
if (shouldActivate) {
UnsetFlags(HTML_ELEMENT_ACTIVE_FOR_KEYBOARD);
}
break;
default:
MOZ_ASSERT_UNREACHABLE("why didn't we bail out earlier?");

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

@ -905,9 +905,24 @@ class HTMLFieldSetElement;
} // namespace dom
} // namespace mozilla
#define FORM_ELEMENT_FLAG_BIT(n_) \
#define HTML_ELEMENT_FLAG_BIT(n_) \
NODE_FLAG_BIT(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_))
// HTMLElement specific bits
enum {
// Used to handle keyboard activation.
HTML_ELEMENT_ACTIVE_FOR_KEYBOARD = HTML_ELEMENT_FLAG_BIT(0),
// Remaining bits are type specific.
HTML_ELEMENT_TYPE_SPECIFIC_BITS_OFFSET =
ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 1,
};
ASSERT_NODE_FLAGS_SPACE(HTML_ELEMENT_TYPE_SPECIFIC_BITS_OFFSET);
#define FORM_ELEMENT_FLAG_BIT(n_) \
NODE_FLAG_BIT(HTML_ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + (n_))
// Form element specific bits
enum {
// If this flag is set on an nsGenericHTMLFormElement or an HTMLImageElement,
@ -931,7 +946,7 @@ enum {
// MAYBE_ORPHAN_FORM_ELEMENT set at the same time, so if it becomes an issue we
// can probably merge them into the same bit. --bz
ASSERT_NODE_FLAGS_SPACE(ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 3);
ASSERT_NODE_FLAGS_SPACE(HTML_ELEMENT_TYPE_SPECIFIC_BITS_OFFSET + 3);
#undef FORM_ELEMENT_FLAG_BIT

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

@ -0,0 +1,38 @@
<!doctype html>
<meta charset="utf-8">
<title>Button activation submits on keyup, but not if keydown is defaultPrevented</title>
<link rel=help href="https://bugzilla.mozilla.org/show_bug.cgi?id=1481400">
<link rel=author href="mailto:emilio@crisal.io" title="Emilio Cobos Álvarez">
<link rel=author href="https://mozilla.org" title="Mozilla">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<button>The button</button>
<script>
let button = document.querySelector("button");
promise_test(async t => {
button.focus();
assert_equals(document.activeElement, button, "Button should be focused");
let clickPromise = new Promise(resolve => {
button.addEventListener("click", resolve, { once: true });
});
await test_driver.send_keys(button, " ");
await clickPromise;
assert_true(true, "Button should have activated");
document.addEventListener("keydown", t.step_func(function(e) {
e.preventDefault();
}));
button.addEventListener("click", t.unreached_func("button got incorrectly activated"));
await test_driver.send_keys(button, " ");
await new Promise(resolve => t.step_timeout(resolve, 0));
assert_true(true, "Button should not have activated");
});
</script>