Bug 1313864 - Make IdleRequest dispatch itself. r?bkelly draft
authorAndreas Farre <farre@mozilla.com>
Tue, 08 Nov 2016 13:54:40 +0100
changeset 439805 03edc5bfb7d2e98710872105b7d237038e989308
parent 436671 41e192bec53699a83c3002bb374c13909f6d6135
child 537261 1723066efafdb3587b6f288502202e160f59656f
push id36101
push userbmo:afarre@mozilla.com
push dateWed, 16 Nov 2016 17:14:52 +0000
reviewersbkelly
bugs1313864
milestone52.0a1
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
dom/base/IdleRequest.cpp
dom/base/IdleRequest.h
dom/base/RunnableTimeoutHandler.cpp
dom/base/RunnableTimeoutHandler.h
dom/base/Timeout.h
dom/base/moz.build
dom/base/nsGlobalWindow.cpp
dom/base/nsGlobalWindow.h
--- 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,52 @@ 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()
 {
   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 +125,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,21 @@ uint32_t sNestingLevel;
 
 uint32_t
 nsGlobalWindow::GetTimeoutId(Timeout::Reason aReason)
 {
   switch (aReason) {
     case Timeout::Reason::eIdleCallbackTimeout:
       return ++mIdleCallbackTimeoutCounter;
     case Timeout::Reason::eTimeoutOrInterval:
+      return ++mTimeoutIdCounter;
     default:
-      return ++mTimeoutIdCounter;
+      MOZ_ASSERT_UNREACHABLE("Unhandled timeout reason");
+    case Timeout::Reason::eInternalTimeout:
+      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)
 {