Bug 1822860 - Make elements handle space key only when it's focused r=emilio

When a focused editable element which is in an element having some default
actions for some keyboard events, keyboard events should be handled only by the
editor associated to the focused editable element.  Therefore,
`nsGenericHTMLElement::HandleKeyboardActivation` should check whether the
element has focus.

Note that if elements having default actions for keyboard events is a focused
editing host, Chrome makes the element handle keyboard events instead of
their builtin editor.  For compatibility with them, let's follow the behavior.

Differential Revision: https://phabricator.services.mozilla.com/D177757
This commit is contained in:
Masayuki Nakano
2023-05-12 08:57:48 +00:00
parent 478a013745
commit e09a83649b
4 changed files with 181 additions and 0 deletions

View File

@@ -79,6 +79,9 @@ class nsFocusManager final : public nsIFocusManager,
* pointer filled in to an out-parameter). * pointer filled in to an out-parameter).
*/ */
mozilla::dom::Element* GetFocusedElement() { return mFocusedElement; } mozilla::dom::Element* GetFocusedElement() { return mFocusedElement; }
static mozilla::dom::Element* GetFocusedElementStatic() {
return sInstance ? sInstance->GetFocusedElement() : nullptr;
}
/** /**
* Returns true if aContent currently has focus. * Returns true if aContent currently has focus.

View File

@@ -2335,6 +2335,17 @@ void nsGenericHTMLElement::HandleKeyboardActivation(
MOZ_ASSERT(aVisitor.mEvent->HasKeyEventMessage()); MOZ_ASSERT(aVisitor.mEvent->HasKeyEventMessage());
MOZ_ASSERT(aVisitor.mEvent->IsTrusted()); MOZ_ASSERT(aVisitor.mEvent->IsTrusted());
// If focused element is different from this element, it may be editable.
// In that case, associated editor for the element should handle the keyboard
// instead. Therefore, if this is not the focused element, we should not
// handle the event here. Note that this element may be an editing host,
// i.e., focused and editable. In the case, keyboard events should be
// handled by the focused element instead of associated editor because
// Chrome handles the case so. For compatibility with Chrome, we follow them.
if (nsFocusManager::GetFocusedElementStatic() != this) {
return;
}
const auto message = aVisitor.mEvent->mMessage; const auto message = aVisitor.mEvent->mMessage;
const WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent(); const WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
if (nsEventStatus_eIgnore != aVisitor.mEventStatus) { if (nsEventStatus_eIgnore != aVisitor.mEventStatus) {

View File

@@ -0,0 +1,77 @@
<!doctype html>
<head>
<meta charset="utf-8">
<title>Tests for pressing space in editable button element</title>
<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>
<script src="/resources/testdriver-actions.js"></script>
</head>
<body>
<button contenteditable>HelloWorld</button>
<div contenteditable><button>HelloWorld</button></div>
<button><div contenteditable>HelloWorld</div></button>
<script>
"use strict";
promise_test(async () => {
await new Promise(resolve => {
addEventListener("load", resolve, {once: true});
});
const button = document.querySelector("button[contenteditable]");
getSelection().collapse(button.firstChild, "Hello".length);
let clickEvent = null;
button.addEventListener("click", event => clickEvent = event, {once: true});
await new this.window.test_driver.Actions()
.keyDown("\uE00D")
.keyUp("\uE00D")
.send();
assert_equals(button.textContent, "HelloWorld", "The button label shouldn't be changed");
assert_not_equals(clickEvent, null, "Click event should be fired on the <button>");
}, "Type space key in <button contenteditable> should be handled by the <button>");
promise_test(async () => {
document.querySelector("div[contenteditable]").focus();
const button = document.querySelector("div[contenteditable] > button");
getSelection().collapse(button.firstChild, "Hello".length);
let clickEvent = null;
button.addEventListener("click", event => clickEvent = event, {once: true});
await new this.window.test_driver.Actions()
.keyDown("\uE00D")
.keyUp("\uE00D")
.send();
assert_equals(button.textContent, "Hello World", "A space should be inserted into the button label");
assert_equals(clickEvent, null, "Click event should not be fired on the <button>");
}, "Type space key in editable <button> shouldn't be handled by the <button> when it's not focused");
promise_test(async () => {
const button = document.querySelector("div[contenteditable] > button");
button.textContent = "HelloWorld";
button.focus();
let clickEvent = null;
button.addEventListener("click", event => clickEvent = event, {once: true});
await new this.window.test_driver.Actions()
.keyDown("\uE00D")
.keyUp("\uE00D")
.send();
assert_equals(button.textContent, "HelloWorld", "The button label shouldn't be changed");
assert_not_equals(clickEvent, null, "Click event should be fired on the <button>");
}, "Type space key in editable <button> should be handled by the <button> when it's focused");
promise_test(async () => {
const div = document.querySelector("button > div[contenteditable]");
div.focus();
getSelection().collapse(div.firstChild, "Hello".length);
let clickEvent = null;
div.parentElement.addEventListener("click", event => clickEvent = event, {once: true});
await new this.window.test_driver.Actions()
.keyDown("\uE00D")
.keyUp("\uE00D")
.send();
assert_equals(div.textContent, "Hello World", "A space should be inserted into the button label");
assert_equals(clickEvent, null, "Click event should not be fired on the <button>");
}, "Type space key in editable element in <button> shouldn't be handled by the <button>");
</script>
</body>
</html>

View File

@@ -0,0 +1,90 @@
<!doctype html>
<head>
<meta charset="utf-8">
<title>Tests for pressing space in editable summary element</title>
<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>
<script src="/resources/testdriver-actions.js"></script>
</head>
<body>
<details contenteditable><summary>HelloWorld</summary>Details</details>
<details><summary contenteditable>HelloWorld</summary>Details</details>
<details><summary><div contenteditable>HelloWorld</div></summary>Details</details>
<script>
"use strict";
promise_test(async () => {
const details = document.querySelector("details[contenteditable]");
const summary = details.querySelector("summary");
getSelection().collapse(summary.firstChild, "Hello".length);
summary.focus();
await new this.window.test_driver.Actions()
.keyDown("\uE00D")
.keyUp("\uE00D")
.send();
assert_equals(
details.innerHTML,
"<summary>HelloWorld</summary>Details",
"A space shouldn't be inserted into the focused <summary>"
);
assert_true(details.open, "<details> shouldn't keep collapsed");
}, "Type space key in editable <summary> should be handled by the <summary> when it's focused");
promise_test(async () => {
const details = document.querySelector("details[contenteditable]");
details.innerHTML = "<summary>HelloWorld</summary>Details";
details.open = false;
const summary = details.querySelector("summary");
getSelection().collapse(summary.firstChild, "Hello".length);
details.focus();
await new this.window.test_driver.Actions()
.keyDown("\uE00D")
.keyUp("\uE00D")
.send();
assert_equals(
details.innerHTML,
"<summary>Hello World</summary>Details",
"A space should be inserted into the <summary>"
);
assert_false(details.open, "<details> should keep collapsed");
}, "Type space key in editable <summary> shouldn't be handled by the <summary> when it's not focused");
promise_test(async () => {
const details = document.querySelector("details > summary[contenteditable]").parentNode;
const summary = details.querySelector("summary");
getSelection().collapse(summary.firstChild, "Hello".length);
summary.focus();
await new this.window.test_driver.Actions()
.keyDown("\uE00D")
.keyUp("\uE00D")
.send();
assert_equals(
details.innerHTML,
'<summary contenteditable="">HelloWorld</summary>Details',
"The content of <details> shouldn't be changed"
);
assert_true(details.open, "<details> shouldn't keep collapsed");
}, "Type space key in <summary contenteditable> should be handled by the <summary>");
promise_test(async () => {
const details = document.querySelector("summary > div[contenteditable]").parentNode.parentNode;
const summary = details.querySelector("summary");
const editable = summary.querySelector("div[contenteditable]");
editable.focus();
getSelection().collapse(editable.firstChild, "Hello".length);
await new this.window.test_driver.Actions()
.keyDown("\uE00D")
.keyUp("\uE00D")
.send();
assert_equals(
details.innerHTML,
'<summary><div contenteditable="">Hello World</div></summary>Details',
"A space should be inserted"
);
assert_false(details.open, "<details> should keep collapsed");
}, "Type space key in editable element in <summary> shouldn't be handled by the <summary>");
</script>
</body>
</html>