Bug 414302 - Incorrect selection events in HTML, XUL and ARIA, r=tbsaunde, f=marcoz

This commit is contained in:
Alexander Surkov 2011-11-01 08:52:27 +08:00
Родитель a09e8acf9a
Коммит 3f07ab58a4
17 изменённых файлов: 663 добавлений и 150 удалений

Просмотреть файл

@ -59,7 +59,7 @@ interface nsIDOMNode;
* if (NS_SUCCEEDED(rv))
* rv = observerService->AddObserver(this, "accessible-event", PR_TRUE);
*/
[scriptable, uuid(fd1378c5-c606-4a5e-a321-8e7fc107e5cf)]
[scriptable, uuid(7f66a33a-9ed7-4fd4-87a8-e431b0f43368)]
interface nsIAccessibleEvent : nsISupports
{
/**
@ -280,7 +280,12 @@ interface nsIAccessibleEvent : nsISupports
const unsigned long EVENT_DOCUMENT_CONTENT_CHANGED = 0x002B;
const unsigned long EVENT_PROPERTY_CHANGED = 0x002C;
const unsigned long EVENT_SELECTION_CHANGED = 0x002D;
/**
* A slide changed in a presentation document or a page boundary was
* crossed in a word processing document.
*/
const unsigned long EVENT_PAGE_CHANGED = 0x002D;
/**
* A text object's attributes changed.
@ -436,16 +441,10 @@ interface nsIAccessibleEvent : nsISupports
*/
const unsigned long EVENT_OBJECT_ATTRIBUTE_CHANGED = 0x0055;
/**
* A slide changed in a presentation document or a page boundary was
* crossed in a word processing document.
*/
const unsigned long EVENT_PAGE_CHANGED = 0x0056;
/**
* Help make sure event map does not get out-of-line.
*/
const unsigned long EVENT_LAST_ENTRY = 0x0057;
const unsigned long EVENT_LAST_ENTRY = 0x0056;
/**
* The type of event, based on the enumerated event values

Просмотреть файл

@ -1089,10 +1089,24 @@ nsAccessibleWrap::FirePlatformEvent(AccEvent* aEvent)
}
} break;
case nsIAccessibleEvent::EVENT_SELECTION_CHANGED:
case nsIAccessibleEvent::EVENT_SELECTION:
case nsIAccessibleEvent::EVENT_SELECTION_ADD:
case nsIAccessibleEvent::EVENT_SELECTION_REMOVE:
{
// XXX: dupe events may be fired
MAI_LOG_DEBUG(("\n\nReceived: EVENT_SELECTION_CHANGED\n"));
AccSelChangeEvent* selChangeEvent = downcast_accEvent(aEvent);
g_signal_emit_by_name(nsAccessibleWrap::GetAtkObject(selChangeEvent->Widget()),
"selection_changed");
break;
}
case nsIAccessibleEvent::EVENT_SELECTION_WITHIN:
{
MAI_LOG_DEBUG(("\n\nReceived: EVENT_SELECTION_CHANGED\n"));
g_signal_emit_by_name(atkObj, "selection_changed");
break;
}
case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED:
MAI_LOG_DEBUG(("\n\nReceived: EVENT_TEXT_SELECTION_CHANGED\n"));

Просмотреть файл

@ -330,6 +330,28 @@ AccCaretMoveEvent::CreateXPCOMObject()
}
////////////////////////////////////////////////////////////////////////////////
// AccSelChangeEvent
////////////////////////////////////////////////////////////////////////////////
AccSelChangeEvent::
AccSelChangeEvent(nsAccessible* aWidget, nsAccessible* aItem,
SelChangeType aSelChangeType) :
AccEvent(0, aItem, eAutoDetect, eCoalesceSelectionChange),
mWidget(aWidget), mItem(aItem), mSelChangeType(aSelChangeType),
mPreceedingCount(0), mPackedEvent(nsnull)
{
if (aSelChangeType == eSelectionAdd) {
if (mWidget->GetSelectedItem(1))
mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
else
mEventType = nsIAccessibleEvent::EVENT_SELECTION;
} else {
mEventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
}
}
////////////////////////////////////////////////////////////////////////////////
// AccTableChangeEvent
////////////////////////////////////////////////////////////////////////////////

Просмотреть файл

@ -82,6 +82,9 @@ public:
// will be processed.
eCoalesceOfSameType,
// eCoalesceSelectionChange: coalescence of selection change events.
eCoalesceSelectionChange,
// eRemoveDupes : For repeat events, only the newest event in queue
// will be emitted.
eRemoveDupes,
@ -125,6 +128,7 @@ public:
eHideEvent,
eShowEvent,
eCaretMoveEvent,
eSelectionChangeEvent,
eTableChangeEvent
};
@ -327,10 +331,37 @@ private:
/**
* Accessible widget selection change event.
*/
class AccSelectionChangeEvent : public AccEvent
class AccSelChangeEvent : public AccEvent
{
public:
enum SelChangeType {
eSelectionAdd,
eSelectionRemove
};
AccSelChangeEvent(nsAccessible* aWidget, nsAccessible* aItem,
SelChangeType aSelChangeType);
virtual ~AccSelChangeEvent() { }
// AccEvent
static const EventGroup kEventGroup = eSelectionChangeEvent;
virtual unsigned int GetEventGroups() const
{
return AccEvent::GetEventGroups() | (1U << eSelectionChangeEvent);
}
// AccSelChangeEvent
nsAccessible* Widget() const { return mWidget; }
private:
nsRefPtr<nsAccessible> mWidget;
nsRefPtr<nsAccessible> mItem;
SelChangeType mSelChangeType;
PRUint32 mPreceedingCount;
AccSelChangeEvent* mPackedEvent;
friend class NotificationController;
};

Просмотреть файл

@ -51,6 +51,10 @@
using namespace mozilla::a11y;
// Defines the number of selection add/remove events in the queue when they
// aren't packed into single selection within event.
const unsigned int kSelChangeCountToPack = 5;
////////////////////////////////////////////////////////////////////////////////
// NotificationCollector
////////////////////////////////////////////////////////////////////////////////
@ -491,6 +495,26 @@ NotificationController::CoalesceEvents()
}
} break; // case eRemoveDupes
case AccEvent::eCoalesceSelectionChange:
{
AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent);
PRInt32 index = tail - 1;
for (; index >= 0; index--) {
AccEvent* thisEvent = mEvents[index];
if (thisEvent->mEventRule == tailEvent->mEventRule) {
AccSelChangeEvent* thisSelChangeEvent =
downcast_accEvent(thisEvent);
// Coalesce selection change events within same control.
if (tailSelChangeEvent->mWidget == thisSelChangeEvent->mWidget) {
CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index);
return;
}
}
}
} break; // eCoalesceSelectionChange
default:
break; // case eAllowDupes, eDoNotEmit
} // switch
@ -511,6 +535,86 @@ NotificationController::ApplyToSiblings(PRUint32 aStart, PRUint32 aEnd,
}
}
void
NotificationController::CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
AccSelChangeEvent* aThisEvent,
PRInt32 aThisIndex)
{
aTailEvent->mPreceedingCount = aThisEvent->mPreceedingCount + 1;
// Pack all preceding events into single selection within event
// when we receive too much selection add/remove events.
if (aTailEvent->mPreceedingCount >= kSelChangeCountToPack) {
aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_WITHIN;
aTailEvent->mAccessible = aTailEvent->mWidget;
aThisEvent->mEventRule = AccEvent::eDoNotEmit;
// Do not emit any preceding selection events for same widget if they
// weren't coalesced yet.
if (aThisEvent->mEventType != nsIAccessibleEvent::EVENT_SELECTION_WITHIN) {
for (PRInt32 jdx = aThisIndex - 1; jdx >= 0; jdx--) {
AccEvent* prevEvent = mEvents[jdx];
if (prevEvent->mEventRule == aTailEvent->mEventRule) {
AccSelChangeEvent* prevSelChangeEvent =
downcast_accEvent(prevEvent);
if (prevSelChangeEvent->mWidget == aTailEvent->mWidget)
prevSelChangeEvent->mEventRule = AccEvent::eDoNotEmit;
}
}
}
return;
}
// Pack sequential selection remove and selection add events into
// single selection change event.
if (aTailEvent->mPreceedingCount == 1 &&
aTailEvent->mItem != aThisEvent->mItem) {
if (aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
aThisEvent->mEventRule = AccEvent::eDoNotEmit;
aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
aTailEvent->mPackedEvent = aThisEvent;
return;
}
if (aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd &&
aTailEvent->mSelChangeType == AccSelChangeEvent::eSelectionRemove) {
aTailEvent->mEventRule = AccEvent::eDoNotEmit;
aThisEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION;
aThisEvent->mPackedEvent = aThisEvent;
return;
}
}
// Unpack the packed selection change event because we've got one
// more selection add/remove.
if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
if (aThisEvent->mPackedEvent) {
aThisEvent->mPackedEvent->mEventType =
aThisEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
nsIAccessibleEvent::EVENT_SELECTION_ADD :
nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
aThisEvent->mPackedEvent->mEventRule =
AccEvent::eCoalesceSelectionChange;
aThisEvent->mPackedEvent = nsnull;
}
aThisEvent->mEventType =
aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
nsIAccessibleEvent::EVENT_SELECTION_ADD :
nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
return;
}
// Convert into selection add since control has single selection but other
// selection events for this control are queued.
if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION)
aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
}
void
NotificationController::CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent,
AccHideEvent* aThisEvent)

