Bug 810268 - there's no way to know unselected item when selection in single selection was changed, r=tbsaunde

This commit is contained in:
Alexander Surkov 2013-07-28 14:33:57 -04:00
parent 238b6bebd6
commit 25db64d524
5 changed files with 154 additions and 34 deletions

View File

@ -303,7 +303,7 @@ EventQueue::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
aTailEvent->mEventRule = AccEvent::eDoNotEmit;
aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
aThisEvent->mPackedEvent = aThisEvent;
aThisEvent->mPackedEvent = aTailEvent;
return;
}
}
@ -472,6 +472,29 @@ EventQueue::ProcessEventQueue()
continue;
}
// Fire selected state change events in support to selection events.
if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_ADD) {
nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
true, event->mIsFromUserInput);
} else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION_REMOVE) {
nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
false, event->mIsFromUserInput);
} else if (event->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
AccSelChangeEvent* selChangeEvent = downcast_accEvent(event);
nsEventShell::FireEvent(event->mAccessible, states::SELECTED,
(selChangeEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd),
event->mIsFromUserInput);
if (selChangeEvent->mPackedEvent) {
nsEventShell::FireEvent(selChangeEvent->mPackedEvent->mAccessible,
states::SELECTED,
(selChangeEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd),
selChangeEvent->mPackedEvent->mIsFromUserInput);
}
}
nsEventShell::FireEvent(event);
// Fire text change events.

View File

