new file mode 100644
--- /dev/null
+++ b/dom/base/IdleDeadline.cpp
@@ -0,0 +1,70 @@
+/* -*- 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/. */
+
+#include <algorithm>
+
+#include "mozilla/dom/IdleDeadline.h"
+#include "mozilla/dom/IdleDeadlineBinding.h"
+#include "mozilla/dom/Performance.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(IdleDeadline, mWindow)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleDeadline)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleDeadline)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleDeadline)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+IdleDeadline::IdleDeadline(nsPIDOMWindowInner* aWindow, bool aDidTimeout,
+ DOMHighResTimeStamp aDeadline)
+ : mWindow(aWindow)
+ , mDidTimeout(aDidTimeout)
+ , mDeadline(aDeadline)
+{
+}
+
+IdleDeadline::~IdleDeadline()
+{
+}
+
+JSObject*
+IdleDeadline::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return IdleDeadlineBinding::Wrap(aCx, this, aGivenProto);
+}
+
+DOMHighResTimeStamp
+IdleDeadline::TimeRemaining()
+{
+ if (mDidTimeout) {
+ return 0.0;
+ }
+
+ RefPtr<Performance> performance = mWindow->GetPerformance();
+ if (!performance) {
+ // If there is no performance object the window is partially torn
+ // down, so we can safely say that there is no time remaining.
+ return 0.0;
+ }
+
+ return std::max(mDeadline - performance->Now(), 0.0);
+}
+
+bool
+IdleDeadline::DidTimeout() const
+{
+ return mDidTimeout;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/base/IdleDeadline.h
@@ -0,0 +1,55 @@
+/* -*- 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_IdleDeadline_h
+#define mozilla_dom_IdleDeadline_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsWrapperCache.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class IdleDeadline final
+ : public nsISupports
+ , public nsWrapperCache
+{
+public:
+ IdleDeadline(nsPIDOMWindowInner* aWindow, bool aDidTimeout,
+ DOMHighResTimeStamp aDeadline);
+
+ nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ DOMHighResTimeStamp TimeRemaining();
+ bool DidTimeout() const;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(IdleDeadline)
+
+private:
+ ~IdleDeadline();
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ const bool mDidTimeout;
+ const DOMHighResTimeStamp mDeadline;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_IdleDeadline_h
new file mode 100644
--- /dev/null
+++ b/dom/base/IdleRequest.cpp
@@ -0,0 +1,157 @@
+/* -*- 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 "IdleRequest.h"
+
+#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"
+#include "nsGlobalWindow.h"
+#include "nsISupportsPrimitives.h"
+#include "nsPIDOMWindow.h"
+
+namespace mozilla {
+namespace dom {
+
+IdleRequest::IdleRequest(JSContext* aCx, nsPIDOMWindowInner* aWindow,
+ IdleRequestCallback& aCallback, uint32_t aHandle)
+ : mWindow(aWindow)
+ , mCallback(&aCallback)
+ , mHandle(aHandle)
+ , mTimeoutHandle(Nothing())
+{
+ MOZ_ASSERT(aWindow);
+
+ // Get the calling location.
+ nsJSUtils::GetCallingLocation(aCx, mFileName, &mLineNo, &mColumn);
+}
+
+IdleRequest::~IdleRequest()
+{
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequest)
+
+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()
+{
+ mCallback = nullptr;
+ CancelTimeout();
+ if (isInList()) {
+ remove();
+ }
+ Release();
+
+ return NS_OK;
+}
+
+void
+IdleRequest::SetDeadline(TimeStamp aDeadline)
+{
+ mozilla::dom::Performance* perf = mWindow->GetPerformance();
+ mDeadline =
+ perf ? perf->GetDOMTiming()->TimeStampToDOMHighRes(aDeadline) : 0.0;
+}
+
+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;
+ Release();
+
+ 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
new file mode 100644
--- /dev/null
+++ b/dom/base/IdleRequest.h
@@ -0,0 +1,74 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_idlerequest_h
+#define mozilla_dom_idlerequest_h
+
+#include "mozilla/LinkedList.h"
+#include "mozilla/Maybe.h"
+#include "nsCOMPtr.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsITimeoutHandler.h"
+
+class nsPIDOMWindowInner;
+
+namespace mozilla {
+namespace dom {
+
+class IdleRequestCallback;
+
+class IdleRequest final : public nsITimeoutHandler
+ , public nsIRunnable
+ , public nsICancelableRunnable
+ , public nsIIncrementalRunnable
+ , public LinkedListElement<IdleRequest>
+{
+public:
+ IdleRequest(JSContext* aCx, nsPIDOMWindowInner* aWindow,
+ 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 SetTimeout(uint32_t aTimout);
+ nsresult RunIdleRequestCallback(bool aDidTimeout);
+ void CancelTimeout();
+
+ NS_DECL_NSIRUNNABLE;
+ virtual nsresult Cancel() override;
+ virtual void SetDeadline(mozilla::TimeStamp aDeadline) override;
+
+ uint32_t Handle() const
+ {
+ return mHandle;
+ }
+
+ 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;
+
+ nsCOMPtr<nsPIDOMWindowInner> mWindow;
+ RefPtr<IdleRequestCallback> mCallback;
+ uint32_t mHandle;
+ mozilla::Maybe<int32_t> mTimeoutHandle;
+ DOMHighResTimeStamp mDeadline;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_idlerequest_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -180,16 +180,18 @@ EXPORTS.mozilla.dom += [
'EventSource.h',
'File.h',
'FileList.h',
'FileReader.h',
'FormData.h',
'FragmentOrElement.h',
'FromParser.h',
'GroupedSHistory.h',
+ 'IdleDeadline.h',
+ 'IdleRequest.h',
'ImageEncoder.h',
'ImageTracker.h',
'ImportManager.h',
'Link.h',
'Location.h',
'MutableBlobStorage.h',
'MutableBlobStreamListener.h',
'NameSpaceConstants.h',
@@ -247,16 +249,18 @@ UNIFIED_SOURCES += [
'Element.cpp',
'EventSource.cpp',
'File.cpp',
'FileList.cpp',
'FileReader.cpp',
'FormData.cpp',
'FragmentOrElement.cpp',
'GroupedSHistory.cpp',
+ 'IdleDeadline.cpp',
+ 'IdleRequest.cpp',
'ImageEncoder.cpp',
'ImageTracker.cpp',
'ImportManager.cpp',
'Link.cpp',
'Location.cpp',
'MultipartBlobImpl.cpp',
'MutableBlobStorage.cpp',
'MutableBlobStreamListener.cpp',
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -13,16 +13,17 @@
// Local Includes
#include "Navigator.h"
#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/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
@@ -322,16 +323,21 @@ nsGlobalWindow::DOMMinTimeoutValue() con
// uses 5.
#define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
// The longest interval (as PRIntervalTime) we permit, or that our
// timer code can handle, really. See DELAY_INTERVAL_LIMIT in
// nsTimerImpl.h for details.
#define DOM_MAX_TIMEOUT_VALUE DELAY_INTERVAL_LIMIT
+// The interval at which we execute idle callbacks
+static uint32_t gThrottledIdlePeriodLength;
+
+#define DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH 10000
+
#define FORWARD_TO_OUTER(method, args, err_rval) \
PR_BEGIN_MACRO \
if (IsInnerWindow()) { \
nsGlobalWindow *outer = GetOuterWindowInternal(); \
if (!AsInner()->HasActiveDocument()) { \
NS_WARNING(outer ? \
"Inner window does not have active document." : \
"No outer window available!"); \
@@ -535,16 +541,119 @@ DialogValueHolder::Get(JSContext* aCx, J
if (aSubject->Subsumes(mOrigin)) {
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());
+}
+
+/* static */ void
+nsGlobalWindow::InsertIdleCallbackIntoList(IdleRequest* aRequest,
+ IdleRequests& aList)
+{
+ aList.insertBack(aRequest);
+ aRequest->AddRef();
+}
+
+uint32_t
+nsGlobalWindow::RequestIdleCallback(JSContext* aCx,
+ IdleRequestCallback& aCallback,
+ const IdleRequestOptions& aOptions,
+ ErrorResult& aError)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+ AssertIsOnMainThread();
+
+ uint32_t handle = ++mIdleRequestCallbackCounter;
+
+ RefPtr<IdleRequest> request =
+ new IdleRequest(aCx, AsInner(), aCallback, handle);
+
+ 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());
+ }
+
+ return handle;
+}
+
+void
+nsGlobalWindow::CancelIdleCallback(uint32_t aHandle)
+{
+ MOZ_RELEASE_ASSERT(IsInnerWindow());
+
+ for (IdleRequest* r : mIdleRequestCallbacks) {
+ if (r->Handle() == aHandle) {
+ r->Cancel();
+ break;
+ }
+ }
+}
+
+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
template<class T>
@@ -1150,16 +1259,17 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
mTimeoutInsertionPoint(nullptr),
mTimeoutIdCounter(1),
mTimeoutFiringDepth(0),
mSuspendDepth(0),
mFreezeDepth(0),
mFocusMethod(0),
mSerial(0),
mIdleCallbackTimeoutCounter(1),
+ mIdleRequestCallbackCounter(1),
#ifdef DEBUG
mSetOpenerWindowCalled(false),
#endif
#ifdef MOZ_B2G
mNetworkUploadObserverEnabled(false),
mNetworkDownloadObserverEnabled(false),
#endif
mCleanedUp(false),
@@ -1215,16 +1325,20 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalW
"dom.min_timeout_value",
DEFAULT_MIN_TIMEOUT_VALUE);
Preferences::AddIntVarCache(&gMinBackgroundTimeoutValue,
"dom.min_background_timeout_value",
DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE);
Preferences::AddBoolVarCache(&sIdleObserversAPIFuzzTimeDisabled,
"dom.idle-observers-api.fuzz_time.disabled",
false);
+
+ Preferences::AddUintVarCache(&gThrottledIdlePeriodLength,
+ "dom.idle_period.throttled_length",
+ DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH);
}
if (gDumpFile == nullptr) {
const nsAdoptingCString& fname =
Preferences::GetCString("browser.dom.window.dump.file");
if (!fname.IsEmpty()) {
// if this fails to open, Dump() knows to just go to stdout
// on null.
@@ -1561,16 +1675,17 @@ nsGlobalWindow::CleanUp()
if (IsInnerWindow()) {
DisableGamepadUpdates();
mHasGamepad = false;
DisableVRUpdates();
mHasVREvents = false;
#ifdef MOZ_B2G
DisableTimeChangeNotifications();
#endif
+ DisableIdleCallbackRequests();
} else {
MOZ_ASSERT(!mHasGamepad);
MOZ_ASSERT(!mHasVREvents);
}
if (mCleanMessageManager) {
MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
nsGlobalChromeWindow *asChrome = static_cast<nsGlobalChromeWindow*>(this);
@@ -1640,16 +1755,18 @@ nsGlobalWindow::FreeInnerObjects()
if (mIdleTimer) {
mIdleTimer->Cancel();
mIdleTimer = nullptr;
}
mIdleObservers.Clear();
+ DisableIdleCallbackRequests();
+
mChromeEventHandler = nullptr;
if (mListenerManager) {
mListenerManager->Disconnect();
mListenerManager = nullptr;
}
mLocation = nullptr;
@@ -1849,16 +1966,25 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplicationCache)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
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)
@@ -1909,16 +2035,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
nsGlobalWindow::Cast(tmp->mOuterWindow)->MaybeClearInnerWindow(tmp);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOuterWindow)
}
if (tmp->mListenerManager) {
tmp->mListenerManager->Disconnect();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager)
}
+
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistory)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomElements)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStorage)
if (tmp->mApplicationCache) {
static_cast<nsDOMOfflineResourceList*>(tmp->mApplicationCache.get())->Disconnect();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplicationCache)
@@ -1954,16 +2081,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mU2F)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mMozSelfSupport)
tmp->UnlinkHostObjectURIs();
+ tmp->DisableIdleCallbackRequests();
+
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
#ifdef DEBUG
void
nsGlobalWindow::RiskyUnlink()
{
NS_CYCLE_COLLECTION_INNERNAME.Unlink(this);
@@ -9850,16 +9979,23 @@ 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
@@ -12580,18 +12716,17 @@ nsGlobalWindow::RunTimeoutHandler(Timeou
const char *reason;
if (timeout->mIsInterval) {
reason = "setInterval handler";
} else {
reason = "setTimeout handler";
}
bool abortIntervalHandler = false;
- nsCOMPtr<nsITimeoutHandler> basicHandler(timeout->mScriptHandler);
- nsCOMPtr<nsIScriptTimeoutHandler> handler(do_QueryInterface(basicHandler));
+ nsCOMPtr<nsIScriptTimeoutHandler> handler(do_QueryInterface(timeout->mScriptHandler));
if (handler) {
RefPtr<Function> callback = handler->GetCallback();
if (!callback) {
// Evaluate the timeout expression.
const nsAString& script = handler->GetHandlerText();
const char* filename = nullptr;
@@ -12618,17 +12753,19 @@ nsGlobalWindow::RunTimeoutHandler(Timeou
callback->Call(me, handler->GetArgs(), &ignoredVal, rv, reason);
if (rv.IsUncatchableException()) {
abortIntervalHandler = true;
}
rv.SuppressException();
}
} else {
+ nsCOMPtr<nsITimeoutHandler> basicHandler(timeout->mScriptHandler);
nsCOMPtr<nsISupports> kungFuDeathGrip(static_cast<nsIDOMWindow*>(this));
+ mozilla::Unused << kungFuDeathGrip;
basicHandler->Call();
}
// If we received an uncatchable exception, do not schedule the timeout again.
// This allows the slow script dialog to break easy DoS attacks like
// setInterval(function() { while(1); }, 100);
if (abortIntervalHandler) {
// If it wasn't an interval timer to begin with, this does nothing. If it
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -109,16 +109,18 @@ struct ChannelPixelLayout;
class Console;
class Crypto;
class CustomElementRegistry;
class DocGroup;
class External;
class Function;
class Gamepad;
enum class ImageBitmapFormat : uint32_t;
+class IdleRequest;
+class IdleRequestCallback;
class Location;
class MediaQueryList;
class MozSelfSupport;
class Navigator;
class OwningExternalOrWindowProxy;
class Promise;
class PostMessageEvent;
struct RequestInit;
@@ -746,16 +748,17 @@ public:
// Inner windows only.
// Enable/disable updates for VR
void EnableVRUpdates();
void DisableVRUpdates();
// Update the VR displays for this window
bool UpdateVRDisplays(nsTArray<RefPtr<mozilla::dom::VRDisplay>>& aDisplays);
+
// Inner windows only.
// Called to inform that the set of active VR displays has changed.
void NotifyActiveVRDisplaysChanged();
#define EVENT(name_, id_, type_, struct_) \
mozilla::dom::EventHandlerNonNull* GetOn##name_() \
{ \
mozilla::EventListenerManager* elm = GetExistingListenerManager(); \
@@ -1062,16 +1065,24 @@ public:
mozilla::ErrorResult& aError);
void GetOuterHeight(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
mozilla::ErrorResult& aError);
void SetOuterHeight(JSContext* aCx, JS::Handle<JS::Value> aValue,
mozilla::ErrorResult& aError);
int32_t RequestAnimationFrame(mozilla::dom::FrameRequestCallback& aCallback,
mozilla::ErrorResult& aError);
void CancelAnimationFrame(int32_t aHandle, mozilla::ErrorResult& aError);
+
+ uint32_t RequestIdleCallback(JSContext* aCx,
+ mozilla::dom::IdleRequestCallback& aCallback,
+ const mozilla::dom::IdleRequestOptions& aOptions,
+ mozilla::ErrorResult& aError);
+ void CancelIdleCallback(uint32_t aHandle);
+
+
#ifdef MOZ_WEBSPEECH
mozilla::dom::SpeechSynthesis*
GetSpeechSynthesis(mozilla::ErrorResult& aError);
bool HasActiveSpeechSynthesis();
#endif
already_AddRefed<nsICSSDeclaration>
GetDefaultComputedStyle(mozilla::dom::Element& aElt,
const nsAString& aPseudoElt,
@@ -1233,17 +1244,16 @@ public:
void GetInterface(JSContext* aCx, nsIJSID* aIID,
JS::MutableHandle<JS::Value> aRetval,
mozilla::ErrorResult& aError);
already_AddRefed<nsWindowRoot> GetWindowRootOuter();
already_AddRefed<nsWindowRoot> GetWindowRoot(mozilla::ErrorResult& aError);
mozilla::dom::Performance* GetPerformance();
-
protected:
// Web IDL helpers
// Redefine the property called aPropName on this window object to be a value
// property with the value aValue, much like we would do for a [Replaceable]
// property in IDL.
void RedefineProperty(JSContext* aCx, const char* aPropName,
JS::Handle<JS::Value> aValue,
@@ -1835,18 +1845,32 @@ protected:
uint32_t mSuspendDepth;
uint32_t mFreezeDepth;
// the method that was used to focus mFocusedNode
uint32_t mFocusMethod;
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;
+
#ifdef DEBUG
bool mSetOpenerWindowCalled;
nsCOMPtr<nsIURI> mLastOpenedURI;
#endif
#ifdef MOZ_B2G
bool mNetworkUploadObserverEnabled;
bool mNetworkDownloadObserverEnabled;
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1409,16 +1409,19 @@ DOMInterfaces = {
'concrete': False,
},
'Window': {
'nativeType': 'nsGlobalWindow',
'binaryNames': {
'postMessage': 'postMessageMoz',
},
+ 'implicitJSContext': [
+ 'requestIdleCallback'
+ ],
},
'WindowProxy': {
'nativeType': 'nsPIDOMWindowOuter',
'headerFile': 'nsPIDOMWindow.h',
'concrete': False
},
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -78,16 +78,17 @@
#endif
#include "mozilla/Unused.h"
#include "mozInlineSpellChecker.h"
#include "nsDocShell.h"
#include "nsIConsoleListener.h"
#include "nsICycleCollectorListener.h"
+#include "nsIIdlePeriod.h"
#include "nsIDragService.h"
#include "nsIIPCBackgroundChildCreateCallback.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsIMemoryReporter.h"
#include "nsIMemoryInfoDumper.h"
#include "nsIMutable.h"
#include "nsIObserverService.h"
#include "nsIScriptSecurityManager.h"
new file mode 100644
--- /dev/null
+++ b/dom/webidl/IdleDeadline.webidl
@@ -0,0 +1,14 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is:
+ * https://w3c.github.io/requestidlecallback/
+ */
+
+[Pref="dom.requestIdleCallback.enabled"]
+interface IdleDeadline {
+ DOMHighResTimeStamp timeRemaining();
+ readonly attribute boolean didTimeout;
+};
--- a/dom/webidl/Window.webidl
+++ b/dom/webidl/Window.webidl
@@ -9,16 +9,17 @@
* https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html
* http://dev.w3.org/csswg/cssom/
* http://dev.w3.org/csswg/cssom-view/
* https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/RequestAnimationFrame/Overview.html
* https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/NavigationTiming/Overview.html
* https://dvcs.w3.org/hg/webcrypto-api/raw-file/tip/spec/Overview.html
* http://dvcs.w3.org/hg/speech-api/raw-file/tip/speechapi.html
* https://w3c.github.io/webappsec-secure-contexts/#monkey-patching-global-object
+ * https://w3c.github.io/requestidlecallback/
*/
interface ApplicationCache;
interface IID;
interface nsIBrowserDOMWindow;
interface nsIMessageBroadcaster;
interface nsIDOMCrypto;
typedef any Transferable;
@@ -485,8 +486,22 @@ partial interface Window {
[Pref="dom.vr.enabled"]
attribute EventHandler onvrdisplaydisconnect;
[Pref="dom.vr.enabled"]
attribute EventHandler onvrdisplaypresentchange;
};
Window implements ChromeWindow;
Window implements WindowOrWorkerGlobalScope;
+
+partial interface Window {
+ [Throws, Pref="dom.requestIdleCallback.enabled"]
+ unsigned long requestIdleCallback(IdleRequestCallback callback,
+ optional IdleRequestOptions options);
+ [Pref="dom.requestIdleCallback.enabled"]
+ void cancelIdleCallback(unsigned long handle);
+};
+
+dictionary IdleRequestOptions {
+ unsigned long timeout;
+};
+
+callback IdleRequestCallback = void (IdleDeadline deadline);
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -264,16 +264,17 @@ WEBIDL_FILES = [
'IDBIndex.webidl',
'IDBKeyRange.webidl',
'IDBMutableFile.webidl',
'IDBObjectStore.webidl',
'IDBOpenDBRequest.webidl',
'IDBRequest.webidl',
'IDBTransaction.webidl',
'IDBVersionChangeEvent.webidl',
+ 'IdleDeadline.webidl',
'IIRFilterNode.webidl',
'ImageBitmap.webidl',
'ImageBitmapRenderingContext.webidl',
'ImageCapture.webidl',
'ImageData.webidl',
'ImageDocument.webidl',
'InputEvent.webidl',
'InputMethod.webidl',
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -146,16 +146,23 @@ using namespace mozilla::gfx;
#define GRID_ENABLED_PREF_NAME "layout.css.grid.enabled"
#define GRID_TEMPLATE_SUBGRID_ENABLED_PREF_NAME "layout.css.grid-template-subgrid-value.enabled"
#define WEBKIT_PREFIXES_ENABLED_PREF_NAME "layout.css.prefixes.webkit"
#define DISPLAY_CONTENTS_ENABLED_PREF_NAME "layout.css.display-contents.enabled"
#define TEXT_ALIGN_UNSAFE_ENABLED_PREF_NAME "layout.css.text-align-unsafe-value.enabled"
#define FLOAT_LOGICAL_VALUES_ENABLED_PREF_NAME "layout.css.float-logical-values.enabled"
#define BG_CLIP_TEXT_ENABLED_PREF_NAME "layout.css.background-clip-text.enabled"
+// The time in number of frames that we estimate for a refresh driver
+// to be quiescent
+#define DEFAULT_QUIESCENT_FRAMES 2
+// The time (milliseconds) we estimate is needed between the end of an
+// idle time and the next Tick.
+#define DEFAULT_IDLE_PERIOD_TIME_LIMIT 3.0f
+
#ifdef DEBUG
// TODO: remove, see bug 598468.
bool nsLayoutUtils::gPreventAssertInCompareTreePosition = false;
#endif // DEBUG
typedef FrameMetrics::ViewID ViewID;
typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox;
@@ -169,16 +176,18 @@ typedef nsStyleTransformMatrix::Transfor
/* static */ bool nsLayoutUtils::sInvalidationDebuggingIsEnabled;
/* static */ bool nsLayoutUtils::sCSSVariablesEnabled;
/* static */ bool nsLayoutUtils::sInterruptibleReflowEnabled;
/* static */ bool nsLayoutUtils::sSVGTransformBoxEnabled;
/* static */ bool nsLayoutUtils::sTextCombineUprightDigitsEnabled;
#ifdef MOZ_STYLO
/* static */ bool nsLayoutUtils::sStyloEnabled;
#endif
+/* static */ uint32_t nsLayoutUtils::sIdlePeriodDeadlineLimit;
+/* static */ uint32_t nsLayoutUtils::sQuiescentFramesBeforeIdlePeriod;
static ViewID sScrollIdCounter = FrameMetrics::START_SCROLL_ID;
typedef nsDataHashtable<nsUint64HashKey, nsIContent*> ContentMap;
static ContentMap* sContentMap = nullptr;
static ContentMap& GetContentMap() {
if (!sContentMap) {
sContentMap = new ContentMap();
@@ -7866,16 +7875,22 @@ nsLayoutUtils::Initialize()
Preferences::AddBoolVarCache(&sSVGTransformBoxEnabled,
"svg.transform-box.enabled");
Preferences::AddBoolVarCache(&sTextCombineUprightDigitsEnabled,
"layout.css.text-combine-upright-digits.enabled");
#ifdef MOZ_STYLO
Preferences::AddBoolVarCache(&sStyloEnabled,
"layout.css.servo.enabled");
#endif
+ Preferences::AddUintVarCache(&sIdlePeriodDeadlineLimit,
+ "layout.idle_period.time_limit",
+ DEFAULT_IDLE_PERIOD_TIME_LIMIT);
+ Preferences::AddUintVarCache(&sQuiescentFramesBeforeIdlePeriod,
+ "layout.idle_period.required_quiescent_frames",
+ DEFAULT_QUIESCENT_FRAMES);
for (auto& callback : kPrefCallbacks) {
Preferences::RegisterCallbackAndCall(callback.func, callback.name);
}
nsComputedDOMStyle::RegisterPrefChangeCallbacks();
}
/* static */
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2424,16 +2424,24 @@ public:
static bool StyloEnabled() {
#ifdef MOZ_STYLO
return sStyloEnabled;
#else
return false;
#endif
}
+ static uint32_t IdlePeriodDeadlineLimit() {
+ return sIdlePeriodDeadlineLimit;
+ }
+
+ static uint32_t QuiescentFramesBeforeIdlePeriod() {
+ return sQuiescentFramesBeforeIdlePeriod;
+ }
+
/**
* See comment above "font.size.inflation.mappingIntercept" in
* modules/libpref/src/init/all.js .
*/
static int32_t FontSizeInflationMappingIntercept() {
return sFontSizeInflationMappingIntercept;
}
@@ -2880,16 +2888,18 @@ private:
static bool sInvalidationDebuggingIsEnabled;
static bool sCSSVariablesEnabled;
static bool sInterruptibleReflowEnabled;
static bool sSVGTransformBoxEnabled;
static bool sTextCombineUprightDigitsEnabled;
#ifdef MOZ_STYLO
static bool sStyloEnabled;
#endif
+ static uint32_t sIdlePeriodDeadlineLimit;
+ static uint32_t sQuiescentFramesBeforeIdlePeriod;
/**
* Helper function for LogTestDataForPaint().
*/
static void DoLogTestDataForPaint(mozilla::layers::LayerManager* aManager,
ViewID aScrollId,
const std::string& aKey,
const std::string& aValue);
--- a/layout/base/nsRefreshDriver.cpp
+++ b/layout/base/nsRefreshDriver.cpp
@@ -133,16 +133,17 @@ namespace mozilla {
* start/stop whatever timer mechanism is in use, and ScheduleNextTick
* is called at the start of the Tick() implementation to set a time
* for the next tick.
*/
class RefreshDriverTimer {
public:
RefreshDriverTimer()
: mLastFireEpoch(0)
+ , mLastFireSkipped(false)
{
}
virtual ~RefreshDriverTimer()
{
MOZ_ASSERT(mContentRefreshDrivers.Length() == 0, "Should have removed all content refresh drivers from here by now!");
MOZ_ASSERT(mRootRefreshDrivers.Length() == 0, "Should have removed all root refresh drivers from here by now!");
}
@@ -216,16 +217,45 @@ public:
driver->mActiveTimer = aNewTimer;
}
mRootRefreshDrivers.Clear();
aNewTimer->mLastFireEpoch = mLastFireEpoch;
aNewTimer->mLastFireTime = mLastFireTime;
}
+ virtual TimeDuration GetTimerRate() = 0;
+
+ bool LastTickSkippedAnyPaints() const
+ {
+ return mLastFireSkipped;
+ }
+
+ Maybe<TimeStamp> GetIdleDeadlineHint()
+ {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (LastTickSkippedAnyPaints()) {
+ return Some(TimeStamp());
+ }
+
+ TimeStamp mostRecentRefresh = MostRecentRefresh();
+ TimeDuration refreshRate = GetTimerRate();
+ TimeStamp idleEnd = mostRecentRefresh + refreshRate;
+
+ if (idleEnd +
+ refreshRate * nsLayoutUtils::QuiescentFramesBeforeIdlePeriod() <
+ TimeStamp::Now()) {
+ return Nothing();
+ }
+
+ return Some(idleEnd - TimeDuration::FromMilliseconds(
+ nsLayoutUtils::IdlePeriodDeadlineLimit()));
+ }
+
protected:
virtual void StartTimer() = 0;
virtual void StopTimer() = 0;
virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
bool IsRootRefreshDriver(nsRefreshDriver* aDriver)
{
nsPresContext* pc = aDriver->GetPresContext();
@@ -257,28 +287,31 @@ protected:
nsTArray<RefPtr<nsRefreshDriver> > drivers(aDrivers);
for (nsRefreshDriver* driver : drivers) {
// don't poke this driver if it's in test mode
if (driver->IsTestControllingRefreshesEnabled()) {
continue;
}
TickDriver(driver, aJsNow, aNow);
+
+ mLastFireSkipped = mLastFireSkipped || driver->mSkippedPaints;
}
}
/*
* Tick the refresh drivers based on the given timestamp.
*/
void Tick(int64_t jsnow, TimeStamp now)
{
ScheduleNextTick(now);
mLastFireEpoch = jsnow;
mLastFireTime = now;
+ mLastFireSkipped = false;
LOG("[%p] ticking drivers...", this);
// RD is short for RefreshDriver
profiler_tracing("Paint", "RD", TRACING_INTERVAL_START);
TickRefreshDrivers(jsnow, now, mContentRefreshDrivers);
TickRefreshDrivers(jsnow, now, mRootRefreshDrivers);
@@ -288,16 +321,17 @@ protected:
static void TickDriver(nsRefreshDriver* driver, int64_t jsnow, TimeStamp now)
{
LOG(">> TickDriver: %p (jsnow: %lld)", driver, jsnow);
driver->Tick(jsnow, now);
}
int64_t mLastFireEpoch;
+ bool mLastFireSkipped;
TimeStamp mLastFireTime;
TimeStamp mTargetTime;
nsTArray<RefPtr<nsRefreshDriver> > mContentRefreshDrivers;
nsTArray<RefPtr<nsRefreshDriver> > mRootRefreshDrivers;
// useful callback for nsITimer-based derived classes, here
// bacause of c++ protected shenanigans
@@ -340,31 +374,36 @@ public:
mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds);
}
double GetRate() const
{
return mRateMilliseconds;
}
+ virtual TimeDuration GetTimerRate() override
+ {
+ return mRateDuration;
+ }
+
protected:
- virtual void StartTimer()
+ virtual void StartTimer() override
{
// pretend we just fired, and we schedule the next tick normally
mLastFireEpoch = JS_Now();
mLastFireTime = TimeStamp::Now();
mTargetTime = mLastFireTime + mRateDuration;
uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
mTimer->InitWithFuncCallback(TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT);
}
- virtual void StopTimer()
+ virtual void StopTimer() override
{
mTimer->Cancel();
}
double mRateMilliseconds;
TimeDuration mRateDuration;
RefPtr<nsITimer> mTimer;
};
@@ -381,26 +420,44 @@ public:
: mVsyncChild(nullptr)
{
MOZ_ASSERT(XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
mVsyncObserver = new RefreshDriverVsyncObserver(this);
RefPtr<mozilla::gfx::VsyncSource> vsyncSource = gfxPlatform::GetPlatform()->GetHardwareVsync();
MOZ_ALWAYS_TRUE(mVsyncDispatcher = vsyncSource->GetRefreshTimerVsyncDispatcher());
mVsyncDispatcher->SetParentRefreshTimer(mVsyncObserver);
+ mVsyncRate = vsyncSource->GetGlobalDisplay().GetVsyncRate();
}
explicit VsyncRefreshDriverTimer(VsyncChild* aVsyncChild)
: mVsyncChild(aVsyncChild)
{
MOZ_ASSERT(!XRE_IsParentProcess());
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mVsyncChild);
mVsyncObserver = new RefreshDriverVsyncObserver(this);
mVsyncChild->SetVsyncObserver(mVsyncObserver);
+ mVsyncRate = mVsyncChild->GetVsyncRate();
+ }
+
+ virtual TimeDuration GetTimerRate() override
+ {
+ if (mVsyncRate != TimeDuration::Forever()) {
+ return mVsyncRate;
+ }
+
+ if (mVsyncChild) {
+ mVsyncRate = mVsyncChild->GetVsyncRate();
+ }
+
+ // If hardware queries fail / are unsupported, we have to just guess.
+ return mVsyncRate != TimeDuration::Forever()
+ ? mVsyncRate
+ : TimeDuration::FromMilliseconds(1000.0 / 60.0);
}
private:
// Since VsyncObservers are refCounted, but the RefreshDriverTimer are
// explicitly shutdown. We create an inner class that has the VsyncObserver
// and is shutdown when the RefreshDriverTimer is deleted. The alternative is
// to (a) make all RefreshDriverTimer RefCounted or (b) use different
// VsyncObserver types.
@@ -453,17 +510,16 @@ private:
}
void OnTimerStart()
{
if (!XRE_IsParentProcess()) {
mLastChildTick = TimeStamp::Now();
}
}
-
private:
virtual ~RefreshDriverVsyncObserver() {}
void RecordTelemetryProbes(TimeStamp aVsyncTimestamp)
{
MOZ_ASSERT(NS_IsMainThread());
#ifndef ANDROID /* bug 1142079 */
if (XRE_IsParentProcess()) {
@@ -608,16 +664,17 @@ private:
RefPtr<RefreshDriverVsyncObserver> mVsyncObserver;
// Used for parent process.
RefPtr<RefreshTimerVsyncDispatcher> mVsyncDispatcher;
// Used for child process.
// The mVsyncChild will be always available before VsncChild::ActorDestroy().
// After ActorDestroy(), StartTimer() and StopTimer() calls will be non-op.
RefPtr<VsyncChild> mVsyncChild;
+ TimeDuration mVsyncRate;
}; // VsyncRefreshDriverTimer
/**
* Since the content process takes some time to setup
* the vsync IPC connection, this timer is used
* during the intial startup process.
* During initial startup, the refresh drivers
* are ticked off this timer, and are swapped out once content
@@ -672,17 +729,17 @@ public:
InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds)
: SimpleTimerBasedRefreshDriverTimer(aRate),
mNextTickDuration(aRate),
mDisableAfterMilliseconds(aDisableAfterMilliseconds),
mNextDriverIndex(0)
{
}
- virtual void AddRefreshDriver(nsRefreshDriver* aDriver)
+ virtual void AddRefreshDriver(nsRefreshDriver* aDriver) override
{
RefreshDriverTimer::AddRefreshDriver(aDriver);
LOG("[%p] inactive timer got new refresh driver %p, resetting rate",
this, aDriver);
// reset the timer, and start with the newly added one next time.
mNextTickDuration = mRateMilliseconds;
@@ -690,39 +747,44 @@ public:
// we don't really have to start with the newly added one, but we may as well
// not tick the old ones at the fastest rate any more than we need to.
mNextDriverIndex = GetRefreshDriverCount() - 1;
StopTimer();
StartTimer();
}
+ virtual TimeDuration GetTimerRate() override
+ {
+ return TimeDuration::FromMilliseconds(mNextTickDuration);
+ }
+
protected:
uint32_t GetRefreshDriverCount()
{
return mContentRefreshDrivers.Length() + mRootRefreshDrivers.Length();
}
- virtual void StartTimer()
+ virtual void StartTimer() override
{
mLastFireEpoch = JS_Now();
mLastFireTime = TimeStamp::Now();
mTargetTime = mLastFireTime + mRateDuration;
uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
mTimer->InitWithFuncCallback(TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT);
}
- virtual void StopTimer()
+ virtual void StopTimer() override
{
mTimer->Cancel();
}
- virtual void ScheduleNextTick(TimeStamp aNowTime)
+ virtual void ScheduleNextTick(TimeStamp aNowTime) override
{
if (mDisableAfterMilliseconds > 0.0 &&
mNextTickDuration > mDisableAfterMilliseconds)
{
// We hit the time after which we should disable
// inactive window refreshes; don't schedule anything
// until we get kicked by an AddRefreshDriver call.
return;
@@ -747,24 +809,27 @@ protected:
{
int64_t jsnow = JS_Now();
TimeStamp now = TimeStamp::Now();
ScheduleNextTick(now);
mLastFireEpoch = jsnow;
mLastFireTime = now;
+ mLastFireSkipped = false;
nsTArray<RefPtr<nsRefreshDriver> > drivers(mContentRefreshDrivers);
drivers.AppendElements(mRootRefreshDrivers);
+ size_t index = mNextDriverIndex;
- if (mNextDriverIndex < drivers.Length() &&
- !drivers[mNextDriverIndex]->IsTestControllingRefreshesEnabled())
+ if (index < drivers.Length() &&
+ !drivers[index]->IsTestControllingRefreshesEnabled())
{
- TickDriver(drivers[mNextDriverIndex], jsnow, now);
+ TickDriver(drivers[index], jsnow, now);
+ mLastFireSkipped = mLastFireSkipped || drivers[index]->SkippedPaints();
}
mNextDriverIndex++;
}
static void TimerTickOne(nsITimer* aTimer, void* aClosure)
{
InactiveRefreshDriverTimer *timer = static_cast<InactiveRefreshDriverTimer*>(aClosure);
@@ -2223,16 +2288,34 @@ nsRefreshDriver::CancelPendingEvents(nsI
{
for (auto i : Reversed(MakeRange(mPendingEvents.Length()))) {
if (mPendingEvents[i].mTarget->OwnerDoc() == aDocument) {
mPendingEvents.RemoveElementAt(i);
}
}
}
+/* static */ Maybe<TimeStamp>
+nsRefreshDriver::GetIdleDeadlineHint()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!sRegularRateTimer) {
+ return Nothing();
+ }
+
+ // For computing idleness of refresh drivers we only care about
+ // sRegularRateTimer, since we consider refresh drivers attached to
+ // sThrottledRateTimer to be inactive. This implies that tasks
+ // resulting from a tick on the sRegularRateTimer counts as being
+ // busy but tasks resulting from a tick on sThrottledRateTimer
+ // counts as being idle.
+ return sRegularRateTimer->GetIdleDeadlineHint();
+}
+
void
nsRefreshDriver::Disconnect()
{
MOZ_ASSERT(NS_IsMainThread());
StopTimer();
if (mPresContext) {
--- a/layout/base/nsRefreshDriver.h
+++ b/layout/base/nsRefreshDriver.h
@@ -324,33 +324,73 @@ public:
bool IsWaitingForPaint(mozilla::TimeStamp aTime);
// nsARefreshObserver
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override { return TransactionIdAllocator::AddRef(); }
NS_IMETHOD_(MozExternalRefCountType) Release(void) override { return TransactionIdAllocator::Release(); }
virtual void WillRefresh(mozilla::TimeStamp aTime) override;
+ /**
+ * Compute the time when the currently active refresh driver timer
+ * will start its next tick.
+ *
+ * Returns 'Nothing' if the refresh driver timer hasn't been
+ * initialized or if we can't tell when the next tick will happen.
+ *
+ * Returns Some(TimeStamp()), i.e. the null time, if the next tick is late.
+ *
+ * Otherwise returns Some(TimeStamp(t)), where t is the time of the next tick.
+ *
+ * Using these three types of return values it is possible to
+ * estimate three different things about the idleness of the
+ * currently active group of refresh drivers. This information is
+ * used by nsThread to schedule lower priority "idle tasks".
+ *
+ * The 'Nothing' return value indicates to nsThread that the
+ * currently active refresh drivers will be idle for a time
+ * significantly longer than the current refresh rate and that it is
+ * free to schedule longer periods for executing idle tasks. This is the
+ * expected result when we aren't animating.
+
+ * Returning the null time indicates to nsThread that we are very
+ * busy and that it should definitely not schedule idle tasks at
+ * all. This is the expected result when we are animating, but
+ * aren't able to keep up with the animation and hence need to skip
+ * paints. Since catching up to missed paints will happen as soon as
+ * possible, this is the expected result if any of the refresh
+ * drivers attached to the current refresh driver misses a paint.
+ *
+ * Returning Some(TimeStamp(t)) indicates to nsThread that we will
+ * be idle until. This is usually the case when we're animating
+ * without skipping paints.
+ */
+ static mozilla::Maybe<mozilla::TimeStamp> GetIdleDeadlineHint();
+
+ bool SkippedPaints() const
+ {
+ return mSkippedPaints;
+ }
+
private:
typedef nsTObserverArray<nsARefreshObserver*> ObserverArray;
typedef nsTHashtable<nsISupportsHashKey> RequestTable;
struct ImageStartData {
ImageStartData()
{
}
mozilla::Maybe<mozilla::TimeStamp> mStartTime;
RequestTable mEntries;
};
typedef nsClassHashtable<nsUint32HashKey, ImageStartData> ImageStartTable;
void DispatchPendingEvents();
void DispatchAnimationEvents();
void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
-
void Tick(int64_t aNowEpoch, mozilla::TimeStamp aNowTime);
enum EnsureTimerStartedFlags {
eNone = 0,
eForceAdjustTimer = 1 << 0,
eAllowTimeToGoBackwards = 1 << 1,
eNeverAdjustTimer = 1 << 2,
};
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -183,16 +183,23 @@ pref("dom.permissions.revoke.enable", fa
// Enable Performance Observer API
#ifdef NIGHTLY_BUILD
pref("dom.enable_performance_observer", true);
#else
pref("dom.enable_performance_observer", false);
#endif
+// Enable requestIdleCallback API
+#ifdef NIGHTLY_BUILD
+pref("dom.requestIdleCallback.enabled", true);
+#else
+pref("dom.requestIdleCallback.enabled", false);
+#endif
+
// Whether the Gamepad API is enabled
pref("dom.gamepad.enabled", true);
pref("dom.gamepad.test.enabled", false);
#ifdef RELEASE_OR_BETA
pref("dom.gamepad.non_standard_events.enabled", false);
#else
pref("dom.gamepad.non_standard_events.enabled", true);
#endif
@@ -2689,16 +2696,24 @@ pref("layout.display-list.dump-content",
pref("layout.frame_rate.precise", false);
// pref to control whether layout warnings that are hit quite often are enabled
pref("layout.spammy_warnings.enabled", false);
// Should we fragment floats inside CSS column layout?
pref("layout.float-fragments-inside-column.enabled", true);
+// The number of frames times the frame rate is the time required to
+// pass without painting used to guess that we'll not paint again soon
+pref("layout.idle_period.required_quiescent_frames", 2);
+
+// The amount of time (milliseconds) needed between an idle period's
+// end and the start of the next tick to avoid jank.
+pref("layout.idle_period.time_limit", 3);
+
// Is support for the Web Animations API enabled?
// Before enabling this by default, make sure also CSSPseudoElement interface
// has been spec'ed properly, or we should add a separate pref for
// CSSPseudoElement interface. See Bug 1174575 for further details.
#ifdef RELEASE_OR_BETA
pref("dom.animations-api.core.enabled", false);
#else
pref("dom.animations-api.core.enabled", true);
@@ -2738,16 +2753,22 @@ pref("dom.max_chrome_script_run_time", 2
pref("dom.max_script_run_time", 10);
// Stop all scripts in a compartment when the "stop script" dialog is used.
pref("dom.global_stop_script", true);
// If true, ArchiveReader will be enabled
pref("dom.archivereader.enabled", false);
+// Time (milliseconds) between throttled idle callbacks.
+pref("dom.idle_period.throttled_length", 10000);
+
+// The amount of idle time (milliseconds) reserved for a long idle period
+pref("idle_queue.long_period", 50);
+
// Hang monitor timeout after which we kill the browser, in seconds
// (0 is disabled)
// Disabled on all platforms per bug 705748 until the found issues are
// resolved.
pref("hangmonitor.timeout", 0);
pref("plugins.load_appdir_plugins", false);
// If true, plugins will be click to play
--- a/xpcom/glue/nsThreadUtils.cpp
+++ b/xpcom/glue/nsThreadUtils.cpp
@@ -2,16 +2,17 @@
/* 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 "nsThreadUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/Likely.h"
+#include "mozilla/TimeStamp.h"
#include "LeakRefPtr.h"
#ifdef MOZILLA_INTERNAL_API
# include "nsThreadManager.h"
#else
# include "nsXPCOMCIDInternal.h"
# include "nsIThreadManager.h"
# include "nsServiceManagerUtils.h"
@@ -215,17 +216,37 @@ NS_DispatchToMainThread(already_AddRefed
// release them here.
nsresult
NS_DispatchToMainThread(nsIRunnable* aEvent, uint32_t aDispatchFlags)
{
nsCOMPtr<nsIRunnable> event(aEvent);
return NS_DispatchToMainThread(event.forget(), aDispatchFlags);
}
-extern NS_METHOD
+nsresult
+NS_DelayedDispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs)
+{
+ nsCOMPtr<nsIRunnable> event(aEvent);
+#ifdef MOZILLA_INTERNAL_API
+ nsIThread* thread = NS_GetCurrentThread();
+ if (!thread) {
+ return NS_ERROR_UNEXPECTED;
+ }
+#else
+ nsresult rv;
+ nsCOMPtr<nsIThread> thread;
+ rv = NS_GetCurrentThread(getter_AddRefs(thread));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return rv;
+ }
+#endif
+
+ return thread->DelayedDispatch(event.forget(), aDelayMs);
+}
+
nsresult
NS_IdleDispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent)
{
nsresult rv;
nsCOMPtr<nsIRunnable> event(aEvent);
#ifdef MOZILLA_INTERNAL_API
nsIThread* thread = NS_GetCurrentThread();
if (!thread) {
--- a/xpcom/glue/nsThreadUtils.h
+++ b/xpcom/glue/nsThreadUtils.h
@@ -18,16 +18,17 @@
#include "nsIIncrementalRunnable.h"
#include "nsStringGlue.h"
#include "nsCOMPtr.h"
#include "nsAutoPtr.h"
#include "mozilla/Atomics.h"
#include "mozilla/IndexSequence.h"
#include "mozilla/Likely.h"
#include "mozilla/Move.h"
+#include "mozilla/TimeStamp.h"
#include "mozilla/Tuple.h"
#include "mozilla/TypeTraits.h"
//-----------------------------------------------------------------------------
// These methods are alternatives to the methods on nsIThreadManager, provided
// for convenience.
/**
@@ -128,16 +129,20 @@ NS_DispatchToCurrentThread(already_AddRe
extern nsresult
NS_DispatchToMainThread(nsIRunnable* aEvent,
uint32_t aDispatchFlags = NS_DISPATCH_NORMAL);
extern nsresult
NS_DispatchToMainThread(already_AddRefed<nsIRunnable>&& aEvent,
uint32_t aDispatchFlags = NS_DISPATCH_NORMAL);
extern nsresult
+NS_DelayedDispatchToCurrentThread(
+ already_AddRefed<nsIRunnable>&& aEvent, uint32_t aDelayMs);
+
+extern nsresult
NS_IdleDispatchToCurrentThread(already_AddRefed<nsIRunnable>&& aEvent);
#ifndef XPCOM_GLUE_AVOID_NSPR
/**
* Process all pending events for the given thread before returning. This
* method simply calls ProcessNextEvent on the thread while HasPendingEvents
* continues to return true and the time spent in NS_ProcessPendingEvents
* does not exceed the given timeout value.
--- a/xpcom/threads/nsThread.h
+++ b/xpcom/threads/nsThread.h
@@ -196,20 +196,22 @@ protected:
private:
~nsNestedEventTarget()
{
}
};
// This lock protects access to mObserver, mEvents, mIdleEvents,
- // All of those fields are only modified on the thread itself (never from
- // another thread). This means that we can avoid holding the lock while
- // using mObserver and mEvents on the thread itself. When calling PutEvent
- // on mEvents, we have to hold the lock to synchronize with PopEventQueue.
+ // mIdlePeriod and mEventsAreDoomed. All of those fields are only
+ // modified on the thread itself (never from another thread). This
+ // means that we can avoid holding the lock while using mObserver
+ // and mEvents on the thread itself. When calling PutEvent on
+ // mEvents, we have to hold the lock to synchronize with
+ // PopEventQueue.
mozilla::Mutex mLock;
nsCOMPtr<nsIThreadObserver> mObserver;
mozilla::CycleCollectedJSContext* mScriptObserver;
// Only accessed on the target thread.
nsAutoTObserverArray<NotNull<nsCOMPtr<nsIThreadObserver>>, 2> mEventObservers;