Bug 391847. Coalesce accessible mutation events for the same subtree. r=ginn.chen, sr=bz, a=bz
--- a/accessible/src/base/nsAccessibilityUtils.cpp
+++ b/accessible/src/base/nsAccessibilityUtils.cpp
@@ -178,8 +178,27 @@ nsAccUtils::FireAccEvent(PRUint32 aEvent
nsCOMPtr<nsIAccessibleEvent> event =
new nsAccEvent(aEventType, aAccessible, nsnull, aIsAsynch);
NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
return pAccessible->FireAccessibleEvent(event);
}
+PRBool
+nsAccUtils::IsAncestorOf(nsIDOMNode *aPossibleAncestorNode,
+ nsIDOMNode *aPossibleDescendantNode)
+{
+ NS_ENSURE_ARG_POINTER(aPossibleAncestorNode);
+ NS_ENSURE_ARG_POINTER(aPossibleDescendantNode);
+
+ nsCOMPtr<nsIDOMNode> loopNode = aPossibleDescendantNode;
+ nsCOMPtr<nsIDOMNode> parentNode;
+ while (NS_SUCCEEDED(loopNode->GetParentNode(getter_AddRefs(parentNode))) &&
+ parentNode) {
+ if (parentNode == aPossibleAncestorNode) {
+ return PR_TRUE;
+ }
+ loopNode.swap(parentNode);
+ }
+ return PR_FALSE;
+}
+
--- a/accessible/src/base/nsAccessibilityUtils.h
+++ b/accessible/src/base/nsAccessibilityUtils.h
@@ -108,11 +108,21 @@ public:
*/
static PRBool HasListener(nsIContent *aContent, const nsAString& aEventType);
/**
* Fire accessible event of the given type for the given accessible.
*/
static nsresult FireAccEvent(PRUint32 aEventType, nsIAccessible *aAccessible,
PRBool aIsAsynch = PR_FALSE);
+
+ /**
+ * Is the first passed in node an ancestor of the second?
+ * Note: A node is not considered to be the ancestor of itself.
+ * @aPossibleAncestorNode -- node to test for ancestor-ness of aPossibleDescendantNode
+ * @aPossibleDescendantNode -- node to test for descendant-ness of aPossibleAncestorNode
+ * @return PR_TRUE if aPossibleAncestorNode is an ancestor of aPossibleDescendantNode
+ */
+ static PRBool IsAncestorOf(nsIDOMNode *aPossibleAncestorNode,
+ nsIDOMNode *aPossibleDescendantNode);
};
#endif
--- a/accessible/src/base/nsCaretAccessible.cpp
+++ b/accessible/src/base/nsCaretAccessible.cpp
@@ -216,17 +216,17 @@ NS_IMETHODIMP nsCaretAccessible::NotifyS
}
mLastCaretOffset = caretOffset;
mLastTextAccessible = textAcc;
nsCOMPtr<nsIAccessibleCaretMoveEvent> event =
new nsAccCaretMoveEvent(focusNode);
NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
- return mRootAccessible->FireDelayedAccessibleEvent(event, PR_FALSE);
+ return mRootAccessible->FireDelayedAccessibleEvent(event, nsDocAccessible::eRemoveDupes);
}
nsRect
nsCaretAccessible::GetCaretRect(nsIWidget **aOutWidget)
{
nsRect caretRect;
NS_ENSURE_TRUE(aOutWidget, caretRect);
*aOutWidget = nsnull;
--- a/accessible/src/base/nsDocAccessible.cpp
+++ b/accessible/src/base/nsDocAccessible.cpp
@@ -1001,17 +1001,17 @@ nsDocAccessible::AttributeChanged(nsIDoc
// Need to find the right event to use here, SELECTION_WITHIN would
// seem right but we had started using it for something else
nsCOMPtr<nsIAccessNode> multiSelectAccessNode =
do_QueryInterface(multiSelect);
nsCOMPtr<nsIDOMNode> multiSelectDOMNode;
multiSelectAccessNode->GetDOMNode(getter_AddRefs(multiSelectDOMNode));
NS_ASSERTION(multiSelectDOMNode, "A new accessible without a DOM node!");
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_SELECTION_WITHIN,
- multiSelectDOMNode, nsnull, PR_TRUE);
+ multiSelectDOMNode, nsnull, eAllowDupes);
static nsIContent::AttrValuesArray strings[] =
{&nsAccessibilityAtoms::_empty, &nsAccessibilityAtoms::_false, nsnull};
if (aContent->FindAttrValueIn(kNameSpaceID_None,
nsAccessibilityAtoms::selected,
strings, eCaseMatters) !=
nsIContent::ATTR_VALUE_NO_MATCH) {
@@ -1376,30 +1376,30 @@ nsDocAccessible::FireTextChangedEventOnD
return;
textAccessible->FireAccessibleEvent(event);
}
nsresult nsDocAccessible::FireDelayedToolkitEvent(PRUint32 aEvent,
nsIDOMNode *aDOMNode,
void *aData,
- PRBool aAllowDupes,
+ EDupeEventRule aAllowDupes,
PRBool aIsAsynch)
{
nsCOMPtr<nsIAccessibleEvent> event =
new nsAccEvent(aEvent, aDOMNode, aData, PR_TRUE);
NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY);
return FireDelayedAccessibleEvent(event, aAllowDupes, aIsAsynch);
}
nsresult
nsDocAccessible::FireDelayedAccessibleEvent(nsIAccessibleEvent *aEvent,
- PRBool aAllowDupes,
- PRBool aIsAsynch)
+ EDupeEventRule aAllowDupes,
+ PRBool aIsAsynch)
{
PRBool isTimerStarted = PR_TRUE;
PRInt32 numQueuedEvents = mEventsToFire.Count();
if (!mFireEventTimer) {
// Do not yet have a timer going for firing another event.
mFireEventTimer = do_CreateInstance("@mozilla.org/timer;1");
NS_ENSURE_TRUE(mFireEventTimer, NS_ERROR_OUT_OF_MEMORY);
}
@@ -1414,17 +1414,46 @@ nsDocAccessible::FireDelayedAccessibleEv
// If already asynchronous don't call PrepareFromEvent() -- it
// should only be called while ESM still knows if the event occurred
// originally because of user input
nsAccEvent::PrepareForEvent(newEventDOMNode);
}
if (numQueuedEvents == 0) {
isTimerStarted = PR_FALSE;
- } else if (!aAllowDupes) {
+ } else if (aAllowDupes == eCoalesceFromSameSubtree) {
+ // Especially for mutation events, we will define a duplicate event
+ // as one on the same node or on a descendant node.
+ // This prevents a flood of events when a subtree is changed.
+ for (PRInt32 index = 0; index < numQueuedEvents; index ++) {
+ nsIAccessibleEvent *accessibleEvent = mEventsToFire[index];
+ NS_ASSERTION(accessibleEvent, "Array item is not an accessible event");
+ if (!accessibleEvent) {
+ continue;
+ }
+ PRUint32 eventType;
+ accessibleEvent->GetEventType(&eventType);
+ if (eventType == newEventType) {
+ nsCOMPtr<nsIDOMNode> domNode;
+ accessibleEvent->GetDOMNode(getter_AddRefs(domNode));
+ if (newEventDOMNode == domNode || nsAccUtils::IsAncestorOf(newEventDOMNode, domNode)) {
+ mEventsToFire.RemoveObjectAt(index);
+ // The other event is the same type, but in a descendant of this
+ // event, so remove that one. The umbrella event in the ancestor
+ // is already enough
+ -- index;
+ -- numQueuedEvents;
+ }
+ else if (nsAccUtils::IsAncestorOf(domNode, newEventDOMNode)) {
+ // There is a better SHOW/HIDE event (it's in an ancestor)
+ return NS_OK;
+ }
+ }
+ }
+ } else if (aAllowDupes == eRemoveDupes) {
// Check for repeat events. If a redundant event exists remove
// original and put the new event at the end of the queue
// so it is fired after the others
for (PRInt32 index = 0; index < numQueuedEvents; index ++) {
nsIAccessibleEvent *accessibleEvent = mEventsToFire[index];
NS_ASSERTION(accessibleEvent, "Array item is not an accessible event");
if (!accessibleEvent) {
continue;
@@ -1501,16 +1530,26 @@ NS_IMETHODIMP nsDocAccessible::FlushPend
}
}
}
else {
// The input state was previously stored with the nsIAccessibleEvent,
// so use that state now when firing the event
nsAccEvent::PrepareForEvent(accessibleEvent);
FireAccessibleEvent(accessibleEvent);
+ // Post event processing
+ if (eventType == nsIAccessibleEvent::EVENT_ASYNCH_HIDE ||
+ eventType == nsIAccessibleEvent::EVENT_DOM_DESTROY) {
+ // Shutdown nsIAccessNode's or nsIAccessibles for any DOM nodes in this subtree
+ nsCOMPtr<nsIDOMNode> hidingNode;
+ accessibleEvent->GetDOMNode(getter_AddRefs(hidingNode));
+ if (hidingNode) {
+ RefreshNodes(hidingNode); // Will this bite us with asynch events
+ }
+ }
}
}
}
mEventsToFire.Clear(); // Clear out array
return NS_OK;
}
void nsDocAccessible::FlushEventsCallback(nsITimer *aTimer, void *aClosure)
@@ -1632,17 +1671,17 @@ NS_IMETHODIMP nsDocAccessible::Invalidat
// which it is if anyone asks for its children right now.
return InvalidateChildren();
}
// Update last change state information
nsCOMPtr<nsIAccessNode> childAccessNode;
GetCachedAccessNode(childNode, getter_AddRefs(childAccessNode));
nsCOMPtr<nsIAccessible> childAccessible = do_QueryInterface(childAccessNode);
- if (!childAccessible && isHiding) {
+ if (!childAccessible && !isHiding) {
// If not about to hide it, make sure there's an accessible so we can fire an
// event for it
GetAccService()->GetAccessibleFor(childNode, getter_AddRefs(childAccessible));
}
#ifdef DEBUG_A11Y
nsAutoString localName;
childNode->GetLocalName(localName);
@@ -1663,24 +1702,26 @@ NS_IMETHODIMP nsDocAccessible::Invalidat
printf("[Destroy %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible);
}
else if (aChangeEventType == nsIAccessibleEvent::EVENT_DOM_SIGNIFICANT_CHANGE) {
printf("[Type change %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible);
}
#endif
if (!isShowing) {
- // Fire EVENT_HIDE or EVENT_DOM_DESTROY if previous accessible existed for node being hidden.
+ // Fire EVENT_ASYNCH_HIDE or EVENT_DOM_DESTROY if previous accessible existed for node being hidden.
// Fire this before the accessible goes away.
if (childAccessible) {
- PRUint32 removalEvent = isAsynch ? nsIAccessibleEvent::EVENT_ASYNCH_HIDE : nsIAccessibleEvent::EVENT_DOM_DESTROY;
- nsAccUtils::FireAccEvent(removalEvent, childAccessible, isAsynch);
+ PRUint32 removalEventType = isAsynch ? nsIAccessibleEvent::EVENT_ASYNCH_HIDE :
+ nsIAccessibleEvent::EVENT_DOM_DESTROY;
+ nsCOMPtr<nsIAccessibleEvent> removalEvent =
+ new nsAccEvent(removalEventType, childAccessible, nsnull, PR_TRUE);
+ NS_ENSURE_TRUE(removalEvent, NS_ERROR_OUT_OF_MEMORY);
+ FireDelayedAccessibleEvent(removalEvent, eCoalesceFromSameSubtree, isAsynch);
}
- // Shutdown nsIAccessNode's or nsIAccessibles for any DOM nodes in this subtree
- RefreshNodes(childNode);
}
// We need to get an accessible for the mutation event's container node
// If there is no accessible for that node, we need to keep moving up the parent
// chain so there is some accessible.
// We will use this accessible to fire the accessible mutation event.
// We're guaranteed success, because we will eventually end up at the doc accessible,
// and there is always one of those.
@@ -1699,37 +1740,35 @@ NS_IMETHODIMP nsDocAccessible::Invalidat
if (aChild && !isHiding) {
// Fire EVENT_SHOW, EVENT_MENUPOPUP_START for newly visible content.
// Fire after a short timer, because we want to make sure the view has been
// updated to make this accessible content visible. If we don't wait,
// the assistive technology may receive the event and then retrieve
// nsIAccessibleStates::STATE_INVISIBLE for the event's accessible object.
PRUint32 additionEvent = isAsynch ? nsIAccessibleEvent::EVENT_ASYNCH_SHOW :
nsIAccessibleEvent::EVENT_DOM_CREATE;
- if (!isAsynch) {
- // Calculate "is from user input" while we still synchronous and have the info
- nsAccEvent::PrepareForEvent(childNode);
- }
- FireDelayedToolkitEvent(additionEvent, childNode, nsnull, PR_TRUE, isAsynch);
+ FireDelayedToolkitEvent(additionEvent, childNode, nsnull,
+ eCoalesceFromSameSubtree, isAsynch);
// Check to see change occured in an ARIA menu, and fire an EVENT_MENUPOPUP_START if it did
nsAutoString role;
if (GetRoleAttribute(aChild, role) &&
StringEndsWith(role, NS_LITERAL_STRING(":menu"), nsCaseInsensitiveStringComparator())) {
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START,
- childNode, nsnull, PR_TRUE, isAsynch);
+ childNode, nsnull, eAllowDupes, isAsynch);
}
// Check to see if change occured inside an alert, and fire an EVENT_ALERT if it did
nsIContent *ancestor = aChild;
while (ancestor) {
if (GetRoleAttribute(ancestor, role) &&
StringEndsWith(role, NS_LITERAL_STRING(":alert"), nsCaseInsensitiveStringComparator())) {
nsCOMPtr<nsIDOMNode> alertNode(do_QueryInterface(ancestor));
- FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_ALERT, alertNode, nsnull, PR_FALSE, isAsynch);
+ FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_ALERT, alertNode, nsnull,
+ eRemoveDupes, isAsynch);
break;
}
ancestor = ancestor->GetParent();
}
}
return NS_OK;
}
--- a/accessible/src/base/nsDocAccessible.h
+++ b/accessible/src/base/nsDocAccessible.h
@@ -97,44 +97,49 @@ class nsDocAccessible : public nsHyperTe
NS_IMETHOD Init();
// nsPIAccessNode
NS_IMETHOD_(nsIFrame *) GetFrame(void);
// nsIAccessibleText
NS_IMETHOD GetAssociatedEditor(nsIEditor **aEditor);
+ enum EDupeEventRule { eAllowDupes, eCoalesceFromSameSubtree, eRemoveDupes };
+
/**
* Non-virtual method to fire a delayed event after a 0 length timeout
*
* @param aEvent - the nsIAccessibleEvent event ype
* @param aDOMNode - DOM node the accesible event should be fired for
* @param aData - any additional data for the event
- * @param aAllowDupes - set to PR_TRUE if more than one event of the same
- * type is allowed. By default this is false and events
- * of the same type are discarded (the last one is used)
+ * @param aAllowDupes - eAllowDupes: more than one event of the same type is allowed.
+ * eCoalesceFromSameSubtree: if two events are in the same subtree,
+ * only the event on ancestor is used
+ * eRemoveDupes (default): events of the same type are discarded
+ * (the last one is used)
+ *
* @param aIsAsyn - set to PR_TRUE if this is not being called from code
* synchronous with a DOM event
*/
nsresult FireDelayedToolkitEvent(PRUint32 aEvent, nsIDOMNode *aDOMNode,
- void *aData, PRBool aAllowDupes = PR_FALSE,
+ void *aData, EDupeEventRule aAllowDupes = eRemoveDupes,
PRBool aIsAsynch = PR_FALSE);
/**
* Fire accessible event in timeout.
*
* @param aEvent - the event to fire
* @param aAllowDupes - if false then delayed events of the same type and
* for the same DOM node in the event queue won't
* be fired.
* @param aIsAsych - set to PR_TRUE if this is being called from
* an event asynchronous with the DOM
*/
nsresult FireDelayedAccessibleEvent(nsIAccessibleEvent *aEvent,
- PRBool aAllowDupes = PR_FALSE,
+ EDupeEventRule aAllowDupes = eRemoveDupes,
PRBool aIsAsynch = PR_FALSE);
void ShutdownChildDocuments(nsIDocShellTreeItem *aStart);
protected:
virtual void GetBoundsRect(nsRect& aRect, nsIFrame** aRelativeFrame);
virtual nsresult AddEventListeners();
virtual nsresult RemoveEventListeners();
--- a/accessible/src/base/nsRootAccessible.cpp
+++ b/accessible/src/base/nsRootAccessible.cpp
@@ -414,17 +414,17 @@ void nsRootAccessible::TryFireEarlyLoadE
if (state & nsIAccessibleStates::STATE_BUSY) {
// Don't fire page load events on subdocuments for initial page load of entire page
return;
}
}
// No frames or iframes, so we can fire the doc load finished event early
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_INTERNAL_LOAD, aDocNode,
- nsnull, PR_FALSE);
+ nsnull, eRemoveDupes);
}
PRBool nsRootAccessible::FireAccessibleFocusEvent(nsIAccessible *aAccessible,
nsIDOMNode *aNode,
nsIDOMEvent *aFocusEvent,
PRBool aForceEvent,
PRBool aIsAsynch)
{
@@ -506,17 +506,17 @@ PRBool nsRootAccessible::FireAccessibleF
// Suppress document focus, because real DOM focus will be fired next,
// and that's what we care about
// Make sure we never fire focus for the nsRootAccessible (mDOMNode)
return PR_FALSE;
}
}
FireDelayedToolkitEvent(nsIAccessibleEvent::EVENT_FOCUS,
- finalFocusNode, nsnull, PR_FALSE, aIsAsynch);
+ finalFocusNode, nsnull, eRemoveDupes, aIsAsynch);
return PR_TRUE;
}
void nsRootAccessible::FireCurrentFocusEvent()
{
nsCOMPtr<nsIDOMNode> focusedNode = GetCurrentFocus();
if (!focusedNode) {
--- a/layout/base/nsFrameManager.cpp
+++ b/layout/base/nsFrameManager.cpp
@@ -1367,17 +1367,18 @@ nsFrameManager::ReResolveStyleContext(ns
// XXX need to do overflow frames???
}
newContext->Release();
}
#ifdef ACCESSIBILITY
if (isAccessibilityActive &&
- aFrame->GetStyleVisibility()->IsVisible() != isVisible) {
+ aFrame->GetStyleVisibility()->IsVisible() != isVisible &&
+ !aFrame->GetPrevContinuation()) { // Primary frames only
// XXX Visibility does not affect descendents with visibility set
// Work on a separate, accurate mechanism for dealing with visibility changes.
// A significant enough change occured that this part
// of the accessible tree is no longer valid.
nsCOMPtr<nsIAccessibilityService> accService =
do_GetService("@mozilla.org/accessibilityService;1");
if (accService) {
accService->InvalidateSubtreeFor(mPresShell, aFrame->GetContent(),