diff --git a/accessible/src/base/nsAccEvent.cpp b/accessible/src/base/nsAccEvent.cpp index f2a4a534a194..68fc2d1f4093 100644 --- a/accessible/src/base/nsAccEvent.cpp +++ b/accessible/src/base/nsAccEvent.cpp @@ -254,160 +254,6 @@ nsAccEvent::CaptureIsFromUserInput(EIsFromUserInput aIsFromUserInput) mIsFromUserInput = esm->IsHandlingUserInputExternal(); } -//////////////////////////////////////////////////////////////////////////////// -// nsAccEvent: static methods - -/* static */ -void -nsAccEvent::ApplyEventRules(nsTArray > &aEventsToFire) -{ - PRUint32 numQueuedEvents = aEventsToFire.Length(); - PRInt32 tail = numQueuedEvents - 1; - - nsAccEvent* tailEvent = aEventsToFire[tail]; - switch(tailEvent->mEventRule) { - case nsAccEvent::eCoalesceFromSameSubtree: - { - for (PRInt32 index = 0; index < tail; index ++) { - nsAccEvent* thisEvent = aEventsToFire[index]; - if (thisEvent->mEventType != tailEvent->mEventType) - continue; // Different type - - if (thisEvent->mEventRule == nsAccEvent::eAllowDupes || - thisEvent->mEventRule == nsAccEvent::eDoNotEmit) - continue; // Do not need to check - - if (thisEvent->mDOMNode == tailEvent->mDOMNode) { - if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) { - CoalesceReorderEventsFromSameSource(thisEvent, tailEvent); - continue; - } - - // 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; - } - - // 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; - } - - // 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; - } - } // 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 - 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 ++) { - nsAccEvent* accEvent = aEventsToFire[index]; - if (accEvent->mEventType == tailEvent->mEventType && - accEvent->mEventRule == tailEvent->mEventRule && - accEvent->mDOMNode == tailEvent->mDOMNode) { - accEvent->mEventRule = nsAccEvent::eDoNotEmit; - } - } - } break; // case eRemoveDupes - - default: - break; // case eAllowDupes, eDoNotEmit - } // switch -} - -/* static */ -void -nsAccEvent::ApplyToSiblings(nsTArray > &aEventsToFire, - PRUint32 aStart, PRUint32 aEnd, - PRUint32 aEventType, nsIDOMNode* aDOMNode, - EEventRule aEventRule) -{ - for (PRUint32 index = aStart; index < aEnd; index ++) { - nsAccEvent* accEvent = aEventsToFire[index]; - if (accEvent->mEventType == aEventType && - accEvent->mEventRule != nsAccEvent::eDoNotEmit && - nsCoreUtils::AreSiblings(accEvent->mDOMNode, aDOMNode)) { - accEvent->mEventRule = aEventRule; - } - } -} - -/* static */ -void -nsAccEvent::CoalesceReorderEventsFromSameSource(nsAccEvent *aAccEvent1, - nsAccEvent *aAccEvent2) -{ - // Do not emit event2 if event1 is unconditional. - nsCOMPtr reorderEvent1 = do_QueryInterface(aAccEvent1); - if (reorderEvent1->IsUnconditionalEvent()) { - aAccEvent2->mEventRule = nsAccEvent::eDoNotEmit; - return; - } - - // Do not emit event1 if event2 is unconditional. - nsCOMPtr reorderEvent2 = do_QueryInterface(aAccEvent2); - if (reorderEvent2->IsUnconditionalEvent()) { - aAccEvent1->mEventRule = nsAccEvent::eDoNotEmit; - return; - } - - // Do not emit event2 if event1 is valid, otherwise do not emit event1. - if (reorderEvent1->HasAccessibleInReasonSubtree()) - aAccEvent2->mEventRule = nsAccEvent::eDoNotEmit; - else - aAccEvent1->mEventRule = nsAccEvent::eDoNotEmit; -} - -void -nsAccEvent::CoalesceReorderEventsFromSameTree(nsAccEvent *aAccEvent, - nsAccEvent *aDescendantAccEvent) -{ - // Do not emit descendant event if this event is unconditional. - nsCOMPtr reorderEvent = do_QueryInterface(aAccEvent); - if (reorderEvent->IsUnconditionalEvent()) { - aDescendantAccEvent->mEventRule = nsAccEvent::eDoNotEmit; - return; - } - - // Do not emit descendant event if this event is valid otherwise do not emit - // this event. - if (reorderEvent->HasAccessibleInReasonSubtree()) - aDescendantAccEvent->mEventRule = nsAccEvent::eDoNotEmit; - else - aAccEvent->mEventRule = nsAccEvent::eDoNotEmit; -} //////////////////////////////////////////////////////////////////////////////// // nsAccReorderEvent diff --git a/accessible/src/base/nsAccEvent.h b/accessible/src/base/nsAccEvent.h index 29e8bc372842..fc772ae8d874 100644 --- a/accessible/src/base/nsAccEvent.h +++ b/accessible/src/base/nsAccEvent.h @@ -141,45 +141,7 @@ protected: nsCOMPtr mDOMNode; nsCOMPtr mDocAccessible; -public: - - /** - * Apply event rules to pending events, this method is called in - * FlushingPendingEvents(). - * Result of this method: - * Event rule of filtered events will be set to eDoNotEmit. - * Events with other event rule are good to emit. - */ - static void ApplyEventRules(nsTArray > &aEventsToFire); - -private: - /** - * Apply aEventRule to same type event that from sibling nodes of aDOMNode. - * @param aEventsToFire array of pending events - * @param aStart start index of pending events to be scanned - * @param aEnd end index to be scanned (not included) - * @param aEventType target event type - * @param aDOMNode target are siblings of this node - * @param aEventRule the event rule to be applied - * (should be eDoNotEmit or eAllowDupes) - */ - static void ApplyToSiblings(nsTArray > &aEventsToFire, - PRUint32 aStart, PRUint32 aEnd, - PRUint32 aEventType, nsIDOMNode* aDOMNode, - EEventRule aEventRule); - - /** - * Do not emit one of two given reorder events fired for the same DOM node. - */ - static void CoalesceReorderEventsFromSameSource(nsAccEvent *aAccEvent1, - nsAccEvent *aAccEvent2); - - /** - * Do not emit one of two given reorder events fired for DOM nodes in the case - * when one DOM node is in parent chain of second one. - */ - static void CoalesceReorderEventsFromSameTree(nsAccEvent *aAccEvent, - nsAccEvent *aDescendantAccEvent); + friend class nsAccEventQueue; }; NS_DEFINE_STATIC_IID_ACCESSOR(nsAccEvent, NS_ACCEVENT_IMPL_CID) diff --git a/accessible/src/base/nsAccessNode.h b/accessible/src/base/nsAccessNode.h index cd2099e4adc6..1a82ca21064e 100644 --- a/accessible/src/base/nsAccessNode.h +++ b/accessible/src/base/nsAccessNode.h @@ -100,11 +100,11 @@ NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_GENERAL, 0x22) PR_END_MACRO #define NS_ACCESSNODE_IMPL_CID \ -{ /* 13555f6e-0c0f-4002-84f6-558d47b8208e */ \ - 0x13555f6e, \ - 0xc0f, \ - 0x4002, \ - { 0x84, 0xf6, 0x55, 0x8d, 0x47, 0xb8, 0x20, 0x8e } \ +{ /* 2b07e3d7-00b3-4379-aa0b-ea22e2c8ffda */ \ + 0x2b07e3d7, \ + 0x00b3, \ + 0x4379, \ + { 0xaa, 0x0b, 0xea, 0x22, 0xe2, 0xc8, 0xff, 0xda } \ } class nsAccessNode: public nsIAccessNode @@ -168,9 +168,20 @@ class nsAccessNode: public nsIAccessNode */ virtual nsIFrame* GetFrame(); + /** + * Return the corresponding press shell for this accessible. + */ + already_AddRefed GetPresShell(); + + /** + * Return true if the accessible still has presentation shell. Light-weight + * version of IsDefunct() method. + */ + PRBool HasWeakShell() const { return !!mWeakShell; } + protected: nsresult MakeAccessNode(nsIDOMNode *aNode, nsIAccessNode **aAccessNode); - already_AddRefed GetPresShell(); + nsPresContext* GetPresContext(); already_AddRefed GetDocAccessible(); void LastRelease(); diff --git a/accessible/src/base/nsDocAccessible.cpp b/accessible/src/base/nsDocAccessible.cpp index 7272ac11ab3f..3c619d7648f9 100644 --- a/accessible/src/base/nsDocAccessible.cpp +++ b/accessible/src/base/nsDocAccessible.cpp @@ -85,8 +85,7 @@ nsIAtom *nsDocAccessible::gLastFocusedFrameType = nsnull; nsDocAccessible::nsDocAccessible(nsIDOMNode *aDOMNode, nsIWeakReference* aShell): nsHyperTextAccessibleWrap(aDOMNode, aShell), mWnd(nsnull), mScrollPositionChangedTicks(0), mIsContentLoaded(PR_FALSE), - mIsLoadCompleteFired(PR_FALSE), mInFlushPendingEvents(PR_FALSE), - mFireEventTimerStarted(PR_FALSE) + mIsLoadCompleteFired(PR_FALSE) { // XXX aaronl should we use an algorithm for the initial cache size? mAccessNodeCache.Init(kDefaultCacheSize); @@ -151,17 +150,14 @@ ElementTraverser(const void *aKey, nsIAccessNode *aAccessNode, NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocAccessible) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDocAccessible, nsAccessible) - PRUint32 i, length = tmp->mEventsToFire.Length(); - for (i = 0; i < length; ++i) { - NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEventsToFire[i]"); - cb.NoteXPCOMChild(tmp->mEventsToFire[i].get()); - } + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEventQueue"); + cb.NoteXPCOMChild(tmp->mEventQueue.get()); tmp->mAccessNodeCache.EnumerateRead(ElementTraverser, &cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDocAccessible, nsAccessible) - NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mEventsToFire) + NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEventQueue) tmp->ClearCache(tmp->mAccessNodeCache); NS_IMPL_CYCLE_COLLECTION_UNLINK_END @@ -609,6 +605,9 @@ nsDocAccessible::Init() nsresult rv = nsHyperTextAccessibleWrap::Init(); NS_ENSURE_SUCCESS(rv, rv); + // Initialize event queue. + mEventQueue = new nsAccEventQueue(this); + // Fire reorder event to notify new accessible document has been created and // attached to the tree. nsCOMPtr reorderEvent = @@ -626,6 +625,9 @@ nsDocAccessible::Shutdown() return NS_OK; // Already shutdown } + mEventQueue->Shutdown(); + mEventQueue = nsnull; + nsCOMPtr treeItem = nsCoreUtils::GetDocShellTreeItemFor(mDOMNode); ShutdownChildDocuments(treeItem); @@ -641,21 +643,6 @@ nsDocAccessible::Shutdown() nsHyperTextAccessibleWrap::Shutdown(); - if (mFireEventTimer) { - // Doc being shut down before delayed events were processed. - mFireEventTimer->Cancel(); - mFireEventTimer = nsnull; - mEventsToFire.Clear(); - - if (mFireEventTimerStarted && !mInFlushPendingEvents) { - // Make sure we release the kung fu death grip which is always there when - // fire event timer was started but FlushPendingEvents() callback wasn't - // triggered yet. If FlushPendingEvents() is in call stack, kung fu death - // grip will be released there. - NS_RELEASE_THIS(); - } - } - // Remove from the cache after other parts of Shutdown(), so that Shutdown() procedures // can find the doc or root accessible in the cache if they need it. // We don't do this during ShutdownAccessibility() because that is already clearing the cache @@ -1615,257 +1602,166 @@ nsDocAccessible::FireDelayedAccessibleEvent(nsIAccessibleEvent *aEvent) NS_ENSURE_ARG(aEvent); nsRefPtr accEvent = nsAccUtils::QueryObject(aEvent); - mEventsToFire.AppendElement(accEvent); + if (mEventQueue) + mEventQueue->Push(accEvent); - // Filter events. - nsAccEvent::ApplyEventRules(mEventsToFire); - - // Process events. - return PreparePendingEventsFlush(); -} - -nsresult -nsDocAccessible::PreparePendingEventsFlush() -{ - nsresult rv = NS_OK; - - // Create timer if we don't have it yet. - if (!mFireEventTimer) { - mFireEventTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); - NS_ENSURE_SUCCESS(rv, rv); - } - - // If there are delayed events in the queue and event timer wasn't started - // then initialize the timer so that delayed event will be processed in - // FlushPendingEvents. - if (mEventsToFire.Length() > 0 && !mFireEventTimerStarted) { - - rv = mFireEventTimer->InitWithFuncCallback(FlushEventsCallback, - this, 0, - nsITimer::TYPE_ONE_SHOT); - - if (NS_SUCCEEDED(rv)) { - // Kung fu death grip to prevent crash in callback. - NS_ADDREF_THIS(); - - mFireEventTimerStarted = PR_TRUE; - } - } - - return rv; + return NS_OK; } void -nsDocAccessible::FlushPendingEvents() -{ - mInFlushPendingEvents = PR_TRUE; +nsDocAccessible::ProcessPendingEvent(nsAccEvent *aEvent) +{ + nsCOMPtr accessible; + aEvent->GetAccessible(getter_AddRefs(accessible)); - PRUint32 length = mEventsToFire.Length(); - NS_ASSERTION(length, "How did we get here without events to fire?"); - nsCOMPtr presShell = GetPresShell(); - if (!presShell) - length = 0; // The doc is now shut down, don't fire events in it anymore - else { - // Flush layout so that all the frame construction, reflow, and styles are - // up-to-date. This will ensure we can get frames for the related nodes, as - // well as get the most current information for calculating things like - // 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); + nsCOMPtr domNode; + aEvent->GetDOMNode(getter_AddRefs(domNode)); + + PRUint32 eventType = aEvent->GetEventType(); + EIsFromUserInput isFromUserInput = + aEvent->IsFromUserInput() ? eFromUserInput : eNoUserInput; + + PRBool isAsync = aEvent->IsAsync(); + + if (domNode == gLastFocusedNode && isAsync && + (eventType == nsIAccessibleEvent::EVENT_SHOW || + eventType == nsIAccessibleEvent::EVENT_HIDE)) { + // If frame type didn't change for this event, then we don't actually need to invalidate + // However, we only keep track of the old frame type for the focus, where it's very + // important not to destroy and recreate the accessible for minor style changes, + // such as a:focus { overflow: scroll; } + nsCOMPtr focusContent(do_QueryInterface(domNode)); + if (focusContent) { + nsIFrame *focusFrame = focusContent->GetPrimaryFrame(); + nsIAtom *newFrameType = + (focusFrame && focusFrame->GetStyleVisibility()->IsVisible()) ? + focusFrame->GetType() : nsnull; + + if (newFrameType == gLastFocusedFrameType) { + // Don't need to invalidate this current accessible, but can + // just invalidate the children instead + FireShowHideEvents(domNode, PR_TRUE, eventType, eNormalEvent, + isAsync, isFromUserInput); + return; + } + gLastFocusedFrameType = newFrameType; + } } - // Process only currently queued events. In the meantime, newly appended - // events will not be processed. - for (PRUint32 index = 0; index < length; index ++) { - - // No presshell means the document was shut down duiring event handling - // by AT. - if (!mWeakShell) - break; + if (eventType == nsIAccessibleEvent::EVENT_SHOW) { - nsAccEvent *accEvent = mEventsToFire[index]; + nsCOMPtr containerAccessible; + if (accessible) + accessible->GetParent(getter_AddRefs(containerAccessible)); - if (accEvent->GetEventRule() == nsAccEvent::eDoNotEmit) - continue; + if (!containerAccessible) { + GetAccessibleInParentChain(domNode, PR_TRUE, + getter_AddRefs(containerAccessible)); + if (!containerAccessible) + containerAccessible = this; + } - nsCOMPtr accessible; - accEvent->GetAccessible(getter_AddRefs(accessible)); + if (isAsync) { + // For asynch show, delayed invalidatation of parent's children + nsRefPtr containerAcc = + nsAccUtils::QueryAccessible(containerAccessible); + if (containerAcc) + containerAcc->InvalidateChildren(); - nsCOMPtr domNode; - accEvent->GetDOMNode(getter_AddRefs(domNode)); + // Some show events in the subtree may have been removed to + // avoid firing redundant events. But, we still need to make sure any + // accessibles parenting those shown nodes lose their child references. + InvalidateChildrenInSubtree(domNode); + } - PRUint32 eventType = accEvent->GetEventType(); - EIsFromUserInput isFromUserInput = - accEvent->IsFromUserInput() ? eFromUserInput : eNoUserInput; - - PRBool isAsync = accEvent->IsAsync(); - - if (domNode == gLastFocusedNode && isAsync && - (eventType == nsIAccessibleEvent::EVENT_SHOW || - eventType == nsIAccessibleEvent::EVENT_HIDE)) { - // If frame type didn't change for this event, then we don't actually need to invalidate - // However, we only keep track of the old frame type for the focus, where it's very - // important not to destroy and recreate the accessible for minor style changes, - // such as a:focus { overflow: scroll; } - nsCOMPtr focusContent(do_QueryInterface(domNode)); - if (focusContent) { - nsIFrame *focusFrame = focusContent->GetPrimaryFrame(); - nsIAtom *newFrameType = - (focusFrame && focusFrame->GetStyleVisibility()->IsVisible()) ? - focusFrame->GetType() : nsnull; - - if (newFrameType == gLastFocusedFrameType) { - // Don't need to invalidate this current accessible, but can - // just invalidate the children instead - FireShowHideEvents(domNode, PR_TRUE, eventType, eNormalEvent, - isAsync, isFromUserInput); - continue; - } - gLastFocusedFrameType = newFrameType; + // Also fire text changes if the node being created could affect the text in an nsIAccessibleText parent. + // When a node is being made visible or is inserted, the text in an ancestor hyper text will gain characters + // At this point we now have the frame and accessible for this node if there is one. That is why we + // wait to fire this here, instead of in InvalidateCacheSubtree(), where we wouldn't be able to calculate + // the offset, length and text for the text change. + if (domNode && domNode != mDOMNode) { + nsRefPtr textChangeEvent = + CreateTextChangeEventForNode(containerAccessible, domNode, accessible, + PR_TRUE, PR_TRUE, isFromUserInput); + if (textChangeEvent) { + // XXX Queue them up and merge the text change events + // XXX We need a way to ignore SplitNode and JoinNode() when they + // do not affect the text within the hypertext + nsEventShell::FireEvent(textChangeEvent); } } - if (eventType == nsIAccessibleEvent::EVENT_SHOW) { + // Fire show/create events for this node or first accessible descendants of it + FireShowHideEvents(domNode, PR_FALSE, eventType, eNormalEvent, isAsync, + isFromUserInput); + return; + } - nsCOMPtr containerAccessible; - if (accessible) - accessible->GetParent(getter_AddRefs(containerAccessible)); + if (accessible) { + if (eventType == nsIAccessibleEvent::EVENT_INTERNAL_LOAD) { + nsRefPtr docAcc = + nsAccUtils::QueryAccessibleDocument(accessible); + NS_ASSERTION(docAcc, "No doc accessible for doc load event"); - if (!containerAccessible) { - GetAccessibleInParentChain(domNode, PR_TRUE, - getter_AddRefs(containerAccessible)); - if (!containerAccessible) - containerAccessible = this; - } - - if (isAsync) { - // For asynch show, delayed invalidatation of parent's children - nsRefPtr containerAcc = - nsAccUtils::QueryAccessible(containerAccessible); - if (containerAcc) - containerAcc->InvalidateChildren(); - - // Some show events in the subtree may have been removed to - // avoid firing redundant events. But, we still need to make sure any - // accessibles parenting those shown nodes lose their child references. - InvalidateChildrenInSubtree(domNode); - } - - // Also fire text changes if the node being created could affect the text in an nsIAccessibleText parent. - // When a node is being made visible or is inserted, the text in an ancestor hyper text will gain characters - // At this point we now have the frame and accessible for this node if there is one. That is why we - // wait to fire this here, instead of in InvalidateCacheSubtree(), where we wouldn't be able to calculate - // the offset, length and text for the text change. - if (domNode && domNode != mDOMNode) { - nsRefPtr textChangeEvent = - CreateTextChangeEventForNode(containerAccessible, domNode, accessible, - PR_TRUE, PR_TRUE, isFromUserInput); - if (textChangeEvent) { - // XXX Queue them up and merge the text change events - // XXX We need a way to ignore SplitNode and JoinNode() when they - // do not affect the text within the hypertext - nsEventShell::FireEvent(textChangeEvent); - } - } - - // Fire show/create events for this node or first accessible descendants of it - FireShowHideEvents(domNode, PR_FALSE, eventType, eNormalEvent, isAsync, - isFromUserInput); - continue; + if (docAcc) + docAcc->FireDocLoadEvents(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE); } - - if (accessible) { - if (eventType == nsIAccessibleEvent::EVENT_INTERNAL_LOAD) { - nsRefPtr docAcc = - nsAccUtils::QueryAccessibleDocument(accessible); - NS_ASSERTION(docAcc, "No doc accessible for doc load event"); - - if (docAcc) - docAcc->FireDocLoadEvents(nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE); - } - else if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) { - nsCOMPtr accessibleText = do_QueryInterface(accessible); - PRInt32 caretOffset; - if (accessibleText && NS_SUCCEEDED(accessibleText->GetCaretOffset(&caretOffset))) { + else if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) { + nsCOMPtr accessibleText = do_QueryInterface(accessible); + PRInt32 caretOffset; + if (accessibleText && NS_SUCCEEDED(accessibleText->GetCaretOffset(&caretOffset))) { #ifdef DEBUG_A11Y - PRUnichar chAtOffset; - accessibleText->GetCharacterAtOffset(caretOffset, &chAtOffset); - printf("\nCaret moved to %d with char %c", caretOffset, chAtOffset); + PRUnichar chAtOffset; + accessibleText->GetCharacterAtOffset(caretOffset, &chAtOffset); + printf("\nCaret moved to %d with char %c", caretOffset, chAtOffset); #endif #ifdef DEBUG_CARET - // Test caret line # -- fire an EVENT_ALERT on the focused node so we can watch the - // line-number object attribute on it - nsCOMPtr accForFocus; - GetAccService()->GetAccessibleFor(gLastFocusedNode, getter_AddRefs(accForFocus)); - nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accForFocus); + // Test caret line # -- fire an EVENT_ALERT on the focused node so we can watch the + // line-number object attribute on it + nsCOMPtr accForFocus; + GetAccService()->GetAccessibleFor(gLastFocusedNode, getter_AddRefs(accForFocus)); + nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accForFocus); #endif - nsRefPtr caretMoveEvent = - new nsAccCaretMoveEvent(accessible, caretOffset); - if (!caretMoveEvent) - break; // Out of memory, break out to release kung fu death grip + nsRefPtr caretMoveEvent = + new nsAccCaretMoveEvent(accessible, caretOffset); + if (!caretMoveEvent) + return; - nsEventShell::FireEvent(caretMoveEvent); + nsEventShell::FireEvent(caretMoveEvent); - PRInt32 selectionCount; - accessibleText->GetSelectionCount(&selectionCount); - if (selectionCount) { // There's a selection so fire selection change as well - nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, - accessible, PR_TRUE); - } - } - } - else if (eventType == nsIAccessibleEvent::EVENT_REORDER) { - // Fire reorder event if it's unconditional (see InvalidateCacheSubtree - // method) or if changed node (that is the reason of this reorder event) - // is accessible or has accessible children. - nsCOMPtr reorderEvent = do_QueryInterface(accEvent); - if (reorderEvent->IsUnconditionalEvent() || - reorderEvent->HasAccessibleInReasonSubtree()) { - nsEventShell::FireEvent(accEvent); + PRInt32 selectionCount; + accessibleText->GetSelectionCount(&selectionCount); + if (selectionCount) { // There's a selection so fire selection change as well + nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED, + accessible, PR_TRUE); } + } + } + else if (eventType == nsIAccessibleEvent::EVENT_REORDER) { + // Fire reorder event if it's unconditional (see InvalidateCacheSubtree + // method) or if changed node (that is the reason of this reorder event) + // is accessible or has accessible children. + nsCOMPtr reorderEvent = do_QueryInterface(aEvent); + if (reorderEvent->IsUnconditionalEvent() || + reorderEvent->HasAccessibleInReasonSubtree()) { + nsEventShell::FireEvent(aEvent); } - else { - nsEventShell::FireEvent(accEvent); + } + else { + nsEventShell::FireEvent(aEvent); - // Post event processing - if (eventType == nsIAccessibleEvent::EVENT_HIDE) { - // Shutdown nsIAccessNode's or nsIAccessibles for any DOM nodes in - // this subtree. - nsCOMPtr hidingNode; - accEvent->GetDOMNode(getter_AddRefs(hidingNode)); - if (hidingNode) { - RefreshNodes(hidingNode); // Will this bite us with asynch events - } + // Post event processing + if (eventType == nsIAccessibleEvent::EVENT_HIDE) { + // Shutdown nsIAccessNode's or nsIAccessibles for any DOM nodes in + // this subtree. + nsCOMPtr hidingNode; + aEvent->GetDOMNode(getter_AddRefs(hidingNode)); + if (hidingNode) { + RefreshNodes(hidingNode); // Will this bite us with asynch events } } } } - - // Mark we are ready to start event processing timer again. - mFireEventTimerStarted = PR_FALSE; - - // If the document accessible is alive then remove processed events from the - // queue (otherwise they were removed on shutdown already) and reinitialize - // queue processing callback if necessary (new events might occur duiring - // delayed event processing). - if (mWeakShell) { - mEventsToFire.RemoveElementsAt(0, length); - PreparePendingEventsFlush(); - } - - mInFlushPendingEvents = PR_FALSE; - NS_RELEASE_THIS(); // Release kung fu death grip. -} - -void nsDocAccessible::FlushEventsCallback(nsITimer *aTimer, void *aClosure) -{ - nsDocAccessible *accessibleDoc = static_cast(aClosure); - NS_ASSERTION(accessibleDoc, "How did we get here without an accessible document?"); - if (accessibleDoc) { - // A lot of crashes were happening here, so now we're reffing the doc - // now until the events are flushed - accessibleDoc->FlushPendingEvents(); - } } void nsDocAccessible::InvalidateChildrenInSubtree(nsIDOMNode *aStartNode) diff --git a/accessible/src/base/nsDocAccessible.h b/accessible/src/base/nsDocAccessible.h index 0ceb5f678ebb..2e017edea71e 100644 --- a/accessible/src/base/nsDocAccessible.h +++ b/accessible/src/base/nsDocAccessible.h @@ -56,11 +56,11 @@ class nsIScrollableView; const PRUint32 kDefaultCacheSize = 256; #define NS_DOCACCESSIBLE_IMPL_CID \ -{ /* 11d54d4f-f135-4b1b-80e4-6425a64f703c */ \ - 0x11d54d4f, \ - 0xf135, \ - 0x4b1b, \ - { 0x80, 0xe4, 0x64, 0x25, 0xa6, 0x4f, 0x70, 0x3c } \ +{ /* 5559d4f2-4338-40eb-bfca-0fb7d73e958a */ \ + 0x5559d4f2, \ + 0x4338, \ + 0x40eb, \ + { 0xbf, 0xca, 0x0f, 0xb7, 0xd7, 0x3e, 0x95, 0x8a } \ } class nsDocAccessible : public nsHyperTextAccessibleWrap, @@ -172,9 +172,10 @@ public: virtual void FireDocLoadEvents(PRUint32 aEventType); /** - * Used to flush pending events, called after timeout. See FlushPendingEvents. + * Process the event when the queue of pending events is untwisted. Fire + * accessible events as result of the processing. */ - static void FlushEventsCallback(nsITimer *aTimer, void *aClosure); + void ProcessPendingEvent(nsAccEvent* aEvent); protected: /** @@ -214,16 +215,6 @@ protected: */ void ARIAAttributeChanged(nsIContent* aContent, nsIAtom* aAttribute); - /** - * Process delayed (pending) events resulted in normal events firing. - */ - void FlushPendingEvents(); - - /** - * Start the timer to flush delayed (pending) events. - */ - nsresult PreparePendingEventsFlush(); - /** * Fire text changed event for character data changed. The method is used * from nsIMutationObserver methods. @@ -293,16 +284,13 @@ protected: void *mWnd; nsCOMPtr mDocument; nsCOMPtr mScrollWatchTimer; - nsCOMPtr mFireEventTimer; PRUint16 mScrollPositionChangedTicks; // Used for tracking scroll events PRPackedBool mIsContentLoaded; PRPackedBool mIsLoadCompleteFired; protected: - PRBool mInFlushPendingEvents; - PRBool mFireEventTimerStarted; - nsTArray > mEventsToFire; + nsRefPtr mEventQueue; static PRUint32 gLastFocusedAccessiblesState; static nsIAtom *gLastFocusedFrameType; diff --git a/accessible/src/base/nsEventShell.cpp b/accessible/src/base/nsEventShell.cpp index 48e3a1877179..76c1c28417c3 100644 --- a/accessible/src/base/nsEventShell.cpp +++ b/accessible/src/base/nsEventShell.cpp @@ -38,7 +38,7 @@ #include "nsEventShell.h" -#include "nsAccessible.h" +#include "nsDocAccessible.h" //////////////////////////////////////////////////////////////////////////////// // nsEventShell @@ -95,3 +95,279 @@ nsEventShell::GetEventAttributes(nsIDOMNode *aNode, PRBool nsEventShell::sEventFromUserInput = PR_FALSE; nsCOMPtr nsEventShell::sEventTargetNode; + + +//////////////////////////////////////////////////////////////////////////////// +// nsAccEventQueue +//////////////////////////////////////////////////////////////////////////////// + +nsAccEventQueue::nsAccEventQueue(nsDocAccessible *aDocument): + mProcessingStarted(PR_FALSE), mDocument(aDocument) +{ +} + +nsAccEventQueue::~nsAccEventQueue() +{ + NS_ASSERTION(mDocument, "Queue wasn't shut down!"); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsAccEventQueue: nsISupports and cycle collection + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsAccEventQueue) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsAccEventQueue) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsAccEventQueue) + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDocument"); + cb.NoteXPCOMChild(static_cast(tmp->mDocument.get())); + + PRUint32 i, length = tmp->mEvents.Length(); + for (i = 0; i < length; ++i) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvents[i]"); + cb.NoteXPCOMChild(tmp->mEvents[i].get()); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsAccEventQueue) + NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument) + NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mEvents) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsAccEventQueue) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsAccEventQueue) + +//////////////////////////////////////////////////////////////////////////////// +// nsAccEventQueue: public + +void +nsAccEventQueue::Push(nsAccEvent *aEvent) +{ + mEvents.AppendElement(aEvent); + + // Filter events. + CoalesceEvents(); + + // Process events. + PrepareFlush(); +} + +void +nsAccEventQueue::Shutdown() +{ + mDocument = nsnull; + mEvents.Clear(); +} + +//////////////////////////////////////////////////////////////////////////////// +// nsAccEventQueue: private + +void +nsAccEventQueue::PrepareFlush() +{ + // If there are pending events in the queue and events flush isn't planed + // yet start events flush asyncroniously. + if (mEvents.Length() > 0 && !mProcessingStarted) { + NS_DISPATCH_RUNNABLEMETHOD(Flush, this) + mProcessingStarted = PR_TRUE; + } +} + +void +nsAccEventQueue::Flush() +{ + // If the document accessible is now shut down, don't fire events in it + // anymore. + if (!mDocument) + return; + + nsCOMPtr presShell = mDocument->GetPresShell(); + if (!presShell) + return; + + // Flush layout so that all the frame construction, reflow, and styles are + // up-to-date. This will ensure we can get frames for the related nodes, as + // well as get the most current information for calculating things like + // 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); + + // Process only currently queued events. Newly appended events duiring events + // flusing won't be processed. + PRUint32 length = mEvents.Length(); + NS_ASSERTION(length, "How did we get here without events to fire?"); + + for (PRUint32 index = 0; index < length; index ++) { + + // No presshell means the document was shut down duiring event handling + // by AT. + if (!mDocument || !mDocument->HasWeakShell()) + break; + + nsAccEvent *accEvent = mEvents[index]; + if (accEvent->mEventRule != nsAccEvent::eDoNotEmit) + mDocument->ProcessPendingEvent(accEvent); + } + + // Mark we are ready to start event processing again. + mProcessingStarted = PR_FALSE; + + // If the document accessible is alive then remove processed events from the + // queue (otherwise they were removed on shutdown already) and reinitialize + // queue processing callback if necessary (new events might occur duiring + // delayed event processing). + if (mDocument && mDocument->HasWeakShell()) { + mEvents.RemoveElementsAt(0, length); + PrepareFlush(); + } +} + +void +nsAccEventQueue::CoalesceEvents() +{ + PRUint32 numQueuedEvents = mEvents.Length(); + PRInt32 tail = numQueuedEvents - 1; + + nsAccEvent* tailEvent = mEvents[tail]; + switch(tailEvent->mEventRule) { + case nsAccEvent::eCoalesceFromSameSubtree: + { + for (PRInt32 index = 0; index < tail; index ++) { + nsAccEvent* thisEvent = mEvents[index]; + if (thisEvent->mEventType != tailEvent->mEventType) + continue; // Different type + + if (thisEvent->mEventRule == nsAccEvent::eAllowDupes || + thisEvent->mEventRule == nsAccEvent::eDoNotEmit) + continue; // Do not need to check + + if (thisEvent->mDOMNode == tailEvent->mDOMNode) { + if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) { + CoalesceReorderEventsFromSameSource(thisEvent, tailEvent); + continue; + } + + // 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; + } + + // Do not emit thisEvent, also apply this result to sibling + // nodes of thisDOMNode. + thisEvent->mEventRule = nsAccEvent::eDoNotEmit; + ApplyToSiblings(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; + } + + // Do not emit tailEvent, also apply this result to sibling + // nodes of tailDOMNode. + tailEvent->mEventRule = nsAccEvent::eDoNotEmit; + ApplyToSiblings(0, tail, tailEvent->mEventType, + tailEvent->mDOMNode, nsAccEvent::eDoNotEmit); + break; + } + } // 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 + ApplyToSiblings(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 ++) { + nsAccEvent* accEvent = mEvents[index]; + if (accEvent->mEventType == tailEvent->mEventType && + accEvent->mEventRule == tailEvent->mEventRule && + accEvent->mDOMNode == tailEvent->mDOMNode) { + accEvent->mEventRule = nsAccEvent::eDoNotEmit; + } + } + } break; // case eRemoveDupes + + default: + break; // case eAllowDupes, eDoNotEmit + } // switch +} + +void +nsAccEventQueue::ApplyToSiblings(PRUint32 aStart, PRUint32 aEnd, + PRUint32 aEventType, nsIDOMNode* aDOMNode, + nsAccEvent::EEventRule aEventRule) +{ + for (PRUint32 index = aStart; index < aEnd; index ++) { + nsAccEvent* accEvent = mEvents[index]; + if (accEvent->mEventType == aEventType && + accEvent->mEventRule != nsAccEvent::eDoNotEmit && + nsCoreUtils::AreSiblings(accEvent->mDOMNode, aDOMNode)) { + accEvent->mEventRule = aEventRule; + } + } +} + +void +nsAccEventQueue::CoalesceReorderEventsFromSameSource(nsAccEvent *aAccEvent1, + nsAccEvent *aAccEvent2) +{ + // Do not emit event2 if event1 is unconditional. + nsCOMPtr reorderEvent1 = do_QueryInterface(aAccEvent1); + if (reorderEvent1->IsUnconditionalEvent()) { + aAccEvent2->mEventRule = nsAccEvent::eDoNotEmit; + return; + } + + // Do not emit event1 if event2 is unconditional. + nsCOMPtr reorderEvent2 = do_QueryInterface(aAccEvent2); + if (reorderEvent2->IsUnconditionalEvent()) { + aAccEvent1->mEventRule = nsAccEvent::eDoNotEmit; + return; + } + + // Do not emit event2 if event1 is valid, otherwise do not emit event1. + if (reorderEvent1->HasAccessibleInReasonSubtree()) + aAccEvent2->mEventRule = nsAccEvent::eDoNotEmit; + else + aAccEvent1->mEventRule = nsAccEvent::eDoNotEmit; +} + +void +nsAccEventQueue::CoalesceReorderEventsFromSameTree(nsAccEvent *aAccEvent, + nsAccEvent *aDescendantAccEvent) +{ + // Do not emit descendant event if this event is unconditional. + nsCOMPtr reorderEvent = do_QueryInterface(aAccEvent); + if (reorderEvent->IsUnconditionalEvent()) { + aDescendantAccEvent->mEventRule = nsAccEvent::eDoNotEmit; + return; + } + + // Do not emit descendant event if this event is valid otherwise do not emit + // this event. + if (reorderEvent->HasAccessibleInReasonSubtree()) + aDescendantAccEvent->mEventRule = nsAccEvent::eDoNotEmit; + else + aAccEvent->mEventRule = nsAccEvent::eDoNotEmit; +} diff --git a/accessible/src/base/nsEventShell.h b/accessible/src/base/nsEventShell.h index 01076631287a..6d1d90c40486 100644 --- a/accessible/src/base/nsEventShell.h +++ b/accessible/src/base/nsEventShell.h @@ -39,8 +39,12 @@ #ifndef _nsEventShell_H_ #define _nsEventShell_H_ +#include "nsCoreUtils.h" #include "nsAccEvent.h" +/** + * Used for everything about events. + */ class nsEventShell { public: @@ -77,4 +81,79 @@ private: static PRBool sEventFromUserInput; }; + +/** + * Event queue. + */ +class nsAccEventQueue : public nsISupports +{ +public: + nsAccEventQueue(nsDocAccessible *aDocument); + ~nsAccEventQueue(); + + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsAccEventQueue) + + /** + * Push event to queue, coalesce it if necessary. Start pending processing. + */ + void Push(nsAccEvent *aEvent); + + /** + * Shutdown the queue. + */ + void Shutdown(); + +private: + + /** + * Start pending events procesing asyncroniously. + */ + void PrepareFlush(); + + /** + * Process pending events. It calls nsDocAccessible::ProcessPendingEvent() + * where the real event processing is happen. + */ + void Flush(); + + NS_DECL_RUNNABLEMETHOD(nsAccEventQueue, Flush) + + /** + * Coalesce redurant events from the queue. + */ + void CoalesceEvents(); + + /** + * Apply aEventRule to same type event that from sibling nodes of aDOMNode. + * @param aEventsToFire array of pending events + * @param aStart start index of pending events to be scanned + * @param aEnd end index to be scanned (not included) + * @param aEventType target event type + * @param aDOMNode target are siblings of this node + * @param aEventRule the event rule to be applied + * (should be eDoNotEmit or eAllowDupes) + */ + void ApplyToSiblings(PRUint32 aStart, PRUint32 aEnd, + PRUint32 aEventType, nsIDOMNode* aDOMNode, + nsAccEvent::EEventRule aEventRule); + + /** + * Do not emit one of two given reorder events fired for the same DOM node. + */ + void CoalesceReorderEventsFromSameSource(nsAccEvent *aAccEvent1, + nsAccEvent *aAccEvent2); + + /** + * Do not emit one of two given reorder events fired for DOM nodes in the case + * when one DOM node is in parent chain of second one. + */ + void CoalesceReorderEventsFromSameTree(nsAccEvent *aAccEvent, + nsAccEvent *aDescendantAccEvent); + + PRBool mProcessingStarted; + nsRefPtr mDocument; + nsTArray > mEvents; +}; + #endif