diff --git a/dom/base/IdleDeadline.cpp b/dom/base/IdleDeadline.cpp index 6e71b4930..7df09f37a 100644 --- a/dom/base/IdleDeadline.cpp +++ b/dom/base/IdleDeadline.cpp @@ -51,13 +51,14 @@ IdleDeadline::TimeRemaining() } RefPtr performance = mWindow->GetPerformance(); - if (!performance) { + if (MOZ_UNLIKELY(!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); + DOMHighResTimeStamp remaining = std::max(mDeadline - performance->Now(), 0.0); + return remaining; } bool diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index 3cf87e110..72f5e2c20 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -258,6 +258,27 @@ class nsIScriptTimeoutHandler; #include "mozilla/dom/IdleDeadline.h" // issue 463 +// Caches for Mach factor monitoring (TenFourFox issue 463). +// These can be singletons since they're shared over the entire machine. +// We are stricter with the minimum than with decode delay (issue 434), +// which also uses Mach factor analysis to determine load, because most +// calls will have a max timeout and thus the function is likely to run at +// *some* point. +static const int32_t MACH_FACTOR_MIN = 700; +static const int32_t MACH_CHECK_INTERVAL = 2000; +// This number is actually in the W3C standard, but we allow it to be +// adjusted. +static const int32_t CALLBACK_IDLE_INTERVAL = 50; +static int32_t sMachFactorMin = MACH_FACTOR_MIN; +static int32_t sIdleCallbackMachCheckInterval = MACH_CHECK_INTERVAL; +static int32_t sIdleCallbackIdleInterval = CALLBACK_IDLE_INTERVAL; +#include +#include +#include +static processor_set_name_port_t sMachDefaultPset; +static struct processor_set_load_info sMachLoadInfo; +static host_name_port_t sMachHost; + static const char kStorageEnabled[] = "dom.storage.enabled"; using namespace mozilla; @@ -1210,6 +1231,24 @@ nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow) Preferences::AddBoolVarCache(&sIdleObserversAPIFuzzTimeDisabled, "dom.idle-observers-api.fuzz_time.disabled", false); + + // Precompute variables for Mach factor/load monitoring and idle callbacks + // (TenFourFox issue 463). + Preferences::AddIntVarCache(&sMachFactorMin, + "tenfourfox.dom.requestIdleCallback.mach_factor_min", + MACH_FACTOR_MIN); + Preferences::AddIntVarCache(&sIdleCallbackMachCheckInterval, + "tenfourfox.dom.requestIdleCallback.mach_check_interval", + MACH_CHECK_INTERVAL); + Preferences::AddIntVarCache(&sIdleCallbackIdleInterval, + "tenfourfox.dom.requestIdleCallback.idle_interval", + CALLBACK_IDLE_INTERVAL); + sMachHost = mach_host_self(); + kern_return_t ret = processor_set_default(sMachHost, &sMachDefaultPset); + if (ret != KERN_SUCCESS) { + fprintf(stderr, "TenFourFox: Unable to initialize Mach idle monitoring: %i\n", (uint32_t)ret); + sMachFactorMin = 0; + } } if (gDumpFile == nullptr) { @@ -11544,8 +11583,15 @@ nsGlobalWindow::SetInterval(JSContext* aCx, const nsAString& aHandler, static bool SystemIsIdle() { - // XXX: check Mach factor - return false; + if (!sMachDefaultPset || !sMachFactorMin) return true; + + mach_msg_type_number_t count = PROCESSOR_SET_LOAD_INFO_COUNT; + kern_return_t kr = processor_set_statistics(sMachDefaultPset, + PROCESSOR_SET_LOAD_INFO, + (processor_set_info_t)&sMachLoadInfo, &count); + if (kr != KERN_SUCCESS) return true; + + return (sMachLoadInfo.mach_factor > sMachFactorMin); } nsresult @@ -11553,8 +11599,6 @@ nsGlobalWindow::SetTimeoutOrInterval(nsIScriptTimeoutHandler *aHandler, int32_t interval, bool aIsInterval, int32_t *aReturn) { - // XXX: see below about interval versus deadline. we abuse interval for - // when to recheck if idle. return SetTimeoutOrIntervalOrIdleCallback(aHandler, interval, aIsInterval, aReturn, nullptr); } @@ -11586,14 +11630,28 @@ nsGlobalWindow::SetTimeoutOrIntervalOrIdleCallback(nsIScriptTimeoutHandler *aHan RefPtr timeout = new nsTimeout(); timeout->mIsInterval = aIsInterval; + timeout->mDeadline = 0; + timeout->mElapsed = 0; - // XXX: add to Interval a deadline field, and use the interval for when to check - // if idle again. TenFourFox issue 463 - timeout->mInterval = interval; - if (aCallback) + if (aCallback) { + // This is actually an idle callback. Populate the fields appropriately + // to schedule the idle checks. TenFourFox issue 463. timeout->mCallback = aCallback; - else + timeout->mDeadline = interval; + timeout->mScriptHandler = nullptr; + + // If RequestIdleCallback says this is not an interval, then it must + // want it to run right away. Otherwise schedule the idle checks. + if (aIsInterval) { + interval = sIdleCallbackMachCheckInterval; + } else + interval = 0; + timeout->mInterval = interval; + } else { + timeout->mInterval = interval; + timeout->mCallback = nullptr; timeout->mScriptHandler = aHandler; + } // Now clamp the actual interval we will use for the timer based on uint32_t nestingLevel = sNestingLevel + 1; @@ -11785,13 +11843,25 @@ nsGlobalWindow::RunTimeoutHandler(nsTimeout* aTimeout, if (!timeout->mScriptHandler) { // Call the idle callback. TenFourFox issue 463. MOZ_ASSERT(timeout->mCallback); + // Hold strong ref to ourselves (as below). nsCOMPtr me(static_cast(this)); ErrorResult error; + DOMHighResTimeStamp budget = 0.0; + bool timedout = (bool)(timeout->mElapsed >= timeout->mDeadline); + if (!timedout) { + RefPtr performance = timeout->mWindow->GetPerformance(); + if (MOZ_LIKELY(performance)) { + budget = performance->Now() + sIdleCallbackIdleInterval; + } else { + NS_WARNING("no performance object, timing out outstanding idle callback"); + timedout = true; + } + } RefPtr deadline = new IdleDeadline(timeout->mWindow, - /* didTimeout */ true, - /* timeRemaining */ 0.0f); + /* mDidTimeout */ timedout, + /* mDeadline */ budget); timeout->mCallback->Call(*deadline, error, "requestIdleCallback handler"); timeout->mCallback = nullptr; error.SuppressException(); @@ -12053,13 +12123,6 @@ nsGlobalWindow::RunTimeout(nsTimeout *aTimeout) // The timeout is on the list to run at this depth, go ahead and // process it. - // If this timeout is an IdleCallback (TenFourFox issue 463), check - // to see if we have hit the deadline. If so, run the timeout. If not, - // check to see if we're idle. If so, run the timeout. If not, reschedule - // to check again. - // - // -- NYI XXX -- - // Get the script context (a strong ref to prevent it going away) // for this timeout and ensure the script language is enabled. nsCOMPtr scx = GetContextInternal(); @@ -12072,7 +12135,20 @@ nsGlobalWindow::RunTimeout(nsTimeout *aTimeout) // This timeout is good to run //++timeoutsRan; - bool timeout_was_cleared = RunTimeoutHandler(timeout, scx); + + bool timeout_was_cleared = false; + bool rescheduleOk = true; + if (timeout->mCallback) { + // This is an idle callback (TenFourFox issue 463). + // Check if we are idle, or if we will exceed the deadline, or + // if we are expected to run immediately (mInterval == 0). + timeout->mElapsed += timeout->mInterval; + if (!timeout->mInterval || timeout->mElapsed >= timeout->mDeadline || SystemIsIdle()) { + rescheduleOk = false; + timeout_was_cleared = RunTimeoutHandler(timeout, scx); + } + } else + timeout_was_cleared = RunTimeoutHandler(timeout, scx); if (timeout_was_cleared) { // The running timeout's window was cleared, this means that @@ -12089,7 +12165,9 @@ nsGlobalWindow::RunTimeout(nsTimeout *aTimeout) // If we have a regular interval timer, we re-schedule the // timeout, accounting for clock drift. - bool needsReinsertion = RescheduleTimeout(timeout, now, !aTimeout); + bool needsReinsertion = false; + if (rescheduleOk) + needsReinsertion = RescheduleTimeout(timeout, now, !aTimeout); // Running a timeout can cause another timeout to be deleted, so // we need to reset the pointer to the following timeout. @@ -13926,33 +14004,41 @@ nsGlobalWindow::RequestIdleCallback(JSContext* aCx, { MOZ_RELEASE_ASSERT(IsInnerWindow()); AssertIsOnMainThread(); + int32_t handle = -1, timeout = 3600000; /* default 60 minutes */ + nsresult rv; - // uint32_t handle = ++mIdleRequestCallbackCounter; + if (aOptions.mTimeout.WasPassed()) + timeout = aOptions.mTimeout.Value(); - fprintf(stderr, "::RequestIdleCallback() is not yet implemented\n"); + if (SystemIsIdle()) { + // The computer is already idle, so we will run the callback now. + rv = SetTimeoutOrIntervalOrIdleCallback(nullptr, timeout, false, &handle, + &aCallback); + } else { + fprintf(stderr, "TenFourFox: A request for idle callback is being deferred.\n"); + rv = SetTimeoutOrIntervalOrIdleCallback(nullptr, timeout, true, &handle, + &aCallback); + } + if (NS_SUCCEEDED(rv)) + return handle; - // Plan: - // Check if idle now. If so, set a timeout of zero so it runs right away. - // Else set the deadline, if provided, and set an interval to recheck idle. - // See SystemIsIdle() -#if DEBUG - MOZ_ASSERT(0); -#endif - return 0; // handle; + aError.Throw(NS_ERROR_FAILURE); + return 0; } void nsGlobalWindow::CancelIdleCallback(uint32_t aHandle) { MOZ_RELEASE_ASSERT(IsInnerWindow()); + ErrorResult ignored; - fprintf(stderr, "::CancelIdleCallback() is not yet implemented\n"); + // XXX: As written, this is just an alias for clearTimeout(), so + // web scripts could call either to clear any time of timeout + // or interval. Do we care? - // Plan: - // Check if this is a timeout with a Callback. If not, fail. - // If so, hand this to RemoveTimeout. -#if DEBUG - MOZ_ASSERT(0); -#endif + if (aHandle > 0) { + ClearTimeoutOrInterval(aHandle, ignored); + ignored.SuppressException(); + } }