Bug 523885 - Tabbrowser handling of window.close possibly leak-prone. r=bz+sr=jst

--HG--
extra : rebase_source : 07b3a2e304498e053cdb301bb0d0d781412a196a
This commit is contained in:
Ben Newman 2010-02-23 10:45:05 -08:00
parent 93ebb4ca17
commit f28f39240d
2 changed files with 254 additions and 205 deletions

View File

@ -666,6 +666,8 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow)
#ifdef DEBUG
, mSetOpenerWindowCalled(PR_FALSE)
#endif
, mCleanedUp(PR_FALSE)
, mCallCleanUpAfterModalDialogCloses(PR_FALSE)
{
memset(mScriptGlobals, 0, sizeof(mScriptGlobals));
nsLayoutStatics::AddRef();
@ -776,68 +778,11 @@ nsGlobalWindow::~nsGlobalWindow()
("DOMWINDOW %p destroyed", this));
#endif
if (mObserver) {
nsCOMPtr<nsIObserverService> os =
do_GetService("@mozilla.org/observer-service;1");
if (os) {
os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
os->RemoveObserver(mObserver, "dom-storage2-changed");
os->RemoveObserver(mObserver, "dom-storage-changed");
}
// Drop its reference to this dying window, in case for some bogus reason
// the object stays around.
mObserver->Forget();
NS_RELEASE(mObserver);
}
if (IsOuterWindow()) {
// An outer window is destroyed with inner windows still possibly
// alive, iterate through the inner windows and null out their
// back pointer to this outer, and pull them out of the list of
// inner windows.
nsGlobalWindow *w;
while ((w = (nsGlobalWindow *)PR_LIST_HEAD(this)) != this) {
NS_ASSERTION(w->mOuterWindow == this, "Uh, bad outer window pointer?");
w->mOuterWindow = nsnull;
PR_REMOVE_AND_INIT_LINK(w);
}
} else {
if (mListenerManager) {
mListenerManager->Disconnect();
mListenerManager = nsnull;
}
// An inner window is destroyed, pull it out of the outer window's
// list if inner windows.
PR_REMOVE_LINK(this);
// If our outer window's inner window is this window, null out the
// outer window's reference to this window that's being deleted.
nsGlobalWindow *outer = GetOuterWindowInternal();
if (outer && outer->mInnerWindow == this) {
outer->mInnerWindow = nsnull;
}
}
mDocument = nsnull; // Forces Release
mDoc = nsnull;
NS_ASSERTION(!mArguments, "mArguments wasn't cleaned up properly!");
CleanUp();
CleanUp(PR_TRUE);
#ifdef DEBUG
nsCycleCollector_DEBUG_wasFreed(static_cast<nsIScriptGlobalObject*>(this));
#endif
delete mPendingStorageEventsObsolete;
nsLayoutStatics::Release();
}
// static
@ -870,57 +815,226 @@ nsGlobalWindow::CleanupCachedXBLHandlers(nsGlobalWindow* aWindow)
}
void
nsGlobalWindow::CleanUp()
nsGlobalWindow::MaybeForgiveSpamCount()
{
mNavigator = nsnull;
mScreen = nsnull;
mHistory = nsnull;
mMenubar = nsnull;
mToolbar = nsnull;
mLocationbar = nsnull;
mPersonalbar = nsnull;
mStatusbar = nsnull;
mScrollbars = nsnull;
mLocation = nsnull;
mFrames = nsnull;
mApplicationCache = nsnull;
if (IsOuterWindow() &&
IsPopupSpamWindow())
{
SetPopupSpamWindow(PR_FALSE);
--gOpenPopupSpamCount;
NS_ASSERTION(gOpenPopupSpamCount >= 0,
"Unbalanced decrement of gOpenPopupSpamCount");
}
}
ClearControllers();
void
nsGlobalWindow::CleanUp(PRBool aIgnoreModalDialog)
{
if (IsOuterWindow() && !aIgnoreModalDialog) {
nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
nsCOMPtr<nsIDOMModalContentWindow>
dlg(do_QueryInterface(static_cast<nsPIDOMWindow*>(inner)));
if (dlg) {
// The window we're trying to clean up is the outer window of a
// modal dialog. Defer cleanup until the window closes, and let
// ShowModalDialog take care of calling CleanUp.
mCallCleanUpAfterModalDialogCloses = PR_TRUE;
return;
}
}
mOpener = nsnull; // Forces Release
if (mContext) {
// Guarantee idempotence.
if (mCleanedUp)
return;
mCleanedUp = PR_TRUE;
// <SetDocShell(nsnull)>
{
NS_ASSERTION(!mDocShell, "Should already have nulled out mDocShell.");
MaybeForgiveSpamCount();
NS_ASSERTION(PR_CLIST_IS_EMPTY(&mTimeouts),
"Uh, outer window holds timeouts!");
// Make sure that this is called before we null out the document.
NotifyDOMWindowDestroyed(this);
nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal();
if (currentInner) {
NS_ASSERTION(mDoc, "Must have doc!");
// Remember the document's principal.
mDocumentPrincipal = mDoc->NodePrincipal();
// Release our document reference
mDocument = nsnull;
mDoc = nsnull;
}
PRUint32 lang_id;
nsIScriptContext *langCtx;
// SetDocShell(nsnull) means the window is being torn down. Drop our
// reference to the script context, allowing it to be deleted
// later. Meanwhile, keep our weak reference to the script object
// (mJSObject) so that it can be retrieved later (until it is
// finalized by the JS GC).
// clear all scopes
NS_STID_FOR_ID(lang_id) {
langCtx = mScriptContexts[NS_STID_INDEX(lang_id)];
if (langCtx)
langCtx->ClearScope(mScriptGlobals[NS_STID_INDEX(lang_id)], PR_TRUE);
}
ClearControllers();
mChromeEventHandler = nsnull; // force release now
if (mArguments) {
// We got no new document after someone called
// SetArguments(), drop our reference to the arguments.
mArguments = nsnull;
mArgumentsLast = nsnull;
mArgumentsOrigin = nsnull;
}
PRUint32 st_ndx;
// Drop holders and tell each context to cleanup and release them now.
NS_ASSERTION(mContext == mScriptContexts[NS_STID_INDEX(nsIProgrammingLanguage::JAVASCRIPT)],
"Contexts confused");
NS_STID_FOR_INDEX(st_ndx) {
mInnerWindowHolders[st_ndx] = nsnull;
langCtx = mScriptContexts[st_ndx];
if (langCtx) {
langCtx->GC();
langCtx->FinalizeContext();
mScriptContexts[st_ndx] = nsnull;
}
}
}
// </SetDocShell(nsnull)>
// <~nsGlobalWindow()>
{
if (mObserver) {
nsCOMPtr<nsIObserverService> os =
do_GetService("@mozilla.org/observer-service;1");
if (os) {
os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
os->RemoveObserver(mObserver, "dom-storage2-changed");
os->RemoveObserver(mObserver, "dom-storage-changed");
}
// Drop its reference to this dying window, in case for some bogus reason
// the object stays around.
mObserver->Forget();
NS_RELEASE(mObserver);
}
if (IsOuterWindow()) {
// An outer window is destroyed with inner windows still possibly
// alive, iterate through the inner windows, free their inner
// objects, and pull them out of the list of inner windows.
nsGlobalWindow *w;
while ((w = (nsGlobalWindow *)PR_LIST_HEAD(this)) != this) {
NS_ASSERTION(w->mOuterWindow == this, "Uh, bad outer window pointer?");
w->FreeInnerObjects(PR_TRUE);
w->mOuterWindow = nsnull;
PR_REMOVE_AND_INIT_LINK(w);
}
} else {
if (mListenerManager) {
mListenerManager->Disconnect();
mListenerManager = nsnull;
}
// An inner window is destroyed, pull it out of the outer window's
// list if inner windows.
PR_REMOVE_LINK(this);
// If our outer window's inner window is this window, null out the
// outer window's reference to this window that's being deleted.
nsGlobalWindow *outer = GetOuterWindowInternal();
if (outer && outer->mInnerWindow == this) {
outer->mInnerWindow = nsnull;
}
}
mDocument = nsnull; // Forces Release
mDoc = nsnull;
NS_ASSERTION(!mArguments, "mArguments wasn't cleaned up properly!");
}
// </~nsGlobalWindow()>
// <CleanUp()>
{
mNavigator = nsnull;
mScreen = nsnull;
mHistory = nsnull;
mMenubar = nsnull;
mToolbar = nsnull;
mLocationbar = nsnull;
mPersonalbar = nsnull;
mStatusbar = nsnull;
mScrollbars = nsnull;
mLocation = nsnull;
mFrames = nsnull;
mApplicationCache = nsnull;
ClearControllers();
mOpener = nsnull; // Forces Release
if (mContext) {
#ifdef DEBUG
nsCycleCollector_DEBUG_shouldBeFreed(mContext);
nsCycleCollector_DEBUG_shouldBeFreed(mContext);
#endif
mContext = nsnull; // Forces Release
mContext = nsnull; // Forces Release
}
mChromeEventHandler = nsnull; // Forces Release
nsGlobalWindow *inner = GetCurrentInnerWindowInternal();
if (inner) {
inner->CleanUp(aIgnoreModalDialog);
}
if (mHasAcceleration) {
nsCOMPtr<nsIAccelerometer> ac = do_GetService(NS_ACCELEROMETER_CONTRACTID);
if (ac)
ac->RemoveWindowListener(this);
}
PRUint32 scriptIndex;
NS_STID_FOR_INDEX(scriptIndex) {
mInnerWindowHolders[scriptIndex] = nsnull;
}
mArguments = nsnull;
mArgumentsLast = nsnull;
mArgumentsOrigin = nsnull;
CleanupCachedXBLHandlers(this);
#ifdef DEBUG
nsCycleCollector_DEBUG_shouldBeFreed(static_cast<nsIScriptGlobalObject*>(this));
#endif
}
mChromeEventHandler = nsnull; // Forces Release
// </CleanUp()>
nsGlobalWindow *inner = GetCurrentInnerWindowInternal();
if (inner) {
inner->CleanUp();
// <~nsGlobalWindow()>
{
delete mPendingStorageEventsObsolete;
nsLayoutStatics::Release();
}
if (mHasAcceleration) {
nsCOMPtr<nsIAccelerometer> ac = do_GetService(NS_ACCELEROMETER_CONTRACTID);
if (ac)
ac->RemoveWindowListener(this);
}
PRUint32 scriptIndex;
NS_STID_FOR_INDEX(scriptIndex) {
mInnerWindowHolders[scriptIndex] = nsnull;
}
mArguments = nsnull;
mArgumentsLast = nsnull;
mArgumentsOrigin = nsnull;
CleanupCachedXBLHandlers(this);
#ifdef DEBUG
nsCycleCollector_DEBUG_shouldBeFreed(static_cast<nsIScriptGlobalObject*>(this));
#endif
// </~nsGlobalWindow()>
}
void
@ -2127,93 +2241,6 @@ nsGlobalWindow::SetDocShell(nsIDocShell* aDocShell)
if (aDocShell == mDocShell)
return;
if (!aDocShell && // window is closing
IsOuterWindow() && IsPopupSpamWindow())
{
SetPopupSpamWindow(PR_FALSE);
--gOpenPopupSpamCount;
NS_ASSERTION(gOpenPopupSpamCount >= 0,
"Unbalanced decrement of gOpenPopupSpamCount");
}
PRUint32 lang_id;
nsIScriptContext *langCtx;
// SetDocShell(nsnull) means the window is being torn down. Drop our
// reference to the script context, allowing it to be deleted
// later. Meanwhile, keep our weak reference to the script object
// (mJSObject) so that it can be retrieved later (until it is
// finalized by the JS GC).
if (!aDocShell) {
NS_ASSERTION(PR_CLIST_IS_EMPTY(&mTimeouts),
"Uh, outer window holds timeouts!");
// Call FreeInnerObjects on all inner windows, not just the current
// one, since some could be held by WindowStateHolder objects that
// are GC-owned.
for (nsRefPtr<nsGlobalWindow> inner = (nsGlobalWindow *)PR_LIST_HEAD(this);
inner != this;
inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) {
NS_ASSERTION(inner->mOuterWindow == this, "bad outer window pointer");
inner->FreeInnerObjects(PR_TRUE);
}
// Make sure that this is called before we null out the document.
NotifyDOMWindowDestroyed(this);
nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal();
if (currentInner) {
NS_ASSERTION(mDoc, "Must have doc!");
// Remember the document's principal.
mDocumentPrincipal = mDoc->NodePrincipal();
// Release our document reference
mDocument = nsnull;
mDoc = nsnull;
}
// clear all scopes
NS_STID_FOR_ID(lang_id) {
langCtx = mScriptContexts[NS_STID_INDEX(lang_id)];
if (langCtx)
langCtx->ClearScope(mScriptGlobals[NS_STID_INDEX(lang_id)], PR_TRUE);
}
ClearControllers();
mChromeEventHandler = nsnull; // force release now
if (mArguments) {
// We got no new document after someone called
// SetArguments(), drop our reference to the arguments.
mArguments = nsnull;
mArgumentsLast = nsnull;
mArgumentsOrigin = nsnull;
}
PRUint32 st_ndx;
// Drop holders and tell each context to cleanup and release them now.
NS_ASSERTION(mContext == mScriptContexts[NS_STID_INDEX(nsIProgrammingLanguage::JAVASCRIPT)],
"Contexts confused");
NS_STID_FOR_INDEX(st_ndx) {
mInnerWindowHolders[st_ndx] = nsnull;
langCtx = mScriptContexts[st_ndx];
if (langCtx) {
langCtx->GC();
langCtx->FinalizeContext();
mScriptContexts[st_ndx] = nsnull;
}
}
#ifdef DEBUG
nsCycleCollector_DEBUG_shouldBeFreed(mContext);
nsCycleCollector_DEBUG_shouldBeFreed(static_cast<nsIScriptGlobalObject*>(this));
#endif
mContext = nsnull; // we nuked it above also
}
mDocShell = aDocShell; // Weak Reference
if (mNavigator)
@ -2227,7 +2254,10 @@ nsGlobalWindow::SetDocShell(nsIDocShell* aDocShell)
if (mScreen)
mScreen->SetDocShell(aDocShell);
if (mDocShell) {
if (!mDocShell) {
NS_ASSERTION(!mCleanedUp, "Should be calling CleanUp for the first time.");
CleanUp(PR_FALSE);
} else {
// tell our member elements about the new browserwindow
if (mMenubar) {
nsCOMPtr<nsIWebBrowserChrome> browserChrome;
@ -5506,19 +5536,30 @@ nsGlobalWindow::PostMessageMoz(const nsAString& aMessage, const nsAString& aOrig
}
class nsCloseEvent : public nsRunnable {
public:
nsCloseEvent (nsGlobalWindow *aWindow)
nsRefPtr<nsGlobalWindow> mWindow;
nsCloseEvent(nsGlobalWindow *aWindow)
: mWindow(aWindow)
{
{}
public:
static nsresult
PostCloseEvent(nsGlobalWindow* aWindow) {
nsCOMPtr<nsIRunnable> ev = new nsCloseEvent(aWindow);
nsresult rv = NS_DispatchToCurrentThread(ev);
if (NS_SUCCEEDED(rv))
aWindow->MaybeForgiveSpamCount();
return rv;
}
NS_IMETHOD Run() {
if (mWindow)
mWindow->ReallyCloseWindow();
return NS_OK;
}
nsRefPtr<nsGlobalWindow> mWindow;
};
PRBool
@ -5686,8 +5727,7 @@ nsGlobalWindow::FinalClose()
// to really close the window.
rv = NS_ERROR_FAILURE;
if (!nsContentUtils::IsCallerChrome()) {
nsCOMPtr<nsIRunnable> ev = new nsCloseEvent(this);
rv = NS_DispatchToCurrentThread(ev);
rv = nsCloseEvent::PostCloseEvent(this);
}
if (NS_FAILED(rv)) {
@ -5749,7 +5789,7 @@ nsGlobalWindow::ReallyCloseWindow()
}
}
CleanUp();
CleanUp(PR_FALSE);
}
}
@ -6234,8 +6274,9 @@ nsGlobalWindow::ShowModalDialog(const nsAString& aURI, nsIVariant *aArgs,
}
}
nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(dlgWin));
if (canAccess) {
nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(dlgWin));
nsPIDOMWindow *inner = win->GetCurrentInnerWindow();
nsCOMPtr<nsIDOMModalContentWindow> dlgInner(do_QueryInterface(inner));
@ -6244,8 +6285,15 @@ nsGlobalWindow::ShowModalDialog(const nsAString& aURI, nsIVariant *aArgs,
dlgInner->GetReturnValue(aRetVal);
}
}
nsRefPtr<nsGlobalWindow> winInternal =
static_cast<nsGlobalWindow*>(win.get());
if (winInternal->mCallCleanUpAfterModalDialogCloses) {
winInternal->mCallCleanUpAfterModalDialogCloses = PR_FALSE;
winInternal->CleanUp(PR_TRUE);
}
}
return NS_OK;
}
@ -7944,11 +7992,8 @@ nsGlobalWindow::CloseWindow(nsISupports *aWindow)
// Need to post an event for closing, otherwise window and
// presshell etc. may get destroyed while creating frames, bug 338897.
nsCOMPtr<nsIRunnable> ev = new nsCloseEvent(globalWin);
if (ev) {
NS_DispatchToCurrentThread(ev);
}
// else if OOM, better not to close. That might cause a crash.
nsCloseEvent::PostCloseEvent(globalWin);
// If OOM, better not to close. That might cause a crash.
}
// static

View File

@ -453,10 +453,12 @@ public:
static PRBool DOMWindowDumpEnabled();
void MaybeForgiveSpamCount();
protected:
// Object Management
virtual ~nsGlobalWindow();
void CleanUp();
void CleanUp(PRBool aIgnoreModalDialog);
void ClearControllers();
nsresult FinalClose();
@ -794,6 +796,8 @@ protected:
nsCOMPtr<nsIDocument> mSuspendedDoc;
PRBool mCleanedUp, mCallCleanUpAfterModalDialogCloses;
friend class nsDOMScriptableHelper;
friend class nsDOMWindowUtils;
friend class PostMessageEvent;