зеркало из https://github.com/mozilla/pjs.git
Bug 513213 - coalesce events when new event is appended to the queue, r=marcoz, ginn, davidb
This commit is contained in:
Родитель
fe0b9938ed
Коммит
b8458eef4c
|
@ -311,89 +311,89 @@ void
|
|||
nsAccEvent::ApplyEventRules(nsCOMArray<nsIAccessibleEvent> &aEventsToFire)
|
||||
{
|
||||
PRUint32 numQueuedEvents = aEventsToFire.Count();
|
||||
for (PRInt32 tail = numQueuedEvents - 1; tail >= 0; tail --) {
|
||||
nsRefPtr<nsAccEvent> tailEvent = GetAccEventPtr(aEventsToFire[tail]);
|
||||
switch(tailEvent->mEventRule) {
|
||||
case nsAccEvent::eCoalesceFromSameSubtree:
|
||||
{
|
||||
for (PRInt32 index = 0; index < tail; index ++) {
|
||||
nsRefPtr<nsAccEvent> thisEvent = GetAccEventPtr(aEventsToFire[index]);
|
||||
if (thisEvent->mEventType != tailEvent->mEventType)
|
||||
continue; // Different type
|
||||
PRInt32 tail = numQueuedEvents - 1;
|
||||
|
||||
if (thisEvent->mEventRule == nsAccEvent::eAllowDupes ||
|
||||
thisEvent->mEventRule == nsAccEvent::eDoNotEmit)
|
||||
continue; // Do not need to check
|
||||
nsRefPtr<nsAccEvent> tailEvent = GetAccEventPtr(aEventsToFire[tail]);
|
||||
switch(tailEvent->mEventRule) {
|
||||
case nsAccEvent::eCoalesceFromSameSubtree:
|
||||
{
|
||||
for (PRInt32 index = 0; index < tail; index ++) {
|
||||
nsRefPtr<nsAccEvent> thisEvent = GetAccEventPtr(aEventsToFire[index]);
|
||||
if (thisEvent->mEventType != tailEvent->mEventType)
|
||||
continue; // Different type
|
||||
|
||||
if (thisEvent->mDOMNode == tailEvent->mDOMNode) {
|
||||
if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) {
|
||||
CoalesceReorderEventsFromSameSource(thisEvent, tailEvent);
|
||||
continue;
|
||||
}
|
||||
if (thisEvent->mEventRule == nsAccEvent::eAllowDupes ||
|
||||
thisEvent->mEventRule == nsAccEvent::eDoNotEmit)
|
||||
continue; // Do not need to check
|
||||
|
||||
// Dupe
|
||||
thisEvent->mEventRule = nsAccEvent::eDoNotEmit;
|
||||
if (thisEvent->mDOMNode == tailEvent->mDOMNode) {
|
||||
if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) {
|
||||
CoalesceReorderEventsFromSameSource(thisEvent, tailEvent);
|
||||
continue;
|
||||
}
|
||||
if (nsCoreUtils::IsAncestorOf(tailEvent->mDOMNode,
|
||||
thisEvent->mDOMNode)) {
|
||||
// thisDOMNode is a descendant of tailDOMNode
|
||||
if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) {
|
||||
CoalesceReorderEventsFromSameTree(tailEvent, thisEvent);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do not emit thisEvent, also apply this result to sibling
|
||||
// nodes of thisDOMNode.
|
||||
thisEvent->mEventRule = nsAccEvent::eDoNotEmit;
|
||||
ApplyToSiblings(aEventsToFire, 0, index, thisEvent->mEventType,
|
||||
thisEvent->mDOMNode, nsAccEvent::eDoNotEmit);
|
||||
// Dupe
|
||||
thisEvent->mEventRule = nsAccEvent::eDoNotEmit;
|
||||
continue;
|
||||
}
|
||||
if (nsCoreUtils::IsAncestorOf(tailEvent->mDOMNode,
|
||||
thisEvent->mDOMNode)) {
|
||||
// thisDOMNode is a descendant of tailDOMNode
|
||||
if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) {
|
||||
CoalesceReorderEventsFromSameTree(tailEvent, thisEvent);
|
||||
continue;
|
||||
}
|
||||
if (nsCoreUtils::IsAncestorOf(thisEvent->mDOMNode,
|
||||
tailEvent->mDOMNode)) {
|
||||
// tailDOMNode is a descendant of thisDOMNode
|
||||
if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) {
|
||||
CoalesceReorderEventsFromSameTree(thisEvent, tailEvent);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Do not emit tailEvent, also apply this result to sibling
|
||||
// nodes of tailDOMNode.
|
||||
tailEvent->mEventRule = nsAccEvent::eDoNotEmit;
|
||||
ApplyToSiblings(aEventsToFire, 0, tail, tailEvent->mEventType,
|
||||
tailEvent->mDOMNode, nsAccEvent::eDoNotEmit);
|
||||
break;
|
||||
// Do not emit thisEvent, also apply this result to sibling
|
||||
// nodes of thisDOMNode.
|
||||
thisEvent->mEventRule = nsAccEvent::eDoNotEmit;
|
||||
ApplyToSiblings(aEventsToFire, 0, index, thisEvent->mEventType,
|
||||
thisEvent->mDOMNode, nsAccEvent::eDoNotEmit);
|
||||
continue;
|
||||
}
|
||||
if (nsCoreUtils::IsAncestorOf(thisEvent->mDOMNode,
|
||||
tailEvent->mDOMNode)) {
|
||||
// tailDOMNode is a descendant of thisDOMNode
|
||||
if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) {
|
||||
CoalesceReorderEventsFromSameTree(thisEvent, tailEvent);
|
||||
continue;
|
||||
}
|
||||
} // for (index)
|
||||
|
||||
if (tailEvent->mEventRule != nsAccEvent::eDoNotEmit) {
|
||||
// Not in another event node's subtree, and no other event is in
|
||||
// this event node's subtree.
|
||||
// This event should be emitted
|
||||
// Apply this result to sibling nodes of tailDOMNode
|
||||
// Do not emit tailEvent, also apply this result to sibling
|
||||
// nodes of tailDOMNode.
|
||||
tailEvent->mEventRule = nsAccEvent::eDoNotEmit;
|
||||
ApplyToSiblings(aEventsToFire, 0, tail, tailEvent->mEventType,
|
||||
tailEvent->mDOMNode, nsAccEvent::eAllowDupes);
|
||||
tailEvent->mDOMNode, nsAccEvent::eDoNotEmit);
|
||||
break;
|
||||
}
|
||||
} break; // case eCoalesceFromSameSubtree
|
||||
} // for (index)
|
||||
|
||||
case nsAccEvent::eRemoveDupes:
|
||||
{
|
||||
// Check for repeat events.
|
||||
for (PRInt32 index = 0; index < tail; index ++) {
|
||||
nsRefPtr<nsAccEvent> accEvent = GetAccEventPtr(aEventsToFire[index]);
|
||||
if (accEvent->mEventType == tailEvent->mEventType &&
|
||||
accEvent->mEventRule == tailEvent->mEventRule &&
|
||||
accEvent->mDOMNode == tailEvent->mDOMNode) {
|
||||
accEvent->mEventRule = nsAccEvent::eDoNotEmit;
|
||||
}
|
||||
if (tailEvent->mEventRule != nsAccEvent::eDoNotEmit) {
|
||||
// Not in another event node's subtree, and no other event is in
|
||||
// this event node's subtree.
|
||||
// This event should be emitted
|
||||
// Apply this result to sibling nodes of tailDOMNode
|
||||
ApplyToSiblings(aEventsToFire, 0, tail, tailEvent->mEventType,
|
||||
tailEvent->mDOMNode, nsAccEvent::eAllowDupes);
|
||||
}
|
||||
} break; // case eCoalesceFromSameSubtree
|
||||
|
||||
case nsAccEvent::eRemoveDupes:
|
||||
{
|
||||
// Check for repeat events.
|
||||
for (PRInt32 index = 0; index < tail; index ++) {
|
||||
nsRefPtr<nsAccEvent> accEvent = GetAccEventPtr(aEventsToFire[index]);
|
||||
if (accEvent->mEventType == tailEvent->mEventType &&
|
||||
accEvent->mEventRule == tailEvent->mEventRule &&
|
||||
accEvent->mDOMNode == tailEvent->mDOMNode) {
|
||||
accEvent->mEventRule = nsAccEvent::eDoNotEmit;
|
||||
}
|
||||
} break; // case eRemoveDupes
|
||||
}
|
||||
} break; // case eRemoveDupes
|
||||
|
||||
default:
|
||||
break; // case eAllowDupes, eDoNotEmit
|
||||
} // switch
|
||||
} // for (tail)
|
||||
default:
|
||||
break; // case eAllowDupes, eDoNotEmit
|
||||
} // switch
|
||||
}
|
||||
|
||||
/* static */
|
||||
|
|
|
@ -1185,8 +1185,8 @@ nsDocAccessible::AttributeChangedImpl(nsIContent* aContent, PRInt32 aNameSpaceID
|
|||
aAttribute == nsAccessibilityAtoms::title ||
|
||||
aAttribute == nsAccessibilityAtoms::aria_label ||
|
||||
aAttribute == nsAccessibilityAtoms::aria_labelledby) {
|
||||
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE,
|
||||
targetNode);
|
||||
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE,
|
||||
targetNode);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1208,21 +1208,21 @@ nsDocAccessible::AttributeChangedImpl(nsIContent* aContent, PRInt32 aNameSpaceID
|
|||
nsCOMPtr<nsIDOMNode> multiSelectDOMNode;
|
||||
multiSelectAccessNode->GetDOMNode(getter_AddRefs(multiSelectDOMNode));
|
||||
NS_ASSERTION(multiSelectDOMNode, "A new accessible without a DOM node!");
|
||||
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
|
||||
multiSelectDOMNode,
|
||||
nsAccEvent::eAllowDupes);
|
||||
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
|
||||
multiSelectDOMNode,
|
||||
nsAccEvent::eAllowDupes);
|
||||
|
||||
static nsIContent::AttrValuesArray strings[] =
|
||||
{&nsAccessibilityAtoms::_empty, &nsAccessibilityAtoms::_false, nsnull};
|
||||
if (aContent->FindAttrValueIn(kNameSpaceID_None, aAttribute,
|
||||
strings, eCaseMatters) >= 0) {
|
||||
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_REMOVE,
|
||||
targetNode);
|
||||
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_REMOVE,
|
||||
targetNode);
|
||||
return;
|
||||
}
|
||||
|
||||
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_ADD,
|
||||
targetNode);
|
||||
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SELECTION_ADD,
|
||||
targetNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1340,7 +1340,8 @@ nsDocAccessible::ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute)
|
|||
aContent->AttrValueIs(kNameSpaceID_None,
|
||||
nsAccessibilityAtoms::aria_valuetext, nsAccessibilityAtoms::_empty,
|
||||
eCaseMatters)))) {
|
||||
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, targetNode);
|
||||
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE,
|
||||
targetNode);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1357,8 +1358,8 @@ nsDocAccessible::ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute)
|
|||
// at least until native API comes up with a more meaningful event.
|
||||
if (aAttribute == nsAccessibilityAtoms::aria_grabbed ||
|
||||
aAttribute == nsAccessibilityAtoms::aria_dropeffect) {
|
||||
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
|
||||
targetNode);
|
||||
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_OBJECT_ATTRIBUTE_CHANGED,
|
||||
targetNode);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1592,13 +1593,14 @@ nsDocAccessible::CreateTextChangeEventForNode(nsIAccessible *aContainerAccessibl
|
|||
return event;
|
||||
}
|
||||
|
||||
nsresult nsDocAccessible::FireDelayedToolkitEvent(PRUint32 aEvent,
|
||||
nsIDOMNode *aDOMNode,
|
||||
nsAccEvent::EEventRule aAllowDupes,
|
||||
PRBool aIsAsynch)
|
||||
nsresult
|
||||
nsDocAccessible::FireDelayedAccessibleEvent(PRUint32 aEventType,
|
||||
nsIDOMNode *aDOMNode,
|
||||
nsAccEvent::EEventRule aAllowDupes,
|
||||
PRBool aIsAsynch)
|
||||
{
|
||||
nsCOMPtr<nsIAccessibleEvent> event =
|
||||
new nsAccEvent(aEvent, aDOMNode, aIsAsynch, aAllowDupes);
|
||||
new nsAccEvent(aEventType, aDOMNode, aIsAsynch, aAllowDupes);
|
||||
NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
|
||||
|
||||
return FireDelayedAccessibleEvent(event);
|
||||
|
@ -1616,6 +1618,10 @@ nsDocAccessible::FireDelayedAccessibleEvent(nsIAccessibleEvent *aEvent)
|
|||
}
|
||||
|
||||
mEventsToFire.AppendObject(aEvent);
|
||||
|
||||
// Filter events.
|
||||
nsAccEvent::ApplyEventRules(mEventsToFire);
|
||||
|
||||
if (mEventsToFire.Count() == 1) {
|
||||
// This is be the first delayed event in queue, start timer
|
||||
// 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
|
||||
// painting. If no flush is necessary the method will simple return.
|
||||
presShell->FlushPendingNotifications(Flush_Layout);
|
||||
|
||||
// filter events
|
||||
nsAccEvent::ApplyEventRules(mEventsToFire);
|
||||
}
|
||||
|
||||
for (PRUint32 index = 0; index < length; index ++) {
|
||||
|
@ -2105,17 +2108,17 @@ nsDocAccessible::InvalidateCacheSubtree(nsIContent *aChild,
|
|||
// nsIAccessibleStates::STATE_INVISIBLE for the event's accessible object.
|
||||
PRUint32 additionEvent = isAsynch ? nsIAccessibleEvent::EVENT_ASYNCH_SHOW :
|
||||
nsIAccessibleEvent::EVENT_DOM_CREATE;
|
||||
FireDelayedToolkitEvent(additionEvent, childNode,
|
||||
nsAccEvent::eCoalesceFromSameSubtree,
|
||||
isAsynch);
|
||||
FireDelayedAccessibleEvent(additionEvent, childNode,
|
||||
nsAccEvent::eCoalesceFromSameSubtree,
|
||||
isAsynch);
|
||||
|
||||
// Check to see change occured in an ARIA menu, and fire
|
||||
// an EVENT_MENUPOPUP_START if it did.
|
||||
nsRoleMapEntry *roleMapEntry = nsAccUtils::GetRoleMapEntry(childNode);
|
||||
if (roleMapEntry && roleMapEntry->role == nsIAccessibleRole::ROLE_MENUPOPUP) {
|
||||
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
|
||||
childNode, nsAccEvent::eRemoveDupes,
|
||||
isAsynch);
|
||||
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
|
||||
childNode, nsAccEvent::eRemoveDupes,
|
||||
isAsynch);
|
||||
}
|
||||
|
||||
// Check to see if change occured inside an alert, and fire an EVENT_ALERT if it did
|
||||
|
@ -2123,8 +2126,8 @@ nsDocAccessible::InvalidateCacheSubtree(nsIContent *aChild,
|
|||
while (PR_TRUE) {
|
||||
if (roleMapEntry && roleMapEntry->role == nsIAccessibleRole::ROLE_ALERT) {
|
||||
nsCOMPtr<nsIDOMNode> alertNode(do_QueryInterface(ancestor));
|
||||
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_ALERT, alertNode,
|
||||
nsAccEvent::eRemoveDupes, isAsynch);
|
||||
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_ALERT, alertNode,
|
||||
nsAccEvent::eRemoveDupes, isAsynch);
|
||||
break;
|
||||
}
|
||||
ancestor = ancestor->GetParent();
|
||||
|
|
|
@ -121,15 +121,15 @@ public:
|
|||
/**
|
||||
* 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 aAllowDupes [in] rule to process an event (see EEventRule constants)
|
||||
* @param aIsAsynch [in] set to PR_TRUE if this is not being called from
|
||||
* code synchronous with a DOM event
|
||||
*/
|
||||
nsresult FireDelayedToolkitEvent(PRUint32 aEvent, nsIDOMNode *aDOMNode,
|
||||
nsAccEvent::EEventRule aAllowDupes = nsAccEvent::eRemoveDupes,
|
||||
PRBool aIsAsynch = PR_FALSE);
|
||||
nsresult FireDelayedAccessibleEvent(PRUint32 aEventType, nsIDOMNode *aDOMNode,
|
||||
nsAccEvent::EEventRule aAllowDupes = nsAccEvent::eRemoveDupes,
|
||||
PRBool aIsAsynch = PR_FALSE);
|
||||
|
||||
/**
|
||||
* Fire accessible event after timeout.
|
||||
|
|
|
@ -396,7 +396,8 @@ void nsRootAccessible::TryFireEarlyLoadEvent(nsIDOMNode *aDocNode)
|
|||
NS_ASSERTION(rootContentTreeItem, "No root content tree item");
|
||||
if (rootContentTreeItem == treeItem) {
|
||||
// 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,9 +539,9 @@ PRBool nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *aAccessible,
|
|||
}
|
||||
}
|
||||
|
||||
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_FOCUS,
|
||||
finalFocusNode, nsAccEvent::eRemoveDupes,
|
||||
aIsAsynch);
|
||||
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_FOCUS,
|
||||
finalFocusNode, nsAccEvent::eRemoveDupes,
|
||||
aIsAsynch);
|
||||
|
||||
return PR_TRUE;
|
||||
}
|
||||
|
@ -893,7 +894,8 @@ nsresult nsRootAccessible::HandleEventWithTarget(nsIDOMEvent* aEvent,
|
|||
FireCurrentFocusEvent();
|
||||
}
|
||||
else if (eventType.EqualsLiteral("ValueChange")) {
|
||||
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aTargetNode, nsAccEvent::eRemoveDupes);
|
||||
FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE,
|
||||
aTargetNode, nsAccEvent::eRemoveDupes);
|
||||
}
|
||||
#ifdef DEBUG
|
||||
else if (eventType.EqualsLiteral("mouseover")) {
|
||||
|
|
|
@ -98,6 +98,7 @@ _TEST_FILES =\
|
|||
test_events_draganddrop.html \
|
||||
test_events_focus.xul \
|
||||
test_events_mutation.html \
|
||||
test_events_mutation_coalesce.html \
|
||||
test_events_tree.xul \
|
||||
test_events_valuechange.html \
|
||||
test_groupattrs.xul \
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
////////////////////////////////////////////////////////////////////////////////
|
||||
// Constants
|
||||
|
||||
const EVENT_ASYNCH_HIDE = nsIAccessibleEvent.EVENT_ASYNCH_HIDE;
|
||||
const EVENT_ASYNCH_SHOW = nsIAccessibleEvent.EVENT_ASYNCH_SHOW;
|
||||
const 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_FOCUS = nsIAccessibleEvent.EVENT_FOCUS;
|
||||
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).
|
||||
* DOMNode getter: function() {},
|
||||
*
|
||||
* // Array of items defining events expected (or not expected, see
|
||||
* // 'doNotExpectEvents' property) on invoker's action.
|
||||
* // Array of checker objects defining expected events on invoker's action.
|
||||
* //
|
||||
* // Every array item should be either
|
||||
* // 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).
|
||||
* // Checker object interface:
|
||||
* //
|
||||
* // 2) object (invoker's checker object) like
|
||||
* // var checker = {
|
||||
* // type getter: function() {}, // DOM or a11y event type
|
||||
* // target getter: function() {}, // DOM node or accessible
|
||||
|
@ -141,9 +139,9 @@ function invokerChecker(aEventType, aTarget)
|
|||
* // };
|
||||
* eventSeq getter() {},
|
||||
*
|
||||
* // [optional, used together with 'eventSeq'] Boolean indicates if events
|
||||
* // specified by 'eventSeq' property shouldn't be triggerd by invoker.
|
||||
* doNotExpectEvents getter() {},
|
||||
* // Array of checker objects defining unexpected events on invoker's
|
||||
* // action.
|
||||
* unexpectedEventSeq getter() {},
|
||||
*
|
||||
* // The ID of invoker.
|
||||
* getID: function(){} // returns invoker ID
|
||||
|
@ -174,8 +172,7 @@ function eventQueue(aEventType)
|
|||
|
||||
// XXX: Intermittent test_events_caretmove.html fails withouth timeout,
|
||||
// see bug 474952.
|
||||
window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 500,
|
||||
this);
|
||||
this.processNextInvokerInTimeout(true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -198,15 +195,20 @@ function eventQueue(aEventType)
|
|||
|
||||
var invoker = this.getInvoker();
|
||||
if (invoker) {
|
||||
if ("finalCheck" in invoker)
|
||||
invoker.finalCheck();
|
||||
|
||||
if (invoker.wasCaught) {
|
||||
for (var idx = 0; idx < invoker.wasCaught.length; idx++) {
|
||||
var id = this.getEventID(idx);
|
||||
var type = this.getEventType(idx);
|
||||
var unexpected = this.mEventSeq[idx].unexpected;
|
||||
|
||||
var typeStr = (typeof type == "string") ?
|
||||
type : gAccRetrieval.getStringEventType(type);
|
||||
|
||||
var msg = "test with ID = '" + id + "' failed. ";
|
||||
if (invoker.doNotExpectEvents) {
|
||||
if (unexpected) {
|
||||
var wasCaught = invoker.wasCaught[idx];
|
||||
if (!testFailed)
|
||||
testFailed = wasCaught;
|
||||
|
@ -256,11 +258,22 @@ function eventQueue(aEventType)
|
|||
return;
|
||||
}
|
||||
|
||||
if (invoker.doNotExpectEvents) {
|
||||
// Check in timeout invoker didn't fire registered events.
|
||||
window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 500,
|
||||
this);
|
||||
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.
|
||||
window.setTimeout(function(aQueue) { aQueue.processNextInvoker(); }, 500,
|
||||
this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -282,33 +295,38 @@ function eventQueue(aEventType)
|
|||
if ("debugCheck" in invoker)
|
||||
invoker.debugCheck(aEvent);
|
||||
|
||||
if (invoker.doNotExpectEvents) {
|
||||
// Search through event sequence to ensure it doesn't contain handled
|
||||
// event.
|
||||
for (var idx = 0; idx < this.mEventSeq.length; idx++) {
|
||||
if (this.compareEvents(idx, aEvent))
|
||||
invoker.wasCaught[idx] = true;
|
||||
}
|
||||
} else {
|
||||
// We wait for events in order specified by eventSeq variable.
|
||||
var idx = this.mEventSeqIdx + 1;
|
||||
|
||||
var matched = this.compareEvents(idx, aEvent);
|
||||
this.dumpEventToDOM(aEvent, idx, matched);
|
||||
|
||||
if (matched) {
|
||||
this.checkEvent(idx, aEvent);
|
||||
// Search through unexpected events to ensure no one of them was handled.
|
||||
for (var idx = 0; idx < this.mEventSeq.length; idx++) {
|
||||
if (this.mEventSeq[idx].unexpected && this.compareEvents(idx, aEvent))
|
||||
invoker.wasCaught[idx] = true;
|
||||
}
|
||||
|
||||
if (idx == this.mEventSeq.length - 1) {
|
||||
// We need delay to avoid events coalesce from different invokers.
|
||||
var queue = this;
|
||||
SimpleTest.executeSoon(function() { queue.processNextInvoker(); });
|
||||
return;
|
||||
}
|
||||
// Wait for next expected event in an order specified by event sequence.
|
||||
|
||||
this.mEventSeqIdx = idx;
|
||||
// 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);
|
||||
this.dumpEventToDOM(aEvent, idx, matched);
|
||||
|
||||
if (matched) {
|
||||
this.checkEvent(idx, aEvent);
|
||||
invoker.wasCaught[idx] = true;
|
||||
|
||||
// The last event is expected and was handled, proceed next invoker.
|
||||
if (idx == this.mEventSeq.length - 1) {
|
||||
this.processNextInvokerInTimeout();
|
||||
return;
|
||||
}
|
||||
|
||||
this.mEventSeqIdx = idx;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -325,12 +343,26 @@ function eventQueue(aEventType)
|
|||
|
||||
this.setEventHandler = function eventQueue_setEventHandler(aInvoker)
|
||||
{
|
||||
// Create unique event sequence concatenating expected and unexpected
|
||||
// events.
|
||||
this.mEventSeq = ("eventSeq" in aInvoker) ?
|
||||
aInvoker.eventSeq :
|
||||
[ 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;
|
||||
|
||||
// Register event listeners
|
||||
if (this.mEventSeq) {
|
||||
aInvoker.wasCaught = new Array(this.mEventSeq.length);
|
||||
|
||||
|
@ -390,6 +422,16 @@ function eventQueue(aEventType)
|
|||
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)
|
||||
{
|
||||
var eventType1 = this.getEventType(aIdx);
|
||||
|
@ -426,14 +468,24 @@ function eventQueue(aEventType)
|
|||
invoker.check(aEvent);
|
||||
}
|
||||
|
||||
this.getEventID = function eventQueue_getEventID(aIdx)
|
||||
this.areAllEventsExpected = function eventQueue_areAllEventsExpected()
|
||||
{
|
||||
var eventItem = this.mEventSeq[aIdx];
|
||||
if ("getID" in eventItem)
|
||||
return eventItem.getID();
|
||||
for (var idx = 0; idx < this.mEventSeq.length; idx++) {
|
||||
if (this.mEventSeq[idx].unexpected)
|
||||
return false;
|
||||
}
|
||||
|
||||
var invoker = this.getInvoker();
|
||||
return invoker.getID();
|
||||
return true;
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
@ -52,6 +52,7 @@
|
|||
this.DOMNode = getNode(aNodeOrID);
|
||||
this.doNotExpectEvents = aDoNotExpectEvents;
|
||||
this.eventSeq = [];
|
||||
this.unexpectedEventSeq = [];
|
||||
|
||||
/**
|
||||
* Change default target (aNodeOrID) registered for the given event type.
|
||||
|
@ -59,9 +60,9 @@
|
|||
this.setTarget = function mutateA11yTree_setTarget(aEventType, aTarget)
|
||||
{
|
||||
var type = this.getA11yEventType(aEventType);
|
||||
for (var idx = 0; idx < this.eventSeq.length; idx++) {
|
||||
if (this.eventSeq[idx].type == type) {
|
||||
this.eventSeq[idx].target = aTarget;
|
||||
for (var idx = 0; idx < this.getEventSeq().length; idx++) {
|
||||
if (this.getEventSeq()[idx].type == type) {
|
||||
this.getEventSeq()[idx].target = aTarget;
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
|
@ -78,7 +79,7 @@
|
|||
var type = this.getA11yEventType(aEventType);
|
||||
for (var i = 1; i < aTargets.length; 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;
|
||||
|
||||
if (aEventTypes & kHideEvent) {
|
||||
var checker = new invokerChecker(this.getA11yEventType(kHideEvent),
|
||||
this.DOMNode);
|
||||
this.eventSeq.push(checker);
|
||||
this.getEventSeq().push(checker);
|
||||
}
|
||||
|
||||
if (aEventTypes & kShowEvent) {
|
||||
var checker = new invokerChecker(this.getA11yEventType(kShowEvent),
|
||||
this.DOMNode);
|
||||
this.eventSeq.push(checker);
|
||||
this.getEventSeq().push(checker);
|
||||
}
|
||||
|
||||
if (aEventTypes & kReorderEvent) {
|
||||
var checker = new invokerChecker(this.getA11yEventType(kReorderEvent),
|
||||
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>
|
Загрузка…
Ссылка в новой задаче