Просмотреть файл

@ -251,11 +251,11 @@ private:
AccEvent::EEventRule aEventRule);
/**
* Do not emit one of two given reorder events fired for DOM nodes in the case
* when one DOM node is in parent chain of second one.
* Coalesce two selection change events within the same select control.
*/
void CoalesceReorderEventsFromSameTree(AccEvent* aAccEvent,
AccEvent* aDescendantAccEvent);
void CoalesceSelChangeEvents(AccSelChangeEvent* aTailEvent,
AccSelChangeEvent* aThisEvent,
PRInt32 aThisIndex);
/**
* Coalesce text change events caused by sibling hide events.

Просмотреть файл

@ -473,7 +473,7 @@ static const char kEventTypeNames[][40] = {
"document attributes changed", // EVENT_DOCUMENT_ATTRIBUTES_CHANGED
"document content changed", // EVENT_DOCUMENT_CONTENT_CHANGED
"property changed", // EVENT_PROPERTY_CHANGED
"selection changed", // EVENT_SELECTION_CHANGED
"page changed", // EVENT_PAGE_CHANGED
"text attribute changed", // EVENT_TEXT_ATTRIBUTE_CHANGED
"text caret moved", // EVENT_TEXT_CARET_MOVED
"text changed", // EVENT_TEXT_CHANGED
@ -514,7 +514,6 @@ static const char kEventTypeNames[][40] = {
"hypertext changed", // EVENT_HYPERTEXT_CHANGED
"hypertext links count changed", // EVENT_HYPERTEXT_NLINKS_CHANGED
"object attribute changed", // EVENT_OBJECT_ATTRIBUTE_CHANGED
"page changed" // EVENT_PAGE_CHANGED
};
/**

Просмотреть файл

@ -1042,34 +1042,25 @@ nsDocAccessible::AttributeChangedImpl(nsIContent* aContent, PRInt32 aNameSpaceID
return;
}
if (aAttribute == nsGkAtoms::selected ||
aAttribute == nsGkAtoms::aria_selected) {
// ARIA or XUL selection
if ((aContent->IsXUL() && aAttribute == nsGkAtoms::selected) ||
aAttribute == nsGkAtoms::aria_selected) {
nsAccessible* item = GetAccessible(aContent);
nsAccessible* widget =
nsAccUtils::GetSelectableContainer(item, item->State());
if (widget) {
AccSelChangeEvent::SelChangeType selChangeType =
aContent->AttrValueIs(aNameSpaceID, aAttribute,
nsGkAtoms::_true, eCaseMatters) ?
AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
nsAccessible *multiSelect =
nsAccUtils::GetMultiSelectableContainer(aContent);
// XXX: Multi selects are handled here only (bug 414302).
if (multiSelect) {
// Need to find the right event to use here, SELECTION_WITHIN would
// seem right but we had started using it for something else
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
multiSelect->GetNode(),
AccEvent::eAllowDupes);
static nsIContent::AttrValuesArray strings[] =
{&nsGkAtoms::_empty, &nsGkAtoms::_false, nsnull};
if (aContent->FindAttrValueIn(kNameSpaceID_None, aAttribute,
strings, eCaseMatters) >= 0) {
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_REMOVE,
aContent);
nsRefPtr<AccEvent> event =
new AccSelChangeEvent(widget, item, selChangeType);
FireDelayedAccessibleEvent(event);
}
return;
}
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_ADD,
aContent);
}
}
if (aAttribute == nsGkAtoms::contenteditable) {
nsRefPtr<AccEvent> editableChangeEvent =
new AccStateChangeEvent(aContent, states::EDITABLE);
@ -1209,7 +1200,18 @@ void nsDocAccessible::ContentStateChanged(nsIDocument* aDocument,
nsEventStates aStateMask)
{
if (aStateMask.HasState(NS_EVENT_STATE_CHECKED)) {
nsHTMLSelectOptionAccessible::SelectionChangedIfOption(aContent);
nsAccessible* item = GetAccessible(aContent);
if (item) {
nsAccessible* widget = item->ContainerWidget();
if (widget && widget->IsSelect()) {
AccSelChangeEvent::SelChangeType selChangeType =
aContent->AsElement()->State().HasState(NS_EVENT_STATE_CHECKED) ?
AccSelChangeEvent::eSelectionAdd : AccSelChangeEvent::eSelectionRemove;
nsRefPtr<AccEvent> event = new AccSelChangeEvent(widget, item,
selChangeType);
FireDelayedAccessibleEvent(event);
}
}
}
if (aStateMask.HasState(NS_EVENT_STATE_INVALID)) {

Просмотреть файл

@ -473,6 +473,8 @@ nsRootAccessible::ProcessDOMEvent(nsIDOMEvent* aDOMEvent)
}
if (treeItemAccessible && eventType.EqualsLiteral("select")) {
// XXX: We shouldn't be based on DOM select event which doesn't provide us
// any context info. We should integrate into nsTreeSelection instead.
// If multiselect tree, we should fire selectionadd or selection removed
if (FocusMgr()->HasDOMFocus(targetNode)) {
nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSel =

Просмотреть файл

@ -416,59 +416,7 @@ nsHTMLSelectOptionAccessible::SetSelected(bool aSelect)
nsAccessible*
nsHTMLSelectOptionAccessible::ContainerWidget() const
{
if (mParent && mParent->IsListControl()) {
nsAccessible* grandParent = mParent->Parent();
if (grandParent && grandParent->IsCombobox())
return grandParent;
return mParent;
}
return nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsHTMLSelectOptionAccessible: static methods
void
nsHTMLSelectOptionAccessible::SelectionChangedIfOption(nsIContent *aPossibleOptionNode)
{
if (!aPossibleOptionNode ||
aPossibleOptionNode->Tag() != nsGkAtoms::option ||
!aPossibleOptionNode->IsHTML()) {
return;
}
nsAccessible *multiSelect =
nsAccUtils::GetMultiSelectableContainer(aPossibleOptionNode);
if (!multiSelect)
return;
nsAccessible *option = GetAccService()->GetAccessible(aPossibleOptionNode);
if (!option)
return;
nsRefPtr<AccEvent> selWithinEvent =
new AccEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, multiSelect);
if (!selWithinEvent)
return;
option->GetDocAccessible()->FireDelayedAccessibleEvent(selWithinEvent);
PRUint64 state = option->State();
PRUint32 eventType;
if (state & states::SELECTED) {
eventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
}
else {
eventType = nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
}
nsRefPtr<AccEvent> selAddRemoveEvent = new AccEvent(eventType, option);
if (selAddRemoveEvent)
option->GetDocAccessible()->FireDelayedAccessibleEvent(selAddRemoveEvent);
return mParent && mParent->IsListControl() ? mParent : nsnull;
}
////////////////////////////////////////////////////////////////////////////////
@ -738,26 +686,22 @@ nsHTMLComboboxAccessible::AreItemsOperable() const
nsAccessible*
nsHTMLComboboxAccessible::CurrentItem()
{
// No current item for collapsed combobox.
return SelectedOption(true);
return AreItemsOperable() ? mListAccessible->CurrentItem() : nsnull;
}
////////////////////////////////////////////////////////////////////////////////
// nsHTMLComboboxAccessible: protected
nsAccessible*
nsHTMLComboboxAccessible::SelectedOption(bool aIgnoreIfCollapsed) const
nsHTMLComboboxAccessible::SelectedOption() const
{
nsIFrame* frame = GetFrame();
nsIComboboxControlFrame* comboboxFrame = do_QueryFrame(frame);
if (comboboxFrame) {
if (aIgnoreIfCollapsed && !comboboxFrame->IsDroppedDown())
if (!comboboxFrame)
return nsnull;
frame = comboboxFrame->GetDropDown();
}
nsIListControlFrame* listControlFrame = do_QueryFrame(frame);
nsIListControlFrame* listControlFrame =
do_QueryFrame(comboboxFrame->GetDropDown());
if (listControlFrame) {
nsCOMPtr<nsIContent> activeOptionNode = listControlFrame->GetCurrentOption();
if (activeOptionNode) {
@ -858,3 +802,19 @@ void nsHTMLComboboxListAccessible::GetBoundsRect(nsRect& aBounds, nsIFrame** aBo
*aBoundingFrame = frame->GetParent();
aBounds = (*aBoundingFrame)->GetRect();
}
////////////////////////////////////////////////////////////////////////////////
// nsHTMLComboboxListAccessible: Widgets
bool
nsHTMLComboboxListAccessible::IsActiveWidget() const
{
return mParent && mParent->IsActiveWidget();
}
bool
nsHTMLComboboxListAccessible::AreItemsOperable() const
{
return mParent && mParent->AreItemsOperable();
}

Просмотреть файл

@ -130,8 +130,6 @@ public:
// Widgets
virtual nsAccessible* ContainerWidget() const;
static void SelectionChangedIfOption(nsIContent *aPossibleOption);
protected:
// nsAccessible
virtual nsIFrame* GetBoundsFrame();
@ -219,7 +217,7 @@ protected:
/**
* Return selected option.
*/
nsAccessible* SelectedOption(bool aIgnoreIfCollapsed = false) const;
nsAccessible* SelectedOption() const;
private:
nsRefPtr<nsHTMLComboboxListAccessible> mListAccessible;
@ -246,6 +244,10 @@ public:
// nsAccessible
virtual PRUint64 NativeState();
virtual void GetBoundsRect(nsRect& aBounds, nsIFrame** aBoundingFrame);
// Widgets
virtual bool IsActiveWidget() const;
virtual bool AreItemsOperable() const;
};
#endif