@ -35,6 +35,20 @@ public:
mozilla::a11y::Accessible* aAccessible,
mozilla::a11y::EIsFromUserInput aIsFromUserInput = mozilla::a11y::eAutoDetect);
/**
* Fire state change event.
*/
static void FireEvent(mozilla::a11y::Accessible* aTarget, uint64_t aState,
bool aIsEnabled, bool aIsFromUserInput)
{
nsRefPtr<mozilla::a11y::AccStateChangeEvent> stateChangeEvent =
new mozilla::a11y::AccStateChangeEvent(aTarget, aState, aIsEnabled,
(aIsFromUserInput ?
mozilla::a11y::eFromUserInput :
mozilla::a11y::eNoUserInput));
FireEvent(stateChangeEvent);
}
/**
* Append 'event-from-input' object attribute if the accessible event has
* been fired just now for the given node.

View File

@ -508,7 +508,7 @@ function eventQueue(aEventType)
}
}
var matchedChecker = null;
var hasMatchedCheckers = false;
for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
var eventSeq = this.mScenarios[scnIdx];
@ -516,9 +516,9 @@ function eventQueue(aEventType)
var nextChecker = this.getNextExpectedEvent(eventSeq);
if (nextChecker) {
if (eventQueue.compareEvents(nextChecker, aEvent)) {
matchedChecker = nextChecker;
matchedChecker.wasCaught++;
break;
this.processMatchedChecker(aEvent, nextChecker, scnIdx, eventSeq.idx);
hasMatchedCheckers = true;
continue;
}
}
@ -526,41 +526,46 @@ function eventQueue(aEventType)
for (idx = 0; idx < eventSeq.length; idx++) {
if (!eventSeq[idx].unexpected && eventSeq[idx].async) {
if (eventQueue.compareEvents(eventSeq[idx], aEvent)) {
matchedChecker = eventSeq[idx];
matchedChecker.wasCaught++;
this.processMatchedChecker(aEvent, eventSeq[idx], scnIdx, idx);
hasMatchedCheckers = true;
break;
}
}
}
}
// Call 'check' functions on invoker's side.
if (matchedChecker) {
if ("check" in matchedChecker)
matchedChecker.check(aEvent);
if (hasMatchedCheckers) {
var invoker = this.getInvoker();
if ("check" in invoker)
invoker.check(aEvent);
}
// Dump handled event.
eventQueue.logEvent(aEvent, matchedChecker, this.areExpectedEventsLeft(),
this.mNextInvokerStatus);
// If we don't have more events to wait then schedule next invoker.
if (!this.areExpectedEventsLeft() &&
if (this.hasMatchedScenario() &&
(this.mNextInvokerStatus == kInvokerNotScheduled)) {
this.processNextInvokerInTimeout();
return;
}
// If we have scheduled a next invoker then cancel in case of match.
if ((this.mNextInvokerStatus == kInvokerPending) && matchedChecker)
if ((this.mNextInvokerStatus == kInvokerPending) && hasMatchedCheckers)
this.mNextInvokerStatus = kInvokerCanceled;
}
// Helpers
this.processMatchedChecker =
function eventQueue_function(aEvent, aMatchedChecker, aScenarioIdx, aEventIdx)
{
aMatchedChecker.wasCaught++;
if ("check" in aMatchedChecker)
aMatchedChecker.check(aEvent);
eventQueue.logEvent(aEvent, aMatchedChecker, aScenarioIdx, aEventIdx,
this.areExpectedEventsLeft(),
this.mNextInvokerStatus);
}
this.getNextExpectedEvent =
function eventQueue_getNextExpectedEvent(aEventSeq)
{
@ -635,6 +640,16 @@ function eventQueue(aEventType)
return false;
}
this.hasMatchedScenario =
function eventQueue_hasMatchedScenario()
{
for (var scnIdx = 0; scnIdx < this.mScenarios.length; scnIdx++) {
if (!this.areExpectedEventsLeft(this.mScenarios[scnIdx]))
return true;
}
return false;
}
this.getInvoker = function eventQueue_getInvoker()
{
@ -858,6 +873,7 @@ eventQueue.isSameEvent = function eventQueue_isSameEvent(aChecker, aEvent)
}
eventQueue.logEvent = function eventQueue_logEvent(aOrigEvent, aMatchedChecker,
aScenarioIdx, aEventIdx,
aAreExpectedEventsLeft,
aInvokerStatus)
{
@ -897,7 +913,8 @@ eventQueue.logEvent = function eventQueue_logEvent(aOrigEvent, aMatchedChecker,
var currType = eventQueue.getEventTypeAsString(aMatchedChecker);
var currTargetDescr = eventQueue.getEventTargetDescr(aMatchedChecker);
var consoleMsg = "*****\nEQ matched: " + currType + "\n*****";
var consoleMsg = "*****\nScenario " + aScenarioIdx +
", event " + aEventIdx + " matched: " + currType + "\n*****";
gLogger.logToConsole(consoleMsg);
msg += " event, type: " + currType + ", target: " + currTargetDescr;
@ -1727,7 +1744,8 @@ function stateChangeChecker(aState, aIsExtraState, aIsEnabled,
{
if (aEvent instanceof nsIAccessibleStateChangeEvent) {
var scEvent = aEvent.QueryInterface(nsIAccessibleStateChangeEvent);
return aEvent.accessible = this.target && scEvent.state == aState;
return (aEvent.accessible == getAccessible(this.target)) &&
(scEvent.state == aState);
}
return false;
}
@ -1771,6 +1789,59 @@ function expandedStateChecker(aIsEnabled, aTargetOrFunc, aTargetFuncArg)
}
}
////////////////////////////////////////////////////////////////////////////////
// Event sequances (array of predefined checkers)
/**
* Event seq for single selection change.
*/
function selChangeSeq(aUnselectedID, aSelectedID)
{
if (!aUnselectedID) {
return [
new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
new invokerChecker(EVENT_SELECTION, aSelectedID)
];
}
// Return two possible scenarios: depending on widget type when selection is
// moved the the order of items that get selected and unselected may vary.
return [
[
new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
new invokerChecker(EVENT_SELECTION, aSelectedID)
],
[
new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
new invokerChecker(EVENT_SELECTION, aSelectedID)
]
];
}
/**
* Event seq for item removed form the selection.
*/
function selRemoveSeq(aUnselectedID)
{
return [
new stateChangeChecker(STATE_SELECTED, false, false, aUnselectedID),
new invokerChecker(EVENT_SELECTION_REMOVE, aUnselectedID)
];
}
/**
* Event seq for item added to the selection.
*/
function selAddSeq(aSelectedID)
{
return [
new stateChangeChecker(STATE_SELECTED, false, true, aSelectedID),
new invokerChecker(EVENT_SELECTION_ADD, aSelectedID)
];
}
////////////////////////////////////////////////////////////////////////////////
// Private implementation details.
////////////////////////////////////////////////////////////////////////////////
@ -2043,16 +2114,23 @@ function sequenceItem(aProcessor, aEventType, aTarget, aItemID)
/**
* Invoker base class for prepare an action.
*/
function synthAction(aNodeOrID, aCheckerOrEventSeq)
function synthAction(aNodeOrID, aEventsObj)
{
this.DOMNode = getNode(aNodeOrID);
if (aCheckerOrEventSeq) {
if (aCheckerOrEventSeq instanceof Array) {
this.eventSeq = aCheckerOrEventSeq;
if (aEventsObj) {
var scenarios = null;
if (aEventsObj instanceof Array) {
if (aEventsObj[0] instanceof Array)
scenarios = aEventsObj; // scenarios
else
scenarios = [ aEventsObj ]; // event sequance
} else {
this.eventSeq = [ aCheckerOrEventSeq ];
scenarios = [ [ aEventsObj ] ]; // a single checker object
}
for (var i = 0; i < scenarios.length; i++)
defineScenario(this, scenarios[i]);
}
this.getID = function synthAction_getID()

View File

@ -38,31 +38,31 @@
gQueue.push(new synthClick("combobox",
new invokerChecker(EVENT_FOCUS, "cb1_item1")));
gQueue.push(new synthDownKey("cb1_item1",
new invokerChecker(EVENT_SELECTION, "cb1_item2")));
selChangeSeq("cb1_item1", "cb1_item2")));
// closed combobox
gQueue.push(new synthEscapeKey("combobox",
new invokerChecker(EVENT_FOCUS, "combobox")));
gQueue.push(new synthDownKey("cb1_item2",
new invokerChecker(EVENT_SELECTION, "cb1_item3")));
selChangeSeq("cb1_item2", "cb1_item3")));
// listbox
gQueue.push(new synthClick("lb1_item1",
new invokerChecker(EVENT_SELECTION, "lb1_item1")));
gQueue.push(new synthDownKey("lb1_item1",
new invokerChecker(EVENT_SELECTION, "lb1_item2")));
selChangeSeq("lb1_item1", "lb1_item2")));
// multiselectable listbox
gQueue.push(new synthClick("lb2_item1",
new invokerChecker(EVENT_SELECTION, "lb2_item1")));
selChangeSeq(null, "lb2_item1")));
gQueue.push(new synthDownKey("lb2_item1",
new invokerChecker(EVENT_SELECTION_ADD, "lb2_item2"),
selAddSeq("lb2_item2"),
{ shiftKey: true }));
gQueue.push(new synthUpKey("lb2_item2",
new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item2"),
selRemoveSeq("lb2_item2"),
{ shiftKey: true }));
gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true },
new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item1")));
selRemoveSeq("lb2_item1")));
gQueue.invoke(); // Will call SimpleTest.finish();
}
@ -77,7 +77,12 @@
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302"
title="Incorrect selection events in HTML, XUL and ARIA">
Mozilla Bug 414302
Bug 414302
</a>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=810268"
title="There's no way to know unselected item when selection in single selection was changed">
Bug 810268
</a>
<p id="display"></p>

View File

@ -36,7 +36,7 @@
this.invoke = function selectItem_invoke() {
var itemNode = this.selectNode.querySelector("*[aria-selected='true']");
if (itemNode)
itemNode.removeAttribute("aria-selected", "true");
itemNode.removeAttribute("aria-selected");
this.itemNode.setAttribute("aria-selected", "true");
}