Bug 513213 - coalesce events when new event is appended to the queue, r=marcoz, ginn, davidb

This commit is contained in:
Alexander Surkov 2009-09-03 10:01:18 +08:00
Родитель fe0b9938ed
Коммит b8458eef4c
8 изменённых файлов: 630 добавлений и 157 удалений

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

@ -311,7 +311,8 @@ void
nsAccEvent::ApplyEventRules(nsCOMArray<nsIAccessibleEvent> &aEventsToFire) nsAccEvent::ApplyEventRules(nsCOMArray<nsIAccessibleEvent> &aEventsToFire)
{ {
PRUint32 numQueuedEvents = aEventsToFire.Count(); PRUint32 numQueuedEvents = aEventsToFire.Count();
for (PRInt32 tail = numQueuedEvents - 1; tail >= 0; tail --) { PRInt32 tail = numQueuedEvents - 1;
nsRefPtr<nsAccEvent> tailEvent = GetAccEventPtr(aEventsToFire[tail]); nsRefPtr<nsAccEvent> tailEvent = GetAccEventPtr(aEventsToFire[tail]);
switch(tailEvent->mEventRule) { switch(tailEvent->mEventRule) {
case nsAccEvent::eCoalesceFromSameSubtree: case nsAccEvent::eCoalesceFromSameSubtree:
@ -393,7 +394,6 @@ nsAccEvent::ApplyEventRules(nsCOMArray<nsIAccessibleEvent> &aEventsToFire)
default: default:
break; // case eAllowDupes, eDoNotEmit break; // case eAllowDupes, eDoNotEmit
} // switch } // switch
} // for (tail)
} }
/* static */ /* static */

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

@ -1185,7 +1185,7 @@ nsDocAccessible::AttributeChangedImpl(nsIContent* aContent, PRInt32 aNameSpaceID
aAttribute == nsAccessibilityAtoms::title || aAttribute == nsAccessibilityAtoms::title ||
aAttribute == nsAccessibilityAtoms::aria_label || aAttribute == nsAccessibilityAtoms::aria_label ||
aAttribute == nsAccessibilityAtoms::aria_labelledby) { aAttribute == nsAccessibilityAtoms::aria_labelledby) {
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE,
targetNode); targetNode);
return; return;
} }
@ -1208,7 +1208,7 @@ nsDocAccessible::AttributeChangedImpl(nsIContent* aContent, PRInt32 aNameSpaceID
nsCOMPtr<nsIDOMNode> multiSelectDOMNode; nsCOMPtr<nsIDOMNode> multiSelectDOMNode;
multiSelectAccessNode->GetDOMNode(getter_AddRefs(multiSelectDOMNode)); multiSelectAccessNode->GetDOMNode(getter_AddRefs(multiSelectDOMNode));
NS_ASSERTION(multiSelectDOMNode, "A new accessible without a DOM node!"); NS_ASSERTION(multiSelectDOMNode, "A new accessible without a DOM node!");
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN, FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
multiSelectDOMNode, multiSelectDOMNode,
nsAccEvent::eAllowDupes); nsAccEvent::eAllowDupes);
@ -1216,12 +1216,12 @@ nsDocAccessible::AttributeChangedImpl(nsIContent* aContent, PRInt32 aNameSpaceID
{&nsAccessibilityAtoms::_empty, &nsAccessibilityAtoms::_false, nsnull}; {&nsAccessibilityAtoms::_empty, &nsAccessibilityAtoms::_false, nsnull};
if (aContent->FindAttrValueIn(kNameSpaceID_None, aAttribute, if (aContent->FindAttrValueIn(kNameSpaceID_None, aAttribute,
strings, eCaseMatters) >= 0) { strings, eCaseMatters) >= 0) {
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_REMOVE, FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_REMOVE,
targetNode); targetNode);
return; return;
} }
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_ADD, FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_ADD,
targetNode); targetNode);
} }
} }
@ -1340,7 +1340,8 @@ nsDocAccessible::ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute)
aContent->AttrValueIs(kNameSpaceID_None, aContent->AttrValueIs(kNameSpaceID_None,
nsAccessibilityAtoms::aria_valuetext, nsAccessibilityAtoms::_empty, nsAccessibilityAtoms::aria_valuetext, nsAccessibilityAtoms::_empty,
eCaseMatters)))) { eCaseMatters)))) {
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, targetNode); FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE,
targetNode);
return; return;
} }
@ -1357,7 +1358,7 @@ nsDocAccessible::ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute)
// at least until native API comes up with a more meaningful event. // at least until native API comes up with a more meaningful event.
if (aAttribute == nsAccessibilityAtoms::aria_grabbed || if (aAttribute == nsAccessibilityAtoms::aria_grabbed ||
aAttribute == nsAccessibilityAtoms::aria_dropeffect) { aAttribute == nsAccessibilityAtoms::aria_dropeffect) {
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED, FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
targetNode); targetNode);
} }
} }
@ -1592,13 +1593,14 @@ nsDocAccessible::CreateTextChangeEventForNode(nsIAccessible *aContainerAccessibl
return event; return event;
} }
nsresult nsDocAccessible::FireDelayedToolkitEvent(PRUint32 aEvent, nsresult
nsDocAccessible::FireDelayedAccessibleEvent(PRUint32 aEventType,
nsIDOMNode *aDOMNode, nsIDOMNode *aDOMNode,
nsAccEvent::EEventRule aAllowDupes, nsAccEvent::EEventRule aAllowDupes,
PRBool aIsAsynch) PRBool aIsAsynch)
{ {
nsCOMPtr<nsIAccessibleEvent> event = nsCOMPtr<nsIAccessibleEvent> event =
new nsAccEvent(aEvent, aDOMNode, aIsAsynch, aAllowDupes); new nsAccEvent(aEventType, aDOMNode, aIsAsynch, aAllowDupes);
NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
return FireDelayedAccessibleEvent(event); return FireDelayedAccessibleEvent(event);
@ -1616,6 +1618,10 @@ nsDocAccessible::FireDelayedAccessibleEvent(nsIAccessibleEvent *aEvent)
} }
mEventsToFire.AppendObject(aEvent); mEventsToFire.AppendObject(aEvent);
// Filter events.
nsAccEvent::ApplyEventRules(mEventsToFire);
if (mEventsToFire.Count() == 1) { if (mEventsToFire.Count() == 1) {
// This is be the first delayed event in queue, start timer // This is be the first delayed event in queue, start timer
// so that event gets fired via FlushEventsCallback // so that event gets fired via FlushEventsCallback
@ -1643,9 +1649,6 @@ nsDocAccessible::FlushPendingEvents()
// visibility. We don't flush the display because we don't care about // visibility. We don't flush the display because we don't care about
// painting. If no flush is necessary the method will simple return. // painting. If no flush is necessary the method will simple return.
presShell->FlushPendingNotifications(Flush_Layout); presShell->FlushPendingNotifications(Flush_Layout);
// filter events
nsAccEvent::ApplyEventRules(mEventsToFire);
} }
for (PRUint32 index = 0; index < length; index ++) { for (PRUint32 index = 0; index < length; index ++) {
@ -2105,7 +2108,7 @@ nsDocAccessible::InvalidateCacheSubtree(nsIContent *aChild,
// nsIAccessibleStates::STATE_INVISIBLE for the event's accessible object. // nsIAccessibleStates::STATE_INVISIBLE for the event's accessible object.
PRUint32 additionEvent = isAsynch ? nsIAccessibleEvent::EVENT_ASYNCH_SHOW : PRUint32 additionEvent = isAsynch ? nsIAccessibleEvent::EVENT_ASYNCH_SHOW :
nsIAccessibleEvent::EVENT_DOM_CREATE; nsIAccessibleEvent::EVENT_DOM_CREATE;
FireDelayedToolkitEvent(additionEvent, childNode, FireDelayedAccessibleEvent(additionEvent, childNode,
nsAccEvent::eCoalesceFromSameSubtree, nsAccEvent::eCoalesceFromSameSubtree,
isAsynch); isAsynch);
@ -2113,7 +2116,7 @@ nsDocAccessible::InvalidateCacheSubtree(nsIContent *aChild,
// an EVENT_MENUPOPUP_START if it did. // an EVENT_MENUPOPUP_START if it did.
nsRoleMapEntry *roleMapEntry = nsAccUtils::GetRoleMapEntry(childNode); nsRoleMapEntry *roleMapEntry = nsAccUtils::GetRoleMapEntry(childNode);
if (roleMapEntry && roleMapEntry->role == nsIAccessibleRole::ROLE_MENUPOPUP) { if (roleMapEntry && roleMapEntry->role == nsIAccessibleRole::ROLE_MENUPOPUP) {
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
childNode, nsAccEvent::eRemoveDupes, childNode, nsAccEvent::eRemoveDupes,
isAsynch); isAsynch);
} }
@ -2123,7 +2126,7 @@ nsDocAccessible::InvalidateCacheSubtree(nsIContent *aChild,
while (PR_TRUE) { while (PR_TRUE) {
if (roleMapEntry && roleMapEntry->role == nsIAccessibleRole::ROLE_ALERT) { if (roleMapEntry && roleMapEntry->role == nsIAccessibleRole::ROLE_ALERT) {
nsCOMPtr<nsIDOMNode> alertNode(do_QueryInterface(ancestor)); nsCOMPtr<nsIDOMNode> alertNode(do_QueryInterface(ancestor));
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_ALERT, alertNode, FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_ALERT, alertNode,
nsAccEvent::eRemoveDupes, isAsynch); nsAccEvent::eRemoveDupes, isAsynch);
break; break;
} }

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

@ -121,13 +121,13 @@ public:
/** /**
* Non-virtual method to fire a delayed event after a 0 length timeout. * Non-virtual method to fire a delayed event after a 0 length timeout.
* *
* @param aEvent [in] the nsIAccessibleEvent event type * @param aEventType [in] the nsIAccessibleEvent event type
* @param aDOMNode [in] DOM node the accesible event should be fired for * @param aDOMNode [in] DOM node the accesible event should be fired for
* @param aAllowDupes [in] rule to process an event (see EEventRule constants) * @param aAllowDupes [in] rule to process an event (see EEventRule constants)
* @param aIsAsynch [in] set to PR_TRUE if this is not being called from * @param aIsAsynch [in] set to PR_TRUE if this is not being called from
* code synchronous with a DOM event * code synchronous with a DOM event
*/ */
nsresult FireDelayedToolkitEvent(PRUint32 aEvent, nsIDOMNode *aDOMNode, nsresult FireDelayedAccessibleEvent(PRUint32 aEventType, nsIDOMNode *aDOMNode,
nsAccEvent::EEventRule aAllowDupes = nsAccEvent::eRemoveDupes, nsAccEvent::EEventRule aAllowDupes = nsAccEvent::eRemoveDupes,
PRBool aIsAsynch = PR_FALSE); PRBool aIsAsynch = PR_FALSE);

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

@ -396,7 +396,8 @@ void nsRootAccessible::TryFireEarlyLoadEvent(nsIDOMNode *aDocNode)
NS_ASSERTION(rootContentTreeItem, "No root content tree item"); NS_ASSERTION(rootContentTreeItem, "No root content tree item");
if (rootContentTreeItem == treeItem) { if (rootContentTreeItem == treeItem) {
// No frames or iframes, so we can fire the doc load finished event early // No frames or iframes, so we can fire the doc load finished event early
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_INTERNAL_LOAD, aDocNode); FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_INTERNAL_LOAD,
aDocNode);
} }
} }
@ -538,7 +539,7 @@ PRBool nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *aAccessible,
} }
} }
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_FOCUS, FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_FOCUS,
finalFocusNode, nsAccEvent::eRemoveDupes, finalFocusNode, nsAccEvent::eRemoveDupes,
aIsAsynch); aIsAsynch);
@ -893,7 +894,8 @@ nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent,
FireCurrentFocusEvent(); FireCurrentFocusEvent();
} }
else if (eventType.EqualsLiteral("ValueChange")) { else if (eventType.EqualsLiteral("ValueChange")) {
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aTargetNode, nsAccEvent::eRemoveDupes); FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE,
aTargetNode, nsAccEvent::eRemoveDupes);
} }
#ifdef DEBUG #ifdef DEBUG
else if (eventType.EqualsLiteral("mouseover")) { else if (eventType.EqualsLiteral("mouseover")) {

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

@ -98,6 +98,7 @@ _TEST_FILES =\
test_events_draganddrop.html \ test_events_draganddrop.html \
test_events_focus.xul \ test_events_focus.xul \
test_events_mutation.html \ test_events_mutation.html \
test_events_mutation_coalesce.html \
test_events_tree.xul \ test_events_tree.xul \
test_events_valuechange.html \ test_events_valuechange.html \
test_groupattrs.xul \ test_groupattrs.xul \

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

@ -1,8 +1,11 @@
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Constants // Constants
const EVENT_ASYNCH_HIDE = nsIAccessibleEvent.EVENT_ASYNCH_HIDE;
const EVENT_ASYNCH_SHOW = nsIAccessibleEvent.EVENT_ASYNCH_SHOW;
const EVENT_DOCUMENT_LOAD_COMPLETE = const EVENT_DOCUMENT_LOAD_COMPLETE =
nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE; nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE;
const EVENT_DOM_CREATE = nsIAccessibleEvent.EVENT_DOM_CREATE;
const EVENT_DOM_DESTROY = nsIAccessibleEvent.EVENT_DOM_DESTROY; const EVENT_DOM_DESTROY = nsIAccessibleEvent.EVENT_DOM_DESTROY;
const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS; const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE; const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE;
@ -123,15 +126,10 @@ function invokerChecker(aEventType, aTarget)
* // (used in the case when invoker expects single event). * // (used in the case when invoker expects single event).
* DOMNode getter: function() {}, * DOMNode getter: function() {},
* *
* // Array of items defining events expected (or not expected, see * // Array of checker objects defining expected events on invoker's action.
* // 'doNotExpectEvents' property) on invoker's action.
* // * //
* // Every array item should be either * // Checker object interface:
* // 1) an array consisted from two elements, the first element is DOM or
* // a11y event type, second element is event target (DOM node or
* // accessible).
* // * //
* // 2) object (invoker's checker object) like
* // var checker = { * // var checker = {
* // type getter: function() {}, // DOM or a11y event type * // type getter: function() {}, // DOM or a11y event type
* // target getter: function() {}, // DOM node or accessible * // target getter: function() {}, // DOM node or accessible
@ -141,9 +139,9 @@ function invokerChecker(aEventType, aTarget)
* // }; * // };
* eventSeq getter() {}, * eventSeq getter() {},
* *
* // [optional, used together with 'eventSeq'] Boolean indicates if events * // Array of checker objects defining unexpected events on invoker's
* // specified by 'eventSeq' property shouldn't be triggerd by invoker. * // action.
* doNotExpectEvents getter() {}, * unexpectedEventSeq getter() {},
* *
* // The ID of invoker. * // The ID of invoker.
* getID: function(){} // returns invoker ID * getID: function(){} // returns invoker ID
@ -174,8 +172,7 @@ function eventQueue(aEventType)
// XXX: Intermittent test_events_caretmove.html fails withouth timeout, // XXX: Intermittent test_events_caretmove.html fails withouth timeout,
// see bug 474952. // see bug 474952.
window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 500, this.processNextInvokerInTimeout(true);
this);
} }
/** /**
@ -198,15 +195,20 @@ function eventQueue(aEventType)
var invoker = this.getInvoker(); var invoker = this.getInvoker();
if (invoker) { if (invoker) {
if ("finalCheck" in invoker)
invoker.finalCheck();
if (invoker.wasCaught) { if (invoker.wasCaught) {
for (var idx = 0; idx < invoker.wasCaught.length; idx++) { for (var idx = 0; idx < invoker.wasCaught.length; idx++) {
var id = this.getEventID(idx); var id = this.getEventID(idx);
var type = this.getEventType(idx); var type = this.getEventType(idx);
var unexpected = this.mEventSeq[idx].unexpected;
var typeStr = (typeof type == "string") ? var typeStr = (typeof type == "string") ?
type : gAccRetrieval.getStringEventType(type); type : gAccRetrieval.getStringEventType(type);
var msg = "test with ID = '" + id + "' failed. "; var msg = "test with ID = '" + id + "' failed. ";
if (invoker.doNotExpectEvents) { if (unexpected) {
var wasCaught = invoker.wasCaught[idx]; var wasCaught = invoker.wasCaught[idx];
if (!testFailed) if (!testFailed)
testFailed = wasCaught; testFailed = wasCaught;
@ -256,12 +258,23 @@ function eventQueue(aEventType)
return; return;
} }
if (invoker.doNotExpectEvents) { if (this.areAllEventsUnexpected())
this.processNextInvokerInTimeout(true);
}
this.processNextInvokerInTimeout = function eventQueue_processNextInvokerInTimeout(aUncondProcess)
{
if (!aUncondProcess && this.areAllEventsExpected()) {
// We need delay to avoid events coalesce from different invokers.
var queue = this;
SimpleTest.executeSoon(function() { queue.processNextInvoker(); });
return;
}
// Check in timeout invoker didn't fire registered events. // Check in timeout invoker didn't fire registered events.
window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 500, window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 500,
this); this);
} }
}
/** /**
* Handle events for the current invoker. * Handle events for the current invoker.
@ -282,16 +295,23 @@ function eventQueue(aEventType)
if ("debugCheck" in invoker) if ("debugCheck" in invoker)
invoker.debugCheck(aEvent); invoker.debugCheck(aEvent);
if (invoker.doNotExpectEvents) { // Search through unexpected events to ensure no one of them was handled.
// Search through event sequence to ensure it doesn't contain handled
// event.
for (var idx = 0; idx < this.mEventSeq.length; idx++) { for (var idx = 0; idx < this.mEventSeq.length; idx++) {
if (this.compareEvents(idx, aEvent)) if (this.mEventSeq[idx].unexpected && this.compareEvents(idx, aEvent))
invoker.wasCaught[idx] = true; invoker.wasCaught[idx] = true;
} }
} else {
// We wait for events in order specified by eventSeq variable. // Wait for next expected event in an order specified by event sequence.
var idx = this.mEventSeqIdx + 1;
// Compute next expected event index.
for (var idx = this.mEventSeqIdx + 1;
idx < this.mEventSeq.length && this.mEventSeq[idx].unexpected; idx++);
if (idx == this.mEventSeq.length) {
// There is no expected events in the sequence.
this.processNextInvokerInTimeout();
return;
}
var matched = this.compareEvents(idx, aEvent); var matched = this.compareEvents(idx, aEvent);
this.dumpEventToDOM(aEvent, idx, matched); this.dumpEventToDOM(aEvent, idx, matched);
@ -300,17 +320,15 @@ function eventQueue(aEventType)
this.checkEvent(idx, aEvent); this.checkEvent(idx, aEvent);
invoker.wasCaught[idx] = true; invoker.wasCaught[idx] = true;
// The last event is expected and was handled, proceed next invoker.
if (idx == this.mEventSeq.length - 1) { if (idx == this.mEventSeq.length - 1) {
// We need delay to avoid events coalesce from different invokers. this.processNextInvokerInTimeout();
var queue = this;
SimpleTest.executeSoon(function() { queue.processNextInvoker(); });
return; return;
} }
this.mEventSeqIdx = idx; this.mEventSeqIdx = idx;
} }
} }
}
// Helpers // Helpers
this.getInvoker = function eventQueue_getInvoker() this.getInvoker = function eventQueue_getInvoker()
@ -325,12 +343,26 @@ function eventQueue(aEventType)
this.setEventHandler = function eventQueue_setEventHandler(aInvoker) this.setEventHandler = function eventQueue_setEventHandler(aInvoker)
{ {
// Create unique event sequence concatenating expected and unexpected
// events.
this.mEventSeq = ("eventSeq" in aInvoker) ? this.mEventSeq = ("eventSeq" in aInvoker) ?
aInvoker.eventSeq : aInvoker.eventSeq :
[ new invokerChecker(this.mDefEventType, aInvoker.DOMNode) ]; [ new invokerChecker(this.mDefEventType, aInvoker.DOMNode) ];
for (var idx = 0; idx < this.mEventSeq.length; idx++)
this.mEventSeq[idx].unexpected = false;
var unexpectedSeq = aInvoker.unexpectedEventSeq;
if (unexpectedSeq) {
for (var idx = 0; idx < unexpectedSeq.length; idx++)
unexpectedSeq[idx].unexpected = true;
this.mEventSeq = this.mEventSeq.concat(unexpectedSeq);
}
this.mEventSeqIdx = -1; this.mEventSeqIdx = -1;
// Register event listeners
if (this.mEventSeq) { if (this.mEventSeq) {
aInvoker.wasCaught = new Array(this.mEventSeq.length); aInvoker.wasCaught = new Array(this.mEventSeq.length);
@ -390,6 +422,16 @@ function eventQueue(aEventType)
return true; return true;
} }
this.getEventID = function eventQueue_getEventID(aIdx)
{
var eventItem = this.mEventSeq[aIdx];
if ("getID" in eventItem)
return eventItem.getID();
var invoker = this.getInvoker();
return invoker.getID();
}
this.compareEvents = function eventQueue_compareEvents(aIdx, aEvent) this.compareEvents = function eventQueue_compareEvents(aIdx, aEvent)
{ {
var eventType1 = this.getEventType(aIdx); var eventType1 = this.getEventType(aIdx);
@ -426,14 +468,24 @@ function eventQueue(aEventType)
invoker.check(aEvent); invoker.check(aEvent);
} }
this.getEventID = function eventQueue_getEventID(aIdx) this.areAllEventsExpected = function eventQueue_areAllEventsExpected()
{ {
var eventItem = this.mEventSeq[aIdx]; for (var idx = 0; idx < this.mEventSeq.length; idx++) {
if ("getID" in eventItem) if (this.mEventSeq[idx].unexpected)
return eventItem.getID(); return false;
}
var invoker = this.getInvoker(); return true;
return invoker.getID(); }
this.areAllEventsUnexpected = function eventQueue_areAllEventsUnxpected()
{
for (var idx = 0; idx < this.mEventSeq.length; idx++) {
if (!this.mEventSeq[idx].unexpected)
return false;
}
return true;
} }
this.dumpEventToDOM = function eventQueue_dumpEventToDOM(aOrigEvent, this.dumpEventToDOM = function eventQueue_dumpEventToDOM(aOrigEvent,

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

@ -52,6 +52,7 @@
this.DOMNode = getNode(aNodeOrID); this.DOMNode = getNode(aNodeOrID);
this.doNotExpectEvents = aDoNotExpectEvents; this.doNotExpectEvents = aDoNotExpectEvents;
this.eventSeq = []; this.eventSeq = [];
this.unexpectedEventSeq = [];
/** /**
* Change default target (aNodeOrID) registered for the given event type. * Change default target (aNodeOrID) registered for the given event type.
@ -59,9 +60,9 @@
this.setTarget = function mutateA11yTree_setTarget(aEventType, aTarget) this.setTarget = function mutateA11yTree_setTarget(aEventType, aTarget)
{ {
var type = this.getA11yEventType(aEventType); var type = this.getA11yEventType(aEventType);
for (var idx = 0; idx < this.eventSeq.length; idx++) { for (var idx = 0; idx < this.getEventSeq().length; idx++) {
if (this.eventSeq[idx].type == type) { if (this.getEventSeq()[idx].type == type) {
this.eventSeq[idx].target = aTarget; this.getEventSeq()[idx].target = aTarget;
return idx; return idx;
} }
} }
@ -78,7 +79,7 @@
var type = this.getA11yEventType(aEventType); var type = this.getA11yEventType(aEventType);
for (var i = 1; i < aTargets.length; i++) { for (var i = 1; i < aTargets.length; i++) {
var checker = new invokerChecker(type, aTargets[i]); var checker = new invokerChecker(type, aTargets[i]);
this.eventSeq.splice(++targetIdx, 0, checker); this.getEventSeq().splice(++targetIdx, 0, checker);
} }
} }
@ -103,24 +104,29 @@
} }
} }
this.getEventSeq = function mutateA11yTree_getEventSeq()
{
return this.doNotExpectEvents ? this.unexpectedEventSeq : this.eventSeq;
}
this.mIsDOMChange = aIsDOMChange; this.mIsDOMChange = aIsDOMChange;
if (aEventTypes & kHideEvent) { if (aEventTypes & kHideEvent) {
var checker = new invokerChecker(this.getA11yEventType(kHideEvent), var checker = new invokerChecker(this.getA11yEventType(kHideEvent),
this.DOMNode); this.DOMNode);
this.eventSeq.push(checker); this.getEventSeq().push(checker);
} }
if (aEventTypes & kShowEvent) { if (aEventTypes & kShowEvent) {
var checker = new invokerChecker(this.getA11yEventType(kShowEvent), var checker = new invokerChecker(this.getA11yEventType(kShowEvent),
this.DOMNode); this.DOMNode);
this.eventSeq.push(checker); this.getEventSeq().push(checker);
} }
if (aEventTypes & kReorderEvent) { if (aEventTypes & kReorderEvent) {
var checker = new invokerChecker(this.getA11yEventType(kReorderEvent), var checker = new invokerChecker(this.getA11yEventType(kReorderEvent),
this.DOMNode.parentNode); this.DOMNode.parentNode);
this.eventSeq.push(checker); this.getEventSeq().push(checker);
} }
} }

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

@ -0,0 +1,409 @@
<html>
<head>
<title>Accessible mutation events coalescence testing</title>
<link rel="stylesheet" type="text/css"
href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script type="application/javascript"
src="chrome://mochikit/content/MochiKit/packed.js"></script>
<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="chrome://mochikit/content/a11y/accessible/common.js"></script>
<script type="application/javascript"
src="chrome://mochikit/content/a11y/accessible/events.js"></script>
<script type="application/javascript">
////////////////////////////////////////////////////////////////////////////
// Invoker base classes
const kRemoveElm = 1;
const kHideElm = 2;
const kAddElm = 3;
const kShowElm = 4;
/**
* Base class to test of mutation events coalescence.
*/
function coalescenceBase(aChildAction, aParentAction,
aPerformActionOnChildInTheFirstPlace,
aIsChildsToDo)
{
// Invoker interface
this.invoke = function coalescenceBase_invoke()
{
if (aPerformActionOnChildInTheFirstPlace) {
this.invokeAction(this.childNode, aChildAction);
this.invokeAction(this.parentNode, aParentAction);
} else {
this.invokeAction(this.parentNode, aParentAction);
this.invokeAction(this.childNode, aChildAction);
}
}
this.getID = function coalescenceBase_getID()
{
var childAction = this.getActionName(aChildAction) + " child";
var parentAction = this.getActionName(aParentAction) + " parent";
if (aPerformActionOnChildInTheFirstPlace)
return childAction + " and then " + parentAction;
return parentAction + " and then " + childAction;
}
this.finalCheck = function coalescenceBase_check()
{
if (!aIsChildsToDo)
return;
todo(false,
"Unexpected event " + this.getEventType(aChildAction) +
" for child in the test '" + this.getID() + "'");
}
// Implementation details
this.invokeAction = function coalescenceBase_invokeAction(aNode, aAction)
{
switch (aAction) {
case kRemoveElm:
aNode.parentNode.removeChild(aNode);
break;
case kHideElm:
aNode.style.display = "none";
break;
case kAddElm:
if (aNode == this.parentNode)
this.hostNode.appendChild(this.parentNode);
else
this.parentNode.appendChild(this.childNode);
break;
case kShowElm:
aNode.style.display = "block";
default:
return INVOKER_ACTION_FAILED;
}
}
this.getEventType = function coalescenceBase_getEventType(aAction)
{
switch (aAction) {
case kRemoveElm:
return EVENT_DOM_DESTROY;
case kHideElm:
return EVENT_ASYNCH_HIDE;
case kAddElm:
return EVENT_DOM_CREATE;
case kShowElm:
return EVENT_ASYNCH_SHOW;
}
}
this.getActionName = function coalescenceBase_getActionName(aAction)
{
switch (aAction) {
case kRemoveElm:
return "remove";
case kHideElm:
return "hide";
case kAddElm:
return "add";
case kShowElm:
return "show";
default:
return "??";
}
}
this.initSequence = function coalescenceBase_initSequence()
{
// expected events
var eventType = this.getEventType(aParentAction);
this.eventSeq = [
new invokerChecker(eventType, this.parentNode),
new invokerChecker(EVENT_REORDER, this.hostNode)
];
// unexpected events
this.unexpectedEventSeq = [
new invokerChecker(EVENT_REORDER, this.parentNode)
];
if (!aIsChildsToDo) {
var eventType = this.getEventType(aChildAction);
var checker = new invokerChecker(eventType, this.childNode);
this.unexpectedEventSeq.unshift(checker);
}
}
}
/**
* Remove or hide mutation events coalescence testing.
*/
function removeOrHidecoalescenceBase(aChildID, aParentID,
aChildAction, aParentAction,
aPerformActionOnChildInTheFirstPlace,
aIsChildsToDo)
{
this.__proto__ = new coalescenceBase(aChildAction, aParentAction,
aPerformActionOnChildInTheFirstPlace,
aIsChildsToDo);
this.init = function removeOrHidecoalescenceBase_init()
{
this.childNode = getNode(aChildID);
this.parentNode = getNode(aParentID);
this.hostNode = this.parentNode.parentNode;
// ensure child accessible is created
getAccessible(this.childNode);
}
// Initalization
this.init();
this.initSequence();
}
////////////////////////////////////////////////////////////////////////////
// Invokers
/**
* Remove child node and then its parent node from DOM tree.
*/
function removeChildNParent(aChildID, aParentID)
{
this.__proto__ = new removeOrHidecoalescenceBase(aChildID, aParentID,
kRemoveElm, kRemoveElm,
true, true);
}
/**
* Remove parent node and then its child node from DOM tree.
*/
function removeParentNChild(aChildID, aParentID)
{
this.__proto__ = new removeOrHidecoalescenceBase(aChildID, aParentID,
kRemoveElm, kRemoveElm,
false);
}
/**
* Hide child node and then its parent node.
*/
function hideChildNParent(aChildID, aParentID)
{
this.__proto__ = new removeOrHidecoalescenceBase(aChildID, aParentID,
kHideElm, kHideElm,
true);
}
/**
* Hide parent node and then its child node.
*/
function hideParentNChild(aChildID, aParentID)
{
this.__proto__ = new removeOrHidecoalescenceBase(aChildID, aParentID,
kHideElm, kHideElm,
false);
}
/**
* Hide child node and then remove its parent node.
*/
function hideChildNRemoveParent(aChildID, aParentID)
{
this.__proto__ = new removeOrHidecoalescenceBase(aChildID, aParentID,
kHideElm, kRemoveElm,
true);
}
/**
* Hide parent node and then remove its child node.
*/
function hideParentNRemoveChild(aChildID, aParentID)
{
this.__proto__ = new removeOrHidecoalescenceBase(aChildID, aParentID,
kRemoveElm, kHideElm,
false, true);
}
/**
* Remove child node and then hide its parent node.
*/
function removeChildNHideParent(aChildID, aParentID)
{
this.__proto__ = new removeOrHidecoalescenceBase(aChildID, aParentID,
kRemoveElm, kHideElm,
true, true);
}
/**
* Remove parent node and then hide its child node.
*/
function removeParentNHideChild(aChildID, aParentID)
{
this.__proto__ = new removeOrHidecoalescenceBase(aChildID, aParentID,
kHideElm, kRemoveElm,
false);
}
/**
* Create and append parent node and create and append child node to it.
*/
function addParentNChild(aHostID, aPerformActionOnChildInTheFirstPlace)
{
this.init = function addParentNChild_init()
{
this.hostNode = getNode(aHostID);
this.parentNode = document.createElement("select");
this.childNode = document.createElement("option");
this.childNode.textContent = "testing";
}
this.__proto__ = new coalescenceBase(kAddElm, kAddElm,
aPerformActionOnChildInTheFirstPlace);
this.init();
this.initSequence();
}
/**
* Show parent node and show child node to it.
*/
function showParentNChild(aParentID, aChildID,
aPerformActionOnChildInTheFirstPlace)
{
this.init = function showParentNChild_init()
{
this.parentNode = getNode(aParentID);
this.hostNode = this.parentNode.parentNode;
this.childNode = getNode(aChildID);
}
this.__proto__ = new coalescenceBase(kShowElm, kShowElm,
aPerformActionOnChildInTheFirstPlace);
this.init();
this.initSequence();
}
/**
* Create and append child node to the DOM and then show parent node.
*/
function showParentNAddChild(aParentID,
aPerformActionOnChildInTheFirstPlace)
{
this.init = function showParentNAddChild_init()
{
this.parentNode = getNode(aParentID);
this.hostNode = this.parentNode.parentNode;
this.childNode = document.createElement("option");
this.childNode.textContent = "testing";
}
this.__proto__ = new coalescenceBase(kAddElm, kShowElm,
aPerformActionOnChildInTheFirstPlace,
true);
this.init();
this.initSequence();
}
////////////////////////////////////////////////////////////////////////////
// Do tests.
var gQueue = null;
// var gA11yEventDumpID = "eventdump"; // debug stuff
function doTests()
{
gQueue = new eventQueue();
gQueue.push(new removeChildNParent("option1", "select1"));
gQueue.push(new removeParentNChild("option2", "select2"));
gQueue.push(new hideChildNParent("option3", "select3"));
gQueue.push(new hideParentNChild("option4", "select4"));
gQueue.push(new hideChildNRemoveParent("option5", "select5"));
gQueue.push(new hideParentNRemoveChild("option6", "select6"));
gQueue.push(new removeChildNHideParent("option7", "select7"));
gQueue.push(new removeParentNHideChild("option8", "select8"));
gQueue.push(new addParentNChild("testContainer", false));
gQueue.push(new addParentNChild("testContainer", true));
gQueue.push(new showParentNChild("select9", "option9", false));
gQueue.push(new showParentNChild("select10", "option10", true));
gQueue.push(new showParentNAddChild("select11", false));
gQueue.push(new showParentNAddChild("select12", true));
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=513213"
title="coalesce events when new event is appended to the queue">
Mozilla Bug 513213
</a>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test">
</pre>
<div id="eventdump"></div>
<div id="testContainer">
<select id="select1">
<option id="option1">option</option>
</select>
<select id="select2">
<option id="option2">option</option>
</select>
<select id="select3">
<option id="option3">option</option>
</select>
<select id="select4">
<option id="option4">option</option>
</select>
<select id="select5">
<option id="option5">option</option>
</select>
<select id="select6">
<option id="option6">option</option>
</select>
<select id="select7">
<option id="option7">option</option>
</select>
<select id="select8">
<option id="option8">option</option>
</select>
<select id="select9" style="display: none">
<option id="option9" style="display: none">testing</option>
</select>
<select id="select10" style="display: none">
<option id="option10" style="display: none">testing</option>
</select>
<select id="select11" style="display: none"></select>
<select id="select12" style="display: none"></select>
</div>
</body>
</html>