Просмотреть файл

@ -90,7 +90,7 @@ static const PRUint32 gWinEventMap[] = {
IA2_EVENT_DOCUMENT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_DOCUMENT_ATTRIBUTES_CHANGED
IA2_EVENT_DOCUMENT_CONTENT_CHANGED, // nsIAccessibleEvent::EVENT_DOCUMENT_CONTENT_CHANGED
kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_PROPERTY_CHANGED
kEVENT_WIN_UNKNOWN, // nsIAccessibleEvent::EVENT_SELECTION_CHANGED
IA2_EVENT_PAGE_CHANGED, // nsIAccessibleEvent::IA2_EVENT_PAGE_CHANGED
IA2_EVENT_TEXT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED
IA2_EVENT_TEXT_CARET_MOVED, // nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED
IA2_EVENT_TEXT_CHANGED, // nsIAccessibleEvent::EVENT_TEXT_CHANGED
@ -131,7 +131,6 @@ static const PRUint32 gWinEventMap[] = {
IA2_EVENT_HYPERTEXT_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_CHANGED
IA2_EVENT_HYPERTEXT_NLINKS_CHANGED, // nsIAccessibleEvent::EVENT_HYPERTEXT_NLINKS_CHANGED
IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED, // nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED
IA2_EVENT_PAGE_CHANGED, // nsIAccessibleEvent::EVENT_PAGE_CHANGED
kEVENT_LAST_ENTRY // nsIAccessibleEvent::EVENT_LAST_ENTRY
};

Просмотреть файл

@ -15,7 +15,9 @@ const EVENT_MENUPOPUP_END = nsIAccessibleEvent.EVENT_MENUPOPUP_END;
const EVENT_OBJECT_ATTRIBUTE_CHANGED = nsIAccessibleEvent.EVENT_OBJECT_ATTRIBUTE_CHANGED;
const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER;
const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START;
const EVENT_SELECTION = nsIAccessibleEvent.EVENT_SELECTION;
const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD;
const EVENT_SELECTION_REMOVE = nsIAccessibleEvent.EVENT_SELECTION_REMOVE;
const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN;
const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW;
const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE;

Просмотреть файл

@ -82,7 +82,9 @@ _TEST_FILES =\
test_mutation.html \
test_mutation.xhtml \
test_scroll.xul \
test_selection_aria.html \
test_selection.html \
test_selection.xul \
test_statechange.html \
test_text_alg.html \
test_text.html \

Просмотреть файл

@ -22,39 +22,45 @@
////////////////////////////////////////////////////////////////////////////
// Invokers
function addSelection(aNode, aOption)
{
this.DOMNode = aNode;
this.optionNode = aOption;
this.eventSeq = [
new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.DOMNode)),
new invokerChecker(EVENT_SELECTION_ADD, getAccessible(this.optionNode))
];
this.invoke = function addselection_invoke() {
synthesizeMouse(this.optionNode, 1, 1, {});
};
this.getID = function addselection_getID() {
return prettyName(this.optionNode) + " added to selection";
};
}
////////////////////////////////////////////////////////////////////////////
// Do tests
//gA11yEventDumpToConsole = true; // debuggin
var gQueue = null;
//var gA11yEventDumpID = "eventdump"; // debug stuff
function doTests()
{
gQueue = new eventQueue();
var select = document.getElementById("toppings");
var option = document.getElementById("onions");
gQueue.push(new addSelection(select, option));
// open combobox
gQueue.push(new synthClick("combobox",
new invokerChecker(EVENT_FOCUS, "cb1_item1")));
gQueue.push(new synthDownKey("cb1_item1",
new invokerChecker(EVENT_SELECTION, "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")));
// 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")));
// multiselectable listbox
gQueue.push(new synthClick("lb2_item1",
new invokerChecker(EVENT_SELECTION, "lb2_item1")));
gQueue.push(new synthDownKey("lb2_item1",
new invokerChecker(EVENT_SELECTION_ADD, "lb2_item2"),
{ shiftKey: true }));
gQueue.push(new synthUpKey("lb2_item2",
new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item2"),
{ shiftKey: true }));
gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true },
new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item1")));
gQueue.invoke(); // Will call SimpleTest.finish();
}
@ -67,9 +73,9 @@
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=569653"
title="Make selection events async">
Mozilla Bug 569653
href="https://bugzilla.mozilla.org/show_bug.cgi?id=414302"
title="Incorrect selection events in HTML, XUL and ARIA">
Mozilla Bug 414302
</a>
<p id="display"></p>
@ -77,18 +83,31 @@
<pre id="test">
</pre>
<p>Pizza</p>
<select id="toppings" name="toppings" multiple size=5>
<option value="mushrooms">mushrooms
<option value="greenpeppers">green peppers
<option value="onions" id="onions">onions
<option value="tomatoes">tomatoes
<option value="olives">olives
<select id="combobox">
<option id="cb1_item1" value="mushrooms">mushrooms
<option id="cb1_item2" value="greenpeppers">green peppers
<option id="cb1_item3" value="onions" id="onions">onions
<option id="cb1_item4" value="tomatoes">tomatoes
<option id="cb1_item5" value="olives">olives
</select>
<select id="listbox" size=5>
<option id="lb1_item1" value="mushrooms">mushrooms
<option id="lb1_item2" value="greenpeppers">green peppers
<option id="lb1_item3" value="onions" id="onions">onions
<option id="lb1_item4" value="tomatoes">tomatoes
<option id="lb1_item5" value="olives">olives
</select>
<p>Pizza</p>
<select id="listbox2" multiple size=5>
<option id="lb2_item1" value="mushrooms">mushrooms
<option id="lb2_item2" value="greenpeppers">green peppers
<option id="lb2_item3" value="onions" id="onions">onions
<option id="lb2_item4" value="tomatoes">tomatoes
<option id="lb2_item5" value="olives">olives
</select>
<div id="testContainer">
<iframe id="iframe"></iframe>
</div>
<div id="eventdump"></div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,244 @@
<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
type="text/css"?>
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title="Selection event tests">
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"/>
<script type="application/javascript"
src="../common.js" />
<script type="application/javascript"
src="../states.js" />
<script type="application/javascript"
src="../events.js" />
<script type="application/javascript">
function advanceTab(aTabsID, aDirection, aNextTabID)
{
this.eventSeq = [
new invokerChecker(EVENT_SELECTION, aNextTabID)
];
this.invoke = function advanceTab_invoke()
{
getNode(aTabsID).advanceSelectedTab(aDirection, true);
}
this.getID = function synthFocus_getID()
{
return "advanceTab on " + prettyName(aTabsID) + " to " + prettyName(aNextTabID);
}
}
function select4FirstItems(aID)
{
this.listboxNode = getNode(aID);
this.eventSeq = [
new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0)),
new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(1)),
new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(2)),
new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(3))
];
this.invoke = function select4FirstItems_invoke()
{
synthesizeKey("VK_DOWN", { shiftKey: true }); // selects two items
synthesizeKey("VK_DOWN", { shiftKey: true });
synthesizeKey("VK_DOWN", { shiftKey: true });
}
this.getID = function select4FirstItems_getID()
{
return "select 4 first items for " + prettyName(aID);
}
}
function unselect4FirstItems(aID)
{
this.listboxNode = getNode(aID);
this.eventSeq = [
new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(3)),
new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(2)),
new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(1)),
new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0))
];
this.invoke = function unselect4FirstItems_invoke()
{
synthesizeKey("VK_UP", { shiftKey: true });
synthesizeKey("VK_UP", { shiftKey: true });
synthesizeKey("VK_UP", { shiftKey: true });
synthesizeKey(" ", { ctrlKey: true }); // unselect first item
}
this.getID = function unselect4FirstItems_getID()
{
return "unselect 4 first items for " + prettyName(aID);
}
}
function selectAllItems(aID)
{
this.listboxNode = getNode(aID);
this.eventSeq = [
new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode))
];
this.invoke = function selectAllItems_invoke()
{
synthesizeKey("VK_END", { shiftKey: true });
}
this.getID = function selectAllItems_getID()
{
return "select all items for " + prettyName(aID);
}
}
function unselectAllItemsButFirst(aID)
{
this.listboxNode = getNode(aID);
this.eventSeq = [
new invokerChecker(EVENT_SELECTION_WITHIN, getAccessible(this.listboxNode))
];
this.invoke = function unselectAllItemsButFirst_invoke()
{
synthesizeKey("VK_HOME", { shiftKey: true });
}
this.getID = function unselectAllItemsButFirst_getID()
{
return "unselect all items for " + prettyName(aID);
}
}
function unselectSelectItem(aID)
{
this.listboxNode = getNode(aID);
this.eventSeq = [
new invokerChecker(EVENT_SELECTION_REMOVE, this.listboxNode.getItemAtIndex(0)),
new invokerChecker(EVENT_SELECTION_ADD, this.listboxNode.getItemAtIndex(0))
];
this.invoke = function unselectSelectItem_invoke()
{
synthesizeKey(" ", { ctrlKey: true }); // select item
synthesizeKey(" ", { ctrlKey: true }); // unselect item
}
this.getID = function unselectSelectItem_getID()
{
return "unselect and then select first item for " + prettyName(aID);
}
}
/**
* Do tests.
*/
var gQueue = null;
//gA11yEventDumpToConsole = true; // debuggin
function doTests()
{
gQueue = new eventQueue();
//////////////////////////////////////////////////////////////////////////
// tabbox
gQueue.push(new advanceTab("tabs", 1, "tab3"));
//////////////////////////////////////////////////////////////////////////
// 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")));
//////////////////////////////////////////////////////////////////////////
// multiselectable listbox
gQueue.push(new synthClick("lb2_item1",
new invokerChecker(EVENT_SELECTION, "lb2_item1")));
gQueue.push(new synthDownKey("lb2_item1",
new invokerChecker(EVENT_SELECTION_ADD, "lb2_item2"),
{ shiftKey: true }));
gQueue.push(new synthUpKey("lb2_item2",
new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item2"),
{ shiftKey: true }));
gQueue.push(new synthKey("lb2_item1", " ", { ctrlKey: true },
new invokerChecker(EVENT_SELECTION_REMOVE, "lb2_item1")));
//////////////////////////////////////////////////////////////////////////
// selection event coalescence
// fire 4 selection_add events
gQueue.push(new select4FirstItems("listbox2"));
// fire 4 selection_remove events
gQueue.push(new unselect4FirstItems("listbox2"));
// fire selection_within event
gQueue.push(new selectAllItems("listbox2"));
// fire selection_within event
gQueue.push(new unselectAllItemsButFirst("listbox2"));
// fire selection_remove/add events
gQueue.push(new unselectSelectItem("listbox2"));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
<hbox flex="1" style="overflow: auto;">
<body xmlns="http://www.w3.org/1999/xhtml">
<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
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
</body>
<tabbox id="tabbox" selectedIndex="1">
<tabs id="tabs">
<tab id="tab1" label="tab1"/>
<tab id="tab2" label="tab2"/>
<tab id="tab3" label="tab3"/>
<tab id="tab4" label="tab4"/>
</tabs>
<tabpanels>
<tabpanel><!-- tabpanel First elements go here --></tabpanel>
<tabpanel><button id="b1" label="b1"/></tabpanel>
<tabpanel><button id="b2" label="b2"/></tabpanel>
<tabpanel></tabpanel>
</tabpanels>
</tabbox>
<listbox id="listbox">
<listitem id="lb1_item1" label="item1"/>
<listitem id="lb1_item2" label="item2"/>
</listbox>
<listbox id="listbox2" seltype="multiple">
<listitem id="lb2_item1" label="item1"/>
<listitem id="lb2_item2" label="item2"/>
<listitem id="lb2_item3" label="item3"/>
<listitem id="lb2_item4" label="item4"/>
<listitem id="lb2_item5" label="item5"/>
<listitem id="lb2_item6" label="item6"/>
<listitem id="lb2_item7" label="item7"/>
</listbox>
</hbox>
</window>

Просмотреть файл

@ -0,0 +1,112 @@
<html>
<head>
<title>ARIA selection event testing</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
<script type="application/javascript"
src="../common.js"></script>
<script type="application/javascript"
src="../events.js"></script>
<script type="application/javascript"
src="../states.js"></script>
<script type="application/javascript">
////////////////////////////////////////////////////////////////////////////
// Invokers
function selectTreeItem(aTreeID, aItemID)
{
this.treeNode = getNode(aTreeID);
this.itemNode = getNode(aItemID);
this.eventSeq = [
new invokerChecker(EVENT_SELECTION, aItemID)
];
this.invoke = function selectTreeItem_invoke() {
var itemNode = this.treeNode.querySelector("*[aria-selected='true']");
if (itemNode)
itemNode.removeAttribute("aria-selected", "true");
this.itemNode.setAttribute("aria-selected", "true");
}
this.getID = function selectTreeItem_getID()
{
return "selectTreeItem " + prettyName(aItemID);
}
}
////////////////////////////////////////////////////////////////////////////
// Do tests
var gQueue = null;
//var gA11yEventDumpID = "eventdump"; // debug stuff
function doTests()
{
gQueue = new eventQueue();
gQueue.push(new selectTreeItem("tree", "treeitem1"));
gQueue.push(new selectTreeItem("tree", "treeitem1a"));
gQueue.push(new selectTreeItem("tree", "treeitem1a1"));
gQueue.push(new selectTreeItem("tree2", "tree2item1"));
gQueue.push(new selectTreeItem("tree2", "tree2item1a"));
gQueue.push(new selectTreeItem("tree2", "tree2item1a1"));
gQueue.invoke(); // Will call SimpleTest.finish();
}
SimpleTest.waitForExplicitFinish();
addA11yLoadEvent(doTests);
</script>
</head>
<body>
<a target="_blank"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=569653"
title="Make selection events async">
Mozilla Bug 569653
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div id="tree" role="tree">
<div id="treeitem1" role="treeitem">Canada
<div id="treeitem1a" role="treeitem">- Ontario
<div id="treeitem1a1" role="treeitem">-- Toronto</div>
</div>
<div id="treeitem1b" role="treeitem">- Manitoba</div>
</div>
<div id="treeitem2" role="treeitem">Germany</div>
<div id="treeitem3" role="treeitem">Russia</div>
</div>
<div id="tree2" role="tree" aria-multiselectable="true">
<div id="tree2item1" role="treeitem">Canada
<div id="tree2item1a" role="treeitem">- Ontario
<div id="tree2item1a1" role="treeitem">-- Toronto</div>
</div>
<div id="tree2item1b" role="treeitem">- Manitoba</div>
</div>
<div id="tree2item2" role="treeitem">Germany</div>
<div id="tree2item3" role="treeitem">Russia</div>
</div>
<div id="eventdump"></div>
</body>
</html>