Bug 1733263 - P5: Rely on DOMMenuItem events for ACTIVE state changes in select elements. r=Jamie

1. Use dom events in RootAccessible to fire ACTIVE state changes.
2. Add DOMMenuItemInactive events to nsListControlFrame and fire it on
   the previously active option.
3. Don't fire those DOM events on collapsed combo boxes.
4. Add ACTIVE state change events for collapsed combo box options.

Differential Revision: https://phabricator.services.mozilla.com/D130298
This commit is contained in:
Eitan Isaacson 2021-11-11 17:05:58 +00:00
parent bdc3cd4279
commit 37107fdf4c
6 changed files with 104 additions and 32 deletions

View File

@ -399,6 +399,9 @@ void RootAccessible::ProcessDOMEvent(Event* aDOMEvent, nsINode* aTarget) {
accessible);
}
} else if (eventType.EqualsLiteral("DOMMenuItemActive")) {
RefPtr<AccEvent> event =
new AccStateChangeEvent(accessible, states::ACTIVE, true);
nsEventShell::FireEvent(event);
FocusMgr()->ActiveItemChanged(accessible);
#ifdef A11Y_LOG
if (logging::IsEnabled(logging::eFocus)) {
@ -406,6 +409,10 @@ void RootAccessible::ProcessDOMEvent(Event* aDOMEvent, nsINode* aTarget) {
}
#endif
} else if (eventType.EqualsLiteral("DOMMenuItemInactive")) {
RefPtr<AccEvent> event =
new AccStateChangeEvent(accessible, states::ACTIVE, false);
nsEventShell::FireEvent(event);
// Process DOMMenuItemInactive event for autocomplete only because this is
// unique widget that may acquire focus from autocomplete popup while popup
// stays open and has no active item. In case of XUL tree autocomplete

View File

@ -235,6 +235,22 @@ nsRect HTMLSelectOptionAccessible::RelativeBounds(
return HyperTextAccessibleWrap::RelativeBounds(aBoundingFrame);
}
nsresult HTMLSelectOptionAccessible::HandleAccEvent(AccEvent* aEvent) {
nsresult rv = HyperTextAccessibleWrap::HandleAccEvent(aEvent);
NS_ENSURE_SUCCESS(rv, rv);
AccStateChangeEvent* event = downcast_accEvent(aEvent);
if (event && (event->GetState() == states::SELECTED)) {
if (!ContainerWidget()->AreItemsOperable()) {
// Collapsed options' ACTIVE state reflects their SELECT state.
nsEventShell::FireEvent(this, states::ACTIVE, event->IsStateEnabled(),
true);
}
}
return NS_OK;
}
void HTMLSelectOptionAccessible::ActionNameAt(uint8_t aIndex,
nsAString& aName) {
if (aIndex == eAction_Select) aName.AssignLiteral("select");

View File

@ -71,6 +71,8 @@ class HTMLSelectOptionAccessible : public HyperTextAccessibleWrap {
virtual nsRect RelativeBounds(nsIFrame** aBoundingFrame) const override;
virtual void SetSelected(bool aSelect) override;
nsresult HandleAccEvent(AccEvent* aEvent) override;
// ActionAccessible
virtual uint8_t ActionCount() const override;
virtual void ActionNameAt(uint8_t aIndex, nsAString& aName) override;

View File

@ -21,7 +21,6 @@
<script type="application/javascript">
// gA11yEventDumpID = "eventdump"; // debug stuff
// gA11yEventDumpToConsole = true;
var gQueue = null;
async function doTests() {
@ -39,20 +38,33 @@
p = waitForEvents({
expected: [[EVENT_SELECTION, "orange"]],
unexpected: [[EVENT_FOCUS]]
unexpected: [
[EVENT_FOCUS],
stateChangeEventArgs("orange", EXT_STATE_ACTIVE, true, true),
],
});
// item is selected and stays focused
// item is selected and stays focused and active
synthesizeKey("VK_DOWN");
await p;
p = waitForEvent(EVENT_FOCUS, "apple");
p = waitForEvents([
stateChangeEventArgs("orange", EXT_STATE_ACTIVE, false, true),
stateChangeEventArgs("apple", EXT_STATE_ACTIVE, true, true),
[EVENT_FOCUS, "apple"],
]);
// last selected item is focused
synthesizeKey("VK_DOWN", { shiftKey: true });
await p;
p = waitForEvents({
expected: [[EVENT_FOCUS, "orange"]],
unexpected: [[EVENT_FOCUS, "apple"]]
expected: [
[EVENT_FOCUS, "orange"],
stateChangeEventArgs("orange", EXT_STATE_ACTIVE, true, true),
],
unexpected: [
[EVENT_FOCUS, "apple"],
stateChangeEventArgs("apple", EXT_STATE_ACTIVE, true, true),
],
});
// no focus event if nothing is changed
synthesizeKey("VK_DOWN");
@ -65,7 +77,10 @@
synthesizeKey("VK_TAB");
await p;
p = waitForEvent(EVENT_FOCUS, "orange");
p = waitForEvents({
expected: [[EVENT_FOCUS, "orange"]],
unexpected: [stateChangeEventArgs("orange", EXT_STATE_ACTIVE, true, true)],
});
// current item is focused
synthesizeKey("VK_TAB", { shiftKey: true });
await p;
@ -75,8 +90,11 @@
await p;
p = waitForEvents({
expected: [[EVENT_SELECTION, "cb_apple"]],
unexpected: [[EVENT_FOCUS]]
expected: [
[EVENT_SELECTION, "cb_apple"],
stateChangeEventArgs("cb_apple", EXT_STATE_ACTIVE, true, true),
],
unexpected: [[EVENT_FOCUS]],
});
// collapsed combobox keeps a focus
synthesizeKey("VK_DOWN");
@ -87,7 +105,12 @@
synthesizeKey("VK_DOWN", { altKey: true });
await p;
p = waitForEvent(EVENT_FOCUS, "cb_orange");
p = waitForEvents({
expected: [
[EVENT_SELECTION, "cb_orange"],
stateChangeEventArgs("cb_orange", EXT_STATE_ACTIVE, true, true),
],
});
// selected item is focused for expanded combobox
synthesizeKey("VK_UP");
await p;
@ -106,15 +129,25 @@
p = waitForEvents({
expected: [[EVENT_SELECTION, "orange"]],
unexpected: [[EVENT_FOCUS]]
unexpected: [
[EVENT_FOCUS],
stateChangeEventArgs("orange", EXT_STATE_ACTIVE, true, true),
],
});
// An unfocused selectable list gets selection change events,
// but not active or focus change events.
getNode("list").selectedIndex = getNode("orange").index;
await p;
p = waitForEvents({
expected: [[EVENT_SELECTION, "cb_apple"]],
unexpected: [[EVENT_FOCUS]]
expected: [
[EVENT_SELECTION, "cb_apple"],
stateChangeEventArgs("cb_apple", EXT_STATE_ACTIVE, true, true),
],
unexpected: [[EVENT_FOCUS]],
});
// An unfocused selectable combobox gets selection change events,
// and active state change events, but not focus.
getNode("cb_apple").selected = true;
await p;

View File

@ -620,6 +620,9 @@ bool nsListControlFrame::SingleSelection(int32_t aClickedIndex,
mComboboxFrame->UpdateRecentIndex(GetSelectedIndex());
}
#ifdef ACCESSIBILITY
nsCOMPtr<nsIContent> prevOption = GetCurrentOption();
#endif
bool wasChanged = false;
// Get Current selection
if (aDoToggle) {
@ -634,17 +637,12 @@ bool nsListControlFrame::SingleSelection(int32_t aClickedIndex,
return wasChanged;
}
#ifdef ACCESSIBILITY
bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
#endif
mStartSelectionIndex = aClickedIndex;
mEndSelectionIndex = aClickedIndex;
InvalidateFocus();
#ifdef ACCESSIBILITY
if (isCurrentOptionChanged) {
FireMenuItemActiveEvent();
}
FireMenuItemActiveEvent(prevOption);
#endif
return wasChanged;
@ -766,15 +764,13 @@ bool nsListControlFrame::PerformSelection(int32_t aClickedIndex, bool aIsShift,
mStartSelectionIndex = aClickedIndex;
}
#ifdef ACCESSIBILITY
bool isCurrentOptionChanged = mEndSelectionIndex != aClickedIndex;
nsCOMPtr<nsIContent> prevOption = GetCurrentOption();
#endif
mEndSelectionIndex = aClickedIndex;
InvalidateFocus();
#ifdef ACCESSIBILITY
if (isCurrentOptionChanged) {
FireMenuItemActiveEvent();
}
FireMenuItemActiveEvent(prevOption);
#endif
} else if (aIsControl) {
wasChanged = SingleSelection(aClickedIndex, true); // might destroy us
@ -1289,6 +1285,10 @@ nsListControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) {
mComboboxFrame->UpdateRecentIndex(NS_SKIP_NOTIFY_INDEX);
}
#ifdef ACCESSIBILITY
nsCOMPtr<nsIContent> prevOption = GetCurrentOption();
#endif
AutoWeakFrame weakFrame(this);
ScrollToIndex(aNewIndex);
if (!weakFrame.IsAlive()) {
@ -1299,7 +1299,9 @@ nsListControlFrame::OnSetSelectedIndex(int32_t aOldIndex, int32_t aNewIndex) {
InvalidateFocus();
#ifdef ACCESSIBILITY
FireMenuItemActiveEvent();
if (aOldIndex != aNewIndex) {
FireMenuItemActiveEvent(prevOption);
}
#endif
}
@ -1355,7 +1357,7 @@ void nsListControlFrame::AboutToDropDown() {
return;
}
#ifdef ACCESSIBILITY
FireMenuItemActiveEvent(); // Inform assistive tech what got focus
FireMenuItemActiveEvent(nullptr); // Inform assistive tech what got focus
#endif
}
mItemSelectionStarted = false;
@ -1579,17 +1581,25 @@ bool nsListControlFrame::IgnoreMouseEventForSelection(dom::Event* aEvent) {
}
#ifdef ACCESSIBILITY
void nsListControlFrame::FireMenuItemActiveEvent() {
if (mFocused != this && !IsInDropDownMode()) {
void nsListControlFrame::FireMenuItemActiveEvent(nsIContent* aPreviousOption) {
if ((mFocused != this && !IsInDropDownMode()) ||
(IsInDropDownMode() && !mComboboxFrame->IsDroppedDown())) {
return;
}
nsCOMPtr<nsIContent> optionContent = GetCurrentOption();
if (!optionContent) {
nsIContent* optionContent = GetCurrentOption();
if (aPreviousOption == optionContent) {
// No change
return;
}
FireDOMEvent(u"DOMMenuItemActive"_ns, optionContent);
if (aPreviousOption) {
FireDOMEvent(u"DOMMenuItemInactive"_ns, aPreviousOption);
}
if (optionContent) {
FireDOMEvent(u"DOMMenuItemActive"_ns, optionContent);
}
}
#endif
@ -2311,6 +2321,9 @@ void nsListControlFrame::PostHandleKeyEvent(int32_t aNewIndex,
AutoWeakFrame weakFrame(this);
bool wasChanged = false;
if (aIsControlOrMeta && !aIsShift && aCharCode != ' ') {
#ifdef ACCESSIBILITY
nsCOMPtr<nsIContent> prevOption = GetCurrentOption();
#endif
mStartSelectionIndex = aNewIndex;
mEndSelectionIndex = aNewIndex;
InvalidateFocus();
@ -2320,7 +2333,7 @@ void nsListControlFrame::PostHandleKeyEvent(int32_t aNewIndex,
}
#ifdef ACCESSIBILITY
FireMenuItemActiveEvent();
FireMenuItemActiveEvent(prevOption);
#endif
} else if (mControlSelectMode && aCharCode == ' ') {
wasChanged = SingleSelection(aNewIndex, true);

View File

@ -249,7 +249,8 @@ class nsListControlFrame final : public nsHTMLScrollFrame,
* fire a native focus event for accessibility
* (Some 3rd party products need to track our focus)
*/
void FireMenuItemActiveEvent(); // Inform assistive tech what got focused
void FireMenuItemActiveEvent(
nsIContent* aPreviousOption); // Inform assistive tech what got focused
#endif
protected: