Bug 669263 - reorder event for document must be fired after document initial tree creation, r=tbsaunde

This commit is contained in:
Alexander Surkov 2011-07-19 17:30:32 +09:00
parent e556610622
commit 31158df4e9
6 changed files with 168 additions and 63 deletions

View File

@ -258,17 +258,9 @@ NotificationController::WillRefresh(mozilla::TimeStamp aTime)
if (ownerContent) {
nsAccessible* outerDocAcc = mDocument->GetAccessible(ownerContent);
if (outerDocAcc && outerDocAcc->AppendChild(childDoc)) {
if (mDocument->AppendChildDocument(childDoc)) {
// Fire reorder event to notify new accessible document has been
// attached to the tree.
nsRefPtr<AccEvent> reorderEvent =
new AccEvent(nsIAccessibleEvent::EVENT_REORDER, outerDocAcc,
eAutoDetect, AccEvent::eCoalesceFromSameSubtree);
if (reorderEvent)
QueueEvent(reorderEvent);
if (mDocument->AppendChildDocument(childDoc))
continue;
}
outerDocAcc->RemoveChild(childDoc);
}

View File

@ -468,12 +468,13 @@ nsAccDocManager::CreateDocOrRootAccessible(nsIDocument *aDocument)
}
// Fire reorder event to notify new accessible document has been attached to
// the tree.
// the tree. The reorder event is delivered after the document tree is
// constructed because event processing and tree construction are done by
// the same document.
nsRefPtr<AccEvent> reorderEvent =
new AccEvent(nsIAccessibleEvent::EVENT_REORDER, appAcc, eAutoDetect,
AccEvent::eCoalesceFromSameSubtree);
if (reorderEvent)
docAcc->FireDelayedAccessibleEvent(reorderEvent);
docAcc->FireDelayedAccessibleEvent(reorderEvent);
} else {
parentDocAcc->BindChildDocument(docAcc);

View File

@ -1494,6 +1494,17 @@ nsDocAccessible::NotifyOfInitialUpdate()
// Build initial tree.
CacheChildrenInSubtree(this);
// Fire reorder event after the document tree is constructed. Note, since
// this reorder event is processed by parent document then events targeted to
// this document may be fired prior to this reorder event. If this is
// a problem then consider to keep event processing per tab document.
if (!IsRoot()) {
nsRefPtr<AccEvent> reorderEvent =
new AccEvent(nsIAccessibleEvent::EVENT_REORDER, GetParent(),
eAutoDetect, AccEvent::eCoalesceFromSameSubtree);
ParentDocument()->FireDelayedAccessibleEvent(reorderEvent);
}
}
void

View File

