diff --git a/content/base/src/nsDocument.cpp b/content/base/src/nsDocument.cpp index 80518ad8a3f..ac7df06a8f5 100644 --- a/content/base/src/nsDocument.cpp +++ b/content/base/src/nsDocument.cpp @@ -6892,6 +6892,11 @@ nsDocument::CanSavePresentation(nsIRequest *aNewRequest) return PR_FALSE; } + nsPIDOMWindow* win = GetInnerWindow(); + if (win && win->TimeoutSuspendCount()) { + return PR_FALSE; + } + // Check our event listener manager for unload/beforeunload listeners. nsCOMPtr piTarget = do_QueryInterface(mScriptGlobalObject); if (piTarget) { diff --git a/content/base/src/nsXMLHttpRequest.cpp b/content/base/src/nsXMLHttpRequest.cpp index c6aed57b30d..c2ce3295b84 100644 --- a/content/base/src/nsXMLHttpRequest.cpp +++ b/content/base/src/nsXMLHttpRequest.cpp @@ -145,6 +145,21 @@ #define NS_PROGRESS_EVENT_INTERVAL 50 +class nsResumeTimeoutsEvent : public nsRunnable +{ +public: + nsResumeTimeoutsEvent(nsPIDOMWindow* aWindow) : mWindow(aWindow) {} + + NS_IMETHOD Run() + { + mWindow->ResumeTimeouts(PR_FALSE); + return NS_OK; + } + +private: + nsCOMPtr mWindow; +}; + NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMEventListenerWrapper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMEventListenerWrapper) @@ -2793,15 +2808,14 @@ nsXMLHttpRequest::Send(nsIVariant *aBody) nsCOMPtr topWindow; if (NS_SUCCEEDED(mOwner->GetTop(getter_AddRefs(topWindow)))) { nsCOMPtr suspendedWindow(do_QueryInterface(topWindow)); - if (suspendedWindow) { - suspendedDoc = do_QueryInterface(suspendedWindow->GetExtantDocument()); - if (suspendedDoc) { - suspendedDoc->SuppressEventHandling(); - } - suspendedWindow->SuspendTimeouts(); - resumeTimeoutRunnable = NS_NEW_RUNNABLE_METHOD(nsPIDOMWindow, - suspendedWindow.get(), - ResumeTimeouts); + if (suspendedWindow && + (suspendedWindow = suspendedWindow->GetCurrentInnerWindow())) { + suspendedDoc = do_QueryInterface(suspendedWindow->GetExtantDocument()); + if (suspendedDoc) { + suspendedDoc->SuppressEventHandling(); + } + suspendedWindow->SuspendTimeouts(1, PR_FALSE); + resumeTimeoutRunnable = new nsResumeTimeoutsEvent(suspendedWindow); } } } diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp index 0344be93834..affdd9e3d37 100644 --- a/docshell/base/nsDocShell.cpp +++ b/docshell/base/nsDocShell.cpp @@ -6020,13 +6020,20 @@ nsDocShell::RestoreFromHistory() } nsCOMPtr document = do_QueryInterface(domDoc); + PRUint32 parentSuspendCount = 0; if (document) { nsCOMPtr parent; GetParent(getter_AddRefs(parent)); nsCOMPtr parentDoc = do_GetInterface(parent); nsCOMPtr d = do_QueryInterface(parentDoc); - if (d && d->EventHandlingSuppressed()) { - document->SuppressEventHandling(d->EventHandlingSuppressed()); + if (d) { + if (d->EventHandlingSuppressed()) { + document->SuppressEventHandling(d->EventHandlingSuppressed()); + } + nsCOMPtr parentWindow = d->GetWindow(); + if (parentWindow) { + parentSuspendCount = parentWindow->TimeoutSuspendCount(); + } } // Use the uri from the mLSHE we had when we entered this function @@ -6116,6 +6123,13 @@ nsDocShell::RestoreFromHistory() } } + // If parent is suspended, increase suspension count. + // This can't be done as early as event suppression since this + // depends on docshell tree. + if (parentSuspendCount) { + privWin->SuspendTimeouts(parentSuspendCount, PR_FALSE); + } + // Now that all of the child docshells have been put into place, we can // restart the timers for the window and all of the child frames. privWin->ResumeTimeouts(); diff --git a/dom/public/base/nsPIDOMWindow.h b/dom/public/base/nsPIDOMWindow.h index ae1514326f2..39672045b3e 100644 --- a/dom/public/base/nsPIDOMWindow.h +++ b/dom/public/base/nsPIDOMWindow.h @@ -76,8 +76,8 @@ class nsScriptObjectHolder; class nsXBLPrototypeHandler; #define NS_PIDOMWINDOW_IID \ -{ 0x3d2b6b38, 0x810d, 0x4ac5, \ - { 0x81, 0x7c, 0xb9, 0x70, 0x81, 0x80, 0x4d, 0x9f } } +{ 0x80dd53b6, 0x8c61, 0x4dd6, \ + { 0xb4, 0x51, 0xf7, 0xd7, 0x5c, 0xfc, 0x51, 0x96 } } class nsPIDOMWindow : public nsIDOMWindowInternal { @@ -272,11 +272,14 @@ public: virtual nsresult RestoreWindowState(nsISupports *aState) = 0; // Suspend timeouts in this window and in child windows. - virtual void SuspendTimeouts() = 0; + virtual void SuspendTimeouts(PRUint32 aIncrease = 1, + PRBool aFreezeChildren = PR_TRUE) = 0; // Resume suspended timeouts in this window and in child windows. - virtual nsresult ResumeTimeouts() = 0; - + virtual nsresult ResumeTimeouts(PRBool aThawChildren = PR_TRUE) = 0; + + virtual PRUint32 TimeoutSuspendCount() = 0; + // Fire any DOM notification events related to things that happened while // the window was frozen. virtual nsresult FireDelayedDOMEvents() = 0; diff --git a/dom/src/base/nsGlobalWindow.cpp b/dom/src/base/nsGlobalWindow.cpp index 52ae38355d1..3b5ae52ef59 100644 --- a/dom/src/base/nsGlobalWindow.cpp +++ b/dom/src/base/nsGlobalWindow.cpp @@ -1885,6 +1885,14 @@ nsGlobalWindow::SetNewDocument(nsIDocument* aDocument, if (st_id == nsIProgrammingLanguage::JAVASCRIPT) JS_EndRequest((JSContext *)this_ctx->GetNativeContext()); } + + nsCOMPtr frame = do_QueryInterface(GetFrameElementInternal()); + if (frame && frame->GetOwnerDoc()) { + nsPIDOMWindow* parentWindow = frame->GetOwnerDoc()->GetWindow(); + if (parentWindow && parentWindow->TimeoutSuspendCount()) { + SuspendTimeouts(parentWindow->TimeoutSuspendCount()); + } + } } // Tell the contexts we have completed setting up the doc. NS_STID_FOR_ID(st_id) { @@ -2054,7 +2062,7 @@ nsGlobalWindow::SetDocShell(nsIDocShell* aDocShell) // Call FreeInnerObjects on all inner windows, not just the current // one, since some could be held by WindowStateHolder objects that // are GC-owned. - for (nsGlobalWindow *inner = (nsGlobalWindow *)PR_LIST_HEAD(this); + for (nsRefPtr inner = (nsGlobalWindow *)PR_LIST_HEAD(this); inner != this; inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) { NS_ASSERTION(inner->mOuterWindow == this, "bad outer window pointer"); @@ -8389,37 +8397,39 @@ nsGlobalWindow::RestoreWindowState(nsISupports *aState) } void -nsGlobalWindow::SuspendTimeouts() +nsGlobalWindow::SuspendTimeouts(PRUint32 aIncrease, + PRBool aFreezeChildren) { - FORWARD_TO_INNER_VOID(SuspendTimeouts, ()); + FORWARD_TO_INNER_VOID(SuspendTimeouts, (aIncrease, aFreezeChildren)); - if (++mTimeoutsSuspendDepth != 1) { - return; - } + PRBool suspended = (mTimeoutsSuspendDepth != 0); + mTimeoutsSuspendDepth += aIncrease; - nsDOMThreadService* dts = nsDOMThreadService::get(); - if (dts) { - dts->SuspendWorkersForGlobal(static_cast(this)); - } - - PRTime now = PR_Now(); - for (nsTimeout *t = FirstTimeout(); IsTimeout(t); t = t->Next()) { - // Change mWhen to be the time remaining for this timer. - if (t->mWhen > now) - t->mWhen -= now; - else - t->mWhen = 0; - - // Drop the XPCOM timer; we'll reschedule when restoring the state. - if (t->mTimer) { - t->mTimer->Cancel(); - t->mTimer = nsnull; - - // Drop the reference that the timer's closure had on this timeout, we'll - // add it back in ResumeTimeouts. Note that it shouldn't matter that we're - // passing null for the context, since this shouldn't actually release this - // timeout. - t->Release(); + if (!suspended) { + nsDOMThreadService* dts = nsDOMThreadService::get(); + if (dts) { + dts->SuspendWorkersForGlobal(static_cast(this)); + } + + PRTime now = PR_Now(); + for (nsTimeout *t = FirstTimeout(); IsTimeout(t); t = t->Next()) { + // Change mWhen to be the time remaining for this timer. + if (t->mWhen > now) + t->mWhen -= now; + else + t->mWhen = 0; + + // Drop the XPCOM timer; we'll reschedule when restoring the state. + if (t->mTimer) { + t->mTimer->Cancel(); + t->mTimer = nsnull; + + // Drop the reference that the timer's closure had on this timeout, we'll + // add it back in ResumeTimeouts. Note that it shouldn't matter that we're + // passing null for the context, since this shouldn't actually release this + // timeout. + t->Release(); + } } } @@ -8439,12 +8449,11 @@ nsGlobalWindow::SuspendTimeouts() nsGlobalWindow *win = static_cast (static_cast(pWin)); - - win->SuspendTimeouts(); + win->SuspendTimeouts(aIncrease, aFreezeChildren); NS_ASSERTION(win->IsOuterWindow(), "Expected outer window"); nsGlobalWindow* inner = win->GetCurrentInnerWindowInternal(); - if (inner) { + if (inner && aFreezeChildren) { inner->Freeze(); } } @@ -8453,67 +8462,68 @@ nsGlobalWindow::SuspendTimeouts() } nsresult -nsGlobalWindow::ResumeTimeouts() +nsGlobalWindow::ResumeTimeouts(PRBool aThawChildren) { FORWARD_TO_INNER(ResumeTimeouts, (), NS_ERROR_NOT_INITIALIZED); NS_ASSERTION(mTimeoutsSuspendDepth, "Mismatched calls to ResumeTimeouts!"); - if (--mTimeoutsSuspendDepth != 0) { - return NS_OK; - } - - nsDOMThreadService* dts = nsDOMThreadService::get(); - if (dts) { - dts->ResumeWorkersForGlobal(static_cast(this)); - } - - // Restore all of the timeouts, using the stored time remaining - // (stored in timeout->mWhen). - - PRTime now = PR_Now(); + --mTimeoutsSuspendDepth; + PRBool shouldResume = (mTimeoutsSuspendDepth == 0); nsresult rv; -#ifdef DEBUG - PRBool _seenDummyTimeout = PR_FALSE; -#endif - - for (nsTimeout *t = FirstTimeout(); IsTimeout(t); t = t->Next()) { - // There's a chance we're being called with RunTimeout on the stack in which - // case we have a dummy timeout in the list that *must not* be resumed. It - // can be identified by a null mWindow. - if (!t->mWindow) { -#ifdef DEBUG - NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!"); - _seenDummyTimeout = PR_TRUE; -#endif - continue; + if (shouldResume) { + nsDOMThreadService* dts = nsDOMThreadService::get(); + if (dts) { + dts->ResumeWorkersForGlobal(static_cast(this)); } - // Make sure to cast the unsigned PR_USEC_PER_MSEC to signed - // PRTime to make the division do the right thing on 64-bit - // platforms whether t->mWhen is positive or negative (which is - // likely to always be positive here, but cast anyways for - // consistency). - PRUint32 delay = - PR_MAX(((PRUint32)(t->mWhen / (PRTime)PR_USEC_PER_MSEC)), - DOM_MIN_TIMEOUT_VALUE); + // Restore all of the timeouts, using the stored time remaining + // (stored in timeout->mWhen). - // Set mWhen back to the time when the timer is supposed to - // fire. - t->mWhen += now; + PRTime now = PR_Now(); - t->mTimer = do_CreateInstance("@mozilla.org/timer;1"); - NS_ENSURE_TRUE(t->mTimer, NS_ERROR_OUT_OF_MEMORY); +#ifdef DEBUG + PRBool _seenDummyTimeout = PR_FALSE; +#endif - rv = t->mTimer->InitWithFuncCallback(TimerCallback, t, delay, - nsITimer::TYPE_ONE_SHOT); - if (NS_FAILED(rv)) { - t->mTimer = nsnull; - return rv; + for (nsTimeout *t = FirstTimeout(); IsTimeout(t); t = t->Next()) { + // There's a chance we're being called with RunTimeout on the stack in which + // case we have a dummy timeout in the list that *must not* be resumed. It + // can be identified by a null mWindow. + if (!t->mWindow) { +#ifdef DEBUG + NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!"); + _seenDummyTimeout = PR_TRUE; +#endif + continue; + } + + // Make sure to cast the unsigned PR_USEC_PER_MSEC to signed + // PRTime to make the division do the right thing on 64-bit + // platforms whether t->mWhen is positive or negative (which is + // likely to always be positive here, but cast anyways for + // consistency). + PRUint32 delay = + PR_MAX(((PRUint32)(t->mWhen / (PRTime)PR_USEC_PER_MSEC)), + DOM_MIN_TIMEOUT_VALUE); + + // Set mWhen back to the time when the timer is supposed to + // fire. + t->mWhen += now; + + t->mTimer = do_CreateInstance("@mozilla.org/timer;1"); + NS_ENSURE_TRUE(t->mTimer, NS_ERROR_OUT_OF_MEMORY); + + rv = t->mTimer->InitWithFuncCallback(TimerCallback, t, delay, + nsITimer::TYPE_ONE_SHOT); + if (NS_FAILED(rv)) { + t->mTimer = nsnull; + return rv; + } + + // Add a reference for the new timer's closure. + t->AddRef(); } - - // Add a reference for the new timer's closure. - t->AddRef(); } // Resume our children as well. @@ -8536,11 +8546,11 @@ nsGlobalWindow::ResumeTimeouts() NS_ASSERTION(win->IsOuterWindow(), "Expected outer window"); nsGlobalWindow* inner = win->GetCurrentInnerWindowInternal(); - if (inner) { + if (inner && aThawChildren) { inner->Thaw(); } - rv = win->ResumeTimeouts(); + rv = win->ResumeTimeouts(aThawChildren); NS_ENSURE_SUCCESS(rv, rv); } } @@ -8549,6 +8559,13 @@ nsGlobalWindow::ResumeTimeouts() return NS_OK; } +PRUint32 +nsGlobalWindow::TimeoutSuspendCount() +{ + FORWARD_TO_INNER(TimeoutSuspendCount, (), 0); + return mTimeoutsSuspendDepth; +} + NS_IMETHODIMP nsGlobalWindow::GetScriptTypeID(PRUint32 *aScriptType) { diff --git a/dom/src/base/nsGlobalWindow.h b/dom/src/base/nsGlobalWindow.h index ac01a7f8153..c6a56f618b2 100644 --- a/dom/src/base/nsGlobalWindow.h +++ b/dom/src/base/nsGlobalWindow.h @@ -303,8 +303,10 @@ public: virtual NS_HIDDEN_(nsresult) SaveWindowState(nsISupports **aState); virtual NS_HIDDEN_(nsresult) RestoreWindowState(nsISupports *aState); - virtual NS_HIDDEN_(void) SuspendTimeouts(); - virtual NS_HIDDEN_(nsresult) ResumeTimeouts(); + virtual NS_HIDDEN_(void) SuspendTimeouts(PRUint32 aIncrease = 1, + PRBool aFreezeChildren = PR_TRUE); + virtual NS_HIDDEN_(nsresult) ResumeTimeouts(PRBool aThawChildren = PR_TRUE); + virtual NS_HIDDEN_(PRUint32) TimeoutSuspendCount(); virtual NS_HIDDEN_(nsresult) FireDelayedDOMEvents(); virtual NS_HIDDEN_(PRBool) IsFrozen() const {