mirror of
https://github.com/mozilla/gecko-dev.git
synced 2025-02-04 13:07:52 +00:00
Bug 1249556 - Implement toggling details by keyboard. r=smaug
The user can switch to the main <summary> by tab key, and toggle the <details> by either 'space' key or 'enter' key. 'return' key is handled with 'keypress', and the 'space' key is handled with 'keyup' like the HTMLInputElement. MozReview-Commit-ID: HE6IduUGCpj --HG-- extra : rebase_source : 34598d95f35bf6b5bd927457ee09e42eb6ec0a68
This commit is contained in:
parent
03310cda94
commit
22224242ba
@ -292,15 +292,8 @@ HTMLButtonElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
|
||||
eKeyPress == aVisitor.mEvent->mMessage) ||
|
||||
(keyEvent->keyCode == NS_VK_SPACE &&
|
||||
eKeyUp == aVisitor.mEvent->mMessage)) {
|
||||
nsEventStatus status = nsEventStatus_eIgnore;
|
||||
|
||||
WidgetMouseEvent event(aVisitor.mEvent->mFlags.mIsTrusted,
|
||||
eMouseClick, nullptr,
|
||||
WidgetMouseEvent::eReal);
|
||||
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
|
||||
EventDispatcher::Dispatch(static_cast<nsIContent*>(this),
|
||||
aVisitor.mPresContext, &event, nullptr,
|
||||
&status);
|
||||
DispatchSimulatedClick(this, aVisitor.mEvent->mFlags.mIsTrusted,
|
||||
aVisitor.mPresContext);
|
||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
}
|
||||
|
@ -3824,15 +3824,8 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
|
||||
case NS_FORM_INPUT_IMAGE: // Bug 34418
|
||||
case NS_FORM_INPUT_COLOR:
|
||||
{
|
||||
WidgetMouseEvent event(aVisitor.mEvent->mFlags.mIsTrusted,
|
||||
eMouseClick, nullptr,
|
||||
WidgetMouseEvent::eReal);
|
||||
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
|
||||
nsEventStatus status = nsEventStatus_eIgnore;
|
||||
|
||||
EventDispatcher::Dispatch(static_cast<nsIContent*>(this),
|
||||
aVisitor.mPresContext, &event,
|
||||
nullptr, &status);
|
||||
DispatchSimulatedClick(this, aVisitor.mEvent->mFlags.mIsTrusted,
|
||||
aVisitor.mPresContext);
|
||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||
} // case
|
||||
} // switch
|
||||
@ -3859,15 +3852,9 @@ HTMLInputElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
|
||||
if (selectedRadioButton) {
|
||||
rv = selectedRadioButton->Focus();
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
nsEventStatus status = nsEventStatus_eIgnore;
|
||||
WidgetMouseEvent event(aVisitor.mEvent->mFlags.mIsTrusted,
|
||||
eMouseClick, nullptr,
|
||||
WidgetMouseEvent::eReal);
|
||||
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
|
||||
rv =
|
||||
EventDispatcher::Dispatch(ToSupports(selectedRadioButton),
|
||||
aVisitor.mPresContext,
|
||||
&event, nullptr, &status);
|
||||
rv = DispatchSimulatedClick(selectedRadioButton,
|
||||
aVisitor.mEvent->mFlags.mIsTrusted,
|
||||
aVisitor.mPresContext);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
|
@ -10,6 +10,8 @@
|
||||
#include "mozilla/dom/HTMLUnknownElement.h"
|
||||
#include "mozilla/MouseEvents.h"
|
||||
#include "mozilla/Preferences.h"
|
||||
#include "mozilla/TextEvents.h"
|
||||
#include "nsFocusManager.h"
|
||||
|
||||
// Expand NS_IMPL_NS_NEW_HTML_ELEMENT(Summary) to add pref check.
|
||||
nsGenericHTMLElement*
|
||||
@ -44,34 +46,99 @@ HTMLSummaryElement::PostHandleEvent(EventChainPostVisitor& aVisitor)
|
||||
return rv;
|
||||
}
|
||||
|
||||
auto toggleDetails = false;
|
||||
auto* event = aVisitor.mEvent;
|
||||
|
||||
if (event->HasMouseEventMessage()) {
|
||||
auto* mouseEvent = event->AsMouseEvent();
|
||||
toggleDetails = mouseEvent->IsLeftClickEvent();
|
||||
}
|
||||
|
||||
// Todo: Bug 634004: Implement toggle details by keyboard.
|
||||
|
||||
if (!toggleDetails || !IsMainSummary()) {
|
||||
if (!IsMainSummary()) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
auto* details = GetDetails();
|
||||
MOZ_ASSERT(details, "Expected to find details since this is the main summary!");
|
||||
WidgetEvent* const event = aVisitor.mEvent;
|
||||
|
||||
// When dispatching a synthesized mouse click event to a details with
|
||||
// 'display: none', both Chrome and Safari do not toggle the 'open' attribute.
|
||||
// We follow them by checking whether details has a frame or not.
|
||||
if (details->GetPrimaryFrame()) {
|
||||
details->ToggleOpen();
|
||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
if (event->HasMouseEventMessage()) {
|
||||
WidgetMouseEvent* mouseEvent = event->AsMouseEvent();
|
||||
|
||||
if (mouseEvent->IsLeftClickEvent()) {
|
||||
RefPtr<HTMLDetailsElement> details = GetDetails();
|
||||
MOZ_ASSERT(details,
|
||||
"Expected to find details since this is the main summary!");
|
||||
|
||||
// When dispatching a synthesized mouse click event to a details element
|
||||
// with 'display: none', both Chrome and Safari do not toggle the 'open'
|
||||
// attribute. We follow them by checking whether the details element has a
|
||||
// frame or not.
|
||||
if (details->GetPrimaryFrame(Flush_Frames)) {
|
||||
details->ToggleOpen();
|
||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||
return NS_OK;
|
||||
}
|
||||
}
|
||||
} // event->HasMouseEventMessage()
|
||||
|
||||
if (event->HasKeyEventMessage()) {
|
||||
WidgetKeyboardEvent* keyboardEvent = event->AsKeyboardEvent();
|
||||
bool dispatchClick = false;
|
||||
|
||||
switch (event->mMessage) {
|
||||
case eKeyPress:
|
||||
if (keyboardEvent->charCode == nsIDOMKeyEvent::DOM_VK_SPACE) {
|
||||
// Consume 'space' key to prevent scrolling the page down.
|
||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
|
||||
dispatchClick = keyboardEvent->keyCode == nsIDOMKeyEvent::DOM_VK_RETURN;
|
||||
break;
|
||||
|
||||
case eKeyUp:
|
||||
dispatchClick = keyboardEvent->keyCode == nsIDOMKeyEvent::DOM_VK_SPACE;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (dispatchClick) {
|
||||
rv = DispatchSimulatedClick(this, event->mFlags.mIsTrusted,
|
||||
aVisitor.mPresContext);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
|
||||
}
|
||||
}
|
||||
} // event->HasKeyEventMessage()
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLSummaryElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
|
||||
int32_t* aTabIndex)
|
||||
{
|
||||
bool disallowOverridingFocusability =
|
||||
nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable, aTabIndex);
|
||||
|
||||
if (disallowOverridingFocusability || !IsMainSummary()) {
|
||||
return disallowOverridingFocusability;
|
||||
}
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
// The parent does not have strong opinion about the focusability of this main
|
||||
// summary element, but we'd like to override it when mouse clicking on Mac OS
|
||||
// like other form elements.
|
||||
*aIsFocusable = !aWithMouse || nsFocusManager::sMouseFocusesFormControl;
|
||||
#else
|
||||
// The main summary element is focusable on other platforms.
|
||||
*aIsFocusable = true;
|
||||
#endif
|
||||
|
||||
// Give a chance to allow the subclass to override aIsFocusable.
|
||||
return false;
|
||||
}
|
||||
|
||||
int32_t
|
||||
HTMLSummaryElement::TabIndexDefault()
|
||||
{
|
||||
// Make the main summary be able to navigate via tab, and be focusable.
|
||||
// See nsGenericHTMLElement::IsHTMLFocusable().
|
||||
return IsMainSummary() ? 0 : nsGenericHTMLElement::TabIndexDefault();
|
||||
}
|
||||
|
||||
bool
|
||||
HTMLSummaryElement::IsMainSummary() const
|
||||
{
|
||||
|
@ -33,6 +33,11 @@ public:
|
||||
|
||||
nsresult PostHandleEvent(EventChainPostVisitor& aVisitor) override;
|
||||
|
||||
bool IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
|
||||
int32_t* aTabIndex) override;
|
||||
|
||||
int32_t TabIndexDefault() override;
|
||||
|
||||
// Return true if this is the first summary element child of a details or the
|
||||
// default summary element generated by DetailsFrame.
|
||||
bool IsMainSummary() const;
|
||||
|
@ -2805,20 +2805,25 @@ nsGenericHTMLElement::PerformAccesskey(bool aKeyCausesActivation,
|
||||
|
||||
if (aKeyCausesActivation) {
|
||||
// Click on it if the users prefs indicate to do so.
|
||||
WidgetMouseEvent event(aIsTrustedEvent, eMouseClick, nullptr,
|
||||
WidgetMouseEvent::eReal);
|
||||
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
|
||||
|
||||
nsAutoPopupStatePusher popupStatePusher(aIsTrustedEvent ?
|
||||
openAllowed : openAbused);
|
||||
|
||||
EventDispatcher::Dispatch(static_cast<nsIContent*>(this),
|
||||
presContext, &event);
|
||||
DispatchSimulatedClick(this, aIsTrustedEvent, presContext);
|
||||
}
|
||||
|
||||
return focused;
|
||||
}
|
||||
|
||||
nsresult
|
||||
nsGenericHTMLElement::DispatchSimulatedClick(nsGenericHTMLElement* aElement,
|
||||
bool aIsTrusted,
|
||||
nsPresContext* aPresContext)
|
||||
{
|
||||
WidgetMouseEvent event(aIsTrusted, eMouseClick, nullptr,
|
||||
WidgetMouseEvent::eReal);
|
||||
event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD;
|
||||
return EventDispatcher::Dispatch(ToSupports(aElement), aPresContext, &event);
|
||||
}
|
||||
|
||||
const nsAttrName*
|
||||
nsGenericHTMLElement::InternalGetExistingAttrNameFromQName(const nsAString& aStr) const
|
||||
{
|
||||
|
@ -1027,6 +1027,13 @@ protected:
|
||||
|
||||
virtual const nsAttrName* InternalGetExistingAttrNameFromQName(const nsAString& aStr) const override;
|
||||
|
||||
/**
|
||||
* Dispatch a simulated mouse click by keyboard to the given element.
|
||||
*/
|
||||
nsresult DispatchSimulatedClick(nsGenericHTMLElement* aElement,
|
||||
bool aIsTrusted,
|
||||
nsPresContext* aPresContext);
|
||||
|
||||
/**
|
||||
* Create a URI for the given aURISpec string.
|
||||
* Returns INVALID_STATE_ERR and nulls *aURI if aURISpec is empty
|
||||
|
@ -0,0 +1,21 @@
|
||||
<!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>
|
@ -0,0 +1,24 @@
|
||||
<!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>
|
@ -0,0 +1,20 @@
|
||||
<!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>
|
@ -0,0 +1,21 @@
|
||||
<!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>
|
@ -72,3 +72,9 @@ pref(dom.details_element.enabled,true) == mouse-click-fixed-summary.html open-fi
|
||||
pref(dom.details_element.enabled,true) == mouse-click-twice-fixed-summary.html fixed-summary.html
|
||||
pref(dom.details_element.enabled,true) == mouse-click-float-details.html open-float-details.html
|
||||
pref(dom.details_element.enabled,true) == mouse-click-twice-float-details.html float-details.html
|
||||
|
||||
# Dispatch keyboard event to summary
|
||||
pref(dom.details_element.enabled,true) == key-enter-single-summary.html open-single-summary.html
|
||||
pref(dom.details_element.enabled,true) == key-enter-open-second-summary.html open-multiple-summary.html
|
||||
pref(dom.details_element.enabled,true) == key-enter-prevent-default.html single-summary.html
|
||||
pref(dom.details_element.enabled,true) == key-space-single-summary.html open-single-summary.html
|
||||
|
Loading…
x
Reference in New Issue
Block a user