Bug 1198381 - Extend setTimeout handling in nsGlobalWindow, r?smaug
The requestIdleCallback feature behaves in many ways as setTimeout
since it takes an optional timout when the idle callback will be
called regardless of the user agent being idle or not. This means that
the same mechanisms controlling setTimeout are needed for
requestIdleCallback.
MozReview-Commit-ID: 9mESsJnUexf
--- a/dom/base/Timeout.cpp
+++ b/dom/base/Timeout.cpp
@@ -13,17 +13,18 @@
namespace mozilla {
namespace dom {
Timeout::Timeout()
: mCleared(false),
mRunning(false),
mIsInterval(false),
- mPublicId(0),
+ mReason(Reason::eTimeoutOrInterval),
+ mTimeoutId(0),
mInterval(0),
mFiringDepth(0),
mNestingLevel(0),
mPopupState(openAllowed)
{
MOZ_COUNT_CTOR(Timeout);
}
--- a/dom/base/Timeout.h
+++ b/dom/base/Timeout.h
@@ -31,16 +31,18 @@ class Timeout final
public:
Timeout();
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Timeout)
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Timeout)
nsresult InitTimer(uint32_t aDelay);
+ enum class Reason { eTimeoutOrInterval, eIdleCallbackTimeout };
+
static void TimerNameCallback(nsITimer* aTimer, void* aClosure, char* aBuf,
size_t aLen);
#ifdef DEBUG
bool HasRefCntOne() const;
#endif // DEBUG
// Window for which this timeout fires
@@ -53,18 +55,20 @@ public:
bool mCleared;
// True if this is one of the timeouts that are currently running
bool mRunning;
// True if this is a repeating/interval timer
bool mIsInterval;
+ Reason mReason;
+
// Returned as value of setTimeout()
- uint32_t mPublicId;
+ uint32_t mTimeoutId;
// Interval in milliseconds
uint32_t mInterval;
// mWhen and mTimeRemaining can't be in a union, sadly, because they
// have constructors.
// Nominal time to run this timeout. Use only when timeouts are not
// suspended.
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1143,22 +1143,23 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
mHasGamepad(false),
mHasVREvents(false),
#ifdef MOZ_GAMEPAD
mHasSeenGamepadInput(false),
#endif
mNotifiedIDDestroyed(false),
mAllowScriptsToClose(false),
mTimeoutInsertionPoint(nullptr),
- mTimeoutPublicIdCounter(1),
+ mTimeoutIdCounter(1),
mTimeoutFiringDepth(0),
mSuspendDepth(0),
mFreezeDepth(0),
mFocusMethod(0),
mSerial(0),
+ mIdleCallbackTimeoutCounter(1),
#ifdef DEBUG
mSetOpenerWindowCalled(false),
#endif
#ifdef MOZ_B2G
mNetworkUploadObserverEnabled(false),
mNetworkDownloadObserverEnabled(false),
#endif
mCleanedUp(false),
@@ -7887,25 +7888,25 @@ nsGlobalWindow::MozRequestOverfill(Overf
}
void
nsGlobalWindow::ClearTimeout(int32_t aHandle)
{
MOZ_RELEASE_ASSERT(IsInnerWindow());
if (aHandle > 0) {
- ClearTimeoutOrInterval(aHandle);
+ ClearTimeoutOrInterval(aHandle, Timeout::Reason::eTimeoutOrInterval);
}
}
void
nsGlobalWindow::ClearInterval(int32_t aHandle)
{
if (aHandle > 0) {
- ClearTimeoutOrInterval(aHandle);
+ ClearTimeoutOrInterval(aHandle, Timeout::Reason::eTimeoutOrInterval);
}
}
void
nsGlobalWindow::SetResizable(bool aResizable) const
{
// nop
}
@@ -12257,16 +12258,28 @@ nsGlobalWindow::OpenInternal(const nsASt
}
//*****************************************************************************
// nsGlobalWindow: Timeout Functions
//*****************************************************************************
uint32_t sNestingLevel;
+uint32_t
+nsGlobalWindow::GetTimeoutId(Timeout::Reason aReason)
+{
+ switch (aReason) {
+ case Timeout::Reason::eIdleCallbackTimeout:
+ return ++mIdleCallbackTimeoutCounter;
+ case Timeout::Reason::eTimeoutOrInterval:
+ default:
+ return ++mTimeoutIdCounter;
+ }
+}
+
nsGlobalWindow*
nsGlobalWindow::InnerForSetTimeoutOrInterval(ErrorResult& aError)
{
nsGlobalWindow* currentInner;
nsGlobalWindow* forwardTo;
if (IsInnerWindow()) {
nsGlobalWindow* outer = GetOuterWindowInternal();
currentInner = outer ? outer->GetCurrentInnerWindowInternal() : this;
@@ -12366,17 +12379,17 @@ nsGlobalWindow::SetInterval(JSContext* a
int32_t timeout;
bool isInterval = IsInterval(aTimeout, timeout);
return SetTimeoutOrInterval(aCx, aHandler, timeout, isInterval, aError);
}
nsresult
nsGlobalWindow::SetTimeoutOrInterval(nsITimeoutHandler* aHandler,
int32_t interval, bool aIsInterval,
- int32_t* aReturn)
+ Timeout::Reason aReason, int32_t* aReturn)
{
MOZ_ASSERT(IsInnerWindow());
// If we don't have a document (we could have been unloaded since
// the call to setTimeout was made), do nothing.
if (!mDoc) {
return NS_OK;
}
@@ -12392,16 +12405,17 @@ nsGlobalWindow::SetTimeoutOrInterval(nsI
if (static_cast<uint32_t>(interval) > maxTimeoutMs) {
interval = maxTimeoutMs;
}
RefPtr<Timeout> timeout = new Timeout();
timeout->mIsInterval = aIsInterval;
timeout->mInterval = interval;
timeout->mScriptHandler = aHandler;
+ timeout->mReason = aReason;
// Now clamp the actual interval we will use for the timer based on
uint32_t nestingLevel = sNestingLevel + 1;
uint32_t realInterval = interval;
if (aIsInterval || nestingLevel >= DOM_CLAMP_TIMEOUT_NESTING_LEVEL) {
// Don't allow timeouts less than DOMMinTimeoutValue() from
// now...
realInterval = std::max(realInterval, uint32_t(DOMMinTimeoutValue()));
@@ -12466,18 +12480,18 @@ nsGlobalWindow::SetTimeoutOrInterval(nsI
// in some cases.
if (interval <= delay) {
timeout->mPopupState = gPopupControlState;
}
}
InsertTimeoutIntoList(timeout);
- timeout->mPublicId = ++mTimeoutPublicIdCounter;
- *aReturn = timeout->mPublicId;
+ timeout->mTimeoutId = GetTimeoutId(aReason);
+ *aReturn = timeout->mTimeoutId;
return NS_OK;
}
int32_t
nsGlobalWindow::SetTimeoutOrInterval(JSContext *aCx, Function& aFunction,
int32_t aTimeout,
const Sequence<JS::Value>& aArguments,
@@ -12495,17 +12509,18 @@ nsGlobalWindow::SetTimeoutOrInterval(JSC
nsCOMPtr<nsIScriptTimeoutHandler> handler =
NS_CreateJSTimeoutHandler(aCx, this, aFunction, aArguments, aError);
if (!handler) {
return 0;
}
int32_t result;
- aError = SetTimeoutOrInterval(handler, aTimeout, aIsInterval, &result);
+ aError = SetTimeoutOrInterval(handler, aTimeout, aIsInterval,
+ Timeout::Reason::eTimeoutOrInterval, &result);
return result;
}
int32_t
nsGlobalWindow::SetTimeoutOrInterval(JSContext* aCx, const nsAString& aHandler,
int32_t aTimeout, bool aIsInterval,
ErrorResult& aError)
{
@@ -12521,17 +12536,18 @@ nsGlobalWindow::SetTimeoutOrInterval(JSC
nsCOMPtr<nsIScriptTimeoutHandler> handler =
NS_CreateJSTimeoutHandler(aCx, this, aHandler, aError);
if (!handler) {
return 0;
}
int32_t result;
- aError = SetTimeoutOrInterval(handler, aTimeout, aIsInterval, &result);
+ aError = SetTimeoutOrInterval(handler, aTimeout, aIsInterval,
+ Timeout::Reason::eTimeoutOrInterval, &result);
return result;
}
bool
nsGlobalWindow::RunTimeoutHandler(Timeout* aTimeout,
nsIScriptContext* aScx)
{
// Hold on to the timeout in case mExpr or mFunObj releases its
@@ -12884,25 +12900,25 @@ nsGlobalWindow::RunTimeout(Timeout* aTim
dummy_timeout->remove();
timeoutExtraRef = nullptr;
MOZ_ASSERT(dummy_timeout->HasRefCntOne(), "dummy_timeout may leak");
mTimeoutInsertionPoint = last_insertion_point;
}
void
-nsGlobalWindow::ClearTimeoutOrInterval(int32_t aTimerId)
+nsGlobalWindow::ClearTimeoutOrInterval(int32_t aTimerId, Timeout::Reason aReason)
{
MOZ_RELEASE_ASSERT(IsInnerWindow());
uint32_t timerId = (uint32_t)aTimerId;
Timeout* timeout;
for (timeout = mTimeouts.getFirst(); timeout; timeout = timeout->getNext()) {
- if (timeout->mPublicId == timerId) {
+ if (timeout->mTimeoutId == timerId && timeout->mReason == aReason) {
if (timeout->mRunning) {
/* We're running from inside the timeout. Mark this
timeout for deferred deletion by the code in
RunTimeout() */
timeout->mIsInterval = false;
}
else {
/* Delete the timeout from the pending timeout list */
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1446,26 +1446,28 @@ private:
void ThawInternal();
public:
// Timeout Functions
// Language agnostic timeout function (all args passed).
// |interval| is in milliseconds.
nsresult SetTimeoutOrInterval(nsITimeoutHandler* aHandler,
int32_t interval, bool aIsInterval,
+ mozilla::dom::Timeout::Reason aReason,
int32_t* aReturn);
int32_t SetTimeoutOrInterval(JSContext* aCx,
mozilla::dom::Function& aFunction,
int32_t aTimeout,
const mozilla::dom::Sequence<JS::Value>& aArguments,
bool aIsInterval, mozilla::ErrorResult& aError);
int32_t SetTimeoutOrInterval(JSContext* aCx, const nsAString& aHandler,
int32_t aTimeout, bool aIsInterval,
mozilla::ErrorResult& aError);
- void ClearTimeoutOrInterval(int32_t aTimerId);
+ void ClearTimeoutOrInterval(int32_t aTimerId,
+ mozilla::dom::Timeout::Reason aReason);
// JS specific timeout functions (JS args grabbed from context).
nsresult ResetTimersForNonBackgroundWindow();
// The timeout implementation functions.
void RunTimeout(mozilla::dom::Timeout* aTimeout);
void RunTimeout() { RunTimeout(nullptr); }
// Return true if |aTimeout| was cleared while its handler ran.
@@ -1474,16 +1476,17 @@ public:
bool RescheduleTimeout(mozilla::dom::Timeout* aTimeout, const TimeStamp& now,
bool aRunningPendingTimeouts);
void ClearAllTimeouts();
// Insert aTimeout into the list, before all timeouts that would
// fire after it, but no earlier than mTimeoutInsertionPoint, if any.
void InsertTimeoutIntoList(mozilla::dom::Timeout* aTimeout);
static void TimerCallback(nsITimer *aTimer, void *aClosure);
+ uint32_t GetTimeoutId(mozilla::dom::Timeout::Reason aReason);
// Helper Functions
already_AddRefed<nsIDocShellTreeOwner> GetTreeOwner();
already_AddRefed<nsIBaseWindow> GetTreeOwnerWindow();
already_AddRefed<nsIWebBrowserChrome> GetWebBrowserChrome();
nsresult SecurityCheckURL(const char *aURL);
bool IsPrivateBrowsing();
@@ -1812,17 +1815,17 @@ protected:
// non-null. In that case, the dummy timeout pointed to by
// mTimeoutInsertionPoint may have a later mWhen than some of the timeouts
// that come after it.
mozilla::LinkedList<mozilla::dom::Timeout> mTimeouts;
// If mTimeoutInsertionPoint is non-null, insertions should happen after it.
// This is a dummy timeout at the moment; if that ever changes, the logic in
// ResetTimersForNonBackgroundWindow needs to change.
mozilla::dom::Timeout* mTimeoutInsertionPoint;
- uint32_t mTimeoutPublicIdCounter;
+ uint32_t mTimeoutIdCounter;
uint32_t mTimeoutFiringDepth;
RefPtr<mozilla::dom::Location> mLocation;
RefPtr<nsHistory> mHistory;
RefPtr<mozilla::dom::CustomElementRegistry> mCustomElements;
// These member variables are used on both inner and the outer windows.
nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
@@ -1832,16 +1835,18 @@ protected:
uint32_t mSuspendDepth;
uint32_t mFreezeDepth;
// the method that was used to focus mFocusedNode
uint32_t mFocusMethod;
uint32_t mSerial;
+ // The current idle request callback timeout handle
+ uint32_t mIdleCallbackTimeoutCounter;
#ifdef DEBUG
bool mSetOpenerWindowCalled;
nsCOMPtr<nsIURI> mLastOpenedURI;
#endif
#ifdef MOZ_B2G
bool mNetworkUploadObserverEnabled;
bool mNetworkDownloadObserverEnabled;