Bug 571459 - shutdown document accessible when presshell goes away, patch=bz, surkov, r=surkov, davidb, sr=roc, bz, f=marcoz

This commit is contained in:
Alexander Surkov 2010-06-18 11:44:09 +09:00
parent aa038faed7
commit b9b500e77b
11 changed files with 154 additions and 92 deletions

View File

@ -51,10 +51,10 @@ class nsIFrame;
class nsIPresShell;
class nsObjectFrame;
// 9f43b315-53c6-4d46-9818-9c8593e91984
// 10ff6dca-b219-4b64-9a4c-67a62b86edce
#define NS_IACCESSIBILITYSERVICE_IID \
{0x9f43b315, 0x53c6, 0x4d46, \
{0x98, 0x18, 0x9c, 0x85, 0x93, 0xe9, 0x19, 0x84} }
{ 0x10ff6dca, 0xb219, 0x4b64, \
{ 0x9a, 0x4c, 0x67, 0xa6, 0x2b, 0x86, 0xed, 0xce } }
class nsIAccessibilityService : public nsIAccessibleRetrieval
{
@ -166,6 +166,12 @@ public:
*/
virtual void NotifyOfAnchorJumpTo(nsIContent *aTarget) = 0;
/**
* Notify the accessibility service that the given presshell is
* being destroyed.
*/
virtual void PresShellDestroyed(nsIPresShell *aPresShell) = 0;
/**
* Fire accessible event of the given type for the given target.
*

View File

@ -96,6 +96,7 @@ nsAccDocManager::ShutdownDocAccessiblesInTree(nsIDocument *aDocument)
ShutdownDocAccessiblesInTree(treeItem, aDocument);
}
////////////////////////////////////////////////////////////////////////////////
// nsAccDocManager protected
@ -128,6 +129,22 @@ nsAccDocManager::Shutdown()
ClearDocCache();
}
void
nsAccDocManager::ShutdownDocAccessible(nsIDocument *aDocument)
{
nsDocAccessible* docAccessible =
mDocAccessibleCache.GetWeak(static_cast<void*>(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));
}
////////////////////////////////////////////////////////////////////////////////
// nsISupports
@ -413,30 +430,6 @@ nsAccDocManager::AddListeners(nsIDocument *aDocument,
}
}
void
nsAccDocManager::RemoveListeners(nsIDocument *aDocument)
{
// Document has no window when application shuts down. The document can still
// exist because we didn't receive a "pagehide" event.
nsPIDOMWindow *window = aDocument->GetWindow();
if (!window)
return;
nsPIDOMEventTarget *target = window->GetChromeEventHandler();
nsIEventListenerManager* elm = target->GetListenerManager(PR_TRUE);
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("pagehide"),
NS_EVENT_FLAG_CAPTURE, nsnull);
NS_LOG_ACCDOCDESTROY("removed 'pagehide' listener", aDocument)
if (nsCoreUtils::IsRootDocument(aDocument)) {
elm->RemoveEventListenerByType(this, NS_LITERAL_STRING("DOMContentLoaded"),
NS_EVENT_FLAG_CAPTURE, nsnull);
NS_LOG_ACCDOCDESTROY("removed 'DOMContentLoaded' listener", aDocument)
}
}
nsDocAccessible*
nsAccDocManager::CreateDocOrRootAccessible(nsIDocument *aDocument)
{
@ -502,7 +495,6 @@ nsAccDocManager::CreateDocOrRootAccessible(nsIDocument *aDocument)
if (outerDocAcc) {
// Root document accessible doesn't have associated outerdoc accessible, it
// adds itself to application accessible instead.
NS_LOG_ACCDOCCREATE("append document to outerdoc", aDocument)
outerDocAcc->AppendChild(docAcc);
}
@ -512,7 +504,7 @@ nsAccDocManager::CreateDocOrRootAccessible(nsIDocument *aDocument)
return nsnull;
}
NS_LOG_ACCDOCCREATE("document created", aDocument)
NS_LOG_ACCDOCCREATE("document creation finished", aDocument)
AddListeners(aDocument, isRootDoc);
return docAcc;
@ -547,19 +539,6 @@ nsAccDocManager::ShutdownDocAccessiblesInTree(nsIDocShellTreeItem *aTreeItem,
ShutdownDocAccessible(aDocument);
}
void
nsAccDocManager::ShutdownDocAccessible(nsIDocument *aDocument)
{
RemoveListeners(aDocument);
nsDocAccessible *docAccessible =
mDocAccessibleCache.GetWeak(static_cast<void*>(aDocument));
if (docAccessible)
docAccessible->Shutdown();
mDocAccessibleCache.Remove(static_cast<void*>(aDocument));
}
////////////////////////////////////////////////////////////////////////////////
// nsAccDocManager static
@ -573,14 +552,8 @@ nsAccDocManager::ClearDocCacheEntry(const void* aKey,
NS_ASSERTION(aDocAccessible,
"Calling ClearDocCacheEntry with a NULL pointer!");
if (aDocAccessible) {
nsCOMPtr<nsIDocument> document = aDocAccessible->GetDOMDocument();
NS_ASSERTION(document, "Document accessible was shutdown already!");
if (document)
accDocMgr->RemoveListeners(document);
if (aDocAccessible)
aDocAccessible->Shutdown();
}
return PL_DHASH_REMOVE;
}

View File

@ -48,6 +48,8 @@
class nsDocAccessible;
//#define DEBUG_ACCDOCMGR
/**
* Manage the document accessible life cycle.
*/
@ -80,6 +82,14 @@ public:
*/
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));
}
protected:
nsAccDocManager() { };
@ -93,10 +103,10 @@ protected:
*/
void Shutdown();
inline nsDocAccessible* GetDocAccessibleFromCache(nsIDocument* aDocument) const
{
return mDocAccessibleCache.GetWeak(static_cast<void*>(aDocument));
}
/**
* Shutdown the document accessible.
*/
void ShutdownDocAccessible(nsIDocument* aDocument);
private:
nsAccDocManager(const nsAccDocManager&);
@ -137,10 +147,9 @@ private:
PRBool IsEventTargetDocument(nsIDocument *aDocument) const;
/**
* Add/remove 'pagehide' and 'DOMContentLoaded' event listeners.
* Add 'pagehide' and 'DOMContentLoaded' event listeners.
*/
void AddListeners(nsIDocument *aDocument, PRBool aAddPageShowListener);
void RemoveListeners(nsIDocument *aDocument);
/**
* Create document or root accessible.
@ -153,11 +162,6 @@ private:
void ShutdownDocAccessiblesInTree(nsIDocShellTreeItem *aTreeItem,
nsIDocument *aDocument);
/**
* Shutdown the document accessible.
*/
void ShutdownDocAccessible(nsIDocument *aDocument);
typedef nsRefPtrHashtable<nsVoidPtrHashKey, nsDocAccessible>
nsDocAccessibleHashtable;
@ -194,8 +198,6 @@ private:
/**
* nsAccDocManager debugging macros.
*/
//#define DEBUG_ACCDOCMGR
#ifdef DEBUG_ACCDOCMGR
// Enable these to log accessible document loading, creation or destruction.
@ -205,9 +207,7 @@ private:
// Common macros, do not use directly.
#define NS_LOG_ACCDOC_ADDRESS(aDocument, aDocAcc) \
printf("DOM id: 0x%x, acc id: 0x%x", \
reinterpret_cast<PRInt32>(static_cast<void*>(aDocument)), \
reinterpret_cast<PRInt32>(aDocAcc));
printf("DOM id: %p, acc id: %p", aDocument, aDocAcc);
#define NS_LOG_ACCDOC_URI(aDocument) \
nsIURI *uri = aDocument->GetDocumentURI(); \
@ -260,19 +260,18 @@ private:
#define NS_LOG_ACCDOC_DOCPRESSHELL(aDocument) \
nsIPresShell *ps = aDocument->GetPrimaryShell(); \
printf("presshell: 0x%x", reinterpret_cast<PRInt32>(ps)); \
printf("presshell: %p", ps); \
nsIScrollableFrame *sf = ps ? \
ps->GetRootScrollFrameAsScrollableExternal() : nsnull; \
printf(", root scroll frame: 0x%x", reinterpret_cast<PRInt32>(sf));
printf(", root scroll frame: %p", sf);
#define NS_LOG_ACCDOC_DOCLOADGROUP(aDocument) \
nsCOMPtr<nsILoadGroup> loadGroup = aDocument->GetDocumentLoadGroup(); \
printf("load group: 0x%x", reinterpret_cast<PRInt32>(loadGroup.get()));
printf("load group: %p", loadGroup);
#define NS_LOG_ACCDOC_DOCPARENT(aDocument) \
nsIDocument *parentDoc = aDocument->GetParentDocument(); \
printf("parent id: 0x%x", \
reinterpret_cast<PRInt32>(parentDoc)); \
printf("parent id: %p", parentDoc); \
if (parentDoc) { \
printf("\n parent "); \
NS_LOG_ACCDOC_URI(parentDoc) \
@ -397,6 +396,21 @@ private:
} \
}
#define NS_LOG_ACCDOC_ACCADDRESS(aName, aAcc) \
{ \
nsINode* node = aAcc->GetNode(); \
nsIDocument* doc = aAcc->GetDocumentNode(); \
nsDocAccessible *docacc = GetAccService()->GetDocAccessibleFromCache(doc); \
printf(" " aName " accessible: %p, node: %p\n", aAcc, node); \
printf(" docacc for " aName " accessible: %p, node: %p\n", docacc, doc); \
printf(" "); \
NS_LOG_ACCDOC_URI(doc) \
printf("\n"); \
}
#define NS_LOG_ACCDOC_MSG(aMsg) \
printf("\n" aMsg "\n"); \
#define NS_LOG_ACCDOC_TEXT(aMsg) \
printf(" " aMsg "\n");
@ -431,7 +445,7 @@ private:
#define NS_LOG_ACCDOCLOAD(aMsg, aWebProgress, aRequest, aStateFlags) \
{ \
printf("\nA11Y DOCLOAD: " aMsg "\n"); \
NS_LOG_ACCDOC_MSG("A11Y DOCLOAD: " aMsg); \
\
nsCOMPtr<nsIDOMWindow> DOMWindow; \
aWebProgress->GetDOMWindow(getter_AddRefs(DOMWindow)); \
@ -441,7 +455,7 @@ private:
if (DOMDocument) { \
nsCOMPtr<nsIDocument> document(do_QueryInterface(DOMDocument)); \
nsDocAccessible *docAcc = \
mDocAccessibleCache.GetWeak(static_cast<void*>(document)); \
GetAccService()->GetDocAccessibleFromCache(document); \
NS_LOG_ACCDOC_DOCINFO(document, docAcc) \
\
printf(" {\n"); \
@ -463,9 +477,9 @@ private:
#define NS_LOG_ACCDOCLOAD2(aMsg, aDocument) \
{ \
printf("\nA11Y DOCLOAD: " aMsg "\n"); \
NS_LOG_ACCDOC_MSG("A11Y DOCLOAD: " aMsg); \
nsDocAccessible *docAcc = \
mDocAccessibleCache.GetWeak(static_cast<void*>(aDocument)); \
GetAccService()->GetDocAccessibleFromCache(aDocument); \
NS_LOG_ACCDOC_DOCINFO(aDocument, docAcc) \
}
@ -496,16 +510,19 @@ private:
// Accessible document creation macros.
#ifdef DEBUG_ACCDOCMGR_DOCCREATE
#define NS_LOG_ACCDOCCREATE_FOR(aMsg, aDocument, aDocAcc) \
printf("\nA11Y DOCCREATE: " aMsg "\n"); \
NS_LOG_ACCDOC_MSG("A11Y DOCCREATE: " aMsg); \
NS_LOG_ACCDOC_DOCINFO(aDocument, aDocAcc)
#define NS_LOG_ACCDOCCREATE(aMsg, aDocument) \
{ \
nsDocAccessible *docAcc = \
mDocAccessibleCache.GetWeak(static_cast<void*>(aDocument)); \
GetAccService()->GetDocAccessibleFromCache(aDocument); \
NS_LOG_ACCDOCCREATE_FOR(aMsg, aDocument, docAcc) \
}
#define NS_LOG_ACCDOCCREATE_ACCADDRESS(aName, aAcc) \
NS_LOG_ACCDOC_ACCADDRESS(aName, aAcc)
#define NS_LOG_ACCDOCCREATE_TEXT(aMsg) \
NS_LOG_ACCDOC_TEXT(aMsg)
@ -514,16 +531,24 @@ private:
// Accessible document destruction macros.
#ifdef DEBUG_ACCDOCMGR_DOCDESTROY
#define NS_LOG_ACCDOCDESTROY_FOR(aMsg, aDocument, aDocAcc) \
printf("\nA11Y DOCDESTROY: " aMsg "\n"); \
NS_LOG_ACCDOC_MSG("A11Y DOCDESTROY: " aMsg); \
NS_LOG_ACCDOC_DOCINFO(aDocument, aDocAcc)
#define NS_LOG_ACCDOCDESTROY(aMsg, aDocument) \
nsDocAccessible *docAcc = \
mDocAccessibleCache.GetWeak(static_cast<void*>(aDocument)); \
NS_LOG_ACCDOCDESTROY_FOR(aMsg, aDocument, docAcc)
{ \
nsDocAccessible* docAcc = \
GetAccService()->GetDocAccessibleFromCache(aDocument); \
NS_LOG_ACCDOCDESTROY_FOR(aMsg, aDocument, docAcc) \
}
#define NS_LOG_ACCDOCDESTROY_TEXT(aMsg) \
NS_LOG_ACCDOC_TEXT(aMsg)
#define NS_LOG_ACCDOCDESTROY_ACCADDRESS(aName, aAcc) \
NS_LOG_ACCDOC_ACCADDRESS(aName, aAcc)
#define NS_LOG_ACCDOCDESTROY_MSG(aMsg) \
NS_LOG_ACCDOC_MSG(aMsg)
#define NS_LOG_ACCDOCDESTROY_TEXT(aMsg) \
NS_LOG_ACCDOC_TEXT(aMsg)
#endif // DEBUG_ACCDOCMGR_DOCDESTROY
@ -541,12 +566,15 @@ private:
#ifndef DEBUG_ACCDOCMGR_DOCCREATE
#define NS_LOG_ACCDOCCREATE_FOR(aMsg, aDocument, aDocAcc)
#define NS_LOG_ACCDOCCREATE(aMsg, aDocument)
#define NS_LOG_ACCDOCCREATE_ACCADDRESS(aName, aAcc)
#define NS_LOG_ACCDOCCREATE_TEXT(aMsg)
#endif
#ifndef DEBUG_ACCDOCMGR_DOCDESTROY
#define NS_LOG_ACCDOCDESTROY_FOR(aMsg, aDocument, aDocAcc)
#define NS_LOG_ACCDOCDESTROY(aMsg, aDocument)
#define NS_LOG_ACCDOCDESTROY_MSG(aMsg)
#define NS_LOG_ACCDOCDESTROY_ACCADDRESS(aName, aAcc)
#define NS_LOG_ACCDOCDESTROY_TEXT(aMsg)
#endif

View File

@ -157,6 +157,8 @@ public:
*/
virtual nsINode* GetNode() const { return mContent; }
nsIContent* GetContent() const { return mContent; }
nsIDocument* GetDocumentNode() const
{ return mContent ? mContent->GetOwnerDoc() : nsnull; }
/**
* Return node type information of DOM node associated with the accessible.

View File

@ -780,6 +780,25 @@ nsAccessibilityService::CreateHTMLCaptionAccessible(nsIFrame *aFrame,
return NS_OK;
}
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
// destroyed before we're notified styles were changed. Shutdown the document
// accessible early.
nsIDocument* doc = aPresShell->GetDocument();
if (!doc)
return;
NS_LOG_ACCDOCDESTROY("presshell destroyed", doc)
ShutdownDocAccessible(doc);
}
// nsAccessibilityService protected
nsAccessible *
nsAccessibilityService::GetCachedAccessible(nsINode *aNode,

View File

@ -124,6 +124,8 @@ public:
virtual void NotifyOfAnchorJumpTo(nsIContent *aTarget);
virtual void PresShellDestroyed(nsIPresShell* aPresShell);
virtual nsresult FireAccessibleEvent(PRUint32 aEvent, nsIAccessible *aTarget);
// nsAccessibiltiyService

View File

@ -596,6 +596,8 @@ nsDocAccessible::RemoveAccessNodeFromCache(nsAccessible *aAccessible)
PRBool
nsDocAccessible::Init()
{
NS_LOG_ACCDOCCREATE_FOR("document initialize", mDocument, this)
// Initialize event queue.
mEventQueue = new nsAccEventQueue(this);
if (!mEventQueue)
@ -630,8 +632,8 @@ nsDocAccessible::Shutdown()
RemoveEventListeners();
if (mParent) {
NS_LOG_ACCDOCDESTROY_FOR("remove document from outer doc", mDocument, this);
mParent->RemoveChild(this);
mParent = nsnull;
}
mWeakShell = nsnull; // Avoid reentrancy

View File

@ -124,8 +124,6 @@ public:
// nsDocAccessible
nsIDocument *GetDOMDocument() const { return mDocument; }
/**
* Return true if associated DOM document was loaded and isn't unloading.
*/

View File

@ -159,17 +159,20 @@ nsOuterDocAccessible::DoAction(PRUint8 aIndex)
void
nsOuterDocAccessible::Shutdown()
{
// Shutdown child document if any.
// XXX: sometimes outerdoc accessible is shutdown because of layout style
// change however the presshell of underlying document isn't destroyed and
// the document doesn't get pagehide events. Shutdown underlying document if
// any to avoid hanging document accessible.
NS_LOG_ACCDOCDESTROY_MSG("A11y outerdoc shutdown")
NS_LOG_ACCDOCDESTROY_ACCADDRESS("outerdoc", this)
nsAccessible *childAcc = mChildren.SafeElementAt(0, nsnull);
if (childAcc) {
nsRefPtr<nsDocAccessible> docAcc(do_QueryObject(childAcc));
NS_LOG_ACCDOCDESTROY_FOR("outerdoc document shutdown",
docAcc->GetDOMDocument(), docAcc.get())
GetAccService()->ShutdownDocAccessiblesInTree(docAcc->GetDOMDocument());
NS_LOG_ACCDOCDESTROY("outerdoc's child document shutdown",
childAcc->GetDocumentNode())
GetAccService()->ShutdownDocAccessiblesInTree(childAcc->GetDocumentNode());
}
nsAccessible::InvalidateChildren();
nsAccessibleWrap::Shutdown();
}
@ -201,6 +204,11 @@ nsOuterDocAccessible::AppendChild(nsAccessible *aAccessible)
return PR_FALSE;
aAccessible->SetParent(this);
NS_LOG_ACCDOCCREATE("append document to outerdoc",
aAccessible->GetDocumentNode())
NS_LOG_ACCDOCCREATE_ACCADDRESS("outerdoc", this)
return PR_TRUE;
}
@ -213,13 +221,19 @@ nsOuterDocAccessible::RemoveChild(nsAccessible *aAccessible)
return PR_FALSE;
}
NS_LOG_ACCDOCDESTROY("remove document from outerdoc",
child->GetDocumentNode())
NS_LOG_ACCDOCDESTROY_ACCADDRESS("outerdoc", this)
mChildren.RemoveElement(child);
NS_ASSERTION(!mChildren.Length(),
"This child document of outerdoc accessible wasn't removed!");
return PR_TRUE;
}
////////////////////////////////////////////////////////////////////////////////
// nsAccessible protected

View File

@ -25,6 +25,11 @@ const EVENT_VALUE_CHANGE = nsIAccessibleEvent.EVENT_VALUE_CHANGE;
*/
var gA11yEventDumpID = "";
/**
* Set up this variable to dump event processing into console.
*/
var gA11yEventDumpToConsole = false;
/**
* Executes the function when requested event is handled.
*
@ -262,6 +267,9 @@ function eventQueue(aEventType)
this.setEventHandler(invoker);
if (gA11yEventDumpToConsole)
dump("\nEvent queue: \n invoke: " + invoker.getID() + "\n");
if (invoker.invoke() == INVOKER_ACTION_FAILED) {
// Invoker failed to prepare action, fail and finish tests.
this.processNextInvoker();

View File

@ -1816,6 +1816,16 @@ PresShell::Destroy()
if (mHaveShutDown)
return;
#ifdef ACCESSIBILITY
if (gIsAccessibilityActive) {
nsCOMPtr<nsIAccessibilityService> accService =
do_GetService("@mozilla.org/accessibilityService;1");
if (accService) {
accService->PresShellDestroyed(this);
}
}
#endif // ACCESSIBILITY
MaybeReleaseCapturingContent();
mContentToScrollTo = nsnull;