Bug 1313864 - Make IdleRequest dispatch itself. r?bkelly
Also let the throttled idle callbacks schedule themselves by calling
PostThrottledIdleCallback indirectly by using SetTimeoutOrInterval.
MozReview-Commit-ID: EuJfpkM6Acf
--- a/dom/base/IdleRequest.cpp
+++ b/dom/base/IdleRequest.cpp
@@ -11,23 +11,26 @@
#include "mozilla/dom/IdleDeadline.h"
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/PerformanceTiming.h"
#include "mozilla/dom/WindowBinding.h"
#include "nsComponentManagerUtils.h"
#include "nsGlobalWindow.h"
#include "nsISupportsPrimitives.h"
#include "nsPIDOMWindow.h"
+#include "nsThreadUtils.h"
+#include "RunnableTimeoutHandler.h"
namespace mozilla {
namespace dom {
IdleRequest::IdleRequest(JSContext* aCx, nsPIDOMWindowInner* aWindow,
IdleRequestCallback& aCallback, uint32_t aHandle)
- : mWindow(aWindow)
+ : mRunning(false)
+ , mWindow(aWindow)
, mCallback(&aCallback)
, mHandle(aHandle)
, mTimeoutHandle(Nothing())
{
MOZ_ASSERT(aWindow);
// Get the calling location.
nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn);
@@ -66,36 +69,53 @@ IdleRequest::SetTimeout(uint32_t aTimeou
int32_t handle;
nsresult rv = nsGlobalWindow::Cast(mWindow)->SetTimeoutOrInterval(
this, aTimeout, false, Timeout::Reason::eIdleCallbackTimeout, &handle);
mTimeoutHandle = Some(handle);
return rv;
}
+void
+IdleRequest::Dispatch()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_DIAGNOSTIC_ASSERT(!mRunning);
+
+ RefPtr<IdleRequest> kungFuDeathGrip(this);
+ NS_IdleDispatchToCurrentThread(kungFuDeathGrip.forget());
+ mRunning = true;
+}
+
nsresult
IdleRequest::Run()
{
+ MOZ_ASSERT(NS_IsMainThread());
if (mCallback) {
RunIdleRequestCallback(false);
}
return NS_OK;
}
nsresult
IdleRequest::Cancel()
{
mCallback = nullptr;
CancelTimeout();
+
if (isInList()) {
remove();
}
Release();
+ if (mRunning) {
+ nsGlobalWindow::Cast(mWindow)->DispatchNextIdleRequest();
+ }
+
return NS_OK;
}
void
IdleRequest::SetDeadline(TimeStamp aDeadline)
{
mozilla::dom::Performance* perf = mWindow->GetPerformance();
mDeadline =
@@ -106,24 +126,27 @@ nsresult
IdleRequest::RunIdleRequestCallback(bool aDidTimeout)
{
MOZ_ASSERT(NS_IsMainThread());
if (!aDidTimeout) {
CancelTimeout();
}
- remove();
ErrorResult error;
RefPtr<IdleDeadline> deadline =
new IdleDeadline(mWindow, aDidTimeout, mDeadline);
mCallback->Call(*deadline, error, "requestIdleCallback handler");
mCallback = nullptr;
+
+ remove();
Release();
+ nsGlobalWindow::Cast(mWindow)->DispatchNextIdleRequest();
+
return error.StealNSResult();
}
void
IdleRequest::CancelTimeout()
{
if (mTimeoutHandle.isSome()) {
nsGlobalWindow::Cast(mWindow)->ClearTimeoutOrInterval(
--- a/dom/base/IdleRequest.h
+++ b/dom/base/IdleRequest.h
@@ -44,28 +44,30 @@ public:
virtual nsresult Cancel() override;
virtual void SetDeadline(mozilla::TimeStamp aDeadline) override;
uint32_t Handle() const
{
return mHandle;
}
+ void Dispatch();
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequest, nsITimeoutHandler)
private:
~IdleRequest();
// filename, line number and JS language version string of the
// caller of setTimeout()
nsCString mFileName;
uint32_t mLineNo;
uint32_t mColumn;
+ bool mRunning;
nsCOMPtr<nsPIDOMWindowInner> mWindow;
RefPtr<IdleRequestCallback> mCallback;
uint32_t mHandle;
mozilla::Maybe<int32_t> mTimeoutHandle;
DOMHighResTimeStamp mDeadline;
};
} // namespace dom
new file mode 100644
--- /dev/null
+++ b/dom/base/RunnableTimeoutHandler.cpp
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RunnableTimeoutHandler.h"
+
+#include "nsIRunnable.h"
+
+namespace mozilla {
+namespace dom {
+
+RunnableTimeoutHandler::RunnableTimeoutHandler(
+ already_AddRefed<nsIRunnable> aRunnable)
+ : mRunnable(Move(aRunnable))
+{
+ MOZ_ASSERT(mRunnable);
+}
+
+nsresult
+RunnableTimeoutHandler::Call()
+{
+ return mRunnable->Run();
+}
+
+void
+RunnableTimeoutHandler::GetLocation(const char** aFileName, uint32_t* aLineNo,
+ uint32_t* aColumn)
+{
+ *aFileName = "";
+ *aLineNo = 0;
+ *aColumn = 0;
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(RunnableTimeoutHandler)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(RunnableTimeoutHandler)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(RunnableTimeoutHandler)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(RunnableTimeoutHandler)
+NS_IMPL_CYCLE_COLLECTION_UNLINK(mRunnable)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RunnableTimeoutHandler)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRunnable)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RunnableTimeoutHandler)
+NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler)
+NS_INTERFACE_MAP_END
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/RunnableTimeoutHandler.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_timeout_handler_h
+#define mozilla_dom_timeout_handler_h
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "nsCOMPtr.h"
+#include "nsISupports.h"
+#include "nsITimeoutHandler.h"
+
+class nsIRunnable;
+
+namespace mozilla {
+namespace dom {
+
+class RunnableTimeoutHandler final : public nsITimeoutHandler
+{
+public:
+ explicit RunnableTimeoutHandler(already_AddRefed<nsIRunnable> aRunnable);
+ virtual nsresult Call() override;
+ virtual void GetLocation(const char** aFileName, uint32_t* aLineNo,
+ uint32_t* aColumn) override;
+ virtual void MarkForCC() override {}
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS(RunnableTimeoutHandler)
+
+private:
+ ~RunnableTimeoutHandler() {}
+ nsCOMPtr<nsIRunnable> mRunnable;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_timeout_handler_h
--- a/dom/base/Timeout.h
+++ b/dom/base/Timeout.h
@@ -6,18 +6,20 @@
#ifndef mozilla_dom_timeout_h
#define mozilla_dom_timeout_h
#include "mozilla/LinkedList.h"
#include "mozilla/TimeStamp.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
+#include "nsPIDOMWindow.h"
class nsGlobalWindow;
+class nsIEventTarget;
class nsIPrincipal;
class nsITimeoutHandler;
class nsITimer;
namespace mozilla {
namespace dom {
/*
@@ -34,17 +36,22 @@ public:
NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(Timeout)
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(Timeout)
// The target may be specified to use a particular event queue for the
// resulting timer runnable. A nullptr target will result in the
// default main thread being used.
nsresult InitTimer(nsIEventTarget* aTarget, uint32_t aDelay);
- enum class Reason { eTimeoutOrInterval, eIdleCallbackTimeout };
+ enum class Reason
+ {
+ eTimeoutOrInterval,
+ eIdleCallbackTimeout,
+ eInternalTimeout,
+ };
#ifdef DEBUG
bool HasRefCntOne() const;
#endif // DEBUG
// Window for which this timeout fires
RefPtr<nsGlobalWindow> mWindow;
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -198,16 +198,17 @@ EXPORTS.mozilla.dom += [
'NameSpaceConstants.h',
'Navigator.h',
'NodeInfo.h',
'NodeInfoInlines.h',
'NodeIterator.h',
'PartialSHistory.h',
'ProcessGlobal.h',
'ResponsiveImageSelector.h',
+ 'RunnableTimeoutHandler.h',
'SameProcessMessageQueue.h',
'ScreenOrientation.h',
'ScriptSettings.h',
'ShadowRoot.h',
'StructuredCloneHolder.h',
'StructuredCloneTags.h',
'StyleSheetList.h',
'SubtleCrypto.h',
@@ -343,16 +344,17 @@ UNIFIED_SOURCES += [
'nsWrapperCache.cpp',
'nsXHTMLContentSerializer.cpp',
'nsXMLContentSerializer.cpp',
'nsXMLNameSpaceMap.cpp',
'PartialSHistory.cpp',
'PostMessageEvent.cpp',
'ProcessGlobal.cpp',
'ResponsiveImageSelector.cpp',
+ 'RunnableTimeoutHandler.cpp',
'SameProcessMessageQueue.cpp',
'ScreenOrientation.cpp',
'ScriptSettings.cpp',
'ShadowRoot.cpp',
'StructuredCloneHolder.cpp',
'StyleSheetList.cpp',
'SubtleCrypto.cpp',
'TabGroup.cpp',
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -15,16 +15,17 @@
#include "nsContentSecurityManager.h"
#include "nsScreen.h"
#include "nsHistory.h"
#include "nsDOMNavigationTiming.h"
#include "nsIDOMStorageManager.h"
#include "mozilla/dom/DOMStorage.h"
#include "mozilla/dom/IdleRequest.h"
#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/RunnableTimeoutHandler.h"
#include "mozilla/dom/StorageEvent.h"
#include "mozilla/dom/StorageEventBinding.h"
#include "mozilla/dom/Timeout.h"
#include "mozilla/IntegerPrintfMacros.h"
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
#include "mozilla/dom/WindowOrientationObserver.h"
#endif
#include "nsDOMOfflineResourceList.h"
@@ -546,28 +547,48 @@ DialogValueHolder::Get(JSContext* aCx, J
aError = nsContentUtils::XPConnect()->VariantToJS(aCx, aScope,
mValue, aResult);
} else {
aResult.setUndefined();
}
}
void
-nsGlobalWindow::PostThrottledIdleCallback()
-{
- AssertIsOnMainThread();
-
- if (mThrottledIdleRequestCallbacks.isEmpty())
- return;
-
- RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
- // ownership transferred from mThrottledIdleRequestCallbacks to
- // mIdleRequestCallbacks
- mIdleRequestCallbacks.insertBack(request);
- NS_IdleDispatchToCurrentThread(request.forget());
+nsGlobalWindow::DispatchIdleRequest()
+{
+ if (RefPtr<IdleRequest> request = mIdleRequestCallbacks.getFirst()) {
+ request->Dispatch();
+ }
+}
+
+void
+nsGlobalWindow::DispatchNextIdleRequest()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mIdleRequestCallbacks.isEmpty()) {
+ return;
+ }
+
+ nsGlobalWindow* outer = GetOuterWindowInternal();
+ if (outer && outer->AsOuter()->IsBackground()) {
+ int32_t dummy;
+ RefPtr<RunnableTimeoutHandler> timeout(new RunnableTimeoutHandler(
+ NewRunnableMethod(this, &nsGlobalWindow::DispatchIdleRequest)));
+ // We really want to re-use the mechanisms in SetTimeoutOrInterval
+ // to handle throttling, but due to Bug 1316871 we can't do that
+ // yet. When that is fixed we can instead of passing
+ // gMinBackgroundTimeoutValue just pass 0.
+ SetTimeoutOrInterval(timeout, gMinBackgroundTimeoutValue, false,
+ Timeout::Reason::eInternalTimeout, &dummy);
+
+ return;
+ }
+
+ RefPtr<IdleRequest> request(mIdleRequestCallbacks.getFirst());
+ request->Dispatch();
}
/* static */ void
nsGlobalWindow::InsertIdleCallbackIntoList(IdleRequest* aRequest,
IdleRequests& aList)
{
aList.insertBack(aRequest);
aRequest->AddRef();
@@ -589,31 +610,25 @@ nsGlobalWindow::RequestIdleCallback(JSCo
if (aOptions.mTimeout.WasPassed()) {
aError = request->SetTimeout(aOptions.mTimeout.Value());
if (NS_WARN_IF(aError.Failed())) {
return 0;
}
}
- nsGlobalWindow* outer = GetOuterWindowInternal();
- if (outer && outer->AsOuter()->IsBackground()) {
- // mThrottledIdleRequestCallbacks now owns request
- InsertIdleCallbackIntoList(request, mThrottledIdleRequestCallbacks);
-
- NS_DelayedDispatchToCurrentThread(
- NewRunnableMethod(this, &nsGlobalWindow::PostThrottledIdleCallback),
- 10000);
- } else {
- MOZ_ASSERT(mThrottledIdleRequestCallbacks.isEmpty());
-
- // mIdleRequestCallbacks now owns request
- InsertIdleCallbackIntoList(request, mIdleRequestCallbacks);
-
- NS_IdleDispatchToCurrentThread(request.forget());
+ // If the list of idle callback requests is not empty it means that
+ // we've already dispatched the first idle request. It is the
+ // responsibility of that to dispatch the next.
+ bool needsScheduling = mIdleRequestCallbacks.isEmpty();
+ // mIdleRequestCallbacks now owns request
+ InsertIdleCallbackIntoList(request, mIdleRequestCallbacks);
+
+ if (needsScheduling) {
+ DispatchNextIdleRequest();
}
return handle;
}
void
nsGlobalWindow::CancelIdleCallback(uint32_t aHandle)
{
@@ -629,34 +644,17 @@ nsGlobalWindow::CancelIdleCallback(uint3
void
nsGlobalWindow::DisableIdleCallbackRequests()
{
while (!mIdleRequestCallbacks.isEmpty()) {
RefPtr<IdleRequest> request = mIdleRequestCallbacks.popFirst();
request->Cancel();
}
-
- while (!mThrottledIdleRequestCallbacks.isEmpty()) {
- RefPtr<IdleRequest> request = mThrottledIdleRequestCallbacks.popFirst();
- request->Cancel();
- }
-}
-
-void nsGlobalWindow::UnthrottleIdleCallbackRequests()
-{
- AssertIsOnMainThread();
-
- while (!mThrottledIdleRequestCallbacks.isEmpty()) {
- RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
- mIdleRequestCallbacks.insertBack(request);
- NS_IdleDispatchToCurrentThread(request.forget());
- }
-}
-
+}
namespace mozilla {
namespace dom {
extern uint64_t
NextWindowID();
} // namespace dom
} // namespace mozilla
@@ -1977,20 +1975,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents)
for (IdleRequest* request : tmp->mIdleRequestCallbacks) {
cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest));
}
- for (IdleRequest* request : tmp->mThrottledIdleRequestCallbacks) {
- cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest));
- }
-
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers)
#ifdef MOZ_GAMEPAD
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads)
#endif
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDisplays)
@@ -10068,22 +10062,16 @@ void nsGlobalWindow::SetIsBackground(boo
MOZ_ASSERT(IsOuterWindow());
bool resetTimers = (!aIsBackground && AsOuter()->IsBackground());
nsPIDOMWindow::SetIsBackground(aIsBackground);
if (resetTimers) {
ResetTimersForNonBackgroundWindow();
}
- if (!aIsBackground) {
- nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
- if (inner) {
- inner->UnthrottleIdleCallbackRequests();
- }
- }
#ifdef MOZ_GAMEPAD
if (!aIsBackground) {
nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
if (inner) {
inner->SyncGamepadState();
}
}
#endif
@@ -12489,18 +12477,22 @@ uint32_t sNestingLevel;
uint32_t
nsGlobalWindow::GetTimeoutId(Timeout::Reason aReason)
{
switch (aReason) {
case Timeout::Reason::eIdleCallbackTimeout:
return ++mIdleCallbackTimeoutCounter;
case Timeout::Reason::eTimeoutOrInterval:
+ return ++mTimeoutIdCounter;
+ case Timeout::Reason::eInternalTimeout:
+ return 0;
default:
- return ++mTimeoutIdCounter;
+ MOZ_ASSERT_UNREACHABLE("Unhandled timeout reason");
+ return 0;
}
}
nsGlobalWindow*
nsGlobalWindow::InnerForSetTimeoutOrInterval(ErrorResult& aError)
{
nsGlobalWindow* currentInner;
nsGlobalWindow* forwardTo;
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1860,30 +1860,30 @@ protected:
uint32_t mFreezeDepth;
// the method that was used to focus mFocusedNode
uint32_t mFocusMethod;
uint32_t mSerial;
void DisableIdleCallbackRequests();
- void UnthrottleIdleCallbackRequests();
+
- void PostThrottledIdleCallback();
+ void DispatchNextIdleRequest();
+ void DispatchIdleRequest();
typedef mozilla::LinkedList<mozilla::dom::IdleRequest> IdleRequests;
static void InsertIdleCallbackIntoList(mozilla::dom::IdleRequest* aRequest,
IdleRequests& aList);
// The current idle request callback timeout handle
uint32_t mIdleCallbackTimeoutCounter;
// The current idle request callback handle
uint32_t mIdleRequestCallbackCounter;
IdleRequests mIdleRequestCallbacks;
- IdleRequests mThrottledIdleRequestCallbacks;
#ifdef DEBUG
bool mSetOpenerWindowCalled;
nsCOMPtr<nsIURI> mLastOpenedURI;
#endif
#ifdef MOZ_B2G
bool mNetworkUploadObserverEnabled;
@@ -1949,16 +1949,17 @@ protected:
nsTArray<RefPtr<mozilla::dom::VRDisplay>> mVRDisplays;
nsAutoPtr<mozilla::dom::VREventObserver> mVREventObserver;
friend class nsDOMScriptableHelper;
friend class nsDOMWindowUtils;
friend class mozilla::dom::PostMessageEvent;
friend class DesktopNotification;
+ friend class mozilla::dom::IdleRequest;
static WindowByIdTable* sWindowsById;
static bool sWarnedAboutWindowInternal;
};
inline nsISupports*
ToSupports(nsGlobalWindow *p)
{