@ -159,11 +159,23 @@ const DO_NOT_FINISH_TEST = 1;
* // Checker object interface:
* //
* // var checker = {
* // type getter: function() {}, // DOM or a11y event type
* // target getter: function() {}, // DOM node or accessible
* // phase getter: function() {}, // DOM event phase (false - bubbling)
* // * DOM or a11y event type. *
* // type getter: function() {},
* //
* // * DOM node or accessible. *
* // target getter: function() {},
* //
* // * DOM event phase (false - bubbling). *
* // phase getter: function() {},
* //
* // * Callback, called when event is handled
* // check: function(aEvent) {},
* // getID: function() {}
* //
* // * Checker ID *
* // getID: function() {},
* //
* // * Event that don't have predefined order relative other events. *
* // async getter: function() {}
* // };
* eventSeq getter() {},
*
@ -327,10 +339,11 @@ function eventQueue(aEventType)
if ("debugCheck" in invoker)
invoker.debugCheck(aEvent);
// Search through handled expected events if one of them was handled again.
// Search through handled expected events to report error if one of them is
// handled for a second time.
var idx = 0;
for (; idx < this.mEventSeq.length; idx++) {
if (!this.isEventUnexpected(idx) && (invoker.wasCaught[idx] == true) &&
if (this.isEventExpected(idx) && (invoker.wasCaught[idx] == true) &&
this.isAlreadyCaught(idx, aEvent)) {
var msg = "Doubled event { event type: " +
@ -341,52 +354,85 @@ function eventQueue(aEventType)
}
}
// Search through unexpected events to ensure no one of them was handled.
// Search through unexpected events, any matches result in error report
// after this invoker processing.
for (idx = 0; idx < this.mEventSeq.length; idx++) {
if (this.isEventUnexpected(idx) && this.compareEvents(idx, aEvent))
invoker.wasCaught[idx] = true;
}
// We've handled all expected events, next invoker processing is pending.
if (this.mEventSeqIdx == this.mEventSeq.length)
// Nothing left, proceed next invoker in timeout. Otherwise check if
// handled event is matched.
var idxObj = {};
if (!this.prepareForExpectedEvent(invoker, idxObj))
return;
// Compute next expected event index.
for (idx = this.mEventSeqIdx + 1;
idx < this.mEventSeq.length && this.mEventSeq[idx].unexpected;
idx++);
// No expected events were registered, proceed to next invoker to ensure
// unexpected events for current invoker won't be handled.
if (idx == this.mEventSeq.length) {
this.mEventSeqIdx = idx;
this.processNextInvokerInTimeout();
return;
// Check if handled event matches expected sync event.
var matched = false;
idx = idxObj.value;
if (idx < this.mEventSeq.length) {
matched = this.compareEvents(idx, aEvent);
if (matched)
this.mEventSeqIdx = idx;
}
// Check if handled event matches expected event.
var matched = this.compareEvents(idx, aEvent);
// Check if handled event matches any expected async events.
if (!matched) {
for (idx = 0; idx < this.mEventSeq.length; idx++) {
if (this.mEventSeq[idx].async) {
matched = this.compareEvents(idx, aEvent);
if (matched)
break;
}
}
}
this.dumpEventToDOM(aEvent, idx, matched);
if (matched) {
this.checkEvent(idx, aEvent);
invoker.wasCaught[idx] = true;
this.mEventSeqIdx = idx;
// Get next expected event index.
while (++idx < this.mEventSeq.length && this.mEventSeq[idx].unexpected);
// If the last expected event was processed, proceed next invoker in
// timeout to ensure unexpected events for current invoker won't be
// handled.
if (idx == this.mEventSeq.length) {
this.mEventSeqIdx = idx;
this.processNextInvokerInTimeout();
}
this.prepareForExpectedEvent(invoker);
}
}
// Helpers
this.prepareForExpectedEvent =
function eventQueue_prepareForExpectedEvent(aInvoker, aIdxObj)
{
// Nothing left, wait for next invoker.
if (this.mEventSeqFinished)
return false;
// Compute next expected sync event index.
for (var idx = this.mEventSeqIdx + 1;
idx < this.mEventSeq.length &&
(this.mEventSeq[idx].unexpected || this.mEventSeq[idx].async);
idx++);
// If no expected events were left, proceed to next invoker in timeout
// to make sure unexpected events for current invoker aren't be handled.
if (idx == this.mEventSeq.length) {
var allHandled = true;
for (var jdx = 0; jdx < this.mEventSeq.length; jdx++) {
if (this.isEventExpected(jdx) && !aInvoker.wasCaught[jdx])
allHandled = false;
}
if (allHandled) {
this.mEventSeqIdx = this.mEventSeq.length;
this.mEventFinished = true;
this.processNextInvokerInTimeout();
return false;
}
}
if (aIdxObj)
aIdxObj.value = idx;
return true;
}
this.getInvoker = function eventQueue_getInvoker()
{
return this.mInvokers[this.mIndex];
@ -405,18 +451,24 @@ function eventQueue(aEventType)
aInvoker.eventSeq :
[ new invokerChecker(this.mDefEventType, aInvoker.DOMNode) ];
for (var idx = 0; idx < this.mEventSeq.length; idx++)
for (var idx = 0; idx < this.mEventSeq.length; idx++) {
this.mEventSeq[idx].unexpected = false;
if (!("async" in this.mEventSeq[idx]))
this.mEventSeq[idx].async = false;
}
var unexpectedSeq = aInvoker.unexpectedEventSeq;
if (unexpectedSeq) {
for (var idx = 0; idx < unexpectedSeq.length; idx++)
for (var idx = 0; idx < unexpectedSeq.length; idx++) {
unexpectedSeq[idx].unexpected = true;
unexpectedSeq[idx].async = false;
}
this.mEventSeq = this.mEventSeq.concat(unexpectedSeq);
}
this.mEventSeqIdx = -1;
this.mEventSeqFinished = false;
// Register event listeners
if (this.mEventSeq) {
@ -517,6 +569,10 @@ function eventQueue(aEventType)
{
return this.mEventSeq[aIdx].unexpected;
}
this.isEventExpected = function eventQueue_isEventExpected(aIdx)
{
return !this.mEventSeq[aIdx].unexpected;
}
this.compareEvents = function eventQueue_compareEvents(aIdx, aEvent)
{
@ -598,20 +654,17 @@ function eventQueue(aEventType)
gLogger.logToDOM(info);
}
var currType = this.getEventTypeAsString(aExpectedEventIdx);
var currTarget = this.getEventTarget(aExpectedEventIdx);
if (!aMatch)
return;
var msg = "EQ: ";
var emphText = "";
if (aMatch) {
emphText = "matched ";
var emphText = "matched ";
var consoleMsg = "*****\nEQ matched: " + currType + "\n*****";
gLogger.logToConsole(consoleMsg);
var currType = this.getEventTypeAsString(aExpectedEventIdx);
var currTarget = this.getEventTarget(aExpectedEventIdx);
var consoleMsg = "*****\nEQ matched: " + currType + "\n*****";
gLogger.logToConsole(consoleMsg);
} else {
msg += "expected";
}
msg += " event, type: " + currType + ", target: " + prettyName(currTarget);
gLogger.logToDOM(msg, true, emphText);
@ -624,6 +677,7 @@ function eventQueue(aEventType)
this.mEventSeq = null;
this.mEventSeqIdx = -1;
this.mEventSeqFinished = false;
}
@ -896,9 +950,10 @@ function synthSelectAll(aNodeOrID, aCheckerOrEventSeq, aEventType)
/**
* Common invoker checker (see eventSeq of eventQueue).
*/
function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg, aIsAsync)
{
this.type = aEventType;
this.async = aIsAsync;
this.__defineGetter__("target", invokerChecker_targetGetter);
this.__defineSetter__("target", invokerChecker_targetSetter);
@ -932,6 +987,15 @@ function invokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
this.mTargetFuncArg = aTargetFuncArg;
}
/**
* Common invoker checker for async events.
*/
function asyncInvokerChecker(aEventType, aTargetOrFunc, aTargetFuncArg)
{
this.__proto__ = new invokerChecker(aEventType, aTargetOrFunc,
aTargetFuncArg, true);
}
/**
* Text inserted/removed events checker.
*/

View File

@ -36,6 +36,7 @@
}
var invokerChecker = gOpenerWnd.invokerChecker;
var asyncInvokerChecker = gOpenerWnd.asyncInvokerChecker;
const STATE_BUSY = gOpenerWnd.STATE_BUSY;
const EVENT_DOCUMENT_LOAD_COMPLETE =
@ -48,6 +49,8 @@
const nsIAccessibleStateChangeEvent =
gOpenerWnd.nsIAccessibleStateChangeEvent;
//gOpenerWnd.gA11yEventDumpToConsole = true; // debug
////////////////////////////////////////////////////////////////////////////
// Hacks to make xul:tabbrowser work.
@ -132,7 +135,7 @@
this.eventSeq = [
// We don't expect state change event for busy true since things happen
// quickly and it's coalesced.
new invokerChecker(EVENT_REORDER, getContainer),
new asyncInvokerChecker(EVENT_REORDER, getContainer),
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getDocument),
new stateBusyChecker(false)
];
@ -155,7 +158,7 @@
this.eventSeq = [
new documentReloadChecker(true),
new invokerChecker(EVENT_REORDER, getContainer),
new asyncInvokerChecker(EVENT_REORDER, getContainer),
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getDocument),
new stateBusyChecker(false)
];
@ -178,7 +181,7 @@
this.eventSeq = [
new documentReloadChecker(false),
new invokerChecker(EVENT_REORDER, getContainer),
new asyncInvokerChecker(EVENT_REORDER, getContainer),
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getDocument),
new stateBusyChecker(false)
];
@ -202,7 +205,7 @@
this.eventSeq = [
// We don't expect state change for busy true, load stopped events since
// things happen quickly and it's coalesced.
new invokerChecker(EVENT_REORDER, getContainer),
new asyncInvokerChecker(EVENT_REORDER, getContainer),
new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getDocument),
new stateBusyChecker(false)
];

