moebius#73: DOM - window.requestIdleCallback - improvements (basic)

https://github.com/MoonchildProductions/moebius/pull/73
This commit is contained in:
janekptacijarabaci 2018-04-18 15:15:49 +02:00 committed by Roy Tam
commit da2320b2ef
11 changed files with 558 additions and 200 deletions

View file

@ -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,283 @@ DialogValueHolder::Get(JSContext* aCx, JS::Handle<JSObject*> aScope,
}
}
void
nsGlobalWindow::PostThrottledIdleCallback()
class IdleRequestExecutor final : public nsIRunnable
, public nsICancelableRunnable
, public nsIIncrementalRunnable
{
AssertIsOnMainThread();
public:
explicit IdleRequestExecutor(nsGlobalWindow* aWindow)
: mDispatched(false)
, mDeadline(TimeStamp::Now())
, mWindow(aWindow)
{
MOZ_DIAGNOSTIC_ASSERT(mWindow);
MOZ_DIAGNOSTIC_ASSERT(mWindow->IsInnerWindow());
}
if (mThrottledIdleRequestCallbacks.isEmpty())
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
IdleRequestExecutor::SetDeadline(TimeStamp aDeadline)
{
MOZ_ASSERT(NS_IsMainThread());
if (!mWindow) {
return;
}
RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
// ownership transferred from mThrottledIdleRequestCallbacks to
// mIdleRequestCallbacks
mIdleRequestCallbacks.insertBack(request);
mDeadline = aDeadline;
}
void
IdleRequestExecutor::MaybeDispatch()
{
MOZ_DIAGNOSTIC_ASSERT(mWindow);
if (mDispatched) {
return;
}
mDispatched = true;
RefPtr<IdleRequestExecutor> request = this;
NS_IdleDispatchToCurrentThread(request.forget());
}
/* static */ void
nsGlobalWindow::InsertIdleCallbackIntoList(IdleRequest* aRequest,
IdleRequests& aList)
class IdleRequestExecutorTimeoutHandler final : public TimeoutHandler
{
aList.insertBack(aRequest);
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()
{
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);
nsresult result = request->IdleRun(AsInner(), aDeadline, aDidTimeout);
RemoveIdleCallback(request);
return result;
}
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 +839,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;
}
request->SetTimeoutHandle(timeoutHandle);
}
nsGlobalWindow* outer = GetOuterWindowInternal();
if (outer && outer->AsOuter()->IsBackground()) {
// mThrottledIdleRequestCallbacks now owns request
InsertIdleCallbackIntoList(request, mThrottledIdleRequestCallbacks);
// 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_DelayedDispatchToCurrentThread(
NewRunnableMethod(this, &nsGlobalWindow::PostThrottledIdleCallback),
10000);
} else {
MOZ_ASSERT(mThrottledIdleRequestCallbacks.isEmpty());
// mIdleRequestCallbacks now owns request
InsertIdleCallbackIntoList(request, mIdleRequestCallbacks);
NS_IdleDispatchToCurrentThread(request.forget());
if (needsScheduling) {
ScheduleIdleRequestDispatch();
}
return handle;
@ -623,7 +881,7 @@ nsGlobalWindow::CancelIdleCallback(uint32_t aHandle)
for (IdleRequest* r : mIdleRequestCallbacks) {
if (r->Handle() == aHandle) {
r->Cancel();
RemoveIdleCallback(r);
break;
}
}
@ -632,29 +890,17 @@ nsGlobalWindow::CancelIdleCallback(uint32_t aHandle)
void
nsGlobalWindow::DisableIdleCallbackRequests()
{
if (mIdleRequestExecutor) {
mIdleRequestExecutor->Cancel();
mIdleRequestExecutor = nullptr;
}
while (!mIdleRequestCallbacks.isEmpty()) {
RefPtr<IdleRequest> request = mIdleRequestCallbacks.popFirst();
request->Cancel();
}
while (!mThrottledIdleRequestCallbacks.isEmpty()) {
RefPtr<IdleRequest> request = mThrottledIdleRequestCallbacks.popFirst();
request->Cancel();
RefPtr<IdleRequest> request = mIdleRequestCallbacks.getFirst();
RemoveIdleCallback(request);
}
}
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
@ -1306,6 +1552,7 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow)
mSerial(0),
mIdleCallbackTimeoutCounter(1),
mIdleRequestCallbackCounter(1),
mIdleRequestExecutor(nullptr),
#ifdef DEBUG
mSetOpenerWindowCalled(false),
#endif
@ -2033,14 +2280,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 +2391,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 +10482,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 +12275,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 +12387,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().