Fix for
bug 512645 (Only clamp nested timeouts). r=jst.
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -235,16 +235,20 @@ static PRBool gDOMWindowDu
#if defined(DEBUG_bryner) || defined(DEBUG_chb)
#define DEBUG_PAGE_CACHE
#endif
// The shortest interval/timeout we permit
#define DOM_MIN_TIMEOUT_VALUE 10 // 10ms
+// The number of nested timeouts before we start clamping. HTML5 says 1, WebKit
+// uses 5.
+#define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
+
// The longest interval (as PRIntervalTime) we permit, or that our
// timer code can handle, really. See DELAY_INTERVAL_LIMIT in
// nsTimerImpl.h for details.
#define DOM_MAX_TIMEOUT_VALUE PR_BIT(8 * sizeof(PRIntervalTime) - 1)
#define FORWARD_TO_OUTER(method, args, err_rval) \
PR_BEGIN_MACRO \
if (IsInnerWindow()) { \
@@ -7660,31 +7664,35 @@ nsGlobalWindow::ClearWindowScope(nsISupp
}
}
}
//*****************************************************************************
// nsGlobalWindow: Timeout Functions
//*****************************************************************************
+PRUint32 sNestingLevel;
+
nsresult
nsGlobalWindow::SetTimeoutOrInterval(nsIScriptTimeoutHandler *aHandler,
PRInt32 interval,
PRBool aIsInterval, PRInt32 *aReturn)
{
FORWARD_TO_INNER(SetTimeoutOrInterval, (aHandler, interval, aIsInterval, aReturn),
NS_ERROR_NOT_INITIALIZED);
// If we don't have a document (we could have been unloaded since
// the call to setTimeout was made), do nothing.
if (!mDocument) {
return NS_OK;
}
- if (interval < DOM_MIN_TIMEOUT_VALUE) {
+ PRUint32 nestingLevel = sNestingLevel + 1;
+ if (interval < DOM_MIN_TIMEOUT_VALUE &&
+ (aIsInterval || nestingLevel >= DOM_CLAMP_TIMEOUT_NESTING_LEVEL)) {
// Don't allow timeouts less than DOM_MIN_TIMEOUT_VALUE from
// now...
interval = DOM_MIN_TIMEOUT_VALUE;
}
NS_ASSERTION(interval >= 0, "DOM_MIN_TIMEOUT_VALUE lies");
PRUint32 realInterval = interval;
@@ -7776,16 +7784,20 @@ nsGlobalWindow::SetTimeoutOrInterval(nsI
// don't create a timer for it, since that will happen when we are thawed
// and the timeout will then get a timer and run to completion.
timeout->mWhen = delta;
}
timeout->mWindow = this;
+ if (!aIsInterval) {
+ timeout->mNestingLevel = nestingLevel;
+ }
+
// No popups from timeouts by default
timeout->mPopupState = openAbused;
if (gRunningTimeoutDepth == 0 && gPopupControlState < openAbused) {
// This timeout is *not* set from another timeout and it's set
// while popups are enabled. Propagate the state to the timeout if
// its delay (interval) is equal to or less than what
// "dom.disable_open_click_delay" is set to (in ms).
@@ -7993,16 +8005,23 @@ nsGlobalWindow::RunTimeout(nsTimeout *aT
// Hold on to the timeout in case mExpr or mFunObj releases its
// doc.
timeout->AddRef();
++gRunningTimeoutDepth;
++mTimeoutFiringDepth;
+ PRBool trackNestingLevel = !timeout->mInterval;
+ PRUint32 nestingLevel;
+ if (trackNestingLevel) {
+ nestingLevel = sNestingLevel;
+ sNestingLevel = timeout->mNestingLevel;
+ }
+
nsCOMPtr<nsIScriptTimeoutHandler> handler(timeout->mScriptHandler);
void *scriptObject = handler->GetScriptObject();
if (!scriptObject) {
// Evaluate the timeout expression.
const PRUnichar *script = handler->GetHandlerText();
NS_ASSERTION(script, "timeout has no script nor handler text!");
const char *filename = nsnull;
@@ -8033,16 +8052,20 @@ nsGlobalWindow::RunTimeout(nsTimeout *aT
scriptObject, handler->GetArgv(),
// XXXmarkh - consider allowing CallEventHandler to
// accept nsnull?
getter_AddRefs(dummy));
}
handler = nsnull; // drop reference before dropping timeout refs.
+ if (trackNestingLevel) {
+ sNestingLevel = nestingLevel;
+ }
+
--mTimeoutFiringDepth;
--gRunningTimeoutDepth;
mRunningTimeout = last_running_timeout;
timeout->mRunning = PR_FALSE;
// We ignore any failures from calling EvaluateString() or
// CallEventHandler() on the context here since we're in a loop
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -180,16 +180,19 @@ struct nsTimeout : PRCList
PRTime mWhen;
// Principal with which to execute
nsCOMPtr<nsIPrincipal> mPrincipal;
// stack depth at which timeout is firing
PRUint32 mFiringDepth;
+ //
+ PRUint32 mNestingLevel;
+
// The popup state at timeout creation time if not created from
// another timeout
PopupControlState mPopupState;
// The language-specific information about the callback.
nsCOMPtr<nsIScriptTimeoutHandler> mScriptHandler;
private: