author | Alexander Surkov <surkov.alexander@gmail.com> |
Thu, 21 Oct 2010 13:16:10 +0900 | |
changeset 56292 | 93e95f4f73af122cd0f745de54445cc1afdcf229 |
parent 56291 | 87f412257c9f52e168ad4108932ce3e713799162 |
child 56293 | 9f5af65749fae34e2de3aff961f48afd314b2392 |
push id | 1 |
push user | root |
push date | Tue, 26 Apr 2011 22:38:44 +0000 |
treeherder | mozilla-beta@bfdb6e623a36 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | marcoz, davidb, bz, blocking |
bugs | 570275 |
milestone | 2.0b8pre |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/accessible/public/nsIAccessibilityService.h +++ b/accessible/public/nsIAccessibilityService.h @@ -135,51 +135,49 @@ public: /** * Adds/remove ATK root accessible for gtk+ native window to/from children * of the application accessible. */ virtual nsAccessible* AddNativeRootAccessible(void* aAtkAccessible) = 0; virtual void RemoveNativeRootAccessible(nsAccessible* aRootAccessible) = 0; /** - * Used to describe sort of changes leading to accessible tree invalidation. + * Notification used to update the accessible tree when new content is + * inserted. */ - enum { - NODE_APPEND = 0x01, - NODE_REMOVE = 0x02, - NODE_SIGNIFICANT_CHANGE = 0x03, - FRAME_SHOW = 0x04, - FRAME_HIDE = 0x05, - FRAME_SIGNIFICANT_CHANGE = 0x06 - }; + virtual void ContentRangeInserted(nsIPresShell* aPresShell, + nsIContent* aContainer, + nsIContent* aStartChild, + nsIContent* aEndChild) = 0; /** - * Invalidate the accessible tree when DOM tree or frame tree is changed. - * - * @param aPresShell [in] the presShell where changes occurred - * @param aContent [in] the affected DOM content - * @param aChangeType [in] the change type (see constants declared above) + * Notification used to update the accessible tree when content is removed. */ - virtual nsresult InvalidateSubtreeFor(nsIPresShell *aPresShell, - nsIContent *aContent, - PRUint32 aChangeType) = 0; + virtual void ContentRemoved(nsIPresShell* aPresShell, nsIContent* aContainer, + nsIContent* aChild) = 0; /** * Notify accessibility that anchor jump has been accomplished to the given * target. Used by layout. */ virtual void NotifyOfAnchorJumpTo(nsIContent *aTarget) = 0; /** * Notify the accessibility service that the given presshell is * being destroyed. */ virtual void PresShellDestroyed(nsIPresShell *aPresShell) = 0; /** + * Recreate an accessible for the given content node in the presshell. + */ + virtual void RecreateAccessible(nsIPresShell* aPresShell, + nsIContent* aContent) = 0; + + /** * Fire accessible event of the given type for the given target. * * @param aEvent [in] accessible event type * @param aTarget [in] target of accessible event */ virtual void FireAccessibleEvent(PRUint32 aEvent, nsAccessible* aTarget) = 0; };
--- a/accessible/src/base/AccEvent.cpp +++ b/accessible/src/base/AccEvent.cpp @@ -59,29 +59,25 @@ //////////////////////////////////////////////////////////////////////////////// // AccEvent //////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////// // AccEvent constructors AccEvent::AccEvent(PRUint32 aEventType, nsAccessible* aAccessible, - PRBool aIsAsync, EIsFromUserInput aIsFromUserInput, - EEventRule aEventRule) : - mEventType(aEventType), mEventRule(aEventRule), mIsAsync(aIsAsync), - mAccessible(aAccessible) + EIsFromUserInput aIsFromUserInput, EEventRule aEventRule) : + mEventType(aEventType), mEventRule(aEventRule), mAccessible(aAccessible) { CaptureIsFromUserInput(aIsFromUserInput); } AccEvent::AccEvent(PRUint32 aEventType, nsINode* aNode, - PRBool aIsAsync, EIsFromUserInput aIsFromUserInput, - EEventRule aEventRule) : - mEventType(aEventType), mEventRule(aEventRule), mIsAsync(aIsAsync), - mNode(aNode) + EIsFromUserInput aIsFromUserInput, EEventRule aEventRule) : + mEventType(aEventType), mEventRule(aEventRule), mNode(aNode) { CaptureIsFromUserInput(aIsFromUserInput); } //////////////////////////////////////////////////////////////////////////////// // AccEvent public methods nsAccessible * @@ -213,58 +209,27 @@ AccEvent::CaptureIsFromUserInput(EIsFrom return; } mIsFromUserInput = esm->IsHandlingUserInputExternal(); } //////////////////////////////////////////////////////////////////////////////// -// AccReorderEvent -//////////////////////////////////////////////////////////////////////////////// - -AccReorderEvent:: - AccReorderEvent(nsAccessible* aAccTarget, PRBool aIsAsynch, - PRBool aIsUnconditional, nsINode* aReasonNode) : - AccEvent(::nsIAccessibleEvent::EVENT_REORDER, aAccTarget, - aIsAsynch, eAutoDetect, AccEvent::eCoalesceFromSameSubtree), - mUnconditionalEvent(aIsUnconditional), mReasonNode(aReasonNode) -{ -} - -PRBool -AccReorderEvent::IsUnconditionalEvent() -{ - return mUnconditionalEvent; -} - -PRBool -AccReorderEvent::HasAccessibleInReasonSubtree() -{ - if (!mReasonNode) - return PR_FALSE; - - nsAccessible *accessible = GetAccService()->GetAccessible(mReasonNode); - return accessible || nsAccUtils::HasAccessibleChildren(mReasonNode); -} - - -//////////////////////////////////////////////////////////////////////////////// // AccStateChangeEvent //////////////////////////////////////////////////////////////////////////////// // Note: we pass in eAllowDupes to the base class because we don't currently // support correct state change coalescence (XXX Bug 569356). Also we need to // decide how to coalesce events created via accessible (instead of node). AccStateChangeEvent:: AccStateChangeEvent(nsAccessible* aAccessible, PRUint32 aState, PRBool aIsExtraState, - PRBool aIsEnabled, PRBool aIsAsynch, - EIsFromUserInput aIsFromUserInput): - AccEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible, aIsAsynch, + PRBool aIsEnabled, EIsFromUserInput aIsFromUserInput): + AccEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible, aIsFromUserInput, eAllowDupes), mState(aState), mIsExtraState(aIsExtraState), mIsEnabled(aIsEnabled) { } AccStateChangeEvent:: AccStateChangeEvent(nsINode* aNode, PRUint32 aState, PRBool aIsExtraState, PRBool aIsEnabled): @@ -310,67 +275,92 @@ AccStateChangeEvent::CreateXPCOMObject() // node. This means we won't try to create an accessible based on the node when // we are ready to fire the event and so we will no longer assert at that point // if the node was removed from the document. Either way, the AT won't work with // a defunct accessible so the behaviour should be equivalent. // XXX revisit this when coalescence is faster (eCoalesceFromSameSubtree) AccTextChangeEvent:: AccTextChangeEvent(nsAccessible* aAccessible, PRInt32 aStart, nsAString& aModifiedText, PRBool aIsInserted, - PRBool aIsAsynch, EIsFromUserInput aIsFromUserInput) + EIsFromUserInput aIsFromUserInput) : AccEvent(aIsInserted ? static_cast<PRUint32>(nsIAccessibleEvent::EVENT_TEXT_INSERTED) : static_cast<PRUint32>(nsIAccessibleEvent::EVENT_TEXT_REMOVED), - aAccessible, aIsAsynch, aIsFromUserInput, eAllowDupes) + aAccessible, aIsFromUserInput, eAllowDupes) , mStart(aStart) , mIsInserted(aIsInserted) , mModifiedText(aModifiedText) { } already_AddRefed<nsAccEvent> AccTextChangeEvent::CreateXPCOMObject() { nsAccEvent* event = new nsAccTextChangeEvent(this); NS_IF_ADDREF(event); return event; } //////////////////////////////////////////////////////////////////////////////// +// AccMutationEvent +//////////////////////////////////////////////////////////////////////////////// + +AccMutationEvent:: + AccMutationEvent(PRUint32 aEventType, nsAccessible* aTarget, + nsINode* aTargetNode, EIsFromUserInput aIsFromUserInput) : + AccEvent(aEventType, aTarget, aIsFromUserInput, eCoalesceFromSameSubtree) +{ + mNode = aTargetNode; +} + + +//////////////////////////////////////////////////////////////////////////////// // AccHideEvent //////////////////////////////////////////////////////////////////////////////// AccHideEvent:: AccHideEvent(nsAccessible* aTarget, nsINode* aTargetNode, - PRBool aIsAsynch, EIsFromUserInput aIsFromUserInput) : - AccEvent(nsIAccessibleEvent::EVENT_HIDE, aTarget, aIsAsynch, - aIsFromUserInput, eCoalesceFromSameSubtree) + EIsFromUserInput aIsFromUserInput) : + AccMutationEvent(::nsIAccessibleEvent::EVENT_HIDE, aTarget, aTargetNode, + aIsFromUserInput) { - mNode = aTargetNode; mParent = mAccessible->GetCachedParent(); mNextSibling = mAccessible->GetCachedNextSibling(); mPrevSibling = mAccessible->GetCachedPrevSibling(); } //////////////////////////////////////////////////////////////////////////////// +// AccShowEvent +//////////////////////////////////////////////////////////////////////////////// + +AccShowEvent:: + AccShowEvent(nsAccessible* aTarget, nsINode* aTargetNode, + EIsFromUserInput aIsFromUserInput) : + AccMutationEvent(::nsIAccessibleEvent::EVENT_SHOW, aTarget, aTargetNode, + aIsFromUserInput) +{ +} + + +//////////////////////////////////////////////////////////////////////////////// // AccCaretMoveEvent //////////////////////////////////////////////////////////////////////////////// AccCaretMoveEvent:: AccCaretMoveEvent(nsAccessible* aAccessible, PRInt32 aCaretOffset) : - AccEvent(::nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED, aAccessible, PR_TRUE), // Currently always asynch + AccEvent(::nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED, aAccessible), mCaretOffset(aCaretOffset) { } AccCaretMoveEvent:: AccCaretMoveEvent(nsINode* aNode) : - AccEvent(::nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED, aNode, PR_TRUE), // Currently always asynch + AccEvent(::nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED, aNode), mCaretOffset(-1) { } already_AddRefed<nsAccEvent> AccCaretMoveEvent::CreateXPCOMObject() { nsAccEvent* event = new nsAccCaretMoveEvent(this); @@ -380,19 +370,18 @@ AccCaretMoveEvent::CreateXPCOMObject() //////////////////////////////////////////////////////////////////////////////// // AccTableChangeEvent //////////////////////////////////////////////////////////////////////////////// AccTableChangeEvent:: AccTableChangeEvent(nsAccessible* aAccessible, PRUint32 aEventType, - PRInt32 aRowOrColIndex, PRInt32 aNumRowsOrCols, - PRBool aIsAsynch) : - AccEvent(aEventType, aAccessible, aIsAsynch), + PRInt32 aRowOrColIndex, PRInt32 aNumRowsOrCols) : + AccEvent(aEventType, aAccessible), mRowOrColIndex(aRowOrColIndex), mNumRowsOrCols(aNumRowsOrCols) { } already_AddRefed<nsAccEvent> AccTableChangeEvent::CreateXPCOMObject() { nsAccEvent* event = new nsAccTableChangeEvent(this);
--- a/accessible/src/base/AccEvent.h +++ b/accessible/src/base/AccEvent.h @@ -36,16 +36,18 @@ * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #ifndef _AccEvent_H_ #define _AccEvent_H_ +#include "nsIAccessibleEvent.h" + #include "nsAccessible.h" class nsAccEvent; class nsDocAccessible; // Constants used to point whether the event is from user input. enum EIsFromUserInput { @@ -85,49 +87,48 @@ public: eRemoveDupes, // eDoNotEmit : This event is confirmed as a duplicate, do not emit it. eDoNotEmit }; // Initialize with an nsIAccessible AccEvent(PRUint32 aEventType, nsAccessible* aAccessible, - PRBool aIsAsynch = PR_FALSE, EIsFromUserInput aIsFromUserInput = eAutoDetect, EEventRule aEventRule = eRemoveDupes); // Initialize with an nsIDOMNode - AccEvent(PRUint32 aEventType, nsINode* aNode, PRBool aIsAsynch = PR_FALSE, + AccEvent(PRUint32 aEventType, nsINode* aNode, EIsFromUserInput aIsFromUserInput = eAutoDetect, EEventRule aEventRule = eRemoveDupes); virtual ~AccEvent() {} // AccEvent PRUint32 GetEventType() const { return mEventType; } EEventRule GetEventRule() const { return mEventRule; } - PRBool IsAsync() const { return mIsAsync; } PRBool IsFromUserInput() const { return mIsFromUserInput; } nsAccessible *GetAccessible(); nsDocAccessible* GetDocAccessible(); nsINode* GetNode(); /** * Create and return an XPCOM object for accessible event object. */ virtual already_AddRefed<nsAccEvent> CreateXPCOMObject(); /** * Down casting. */ enum EventGroup { eGenericEvent, - eReorderEvent, eStateChangeEvent, eTextChangeEvent, + eMutationEvent, eHideEvent, + eShowEvent, eCaretMoveEvent, eTableChangeEvent }; static const EventGroup kEventGroup = eGenericEvent; virtual unsigned int GetEventGroups() const { return 1U << eGenericEvent; @@ -149,66 +150,32 @@ protected: * Determine whether the event is from user input by event state manager if * it's not pointed explicetly. */ void CaptureIsFromUserInput(EIsFromUserInput aIsFromUserInput); PRBool mIsFromUserInput; PRUint32 mEventType; EEventRule mEventRule; - PRPackedBool mIsAsync; nsRefPtr<nsAccessible> mAccessible; nsCOMPtr<nsINode> mNode; friend class nsAccEventQueue; }; /** - * Accessible reorder event. - */ -class AccReorderEvent : public AccEvent -{ -public: - AccReorderEvent(nsAccessible* aAccTarget, PRBool aIsAsynch, - PRBool aIsUnconditional, nsINode* aReasonNode); - - // AccEvent - static const EventGroup kEventGroup = eReorderEvent; - virtual unsigned int GetEventGroups() const - { - return AccEvent::GetEventGroups() | (1U << eReorderEvent); - } - - // AccReorderEvent - /** - * Return true if event is unconditional, i.e. must be fired. - */ - PRBool IsUnconditionalEvent(); - - /** - * Return true if changed DOM node has accessible in its tree. - */ - PRBool HasAccessibleInReasonSubtree(); - -private: - PRBool mUnconditionalEvent; - nsCOMPtr<nsINode> mReasonNode; -}; - - -/** * Accessible state change event. */ class AccStateChangeEvent: public AccEvent { public: AccStateChangeEvent(nsAccessible* aAccessible, PRUint32 aState, PRBool aIsExtraState, - PRBool aIsEnabled, PRBool aIsAsynch = PR_FALSE, + PRBool aIsEnabled, EIsFromUserInput aIsFromUserInput = eAutoDetect); AccStateChangeEvent(nsINode* aNode, PRUint32 aState, PRBool aIsExtraState, PRBool aIsEnabled); AccStateChangeEvent(nsINode* aNode, PRUint32 aState, PRBool aIsExtraState); // AccEvent @@ -234,18 +201,17 @@ private: /** * Accessible text change event. */ class AccTextChangeEvent: public AccEvent { public: AccTextChangeEvent(nsAccessible* aAccessible, PRInt32 aStart, - nsAString& aModifiedText, - PRBool aIsInserted, PRBool aIsAsynch = PR_FALSE, + nsAString& aModifiedText, PRBool aIsInserted, EIsFromUserInput aIsFromUserInput = eAutoDetect); // AccEvent virtual already_AddRefed<nsAccEvent> CreateXPCOMObject(); static const EventGroup kEventGroup = eTextChangeEvent; virtual unsigned int GetEventGroups() const { @@ -264,42 +230,86 @@ private: PRBool mIsInserted; nsString mModifiedText; friend class nsAccEventQueue; }; /** - * Accessible hide events. + * Base class for show and hide accessible events. */ -class AccHideEvent : public AccEvent +class AccMutationEvent: public AccEvent +{ +public: + AccMutationEvent(PRUint32 aEventType, nsAccessible* aTarget, + nsINode* aTargetNode, EIsFromUserInput aIsFromUserInput); + + // Event + static const EventGroup kEventGroup = eMutationEvent; + virtual unsigned int GetEventGroups() const + { + return AccEvent::GetEventGroups() | (1U << eMutationEvent); + } + + // MutationEvent + bool IsShow() const { return mEventType == nsIAccessibleEvent::EVENT_SHOW; } + bool IsHide() const { return mEventType == nsIAccessibleEvent::EVENT_HIDE; } + +protected: + nsRefPtr<AccTextChangeEvent> mTextChangeEvent; + + friend class nsAccEventQueue; +}; + + +/** + * Accessible hide event. + */ +class AccHideEvent: public AccMutationEvent { public: AccHideEvent(nsAccessible* aTarget, nsINode* aTargetNode, - PRBool aIsAsynch, EIsFromUserInput aIsFromUserInput); + EIsFromUserInput aIsFromUserInput); // Event static const EventGroup kEventGroup = eHideEvent; virtual unsigned int GetEventGroups() const { - return AccEvent::GetEventGroups() | (1U << eHideEvent); + return AccMutationEvent::GetEventGroups() | (1U << eHideEvent); } protected: nsRefPtr<nsAccessible> mParent; nsRefPtr<nsAccessible> mNextSibling; nsRefPtr<nsAccessible> mPrevSibling; - nsRefPtr<AccTextChangeEvent> mTextChangeEvent; friend class nsAccEventQueue; }; /** + * Accessible show event. + */ +class AccShowEvent: public AccMutationEvent +{ +public: + AccShowEvent(nsAccessible* aTarget, nsINode* aTargetNode, + EIsFromUserInput aIsFromUserInput); + + // Event + static const EventGroup kEventGroup = eShowEvent; + virtual unsigned int GetEventGroups() const + { + return AccMutationEvent::GetEventGroups() | (1U << eShowEvent); + } +}; + + +/** * Accessible caret move event. */ class AccCaretMoveEvent: public AccEvent { public: AccCaretMoveEvent(nsAccessible* aAccessible, PRInt32 aCaretOffset); AccCaretMoveEvent(nsINode* aNode); @@ -322,18 +332,17 @@ private: /** * Accessible table change event. */ class AccTableChangeEvent : public AccEvent { public: AccTableChangeEvent(nsAccessible* aAccessible, PRUint32 aEventType, - PRInt32 aRowOrColIndex, PRInt32 aNumRowsOrCols, - PRBool aIsAsynch); + PRInt32 aRowOrColIndex, PRInt32 aNumRowsOrCols); // AccEvent virtual already_AddRefed<nsAccEvent> CreateXPCOMObject(); static const EventGroup kEventGroup = eTableChangeEvent; virtual unsigned int GetEventGroups() const { return AccEvent::GetEventGroups() | (1U << eTableChangeEvent);
--- a/accessible/src/base/nsAccDocManager.cpp +++ b/accessible/src/base/nsAccDocManager.cpp @@ -67,29 +67,28 @@ nsDocAccessible* nsAccDocManager::GetDocAccessible(nsIDocument *aDocument) { if (!aDocument) return nsnull; // Ensure CacheChildren is called before we query cache. nsAccessNode::GetApplicationAccessible()->EnsureChildren(); - nsDocAccessible *docAcc = - mDocAccessibleCache.GetWeak(static_cast<void*>(aDocument)); + nsDocAccessible* docAcc = mDocAccessibleCache.GetWeak(aDocument); if (docAcc) return docAcc; return CreateDocOrRootAccessible(aDocument); } nsAccessible* -nsAccDocManager::FindAccessibleInCache(void *aUniqueID) const +nsAccDocManager::FindAccessibleInCache(nsINode* aNode) const { nsSearchAccessibleInCacheArg arg; - arg.mUniqueID = aUniqueID; + arg.mNode = aNode; mDocAccessibleCache.EnumerateRead(SearchAccessibleInDocCache, static_cast<void*>(&arg)); return arg.mAccessible; } void @@ -131,27 +130,26 @@ nsAccDocManager::Shutdown() progress->RemoveProgressListener(static_cast<nsIWebProgressListener*>(this)); ClearDocCache(); } void nsAccDocManager::ShutdownDocAccessible(nsIDocument *aDocument) { - nsDocAccessible* docAccessible = - mDocAccessibleCache.GetWeak(static_cast<void*>(aDocument)); + nsDocAccessible* docAccessible = mDocAccessibleCache.GetWeak(aDocument); if (!docAccessible) return; // We're allowed to not remove listeners when accessible document is shutdown // since we don't keep strong reference on chrome event target and listeners // are removed automatically when chrome event target goes away. docAccessible->Shutdown(); - mDocAccessibleCache.Remove(static_cast<void*>(aDocument)); + mDocAccessibleCache.Remove(aDocument); } //////////////////////////////////////////////////////////////////////////////// // nsISupports NS_IMPL_THREADSAFE_ISUPPORTS3(nsAccDocManager, nsIWebProgressListener, nsIDOMEventListener, @@ -210,18 +208,17 @@ nsAccDocManager::OnStateChange(nsIWebPro // Document loading was started. NS_LOG_ACCDOCLOAD("start document loading", aWebProgress, aRequest, aStateFlags) if (!IsEventTargetDocument(document)) return NS_OK; - nsDocAccessible *docAcc = - mDocAccessibleCache.GetWeak(static_cast<void*>(document)); + nsDocAccessible* docAcc = mDocAccessibleCache.GetWeak(document); if (!docAcc) return NS_OK; nsCOMPtr<nsIWebNavigation> webNav(do_GetInterface(DOMWindow)); nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(webNav)); NS_ENSURE_STATE(docShell); // Fire reload and state busy events on existing document accessible while @@ -341,45 +338,35 @@ nsAccDocManager::HandleEvent(nsIDOMEvent void nsAccDocManager::HandleDOMDocumentLoad(nsIDocument *aDocument, PRUint32 aLoadEventType, PRBool aMarkAsLoaded) { // Document accessible can be created before we were notified the DOM document // was loaded completely. However if it's not created yet then create it. - nsDocAccessible *docAcc = - mDocAccessibleCache.GetWeak(static_cast<void*>(aDocument)); - + nsDocAccessible* docAcc = mDocAccessibleCache.GetWeak(aDocument); if (!docAcc) { docAcc = CreateDocOrRootAccessible(aDocument); NS_ASSERTION(docAcc, "Can't create document accessible!"); if (!docAcc) return; } if (aMarkAsLoaded) docAcc->MarkAsLoaded(); // Do not fire document complete/stop events for root chrome document // accessibles and for frame/iframe documents because // a) screen readers start working on focus event in the case of root chrome // documents // b) document load event on sub documents causes screen readers to act is if // entire page is reloaded. - if (!IsEventTargetDocument(aDocument)) { - // XXX: AT doesn't update their virtual buffer once frame is loaded and it - // has dynamic content added after frame load. There's something wrong how - // we handle this changes. - if (!nsCoreUtils::IsRootDocument(aDocument)) { - docAcc->InvalidateCacheSubtree(nsnull, - nsIAccessibilityService::NODE_SIGNIFICANT_CHANGE); - } + if (!IsEventTargetDocument(aDocument)) return; - } // Fire complete/load stopped if the load event type is given. if (aLoadEventType) { nsRefPtr<AccEvent> loadEvent = new AccEvent(aLoadEventType, aDocument); docAcc->FireDelayedAccessibleEvent(loadEvent); } // Fire busy state change event. @@ -488,27 +475,27 @@ nsAccDocManager::CreateDocOrRootAccessib nsDocAccessible *docAcc = isRootDoc ? new nsRootAccessibleWrap(aDocument, rootElm, weakShell) : new nsDocAccessibleWrap(aDocument, rootElm, weakShell); if (!docAcc) return nsnull; // Cache and addref document accessible. - if (!mDocAccessibleCache.Put(static_cast<void*>(aDocument), docAcc)) { + if (!mDocAccessibleCache.Put(aDocument, docAcc)) { delete docAcc; return nsnull; } // XXX: ideally we should initialize an accessible and then put it into tree, // we can't since document accessible fires reorder event on its container // while initialized. if (!outerDocAcc->AppendChild(docAcc) || !GetAccService()->InitAccessible(docAcc, nsAccUtils::GetRoleMapEntry(aDocument))) { - mDocAccessibleCache.Remove(static_cast<void*>(aDocument)); + mDocAccessibleCache.Remove(aDocument); return nsnull; } NS_LOG_ACCDOCCREATE("document creation finished", aDocument) AddListeners(aDocument, isRootDoc); return docAcc; } @@ -541,39 +528,39 @@ nsAccDocManager::ShutdownDocAccessiblesI ShutdownDocAccessible(aDocument); } //////////////////////////////////////////////////////////////////////////////// // nsAccDocManager static PLDHashOperator -nsAccDocManager::ClearDocCacheEntry(const void* aKey, +nsAccDocManager::ClearDocCacheEntry(const nsIDocument* aKey, nsRefPtr<nsDocAccessible>& aDocAccessible, void* aUserArg) { NS_ASSERTION(aDocAccessible, "Calling ClearDocCacheEntry with a NULL pointer!"); if (aDocAccessible) aDocAccessible->Shutdown(); return PL_DHASH_REMOVE; } PLDHashOperator -nsAccDocManager::SearchAccessibleInDocCache(const void* aKey, +nsAccDocManager::SearchAccessibleInDocCache(const nsIDocument* aKey, nsDocAccessible* aDocAccessible, void* aUserArg) { NS_ASSERTION(aDocAccessible, "No doc accessible for the object in doc accessible cache!"); if (aDocAccessible) { nsSearchAccessibleInCacheArg* arg = static_cast<nsSearchAccessibleInCacheArg*>(aUserArg); - arg->mAccessible = aDocAccessible->GetCachedAccessible(arg->mUniqueID); + arg->mAccessible = aDocAccessible->GetCachedAccessible(arg->mNode); if (arg->mAccessible) return PL_DHASH_STOP; } return PL_DHASH_NEXT; }
--- a/accessible/src/base/nsAccDocManager.h +++ b/accessible/src/base/nsAccDocManager.h @@ -68,31 +68,31 @@ public: * Return document accessible for the given DOM node. */ nsDocAccessible *GetDocAccessible(nsIDocument *aDocument); /** * Search through all document accessibles for an accessible with the given * unique id. */ - nsAccessible *FindAccessibleInCache(void *aUniqueID) const; + nsAccessible* FindAccessibleInCache(nsINode* aNode) const; /** * Shutdown document accessibles in the tree starting from the given one. * * @param aDocument [in] the DOM document of start document accessible */ void ShutdownDocAccessiblesInTree(nsIDocument *aDocument); /** * Return document accessible from the cache. Convenient method for testing. */ inline nsDocAccessible* GetDocAccessibleFromCache(nsIDocument* aDocument) const { - return mDocAccessibleCache.GetWeak(static_cast<void*>(aDocument)); + return mDocAccessibleCache.GetWeak(aDocument); } protected: nsAccDocManager() { }; /** * Initialize the manager. */ @@ -157,43 +157,43 @@ private: nsDocAccessible *CreateDocOrRootAccessible(nsIDocument *aDocument); /** * Shutdown document accessibles in the tree starting from given tree item. */ void ShutdownDocAccessiblesInTree(nsIDocShellTreeItem *aTreeItem, nsIDocument *aDocument); - typedef nsRefPtrHashtable<nsVoidPtrHashKey, nsDocAccessible> + typedef nsRefPtrHashtable<nsPtrHashKey<const nsIDocument>, nsDocAccessible> nsDocAccessibleHashtable; /** * Shutdown and remove the document accessible from cache. */ static PLDHashOperator - ClearDocCacheEntry(const void* aKey, + ClearDocCacheEntry(const nsIDocument* aKey, nsRefPtr<nsDocAccessible>& aDocAccessible, void* aUserArg); /** * Clear the cache and shutdown the document accessibles. */ void ClearDocCache() { mDocAccessibleCache.Enumerate(ClearDocCacheEntry, static_cast<void*>(this)); } struct nsSearchAccessibleInCacheArg { nsAccessible *mAccessible; - void *mUniqueID; + nsINode* mNode; }; static PLDHashOperator - SearchAccessibleInDocCache(const void* aKey, + SearchAccessibleInDocCache(const nsIDocument* aKey, nsDocAccessible* aDocAccessible, void* aUserArg); nsDocAccessibleHashtable mDocAccessibleCache; }; /** * nsAccDocManager debugging macros.
--- a/accessible/src/base/nsAccessNode.cpp +++ b/accessible/src/base/nsAccessNode.cpp @@ -147,19 +147,22 @@ nsAccessNode::Init() void nsAccessNode::Shutdown() { mContent = nsnull; mWeakShell = nsnull; } // nsIAccessNode -NS_IMETHODIMP nsAccessNode::GetUniqueID(void **aUniqueID) +NS_IMETHODIMP +nsAccessNode::GetUniqueID(void **aUniqueID) { - *aUniqueID = static_cast<void*>(GetNode()); + NS_ENSURE_ARG_POINTER(aUniqueID); + + *aUniqueID = UniqueID(); return NS_OK; } // nsIAccessNode NS_IMETHODIMP nsAccessNode::GetOwnerWindow(void **aWindow) { NS_ENSURE_ARG_POINTER(aWindow); @@ -305,16 +308,22 @@ already_AddRefed<nsRootAccessible> nsAcc } nsIFrame* nsAccessNode::GetFrame() { return mContent ? mContent->GetPrimaryFrame() : nsnull; } +bool +nsAccessNode::IsPrimaryForNode() const +{ + return true; +} + //////////////////////////////////////////////////////////////////////////////// // nsIAccessNode NS_IMETHODIMP nsAccessNode::GetDOMNode(nsIDOMNode **aDOMNode) { NS_ENSURE_ARG_POINTER(aDOMNode); *aDOMNode = nsnull;
--- a/accessible/src/base/nsAccessNode.h +++ b/accessible/src/base/nsAccessNode.h @@ -176,16 +176,30 @@ public: */ already_AddRefed<nsIPresShell> GetPresShell(); /** * Return presentation shell for the accessible. */ nsIWeakReference* GetWeakShell() const { return mWeakShell; } + /** + * Return the unique identifier of the accessible. + */ + void* UniqueID() { return static_cast<void*>(this); } + + /** + * Return true if the accessible is primary accessible for the given DOM node. + * + * Accessible hierarchy may be complex for single DOM node, in this case + * these accessibles share the same DOM node. The primary accessible "owns" + * that DOM node in terms it gets stored in the accessible to node map. + */ + virtual bool IsPrimaryForNode() const; + protected: nsPresContext* GetPresContext(); void LastRelease(); nsCOMPtr<nsIContent> mContent; nsCOMPtr<nsIWeakReference> mWeakShell;
--- a/accessible/src/base/nsAccessibilityService.cpp +++ b/accessible/src/base/nsAccessibilityService.cpp @@ -69,16 +69,17 @@ #include "nsIObserverService.h" #include "nsIPluginInstance.h" #include "nsISupportsUtils.h" #include "nsObjectFrame.h" #include "nsOuterDocAccessible.h" #include "nsRootAccessibleWrap.h" #include "nsTextFragment.h" #include "mozilla/Services.h" +#include "nsIEventStateManager.h" #ifdef MOZ_XUL #include "nsXULAlertAccessible.h" #include "nsXULColorPickerAccessible.h" #include "nsXULComboboxAccessible.h" #include "nsXULFormControlAccessible.h" #include "nsXULListboxAccessibleWrap.h" #include "nsXULMenuAccessibleWrap.h" @@ -466,16 +467,72 @@ nsAccessibilityService::CreateHTMLCaptio { nsCOMPtr<nsIWeakReference> weakShell(do_GetWeakReference(aPresShell)); nsAccessible* accessible = new nsHTMLCaptionAccessible(aContent, weakShell); NS_IF_ADDREF(accessible); return accessible; } void +nsAccessibilityService::ContentRangeInserted(nsIPresShell* aPresShell, + nsIContent* aContainer, + nsIContent* aStartChild, + nsIContent* aEndChild) +{ +#ifdef DEBUG_A11Y + nsAutoString tag; + aStartChild->Tag()->ToString(tag); + nsIAtom* id = aStartChild->GetID(); + nsCAutoString strid; + if (id) + id->ToUTF8String(strid); + nsAutoString ctag; + aContainer->Tag()->ToString(ctag); + nsIAtom* cid = aContainer->GetID(); + nsCAutoString strcid; + if (cid) + cid->ToUTF8String(strcid); + printf("\ncontent inserted: %s@id='%s', container: %s@id='%s', end node: %p\n\n", + NS_ConvertUTF16toUTF8(tag).get(), strid.get(), + NS_ConvertUTF16toUTF8(ctag).get(), strcid.get(), aEndChild); +#endif + + // XXX: bug 606082. aContainer is null when root element is inserted into + // document, we need to handle this and update the tree, also we need to + // update a content node of the document accessible. + if (aContainer) { + nsDocAccessible* docAccessible = GetDocAccessible(aPresShell->GetDocument()); + if (docAccessible) + docAccessible->UpdateTree(aContainer, aStartChild, aEndChild, PR_TRUE); + } +} + +void +nsAccessibilityService::ContentRemoved(nsIPresShell* aPresShell, + nsIContent* aContainer, + nsIContent* aChild) +{ +#ifdef DEBUG_A11Y + nsAutoString id; + aChild->Tag()->ToString(id); + printf("\ncontent removed: %s\n", NS_ConvertUTF16toUTF8(id).get()); +#endif + + // XXX: bug 606082. aContainer is null when root element is inserted into + // document, we need to handle this and update the tree, perhaps destroy + // the document accessible. + if (aContainer) { + nsDocAccessible* docAccessible = GetDocAccessible(aPresShell->GetDocument()); + if (docAccessible) + docAccessible->UpdateTree(aContainer, aChild, aChild->GetNextSibling(), + PR_FALSE); + } +} + +void nsAccessibilityService::PresShellDestroyed(nsIPresShell *aPresShell) { // Presshell destruction will automatically destroy shells for descendant // documents, so no need to worry about those. Just shut down the accessible // for this one document. That keeps us from having bad behavior in case of // deep bushy subtrees. // When document subtree containing iframe is hidden then we don't get // pagehide event for the iframe's underlying document and its presshell is @@ -484,24 +541,32 @@ nsAccessibilityService::PresShellDestroy nsIDocument* doc = aPresShell->GetDocument(); if (!doc) return; NS_LOG_ACCDOCDESTROY("presshell destroyed", doc) ShutdownDocAccessible(doc); } +void +nsAccessibilityService::RecreateAccessible(nsIPresShell* aPresShell, + nsIContent* aContent) +{ + nsDocAccessible* document = GetDocAccessible(aPresShell->GetDocument()); + if (document) + document->RecreateAccessible(aContent); +} + // nsAccessibilityService protected nsAccessible * nsAccessibilityService::GetCachedAccessible(nsINode *aNode, nsIWeakReference *aWeakShell) { nsDocAccessible *docAccessible = GetDocAccessible(aNode->GetOwnerDoc()); - return docAccessible ? - docAccessible->GetCachedAccessible(static_cast<void*>(aNode)) : nsnull; + return docAccessible ? docAccessible->GetCachedAccessible(aNode) : nsnull; } //////////////////////////////////////////////////////////////////////////////// // nsIAccessibleRetrieval NS_IMETHODIMP nsAccessibilityService::GetApplicationAccessible(nsIAccessible **aAccessibleApplication) { @@ -689,17 +754,17 @@ nsAccessibilityService::GetAccessibleFro // Search for an accessible in each of our per document accessible object // caches. If we don't find it, and the given node is itself a document, check // our cache of document accessibles (document cache). Note usually shutdown // document accessibles are not stored in the document cache, however an // "unofficially" shutdown document (i.e. not from nsAccDocManager) can still // exist in the document cache. nsCOMPtr<nsINode> node(do_QueryInterface(aNode)); - nsAccessible* accessible = FindAccessibleInCache(static_cast<void*>(node)); + nsAccessible* accessible = FindAccessibleInCache(node); if (!accessible) { nsCOMPtr<nsIDocument> document(do_QueryInterface(node)); if (document) accessible = GetDocAccessibleFromCache(document); } NS_IF_ADDREF(*aAccessible = accessible); return NS_OK; @@ -727,35 +792,35 @@ nsAccessibilityService::GetAccessible(ns nsCOMPtr<nsIWeakReference> weakShell(nsCoreUtils::GetWeakShellFor(aNode)); if (weakShell) return GetAccessibleByRule(aNode, weakShell, eGetAccForNode); } return nsnull; } nsAccessible* -nsAccessibilityService::GetCachedContainerAccessible(nsINode* aNode) +nsAccessibilityService::GetCachedAccessibleOrContainer(nsINode* aNode) { if (!aNode) return nsnull; nsIDocument *document = aNode->GetCurrentDoc(); if (!document) return nsnull; nsIPresShell *presShell = document->GetShell(); if (!presShell) return nsnull; nsINode *currNode = aNode; nsCOMPtr<nsIWeakReference> weakShell(do_GetWeakReference(presShell)); nsAccessible *accessible = nsnull; - while ((currNode = currNode->GetNodeParent()) && - !(accessible = GetCachedAccessible(currNode, weakShell))); + while (!(accessible = GetCachedAccessible(currNode, weakShell)) && + (currNode = currNode->GetNodeParent())); return accessible; } PRBool nsAccessibilityService::InitAccessible(nsAccessible *aAccessible, nsRoleMapEntry *aRoleMapEntry) { @@ -852,18 +917,17 @@ nsAccessibilityService::GetOrCreateAcces // Frames can be deallocated when we flush layout, or when we call into code // that can flush layout, either directly, or via DOM manipulation, or some // CSS styles like :hover. We use the weak frame checks to avoid calling // methods on a dead frame pointer. nsWeakFrame weakFrame = content->GetPrimaryFrame(); // Check frame to see if it is hidden. - if (!weakFrame.GetFrame() || - !weakFrame.GetFrame()->GetStyleVisibility()->IsVisible()) { + if (!weakFrame.GetFrame()) { if (aIsHidden) *aIsHidden = PR_TRUE; return nsnull; } if (weakFrame.GetFrame()->GetContent() != content) { // Not the main content for this frame. This happens because <area> @@ -1190,33 +1254,34 @@ nsAccessibilityService::HasUniversalAria nsAccessible* nsAccessibilityService::GetAccessibleByRule(nsINode* aNode, nsIWeakReference* aWeakShell, EWhatAccToGet aWhatToGet) { if (!aNode || !aWeakShell) return nsnull; - nsAccessible* cachedAcc = GetCachedAccessible(aNode, aWeakShell); - if (cachedAcc) { - if (aWhatToGet & eGetAccForNode) + if (aWhatToGet & eGetAccForNode) { + nsAccessible* cachedAcc = GetCachedAccessible(aNode, aWeakShell); + if (cachedAcc && cachedAcc->IsBoundToParent()) return cachedAcc; - - // XXX: while nsAccessible::GetParent() tries to repair broken tree and - // may not return cached parent then we use GetAccessibleOrContainer(). - return GetAccessibleByRule(aNode->GetNodeParent(), aWeakShell, - eGetAccForNodeOrContainer); } - // Go up looking for the nearest accessible container stored in cache. + // Go up looking for the nearest accessible container having cached children. nsTArray<nsINode*> nodes; + nsINode* node = aNode; - while ((node = node->GetNodeParent()) && - !(cachedAcc = GetCachedAccessible(node, aWeakShell))) + nsAccessible* cachedAcc = nsnull; + while ((node = node->GetNodeParent())) { + cachedAcc = GetCachedAccessible(node, aWeakShell); + if (cachedAcc && cachedAcc->IsBoundToParent()) + break; + nodes.AppendElement(node); + } // Node is not in accessible document. if (!cachedAcc) return nsnull; // If children of the cached accessible weren't initialized then go down to // the given node and create accessible tree. nsAccessible* containerAcc = cachedAcc; @@ -1708,39 +1773,16 @@ nsAccessibilityService::RemoveNativeRoot nsApplicationAccessible* applicationAcc = nsAccessNode::GetApplicationAccessible(); if (applicationAcc) applicationAcc->RemoveChild(aAccessible); #endif } -// Called from layout when the frame tree owned by a node changes significantly -nsresult -nsAccessibilityService::InvalidateSubtreeFor(nsIPresShell *aShell, - nsIContent *aChangeContent, - PRUint32 aChangeType) -{ - NS_ASSERTION(aChangeType == nsIAccessibilityService::FRAME_SIGNIFICANT_CHANGE || - aChangeType == nsIAccessibilityService::FRAME_SHOW || - aChangeType == nsIAccessibilityService::FRAME_HIDE || - aChangeType == nsIAccessibilityService::NODE_SIGNIFICANT_CHANGE || - aChangeType == nsIAccessibilityService::NODE_APPEND || - aChangeType == nsIAccessibilityService::NODE_REMOVE, - "Incorrect aEvent passed in"); - - NS_ENSURE_ARG_POINTER(aShell); - - nsDocAccessible *docAccessible = GetDocAccessible(aShell->GetDocument()); - if (docAccessible) - docAccessible->InvalidateCacheSubtree(aChangeContent, aChangeType); - - return NS_OK; -} - //////////////////////////////////////////////////////////////////////////////// // NS_GetAccessibilityService //////////////////////////////////////////////////////////////////////////////// /** * Return accessibility service; creating one if necessary. */ nsresult
--- a/accessible/src/base/nsAccessibilityService.h +++ b/accessible/src/base/nsAccessibilityService.h @@ -105,24 +105,31 @@ public: virtual already_AddRefed<nsAccessible> CreateHyperTextAccessible(nsIContent* aContent, nsIPresShell* aPresShell); virtual already_AddRefed<nsAccessible> CreateOuterDocAccessible(nsIContent* aContent, nsIPresShell* aPresShell); virtual nsAccessible* AddNativeRootAccessible(void* aAtkAccessible); virtual void RemoveNativeRootAccessible(nsAccessible* aRootAccessible); - virtual nsresult InvalidateSubtreeFor(nsIPresShell *aPresShell, - nsIContent *aContent, - PRUint32 aChangeType); + virtual void ContentRangeInserted(nsIPresShell* aPresShell, + nsIContent* aContainer, + nsIContent* aStartChild, + nsIContent* aEndChild); + + virtual void ContentRemoved(nsIPresShell* aPresShell, nsIContent* aContainer, + nsIContent* aChild); virtual void NotifyOfAnchorJumpTo(nsIContent *aTarget); virtual void PresShellDestroyed(nsIPresShell* aPresShell); + virtual void RecreateAccessible(nsIPresShell* aPresShell, + nsIContent* aContent); + virtual void FireAccessibleEvent(PRUint32 aEvent, nsAccessible* aTarget); // nsAccessibiltiyService /** * Return true if accessibility service has been shutdown. */ static PRBool IsShutdown() { return gIsShutdown; } @@ -174,21 +181,31 @@ public: */ inline nsAccessible* GetContainerAccessible(nsINode* aNode, nsIWeakReference* aWeakShell) { return GetAccessibleByRule(aNode, aWeakShell, eGetAccForContainer); } /** + * Return cached accessible for the given DOM node or cached container + * accessible if there's no cached accessible for the given node. + */ + nsAccessible* GetCachedAccessibleOrContainer(nsINode* aNode); + + /** * Return the first cached accessible parent of a DOM node. * * @param aDOMNode [in] the DOM node to get an accessible for */ - nsAccessible* GetCachedContainerAccessible(nsINode *aNode); + inline nsAccessible* GetCachedContainerAccessible(nsINode *aNode) + { + return aNode ? + GetCachedAccessibleOrContainer(aNode->GetNodeParent()) : nsnull; + } /** * Initialize an accessible and cache it. The method should be called for * every created accessible. * * @param aAccessible [in] accessible to initialize. * @param aRoleMapEntry [in] the role map entry role the ARIA role or nsnull * if none
--- a/accessible/src/base/nsAccessible.cpp +++ b/accessible/src/base/nsAccessible.cpp @@ -2624,38 +2624,31 @@ nsAccessible::AppendTextTo(nsAString& aT // nsAccessNode public methods PRBool nsAccessible::Init() { if (!nsAccessNodeWrap::Init()) return PR_FALSE; - nsDocAccessible *docAcc = + nsDocAccessible* document = GetAccService()->GetDocAccessible(mContent->GetOwnerDoc()); - NS_ASSERTION(docAcc, "Cannot cache new nsAccessible!"); - if (!docAcc) - return PR_FALSE; - - void *uniqueID = nsnull; - GetUniqueID(&uniqueID); - - return docAcc->CacheAccessible(uniqueID, this); + NS_ASSERTION(document, "Cannot cache new nsAccessible!"); + + return document ? document->CacheAccessible(this) : PR_FALSE; } void nsAccessible::Shutdown() { // Invalidate the child count and pointers to other accessibles, also make // sure none of its children point to this parent InvalidateChildren(); - if (mParent) { - mParent->InvalidateChildren(); - UnbindFromParent(); - } + if (mParent) + mParent->RemoveChild(this); nsAccessNodeWrap::Shutdown(); } //////////////////////////////////////////////////////////////////////////////// // nsAccessible public methods nsresult @@ -2694,24 +2687,24 @@ nsAccessible::GetNameInternal(nsAString& } // nsAccessible protected void nsAccessible::BindToParent(nsAccessible* aParent, PRUint32 aIndexInParent) { NS_PRECONDITION(aParent, "This method isn't used to set null parent!"); - if (mParent && mParent != aParent) { - // Adopt a child -- we allow this now. the new parent - // may be a dom node which wasn't previously accessible but now is. - // The old parent's children now need to be invalidated, since - // it no longer owns the child, the new parent does - NS_ASSERTION(PR_FALSE, "Adopting child!"); - if (mParent) + if (mParent) { + if (mParent != aParent) { + NS_ERROR("Adopting child!"); mParent->InvalidateChildren(); + } else { + NS_ERROR("Binding to the same parent!"); + return; + } } mParent = aParent; mIndexInParent = aIndexInParent; } void nsAccessible::UnbindFromParent() @@ -2902,23 +2895,20 @@ nsAccessible::GetIndexOfEmbeddedChild(ns } #ifdef DEBUG PRBool nsAccessible::IsInCache() { nsDocAccessible *docAccessible = GetAccService()->GetDocAccessible(mContent->GetOwnerDoc()); - if (!docAccessible) - return nsnull; - - void *uniqueID = nsnull; - GetUniqueID(&uniqueID); - - return docAccessible->GetCachedAccessible(uniqueID) ? PR_TRUE : PR_FALSE; + if (docAccessible) + return docAccessible->GetCachedAccessibleByUniqueID(UniqueID()) ? PR_TRUE : PR_FALSE; + + return PR_FALSE; } #endif //////////////////////////////////////////////////////////////////////////////// // HyperLinkAccessible methods bool
--- a/accessible/src/base/nsAccessible.h +++ b/accessible/src/base/nsAccessible.h @@ -46,31 +46,34 @@ #include "nsIAccessibleSelectable.h" #include "nsIAccessibleValue.h" #include "nsIAccessibleRole.h" #include "nsIAccessibleStates.h" #include "nsStringGlue.h" #include "nsTArray.h" #include "nsRefPtrHashtable.h" +#include "nsDataHashtable.h" class AccGroupInfo; class EmbeddedObjCollector; class nsAccessible; class AccEvent; struct nsRoleMapEntry; struct nsRect; class nsIContent; class nsIFrame; class nsIAtom; class nsIView; typedef nsRefPtrHashtable<nsVoidPtrHashKey, nsAccessible> nsAccessibleHashtable; +typedef nsDataHashtable<nsPtrHashKey<const nsINode>, nsAccessible*> + NodeToAccessibleMap; // see nsAccessible::GetAttrValue #define NS_OK_NO_ARIA_VALUE \ NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_GENERAL, 0x21) // see nsAccessible::GetNameInternal #define NS_OK_EMPTY_NAME \ NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_GENERAL, 0x23) @@ -201,16 +204,17 @@ public: /** * Set the ARIA role map entry for a new accessible. * For a newly created accessible, specify which role map entry should be used. * * @param aRoleMapEntry The ARIA nsRoleMapEntry* for the accessible, or * nsnull if none. */ virtual void SetRoleMapEntry(nsRoleMapEntry *aRoleMapEntry); + const nsRoleMapEntry* GetRoleMapEntry() const { return mRoleMapEntry; } /** * Cache children if necessary. Return true if the accessible is defunct. */ PRBool EnsureChildren(); /** * Set the child count to -1 (unknown) and null out cached child pointers. @@ -285,17 +289,19 @@ public: mParent->mChildren.SafeElementAt(mIndexInParent + 1, nsnull).get() : nsnull; } nsAccessible* GetCachedPrevSibling() const { return mParent ? mParent->mChildren.SafeElementAt(mIndexInParent - 1, nsnull).get() : nsnull; } PRUint32 GetCachedChildCount() const { return mChildren.Length(); } + nsAccessible* GetCachedChildAt(PRUint32 aIndex) const { return mChildren.ElementAt(aIndex); } PRBool AreChildrenCached() const { return mChildrenFlags != eChildrenUninitialized; } + bool IsBoundToParent() const { return mParent; } #ifdef DEBUG /** * Return true if the access node is cached. */ PRBool IsInCache(); #endif
--- a/accessible/src/base/nsApplicationAccessible.cpp +++ b/accessible/src/base/nsApplicationAccessible.cpp @@ -358,16 +358,22 @@ nsApplicationAccessible::Init() } void nsApplicationAccessible::Shutdown() { mAppInfo = nsnull; } +bool +nsApplicationAccessible::IsPrimaryForNode() const +{ + return false; +} + //////////////////////////////////////////////////////////////////////////////// // nsAccessible public methods nsresult nsApplicationAccessible::GetARIAState(PRUint32 *aState, PRUint32 *aExtraState) { return NS_OK; } @@ -465,17 +471,17 @@ nsApplicationAccessible::GetSiblingAtOff if (aError) *aError = NS_OK; // fail peacefully return nsnull; } //////////////////////////////////////////////////////////////////////////////// -// nsIAccessNode +// nsIAccessNode and nsAccessNode NS_IMETHODIMP nsApplicationAccessible::GetDOMNode(nsIDOMNode **aDOMNode) { NS_ENSURE_ARG_POINTER(aDOMNode); *aDOMNode = nsnull; return NS_OK; } @@ -520,24 +526,16 @@ NS_IMETHODIMP nsApplicationAccessible::GetOwnerWindow(void **aOwnerWindow) { NS_ENSURE_ARG_POINTER(aOwnerWindow); *aOwnerWindow = nsnull; return NS_OK; } NS_IMETHODIMP -nsApplicationAccessible::GetUniqueID(void **aUniqueID) -{ - NS_ENSURE_ARG_POINTER(aUniqueID); - *aUniqueID = static_cast<void *>(this); - return NS_OK; -} - -NS_IMETHODIMP nsApplicationAccessible::GetComputedStyleValue(const nsAString &aPseudoElt, const nsAString &aPropertyName, nsAString &aValue) { return NS_OK; } NS_IMETHODIMP @@ -551,8 +549,9 @@ nsApplicationAccessible::GetComputedStyl } NS_IMETHODIMP nsApplicationAccessible::GetLanguage(nsAString &aLanguage) { aLanguage.Truncate(); return NS_OK; } +
--- a/accessible/src/base/nsApplicationAccessible.h +++ b/accessible/src/base/nsApplicationAccessible.h @@ -66,17 +66,30 @@ public: using nsAccessible::GetChildAtPoint; nsApplicationAccessible(); // nsISupports NS_DECL_ISUPPORTS_INHERITED // nsIAccessNode - NS_DECL_NSIACCESSNODE + NS_SCRIPTABLE NS_IMETHOD GetDOMNode(nsIDOMNode** aDOMNode); + NS_SCRIPTABLE NS_IMETHOD GetDocument(nsIAccessibleDocument** aDocument); + NS_SCRIPTABLE NS_IMETHOD GetRootDocument(nsIAccessibleDocument** aRootDocument); + NS_SCRIPTABLE NS_IMETHOD GetInnerHTML(nsAString& aInnerHTML); + NS_SCRIPTABLE NS_IMETHOD ScrollTo(PRUint32 aScrollType); + NS_SCRIPTABLE NS_IMETHOD ScrollToPoint(PRUint32 aCoordinateType, PRInt32 aX, PRInt32 aY); + NS_IMETHOD GetOwnerWindow(void **aOwnerWindow); + NS_SCRIPTABLE NS_IMETHOD GetComputedStyleValue(const nsAString& aPseudoElt, + const nsAString& aPropertyName, + nsAString& aValue NS_OUTPARAM); + NS_SCRIPTABLE NS_IMETHOD GetComputedStyleCSSValue(const nsAString& aPseudoElt, + const nsAString& aPropertyName, + nsIDOMCSSPrimitiveValue** aValue NS_OUTPARAM); + NS_SCRIPTABLE NS_IMETHOD GetLanguage(nsAString& aLanguage); // nsIAccessible NS_IMETHOD GetParent(nsIAccessible **aParent); NS_IMETHOD GetNextSibling(nsIAccessible **aNextSibling); NS_IMETHOD GetPreviousSibling(nsIAccessible **aPreviousSibling); NS_IMETHOD GetName(nsAString &aName); NS_IMETHOD GetValue(nsAString &aValue); NS_IMETHOD GetDescription(nsAString &aDescription); @@ -104,16 +117,17 @@ public: // nsIAccessibleApplication NS_DECL_NSIACCESSIBLEAPPLICATION // nsAccessNode virtual PRBool IsDefunct(); virtual PRBool Init(); virtual void Shutdown(); + virtual bool IsPrimaryForNode() const; // nsAccessible virtual nsresult GetARIAState(PRUint32 *aState, PRUint32 *aExtraState); virtual PRUint32 Role(); virtual PRUint32 NativeRole(); virtual nsresult GetStateInternal(PRUint32 *aState, PRUint32 *aExtraState); virtual void InvalidateChildren();
--- a/accessible/src/base/nsCaretAccessible.cpp +++ b/accessible/src/base/nsCaretAccessible.cpp @@ -280,18 +280,17 @@ nsCaretAccessible::SpellcheckSelectionCh // misspelled word). If spellchecking is disabled (for example, // @spellcheck="false" on html:body) then we won't fire any event. nsRefPtr<nsHyperTextAccessible> textAcc = nsAccUtils::GetTextAccessibleFromSelection(aSel); NS_ENSURE_STATE(textAcc); nsRefPtr<AccEvent> event = - new AccEvent(nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED, - textAcc, nsnull); + new AccEvent(nsIAccessibleEvent::EVENT_TEXT_ATTRIBUTE_CHANGED, textAcc); nsEventShell::FireEvent(event); return NS_OK; } nsIntRect nsCaretAccessible::GetCaretRect(nsIWidget **aOutWidget) {
--- a/accessible/src/base/nsDocAccessible.cpp +++ b/accessible/src/base/nsDocAccessible.cpp @@ -79,30 +79,30 @@ #endif namespace dom = mozilla::dom; //////////////////////////////////////////////////////////////////////////////// // Static member initialization PRUint32 nsDocAccessible::gLastFocusedAccessiblesState = 0; -nsIAtom *nsDocAccessible::gLastFocusedFrameType = nsnull; //////////////////////////////////////////////////////////////////////////////// // Constructor/desctructor nsDocAccessible:: nsDocAccessible(nsIDocument *aDocument, nsIContent *aRootContent, nsIWeakReference *aShell) : nsHyperTextAccessibleWrap(aRootContent, aShell), mDocument(aDocument), mScrollPositionChangedTicks(0), mIsLoaded(PR_FALSE) { // XXX aaronl should we use an algorithm for the initial cache size? mAccessibleCache.Init(kDefaultCacheSize); + mNodeToAccessibleMap.Init(kDefaultCacheSize); // For GTK+ native window, we do nothing here. if (!mDocument) return; // nsAccDocManager creates document accessible when scrollable frame is // available already, it should be safe time to add scroll listener. AddScrollListener(); @@ -129,16 +129,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_ } CycleCollectorTraverseCache(tmp->mAccessibleCache, &cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDocAccessible, nsAccessible) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEventQueue) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSTARRAY(mChildDocuments) + tmp->mNodeToAccessibleMap.Clear(); ClearCache(tmp->mAccessibleCache); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDocAccessible) NS_INTERFACE_MAP_STATIC_AMBIGUOUS(nsDocAccessible) NS_INTERFACE_MAP_ENTRY(nsIAccessibleDocument) NS_INTERFACE_MAP_ENTRY(nsIDocumentObserver) NS_INTERFACE_MAP_ENTRY(nsIMutationObserver) @@ -563,26 +564,24 @@ NS_IMETHODIMP nsDocAccessible::GetAssoci if (isEditable) { NS_ADDREF(*aEditor = editor); } return NS_OK; } // nsDocAccessible public method nsAccessible * -nsDocAccessible::GetCachedAccessible(void *aUniqueID) +nsDocAccessible::GetCachedAccessible(nsINode *aNode) { - nsAccessible* accessible = mAccessibleCache.GetWeak(aUniqueID); + nsAccessible* accessible = mNodeToAccessibleMap.Get(aNode); // No accessible in the cache, check if the given ID is unique ID of this // document accessible. if (!accessible) { - void* thisUniqueID = nsnull; - GetUniqueID(&thisUniqueID); - if (thisUniqueID != aUniqueID) + if (GetNode() != aNode) return nsnull; accessible = this; } #ifdef DEBUG // All cached accessible nodes should be in the parent // It will assert if not all the children were created @@ -593,39 +592,36 @@ nsDocAccessible::GetCachedAccessible(voi parent->TestChildCache(accessible); #endif return accessible; } // nsDocAccessible public method PRBool -nsDocAccessible::CacheAccessible(void *aUniqueID, nsAccessible *aAccessible) +nsDocAccessible::CacheAccessible(nsAccessible* aAccessible) { - // If there is already an accessible with the given unique ID, shut it down - // because the DOM node has changed. - nsAccessible *accessible = mAccessibleCache.GetWeak(aUniqueID); - NS_ASSERTION(!accessible, - "Caching new accessible for the DOM node while the old one is alive"); + if (aAccessible->IsPrimaryForNode() && + !mNodeToAccessibleMap.Put(aAccessible->GetNode(), aAccessible)) + return PR_FALSE; - if (accessible) - accessible->Shutdown(); - - return mAccessibleCache.Put(aUniqueID, aAccessible); + return mAccessibleCache.Put(aAccessible->UniqueID(), aAccessible); } // nsDocAccessible public method void -nsDocAccessible::RemoveAccessNodeFromCache(nsAccessible *aAccessible) +nsDocAccessible::ShutdownAccessible(nsAccessible *aAccessible) { - if (!aAccessible) - return; + // Remove an accessible from node to accessible map if it is presented there. + if (aAccessible->IsPrimaryForNode() && + mNodeToAccessibleMap.Get(aAccessible->GetNode()) == aAccessible) + mNodeToAccessibleMap.Remove(aAccessible->GetNode()); - void *uniqueID = nsnull; - aAccessible->GetUniqueID(&uniqueID); + void* uniqueID = aAccessible->UniqueID(); + aAccessible->Shutdown(); mAccessibleCache.Remove(uniqueID); } //////////////////////////////////////////////////////////////////////////////// // nsAccessNode PRBool nsDocAccessible::Init() @@ -641,22 +637,24 @@ nsDocAccessible::Init() nsDocAccessible* parentDocument = mParent->GetDocAccessible(); if (parentDocument) parentDocument->AppendChildDocument(this); // Fire reorder event to notify new accessible document has been created and // attached to the tree. nsRefPtr<AccEvent> reorderEvent = - new AccReorderEvent(mParent, PR_FALSE, PR_TRUE, mDocument); - if (!reorderEvent) - return PR_FALSE; + new AccEvent(nsIAccessibleEvent::EVENT_REORDER, mParent, eAutoDetect, + AccEvent::eCoalesceFromSameSubtree); + if (reorderEvent) { + FireDelayedAccessibleEvent(reorderEvent); + return PR_TRUE; + } - FireDelayedAccessibleEvent(reorderEvent); - return PR_TRUE; + return PR_FALSE; } void nsDocAccessible::Shutdown() { if (!mWeakShell) // already shutdown return; @@ -676,16 +674,17 @@ nsDocAccessible::Shutdown() mParent->RemoveChild(this); } mChildDocuments.Clear(); mWeakShell = nsnull; // Avoid reentrancy + mNodeToAccessibleMap.Clear(); ClearCache(mAccessibleCache); nsCOMPtr<nsIDocument> kungFuDeathGripDoc = mDocument; mDocument = nsnull; nsHyperTextAccessibleWrap::Shutdown(); } @@ -1039,18 +1038,17 @@ nsDocAccessible::AttributeChangedImpl(ns } if (aAttribute == nsAccessibilityAtoms::role || aAttribute == nsAccessibilityAtoms::href || aAttribute == nsAccessibilityAtoms::onclick) { // Not worth the expense to ensure which namespace these are in // It doesn't kill use to recreate the accessible even if the attribute was used // in the wrong namespace or an element that doesn't support it - InvalidateCacheSubtree(aContent, - nsIAccessibilityService::NODE_SIGNIFICANT_CHANGE); + RecreateAccessible(aContent); return; } if (aAttribute == nsAccessibilityAtoms::alt || aAttribute == nsAccessibilityAtoms::title || aAttribute == nsAccessibilityAtoms::aria_label || aAttribute == nsAccessibilityAtoms::aria_labelledby) { FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_NAME_CHANGE, @@ -1213,44 +1211,28 @@ nsDocAccessible::ARIAAttributeChanged(ns return; } if (aAttribute == nsAccessibilityAtoms::aria_multiselectable && aContent->HasAttr(kNameSpaceID_None, nsAccessibilityAtoms::role)) { // This affects whether the accessible supports SelectAccessible. // COM says we cannot change what interfaces are supported on-the-fly, // so invalidate this object. A new one will be created on demand. - InvalidateCacheSubtree(aContent, - nsIAccessibilityService::NODE_SIGNIFICANT_CHANGE); + RecreateAccessible(aContent); return; } } void nsDocAccessible::ContentAppended(nsIDocument *aDocument, nsIContent* aContainer, nsIContent* aFirstNewContent, PRInt32 /* unused */) { - if (!IsContentLoaded() && mAccessibleCache.Count() <= 1) { - // See comments in nsDocAccessible::InvalidateCacheSubtree - InvalidateChildren(); - return; - } - - // Does this need to be a strong ref? If so, why? - for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) { - // InvalidateCacheSubtree will not fire the EVENT_SHOW for the new node - // unless an accessible can be created for the passed in node, which it - // can't do unless the node is visible. The right thing happens there so - // no need for an extra visibility check here. - InvalidateCacheSubtree(cur, nsIAccessibilityService::NODE_APPEND); - } } - void nsDocAccessible::ContentStatesChanged(nsIDocument* aDocument, nsIContent* aContent1, nsIContent* aContent2, nsEventStates aStateMask) { if (!aStateMask.HasState(NS_EVENT_STATE_CHECKED)) { return; } @@ -1277,34 +1259,23 @@ void nsDocAccessible::CharacterDataChang { FireTextChangeEventForText(aContent, aInfo, PR_TRUE); } void nsDocAccessible::ContentInserted(nsIDocument *aDocument, nsIContent* aContainer, nsIContent* aChild, PRInt32 /* unused */) { - // InvalidateCacheSubtree will not fire the EVENT_SHOW for the new node - // unless an accessible can be created for the passed in node, which it - // can't do unless the node is visible. The right thing happens there so - // no need for an extra visibility check here. - InvalidateCacheSubtree(aChild, nsIAccessibilityService::NODE_APPEND); } void nsDocAccessible::ContentRemoved(nsIDocument *aDocument, nsIContent* aContainer, nsIContent* aChild, PRInt32 /* unused */, nsIContent* aPreviousSibling) { - // It's no needed to invalidate the subtree of the removed element, - // because we get notifications directly from content (see - // nsGenericElement::doRemoveChildAt) *before* the frame for the content is - // destroyed, or any other side effects occur . That allows us to correctly - // calculate the TEXT_REMOVED event if there is one and coalesce events from - // the same subtree. } void nsDocAccessible::ParentChainChanged(nsIContent *aContent) { } @@ -1335,46 +1306,190 @@ nsDocAccessible::GetNativeWindow() const vm->GetRootWidget(getter_AddRefs(widget)); if (widget) return widget->GetNativeData(NS_NATIVE_WINDOW); } return nsnull; } nsAccessible* -nsDocAccessible::GetCachedAccessibleInSubtree(void* aUniqueID) +nsDocAccessible::GetCachedAccessibleByUniqueIDInSubtree(void* aUniqueID) { - nsAccessible* child = GetCachedAccessible(aUniqueID); + nsAccessible* child = GetCachedAccessibleByUniqueID(aUniqueID); if (child) return child; PRUint32 childDocCount = mChildDocuments.Length(); for (PRUint32 childDocIdx= 0; childDocIdx < childDocCount; childDocIdx++) { nsDocAccessible* childDocument = mChildDocuments.ElementAt(childDocIdx); - child = childDocument->GetCachedAccessibleInSubtree(aUniqueID); + child = childDocument->GetCachedAccessibleByUniqueIDInSubtree(aUniqueID); if (child) return child; } return nsnull; } +void +nsDocAccessible::UpdateTree(nsIContent* aContainerNode, + nsIContent* aStartNode, + nsIContent* aEndNode, + PRBool aIsInsert) +{ + // Content change notification mostly are async, thus we can't detect whether + // these actions are from user. This information is used to fire or do not + // fire events to avoid events that are generated because of document loading. + // Since this information may be not correct then we need to fire some events + // regardless the document loading state. + + nsCOMPtr<nsIPresShell> presShell = GetPresShell(); + nsIEventStateManager* esm = presShell->GetPresContext()->EventStateManager(); + PRBool fireAllEvents = PR_TRUE;//IsContentLoaded() || esm->IsHandlingUserInputExternal(); + + // We don't create new accessibles on content removal. + nsAccessible* container = aIsInsert ? + GetAccService()->GetAccessibleOrContainer(aContainerNode, mWeakShell) : + GetAccService()->GetCachedAccessibleOrContainer(aContainerNode); + + if (aIsInsert) { + // XXX: Invalidate parent-child relations for container accessible and its + // children because there's no good way to find insertion point of new child + // accessibles into accessible tree. We need to invalidate children even + // there's no inserted accessibles in the end because accessible children + // are created while parent recaches child accessibles. + container->InvalidateChildren(); + } + + EIsFromUserInput fromUserInput = esm->IsHandlingUserInputExternal() ? + eFromUserInput : eNoUserInput; + + // Update the accessible tree in the case of content removal and fire events + // if allowed. + PRUint32 updateFlags = + UpdateTreeInternal(container, aStartNode, aEndNode, + aIsInsert, fireAllEvents, fromUserInput); + + // Content insertion/removal is not cause of accessible tree change. + if (updateFlags == eNoAccessible) + return; + + // Check to see if change occurred inside an alert, and fire an EVENT_ALERT + // if it did. + if (aIsInsert && !(updateFlags & eAlertAccessible)) { + // XXX: tree traversal is perf issue, accessible should know if they are + // children of alert accessible to avoid this. + nsAccessible* ancestor = container; + while (ancestor) { + const nsRoleMapEntry* roleMapEntry = ancestor->GetRoleMapEntry(); + if (roleMapEntry && roleMapEntry->role == nsIAccessibleRole::ROLE_ALERT) { + FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_ALERT, + ancestor->GetNode(), AccEvent::eRemoveDupes, + fromUserInput); + break; + } + + // Don't climb above this document. + if (ancestor == this) + break; + + ancestor = ancestor->GetParent(); + } + } + + // Fire nether value change nor reorder events if action is not from user + // input and document is loading. We are notified about changes in editor + // synchronously, so from user input flag is correct for value change events. + if (!fireAllEvents) + return; + + // Fire value change event. + if (container->Role() == nsIAccessibleRole::ROLE_ENTRY) { + nsRefPtr<AccEvent> valueChangeEvent = + new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, container, + fromUserInput, AccEvent::eRemoveDupes); + FireDelayedAccessibleEvent(valueChangeEvent); + } + + // Fire reorder event so the MSAA clients know the children have changed. Also + // the event is used internally by MSAA part. + nsRefPtr<AccEvent> reorderEvent = + new AccEvent(nsIAccessibleEvent::EVENT_REORDER, container->GetNode(), + fromUserInput, AccEvent::eCoalesceFromSameSubtree); + if (reorderEvent) + FireDelayedAccessibleEvent(reorderEvent); +} + +void +nsDocAccessible::RecreateAccessible(nsINode* aNode) +{ + // XXX: we shouldn't recreate whole accessible subtree that happens when + // hide event is handled, instead we should subclass hide and show events + // to handle them separately and implement their coalescence with normal hide + // and show events. + + nsAccessible* parent = nsnull; + + // Fire hide event for old accessible. + nsAccessible* oldAccessible = + GetAccService()->GetAccessibleInWeakShell(aNode, mWeakShell); + if (oldAccessible) { + parent = oldAccessible->GetParent(); + + nsRefPtr<AccEvent> hideEvent = new AccHideEvent(oldAccessible, aNode, + eAutoDetect); + if (hideEvent) + FireDelayedAccessibleEvent(hideEvent); + + // Unbind old accessible from tree. + parent->RemoveChild(oldAccessible); + + if (oldAccessible->IsPrimaryForNode() && + mNodeToAccessibleMap.Get(oldAccessible->GetNode()) == oldAccessible) + mNodeToAccessibleMap.Remove(oldAccessible->GetNode()); + + } else { + parent = GetAccService()->GetContainerAccessible(aNode, mWeakShell); + } + + // Get new accessible and fire show event. + parent->InvalidateChildren(); + + nsAccessible* newAccessible = + GetAccService()->GetAccessibleInWeakShell(aNode, mWeakShell); + if (newAccessible) { + nsRefPtr<AccEvent> showEvent = new AccShowEvent(newAccessible, aNode, + eAutoDetect); + if (showEvent) + FireDelayedAccessibleEvent(showEvent); + } + + // Fire reorder event. + if (oldAccessible || newAccessible) { + nsRefPtr<AccEvent> reorderEvent = + new AccEvent(nsIAccessibleEvent::EVENT_REORDER, parent->GetNode(), + eAutoDetect, AccEvent::eCoalesceFromSameSubtree); + + if (reorderEvent) + FireDelayedAccessibleEvent(reorderEvent); + } +} + //////////////////////////////////////////////////////////////////////////////// // Protected members void nsDocAccessible::FireValueChangeForTextFields(nsAccessible *aAccessible) { if (aAccessible->Role() != nsIAccessibleRole::ROLE_ENTRY) return; // Dependent value change event for text changes in textfields nsRefPtr<AccEvent> valueChangeEvent = new AccEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, aAccessible, - PR_FALSE, eAutoDetect, AccEvent::eRemoveDupes); + eAutoDetect, AccEvent::eRemoveDupes); FireDelayedAccessibleEvent(valueChangeEvent); } void nsDocAccessible::FireTextChangeEventForText(nsIContent *aContent, CharacterDataChangeInfo* aInfo, PRBool aIsInserted) { @@ -1421,106 +1536,30 @@ nsDocAccessible::FireTextChangeEventForT if (text.IsEmpty()) return; // Normally we only fire delayed events created from the node, not an // accessible object. See the AccTextChangeEvent constructor for details // about this exceptional case. nsRefPtr<AccEvent> event = new AccTextChangeEvent(textAccessible, offset + textOffset, text, - aIsInserted, PR_FALSE); + aIsInserted); FireDelayedAccessibleEvent(event); FireValueChangeForTextFields(textAccessible); } -already_AddRefed<AccEvent> -nsDocAccessible::CreateTextChangeEventForNode(nsAccessible *aContainerAccessible, - nsIContent *aChangeNode, - nsAccessible *aChangeChild, - PRBool aIsInserting, - PRBool aIsAsynch, - EIsFromUserInput aIsFromUserInput) -{ - nsRefPtr<nsHyperTextAccessible> textAccessible = - do_QueryObject(aContainerAccessible); - if (!textAccessible) { - return nsnull; - } - - nsAutoString text; - PRInt32 offset = 0; - if (aChangeChild) { - // Don't fire event for the first html:br in an editor. - if (aChangeChild->Role() == nsIAccessibleRole::ROLE_WHITESPACE) { - nsCOMPtr<nsIEditor> editor; - textAccessible->GetAssociatedEditor(getter_AddRefs(editor)); - if (editor) { - PRBool isEmpty = PR_FALSE; - editor->GetDocumentIsEmpty(&isEmpty); - if (isEmpty) { - return nsnull; - } - } - } - - offset = textAccessible->GetChildOffset(aChangeChild); - aChangeChild->AppendTextTo(text, 0, PR_UINT32_MAX); - - } else { - // A span-level object or something else without an accessible is being - // added, where it has no accessible but it has descendant content which is - // aggregated as text into the parent hypertext. In this case, changed text - // is compounded from all accessible contained in changed node. - nsAccTreeWalker walker(mWeakShell, aChangeNode, - GetAllowsAnonChildAccessibles()); - nsRefPtr<nsAccessible> child = walker.GetNextChild(); - - // No descendant content that represents any text in the hypertext parent. - if (!child) - return nsnull; - - offset = textAccessible->GetChildOffset(child); - child->AppendTextTo(text, 0, PR_UINT32_MAX); - - nsINode* containerNode = textAccessible->GetNode(); - PRInt32 childCount = textAccessible->GetChildCount(); - PRInt32 childIdx = child->GetIndexInParent(); - - for (PRInt32 idx = childIdx + 1; idx < childCount; idx++) { - nsAccessible* nextChild = textAccessible->GetChildAt(idx); - // We only want accessibles with DOM nodes as children of this node. - if (!nsCoreUtils::IsAncestorOf(aChangeNode, nextChild->GetNode(), - containerNode)) - break; - - nextChild->AppendTextTo(text, 0, PR_UINT32_MAX); - } - } - - if (text.IsEmpty()) - return nsnull; - - AccEvent* event = new AccTextChangeEvent(aContainerAccessible, offset, text, - aIsInserting, aIsAsynch, - aIsFromUserInput); - NS_IF_ADDREF(event); - - return event; -} - // nsDocAccessible public member nsresult nsDocAccessible::FireDelayedAccessibleEvent(PRUint32 aEventType, nsINode *aNode, AccEvent::EEventRule aAllowDupes, - PRBool aIsAsynch, EIsFromUserInput aIsFromUserInput) { nsRefPtr<AccEvent> event = - new AccEvent(aEventType, aNode, aIsAsynch, aIsFromUserInput, aAllowDupes); + new AccEvent(aEventType, aNode, aIsFromUserInput, aAllowDupes); NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); return FireDelayedAccessibleEvent(event); } // nsDocAccessible public member nsresult nsDocAccessible::FireDelayedAccessibleEvent(AccEvent* aEvent) @@ -1529,509 +1568,174 @@ nsDocAccessible::FireDelayedAccessibleEv NS_LOG_ACCDOCLOAD_FIREEVENT(aEvent) if (mEventQueue) mEventQueue->Push(aEvent); return NS_OK; } +// nsDocAccessible public member void nsDocAccessible::ProcessPendingEvent(AccEvent* aEvent) -{ - nsAccessible *accessible = aEvent->GetAccessible(); - nsINode *node = aEvent->GetNode(); - - PRUint32 eventType = aEvent->GetEventType(); - EIsFromUserInput isFromUserInput = - aEvent->IsFromUserInput() ? eFromUserInput : eNoUserInput; - - PRBool isAsync = aEvent->IsAsync(); - - if (node == 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<nsIContent> focusContent(do_QueryInterface(node)); - 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(node, PR_TRUE, eventType, eNormalEvent, - isAsync, isFromUserInput); - return; - } - gLastFocusedFrameType = newFrameType; - } - } - - if (eventType == nsIAccessibleEvent::EVENT_SHOW) { - - nsAccessible* containerAccessible = nsnull; - if (accessible) { - containerAccessible = accessible->GetParent(); - } else { - nsCOMPtr<nsIWeakReference> weakShell(nsCoreUtils::GetWeakShellFor(node)); - containerAccessible = GetAccService()->GetContainerAccessible(node, - weakShell); - } - - if (!containerAccessible) - containerAccessible = this; - - if (isAsync) { - // For asynch show, delayed invalidatation of parent's children - containerAccessible->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(node); - } - - // 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 (node && node != mDocument) { - nsCOMPtr<nsIContent> content(do_QueryInterface(node)); - nsRefPtr<AccEvent> textChangeEvent = - CreateTextChangeEventForNode(containerAccessible, content, 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(node, PR_FALSE, eventType, eNormalEvent, isAsync, - isFromUserInput); - return; - } - - if (accessible) { - if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) { - nsCOMPtr<nsIAccessibleText> accessibleText = do_QueryObject(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); -#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 - nsAccessible *focusedAcc = - GetAccService()->GetAccessible(gLastFocusedNode); - nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, focusedAcc); -#endif - nsRefPtr<AccEvent> caretMoveEvent = - new AccCaretMoveEvent(accessible, caretOffset); - if (!caretMoveEvent) - return; - - 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. - AccReorderEvent* reorderEvent = downcast_accEvent(aEvent); - if (reorderEvent->IsUnconditionalEvent() || - reorderEvent->HasAccessibleInReasonSubtree()) { - nsEventShell::FireEvent(aEvent); - } - } - else { - nsEventShell::FireEvent(aEvent); - - // Post event processing - if (eventType == nsIAccessibleEvent::EVENT_HIDE && node) { - // Shutdown nsIAccessNode's or nsIAccessibles for any DOM nodes in - // this subtree. - // XXX: Will this bite us with asynch events. - RefreshNodes(node); - } - } - } -} - -void -nsDocAccessible::InvalidateChildrenInSubtree(nsINode *aStartNode) { - nsAccessible *accessible = GetCachedAccessible(aStartNode); - if (accessible) - accessible->InvalidateChildren(); - - // Invalidate accessible children in the DOM subtree - PRInt32 index, numChildren = aStartNode->GetChildCount(); - for (index = 0; index < numChildren; index ++) { - nsINode *childNode = aStartNode->GetChildAt(index); - InvalidateChildrenInSubtree(childNode); - } -} - -void -nsDocAccessible::RefreshNodes(nsINode *aStartNode) -{ - if (mAccessibleCache.Count() <= 1) { - return; // All we have is a doc accessible. There is nothing to invalidate, quit early - } - - // Shut down accessible subtree, which may have been created for anonymous - // content subtree. - nsAccessible *accessible = GetCachedAccessible(aStartNode); - if (accessible) { - // Fire menupopup end if a menu goes away - if (accessible->Role() == nsIAccessibleRole::ROLE_MENUPOPUP) { - nsCOMPtr<nsIDOMXULPopupElement> popup(do_QueryInterface(aStartNode)); - if (!popup) { - // Popup elements already fire these via DOMMenuInactive - // handling in nsRootAccessible::HandleEvent - nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_END, - accessible); - } - } - - // We only need to shutdown the accessibles here if one of them has been - // created. - if (accessible->GetCachedChildCount() > 0) { - nsCOMPtr<nsIArray> children; - // use GetChildren() to fetch all children at once, because after shutdown - // the child references are cleared. - accessible->GetChildren(getter_AddRefs(children)); - PRUint32 childCount =0; - if (children) - children->GetLength(&childCount); - nsINode *possibleAnonNode = nsnull; - for (PRUint32 index = 0; index < childCount; index++) { - nsRefPtr<nsAccessNode> childAccessNode; - children->QueryElementAt(index, NS_GET_IID(nsAccessNode), - getter_AddRefs(childAccessNode)); - possibleAnonNode = childAccessNode->GetNode(); - nsCOMPtr<nsIContent> iterContent = do_QueryInterface(possibleAnonNode); - if (iterContent && iterContent->IsInAnonymousSubtree()) { - // IsInAnonymousSubtree() check is a perf win -- make sure we don't - // shut down the same subtree twice since we'll reach non-anon content via - // DOM traversal later in this method - RefreshNodes(possibleAnonNode); - } - } - } - } - - // Shutdown ordinary content subtree as well -- there may be - // access node children which are not full accessible objects - PRUint32 childCount = aStartNode->GetChildCount(); - for (PRUint32 childIdx = 0; childIdx < childCount; childIdx++) { - nsIContent *childContent = aStartNode->GetChildAt(childIdx); - RefreshNodes(childContent); - } - + nsAccessible* accessible = aEvent->GetAccessible(); if (!accessible) return; - if (accessible == this) { - // Don't shutdown our doc object -- this may just be from the finished loading. - // We will completely shut it down when the pagehide event is received - // However, we must invalidate the doc accessible's children in order to be sure - // all pointers to them are correct - InvalidateChildren(); - return; + PRUint32 eventType = aEvent->GetEventType(); + + if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED) { + nsCOMPtr<nsIAccessibleText> accessibleText = do_QueryObject(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); +#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 + nsAccessible* focusedAcc = + GetAccService()->GetAccessible(gLastFocusedNode); + nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, focusedAcc); +#endif + nsRefPtr<AccEvent> caretMoveEvent = + new AccCaretMoveEvent(accessible, caretOffset); + if (!caretMoveEvent) + return; + + 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); + } + } } + else { + nsEventShell::FireEvent(aEvent); - // Shut down the actual accessible or access node - void *uniqueID; - accessible->GetUniqueID(&uniqueID); - accessible->Shutdown(); - - // Remove from hash table as well - mAccessibleCache.Remove(uniqueID); + // Post event processing + if (eventType == nsIAccessibleEvent::EVENT_HIDE) + ShutdownChildrenInSubtree(accessible); + } } -// nsDocAccessible public member -void -nsDocAccessible::InvalidateCacheSubtree(nsIContent *aChild, - PRUint32 aChangeType) +PRUint32 +nsDocAccessible::UpdateTreeInternal(nsAccessible* aContainer, + nsIContent* aStartNode, + nsIContent* aEndNode, + PRBool aIsInsert, + PRBool aFireAllEvents, + EIsFromUserInput aFromUserInput) { - PRBool isHiding = - aChangeType == nsIAccessibilityService::FRAME_HIDE || - aChangeType == nsIAccessibilityService::NODE_REMOVE; - - PRBool isShowing = - aChangeType == nsIAccessibilityService::FRAME_SHOW || - aChangeType == nsIAccessibilityService::NODE_APPEND; - -#ifdef DEBUG - PRBool isChanging = - aChangeType == nsIAccessibilityService::NODE_SIGNIFICANT_CHANGE || - aChangeType == nsIAccessibilityService::FRAME_SIGNIFICANT_CHANGE; -#endif - - NS_ASSERTION(isChanging || isHiding || isShowing, - "Incorrect aChangeEventType passed in"); - - PRBool isAsynch = - aChangeType == nsIAccessibilityService::FRAME_HIDE || - aChangeType == nsIAccessibilityService::FRAME_SHOW || - aChangeType == nsIAccessibilityService::FRAME_SIGNIFICANT_CHANGE; + PRUint32 updateFlags = eNoAccessible; + for (nsIContent* node = aStartNode; node != aEndNode; + node = node->GetNextSibling()) { - // Invalidate cache subtree - // We have to check for accessibles for each dom node by traversing DOM tree - // instead of just the accessible tree, although that would be faster - // Otherwise we might miss the nsAccessNode's that are not nsAccessible's. - - NS_ENSURE_TRUE(mDocument,); - - nsINode *childNode = aChild; - if (!childNode) - childNode = mDocument; + // Tree update triggers for content insertion even if no content was + // inserted actually, check if the given content has a frame to discard + // this case early. + if (aIsInsert && !node->GetPrimaryFrame()) + continue; - nsCOMPtr<nsIPresShell> presShell = GetPresShell(); - NS_ENSURE_TRUE(presShell,); - - if (!IsContentLoaded()) { - // Still loading document - if (mAccessibleCache.Count() <= 1) { - // Still loading and no accessibles has yet been created other than this - // doc accessible. In this case we optimize - // by not firing SHOW/HIDE/REORDER events for every document mutation - // caused by page load, since AT is not going to want to grab the - // document and listen to these changes until after the page is first loaded - // Leave early, and ensure mAccChildCount stays uninitialized instead of 0, - // which it is if anyone asks for its children right now. - InvalidateChildren(); - return; + nsAccessible* accessible = aIsInsert ? + GetAccService()->GetAccessibleInWeakShell(node, mWeakShell) : + GetCachedAccessible(node); + + if (!accessible) { + updateFlags |= UpdateTreeInternal(aContainer, node->GetFirstChild(), + nsnull, aIsInsert, aFireAllEvents, + aFromUserInput); + continue; } - nsIEventStateManager *esm = presShell->GetPresContext()->EventStateManager(); - NS_ENSURE_TRUE(esm,); + updateFlags |= eAccessible; + + // Fire show/hide event. + if (aFireAllEvents) { + nsRefPtr<AccEvent> event; + if (aIsInsert) + event = new AccShowEvent(accessible, node, aFromUserInput); + else + event = new AccHideEvent(accessible, node, aFromUserInput); + + if (event) + FireDelayedAccessibleEvent(event); + } - if (!esm->IsHandlingUserInputExternal()) { - // Changes during page load, but not caused by user input - // Just invalidate accessible hierarchy and return, - // otherwise the page load time slows down way too much - nsAccessible *containerAccessible = - GetAccService()->GetCachedContainerAccessible(childNode); - if (!containerAccessible) { - containerAccessible = this; + if (aIsInsert) { + const nsRoleMapEntry* roleMapEntry = accessible->GetRoleMapEntry(); + if (roleMapEntry) { + if (roleMapEntry->role == nsIAccessibleRole::ROLE_MENUPOPUP) { + // Fire EVENT_MENUPOPUP_START if ARIA menu appears. + FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, + node, AccEvent::eRemoveDupes, aFromUserInput); + + } else if (roleMapEntry->role == nsIAccessibleRole::ROLE_ALERT) { + // Fire EVENT_ALERT if ARIA alert appears. + updateFlags = eAlertAccessible; + FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_ALERT, node, + AccEvent::eRemoveDupes, aFromUserInput); + } } - containerAccessible->InvalidateChildren(); - return; - } - // else: user input, so we must fall through and for full handling, - // e.g. fire the mutation events. Note: user input could cause DOM_CREATE - // during page load if user typed into an input field or contentEditable area - } - - // Update last change state information - nsAccessible *childAccessible = GetCachedAccessible(childNode); - -#ifdef DEBUG_A11Y - nsAutoString localName; - if (aChild) - aChild->NodeInfo()->GetName(localName); - const char *hasAccessible = childAccessible ? " (acc)" : ""; - if (aChangeType == nsIAccessibilityService::FRAME_HIDE) - printf("[Hide %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible); - else if (aChangeType == nsIAccessibilityService::FRAME_SHOW) - printf("[Show %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible); - else if (aChangeType == nsIAccessibilityService::FRAME_SIGNIFICANT_CHANGE) - printf("[Layout change %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible); - else if (aChangeType == nsIAccessibilityService::NODE_APPEND) - printf("[Create %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible); - else if (aChangeType == nsIAccessibilityService::NODE_REMOVE) - printf("[Destroy %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible); - else if (aChangeType == nsIAccessibilityService::NODE_SIGNIFICANT_CHANGE) - printf("[Type change %s %s]\n", NS_ConvertUTF16toUTF8(localName).get(), hasAccessible); -#endif - - nsAccessible *containerAccessible = - GetAccService()->GetCachedContainerAccessible(childNode); - if (!containerAccessible) { - containerAccessible = this; - } - - if (!isShowing) { - // Fire EVENT_HIDE. - if (isHiding) { - if (aChild) { - nsIFrame *frame = aChild->GetPrimaryFrame(); - if (frame) { - nsIFrame *frameParent = frame->GetParent(); - if (!frameParent || !frameParent->GetStyleVisibility()->IsVisible()) { - // Ancestor already hidden or being hidden at the same time: - // don't process redundant hide event - // This often happens when visibility is cleared for node, - // which hides an entire subtree -- we get notified for each - // node in the subtree and need to collate the hide events ourselves. - return; - } - } + // If focused node has been shown then it means its frame was recreated + // while it's focused. Fire focus event on new focused accessible. If + // the queue contains focus event for this node then it's suppressed by + // this one. + if (node == gLastFocusedNode) { + FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_FOCUS, + node, AccEvent::eCoalesceFromSameDocument, + aFromUserInput); } - } - - // Fire an event if the accessible existed for node being hidden, otherwise - // for the first line accessible descendants. Fire before the accessible(s) - // away. - nsresult rv = FireShowHideEvents(childNode, PR_FALSE, - nsIAccessibleEvent::EVENT_HIDE, - eDelayedEvent, isAsynch); - if (NS_FAILED(rv)) - return; - } - - // 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. - - if (aChild && !isHiding) { - if (!isAsynch) { - // DOM already updated with new objects -- invalidate parent's children now - // For asynch we must wait until layout updates before we invalidate the children - containerAccessible->InvalidateChildren(); - } - - // 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. - - FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_SHOW, childNode, - AccEvent::eCoalesceFromSameSubtree, - isAsynch); - - // Check to see change occurred 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) { - FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_MENUPOPUP_START, - childNode, AccEvent::eRemoveDupes, - isAsynch); - } - - // Check to see if change occurred inside an alert, and fire an EVENT_ALERT if it did - nsIContent *ancestor = aChild; - while (PR_TRUE) { - if (roleMapEntry && roleMapEntry->role == nsIAccessibleRole::ROLE_ALERT) { - FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor, - AccEvent::eRemoveDupes, isAsynch); - break; - } - ancestor = ancestor->GetParent(); - if (!ancestor) - break; - - roleMapEntry = nsAccUtils::GetRoleMapEntry(ancestor); + } else { + // Update the tree for content removal. + aContainer->RemoveChild(accessible); + UncacheChildrenInSubtree(accessible); } } - FireValueChangeForTextFields(containerAccessible); - - // Fire an event so the MSAA clients know the children have changed. Also - // the event is used internally by MSAA part. + return updateFlags; +} - // We need to fire a delayed reorder event for the accessible parent of the - // changed node. We fire an unconditional reorder event if the changed node or - // one of its children is already accessible. In the case of show events, the - // accessible object might not be created yet for an otherwise accessible - // changed node (because its frame might not be constructed yet). In this case - // we fire a conditional reorder event, so that we will later check whether - // the changed node is accessible or has accessible children. - // Filtering/coalescing of these events happens during the queue flush. +void +nsDocAccessible::UncacheChildrenInSubtree(nsAccessible* aRoot) +{ + PRUint32 count = aRoot->GetCachedChildCount(); + for (PRUint32 idx = 0; idx < count; idx++) + UncacheChildrenInSubtree(aRoot->GetCachedChildAt(idx)); - PRBool isUnconditionalEvent = childAccessible || - aChild && nsAccUtils::HasAccessibleChildren(childNode); - - nsRefPtr<AccEvent> reorderEvent = - new AccReorderEvent(containerAccessible, isAsynch, isUnconditionalEvent, - aChild ? aChild : nsnull); - NS_ENSURE_TRUE(reorderEvent,); - - FireDelayedAccessibleEvent(reorderEvent); + if (aRoot->IsPrimaryForNode() && + mNodeToAccessibleMap.Get(aRoot->GetNode()) == aRoot) + mNodeToAccessibleMap.Remove(aRoot->GetNode()); } -nsresult -nsDocAccessible::FireShowHideEvents(nsINode *aNode, - PRBool aAvoidOnThisNode, - PRUint32 aEventType, - EEventFiringType aDelayedOrNormal, - PRBool aIsAsyncChange, - EIsFromUserInput aIsFromUserInput) +void +nsDocAccessible::ShutdownChildrenInSubtree(nsAccessible* aAccessible) { - NS_ENSURE_ARG(aNode); +#ifdef DEBUG + nsAccessible* incache = mAccessibleCache.GetWeak(aAccessible->UniqueID()); +#endif - nsAccessible *accessible = nsnull; - if (!aAvoidOnThisNode) { - if (aEventType == nsIAccessibleEvent::EVENT_HIDE) { - // Don't allow creation for accessibles when nodes going away - accessible = GetCachedAccessible(aNode); - } else { - // Allow creation of new accessibles for show events - accessible = GetAccService()->GetAccessible(aNode); + // Traverse through children and shutdown them before this accessible. When + // child gets shutdown then it removes itself from children array of its + //parent. Use jdx index to process the cases if child is not attached to the + // parent and as result doesn't remove itself from its children. + PRUint32 count = aAccessible->GetCachedChildCount(); + for (PRUint32 idx = 0, jdx = 0; idx < count; idx++) { + nsAccessible* child = aAccessible->GetCachedChildAt(jdx); + if (!child->IsBoundToParent()) { + NS_ERROR("Parent refers to a child, child doesn't refer to parent!"); + jdx++; } + + ShutdownChildrenInSubtree(child); } - if (accessible) { - // Found an accessible, so fire the show/hide on it and don't look further - // into this subtree. - nsRefPtr<AccEvent> event; - if (aDelayedOrNormal == eDelayedEvent && - aEventType == nsIAccessibleEvent::EVENT_HIDE) { - // Use AccHideEvent for delayed hide events to coalesce text change events - // caused by these hide events. - event = new AccHideEvent(accessible, accessible->GetNode(), - aIsAsyncChange, aIsFromUserInput); - - } else { - event = new AccEvent(aEventType, accessible, aIsAsyncChange, - aIsFromUserInput, - AccEvent::eCoalesceFromSameSubtree); - } - NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); + ShutdownAccessible(aAccessible); +} - if (aDelayedOrNormal == eDelayedEvent) - return FireDelayedAccessibleEvent(event); - - nsEventShell::FireEvent(event); - return NS_OK; - } - - // Could not find accessible to show hide yet, so fire on any - // accessible descendants in this subtree - PRUint32 count = aNode->GetChildCount(); - for (PRUint32 index = 0; index < count; index++) { - nsINode *childNode = aNode->GetChildAt(index); - nsresult rv = FireShowHideEvents(childNode, PR_FALSE, aEventType, - aDelayedOrNormal, aIsAsyncChange, - aIsFromUserInput); - NS_ENSURE_SUCCESS(rv, rv); - } - - return NS_OK; -}
--- a/accessible/src/base/nsDocAccessible.h +++ b/accessible/src/base/nsDocAccessible.h @@ -165,84 +165,86 @@ public: { return mChildDocuments.SafeElementAt(aIndex, nsnull); } /** * Non-virtual method to fire a delayed event after a 0 length timeout. * * @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 FireDelayedAccessibleEvent(PRUint32 aEventType, nsINode *aNode, AccEvent::EEventRule aAllowDupes = AccEvent::eRemoveDupes, - PRBool aIsAsynch = PR_FALSE, EIsFromUserInput aIsFromUserInput = eAutoDetect); /** * Fire accessible event after timeout. * * @param aEvent [in] the event to fire */ nsresult FireDelayedAccessibleEvent(AccEvent* aEvent); /** - * Find the accessible object in the accessibility cache that corresponds to - * the given node or the first ancestor of it that has an accessible object - * associated with it. Clear that accessible object's parent's cache of - * accessible children and remove the accessible object and any descendants - * from the accessible cache. Fires proper events. New accessible objects will - * be created and cached again on demand. - * - * @param aContent [in] the child that is changing - * @param aEvent [in] the event from nsIAccessibleEvent that caused - * the change. - */ - void InvalidateCacheSubtree(nsIContent *aContent, PRUint32 aEvent); - - /** - * Return the cached accessible by the given unique ID if it's in subtree of + * Return the cached accessible by the given DOM node if it's in subtree of * this document accessible or the document accessible itself, otherwise null. * - * @note the unique ID matches with the uniqueID attribute on nsIAccessNode - * - * @param aUniqueID [in] the unique ID used to cache the node. - * * @return the accessible object */ - nsAccessible* GetCachedAccessible(void *aUniqueID); + nsAccessible* GetCachedAccessible(nsINode* aNode); + + /** + * Return the cached accessible by the given unique ID within this document. + * + * @note the unique ID matches with the uniqueID() of nsAccessNode + * + * @param aUniqueID [in] the unique ID used to cache the node. + */ + nsAccessible* GetCachedAccessibleByUniqueID(void* aUniqueID) + { + return UniqueID() == aUniqueID ? + this : mAccessibleCache.GetWeak(aUniqueID); + } /** * Return the cached accessible by the given unique ID looking through * this and nested documents. */ - nsAccessible* GetCachedAccessibleInSubtree(void* aUniqueID); + nsAccessible* GetCachedAccessibleByUniqueIDInSubtree(void* aUniqueID); /** * Cache the accessible. * - * @param aUniquID [in] the unique identifier of accessible * @param aAccessible [in] accessible to cache * * @return true if accessible being cached, otherwise false */ - PRBool CacheAccessible(void *aUniqueID, nsAccessible *aAccessible); + PRBool CacheAccessible(nsAccessible *aAccessible); /** - * Remove the given accessible from document cache. + * Shutdown the accessible and remove it from document cache. */ - void RemoveAccessNodeFromCache(nsAccessible *aAccessible); + void ShutdownAccessible(nsAccessible *aAccessible); /** * Process the event when the queue of pending events is untwisted. Fire * accessible events as result of the processing. */ void ProcessPendingEvent(AccEvent* aEvent); + /** + * Update the accessible tree. + */ + void UpdateTree(nsIContent* aContainerNode, nsIContent* aStartChildNode, + nsIContent* aEndChildNode, PRBool aIsInsert); + + /** + * Recreate an accessible, results in hide/show events pair. + */ + void RecreateAccessible(nsINode* aNode); + protected: virtual void GetBoundsRect(nsRect& aRect, nsIFrame** aRelativeFrame); virtual nsresult AddEventListeners(); virtual nsresult RemoveEventListeners(); void AddScrollListener(); void RemoveScrollListener(); @@ -259,30 +261,16 @@ protected: * Remove the given document accessible from this document's child document * accessibles. */ void RemoveChildDocument(nsDocAccessible* aChildDocument) { mChildDocuments.RemoveElement(aChildDocument); } - /** - * Invalidate parent-child relations for any cached accessible in the DOM - * subtree. Accessible objects aren't destroyed. - * - * @param aStartNode [in] the root of the subrtee to invalidate accessible - * child/parent refs in - */ - void InvalidateChildrenInSubtree(nsINode *aStartNode); - - /** - * Traverse through DOM tree and shutdown accessible objects. - */ - void RefreshNodes(nsINode *aStartNode); - static void ScrollTimerCallback(nsITimer *aTimer, void *aClosure); /** * Fires accessible events when attribute is changed. * * @param aContent - node that attribute is changed for * @param aNameSpaceID - namespace of changed attribute * @param aAttribute - changed attribute @@ -306,72 +294,64 @@ protected: * @param aIsInserted the flag pointed whether removed or inserted * characters should be cause of event */ void FireTextChangeEventForText(nsIContent *aContent, CharacterDataChangeInfo* aInfo, PRBool aIsInserted); /** - * Create a text change event for a changed node. - * - * @param aContainerAccessible [in] the parent accessible for the node - * @param aChangeNode [in] the node that is being inserted or - * removed, or shown/hidden - * @param aAccessible [in] the accessible for that node, or nsnull - * if none exists - * @param aIsInserting [in] is aChangeNode being created or shown - * (vs. removed or hidden) - * @param aIsAsync [in] whether casual change is async - * @param aIsFromUserInput [in] the event is known to be from user input - */ - already_AddRefed<AccEvent> - CreateTextChangeEventForNode(nsAccessible *aContainerAccessible, - nsIContent *aChangeNode, - nsAccessible *aAccessible, - PRBool aIsInserting, - PRBool aIsAsynch, - EIsFromUserInput aIsFromUserInput = eAutoDetect); - - /** * Used to define should the event be fired on a delay. */ enum EEventFiringType { eNormalEvent, eDelayedEvent }; /** - * Fire show/hide events for either the current node if it has an accessible, - * or the first-line accessible descendants of the given node. - * - * @param aDOMNode [in] the given node - * @param aAvoidOnThisNode [in] call with PR_TRUE the first time to - * prevent event firing on root node for change - * @param aEventType [in] event type to fire an event - * @param aDelayedOrNormal [in] whether to fire the event on a delay - * @param aIsAsyncChange [in] whether casual change is async - * @param aIsFromUserInput [in] the event is known to be from user input - */ - nsresult FireShowHideEvents(nsINode *aDOMNode, PRBool aAvoidOnThisNode, - PRUint32 aEventType, - EEventFiringType aDelayedOrNormal, - PRBool aIsAsyncChange, - EIsFromUserInput aIsFromUserInput = eAutoDetect); - - /** * Fire a value change event for the the given accessible if it is a text * field (has a ROLE_ENTRY). */ void FireValueChangeForTextFields(nsAccessible *aAccessible); /** + * Helper for UpdateTree() method. Go down to DOM subtree and updates + * accessible tree. Return one of these flags. + */ + enum EUpdateTreeFlags { + eNoAccessible = 0, + eAccessible = 1, + eAlertAccessible = 2 + }; + + PRUint32 UpdateTreeInternal(nsAccessible* aContainer, + nsIContent* aStartNode, + nsIContent* aEndNode, + PRBool aIsInsert, + PRBool aFireEvents, + EIsFromUserInput aFromUserInput); + + /** + * Remove accessibles in subtree from node to accessible map. + */ + void UncacheChildrenInSubtree(nsAccessible* aRoot); + + /** + * Shutdown any cached accessible in the subtree. + * + * @param aAccessible [in] the root of the subrtee to invalidate accessible + * child/parent refs in + */ + void ShutdownChildrenInSubtree(nsAccessible *aAccessible); + + /** * Cache of accessibles within this document accessible. */ nsAccessibleHashtable mAccessibleCache; + NodeToAccessibleMap mNodeToAccessibleMap; nsCOMPtr<nsIDocument> mDocument; nsCOMPtr<nsITimer> mScrollWatchTimer; PRUint16 mScrollPositionChangedTicks; // Used for tracking scroll events protected: nsRefPtr<nsAccEventQueue> mEventQueue;
--- a/accessible/src/base/nsEventShell.cpp +++ b/accessible/src/base/nsEventShell.cpp @@ -63,22 +63,22 @@ nsEventShell::FireEvent(AccEvent* aEvent accessible->HandleAccEvent(aEvent); sEventTargetNode = nsnull; } void nsEventShell::FireEvent(PRUint32 aEventType, nsAccessible *aAccessible, - PRBool aIsAsynch, EIsFromUserInput aIsFromUserInput) + EIsFromUserInput aIsFromUserInput) { NS_ENSURE_TRUE(aAccessible,); nsRefPtr<AccEvent> event = new AccEvent(aEventType, aAccessible, - aIsAsynch, aIsFromUserInput); + aIsFromUserInput); FireEvent(event); } void nsEventShell::GetEventAttributes(nsINode *aNode, nsIPersistentProperties *aAttributes) { @@ -142,19 +142,19 @@ nsAccEventQueue::Push(AccEvent* aEvent) { mEvents.AppendElement(aEvent); // Filter events. CoalesceEvents(); // Associate text change with hide event if it wasn't stolen from hiding // siblings during coalescence. - AccHideEvent* hideEvent = downcast_accEvent(aEvent); - if (hideEvent && !hideEvent->mTextChangeEvent) - CreateTextChangeEventFor(hideEvent); + AccMutationEvent* showOrHideEvent = downcast_accEvent(aEvent); + if (showOrHideEvent && !showOrHideEvent->mTextChangeEvent) + CreateTextChangeEventFor(showOrHideEvent); // Process events. PrepareFlush(); } void nsAccEventQueue::Shutdown() { @@ -204,20 +204,20 @@ nsAccEventQueue::WillRefresh(mozilla::Ti NS_ASSERTION(length, "How did we get here without events to fire?"); for (PRUint32 index = 0; index < length; index ++) { AccEvent* accEvent = events[index]; if (accEvent->mEventRule != AccEvent::eDoNotEmit) { mDocument->ProcessPendingEvent(accEvent); - AccHideEvent* hideEvent = downcast_accEvent(accEvent); - if (hideEvent) { - if (hideEvent->mTextChangeEvent) - mDocument->ProcessPendingEvent(hideEvent->mTextChangeEvent); + AccMutationEvent* showOrhideEvent = downcast_accEvent(accEvent); + if (showOrhideEvent) { + if (showOrhideEvent->mTextChangeEvent) + mDocument->ProcessPendingEvent(showOrhideEvent->mTextChangeEvent); } } // No document means it was shut down during event handling by AT if (!mDocument) return; } @@ -262,156 +262,88 @@ nsAccEventQueue::CoalesceEvents() // that is sibling of target of newly appended event then apply its // event rule to the newly appended event. // XXX: deal with show events separately because they can't be // coalesced by accessible tree the same as hide events since target // accessibles can't be created at this point because of lazy frame // construction (bug 570275). - // Coalesce hide events for sibling targets. + // 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 show and reorder events by sibling targets. + // Coalesce earlier event for the same target. + if (thisEvent->mNode == tailEvent->mNode) { + thisEvent->mEventRule = AccEvent::eDoNotEmit; + return; + } + + // Coalesce events by sibling targets (this is a case for reorder + // events). if (thisEvent->mNode->GetNodeParent() == tailEvent->mNode->GetNodeParent()) { tailEvent->mEventRule = thisEvent->mEventRule; return; } - // Specifies if this event target can be descendant of tail node. - PRBool thisCanBeDescendantOfTail = PR_FALSE; - - // Coalesce depending on whether this event was coalesced or not. - if (thisEvent->mEventRule == AccEvent::eDoNotEmit) { - // If this event was coalesced then do not emit tail event iff tail - // event has the same target or its target is contained by this event - // target. Note, we don't need to check whether tail event target - // contains this event target since this event was coalesced already. - - // As well we don't need to apply the calculated rule for siblings of - // tail node because tail event rule was applied to possible tail - // node siblings while this event was coalesced. - - if (thisEvent->mNode == tailEvent->mNode) { - thisEvent->mEventRule = AccEvent::eDoNotEmit; - return; - } - - } else { - // If this event wasn't coalesced already then try to coalesce it or - // tail event. If this event is coalesced by tail event then continue - // search through events other events that can be coalesced by tail - // event. - - // If tail and this events have the same target then coalesce tail - // event because more early event we should fire early and then stop - // processing. - if (thisEvent->mNode == tailEvent->mNode) { - // Coalesce reorder events by special way since reorder events can - // be conditional events (be or not be fired in the end). - if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) { - CoalesceReorderEventsFromSameSource(thisEvent, tailEvent); - if (tailEvent->mEventRule != AccEvent::eDoNotEmit) - continue; - } - else { - tailEvent->mEventRule = AccEvent::eDoNotEmit; - } - - return; - } - - // This and tail events can be anywhere in the tree, make assumptions - // for mutation events. - - // More older show event target (thisNode) can't be contained by - // recent. - // show event target (tailNode), i.e be a descendant of tailNode. - // XXX: target of older show event caused by DOM node appending can be - // contained by target of recent show event caused by style change. - // XXX: target of older show event caused by style change can be - // contained by target of recent show event caused by style change. - thisCanBeDescendantOfTail = - tailEvent->mEventType != nsIAccessibleEvent::EVENT_SHOW || - tailEvent->mIsAsync; - } + // 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)) { - - if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) { - CoalesceReorderEventsFromSameTree(thisEvent, tailEvent); - if (tailEvent->mEventRule != AccEvent::eDoNotEmit) - continue; - - return; - } - tailEvent->mEventRule = AccEvent::eDoNotEmit; return; } -#ifdef DEBUG - if (tailEvent->mEventType == nsIAccessibleEvent::EVENT_HIDE && - nsCoreUtils::IsAncestorOf(thisEvent->mNode, tailEvent->mNode)) { - NS_NOTREACHED("More older hide event target is an ancestor of recent hide event target!"); - } -#endif - // If this node is a descendant of tail node then coalesce this event, - // check other events in the queue. - if (thisCanBeDescendantOfTail && - nsCoreUtils::IsAncestorOf(tailEvent->mNode, thisEvent->mNode)) { - - if (thisEvent->mEventType == nsIAccessibleEvent::EVENT_REORDER) { - CoalesceReorderEventsFromSameTree(tailEvent, thisEvent); - if (tailEvent->mEventRule != AccEvent::eDoNotEmit) - continue; - - return; - } - - // Do not emit thisEvent, also apply this result to sibling nodes of - // thisNode. + // 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; } -#ifdef DEBUG - if (!thisCanBeDescendantOfTail && - nsCoreUtils::IsAncestorOf(tailEvent->mNode, thisEvent->mNode)) { - NS_NOTREACHED("Older event target is a descendant of recent event target!"); - } -#endif - } // 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 @@ -458,51 +390,16 @@ nsAccEventQueue::ApplyToSiblings(PRUint3 accEvent->mEventRule != AccEvent::eDoNotEmit && accEvent->mNode && accEvent->mNode->GetNodeParent() == aNode->GetNodeParent()) { accEvent->mEventRule = aEventRule; } } } void -nsAccEventQueue::CoalesceReorderEventsFromSameSource(AccEvent* aAccEvent1, - AccEvent* aAccEvent2) -{ - // Do not emit event2 if event1 is unconditional. - AccReorderEvent* reorderEvent1 = downcast_accEvent(aAccEvent1); - if (reorderEvent1->IsUnconditionalEvent()) { - aAccEvent2->mEventRule = AccEvent::eDoNotEmit; - return; - } - - // Do not emit event1 if event2 is unconditional. - AccReorderEvent* reorderEvent2 = downcast_accEvent(aAccEvent2); - if (reorderEvent2->IsUnconditionalEvent()) { - aAccEvent1->mEventRule = AccEvent::eDoNotEmit; - return; - } - - // Do not emit event2 if event1 is valid, otherwise do not emit event1. - if (reorderEvent1->HasAccessibleInReasonSubtree()) - aAccEvent2->mEventRule = AccEvent::eDoNotEmit; - else - aAccEvent1->mEventRule = AccEvent::eDoNotEmit; -} - -void -nsAccEventQueue::CoalesceReorderEventsFromSameTree(AccEvent* aAccEvent, - AccEvent* aDescendantAccEvent) -{ - // Do not emit descendant event if this event is unconditional. - AccReorderEvent* reorderEvent = downcast_accEvent(aAccEvent); - if (reorderEvent->IsUnconditionalEvent()) - aDescendantAccEvent->mEventRule = AccEvent::eDoNotEmit; -} - -void nsAccEventQueue::CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent, AccHideEvent* aThisEvent) { // XXX: we need a way to ignore SplitNode and JoinNode() when they do not // affect the text within the hypertext. AccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent; if (!textEvent) @@ -518,17 +415,45 @@ nsAccEventQueue::CoalesceTextChangeEvent 0, PR_UINT32_MAX); textEvent->mStart -= textEvent->GetLength() - oldLen; } aTailEvent->mTextChangeEvent.swap(aThisEvent->mTextChangeEvent); } void -nsAccEventQueue::CreateTextChangeEventFor(AccHideEvent* aEvent) +nsAccEventQueue::CoalesceTextChangeEventsFor(AccShowEvent* aTailEvent, + AccShowEvent* aThisEvent) +{ + AccTextChangeEvent* textEvent = aThisEvent->mTextChangeEvent; + if (!textEvent) + return; + + if (aTailEvent->mAccessible->GetIndexInParent() == + aThisEvent->mAccessible->GetIndexInParent() + 1) { + // If tail target was inserted after this target, i.e. tail target is next + // sibling of this target. + aTailEvent->mAccessible->AppendTextTo(textEvent->mModifiedText, + 0, PR_UINT32_MAX); + + } else if (aTailEvent->mAccessible->GetIndexInParent() == + aThisEvent->mAccessible->GetIndexInParent() -1) { + // If tail target was inserted before this target, i.e. tail target is + // previous sibling of this target. + nsAutoString startText; + aTailEvent->mAccessible->AppendTextTo(startText, 0, PR_UINT32_MAX); + textEvent->mModifiedText = startText + textEvent->mModifiedText; + textEvent->mStart -= startText.Length(); + } + + aTailEvent->mTextChangeEvent.swap(aThisEvent->mTextChangeEvent); +} + +void +nsAccEventQueue::CreateTextChangeEventFor(AccMutationEvent* aEvent) { nsRefPtr<nsHyperTextAccessible> textAccessible = do_QueryObject( GetAccService()->GetContainerAccessible(aEvent->mNode, aEvent->mAccessible->GetWeakShell())); if (!textAccessible) return; // Don't fire event for the first html:br in an editor. @@ -546,12 +471,11 @@ nsAccEventQueue::CreateTextChangeEventFo PRInt32 offset = textAccessible->GetChildOffset(aEvent->mAccessible); nsAutoString text; aEvent->mAccessible->AppendTextTo(text, 0, PR_UINT32_MAX); if (text.IsEmpty()) return; aEvent->mTextChangeEvent = - new AccTextChangeEvent(textAccessible, offset, text, PR_FALSE, - aEvent->mIsAsync, + new AccTextChangeEvent(textAccessible, offset, text, aEvent->IsShow(), aEvent->mIsFromUserInput ? eFromUserInput : eNoUserInput); }
--- a/accessible/src/base/nsEventShell.h +++ b/accessible/src/base/nsEventShell.h @@ -61,21 +61,18 @@ public: */ static void FireEvent(AccEvent* aEvent); /** * Fire accessible event of the given type for the given accessible. * * @param aEventType [in] the event type * @param aAccessible [in] the event target - * @param aIsAsync [in, optional] specifies whether the origin change - * this event is fired owing to is async. */ static void FireEvent(PRUint32 aEventType, nsAccessible *aAccessible, - PRBool aIsAsynch = PR_FALSE, EIsFromUserInput aIsFromUserInput = eAutoDetect); /** * Append 'event-from-input' object attribute if the accessible event has * been fired just now for the given node. * * @param aNode [in] the DOM node * @param aAttributes [in, out] the attributes @@ -140,41 +137,36 @@ private: * @param aEventRule the event rule to be applied * (should be eDoNotEmit or eAllowDupes) */ void ApplyToSiblings(PRUint32 aStart, PRUint32 aEnd, PRUint32 aEventType, nsINode* aNode, AccEvent::EEventRule aEventRule); /** - * Do not emit one of two given reorder events fired for the same DOM node. - */ - void CoalesceReorderEventsFromSameSource(AccEvent* aAccEvent1, - AccEvent* 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(AccEvent* aAccEvent, AccEvent* aDescendantAccEvent); /** * Coalesce text change events caused by sibling hide events. */ void CoalesceTextChangeEventsFor(AccHideEvent* aTailEvent, AccHideEvent* aThisEvent); + void CoalesceTextChangeEventsFor(AccShowEvent* aTailEvent, + AccShowEvent* aThisEvent); /** - * Create text change event caused by hide event. When a node is hidden or - * removed, the text in an ancestor hyper text will lose characters. Create - * text change event unless the node is being removed or frame is being - * destroyed. + * Create text change event caused by hide or show event. When a node is + * hidden/removed or shown/appended, the text in an ancestor hyper text will + * lose or get new characters. */ - void CreateTextChangeEventFor(AccHideEvent* aEvent); + void CreateTextChangeEventFor(AccMutationEvent* aEvent); /** * Indicates whether we're waiting on a refresh notification from our * presshell to flush events */ PRBool mObservingRefresh; /**
--- a/accessible/src/base/nsRootAccessible.cpp +++ b/accessible/src/base/nsRootAccessible.cpp @@ -314,17 +314,16 @@ nsRootAccessible::GetCaretAccessible() return mCaretAccessible; } PRBool nsRootAccessible::FireAccessibleFocusEvent(nsAccessible *aAccessible, nsINode *aNode, nsIDOMEvent *aFocusEvent, PRBool aForceEvent, - PRBool aIsAsynch, EIsFromUserInput aIsFromUserInput) { // Implementors: only fire delayed/async events from this method. if (mCaretAccessible) { nsCOMPtr<nsIDOMNSEvent> nsevent(do_QueryInterface(aFocusEvent)); if (nsevent) { // Use the originally focused node where the selection lives. @@ -393,30 +392,30 @@ nsRootAccessible::FireAccessibleFocusEve nsAccessible *menuBarAccessible = nsAccUtils::GetAncestorWithRole(finalFocusAccessible, nsIAccessibleRole::ROLE_MENUBAR); if (menuBarAccessible) { mCurrentARIAMenubar = menuBarAccessible->GetNode(); if (mCurrentARIAMenubar) { nsRefPtr<AccEvent> menuStartEvent = new AccEvent(nsIAccessibleEvent::EVENT_MENU_START, - menuBarAccessible, PR_FALSE, aIsFromUserInput, + menuBarAccessible, aIsFromUserInput, AccEvent::eAllowDupes); if (menuStartEvent) { FireDelayedAccessibleEvent(menuStartEvent); } } } } } } else if (mCurrentARIAMenubar) { nsRefPtr<AccEvent> menuEndEvent = new AccEvent(nsIAccessibleEvent::EVENT_MENU_END, mCurrentARIAMenubar, - PR_FALSE, aIsFromUserInput, AccEvent::eAllowDupes); + aIsFromUserInput, AccEvent::eAllowDupes); if (menuEndEvent) { FireDelayedAccessibleEvent(menuEndEvent); } mCurrentARIAMenubar = nsnull; } nsCOMPtr<nsIContent> focusContent = do_QueryInterface(finalFocusNode); nsIFrame *focusFrame = nsnull; @@ -429,23 +428,21 @@ nsRootAccessible::FireAccessibleFocusEve focusFrame = focusContent->GetPrimaryFrame(); } NS_IF_RELEASE(gLastFocusedNode); gLastFocusedNode = finalFocusNode; NS_IF_ADDREF(gLastFocusedNode); - gLastFocusedFrameType = (focusFrame && focusFrame->GetStyleVisibility()->IsVisible()) ? focusFrame->GetType() : 0; - // Coalesce focus events from the same document, because DOM focus event might // be fired for the document node and then for the focused DOM element. FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_FOCUS, finalFocusNode, AccEvent::eCoalesceFromSameDocument, - aIsAsynch, aIsFromUserInput); + aIsFromUserInput); return PR_TRUE; } void nsRootAccessible::FireCurrentFocusEvent() { if (IsDefunct()) @@ -658,17 +655,16 @@ nsRootAccessible::HandleEvent(nsIDOMEven return NS_OK; } } } FireAccessibleFocusEvent(accessible, focusedItem, aEvent); } else if (eventType.EqualsLiteral("blur")) { NS_IF_RELEASE(gLastFocusedNode); - gLastFocusedFrameType = nsnull; gLastFocusedAccessiblesState = 0; } else if (eventType.EqualsLiteral("AlertActive")) { nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_ALERT, accessible); } else if (eventType.EqualsLiteral("popupshown")) { HandlePopupShownEvent(accessible); } @@ -727,26 +723,26 @@ nsRootAccessible::HandleEvent(nsIDOMEven break; } containerContent = containerContent->GetParent(); } } if (fireFocus) { // Always asynch, always from user input. FireAccessibleFocusEvent(accessible, targetNode, aEvent, PR_TRUE, - PR_TRUE, eFromUserInput); + eFromUserInput); } } - else if (eventType.EqualsLiteral("DOMMenuBarActive")) { // Always asynch, always from user input + else if (eventType.EqualsLiteral("DOMMenuBarActive")) { // Always from user input nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_START, - accessible, PR_TRUE, eFromUserInput); + accessible, eFromUserInput); } - else if (eventType.EqualsLiteral("DOMMenuBarInactive")) { // Always asynch, always from user input + else if (eventType.EqualsLiteral("DOMMenuBarInactive")) { // Always from user input nsEventShell::FireEvent(nsIAccessibleEvent::EVENT_MENU_END, - accessible, PR_TRUE, eFromUserInput); + accessible, eFromUserInput); FireCurrentFocusEvent(); } else if (eventType.EqualsLiteral("ValueChange")) { FireDelayedAccessibleEvent(nsIAccessibleEvent::EVENT_VALUE_CHANGE, targetNode, AccEvent::eRemoveDupes); } #ifdef DEBUG else if (eventType.EqualsLiteral("mouseover")) {
--- a/accessible/src/base/nsRootAccessible.h +++ b/accessible/src/base/nsRootAccessible.h @@ -101,17 +101,16 @@ public: * @param aForceEvent [in] fire a focus event even if the last focused * item was the same * @return boolean -- was a focus event actually fired */ PRBool FireAccessibleFocusEvent(nsAccessible *aFocusAccessible, nsINode *aFocusNode, nsIDOMEvent *aFocusEvent, PRBool aForceEvent = PR_FALSE, - PRBool aIsAsynch = PR_FALSE, EIsFromUserInput aIsFromUserInput = eAutoDetect); /** * Fire an accessible focus event for the current focused node, * if there is a focus. */ void FireCurrentFocusEvent();
--- a/accessible/src/base/nsTextEquivUtils.cpp +++ b/accessible/src/base/nsTextEquivUtils.cpp @@ -232,16 +232,21 @@ nsTextEquivUtils::AppendFromAccessibleCh return rv; } nsresult nsTextEquivUtils::AppendFromAccessible(nsAccessible *aAccessible, nsAString *aString) { + // Ignore hidden accessible for name computation. + nsIFrame* frame = aAccessible->GetFrame(); + if (!frame || !frame->GetStyleVisibility()->IsVisible()) + return NS_OK; + //XXX: is it necessary to care the accessible is not a document? if (aAccessible->IsContent()) { nsresult rv = AppendTextEquivFromTextContent(aAccessible->GetContent(), aString); if (rv != NS_OK_NO_NAME_CLAUSE_HANDLED) return rv; }
--- a/accessible/src/html/nsHTMLSelectAccessible.cpp +++ b/accessible/src/html/nsHTMLSelectAccessible.cpp @@ -660,16 +660,19 @@ nsHTMLSelectOptGroupAccessible::CacheChi //////////////////////////////////////////////////////////////////////////////// nsHTMLComboboxAccessible:: nsHTMLComboboxAccessible(nsIContent *aContent, nsIWeakReference *aShell) : nsAccessibleWrap(aContent, aShell) { } +//////////////////////////////////////////////////////////////////////////////// +// nsHTMLComboboxAccessible: nsAccessible + PRUint32 nsHTMLComboboxAccessible::NativeRole() { return nsIAccessibleRole::ROLE_COMBOBOX; } void nsHTMLComboboxAccessible::CacheChildren() @@ -854,31 +857,43 @@ NS_IMETHODIMP nsHTMLComboboxAccessible:: nsHTMLComboboxListAccessible:: nsHTMLComboboxListAccessible(nsIAccessible *aParent, nsIContent *aContent, nsIWeakReference *aShell) : nsHTMLSelectListAccessible(aContent, aShell) { } +//////////////////////////////////////////////////////////////////////////////// +// nsHTMLComboboxAccessible: nsAccessNode + nsIFrame* nsHTMLComboboxListAccessible::GetFrame() { nsIFrame* frame = nsHTMLSelectListAccessible::GetFrame(); if (frame) { nsIComboboxControlFrame* comboBox = do_QueryFrame(frame); if (comboBox) { return comboBox->GetDropDown(); } } return nsnull; } +bool +nsHTMLComboboxListAccessible::IsPrimaryForNode() const +{ + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsHTMLComboboxAccessible: nsAccessible + /** * As a nsHTMLComboboxListAccessible we can have the following states: * STATE_FOCUSED * STATE_FOCUSABLE * STATE_INVISIBLE * STATE_FLOATING */ nsresult @@ -894,24 +909,16 @@ nsHTMLComboboxListAccessible::GetStateIn if (comboFrame && comboFrame->IsDroppedDown()) *aState |= nsIAccessibleStates::STATE_FLOATING; else *aState |= nsIAccessibleStates::STATE_INVISIBLE; return NS_OK; } -NS_IMETHODIMP nsHTMLComboboxListAccessible::GetUniqueID(void **aUniqueID) -{ - // Since mContent is same for all tree item, use |this| pointer as the unique - // Id. - *aUniqueID = static_cast<void*>(this); - return NS_OK; -} - /** * Gets the bounds for the areaFrame. * Walks the Frame tree and checks for proper frames. */ void nsHTMLComboboxListAccessible::GetBoundsRect(nsRect& aBounds, nsIFrame** aBoundingFrame) { *aBoundingFrame = nsnull;
--- a/accessible/src/html/nsHTMLSelectAccessible.h +++ b/accessible/src/html/nsHTMLSelectAccessible.h @@ -221,20 +221,18 @@ class nsHTMLComboboxListAccessible : pub { public: nsHTMLComboboxListAccessible(nsIAccessible *aParent, nsIContent *aContent, nsIWeakReference* aShell); virtual ~nsHTMLComboboxListAccessible() {} - // nsIAccessible - NS_IMETHOD GetUniqueID(void **aUniqueID); - // nsAccessNode virtual nsIFrame* GetFrame(); + virtual bool IsPrimaryForNode() const; // nsAccessible virtual nsresult GetStateInternal(PRUint32 *aState, PRUint32 *aExtraState); virtual void GetBoundsRect(nsRect& aBounds, nsIFrame** aBoundingFrame); }; #endif
--- a/accessible/src/html/nsHTMLTextAccessible.cpp +++ b/accessible/src/html/nsHTMLTextAccessible.cpp @@ -330,32 +330,35 @@ nsHTMLLIAccessible::CacheChildren() nsHTMLListBulletAccessible:: nsHTMLListBulletAccessible(nsIContent* aContent, nsIWeakReference* aShell) : nsLeafAccessible(aContent, aShell) { mBulletText += ' '; // Otherwise bullets are jammed up against list text } -NS_IMETHODIMP -nsHTMLListBulletAccessible::GetUniqueID(void **aUniqueID) -{ - // Since mContent is same as for list item, use |this| pointer as the unique - // id. - *aUniqueID = static_cast<void*>(this); - return NS_OK; -} +//////////////////////////////////////////////////////////////////////////////// +// nsHTMLListBulletAccessible: nsAccessNode void nsHTMLListBulletAccessible::Shutdown() { mBulletText.Truncate(); nsLeafAccessible::Shutdown(); } +bool +nsHTMLListBulletAccessible::IsPrimaryForNode() const +{ + return false; +} + +//////////////////////////////////////////////////////////////////////////////// +// nsHTMLListBulletAccessible: nsAccessible + NS_IMETHODIMP nsHTMLListBulletAccessible::GetName(nsAString &aName) { aName.Truncate(); if (IsDefunct()) return NS_ERROR_FAILURE;
--- a/accessible/src/html/nsHTMLTextAccessible.h +++ b/accessible/src/html/nsHTMLTextAccessible.h @@ -127,24 +127,22 @@ public: /** * Used for bullet of HTML list item element (for example, HTML li). */ class nsHTMLListBulletAccessible : public nsLeafAccessible { public: nsHTMLListBulletAccessible(nsIContent* aContent, nsIWeakReference* aShell); - // nsIAccessNode - NS_IMETHOD GetUniqueID(void **aUniqueID); - // nsIAccessible NS_IMETHOD GetName(nsAString& aName); // nsAccessNode virtual void Shutdown(); + virtual bool IsPrimaryForNode() const; // nsAccessible virtual PRUint32 NativeRole(); virtual nsresult GetStateInternal(PRUint32 *aState, PRUint32 *aExtraState); virtual nsresult AppendTextTo(nsAString& aText, PRUint32 aStartOffset, PRUint32 aLength); protected:
--- a/accessible/src/msaa/nsAccessNodeWrap.cpp +++ b/accessible/src/msaa/nsAccessNodeWrap.cpp @@ -252,19 +252,17 @@ STDMETHODIMP nsAccessNodeWrap::get_nodeI *aNameSpaceID = IsContent() ? static_cast<short>(mContent->GetNameSpaceID()) : 0; // This is a unique ID for every content node. The 3rd party // accessibility application can compare this to the childID we // return for events such as focus events, to correlate back to // data nodes in their internal object model. - void *uniqueID; - GetUniqueID(&uniqueID); - *aUniqueID = - NS_PTR_TO_INT32(uniqueID); + *aUniqueID = - NS_PTR_TO_INT32(UniqueID()); *aNumChildren = GetNode()->GetChildCount(); } __except(FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } return S_OK; }
--- a/accessible/src/msaa/nsAccessibleWrap.cpp +++ b/accessible/src/msaa/nsAccessibleWrap.cpp @@ -1349,22 +1349,17 @@ nsAccessibleWrap::get_localizedExtendedS return E_NOTIMPL; } STDMETHODIMP nsAccessibleWrap::get_uniqueID(long *uniqueID) { __try { - void *id = nsnull; - nsresult rv = GetUniqueID(&id); - if (NS_FAILED(rv)) - return GetHRESULT(rv); - - *uniqueID = - reinterpret_cast<long>(id); + *uniqueID = - reinterpret_cast<long>(UniqueID()); return S_OK; } __except(nsAccessNodeWrap::FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { } return E_FAIL; } STDMETHODIMP nsAccessibleWrap::get_windowHandle(HWND *aWindowHandle) @@ -1570,70 +1565,63 @@ nsAccessibleWrap::FirePlatformEvent(AccE nsAccessible *accessible = aEvent->GetAccessible(); if (!accessible) return NS_OK; if (eventType == nsIAccessibleEvent::EVENT_TEXT_CARET_MOVED || eventType == nsIAccessibleEvent::EVENT_FOCUS) { UpdateSystemCaret(); + + } else if (eventType == nsIAccessibleEvent::EVENT_REORDER) { + // If the accessible children are changed then drop the IEnumVariant current + // position of the accessible. + UnattachIEnumVariant(); } - + PRInt32 childID = GetChildIDFor(accessible); // get the id for the accessible if (!childID) return NS_OK; // Can't fire an event without a child ID - // See if we're in a scrollable area with its own window - nsAccessible *newAccessible = nsnull; - if (eventType == nsIAccessibleEvent::EVENT_HIDE) { - // Don't use frame from current accessible when we're hiding that - // accessible. - newAccessible = accessible->GetCachedParent(); - } else { - newAccessible = accessible; + HWND hWnd = GetHWNDFor(accessible); + NS_ENSURE_TRUE(hWnd, NS_ERROR_FAILURE); + + nsAutoString tag; + nsCAutoString id; + nsIContent* cnt = accessible->GetContent(); + if (cnt) { + cnt->Tag()->ToString(tag); + nsIAtom* aid = cnt->GetID(); + if (aid) + aid->ToUTF8String(id); } - HWND hWnd = GetHWNDFor(newAccessible); - NS_ENSURE_TRUE(hWnd, NS_ERROR_FAILURE); - - // Gecko uses two windows for every scrollable area. One window contains - // scrollbars and the child window contains only the client area. - // Details of the 2 window system: - // * Scrollbar window: caret drawing window & return value for WindowFromAccessibleObject() - // * Client area window: text drawing window & MSAA event window +#ifdef DEBUG_A11Y + printf("\n\nMSAA event: event: %d, target: %s@id='%s', childid: %d, hwnd: %d\n\n", + eventType, NS_ConvertUTF16toUTF8(tag).get(), id.get(), + childID, hWnd); +#endif // Fire MSAA event for client area window. NotifyWinEvent(winEvent, hWnd, OBJID_CLIENT, childID); - - // If the accessible children are changed then drop the IEnumVariant current - // position of the accessible. - if (eventType == nsIAccessibleEvent::EVENT_REORDER) - UnattachIEnumVariant(); - return NS_OK; } //------- Helper methods --------- -PRInt32 nsAccessibleWrap::GetChildIDFor(nsIAccessible* aAccessible) +PRInt32 nsAccessibleWrap::GetChildIDFor(nsAccessible* aAccessible) { // A child ID of the window is required, when we use NotifyWinEvent, // so that the 3rd party application can call back and get the IAccessible // the event occurred on. - void *uniqueID = nsnull; - nsCOMPtr<nsIAccessNode> accessNode(do_QueryInterface(aAccessible)); - if (!accessNode) { - return 0; - } - accessNode->GetUniqueID(&uniqueID); - // Yes, this means we're only compatibible with 32 bit // MSAA is only available for 32 bit windows, so it's okay - return - NS_PTR_TO_INT32(uniqueID); + // XXX: bug 606080 + return aAccessible ? - NS_PTR_TO_INT32(aAccessible->UniqueID()) : 0; } HWND nsAccessibleWrap::GetHWNDFor(nsAccessible *aAccessible) { if (!aAccessible) return 0;
--- a/accessible/src/msaa/nsAccessibleWrap.h +++ b/accessible/src/msaa/nsAccessibleWrap.h @@ -305,17 +305,17 @@ public: // construction, destruction VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr); // nsAccessible virtual nsresult HandleAccEvent(AccEvent* aEvent); // Helper methods - static PRInt32 GetChildIDFor(nsIAccessible* aAccessible); + static PRInt32 GetChildIDFor(nsAccessible* aAccessible); static HWND GetHWNDFor(nsAccessible *aAccessible); static HRESULT ConvertToIA2Attributes(nsIPersistentProperties *aAttributes, BSTR *aIA2Attributes); /** * System caret support: update the Windows caret position. * The system caret works more universally than the MSAA caret * For example, Window-Eyes, JAWS, ZoomText and Windows Tablet Edition use it
--- a/accessible/src/msaa/nsDocAccessibleWrap.cpp +++ b/accessible/src/msaa/nsDocAccessibleWrap.cpp @@ -104,17 +104,17 @@ nsDocAccessibleWrap::GetXPAccessibleFor( { // If lVal negative then it is treated as child ID and we should look for // accessible through whole accessible subtree including subdocuments. // Otherwise we treat lVal as index in parent. if (aVarChild.vt == VT_I4 && aVarChild.lVal < 0) { // Convert child ID to unique ID. void* uniqueID = reinterpret_cast<void*>(-aVarChild.lVal); - return GetCachedAccessibleInSubtree(uniqueID); + return GetCachedAccessibleByUniqueIDInSubtree(uniqueID); } return nsAccessibleWrap::GetXPAccessibleFor(aVarChild); } STDMETHODIMP nsDocAccessibleWrap::get_URL(/* [out] */ BSTR __RPC_FAR *aURL) { __try {
--- a/accessible/src/xul/nsXULTreeAccessible.cpp +++ b/accessible/src/xul/nsXULTreeAccessible.cpp @@ -504,25 +504,23 @@ nsXULTreeAccessible::InvalidateCache(PRI // Fire destroy event for removed tree items and delete them from caches. for (PRInt32 rowIdx = aRow; rowIdx < aRow - aCount; rowIdx++) { void* key = reinterpret_cast<void*>(rowIdx); nsAccessible *accessible = mAccessibleCache.GetWeak(key); if (accessible) { nsRefPtr<AccEvent> event = - new AccEvent(nsIAccessibleEvent::EVENT_HIDE, accessible, PR_FALSE); + new AccEvent(nsIAccessibleEvent::EVENT_HIDE, accessible); nsEventShell::FireEvent(event); - accessible->Shutdown(); - - // Remove accessible from document cache and tree cache. + // Shutdown and remove accessible from document cache and tree cache. nsDocAccessible *docAccessible = GetDocAccessible(); if (docAccessible) - docAccessible->RemoveAccessNodeFromCache(accessible); + docAccessible->ShutdownAccessible(accessible); mAccessibleCache.Remove(key); } } // We dealt with removed tree items already however we may keep tree items // having row indexes greater than row count. We should remove these dead tree // items silently from caches. @@ -534,22 +532,20 @@ nsXULTreeAccessible::InvalidateCache(PRI PRInt32 oldRowCount = newRowCount - aCount; for (PRInt32 rowIdx = newRowCount; rowIdx < oldRowCount; ++rowIdx) { void *key = reinterpret_cast<void*>(rowIdx); nsAccessible *accessible = mAccessibleCache.GetWeak(key); if (accessible) { - accessible->Shutdown(); - - // Remove accessible from document cache and tree cache. + // Shutdown and remove accessible from document cache and tree cache. nsDocAccessible *docAccessible = GetDocAccessible(); if (docAccessible) - docAccessible->RemoveAccessNodeFromCache(accessible); + docAccessible->ShutdownAccessible(accessible); mAccessibleCache.Remove(key); } } } void nsXULTreeAccessible::TreeViewInvalidated(PRInt32 aStartRow, PRInt32 aEndRow, @@ -605,28 +601,28 @@ nsXULTreeAccessible::TreeViewChanged() { if (IsDefunct()) return; // Fire only notification destroy/create events on accessible tree to lie to // AT because it should be expensive to fire destroy events for each tree item // in cache. nsRefPtr<AccEvent> eventDestroy = - new AccEvent(nsIAccessibleEvent::EVENT_HIDE, this, PR_FALSE); + new AccEvent(nsIAccessibleEvent::EVENT_HIDE, this); if (!eventDestroy) return; FirePlatformEvent(eventDestroy); ClearCache(mAccessibleCache); mTree->GetView(getter_AddRefs(mTreeView)); nsRefPtr<AccEvent> eventCreate = - new AccEvent(nsIAccessibleEvent::EVENT_SHOW, this, PR_FALSE); + new AccEvent(nsIAccessibleEvent::EVENT_SHOW, this); if (!eventCreate) return; FirePlatformEvent(eventCreate); } //////////////////////////////////////////////////////////////////////////////// // nsXULTreeAccessible: protected implementation @@ -658,28 +654,16 @@ nsXULTreeItemAccessibleBase:: //////////////////////////////////////////////////////////////////////////////// // nsXULTreeItemAccessibleBase: nsISupports implementation NS_IMPL_ISUPPORTS_INHERITED1(nsXULTreeItemAccessibleBase, nsAccessible, nsXULTreeItemAccessibleBase) //////////////////////////////////////////////////////////////////////////////// -// nsXULTreeItemAccessibleBase: nsIAccessNode implementation - -NS_IMETHODIMP -nsXULTreeItemAccessibleBase::GetUniqueID(void **aUniqueID) -{ - // Since mContent is same for all tree items and tree itself, use |this| - // pointer as the unique ID. - *aUniqueID = static_cast<void*>(this); - return NS_OK; -} - -//////////////////////////////////////////////////////////////////////////////// // nsXULTreeItemAccessibleBase: nsIAccessible implementation NS_IMETHODIMP nsXULTreeItemAccessibleBase::GetFocusedChild(nsIAccessible **aFocusedChild) { NS_ENSURE_ARG_POINTER(aFocusedChild); *aFocusedChild = nsnull; @@ -883,16 +867,22 @@ nsXULTreeItemAccessibleBase::Shutdown() { mTree = nsnull; mTreeView = nsnull; mRow = -1; nsAccessibleWrap::Shutdown(); } +bool +nsXULTreeItemAccessibleBase::IsPrimaryForNode() const +{ + return false; +} + //////////////////////////////////////////////////////////////////////////////// // nsXULTreeItemAccessibleBase: nsAccessible public methods // nsIAccessible::groupPosition nsresult nsXULTreeItemAccessibleBase::GroupPosition(PRInt32 *aGroupLevel, PRInt32 *aSimilarItemsInGroup, PRInt32 *aPositionInGroup)
--- a/accessible/src/xul/nsXULTreeAccessible.h +++ b/accessible/src/xul/nsXULTreeAccessible.h @@ -176,19 +176,16 @@ public: nsXULTreeItemAccessibleBase(nsIContent *aContent, nsIWeakReference *aShell, nsAccessible *aParent, nsITreeBoxObject *aTree, nsITreeView *aTreeView, PRInt32 aRow); // nsISupports NS_DECL_ISUPPORTS_INHERITED - // nsIAccessNode - NS_IMETHOD GetUniqueID(void **aUniqueID); - // nsIAccessible NS_IMETHOD GetFocusedChild(nsIAccessible **aFocusedChild); NS_IMETHOD GetBounds(PRInt32 *aX, PRInt32 *aY, PRInt32 *aWidth, PRInt32 *aHeight); NS_IMETHOD SetSelected(PRBool aSelect); NS_IMETHOD TakeFocus(); @@ -202,16 +199,17 @@ public: NS_IMETHOD GetNumActions(PRUint8 *aCount); NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName); NS_IMETHOD DoAction(PRUint8 aIndex); // nsAccessNode virtual PRBool IsDefunct(); virtual void Shutdown(); + virtual bool IsPrimaryForNode() const; // nsAccessible virtual nsresult GetStateInternal(PRUint32 *aState, PRUint32 *aExtraState); virtual PRInt32 GetIndexInParent() { return mParent ? mParent->GetCachedChildCount() + mRow : -1; } // nsXULTreeItemAccessibleBase NS_DECLARE_STATIC_IID_ACCESSOR(NS_XULTREEITEMBASEACCESSIBLE_IMPL_CID)
--- a/accessible/src/xul/nsXULTreeGridAccessible.cpp +++ b/accessible/src/xul/nsXULTreeGridAccessible.cpp @@ -800,28 +800,16 @@ nsXULTreeGridCellAccessible(nsIContent * // nsXULTreeGridCellAccessible: nsISupports implementation NS_IMPL_ISUPPORTS_INHERITED2(nsXULTreeGridCellAccessible, nsLeafAccessible, nsIAccessibleTableCell, nsXULTreeGridCellAccessible) //////////////////////////////////////////////////////////////////////////////// -// nsXULTreeGridCellAccessible: nsIAccessNode implementation - -NS_IMETHODIMP -nsXULTreeGridCellAccessible::GetUniqueID(void **aUniqueID) -{ - NS_ENSURE_ARG_POINTER(aUniqueID); - *aUniqueID = static_cast<void*>(this); - - return NS_OK; -} - -//////////////////////////////////////////////////////////////////////////////// // nsXULTreeGridCellAccessible: nsIAccessible implementation NS_IMETHODIMP nsXULTreeGridCellAccessible::GetFocusedChild(nsIAccessible **aFocusedChild) { NS_ENSURE_ARG_POINTER(aFocusedChild); *aFocusedChild = nsnull; @@ -1119,16 +1107,22 @@ nsXULTreeGridCellAccessible::Init() if (type == nsITreeColumn::TYPE_CHECKBOX) mTreeView->GetCellValue(mRow, mColumn, mCachedTextEquiv); else mTreeView->GetCellText(mRow, mColumn, mCachedTextEquiv); return PR_TRUE; } +bool +nsXULTreeGridCellAccessible::IsPrimaryForNode() const +{ + return false; +} + //////////////////////////////////////////////////////////////////////////////// // nsXULTreeGridCellAccessible: nsAccessible public implementation nsresult nsXULTreeGridCellAccessible::GetAttributesInternal(nsIPersistentProperties *aAttributes) { NS_ENSURE_ARG_POINTER(aAttributes);
--- a/accessible/src/xul/nsXULTreeGridAccessible.h +++ b/accessible/src/xul/nsXULTreeGridAccessible.h @@ -136,19 +136,16 @@ public: nsXULTreeGridCellAccessible(nsIContent *aContent, nsIWeakReference *aShell, nsXULTreeGridRowAccessible *aRowAcc, nsITreeBoxObject *aTree, nsITreeView *aTreeView, PRInt32 aRow, nsITreeColumn* aColumn); // nsISupports NS_DECL_ISUPPORTS_INHERITED - // nsIAccessNode - NS_IMETHOD GetUniqueID(void **aUniqueID); - // nsIAccessible NS_IMETHOD GetFocusedChild(nsIAccessible **aFocusedChild); NS_IMETHOD GetName(nsAString& aName); NS_IMETHOD GetBounds(PRInt32 *aX, PRInt32 *aY, PRInt32 *aWidth, PRInt32 *aHeight); NS_IMETHOD GetNumActions(PRUint8 *aCount); @@ -156,16 +153,17 @@ public: NS_IMETHOD DoAction(PRUint8 aIndex); // nsIAccessibleTableCell NS_DECL_NSIACCESSIBLETABLECELL // nsAccessNode virtual PRBool IsDefunct(); virtual PRBool Init(); + virtual bool IsPrimaryForNode() const; // nsAccessible virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes); virtual PRUint32 NativeRole(); virtual nsresult GetStateInternal(PRUint32 *aState, PRUint32 *aExtraState); virtual PRInt32 GetIndexInParent() { return GetColumnIndex(); } // nsXULTreeGridCellAccessible
--- a/accessible/tests/mochitest/common.js +++ b/accessible/tests/mochitest/common.js @@ -226,16 +226,29 @@ function getAccessible(aAccOrElmOrID, aI function isAccessible(aAccOrElmOrID, aInterfaces) { return getAccessible(aAccOrElmOrID, aInterfaces, null, DONOTFAIL_IF_NO_ACC | DONOTFAIL_IF_NO_INTERFACE) ? true : false; } /** + * Return an accessible that contains the DOM node for the given identifier. + */ +function getContainerAccessible(aAccOrElmOrID) +{ + var node = getNode(aAccOrElmOrID); + if (!node) + return null; + + while ((node = node.parentNode) && !isAccessible(node)); + return node ? getAccessible(node) : null; +} + +/** * Return root accessible for the given identifier. */ function getRootAccessible(aAccOrElmOrID) { var acc = getAccessible(aAccOrElmOrID ? aAccOrElmOrID : document, [nsIAccessNode]); return acc ? acc.rootDocument.QueryInterface(nsIAccessible) : null; } @@ -522,16 +535,25 @@ function prettyName(aIdentifier) var msg = "[" + getNodePrettyName(acc.DOMNode); try { msg += ", role: " + roleToString(acc.role); if (acc.name) msg += ", name: '" + acc.name + "'"; } catch (e) { msg += "defunct"; } + + if (acc) { + var exp = /native\s*@\s*(0x[a-f0-9]+)/g; + var match = exp.exec(acc.valueOf()); + if (match) + msg += ", address: " + match[1]; + else + msg += ", address: " + acc.valueOf(); + } msg += "]"; return msg; } if (aIdentifier instanceof nsIDOMNode) return getNodePrettyName(aIdentifier); @@ -551,19 +573,21 @@ function initialize() getService(nsIAccessibleRetrieval); } addLoadEvent(initialize); function getNodePrettyName(aNode) { try { - if (aNode.nodeType == nsIDOMNode.ELEMENT_NODE && aNode.hasAttribute("id")) - return " '" + aNode.getAttribute("id") + "' "; - if (aNode.nodeType == nsIDOMNode.DOCUMENT_NODE) return " 'document node' "; - return " '" + aNode.localName + " node' "; + var name = " '" + aNode.localName; + if (aNode.nodeType == nsIDOMNode.ELEMENT_NODE && aNode.hasAttribute("id")) + name += "@id='" + aNode.getAttribute("id") + "'"; + + name += " node' " + return name; } catch (e) { - return "no node info"; + return "' no node info '"; } }
--- a/accessible/tests/mochitest/events.js +++ b/accessible/tests/mochitest/events.js @@ -3,16 +3,18 @@ const EVENT_ALERT = nsIAccessibleEvent.EVENT_ALERT; const EVENT_DOCUMENT_LOAD_COMPLETE = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_COMPLETE; const EVENT_DOCUMENT_RELOAD = nsIAccessibleEvent.EVENT_DOCUMENT_RELOAD; const EVENT_DOCUMENT_LOAD_STOPPED = nsIAccessibleEvent.EVENT_DOCUMENT_LOAD_STOPPED; const EVENT_HIDE = nsIAccessibleEvent.EVENT_HIDE; const EVENT_FOCUS = nsIAccessibleEvent.EVENT_FOCUS; const EVENT_NAME_CHANGE = nsIAccessibleEvent.EVENT_NAME_CHANGE; +const EVENT_MENUPOPUP_START = nsIAccessibleEvent.EVENT_MENUPOPUP_START; +const EVENT_MENUPOPUP_END = nsIAccessibleEvent.EVENT_MENUPOPUP_END; const EVENT_REORDER = nsIAccessibleEvent.EVENT_REORDER; const EVENT_SCROLLING_START = nsIAccessibleEvent.EVENT_SCROLLING_START; const EVENT_SELECTION_ADD = nsIAccessibleEvent.EVENT_SELECTION_ADD; const EVENT_SELECTION_WITHIN = nsIAccessibleEvent.EVENT_SELECTION_WITHIN; const EVENT_SHOW = nsIAccessibleEvent.EVENT_SHOW; const EVENT_STATE_CHANGE = nsIAccessibleEvent.EVENT_STATE_CHANGE; const EVENT_TEXT_CARET_MOVED = nsIAccessibleEvent.EVENT_TEXT_CARET_MOVED; const EVENT_TEXT_INSERTED = nsIAccessibleEvent.EVENT_TEXT_INSERTED; @@ -549,17 +551,19 @@ function eventQueue(aEventType) container.appendChild(text1); var styledNode = document.createElement(inlineTagName); if (aMatch) { styledNode.setAttribute("style", "color: blue;"); styledNode.textContent = "matched"; // Dump matched events into console. - dump("\n*****\nEQ matched: " + eventTypeToString(currType) + "\n*****\n"); + if (gA11yEventDumpToConsole) + dump("\n*****\nEQ matched: " + eventTypeToString(currType) + "\n*****\n"); + } else { styledNode.textContent = "expected"; } container.appendChild(styledNode); var info = " event, type: "; info += (typeof currType == "string") ? currType : eventTypeToString(currType); @@ -927,16 +931,19 @@ var gA11yEventObserver = } info += ". Target: " + prettyName(event.accessible); if (listenersArray) info += ". Listeners count: " + listenersArray.length; eventFromDumpArea = false; + + if (gA11yEventDumpToConsole) + dump("\n" + info + "\n"); dumpInfoToDOM(info); } } // Do not notify listeners if event is result of event log changes. if (!listenersArray || eventFromDumpArea) return;
--- a/accessible/tests/mochitest/events/Makefile.in +++ b/accessible/tests/mochitest/events/Makefile.in @@ -49,17 +49,18 @@ include $(topsrcdir)/config/rules.mk docload_wnd.html \ docload_wnd.xul \ focus.html \ scroll.html \ test_aria_alert.html \ test_aria_statechange.html \ test_attrs.html \ test_caretmove.html \ - $(warning test_coalescence.html temporarily disabled) \ + test_coalescence.html \ + test_contextmenu.html \ test_docload.html \ test_docload.xul \ test_dragndrop.html \ test_flush.html \ test_focus.html \ test_focus.xul \ test_focus_name.html \ test_focusdoc.html \
--- a/accessible/tests/mochitest/events/test_coalescence.html +++ b/accessible/tests/mochitest/events/test_coalescence.html @@ -359,16 +359,21 @@ </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><br> + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275 </a> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"> </pre> <div id="eventdump"></div>
new file mode 100644 --- /dev/null +++ b/accessible/tests/mochitest/events/test_contextmenu.html @@ -0,0 +1,141 @@ +<html> + +<head> + <title>Context menu tests</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="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../states.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function showContextMenu(aID) + { + this.DOMNode = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_MENUPOPUP_START, getContextMenuNode()), + ]; + + this.invoke = function showContextMenu_invoke() + { + synthesizeMouse(this.DOMNode, 4, 4, { type: "contextmenu", button: 2 }); + } + + this.getID = function showContextMenu_getID() + { + return "show context menu"; + } + } + + function selectMenuItem() + { + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, getFocusedMenuItem) + ]; + + this.invoke = function selectMenuItem_invoke() + { + synthesizeKey("VK_DOWN", { }); + } + + this.getID = function selectMenuItem_getID() + { + return "select first menuitem"; + } + } + + function closeContextMenu(aID) + { + this.eventSeq = [ + new invokerChecker(EVENT_MENUPOPUP_END, + getAccessible(getContextMenuNode())) + ]; + + this.invoke = function closeContextMenu_invoke() + { + synthesizeKey("VK_ESCAPE", { }); + } + + this.getID = function closeContextMenu_getID() + { + return "close context menu"; + } + } + + function getContextMenuNode() + { + return getRootAccessible().DOMDocument. + getElementById("contentAreaContextMenu"); + } + + function getFocusedMenuItem() + { + var menu = getAccessible(getAccessible(getContextMenuNode())); + for (var idx = 0; idx < menu.childCount; idx++) { + var item = menu.getChildAt(idx); + + if (hasState(item, STATE_FOCUSED)) + return getAccessible(item, [nsIAccessNode]); + } + return null; + } + + //////////////////////////////////////////////////////////////////////////// + // Do tests + + var gQueue = null; + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + function doTests() + { + gQueue = new eventQueue(); + + gQueue.push(new showContextMenu("input")); + gQueue.push(new selectMenuItem()); + gQueue.push(new closeContextMenu()); + + 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=580535" + title="Broken accessibility in context menus"> + Mozilla Bug 580535 + </a><br> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <input id="input"> + + <div id="eventdump"></div> +</body> +</html>
--- a/accessible/tests/mochitest/events/test_focus.html +++ b/accessible/tests/mochitest/events/test_focus.html @@ -53,21 +53,44 @@ ]; this.getID = function focusElmWhileSubdocIsFocused_getID() { return "Focus element while subdocument is focused " + prettyName(aID); } } + function showNFocusAlertDialog() + { + this.ID = "alertdialog"; + this.DOMNode = getNode(this.ID); + + this.invoke = function showNFocusAlertDialog_invoke() + { + document.getElementById(this.ID).style.display = 'block'; + document.getElementById(this.ID).focus(); + todo(false, "Enable show event handling when bug 422744 is fixed."); + } + + this.eventSeq = [ + new invokerChecker(EVENT_FOCUS, this.DOMNode), + // new invokerChecker(EVENT_SHOW, this.DOMNode) + ]; + + this.getID = function showNFocusAlertDialog_getID() + { + return "Show and focus alert dialog " + prettyName(this.ID); + } + } + /** * Do tests. */ - // gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpID = "eventdump"; // debug stuff var gQueue = null; function doTests() { gQueue = new eventQueue(EVENT_FOCUS); gQueue.push(new synthFocus("editablearea")); @@ -77,16 +100,18 @@ gQueue.push(new openCloseDialog("button")); var frameNode = getNode("editabledoc"); gQueue.push(new synthFocusOnFrame(frameNode)); gQueue.push(new openCloseDialog(frameNode.contentDocument)); gQueue.push(new focusElmWhileSubdocIsFocused("button")); + gQueue.push(new showNFocusAlertDialog()); + gQueue.invoke(); // Will call SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); addA11yLoadEvent(doTests); </script> </head> @@ -97,21 +122,33 @@ title="focus is not fired for focused document when switching between windows"> Mozilla Bug 551679 </a> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=352220" title=" Inconsistent focus events when returning to a document frame"> Mozilla Bug 352220 </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=580464" + title="Accessible focus incorrect after JS focus() but correct after switching apps or using menu bar"> + Mozilla Bug 580464 + </a> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"> </pre> <div id="editablearea" contentEditable="true">editable area</div> <input id="textbox"> <button id="button">button</button> <iframe id="editabledoc" src="focus.html"></iframe> + <div id="alertdialog" style="display: none" tabindex="-1" role="alertdialog" aria-labelledby="title2" aria-describedby="desc2"> + <div id="title2">Blah blah</div> + <div id="desc2">Woof woof woof.</div> + <button>Close</button> + </div> + + <div id="eventdump"></div> </body> </html>
--- a/accessible/tests/mochitest/events/test_mutation.html +++ b/accessible/tests/mochitest/events/test_mutation.html @@ -225,26 +225,27 @@ */ function cloneAndReplaceInDOM(aNodeOrID) { this.__proto__ = new mutateA11yTree(aNodeOrID, kHideAndShowEvents, false); this.invoke = function cloneAndReplaceInDOM_invoke() { - var newElm = this.DOMNode.cloneNode(true); - newElm.removeAttribute('id'); - this.eventSeq[1][1] = newElm; - this.DOMNode.parentNode.replaceChild(newElm, this.DOMNode); + this.DOMNode.parentNode.replaceChild(this.newElm, this.DOMNode); } this.getID = function cloneAndReplaceInDOM_getID() { return aNodeOrID + " clone and replace in DOM."; } + + this.newElm = this.DOMNode.cloneNode(true); + this.newElm.removeAttribute('id'); + this.setTarget(kShowEvent, this.newElm); } /** * Target getters. */ function getFirstChild(aNode) { return [aNode.firstChild]; @@ -274,49 +275,36 @@ { return aNode.parentNode; } /** * Do tests. */ var gQueue = null; - // gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpID = "eventdump"; // debug stuff function doTests() { gQueue = new eventQueue(); // Show/hide events by changing of display style of accessible DOM node // from 'inline' to 'none', 'none' to 'inline'. var id = "link1"; getAccessible(id); // ensure accessible is created gQueue.push(new changeStyle(id, "display", "none", kHideEvents)); gQueue.push(new changeStyle(id, "display", "inline", kShowEvents)); - // Show/hide events by changing of visibility style of accessible DOM node - // from 'visible' to 'hidden', 'hidden' to 'visible'. - var id = "link2"; - getAccessible(id); - gQueue.push(new changeStyle(id, "visibility", "hidden", kHideEvents)); - gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents)); - // Show/hide events by changing of display style of accessible DOM node // from 'inline' to 'block', 'block' to 'inline'. var id = "link3"; getAccessible(id); // ensure accessible is created gQueue.push(new changeStyle(id, "display", "block", kHideAndShowEvents)); gQueue.push(new changeStyle(id, "display", "inline", kHideAndShowEvents)); - // Show/hide events by changing of visibility style of accessible DOM node - // from 'collapse' to 'visible', 'visible' to 'collapse'. - var id = "link4"; - gQueue.push(new changeStyle(id, "visibility", "visible", kShowEvents)); - gQueue.push(new changeStyle(id, "visibility", "collapse", kHideEvents)); - // Show/hide events by adding new accessible DOM node and removing old one. var id = "link5"; gQueue.push(new cloneAndAppendToDOM(id)); gQueue.push(new removeFromDOM(id)); // No show/hide events by adding new not accessible DOM node and removing // old one, no reorder event for their parent. var id = "child1"; @@ -342,46 +330,48 @@ // Hide events for accessible children of unaccessible removed DOM node // and reorder event for its parent. gQueue.push(new removeFromDOM("child4", kHideEvents, getNEnsureChildren, getParent)); // Show/hide events by creating new accessible DOM node and replacing // old one. - // XXX: bug 472810 - // gQueue.push(new cloneAndReplaceInDOM("link6")); + getAccessible("link6"); // ensure accessible is created + gQueue.push(new cloneAndReplaceInDOM("link6")); // Show/hide events by changing class name on the parent node. gQueue.push(new changeClass("container2", "link7", "", kShowEvents)); gQueue.push(new changeClass("container2", "link7", "displayNone", kHideEvents)); - gQueue.push(new changeClass("container3", "link8", "", kShowEvents)); - gQueue.push(new changeClass("container3", "link8", "visibilityHidden", - kHideEvents)); 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=469985" title=" turn the test from bug 354745 into mochitest"> Mozilla Bug 469985 - </a> + </a><br> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=472662" title="no reorder event when html:link display property is changed from 'none' to 'inline'"> Mozilla Bug 472662 + </a><br> + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275 </a> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"> </pre> <div id="eventdump"></div>
--- a/accessible/tests/mochitest/events/test_text.html +++ b/accessible/tests/mochitest/events/test_text.html @@ -79,32 +79,46 @@ { return "Remove inaccessible span containing accessible nodes" + prettyName(aID); } } /** * Insert inaccessible child node containing accessibles. */ - function insertChildSpan(aID) + function insertChildSpan(aID, aInsertAllTogether) { this.__proto__ = new textInsertInvoker(aID, 0, 5, "33322"); this.invoke = function insertChildSpan_invoke() { // <span><span>333</span><span>22</span></span> - var topSpan = document.createElement("span"); - var fSpan = document.createElement("span"); - fSpan.textContent = "333"; - topSpan.appendChild(fSpan); - var sSpan = document.createElement("span"); - sSpan.textContent = "22"; - topSpan.appendChild(sSpan); + if (aInsertAllTogether) { + var topSpan = document.createElement("span"); + var fSpan = document.createElement("span"); + fSpan.textContent = "333"; + topSpan.appendChild(fSpan); + var sSpan = document.createElement("span"); + sSpan.textContent = "22"; + topSpan.appendChild(sSpan); + + this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]); - this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]); + } else { + var topSpan = document.createElement("span"); + this.DOMNode.insertBefore(topSpan, this.DOMNode.childNodes[0]); + + var fSpan = document.createElement("span"); + fSpan.textContent = "333"; + topSpan.appendChild(fSpan); + + var sSpan = document.createElement("span"); + sSpan.textContent = "22"; + topSpan.appendChild(sSpan); + } } this.getID = function insertChildSpan_getID() { return "Insert inaccessible span containing accessibles" + prettyName(aID); } } @@ -266,17 +280,18 @@ function doTests() { gQueue = new eventQueue(); // Text remove event on inaccessible child HTML span removal containing // accessible text nodes. gQueue.push(new removeChildSpan("p")); - gQueue.push(new insertChildSpan("p")); + gQueue.push(new insertChildSpan("p"), true); + gQueue.push(new insertChildSpan("p"), false); // Remove embedded character. gQueue.push(new removeChildDiv("div")); gQueue.push(new insertChildDiv("div")); // Remove all children. var text = kEmbedChar + "txt" + kEmbedChar; gQueue.push(new removeChildren("div2", true, 0, 5, text)); @@ -318,16 +333,21 @@ title="Coalesce text events on nodes removal"> Mozilla Bug 574003 </a> <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=575052" title="Cache text offsets within hypertext accessible"> Mozilla Bug 575052 </a> + <a target="_blank" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275" + title="Rework accessible tree update code"> + Mozilla Bug 570275 + </a> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"> </pre> <div id="eventdump"></div> <p id="p"><span><span>333</span><span>22</span></span>1111</p>
--- a/accessible/tests/mochitest/role.js +++ b/accessible/tests/mochitest/role.js @@ -10,16 +10,17 @@ const ROLE_BUTTONDROPDOWNGRID = nsIAcces const ROLE_CAPTION = nsIAccessibleRole.ROLE_CAPTION; const ROLE_CELL = nsIAccessibleRole.ROLE_CELL; const ROLE_CHECKBUTTON = nsIAccessibleRole.ROLE_CHECKBUTTON; const ROLE_CHROME_WINDOW = nsIAccessibleRole.ROLE_CHROME_WINDOW; const ROLE_COMBOBOX = nsIAccessibleRole.ROLE_COMBOBOX; const ROLE_COMBOBOX_LIST = nsIAccessibleRole.ROLE_COMBOBOX_LIST; const ROLE_COMBOBOX_OPTION = nsIAccessibleRole.ROLE_COMBOBOX_OPTION; const ROLE_COLUMNHEADER = nsIAccessibleRole.ROLE_COLUMNHEADER; +const ROLE_DIALOG = nsIAccessibleRole.ROLE_DIALOG; const ROLE_DOCUMENT = nsIAccessibleRole.ROLE_DOCUMENT; const ROLE_EMBEDDED_OBJECT = nsIAccessibleRole.ROLE_EMBEDDED_OBJECT; const ROLE_ENTRY = nsIAccessibleRole.ROLE_ENTRY; const ROLE_FLAT_EQUATION = nsIAccessibleRole.ROLE_FLAT_EQUATION; const ROLE_FORM = nsIAccessibleRole.ROLE_FORM; const ROLE_GRAPHIC = nsIAccessibleRole.ROLE_GRAPHIC; const ROLE_GRID_CELL = nsIAccessibleRole.ROLE_GRID_CELL; const ROLE_GROUPING = nsIAccessibleRole.ROLE_GROUPING; @@ -95,8 +96,28 @@ function getRole(aAccOrElmOrID) try { role = acc.role; } catch(e) { ok(false, "Role for " + aAccOrElmOrID + " could not be retrieved!"); } return role; } + +/** + * Analogy of SimpleTest.is function used to check the role. + */ +function isRole(aIdentifier, aRole, aMsg) +{ + var role = getRole(aIdentifier); + if (role == - 1) + return; + + if (role == aRole) { + ok(true, aMsg); + return; + } + + var got = roleToString(role); + var expected = roleToString(aRole); + + ok(false, aMsg + "got '" + got + "', expected '" + expected + "'"); +}
--- a/accessible/tests/mochitest/states.js +++ b/accessible/tests/mochitest/states.js @@ -184,16 +184,26 @@ function getStates(aAccOrElmOrID) return [0, 0]; var state = {}, extraState = {}; acc.getState(state, extraState); return [state.value, extraState.value]; } +/** + * Return true if the accessible has given states. + */ +function hasState(aAccOrElmOrID, aState, aExtraState) +{ + var [state, exstate] = getStates(aAccOrElmOrID); + return (aState ? state & aState : true) && + (aExtraState ? exstate & aExtraState : true); +} + //////////////////////////////////////////////////////////////////////////////// // Private implementation details /** * Analogy of SimpleTest.is function used to compare states. */ function isState(aState1, aState2, aIsExtraStates, aMsg) {
--- a/accessible/tests/mochitest/test_editabletext_1.html +++ b/accessible/tests/mochitest/test_editabletext_1.html @@ -63,17 +63,17 @@ https://bugzilla.mozilla.org/show_bug.cg function doTest() { testEditable("input"); // testEditable("div"); XXX: bug 452599 var frame = document.getElementById("frame"); frame.contentDocument.designMode = "on"; testEditable(frame.contentDocument); - + SimpleTest.finish(); } SimpleTest.waitForExplicitFinish(); addA11yLoadEvent(doTest); </script> </head> <body>
--- a/accessible/tests/mochitest/tree/test_cssoverflow.html +++ b/accessible/tests/mochitest/tree/test_cssoverflow.html @@ -25,111 +25,105 @@ src="../events.js"></script> <script type="application/javascript"> //////////////////////////////////////////////////////////////////////////// // Invokers function focusAnchor(aID) { - this.DOMNode = getNode(aID); - this.link = getAccessible(this.DOMNode); - this.linkChild = this.link.firstChild; - this.linkChildNode = getAccessible(this.linkChild, [nsIAccessNode]).DOMNode; + this.linkNode = getNode(aID); + this.link = getAccessible(this.linkNode); this.eventSeq = [ -// new invokerChecker(EVENT_HIDE, this.linkChild), -// new invokerChecker(EVENT_SHOW, getAccessible, this.linkChildNode), - new invokerChecker(EVENT_FOCUS, this.link) - ]; - - this.unexpectedEventSeq = [ - new invokerChecker(EVENT_HIDE, this.link), - new invokerChecker(EVENT_SHOW, getAccessible, this.DOMNode) + new invokerChecker(EVENT_FOCUS, getAccessible, this.linkNode) ]; this.invoke = function focusAnchor_invoke() { - todo(false, "enable event hide/show events"); - getNode(aID).focus(); + this.linkNode.focus(); + } + + this.check = function focusAnchor_check(aEvent) + { + isnot(this.link, aEvent.accessible, + "Focus should be fired against new link accessible!"); } this.getID = function focusAnchor_getID() { return "focus a:focus{overflow:scroll} #1"; } } - function tabAnchor(aID, aPrevID) + function tabAnchor(aID) { - this.DOMNode = getNode(aID); - this.link = getAccessible(this.DOMNode); - this.linkChild = this.link.firstChild; - this.linkChildNode = getAccessible(this.linkChild, [nsIAccessNode]).DOMNode; - this.prevLink = getAccessible(aPrevID); - this.prevLinkNode = getAccessible(this.prevLink, [nsIAccessNode]).DOMNode; + this.linkNode = getNode(aID); + this.link = getAccessible(this.linkNode); this.eventSeq = [ - new invokerChecker(EVENT_HIDE, this.prevLink), - new invokerChecker(EVENT_SHOW, getAccessible, this.prevLinkNode), - new invokerChecker(EVENT_HIDE, this.linkChild), - new invokerChecker(EVENT_SHOW, getAccessible, this.linkChildNode), - new invokerChecker(EVENT_FOCUS, this.link) + new invokerChecker(EVENT_FOCUS, getAccessible, this.linkNode) ]; - this.unexpectedEventSeq = [ - new invokerChecker(EVENT_HIDE, this.link), - new invokerChecker(EVENT_SHOW, getAccessible, this.DOMNode) - ]; - - this.invoke = function focusAnchor_invoke() + this.invoke = function tabAnchor_invoke() { synthesizeKey("VK_TAB", { shiftKey: false }); } - this.getID = function focusAnchor_getID() + this.check = function tabAnchor_check(aEvent) + { + isnot(this.link, aEvent.accessible, + "Focus should be fired against new link accessible!"); + } + + this.getID = function tabAnchor_getID() { return "focus a:focus{overflow:scroll} #2"; } } //////////////////////////////////////////////////////////////////////////// // Do tests var gQueue = null; - // gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; function doTests() { gQueue = new eventQueue(); // CSS 'overflow: scroll' property setting and unsetting causes accessible - // recreation (and fire show/hide events) if the accessible is not - // focused. If it's focused its children are recreated. For example, - // focusing the HTML:a with ':focus {overflow: scroll; }' CSS style - // shouldn't cause of HTML:a accessible recreation. The same time blur - // makes its accessible to be recreated. - gQueue.push(new focusAnchor("a")) - gQueue.push(new tabAnchor("a2", "a")); + // recreation (and fire show/hide events). For example, the focus and + // blur of HTML:a with ':focus {overflow: scroll; }' CSS style causes its + // accessible recreation. The focus event should be fired on new + // accessible. + gQueue.push(new focusAnchor("a")); + gQueue.push(new tabAnchor("a2")); 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=591163" title="mochitest for bug 413777: focus the a:focus {overflow: scroll;} shouldn't recreate HTML a accessible"> Mozilla Bug 591163 </a><br> + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275 + </a><br> <p id="display"></p> <div id="content" style="display: none"></div> <pre id="test"> </pre> <div id="eventdump"></div> <div>
--- a/accessible/tests/mochitest/tree/test_tabbrowser.xul +++ b/accessible/tests/mochitest/tree/test_tabbrowser.xul @@ -66,16 +66,19 @@ } function testAccTree() { var tabsAccTree = { role: ROLE_PAGETABLIST, children: [ { + role: ROLE_PUSHBUTTON // tab scroll up button + }, + { role: ROLE_PAGETAB, children: [ { role: ROLE_PUSHBUTTON } ] }, { @@ -83,16 +86,19 @@ children: [ { role: ROLE_PUSHBUTTON } ] }, { role: ROLE_PUSHBUTTON + }, + { + role: ROLE_PUSHBUTTON // tab scroll down button } ] }; testAccessibleTree(getNode("tabbrowser").tabContainer, tabsAccTree); var tabboxAccTree = { role: ROLE_PANE, children: [
--- a/accessible/tests/mochitest/treeupdate/Makefile.in +++ b/accessible/tests/mochitest/treeupdate/Makefile.in @@ -43,12 +43,14 @@ VPATH = @srcdir@ relativesrcdir = accessible/treeupdate include $(DEPTH)/config/autoconf.mk include $(topsrcdir)/config/rules.mk _TEST_FILES =\ test_list_editabledoc.html \ test_list.html \ + test_recreation.html \ + test_tableinsubtree.html \ $(NULL) libs:: $(_TEST_FILES) $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/a11y/$(relativesrcdir)
--- a/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html +++ b/accessible/tests/mochitest/treeupdate/test_list_editabledoc.html @@ -73,20 +73,16 @@ // Test //gA11yEventDumpID = "eventdump"; // debug stuff var gQueue = null; function doTest() { - todo(false, "Enable when bug 570275 is fixed"); - SimpleTest.finish(); - return; - gQueue = new eventQueue(); gQueue.push(new addLi("list")); gQueue.invoke(); // SimpleTest.finish() will be called in the end } SimpleTest.waitForExplicitFinish();
new file mode 100644 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_recreation.html @@ -0,0 +1,166 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Test accessible recreation</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="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function recreateAccessible(aID, aWontBeAccessible) + { + this.node = getNode(aID); + this.accessible = + isAccessible(this.node) ? getAccessible(this.node) : null; + + this.eventSeq = [ ]; + + if (this.accessible) + this.eventSeq.push(new invokerChecker(EVENT_HIDE, + this.accessible)); + + if (!aWontBeAccessible) + this.eventSeq.push(new invokerChecker(EVENT_SHOW, getAccessible, + this.node)); + + this.eventSeq.push(new invokerChecker(EVENT_REORDER, + getContainerAccessible(this.node))); + + if (this.accessible) { + this.unexpectedEventSeq = [ + new invokerChecker(EVENT_SHOW, this.accessible) + ]; + } + } + + function changeAttr(aID, aAttr, aValue) + { + this.__proto__ = new recreateAccessible(aID); + + this.invoke = function changeAttr_invoke() + { + this.node.setAttribute(aAttr, aValue); + } + + this.getID = function changeAttr_getID() + { + return "change " + aAttr + "attribute for " + aID; + } + } + + function removeAttr(aID, aAttr) + { + this.__proto__ = new recreateAccessible(aID, true); + + this.invoke = function remvoeAttr_invoke() + { + this.node.removeAttribute(aAttr); + } + + this.getID = function remvoeAttr_getID() + { + return "remove " + aAttr + "attribute for " + aID; + } + } + + function changeRole(aID, aHasAccessible) + { + this.__proto__ = new changeAttr(aID, "role", "button"); + } + + function removeRole(aID) + { + this.__proto__ = new removeAttr(aID, "role"); + } + + function changeOnclick(aID) + { + this.__proto__ = new changeAttr(aID, "onclick", "alert(3);"); + } + + function changeHref(aID) + { + this.__proto__ = new changeAttr(aID, "href", "www"); + } + + function changeMultiselectable(aID) + { + this.__proto__ = new changeAttr(aID, "aria-multiselectable", "true"); + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() + { + gQueue = new eventQueue(); + + // make the accessible an inaccessible + gQueue.push(new changeRole("span")); + + // make the inaccessible an accessible + gQueue.push(new removeRole("span")); + + // recreate an accessible by role change + gQueue.push(new changeRole("div1")); + + // recreate an accessible by onclick change + gQueue.push(new changeOnclick("div2")); + + // recreate an accessible by href change + gQueue.push(new changeHref("anchor")); + + // recreate an accessible by aria-multiselectable change + gQueue.push(new changeMultiselectable("div3")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <span id="span">span</span> + <div id="div1">div</div> + <div id="div2">div</div> + <a id="anchor">anchor</a> + <div id="div3" role="listbox">list</div> + + <div id="eventdump"></div> +</body> +</html>
new file mode 100644 --- /dev/null +++ b/accessible/tests/mochitest/treeupdate/test_tableinsubtree.html @@ -0,0 +1,120 @@ +<!DOCTYPE html> +<html> + +<head> + <title>Table creation in ARIA dialog test</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="../common.js"></script> + <script type="application/javascript" + src="../role.js"></script> + <script type="application/javascript" + src="../events.js"></script> + + <script type="application/javascript"> + + //////////////////////////////////////////////////////////////////////////// + // Invokers + + function showARIADialog(aID) + { + this.node = getNode(aID); + + this.eventSeq = [ + new invokerChecker(EVENT_SHOW, this.node) + ]; + + this.invoke = function showARIADialog_invoke() + { + this.node.style.display = "block"; + getNode("input").value = "hello"; + getNode("cell").textContent = "cell1"; + getNode("input").focus(); + } + + this.finalCheck = function showARIADialog_finalCheck() + { + var tree = { + role: ROLE_DIALOG, + children: [ + { + role: ROLE_TABLE, + children: [ + { + role: ROLE_ROW, + children: [ + { + role: ROLE_CELL, + children: [ { role: ROLE_TEXT_LEAF } ] + }, + { + role: ROLE_CELL, + children: [ { role: ROLE_ENTRY } ] + } + ] + } + ] + } + ] + }; + testAccessibleTree(aID, tree); + } + + this.getID = function showARIADialog_getID() + { + return "show ARIA dialog"; + } + } + + //////////////////////////////////////////////////////////////////////////// + // Test + + //gA11yEventDumpID = "eventdump"; // debug stuff + //gA11yEventDumpToConsole = true; + + var gQueue = null; + + function doTest() + { + gQueue = new eventQueue(); + + // make the accessible an inaccessible + gQueue.push(new showARIADialog("dialog")); + + gQueue.invoke(); // SimpleTest.finish() will be called in the end + } + + SimpleTest.waitForExplicitFinish(); + addA11yLoadEvent(doTest); + </script> +</head> +<body> + + <a target="_blank" + title="Rework accessible tree update code" + href="https://bugzilla.mozilla.org/show_bug.cgi?id=570275"> + Mozilla Bug 570275 + </a> + + <p id="display"></p> + <div id="content" style="display: none"></div> + <pre id="test"> + </pre> + + <div id="dialog" role="dialog" style="display: none;"> + <table> + <tr><td id="cell"></td><td><input id="input"></td> + </table> + </div> + + <div id="eventdump"></div> +</body> +</html>
--- a/content/base/src/nsGenericElement.cpp +++ b/content/base/src/nsGenericElement.cpp @@ -135,21 +135,16 @@ #include "nsICSSStyleRule.h" /* For nsCSSSelectorList */ #include "nsCSSRuleProcessor.h" #include "nsRuleProcessorData.h" #ifdef MOZ_XUL #include "nsIXULDocument.h" #endif /* MOZ_XUL */ -#ifdef ACCESSIBILITY -#include "nsIAccessibilityService.h" -#include "nsIAccessibleEvent.h" -#endif /* ACCESSIBILITY */ - #include "nsCycleCollectionParticipant.h" #include "nsCCUncollectableMarker.h" #include "mozAutoDocUpdate.h" #include "nsCSSParser.h" #include "nsTPtrArray.h" #include "prprf.h" @@ -3646,31 +3641,16 @@ nsGenericElement::RemoveChildAt(PRUint32 } nsresult nsINode::doRemoveChildAt(PRUint32 aIndex, PRBool aNotify, nsIContent* aKid, nsAttrAndChildArray& aChildArray, PRBool aMutationEvent) { nsIDocument* doc = GetCurrentDoc(); -#ifdef ACCESSIBILITY - // A11y needs to be notified of content removals first, so accessibility - // events can be fired before any changes occur - if (aNotify && doc) { - nsIPresShell *presShell = doc->GetShell(); - if (presShell && presShell->IsAccessibilityActive()) { - nsCOMPtr<nsIAccessibilityService> accService = - do_GetService("@mozilla.org/accessibilityService;1"); - if (accService) { - accService->InvalidateSubtreeFor(presShell, aKid, - nsIAccessibilityService::NODE_REMOVE); - } - } - } -#endif nsMutationGuard::DidMutate(); NS_PRECONDITION(aKid && aKid->GetNodeParent() == this && aKid == GetChildAt(aIndex) && IndexOf(aKid) == (PRInt32)aIndex, "Bogus aKid"); mozAutoDocUpdate updateBatch(doc, UPDATE_CONTENT_MODEL, aNotify);
--- a/layout/base/nsCSSFrameConstructor.cpp +++ b/layout/base/nsCSSFrameConstructor.cpp @@ -125,17 +125,16 @@ #ifdef MOZ_XUL #include "nsIRootBox.h" #include "nsIDOMXULCommandDispatcher.h" #include "nsIDOMXULDocument.h" #include "nsIXULDocument.h" #endif #ifdef ACCESSIBILITY #include "nsIAccessibilityService.h" -#include "nsIAccessibleEvent.h" #endif #include "nsInlineFrame.h" #include "nsBlockFrame.h" #include "nsIScrollableFrame.h" #include "nsIXBLService.h" @@ -6708,16 +6707,27 @@ nsCSSFrameConstructor::ContentAppended(n #ifdef DEBUG if (gReallyNoisyContentUpdates) { printf("nsCSSFrameConstructor::ContentAppended: resulting frame model:\n"); parentFrame->List(stdout, 0); } #endif +#ifdef ACCESSIBILITY + if (mPresShell->IsAccessibilityActive()) { + nsCOMPtr<nsIAccessibilityService> accService = + do_GetService("@mozilla.org/accessibilityService;1"); + if (accService) { + accService->ContentRangeInserted(mPresShell, aContainer, + aFirstNewContent, nsnull); + } + } +#endif + return NS_OK; } #ifdef MOZ_XUL enum content_operation { CONTENT_INSERTED, @@ -7278,16 +7288,27 @@ nsCSSFrameConstructor::ContentRangeInser #ifdef DEBUG if (gReallyNoisyContentUpdates && parentFrame) { printf("nsCSSFrameConstructor::ContentRangeInserted: resulting frame model:\n"); parentFrame->List(stdout, 0); } #endif +#ifdef ACCESSIBILITY + if (mPresShell->IsAccessibilityActive()) { + nsCOMPtr<nsIAccessibilityService> accService = + do_GetService("@mozilla.org/accessibilityService;1"); + if (accService) { + accService->ContentRangeInserted(mPresShell, aContainer, + aStartChild, aEndChild); + } + } +#endif + return NS_OK; } nsresult nsCSSFrameConstructor::ContentRemoved(nsIContent* aContainer, nsIContent* aChild, nsIContent* aOldNextSibling, RemoveFlags aFlags, @@ -7409,17 +7430,27 @@ nsCSSFrameConstructor::ContentRemoved(ns aChild == AnyKidsNeedBlockParent(parentFrame->GetFirstChild(nsnull)) && !AnyKidsNeedBlockParent(childFrame->GetNextSibling())) { *aDidReconstruct = PR_TRUE; LAYOUT_PHASE_TEMP_EXIT(); nsresult rv = RecreateFramesForContent(grandparentFrame->GetContent(), PR_TRUE); LAYOUT_PHASE_TEMP_REENTER(); return rv; } - + +#ifdef ACCESSIBILITY + if (mPresShell->IsAccessibilityActive()) { + nsCOMPtr<nsIAccessibilityService> accService = + do_GetService("@mozilla.org/accessibilityService;1"); + if (accService) { + accService->ContentRemoved(mPresShell, aContainer, aChild); + } + } +#endif + // Examine the containing-block for the removed content and see if // :first-letter style applies. nsIFrame* inflowChild = childFrame; if (childFrame->GetStateBits() & NS_FRAME_OUT_OF_FLOW) { inflowChild = frameManager->GetPlaceholderFrameFor(childFrame); NS_ASSERTION(inflowChild, "No placeholder for out-of-flow?"); } nsIFrame* containingBlock = @@ -9075,38 +9106,16 @@ nsCSSFrameConstructor::RecreateFramesFor PostRestyleEvent(aContent->AsElement(), nsRestyleHint(0), nsChangeHint_ReconstructFrame); } else { rv = ContentInserted(container, aContent, mTempFrameTreeState, PR_FALSE); } } } -#ifdef ACCESSIBILITY - if (mPresShell->IsAccessibilityActive()) { - PRUint32 changeType; - if (frame) { - nsIFrame *newFrame = aContent->GetPrimaryFrame(); - changeType = newFrame ? nsIAccessibilityService::FRAME_SIGNIFICANT_CHANGE : - nsIAccessibilityService::FRAME_HIDE; - } - else { - changeType = nsIAccessibilityService::FRAME_SHOW; - } - - // A significant enough change occurred 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, aContent, changeType); - } - } -#endif - return rv; } ////////////////////////////////////////////////////////////////////// // Block frame construction code already_AddRefed<nsStyleContext>
--- a/layout/base/nsFrameManager.cpp +++ b/layout/base/nsFrameManager.cpp @@ -90,20 +90,16 @@ #include "nsLayoutErrors.h" #include "nsLayoutUtils.h" #include "nsAutoPtr.h" #include "imgIRequest.h" #include "nsTransitionManager.h" #include "RestyleTracker.h" #include "nsFrameManager.h" -#ifdef ACCESSIBILITY -#include "nsIAccessibilityService.h" -#include "nsIAccessibleEvent.h" -#endif #ifdef DEBUG //#define NOISY_DEBUG //#define DEBUG_UNDISPLAYED_MAP #else #undef NOISY_DEBUG #undef DEBUG_UNDISPLAYED_MAP #endif @@ -999,17 +995,16 @@ CaptureChange(nsStyleContext* aOldContex */ nsChangeHint nsFrameManager::ReResolveStyleContext(nsPresContext *aPresContext, nsIFrame *aFrame, nsIContent *aParentContent, nsStyleChangeList *aChangeList, nsChangeHint aMinChange, nsRestyleHint aRestyleHint, - PRBool aFireAccessibilityEvents, RestyleTracker& aRestyleTracker) { if (!NS_IsHintSubset(nsChangeHint_NeedDirtyReflow, aMinChange)) { // If aMinChange doesn't include nsChangeHint_NeedDirtyReflow, clear out // all the reflow change bits from it, so that we'll make sure to append a // change to the list for ourselves if we need a reflow. We need this // because the parent may or may not actually end up reflowing us // otherwise. @@ -1042,19 +1037,16 @@ nsFrameManager::ReResolveStyleContext(ns // XXXbryner we may be able to avoid some of the refcounting goop here. // We do need a reference to oldContext for the lifetime of this function, and it's possible // that the frame has the last reference to it, so AddRef it here. nsChangeHint assumeDifferenceHint = NS_STYLE_HINT_NONE; // XXXbz oldContext should just be an nsRefPtr nsStyleContext* oldContext = aFrame->GetStyleContext(); nsStyleSet* styleSet = aPresContext->StyleSet(); -#ifdef ACCESSIBILITY - PRBool isVisible = aFrame->GetStyleVisibility()->IsVisible(); -#endif // XXXbz the nsIFrame constructor takes an nsStyleContext, so how // could oldContext be null? if (oldContext) { oldContext->AddRef(); nsIAtom* const pseudoTag = oldContext->GetPseudo(); const nsCSSPseudoElements::Type pseudoType = oldContext->GetPseudoType(); nsIContent* localContent = aFrame->GetContent(); @@ -1100,22 +1092,20 @@ nsFrameManager::ReResolveStyleContext(ns // assumeDifferenceHint forces the parent's change to be also // applied to this frame, no matter what // nsStyleContext::CalcStyleDifference says. CalcStyleDifference // can't be trusted because it assumes any changes to the parent // style context provider will be automatically propagated to // the frame(s) with child style contexts. - // Accessibility: we don't need to fire a11y events for child provider - // frame because it is visible or hidden withitn this frame. assumeDifferenceHint = ReResolveStyleContext(aPresContext, providerFrame, aParentContent, aChangeList, aMinChange, aRestyleHint, - PR_FALSE, aRestyleTracker); + aRestyleTracker); // The provider's new context becomes the parent context of // aFrame's context. parentContext = providerFrame->GetStyleContext(); // Set |resolvedChild| so we don't bother resolving the // provider again. resolvedChild = providerFrame; } @@ -1403,42 +1393,16 @@ nsFrameManager::ReResolveStyleContext(ns NS_UpdateHint(aMinChange, nsChangeHint_ReconstructFrame); aChangeList->AppendChange(aFrame, content, nsChangeHint_ReconstructFrame); } } } } - PRBool fireAccessibilityEvents = aFireAccessibilityEvents; -#ifdef ACCESSIBILITY - if (fireAccessibilityEvents && mPresShell->IsAccessibilityActive() && - aFrame->GetStyleVisibility()->IsVisible() != isVisible && - !aFrame->GetPrevContinuation()) { - // A significant enough change occurred that this part - // of the accessible tree is no longer valid. Fire event for primary - // frames only and if it wasn't fired for parent frame already. - - // XXX: bug 355521. Visibility does not affect descendents with - // visibility set. Work on a separate, accurate mechanism for dealing with - // visibility changes. - nsCOMPtr<nsIAccessibilityService> accService = - do_GetService("@mozilla.org/accessibilityService;1"); - if (accService) { - PRUint32 changeType = isVisible ? - nsIAccessibilityService::FRAME_HIDE : - nsIAccessibilityService::FRAME_SHOW; - - accService->InvalidateSubtreeFor(mPresShell, aFrame->GetContent(), - changeType); - fireAccessibilityEvents = PR_FALSE; - } - } -#endif - if (!(aMinChange & nsChangeHint_ReconstructFrame)) { // There is no need to waste time crawling into a frame's children on a frame change. // The act of reconstructing frames will force new style contexts to be resolved on all // of this frame's descendants anyway, so we want to avoid wasting time processing // style contexts that we're just going to throw away anyway. - dwh // now do children @@ -1473,34 +1437,31 @@ nsFrameManager::ReResolveStyleContext(ns // |nsFrame::GetParentStyleContextFrame| checks being out // of flow so that this works correctly. do { ReResolveStyleContext(aPresContext, outOfFlowFrame, content, aChangeList, NS_SubtractHint(aMinChange, nsChangeHint_ReflowFrame), childRestyleHint, - fireAccessibilityEvents, aRestyleTracker); } while (outOfFlowFrame = outOfFlowFrame->GetNextContinuation()); // reresolve placeholder's context under the same parent // as the out-of-flow frame ReResolveStyleContext(aPresContext, child, content, aChangeList, aMinChange, childRestyleHint, - fireAccessibilityEvents, aRestyleTracker); } else { // regular child frame if (child != resolvedChild) { ReResolveStyleContext(aPresContext, child, content, aChangeList, aMinChange, childRestyleHint, - fireAccessibilityEvents, aRestyleTracker); } else { NOISY_TRACE_FRAME("child frame already resolved as descendant, skipping",aFrame); } } } child = child->GetNextSibling(); } @@ -1543,17 +1504,16 @@ nsFrameManager::ComputeStyleChangeFor(ns // Outer loop over special siblings do { // Inner loop over next-in-flows of the current frame nsChangeHint frameChange = ReResolveStyleContext(GetPresContext(), frame, nsnull, aChangeList, topLevelChange, aRestyleDescendants ? eRestyle_Subtree : eRestyle_Self, - PR_TRUE, aRestyleTracker); NS_UpdateHint(topLevelChange, frameChange); if (topLevelChange & nsChangeHint_ReconstructFrame) { // If it's going to cause a framechange, then don't bother // with the continuations or special siblings since they'll be // clobbered by the frame reconstruct anyway. NS_ASSERTION(!frame->GetPrevContinuation(),
--- a/layout/base/nsFrameManager.h +++ b/layout/base/nsFrameManager.h @@ -209,13 +209,12 @@ private: // current parent and existing rulenode, and the same for kids. NS_HIDDEN_(nsChangeHint) ReResolveStyleContext(nsPresContext *aPresContext, nsIFrame *aFrame, nsIContent *aParentContent, nsStyleChangeList *aChangeList, nsChangeHint aMinChange, nsRestyleHint aRestyleHint, - PRBool aFireAccessibilityEvents, RestyleTracker& aRestyleTracker); }; #endif
--- a/layout/base/nsIPresShell.h +++ b/layout/base/nsIPresShell.h @@ -1184,28 +1184,16 @@ protected: PRPackedBool mDidInitialReflow; PRPackedBool mIsDestroying; PRPackedBool mIsReflowing; PRPackedBool mPaintingSuppressed; // For all documents we initially lock down painting. PRPackedBool mIsThemeSupportDisabled; // Whether or not form controls should use nsITheme in this shell. PRPackedBool mIsActive; PRPackedBool mFrozen; -#ifdef ACCESSIBILITY - /** - * Call this when there have been significant changes in the rendering for - * a content subtree, so the matching accessibility subtree can be invalidated - */ - void InvalidateAccessibleSubtree(nsIContent *aContent); -#endif - - // Set to true when the accessibility service is being used to mirror - // the dom/layout trees - PRPackedBool mIsAccessibilityActive; - PRPackedBool mObservesMutationsForPrint; PRPackedBool mReflowScheduled; // If true, we have a reflow // scheduled. Guaranteed to be // false if mReflowContinueTimer // is non-null. PRPackedBool mSuppressInterruptibleReflows;
--- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -3652,30 +3652,16 @@ PresShell::CancelAllPendingReflows() if (mReflowScheduled) { GetPresContext()->RefreshDriver()->RemoveLayoutFlushObserver(this); mReflowScheduled = PR_FALSE; } ASSERT_REFLOW_SCHEDULED_STATE(); } -#ifdef ACCESSIBILITY -void nsIPresShell::InvalidateAccessibleSubtree(nsIContent *aContent) -{ - if (gIsAccessibilityActive) { - nsCOMPtr<nsIAccessibilityService> accService = - do_GetService("@mozilla.org/accessibilityService;1"); - if (accService) { - accService->InvalidateSubtreeFor(this, aContent, - nsIAccessibilityService::FRAME_SIGNIFICANT_CHANGE); - } - } -} -#endif - nsresult PresShell::RecreateFramesFor(nsIContent* aContent) { NS_TIME_FUNCTION_MIN(1.0); NS_ENSURE_TRUE(mPresContext, NS_ERROR_FAILURE); if (!mDidInitialReflow) { // Nothing to do here. In fact, if we proceed and aContent is the @@ -3699,19 +3685,16 @@ PresShell::RecreateFramesFor(nsIContent* changeList.AppendChange(nsnull, aContent, nsChangeHint_ReconstructFrame); // Mark ourselves as not safe to flush while we're doing frame construction. ++mChangeNestCount; nsresult rv = mFrameConstructor->ProcessRestyledFrames(changeList); --mChangeNestCount; batch.EndUpdateViewBatch(NS_VMREFRESH_NO_SYNC); -#ifdef ACCESSIBILITY - InvalidateAccessibleSubtree(aContent); -#endif return rv; } void nsIPresShell::PostRecreateFramesFor(Element* aElement) { FrameConstructor()->PostRestyleEvent(aElement, nsRestyleHint(0), nsChangeHint_ReconstructFrame); @@ -5174,20 +5157,16 @@ nsIPresShell::ReconstructStyleDataIntern } if (!root) { // No content to restyle return; } mFrameConstructor->PostRestyleEvent(root, eRestyle_Subtree, NS_STYLE_HINT_NONE); - -#ifdef ACCESSIBILITY - InvalidateAccessibleSubtree(nsnull); -#endif } void nsIPresShell::ReconstructStyleDataExternal() { ReconstructStyleDataInternal(); } @@ -8058,19 +8037,16 @@ PresShell::Observe(nsISupports* aSubject { nsAutoScriptBlocker scriptBlocker; ++mChangeNestCount; mFrameConstructor->ProcessRestyledFrames(changeList); --mChangeNestCount; } } batch.EndUpdateViewBatch(NS_VMREFRESH_NO_SYNC); -#ifdef ACCESSIBILITY - InvalidateAccessibleSubtree(nsnull); -#endif } return NS_OK; } #endif if (!nsCRT::strcmp(aTopic, "agent-sheet-added") && mStyleSet) { AddAgentSheet(aSubject); return NS_OK;
--- a/layout/generic/nsFrame.cpp +++ b/layout/generic/nsFrame.cpp @@ -6514,18 +6514,22 @@ nsIFrame::IsFocusable(PRInt32 *aTabIndex // Otherwise you couldn't scroll them with keyboard, which is // an accessibility issue (e.g. Section 508 rules) // However, we don't make them to be focusable with the mouse, // because the extra focus outlines are considered unnecessarily ugly. // When clicked on, the selection position within the element // will be enough to make them keyboard scrollable. nsIScrollableFrame *scrollFrame = do_QueryFrame(this); if (scrollFrame) { - nsMargin margin = scrollFrame->GetActualScrollbarSizes(); - if (margin.top || margin.right || margin.bottom || margin.left) { + nsIScrollableFrame::ScrollbarStyles styles = + scrollFrame->GetScrollbarStyles(); + if (styles.mVertical == NS_STYLE_OVERFLOW_SCROLL || + styles.mVertical == NS_STYLE_OVERFLOW_AUTO || + styles.mHorizontal == NS_STYLE_OVERFLOW_SCROLL || + styles.mHorizontal == NS_STYLE_OVERFLOW_AUTO) { // Scroll bars will be used for overflow isFocusable = PR_TRUE; tabIndex = 0; } } } } }
--- a/layout/generic/nsObjectFrame.cpp +++ b/layout/generic/nsObjectFrame.cpp @@ -2277,16 +2277,26 @@ nsObjectFrame::Instantiate(nsIChannel* a if (!weakFrame.IsAlive()) { return NS_ERROR_NOT_AVAILABLE; } NS_ASSERTION(mPreventInstantiation, "Instantiation should still be prevented!"); mPreventInstantiation = PR_FALSE; +#ifdef ACCESSIBILITY + if (PresContext()->PresShell()->IsAccessibilityActive()) { + nsCOMPtr<nsIAccessibilityService> accService = + do_GetService("@mozilla.org/accessibilityService;1"); + if (accService) { + accService->RecreateAccessible(PresContext()->PresShell(), mContent); + } + } +#endif + return rv; } nsresult nsObjectFrame::Instantiate(const char* aMimeType, nsIURI* aURI) { PR_LOG(nsObjectFrameLM, PR_LOG_DEBUG, ("nsObjectFrame::Instantiate(%s) called on frame %p\n", aMimeType, @@ -2335,16 +2345,26 @@ nsObjectFrame::Instantiate(const char* a } CallSetWindow(); } NS_ASSERTION(mPreventInstantiation, "Instantiation should still be prevented!"); +#ifdef ACCESSIBILITY + if (PresContext()->PresShell()->IsAccessibilityActive()) { + nsCOMPtr<nsIAccessibilityService> accService = + do_GetService("@mozilla.org/accessibilityService;1"); + if (accService) { + accService->RecreateAccessible(PresContext()->PresShell(), mContent); + } + } +#endif + mPreventInstantiation = PR_FALSE; return rv; } void nsObjectFrame::TryNotifyContentObjectWrapper() {