Bug 1748975 - Don't trigger click event on untrusted keypresses for buttons and summaries. r=edgar

Differential Revision: https://phabricator.services.mozilla.com/D154942
This commit is contained in:
Adam Vandolder 2022-08-25 01:25:58 +00:00
parent 8466c5994a
commit 867e24f2f7
15 changed files with 343 additions and 98 deletions

View File

@ -233,7 +233,11 @@ nsresult HTMLButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
}
if (nsEventStatus_eIgnore == aVisitor.mEventStatus) {
HandleKeyboardActivation(aVisitor);
WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
if (keyEvent && keyEvent->IsTrusted()) {
HandleKeyboardActivation(aVisitor);
}
if (aVisitor.mItemFlags & NS_OUTER_ACTIVATE_EVENT) {
if (mForm) {
// Hold a strong ref while dispatching

View File

@ -62,7 +62,9 @@ nsresult HTMLSummaryElement::PostHandleEvent(EventChainPostVisitor& aVisitor) {
}
} // event->HasMouseEventMessage()
HandleKeyboardActivation(aVisitor);
if (event->HasKeyEventMessage() && event->IsTrusted()) {
HandleKeyboardActivation(aVisitor);
}
return rv;
}

View File

@ -2246,11 +2246,10 @@ Result<bool, nsresult> nsGenericHTMLElement::PerformAccesskey(
void nsGenericHTMLElement::HandleKeyboardActivation(
EventChainPostVisitor& aVisitor) {
const auto message = aVisitor.mEvent->mMessage;
if (message != eKeyDown && message != eKeyUp && message != eKeyPress) {
return;
}
MOZ_ASSERT(aVisitor.mEvent->HasKeyEventMessage());
MOZ_ASSERT(aVisitor.mEvent->IsTrusted());
const auto message = aVisitor.mEvent->mMessage;
const WidgetKeyboardEvent* keyEvent = aVisitor.mEvent->AsKeyboardEvent();
if (nsEventStatus_eIgnore != aVisitor.mEventStatus) {
if (message == eKeyUp && keyEvent->mKeyCode == NS_VK_SPACE) {

View File

@ -111,6 +111,10 @@ support-files =
[test_image_selection_3.html]
[test_image_selection_in_contenteditable.html]
[test_intrinsic_size_on_loading.html]
[test_key_enter_open_second_summary.html]
[test_key_enter_prevent_default.html]
[test_key_enter_single_summary.html]
[test_key_space_single_summary.html]
[test_movement_by_characters.html]
[test_movement_by_words.html]
# Disable the caret movement by word test on Linux because the shortcut keys

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html>
<head>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script>
SimpleTest.waitForExplicitFinish();
function runTest() {
// Dispatch 'return' key on second summary should not collapse details.
const summary = document.getElementById("summary");
summary.focus();
synthesizeKey("KEY_Enter");
const details = document.querySelector("details");
ok(details.open, "Dispatch 'return' key on second summary should not collapse details.");
SimpleTest.finish();
}
</script>
</head>
<body onload="runTest();">
<details open>
<summary>Summary</summary>
<summary id="summary">Second Summary</summary>
<p>This is the details.</p>
</details>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script>
SimpleTest.waitForExplicitFinish();
function runTest() {
const summary = document.querySelector("summary");
summary.addEventListener('click', function(e) {
// Prevent the details from toggling by key events.
e.preventDefault();
});
// Dispatch 'return' key to the summary element.
summary.focus();
synthesizeKey("KEY_Enter");
const details = document.querySelector("details");
ok(!details.open, "Prevent default on summary should not open details.");
SimpleTest.finish();
}
</script>
</head>
<body onload="runTest();">
<details>
<summary>Summary</summary>
<p>This is the details.</p>
</details>
</body>
</html>

View File

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script>
SimpleTest.waitForExplicitFinish();
function runTest() {
// Dispatch 'return' key to the summary element.
const summary = document.querySelector("summary");
summary.focus();
synthesizeKey("KEY_Enter");
const details = document.querySelector("details");
ok(details.open, "Dispatch return key on summary should open details.");
SimpleTest.finish();
}
</script>
</head>
<body onload="runTest();">
<details>
<summary>Summary</summary>
<p>This is the details.</p>
</details>
</body>
</html>

View File

@ -0,0 +1,26 @@
<!DOCTYPE html>
<html>
<head>
<script src="/tests/SimpleTest/SimpleTest.js"></script>
<script src="/tests/SimpleTest/EventUtils.js"></script>
<script>
SimpleTest.waitForExplicitFinish();
function runTest() {
const summary = document.querySelector("summary");
summary.focus();
synthesizeKey(" ");
const details = document.querySelector("details");
ok(details.open, "Dispatch space key on summary should open details.");
SimpleTest.finish();
}
</script>
</head>
<body onload="runTest();">
<details>
<summary>Summary</summary>
<p>This is the details.</p>
</details>
</body>
</html>

View File

@ -1,21 +0,0 @@
<!DOCTYPE html>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<html class="reftest-wait">
<script>
function runTest() {
// Dispatch 'return' key on second summary should not collapse details.
var summary = document.getElementById("summary");
summary.dispatchEvent(new KeyboardEvent("keypress", {"keyCode": 13}));
document.documentElement.removeAttribute("class");
}
</script>
<body onload="runTest();">
<details open>
<summary>Summary</summary>
<summary id="summary">Second Summary</summary>
<p>This is the details.</p>
</details>
</body>
</html>

View File

@ -1,24 +0,0 @@
<!DOCTYPE html>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<html class="reftest-wait">
<script>
function runTest() {
var summary = document.getElementById("summary");
summary.addEventListener('click', function(e) {
// Prevent the details from toggling by key events.
e.preventDefault();
});
// Dispatch 'return' key to the summary element.
summary.dispatchEvent(new KeyboardEvent("keypress", {"keyCode": 13}));
document.documentElement.removeAttribute("class");
}
</script>
<body onload="runTest();">
<details>
<summary id="summary">Summary</summary>
<p>This is the details.</p>
</details>
</body>
</html>

View File

@ -1,20 +0,0 @@
<!DOCTYPE html>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<html class="reftest-wait">
<script>
function runTest() {
var summary = document.getElementById("summary");
// Dispatch 'return' key to the summary element.
summary.dispatchEvent(new KeyboardEvent("keypress", {"keyCode": 13}));
document.documentElement.removeAttribute("class");
}
</script>
<body onload="runTest();">
<details>
<summary id="summary">Summary</summary>
<p>This is the details.</p>
</details>
</body>
</html>

View File

@ -1,21 +0,0 @@
<!DOCTYPE html>
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<html class="reftest-wait">
<script>
function runTest() {
var summary = document.getElementById("summary");
// Dispatch 'space' keys to the summary element.
summary.dispatchEvent(new KeyboardEvent("keydown", {"keyCode": 32}));
summary.dispatchEvent(new KeyboardEvent("keyup", {"keyCode": 32}));
document.documentElement.removeAttribute("class");
}
</script>
<body onload="runTest();">
<details>
<summary id="summary">Summary</summary>
<p>This is the details.</p>
</details>
</body>
</html>

View File

@ -84,12 +84,6 @@ fuzzy(0-1,0-172) == mouse-click-overflow-auto-details.html overflow-auto-open-de
fuzzy-if(geckoview,0-7,0-1) == mouse-click-float-details.html open-float-details.html
fuzzy(0-4,0-1) == mouse-click-twice-float-details.html float-details.html # Bug 1316430
# Dispatch keyboard event to summary
== key-enter-single-summary.html open-single-summary.html
== key-enter-open-second-summary.html open-multiple-summary.html
== key-enter-prevent-default.html single-summary.html
== key-space-single-summary.html open-single-summary.html
# Generated content bits
== details-after.html single-summary.html
== details-before.html single-summary.html

View File

@ -0,0 +1,112 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Forms</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<form id="input_form">
<button name="submitButton" type="submit">Submit</button>
<button name="resetButton" type="reset">Reset</button>
<button name="buttonButton" type="button">Button</button>
</form>
<script type="module">
const form = document.querySelector("form");
form.addEventListener("submit", (e) => {
e.preventDefault();
assert_true(false, 'form should not be submitted');
});
for (const button of document.querySelectorAll("button")) {
button.addEventListener("click", function(e) {
assert_true(false, `${button.type} button should not be clicked`);
});
}
// Create and append button elements
for (const button of document.querySelectorAll("button")) {
test(() => {
// keyCode: Enter
button.dispatchEvent(
new KeyboardEvent("keypress", {
keyCode: 13,
})
);
// key: Enter
button.dispatchEvent(
new KeyboardEvent("keypress", {
key: "Enter",
})
);
// keyCode: Space
button.dispatchEvent(
new KeyboardEvent("keypress", {
keyCode: 32,
})
);
// key: Space
button.dispatchEvent(
new KeyboardEvent("keypress", {
key: " ",
})
);
}, `Dispatching untrusted keypress events to ${button.type} button should not cause click event`);
test(() => {
// keyCode: Enter
button.dispatchEvent(
new KeyboardEvent("keydown", {
keyCode: 13,
})
);
button.dispatchEvent(
new KeyboardEvent("keyup", {
keyCode: 13,
})
);
// key: Enter
button.dispatchEvent(
new KeyboardEvent("keydown", {
key: "Enter",
})
);
button.dispatchEvent(
new KeyboardEvent("keyup", {
key: "Enter",
})
);
// keyCode: Space
button.dispatchEvent(
new KeyboardEvent("keydown", {
keyCode: 32,
})
);
button.dispatchEvent(
new KeyboardEvent("keyup", {
keyCode: 32,
})
);
// key: Space
button.dispatchEvent(
new KeyboardEvent("keydown", {
key: " ",
})
);
button.dispatchEvent(
new KeyboardEvent("keyup", {
key: " ",
})
);
}, `Dispatching untrusted keyup/keydown events to ${button.type} button should not cause click event`);
}
</script>
</body>
</html>

View File

@ -0,0 +1,104 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Summary</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
</head>
<body>
<div id="log"></div>
<details>
<summary>Summary</summary>
Details
</details>
<script type="module">
const details = document.querySelector("details");
details.addEventListener("toggle",
(e) => assert_true(false, 'details should not be toggled'));
const summary = document.querySelector("summary");
summary.addEventListener("click",
(e) => assert_true(false, `summary should not be clicked`));
test(() => {
// keyCode: Enter
summary.dispatchEvent(
new KeyboardEvent("keypress", {
keyCode: 13,
})
);
// key: Enter
summary.dispatchEvent(
new KeyboardEvent("keypress", {
key: "Enter",
})
);
// keyCode: Space
summary.dispatchEvent(
new KeyboardEvent("keypress", {
keyCode: 32,
})
);
// key: Space
summary.dispatchEvent(
new KeyboardEvent("keypress", {
key: " ",
})
);
}, `Dispatching untrusted keypress events to summary should not cause click event`);
test(() => {
// keyCode: Enter
summary.dispatchEvent(
new KeyboardEvent("keydown", {
keyCode: 13,
})
);
summary.dispatchEvent(
new KeyboardEvent("keyup", {
keyCode: 13,
})
);
// key: Enter
summary.dispatchEvent(
new KeyboardEvent("keydown", {
key: "Enter",
})
);
summary.dispatchEvent(
new KeyboardEvent("keyup", {
key: "Enter",
})
);
// keyCode: Space
summary.dispatchEvent(
new KeyboardEvent("keydown", {
keyCode: 32,
})
);
summary.dispatchEvent(
new KeyboardEvent("keyup", {
keyCode: 32,
})
);
// key: Space
summary.dispatchEvent(
new KeyboardEvent("keydown", {
key: " ",
})
);
summary.dispatchEvent(
new KeyboardEvent("keyup", {
key: " ",
})
);
}, `Dispatching untrusted keyup/keydown events to summary should not cause click event`);
</script>
</body>
</html>