diff options
author | janekptacijarabaci <janekptacijarabaci@seznam.cz> | 2018-04-18 15:15:49 +0200 |
---|---|---|
committer | janekptacijarabaci <janekptacijarabaci@seznam.cz> | 2018-04-18 15:15:49 +0200 |
commit | c559e3e30f79843f0096332334c81ee0d93029f8 (patch) | |
tree | 07ee668e9f82976e2d00912e818b632ea4470170 | |
parent | 29de8de78c3260aad1e7844933d5b3cf1033627c (diff) | |
download | uxp-c559e3e30f79843f0096332334c81ee0d93029f8.tar.gz |
moebius#73: DOM - window.requestIdleCallback - improvements (basic)
https://github.com/MoonchildProductions/moebius/pull/73
-rw-r--r-- | dom/base/IdleRequest.cpp | 119 | ||||
-rw-r--r-- | dom/base/IdleRequest.h | 40 | ||||
-rw-r--r-- | dom/base/Timeout.h | 8 | ||||
-rw-r--r-- | dom/base/TimeoutHandler.cpp | 43 | ||||
-rw-r--r-- | dom/base/TimeoutHandler.h | 50 | ||||
-rwxr-xr-x | dom/base/moz.build | 2 | ||||
-rw-r--r-- | dom/base/nsGlobalWindow.cpp | 359 | ||||
-rw-r--r-- | dom/base/nsGlobalWindow.h | 31 | ||||
-rw-r--r-- | testing/web-platform/meta/MANIFEST.json | 6 | ||||
-rw-r--r-- | testing/web-platform/tests/html/webappapis/idle-callbacks/callback-suspended.html | 93 | ||||
-rw-r--r-- | testing/web-platform/tests/html/webappapis/idle-callbacks/resources/post_name_on_load.html | 7 |
11 files changed, 558 insertions, 200 deletions
diff --git a/dom/base/IdleRequest.cpp b/dom/base/IdleRequest.cpp index 26190f98b7..c371a691bf 100644 --- a/dom/base/IdleRequest.cpp +++ b/dom/base/IdleRequest.cpp @@ -9,7 +9,6 @@ #include "mozilla/Function.h" #include "mozilla/TimeStamp.h" #include "mozilla/dom/IdleDeadline.h" -#include "mozilla/dom/Performance.h" #include "mozilla/dom/PerformanceTiming.h" #include "mozilla/dom/WindowBinding.h" #include "nsComponentManagerUtils.h" @@ -20,138 +19,56 @@ namespace mozilla { namespace dom { -IdleRequest::IdleRequest(JSContext* aCx, nsPIDOMWindowInner* aWindow, - IdleRequestCallback& aCallback, uint32_t aHandle) - : mWindow(aWindow) - , mCallback(&aCallback) +IdleRequest::IdleRequest(IdleRequestCallback* aCallback, uint32_t aHandle) + : mCallback(aCallback) , mHandle(aHandle) , mTimeoutHandle(Nothing()) { - MOZ_ASSERT(aWindow); - - // Get the calling location. - nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn); + MOZ_DIAGNOSTIC_ASSERT(mCallback); } IdleRequest::~IdleRequest() { } -NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequest) +NS_IMPL_CYCLE_COLLECTION(IdleRequest, mCallback) NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequest) NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequest) -NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IdleRequest) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow) - NS_IMPL_CYCLE_COLLECTION_UNLINK(mCallback) -NS_IMPL_CYCLE_COLLECTION_UNLINK_END - -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IdleRequest) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) - NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCallback) -NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END - NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequest) - NS_INTERFACE_MAP_ENTRY(nsIRunnable) - NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable) - NS_INTERFACE_MAP_ENTRY(nsIIncrementalRunnable) - NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler) - NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimeoutHandler) NS_INTERFACE_MAP_END -nsresult -IdleRequest::SetTimeout(uint32_t aTimeout) -{ - int32_t handle; - nsresult rv = nsGlobalWindow::Cast(mWindow)->SetTimeoutOrInterval( - this, aTimeout, false, Timeout::Reason::eIdleCallbackTimeout, &handle); - mTimeoutHandle = Some(handle); - - return rv; -} - -nsresult -IdleRequest::Run() -{ - if (mCallback) { - RunIdleRequestCallback(false); - } - - return NS_OK; -} - -nsresult -IdleRequest::Cancel() +void +IdleRequest::SetTimeoutHandle(int32_t aHandle) { - mCallback = nullptr; - CancelTimeout(); - if (isInList()) { - remove(); - } - Release(); - - return NS_OK; + mTimeoutHandle = Some(aHandle); } -void -IdleRequest::SetDeadline(TimeStamp aDeadline) +int32_t +IdleRequest::GetTimeoutHandle() const { - mozilla::dom::Performance* perf = mWindow->GetPerformance(); - mDeadline = - perf ? perf->GetDOMTiming()->TimeStampToDOMHighRes(aDeadline) : 0.0; + MOZ_DIAGNOSTIC_ASSERT(mTimeoutHandle.isSome()); + return mTimeoutHandle.value(); } nsresult -IdleRequest::RunIdleRequestCallback(bool aDidTimeout) +IdleRequest::IdleRun(nsPIDOMWindowInner* aWindow, + DOMHighResTimeStamp aDeadline, + bool aDidTimeout) { MOZ_ASSERT(NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(mCallback); - if (!aDidTimeout) { - CancelTimeout(); - } - - remove(); ErrorResult error; RefPtr<IdleDeadline> deadline = - new IdleDeadline(mWindow, aDidTimeout, mDeadline); + new IdleDeadline(aWindow, aDidTimeout, aDeadline); mCallback->Call(*deadline, error, "requestIdleCallback handler"); - mCallback = nullptr; - Release(); + mCallback = nullptr; + error.SuppressException(); return error.StealNSResult(); } -void -IdleRequest::CancelTimeout() -{ - if (mTimeoutHandle.isSome()) { - nsGlobalWindow::Cast(mWindow)->ClearTimeoutOrInterval( - mTimeoutHandle.value(), Timeout::Reason::eIdleCallbackTimeout); - } -} - -nsresult -IdleRequest::Call() -{ - SetDeadline(TimeStamp::Now()); - return RunIdleRequestCallback(true); -} - -void -IdleRequest::GetLocation(const char** aFileName, uint32_t* aLineNo, - uint32_t* aColumn) -{ - *aFileName = mFileName.get(); - *aLineNo = mLineNo; - *aColumn = mColumn; -} - -void -IdleRequest::MarkForCC() -{ - mCallback->MarkForCC(); -} - } // namespace dom } // namespace mozilla diff --git a/dom/base/IdleRequest.h b/dom/base/IdleRequest.h index cb234430a2..acf56f8529 100644 --- a/dom/base/IdleRequest.h +++ b/dom/base/IdleRequest.h @@ -12,7 +12,6 @@ #include "nsCOMPtr.h" #include "nsCycleCollectionParticipant.h" #include "nsDOMNavigationTiming.h" -#include "nsITimeoutHandler.h" class nsPIDOMWindowInner; @@ -21,28 +20,19 @@ namespace dom { class IdleRequestCallback; -class IdleRequest final : public nsITimeoutHandler - , public nsIRunnable - , public nsICancelableRunnable - , public nsIIncrementalRunnable - , public LinkedListElement<IdleRequest> +class IdleRequest final : public nsISupports, + public LinkedListElement<IdleRequest> { public: - IdleRequest(JSContext* aCx, nsPIDOMWindowInner* aWindow, - IdleRequestCallback& aCallback, uint32_t aHandle); + IdleRequest(IdleRequestCallback* aCallback, uint32_t aHandle); - virtual nsresult Call() override; - virtual void GetLocation(const char** aFileName, uint32_t* aLineNo, - uint32_t* aColumn) override; - virtual void MarkForCC() override; + nsresult IdleRun(nsPIDOMWindowInner* aWindow, + DOMHighResTimeStamp aDeadline, + bool aDidTimeout); - nsresult SetTimeout(uint32_t aTimout); - nsresult RunIdleRequestCallback(bool aDidTimeout); - void CancelTimeout(); - - NS_DECL_NSIRUNNABLE; - virtual nsresult Cancel() override; - virtual void SetDeadline(mozilla::TimeStamp aDeadline) override; + void SetTimeoutHandle(int32_t aHandle); + bool HasTimeout() const { return mTimeoutHandle.isSome(); } + uint32_t GetTimeoutHandle() const; uint32_t Handle() const { @@ -50,22 +40,14 @@ public: } NS_DECL_CYCLE_COLLECTING_ISUPPORTS - NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequest, nsITimeoutHandler) + NS_DECL_CYCLE_COLLECTION_CLASS(IdleRequest) private: ~IdleRequest(); - // filename, line number and JS language version string of the - // caller of setTimeout() - nsCString mFileName; - uint32_t mLineNo; - uint32_t mColumn; - - nsCOMPtr<nsPIDOMWindowInner> mWindow; RefPtr<IdleRequestCallback> mCallback; - uint32_t mHandle; + const uint32_t mHandle; mozilla::Maybe<int32_t> mTimeoutHandle; - DOMHighResTimeStamp mDeadline; }; } // namespace dom diff --git a/dom/base/Timeout.h b/dom/base/Timeout.h index e929f3dd13..42e2f57f5b 100644 --- a/dom/base/Timeout.h +++ b/dom/base/Timeout.h @@ -41,7 +41,11 @@ public: // default main thread being used. nsresult InitTimer(nsIEventTarget* aTarget, uint32_t aDelay); - enum class Reason { eTimeoutOrInterval, eIdleCallbackTimeout }; + enum class Reason + { + eTimeoutOrInterval, + eIdleCallbackTimeout, + }; #ifdef DEBUG bool HasRefCntOne() const; @@ -62,6 +66,8 @@ public: // True if this is a repeating/interval timer bool mIsInterval; + // Used to allow several reasons for setting a timeout, where each + // 'Reason' value is using a possibly overlapping set of id:s. Reason mReason; // Returned as value of setTimeout() diff --git a/dom/base/TimeoutHandler.cpp b/dom/base/TimeoutHandler.cpp new file mode 100644 index 0000000000..78c3f16dd9 --- /dev/null +++ b/dom/base/TimeoutHandler.cpp @@ -0,0 +1,43 @@ +/* -*- 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 "TimeoutHandler.h" + +namespace mozilla { +namespace dom { + +TimeoutHandler::TimeoutHandler(JSContext* aCx) + : TimeoutHandler() +{ + nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn); +} + +nsresult +TimeoutHandler::Call() +{ + return NS_OK; +} + +void +TimeoutHandler::GetLocation(const char** aFileName, uint32_t* aLineNo, + uint32_t* aColumn) +{ + *aFileName = mFileName.get(); + *aLineNo = mLineNo; + *aColumn = mColumn; +} + +NS_IMPL_CYCLE_COLLECTION_0(TimeoutHandler) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(TimeoutHandler) +NS_IMPL_CYCLE_COLLECTING_RELEASE(TimeoutHandler) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(TimeoutHandler) + NS_INTERFACE_MAP_ENTRY(nsITimeoutHandler) +NS_INTERFACE_MAP_END + +} // namespace dom +} // namespace mozilla diff --git a/dom/base/TimeoutHandler.h b/dom/base/TimeoutHandler.h new file mode 100644 index 0000000000..cb0a0ce945 --- /dev/null +++ b/dom/base/TimeoutHandler.h @@ -0,0 +1,50 @@ +/* -*- 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 "nsCOMPtr.h" +#include "nsISupports.h" +#include "nsITimeoutHandler.h" + +namespace mozilla { +namespace dom { + +/** + * Utility class for implementing nsITimeoutHandlers, designed to be subclassed. + */ +class TimeoutHandler : public nsITimeoutHandler +{ +public: + // TimeoutHandler doesn't actually contain cycles, but subclasses + // probably will. + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(TimeoutHandler) + + virtual nsresult Call() override; + virtual void GetLocation(const char** aFileName, uint32_t* aLineNo, + uint32_t* aColumn) override; + virtual void MarkForCC() override {} +protected: + TimeoutHandler() : mFileName(""), mLineNo(0), mColumn(0) {} + explicit TimeoutHandler(JSContext *aCx); + + virtual ~TimeoutHandler() {} +private: + TimeoutHandler(const TimeoutHandler&) = delete; + TimeoutHandler& operator=(const TimeoutHandler&) = delete; + TimeoutHandler& operator=(const TimeoutHandler&&) = delete; + + nsCString mFileName; + uint32_t mLineNo; + uint32_t mColumn; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_timeout_handler_h diff --git a/dom/base/moz.build b/dom/base/moz.build index 0fb345d224..76c765b1cf 100755 --- a/dom/base/moz.build +++ b/dom/base/moz.build @@ -217,6 +217,7 @@ EXPORTS.mozilla.dom += [ 'TabGroup.h', 'Text.h', 'Timeout.h', + 'TimeoutHandler.h', 'TreeWalker.h', 'WebKitCSSMatrix.h', 'WebSocket.h', @@ -364,6 +365,7 @@ UNIFIED_SOURCES += [ 'TextInputProcessor.cpp', 'ThirdPartyUtil.cpp', 'Timeout.cpp', + 'TimeoutHandler.cpp', 'TimerClamping.cpp', 'TreeWalker.cpp', 'WebKitCSSMatrix.cpp', diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index f784031f6a..6a49f440ff 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,283 @@ 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 (!mWindow) { + return; + } - if (mThrottledIdleRequestCallbacks.isEmpty()) + mDeadline = aDeadline; +} + +void +IdleRequestExecutor::MaybeDispatch() +{ + MOZ_DIAGNOSTIC_ASSERT(mWindow); + + if (mDispatched) { return; + } - RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst()); - // ownership transferred from mThrottledIdleRequestCallbacks to - // mIdleRequestCallbacks - mIdleRequestCallbacks.insertBack(request); + 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); + 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; } - } - 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 +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() { - 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 +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(). diff --git a/dom/base/nsGlobalWindow.h b/dom/base/nsGlobalWindow.h index dbceeab742..78bee63a19 100644 --- a/dom/base/nsGlobalWindow.h +++ b/dom/base/nsGlobalWindow.h @@ -102,6 +102,8 @@ struct nsRect; class nsWindowSizes; +class IdleRequestExecutor; + namespace mozilla { class DOMEventTargetHelper; class ThrottledEventQueue; @@ -118,6 +120,7 @@ class Gamepad; enum class ImageBitmapFormat : uint32_t; class IdleRequest; class IdleRequestCallback; +class IncrementalRunnable; class Location; class MediaQueryList; class MozSelfSupport; @@ -1097,7 +1100,6 @@ public: mozilla::ErrorResult& aError); void CancelIdleCallback(uint32_t aHandle); - #ifdef MOZ_WEBSPEECH mozilla::dom::SpeechSynthesis* GetSpeechSynthesis(mozilla::ErrorResult& aError); @@ -1762,6 +1764,21 @@ private: mozilla::dom::TabGroup* TabGroupInner(); mozilla::dom::TabGroup* TabGroupOuter(); +public: + void DisableIdleCallbackRequests(); + uint32_t IdleRequestHandle() const { return mIdleRequestCallbackCounter; } + nsresult RunIdleRequest(mozilla::dom::IdleRequest* aRequest, + DOMHighResTimeStamp aDeadline, bool aDidTimeout); + nsresult ExecuteIdleRequest(TimeStamp aDeadline); + void ScheduleIdleRequestDispatch(); + void SuspendIdleRequests(); + void ResumeIdleRequests(); + + typedef mozilla::LinkedList<mozilla::dom::IdleRequest> IdleRequests; + void InsertIdleCallback(mozilla::dom::IdleRequest* aRequest); + + void RemoveIdleCallback(mozilla::dom::IdleRequest* aRequest); + protected: // These members are only used on outer window objects. Make sure // you never set any of these on an inner object! @@ -1912,21 +1929,12 @@ protected: uint32_t mSerial; - void DisableIdleCallbackRequests(); - void UnthrottleIdleCallbackRequests(); - - void PostThrottledIdleCallback(); - - 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; + RefPtr<IdleRequestExecutor> mIdleRequestExecutor; #ifdef DEBUG bool mSetOpenerWindowCalled; @@ -2002,6 +2010,7 @@ protected: friend class nsDOMWindowUtils; friend class mozilla::dom::PostMessageEvent; friend class DesktopNotification; + friend class IdleRequestExecutor; static WindowByIdTable* sWindowsById; static bool sWarnedAboutWindowInternal; diff --git a/testing/web-platform/meta/MANIFEST.json b/testing/web-platform/meta/MANIFEST.json index ca574833b5..3c7df67fa8 100644 --- a/testing/web-platform/meta/MANIFEST.json +++ b/testing/web-platform/meta/MANIFEST.json @@ -38674,6 +38674,12 @@ "url": "/html/webappapis/idle-callbacks/callback-multiple-calls.html" } ], + "html/webappapis/idle-callbacks/callback-suspended.html": [ + { + "path": "html/webappapis/idle-callbacks/callback-suspended.html", + "url": "/html/webappapis/idle-callbacks/callback-suspended.html" + } + ], "html/webappapis/idle-callbacks/callback-timeout.html": [ { "path": "html/webappapis/idle-callbacks/callback-timeout.html", diff --git a/testing/web-platform/tests/html/webappapis/idle-callbacks/callback-suspended.html b/testing/web-platform/tests/html/webappapis/idle-callbacks/callback-suspended.html new file mode 100644 index 0000000000..6040de922f --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/idle-callbacks/callback-suspended.html @@ -0,0 +1,93 @@ +<!doctype html> +<meta charset=utf-8> +<title>Dispatching idle callbacks should be able to be suspended and then resumed</title> +<script src=/resources/testharness.js></script> +<script src=/resources/testharnessreport.js></script> +<div id="log"></div> +<script> + function withEventListener(target, event, handler) { + handler = handler || (e => e); + return new Promise(resolve => { + let wrapper = function(e) { + let result = handler(e); + if (!result) { + return; + } + + resolve(result); + } + target.addEventListener(event, wrapper, { once: true }); + }); + } + + function makePostBackUrl(name) { + return new URL('resources/post_name_on_load.html?name=' + name, + window.location).href; + } + + function waitForMessage(message, handler) { + return withEventListener(window, 'message', e => (e.data === message) && handler(e));; + } + + function withWindow(name) { + let win = window.open(makePostBackUrl(name)) + return waitForMessage(name, _ => win); + } + + function navigateWindow(win, name) { + win.location = makePostBackUrl(name); + return waitForMessage(name, _ => win); + } + + function waitDuration(delay) { + return new Promise(resolve => { + setTimeout(resolve, delay); + }) + } + + function goBack(win) { + var p = withEventListener(win, 'pagehide'); + win.history.back(); + return p; + } + + promise_test(t => { + let idleCalled = false; + let running = true; + return withWindow('foo') + .then(win => { + let callback = function(d) { + idleCalled = true; + if (running) { + win.requestIdleCallback(callback); + } + }; + + win.requestIdleCallback(callback); + + return navigateWindow(win, 'bar') + .then(_ => idleCalled = false) + .then(_ => waitDuration(2000)) + .then(_ => { + assert_false(idleCalled, "idle callback shouldn't have been called yet"); + return goBack(win); + }) + .then(_ => Promise.race([ + // At this point it's a matter of having bfcache ... + waitDuration(2000) + .then(_ => { + assert_true(idleCalled, "idle callback should've been called by now"); + running = false; + }), + // ... or not. If not, we expect a load event. + waitForMessage("foo") + ])) + .then(_ => win.close()) + .catch(e => { + win.close(); + throw e; + }) + }); + }); + +</script> diff --git a/testing/web-platform/tests/html/webappapis/idle-callbacks/resources/post_name_on_load.html b/testing/web-platform/tests/html/webappapis/idle-callbacks/resources/post_name_on_load.html new file mode 100644 index 0000000000..4679a6e6ec --- /dev/null +++ b/testing/web-platform/tests/html/webappapis/idle-callbacks/resources/post_name_on_load.html @@ -0,0 +1,7 @@ +<!doctype html> +<script> + addEventListener('load', _ => { + let params = new URLSearchParams(window.location.search); + window.opener.postMessage(params.get('name'), '*'); + }); +</script> |