View File

@ -357,6 +357,36 @@
}
}
function changeSrc(aID)
{
this.containerNode = getNode(aID);
this.eventSeq = [
new invokerChecker(EVENT_REORDER, this.containerNode)
];
this.invoke = function changeSrc_invoke()
{
this.containerNode.src = "data:text/html,<html><input></html>";
}
this.finalCheck = function changeSrc_finalCheck()
{
var tree =
{ INTERNAL_FRAME: [
{ DOCUMENT: [
{ ENTRY: [ ] }
] };
] };
testAccessibleTree(this.containerNode, tree);
}
this.getID() = function changeSrc_getID()
{
return "change src on iframe";
}
}
////////////////////////////////////////////////////////////////////////////
// Test
@ -378,6 +408,7 @@
gQueue.push(new removeBodyFromIFrameDoc("iframe"));
gQueue.push(new insertElmUnderDocElmWhileBodyMissed("iframe"));
gQueue.push(new insertBodyToIFrameDoc("iframe"));
gQueue.push(new changeSrc("iframe"));
gQueue.invoke(); // SimpleTest.finish() will be called in the end
}
@ -394,6 +425,9 @@
<a target="_blank"
title="Elements inserted outside the body aren't accessible"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=608887">Mozilla Bug 608887</a>
<a target="_blank"
title="Reorder event for document must be fired after document initial tree creation"
href="https://bugzilla.mozilla.org/show_bug.cgi?id=669263">Mozilla Bug 669263</a>
<p id="display"></p>
<div id="content" style="display: none"></div>