void NotificationController::WillRefresh(mozilla::TimeStamp aTime) { // If the document accessible that notification collector was created for is // now shut down, don't process notifications anymore. NS_ASSERTION(mDocument, "The document was shut down while refresh observer is attached!"); if (!mDocument) return; // Any generic notifications should be queued if we're processing content // insertions or generic notifications. mObservingState = eRefreshProcessingForUpdate; // Initial accessible tree construction. if (mTreeConstructedState == eTreeConstructionPending) { // If document is not bound to parent at this point then the document is not // ready yet (process notifications later). if (!mDocument->IsBoundToParent()) return; #ifdef DEBUG_NOTIFICATIONS printf("\ninitial tree created, document: %p, document node: %p\n", mDocument.get(), mDocument->GetDocumentNode()); #endif mTreeConstructedState = eTreeConstructed; mDocument->NotifyOfInitialUpdate(); NS_ASSERTION(mContentInsertions.Length() == 0, "Pending content insertions while initial accessible tree isn't created!"); } // Process content inserted notifications to update the tree. Process other // notifications like DOM events and then flush event queue. If any new // notifications are queued during this processing then they will be processed // on next refresh. If notification processing queues up new events then they // are processed in this refresh. If events processing queues up new events // then new events are processed on next refresh. // Note: notification processing or event handling may shut down the owning // document accessible. // Process only currently queued content inserted notifications. nsTArray<nsRefPtr<ContentInsertion> > contentInsertions; contentInsertions.SwapElements(mContentInsertions); PRUint32 insertionCount = contentInsertions.Length(); for (PRUint32 idx = 0; idx < insertionCount; idx++) { contentInsertions[idx]->Process(); if (!mDocument) return; } // Process rendered text change notifications. mTextHash.EnumerateEntries(TextEnumerator, mDocument); mTextHash.Clear(); // Bind hanging child documents. PRUint32 childDocCount = mHangingChildDocuments.Length(); for (PRUint32 idx = 0; idx < childDocCount; idx++) { nsDocAccessible* childDoc = mHangingChildDocuments[idx]; nsIContent* ownerContent = mDocument->GetDocumentNode()-> FindContentForSubDocument(childDoc->GetDocumentNode()); 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); continue; } outerDocAcc->RemoveChild(childDoc); } // Failed to bind the child document, destroy it. childDoc->Shutdown(); } } mHangingChildDocuments.Clear(); // Process only currently queued generic notifications. nsTArray < nsRefPtr<Notification> > notifications; notifications.SwapElements(mNotifications); PRUint32 notificationCount = notifications.Length(); for (PRUint32 idx = 0; idx < notificationCount; idx++) { notifications[idx]->Process(); if (!mDocument) return; } // If a generic notification occurs after this point then we may be allowed to // process it synchronously. mObservingState = eRefreshObserving; // Process only currently queued events. nsTArray<nsRefPtr<AccEvent> > events; events.SwapElements(mEvents); PRUint32 eventCount = events.Length(); for (PRUint32 idx = 0; idx < eventCount; idx++) { AccEvent* accEvent = events[idx]; if (accEvent->mEventRule != AccEvent::eDoNotEmit) { mDocument->ProcessPendingEvent(accEvent); AccMutationEvent* showOrhideEvent = downcast_accEvent(accEvent); if (showOrhideEvent) { if (showOrhideEvent->mTextChangeEvent) mDocument->ProcessPendingEvent(showOrhideEvent->mTextChangeEvent); } } if (!mDocument) return; } // Stop further processing if there are no newly queued insertions, // notifications or events. if (mContentInsertions.Length() == 0 && mNotifications.Length() == 0 && mEvents.Length() == 0 && mPresShell->RemoveRefreshObserver(this, Flush_Display)) { mObservingState = eNotObservingRefresh; } }
void NotificationController::CoalesceEvents() { PRUint32 numQueuedEvents = mEvents.Length(); PRInt32 tail = numQueuedEvents - 1; AccEvent* tailEvent = mEvents[tail]; // No node means this is application accessible (which can be a subject // of reorder events), we do not coalesce events for it currently. if (!tailEvent->mNode) return; switch(tailEvent->mEventRule) { case AccEvent::eCoalesceFromSameSubtree: { for (PRInt32 index = tail - 1; index >= 0; index--) { AccEvent* thisEvent = mEvents[index]; if (thisEvent->mEventType != tailEvent->mEventType) continue; // Different type // Skip event for application accessible since no coalescence for it // is supported. Ignore events from different documents since we don't // coalesce them. if (!thisEvent->mNode || thisEvent->mNode->GetOwnerDoc() != tailEvent->mNode->GetOwnerDoc()) continue; // Coalesce earlier event for the same target. if (thisEvent->mNode == tailEvent->mNode) { thisEvent->mEventRule = AccEvent::eDoNotEmit; return; } // If event queue contains an event of the same type and having target // that is sibling of target of newly appended event then apply its // event rule to the newly appended event. // Coalesce hide and show events for sibling targets. if (tailEvent->mEventType == nsIAccessibleEvent::EVENT_HIDE) { AccHideEvent* tailHideEvent = downcast_accEvent(tailEvent); AccHideEvent* thisHideEvent = downcast_accEvent(thisEvent); if (thisHideEvent->mParent == tailHideEvent->mParent) { tailEvent->mEventRule = thisEvent->mEventRule; // Coalesce text change events for hide events. if (tailEvent->mEventRule != AccEvent::eDoNotEmit) CoalesceTextChangeEventsFor(tailHideEvent, thisHideEvent); return; } } else if (tailEvent->mEventType == nsIAccessibleEvent::EVENT_SHOW) { if (thisEvent->mAccessible->GetParent() == tailEvent->mAccessible->GetParent()) { tailEvent->mEventRule = thisEvent->mEventRule; // Coalesce text change events for show events. if (tailEvent->mEventRule != AccEvent::eDoNotEmit) { AccShowEvent* tailShowEvent = downcast_accEvent(tailEvent); AccShowEvent* thisShowEvent = downcast_accEvent(thisEvent); CoalesceTextChangeEventsFor(tailShowEvent, thisShowEvent); } return; } } // Ignore events unattached from DOM since we don't coalesce them. if (!thisEvent->mNode->IsInDoc()) continue; // Coalesce events by sibling targets (this is a case for reorder // events). if (thisEvent->mNode->GetNodeParent() == tailEvent->mNode->GetNodeParent()) { tailEvent->mEventRule = thisEvent->mEventRule; return; } // This and tail events can be anywhere in the tree, make assumptions // for mutation events. // Coalesce tail event if tail node is descendant of this node. Stop // processing if tail event is coalesced since all possible descendants // of this node was coalesced before. // Note: more older hide event target (thisNode) can't contain recent // hide event target (tailNode), i.e. be ancestor of tailNode. Skip // this check for hide events. if (tailEvent->mEventType != nsIAccessibleEvent::EVENT_HIDE && nsCoreUtils::IsAncestorOf(thisEvent->mNode, tailEvent->mNode)) { tailEvent->mEventRule = AccEvent::eDoNotEmit; return; } // If this node is a descendant of tail node then coalesce this event, // check other events in the queue. Do not emit thisEvent, also apply // this result to sibling nodes of thisNode. if (nsCoreUtils::IsAncestorOf(tailEvent->mNode, thisEvent->mNode)) { thisEvent->mEventRule = AccEvent::eDoNotEmit; ApplyToSiblings(0, index, thisEvent->mEventType, thisEvent->mNode, AccEvent::eDoNotEmit); continue; } } // for (index) } break; // case eCoalesceFromSameSubtree case AccEvent::eCoalesceFromSameDocument: { // Used for focus event, coalesce more older event since focus event // for accessible can be duplicated by event for its document, we are // interested in focus event for accessible. for (PRInt32 index = tail - 1; index >= 0; index--) { AccEvent* thisEvent = mEvents[index]; if (thisEvent->mEventType == tailEvent->mEventType && thisEvent->mEventRule == tailEvent->mEventRule && thisEvent->GetDocAccessible() == tailEvent->GetDocAccessible()) { thisEvent->mEventRule = AccEvent::eDoNotEmit; return; } } } break; // case eCoalesceFromSameDocument case AccEvent::eRemoveDupes: { // Check for repeat events, coalesce newly appended event by more older // event. for (PRInt32 index = tail - 1; index >= 0; index--) { AccEvent* accEvent = mEvents[index]; if (accEvent->mEventType == tailEvent->mEventType && accEvent->mEventRule == tailEvent->mEventRule && accEvent->mNode == tailEvent->mNode) { tailEvent->mEventRule = AccEvent::eDoNotEmit; return; } } } break; // case eRemoveDupes default: break; // case eAllowDupes, eDoNotEmit } // switch }
nsresult nsAccessibleWrap::FirePlatformEvent(AccEvent* aEvent) { nsAccessible *accessible = aEvent->GetAccessible(); NS_ENSURE_TRUE(accessible, NS_ERROR_FAILURE); PRUint32 type = aEvent->GetEventType(); AtkObject *atkObj = nsAccessibleWrap::GetAtkObject(accessible); // We don't create ATK objects for nsIAccessible plain text leaves, // just return NS_OK in such case if (!atkObj) { NS_ASSERTION(type == nsIAccessibleEvent::EVENT_SHOW || type == nsIAccessibleEvent::EVENT_HIDE, "Event other than SHOW and HIDE fired for plain text leaves"); return NS_OK; } nsAccessibleWrap *accWrap = GetAccessibleWrap(atkObj); if (!accWrap) { return NS_OK; // Node is shut down } switch (type) { case nsIAccessibleEvent::EVENT_STATE_CHANGE: return FireAtkStateChangeEvent(aEvent, atkObj); case nsIAccessibleEvent::EVENT_TEXT_REMOVED: case nsIAccessibleEvent::EVENT_TEXT_INSERTED: return FireAtkTextChangedEvent(aEvent, atkObj); case nsIAccessibleEvent::EVENT_FOCUS: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_FOCUS\n")); nsRefPtr<nsRootAccessible> rootAccWrap = accWrap->GetRootAccessible(); if (rootAccWrap && rootAccWrap->mActivated) { atk_focus_tracker_notify(atkObj); // Fire state change event for focus nsRefPtr<AccEvent> stateChangeEvent = new AccStateChangeEvent(accessible, nsIAccessibleStates::STATE_FOCUSED, PR_FALSE, PR_TRUE); return FireAtkStateChangeEvent(stateChangeEvent, atkObj); } } break; case nsIAccessibleEvent::EVENT_VALUE_CHANGE: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_VALUE_CHANGE\n")); nsCOMPtr<nsIAccessibleValue> value(do_QueryObject(accessible)); if (value) { // Make sure this is a numeric value // Don't fire for MSAA string value changes (e.g. text editing) // ATK values are always numeric g_object_notify( (GObject*)atkObj, "accessible-value" ); } } break; case nsIAccessibleEvent::EVENT_SELECTION_CHANGED: MAI_LOG_DEBUG(("\n\nReceived: EVENT_SELECTION_CHANGED\n")); g_signal_emit_by_name(atkObj, "selection_changed"); break; case nsIAccessibleEvent::EVENT_TEXT_SELECTION_CHANGED: MAI_LOG_DEBUG(("\n\nReceived: EVENT_TEXT_SELECTION_CHANGED\n")); g_signal_emit_by_name(atkObj, "text_selection_changed"); break; case nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_TEXT_CARET_MOVED\n")); AccCaretMoveEvent* caretMoveEvent = downcast_accEvent(aEvent); NS_ASSERTION(caretMoveEvent, "Event needs event data"); if (!caretMoveEvent) break; PRInt32 caretOffset = caretMoveEvent->GetCaretOffset(); MAI_LOG_DEBUG(("\n\nCaret postion: %d", caretOffset)); g_signal_emit_by_name(atkObj, "text_caret_moved", // Curent caret position caretOffset); } break; case nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED: MAI_LOG_DEBUG(("\n\nReceived: EVENT_TEXT_ATTRIBUTE_CHANGED\n")); g_signal_emit_by_name(atkObj, "text-attributes-changed"); break; case nsIAccessibleEvent::EVENT_TABLE_MODEL_CHANGED: MAI_LOG_DEBUG(("\n\nReceived: EVENT_TABLE_MODEL_CHANGED\n")); g_signal_emit_by_name(atkObj, "model_changed"); break; case nsIAccessibleEvent::EVENT_TABLE_ROW_INSERT: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_TABLE_ROW_INSERT\n")); AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent); NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE); PRInt32 rowIndex = tableEvent->GetIndex(); PRInt32 numRows = tableEvent->GetCount(); g_signal_emit_by_name(atkObj, "row_inserted", // After which the rows are inserted rowIndex, // The number of the inserted numRows); } break; case nsIAccessibleEvent::EVENT_TABLE_ROW_DELETE: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_TABLE_ROW_DELETE\n")); AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent); NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE); PRInt32 rowIndex = tableEvent->GetIndex(); PRInt32 numRows = tableEvent->GetCount(); g_signal_emit_by_name(atkObj, "row_deleted", // After which the rows are deleted rowIndex, // The number of the deleted numRows); } break; case nsIAccessibleEvent::EVENT_TABLE_ROW_REORDER: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_TABLE_ROW_REORDER\n")); g_signal_emit_by_name(atkObj, "row_reordered"); break; } case nsIAccessibleEvent::EVENT_TABLE_COLUMN_INSERT: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_TABLE_COLUMN_INSERT\n")); AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent); NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE); PRInt32 colIndex = tableEvent->GetIndex(); PRInt32 numCols = tableEvent->GetCount(); g_signal_emit_by_name(atkObj, "column_inserted", // After which the columns are inserted colIndex, // The number of the inserted numCols); } break; case nsIAccessibleEvent::EVENT_TABLE_COLUMN_DELETE: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_TABLE_COLUMN_DELETE\n")); AccTableChangeEvent* tableEvent = downcast_accEvent(aEvent); NS_ENSURE_TRUE(tableEvent, NS_ERROR_FAILURE); PRInt32 colIndex = tableEvent->GetIndex(); PRInt32 numCols = tableEvent->GetCount(); g_signal_emit_by_name(atkObj, "column_deleted", // After which the columns are deleted colIndex, // The number of the deleted numCols); } break; case nsIAccessibleEvent::EVENT_TABLE_COLUMN_REORDER: MAI_LOG_DEBUG(("\n\nReceived: EVENT_TABLE_COLUMN_REORDER\n")); g_signal_emit_by_name(atkObj, "column_reordered"); break; case nsIAccessibleEvent::EVENT_SECTION_CHANGED: MAI_LOG_DEBUG(("\n\nReceived: EVENT_SECTION_CHANGED\n")); g_signal_emit_by_name(atkObj, "visible_data_changed"); break; case nsIAccessibleEvent::EVENT_SHOW: return FireAtkShowHideEvent(aEvent, atkObj, PR_TRUE); case nsIAccessibleEvent::EVENT_HIDE: return FireAtkShowHideEvent(aEvent, atkObj, PR_FALSE); /* * Because dealing with menu is very different between nsIAccessible * and ATK, and the menu activity is important, specially transfer the * following two event. * Need more verification by AT test. */ case nsIAccessibleEvent::EVENT_MENU_START: MAI_LOG_DEBUG(("\n\nReceived: EVENT_MENU_START\n")); break; case nsIAccessibleEvent::EVENT_MENU_END: MAI_LOG_DEBUG(("\n\nReceived: EVENT_MENU_END\n")); break; case nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_WINDOW_ACTIVATED\n")); nsRootAccessible *rootAcc = static_cast<nsRootAccessible *>(accessible); rootAcc->mActivated = PR_TRUE; guint id = g_signal_lookup ("activate", MAI_TYPE_ATK_OBJECT); g_signal_emit(atkObj, id, 0); // Always fire a current focus event after activation. rootAcc->FireCurrentFocusEvent(); } break; case nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_WINDOW_DEACTIVATED\n")); nsRootAccessible *rootAcc = static_cast<nsRootAccessible *>(accessible); rootAcc->mActivated = PR_FALSE; guint id = g_signal_lookup ("deactivate", MAI_TYPE_ATK_OBJECT); g_signal_emit(atkObj, id, 0); } break; case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_COMPLETE: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_DOCUMENT_LOAD_COMPLETE\n")); g_signal_emit_by_name (atkObj, "load_complete"); } break; case nsIAccessibleEvent::EVENT_DOCUMENT_RELOAD: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_DOCUMENT_RELOAD\n")); g_signal_emit_by_name (atkObj, "reload"); } break; case nsIAccessibleEvent::EVENT_DOCUMENT_LOAD_STOPPED: { MAI_LOG_DEBUG(("\n\nReceived: EVENT_DOCUMENT_LOAD_STOPPED\n")); g_signal_emit_by_name (atkObj, "load_stopped"); } break; case nsIAccessibleEvent::EVENT_MENUPOPUP_START: MAI_LOG_DEBUG(("\n\nReceived: EVENT_MENUPOPUP_START\n")); atk_focus_tracker_notify(atkObj); // fire extra focus event atk_object_notify_state_change(atkObj, ATK_STATE_VISIBLE, PR_TRUE); atk_object_notify_state_change(atkObj, ATK_STATE_SHOWING, PR_TRUE); break; case nsIAccessibleEvent::EVENT_MENUPOPUP_END: MAI_LOG_DEBUG(("\n\nReceived: EVENT_MENUPOPUP_END\n")); atk_object_notify_state_change(atkObj, ATK_STATE_VISIBLE, PR_FALSE); atk_object_notify_state_change(atkObj, ATK_STATE_SHOWING, PR_FALSE); break; } return NS_OK; }