summaryrefslogtreecommitdiffstats
path: root/dom/base/nsGlobalWindow.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/base/nsGlobalWindow.cpp')
-rw-r--r--dom/base/nsGlobalWindow.cpp360
1 files changed, 302 insertions, 58 deletions
diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp
index f784031f6..3d5c44a78 100644
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -23,6 +23,7 @@
#include "mozilla/dom/StorageEvent.h"
#include "mozilla/dom/StorageEventBinding.h"
#include "mozilla/dom/Timeout.h"
+#include "mozilla/dom/TimeoutHandler.h"
#include "mozilla/IntegerPrintfMacros.h"
#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
#include "mozilla/dom/WindowOrientationObserver.h"
@@ -552,29 +553,284 @@ DialogValueHolder::Get(JSContext* aCx, JS::Handle<JSObject*> aScope,
}
}
+class IdleRequestExecutor final : public nsIRunnable
+ , public nsICancelableRunnable
+ , public nsIIncrementalRunnable
+{
+public:
+ explicit IdleRequestExecutor(nsGlobalWindow* aWindow)
+ : mDispatched(false)
+ , mDeadline(TimeStamp::Now())
+ , mWindow(aWindow)
+ {
+ MOZ_DIAGNOSTIC_ASSERT(mWindow);
+ MOZ_DIAGNOSTIC_ASSERT(mWindow->IsInnerWindow());
+ }
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequestExecutor, nsIRunnable)
+
+ NS_DECL_NSIRUNNABLE
+ nsresult Cancel() override;
+ void SetDeadline(TimeStamp aDeadline) override;
+
+ void MaybeDispatch();
+private:
+ ~IdleRequestExecutor() {}
+
+ bool mDispatched;
+ TimeStamp mDeadline;
+ RefPtr<nsGlobalWindow> mWindow;
+};
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequestExecutor)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestExecutor)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestExecutor)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IdleRequestExecutor)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IdleRequestExecutor)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutor)
+ NS_INTERFACE_MAP_ENTRY(nsIRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
+ NS_INTERFACE_MAP_ENTRY(nsIIncrementalRunnable)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable)
+NS_INTERFACE_MAP_END
+
+NS_IMETHODIMP
+IdleRequestExecutor::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mDispatched = false;
+ if (mWindow) {
+ return mWindow->ExecuteIdleRequest(mDeadline);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+IdleRequestExecutor::Cancel()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mWindow = nullptr;
+ return NS_OK;
+}
+
void
-nsGlobalWindow::PostThrottledIdleCallback()
+IdleRequestExecutor::SetDeadline(TimeStamp aDeadline)
{
- AssertIsOnMainThread();
+ MOZ_ASSERT(NS_IsMainThread());
- if (mThrottledIdleRequestCallbacks.isEmpty())
+ if (!mWindow) {
return;
+ }
+
+ mDeadline = aDeadline;
+}
- RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
- // ownership transferred from mThrottledIdleRequestCallbacks to
- // mIdleRequestCallbacks
- mIdleRequestCallbacks.insertBack(request);
+void
+IdleRequestExecutor::MaybeDispatch()
+{
+ // If we've already dispatched the executor we don't want to do it
+ // again. Also, if we've called IdleRequestExecutor::Cancel mWindow
+ // will be null, which indicates that we shouldn't dispatch this
+ // executor either.
+ if (mDispatched || !mWindow) {
+ return;
+ }
+
+ mDispatched = true;
+ RefPtr<IdleRequestExecutor> request = this;
NS_IdleDispatchToCurrentThread(request.forget());
}
-/* static */ void
-nsGlobalWindow::InsertIdleCallbackIntoList(IdleRequest* aRequest,
- IdleRequests& aList)
+class IdleRequestExecutorTimeoutHandler final : public TimeoutHandler
+{
+public:
+ explicit IdleRequestExecutorTimeoutHandler(IdleRequestExecutor* aExecutor)
+ : mExecutor(aExecutor)
+ {
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IdleRequestExecutorTimeoutHandler,
+ TimeoutHandler)
+
+ nsresult Call() override
+ {
+ mExecutor->MaybeDispatch();
+ return NS_OK;
+ }
+private:
+ ~IdleRequestExecutorTimeoutHandler() {}
+ RefPtr<IdleRequestExecutor> mExecutor;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler, mExecutor)
+
+NS_IMPL_ADDREF_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler)
+NS_IMPL_RELEASE_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutorTimeoutHandler)
+ NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler)
+NS_INTERFACE_MAP_END_INHERITING(TimeoutHandler)
+
+void
+nsGlobalWindow::ScheduleIdleRequestDispatch()
+{
+ AssertIsOnMainThread();
+
+ if (mIdleRequestCallbacks.isEmpty()) {
+ if (mIdleRequestExecutor) {
+ mIdleRequestExecutor->Cancel();
+ mIdleRequestExecutor = nullptr;
+ }
+
+ return;
+ }
+
+ if (!mIdleRequestExecutor) {
+ mIdleRequestExecutor = new IdleRequestExecutor(this);
+ }
+
+ nsPIDOMWindowOuter* outer = GetOuterWindow();
+ if (outer && outer->AsOuter()->IsBackground()) {
+ nsCOMPtr<nsITimeoutHandler> handler = new IdleRequestExecutorTimeoutHandler(mIdleRequestExecutor);
+ int32_t dummy;
+ // Set a timeout handler with a timeout of 0 ms to throttle idle
+ // callback requests coming from a backround window using
+ // background timeout throttling.
+ SetTimeoutOrInterval(handler, 0, false,
+ Timeout::Reason::eIdleCallbackTimeout, &dummy);
+ return;
+ }
+
+ mIdleRequestExecutor->MaybeDispatch();
+}
+
+void
+nsGlobalWindow::SuspendIdleRequests()
+{
+ if (mIdleRequestExecutor) {
+ mIdleRequestExecutor->Cancel();
+ mIdleRequestExecutor = nullptr;
+ }
+}
+
+void
+nsGlobalWindow::ResumeIdleRequests()
{
- aList.insertBack(aRequest);
+ MOZ_ASSERT(!mIdleRequestExecutor);
+
+ ScheduleIdleRequestDispatch();
+}
+
+void
+nsGlobalWindow::InsertIdleCallback(IdleRequest* aRequest)
+{
+ AssertIsOnMainThread();
+ mIdleRequestCallbacks.insertBack(aRequest);
aRequest->AddRef();
}
+void
+nsGlobalWindow::RemoveIdleCallback(mozilla::dom::IdleRequest* aRequest)
+{
+ AssertIsOnMainThread();
+
+ if (aRequest->HasTimeout()) {
+ ClearTimeoutOrInterval(aRequest->GetTimeoutHandle(),
+ Timeout::Reason::eIdleCallbackTimeout);
+ }
+
+ aRequest->removeFrom(mIdleRequestCallbacks);
+ aRequest->Release();
+}
+
+nsresult
+nsGlobalWindow::RunIdleRequest(IdleRequest* aRequest,
+ DOMHighResTimeStamp aDeadline,
+ bool aDidTimeout)
+{
+ AssertIsOnMainThread();
+ RefPtr<IdleRequest> request(aRequest);
+ RemoveIdleCallback(request);
+ return request->IdleRun(AsInner(), aDeadline, aDidTimeout);
+}
+
+nsresult
+nsGlobalWindow::ExecuteIdleRequest(TimeStamp aDeadline)
+{
+ AssertIsOnMainThread();
+ RefPtr<IdleRequest> request = mIdleRequestCallbacks.getFirst();
+
+ if (!request) {
+ // There are no more idle requests, so stop scheduling idle
+ // request callbacks.
+ return NS_OK;
+ }
+
+ DOMHighResTimeStamp deadline = 0.0;
+
+ if (Performance* perf = GetPerformance()) {
+ deadline = perf->GetDOMTiming()->TimeStampToDOMHighRes(aDeadline);
+ }
+
+ nsresult result = RunIdleRequest(request, deadline, false);
+
+ ScheduleIdleRequestDispatch();
+ return result;
+}
+
+class IdleRequestTimeoutHandler final : public TimeoutHandler
+{
+public:
+ IdleRequestTimeoutHandler(JSContext* aCx,
+ IdleRequest* aIdleRequest,
+ nsPIDOMWindowInner* aWindow)
+ : TimeoutHandler(aCx)
+ , mIdleRequest(aIdleRequest)
+ , mWindow(aWindow)
+ {
+ }
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IdleRequestTimeoutHandler,
+ TimeoutHandler)
+
+ nsresult Call() override
+ {
+ return nsGlobalWindow::Cast(mWindow)->RunIdleRequest(mIdleRequest, 0.0, true);
+ }
+
+private:
+ ~IdleRequestTimeoutHandler() {}
+
+ RefPtr<IdleRequest> mIdleRequest;
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+};
+
+NS_IMPL_CYCLE_COLLECTION_INHERITED(IdleRequestTimeoutHandler,
+ TimeoutHandler,
+ mIdleRequest,
+ mWindow)
+
+NS_IMPL_ADDREF_INHERITED(IdleRequestTimeoutHandler, TimeoutHandler)
+NS_IMPL_RELEASE_INHERITED(IdleRequestTimeoutHandler, TimeoutHandler)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestTimeoutHandler)
+ NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler)
+NS_INTERFACE_MAP_END_INHERITING(TimeoutHandler)
+
uint32_t
nsGlobalWindow::RequestIdleCallback(JSContext* aCx,
IdleRequestCallback& aCallback,
@@ -584,33 +840,36 @@ nsGlobalWindow::RequestIdleCallback(JSContext* aCx,
MOZ_RELEASE_ASSERT(IsInnerWindow());
AssertIsOnMainThread();
- uint32_t handle = ++mIdleRequestCallbackCounter;
+ uint32_t handle = mIdleRequestCallbackCounter++;
RefPtr<IdleRequest> request =
- new IdleRequest(aCx, AsInner(), aCallback, handle);
+ new IdleRequest(&aCallback, handle);
if (aOptions.mTimeout.WasPassed()) {
- aError = request->SetTimeout(aOptions.mTimeout.Value());
- if (NS_WARN_IF(aError.Failed())) {
+ int32_t timeoutHandle;
+ nsCOMPtr<nsITimeoutHandler> handler(new IdleRequestTimeoutHandler(aCx, request, AsInner()));
+
+ nsresult rv = SetTimeoutOrInterval(
+ handler, aOptions.mTimeout.Value(), false,
+ Timeout::Reason::eIdleCallbackTimeout, &timeoutHandle);
+
+ if (NS_WARN_IF(NS_FAILED(rv))) {
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());
+ request->SetTimeoutHandle(timeoutHandle);
+ }
- // mIdleRequestCallbacks now owns request
- InsertIdleCallbackIntoList(request, mIdleRequestCallbacks);
+ // If the list of idle callback requests is not empty it means that
+ // we've already dispatched the first idle request. If we're
+ // suspended we should only queue the idle callback and not schedule
+ // it to run, that will be done in ResumeIdleRequest.
+ bool needsScheduling = !IsSuspended() && mIdleRequestCallbacks.isEmpty();
+ // mIdleRequestCallbacks now owns request
+ InsertIdleCallback(request);
- NS_IdleDispatchToCurrentThread(request.forget());
+ if (needsScheduling) {
+ ScheduleIdleRequestDispatch();
}
return handle;
@@ -623,7 +882,7 @@ nsGlobalWindow::CancelIdleCallback(uint32_t aHandle)
for (IdleRequest* r : mIdleRequestCallbacks) {
if (r->Handle() == aHandle) {
- r->Cancel();
+ RemoveIdleCallback(r);
break;
}
}
@@ -632,29 +891,17 @@ nsGlobalWindow::CancelIdleCallback(uint32_t aHandle)
void
nsGlobalWindow::DisableIdleCallbackRequests()
{
- while (!mIdleRequestCallbacks.isEmpty()) {
- RefPtr<IdleRequest> request = mIdleRequestCallbacks.popFirst();
- request->Cancel();
+ if (mIdleRequestExecutor) {
+ mIdleRequestExecutor->Cancel();
+ mIdleRequestExecutor = nullptr;
}
- 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());
+ while (!mIdleRequestCallbacks.isEmpty()) {
+ RefPtr<IdleRequest> request = mIdleRequestCallbacks.getFirst();
+ RemoveIdleCallback(request);
}
}
-
namespace mozilla {
namespace dom {
extern uint64_t
@@ -1306,6 +1553,7 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow)
mSerial(0),
mIdleCallbackTimeoutCounter(1),
mIdleRequestCallbackCounter(1),
+ mIdleRequestExecutor(nullptr),
#ifdef DEBUG
mSetOpenerWindowCalled(false),
#endif
@@ -2033,14 +2281,11 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleRequestExecutor)
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
@@ -2147,6 +2392,7 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow)
tmp->UnlinkHostObjectURIs();
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleRequestExecutor)
tmp->DisableIdleCallbackRequests();
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
@@ -10237,12 +10483,6 @@ void nsGlobalWindow::SetIsBackground(bool aIsBackground)
ResetTimersForThrottleReduction(gMinBackgroundTimeoutValue);
}
- if (!aIsBackground) {
- nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
- if (inner) {
- inner->UnthrottleIdleCallbackRequests();
- }
- }
#ifdef MOZ_GAMEPAD
if (!aIsBackground) {
nsGlobalWindow* inner = GetCurrentInnerWindowInternal();
@@ -12036,6 +12276,8 @@ nsGlobalWindow::Suspend()
mozilla::dom::workers::SuspendWorkersForWindow(AsInner());
+ SuspendIdleRequests();
+
for (Timeout* t = mTimeouts.getFirst(); t; t = t->getNext()) {
// Leave the timers with the current time remaining. This will
// cause the timers to potentially fire when the window is
@@ -12146,6 +12388,8 @@ nsGlobalWindow::Resume()
t->AddRef();
}
+ ResumeIdleRequests();
+
// Resume all of the workers for this window. We must do this
// after timeouts since workers may have queued events that can trigger
// a setTimeout().