Bug 1267297 - Use AutoEntryScript for script activity bookkeeping instead of the request machinery. r=bholley

Differential Revision: https://phabricator.services.mozilla.com/D4085

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jan de Mooij 2018-09-04 16:59:56 +00:00
Родитель 56ca0b122a
Коммит 53f1370b5f
8 изменённых файлов: 100 добавлений и 40 удалений

Просмотреть файл

@ -662,8 +662,11 @@ AutoEntryScript::AutoEntryScript(nsIGlobalObject* aGlobalObject,
{ {
MOZ_ASSERT(aGlobalObject); MOZ_ASSERT(aGlobalObject);
if (aIsMainThread && gRunToCompletionListeners > 0) { if (aIsMainThread) {
mDocShellEntryMonitor.emplace(cx(), aReason); if (gRunToCompletionListeners > 0) {
mDocShellEntryMonitor.emplace(cx(), aReason);
}
mScriptActivity.emplace(true);
} }
} }
@ -815,11 +818,12 @@ AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMP
} }
AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL) AutoSlowOperation::AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
: AutoJSAPI() : mIsMainThread(NS_IsMainThread())
{ {
MOZ_GUARD_OBJECT_NOTIFIER_INIT; MOZ_GUARD_OBJECT_NOTIFIER_INIT;
if (mIsMainThread) {
Init(); mScriptActivity.emplace(true);
}
} }
void void
@ -828,8 +832,10 @@ AutoSlowOperation::CheckForInterrupt()
// For now we support only main thread! // For now we support only main thread!
if (mIsMainThread) { if (mIsMainThread) {
// JS_CheckForInterrupt expects us to be in a realm. // JS_CheckForInterrupt expects us to be in a realm.
JSAutoRealm ar(cx(), xpc::UnprivilegedJunkScope()); AutoJSAPI jsapi;
JS_CheckForInterrupt(cx()); if (jsapi.Init(xpc::UnprivilegedJunkScope())) {
JS_CheckForInterrupt(jsapi.cx());
}
} }
} }

Просмотреть файл

@ -12,6 +12,7 @@
#include "MainThreadUtils.h" #include "MainThreadUtils.h"
#include "nsIGlobalObject.h" #include "nsIGlobalObject.h"
#include "nsIPrincipal.h" #include "nsIPrincipal.h"
#include "xpcpublic.h"
#include "mozilla/Maybe.h" #include "mozilla/Maybe.h"
@ -383,6 +384,7 @@ private:
friend nsIPrincipal* GetWebIDLCallerPrincipal(); friend nsIPrincipal* GetWebIDLCallerPrincipal();
Maybe<DocshellEntryMonitor> mDocShellEntryMonitor; Maybe<DocshellEntryMonitor> mDocShellEntryMonitor;
Maybe<xpc::AutoScriptActivity> mScriptActivity;
JS::AutoHideScriptedCaller mCallerOverride; JS::AutoHideScriptedCaller mCallerOverride;
#ifdef MOZ_GECKO_PROFILER #ifdef MOZ_GECKO_PROFILER
AutoProfilerLabel mAutoProfilerLabel; AutoProfilerLabel mAutoProfilerLabel;
@ -455,17 +457,21 @@ private:
* Use AutoSlowOperation when native side calls many JS callbacks in a row * Use AutoSlowOperation when native side calls many JS callbacks in a row
* and slow script dialog should be activated if too much time is spent going * and slow script dialog should be activated if too much time is spent going
* through those callbacks. * through those callbacks.
* AutoSlowOperation puts a JSAutoRequest on the stack so that we don't continue * AutoSlowOperation puts an AutoScriptActivity on the stack so that we don't
* to reset the watchdog and CheckForInterrupt can be then used to check whether * continue to reset the watchdog. CheckForInterrupt can then be used to check
* JS execution should be interrupted. * whether JS execution should be interrupted.
* This class (including CheckForInterrupt) is a no-op when used off the main
* thread.
*/ */
class MOZ_RAII AutoSlowOperation : public dom::AutoJSAPI class MOZ_RAII AutoSlowOperation
{ {
public: public:
explicit AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM); explicit AutoSlowOperation(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
void CheckForInterrupt(); void CheckForInterrupt();
private: private:
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
bool mIsMainThread;
Maybe<xpc::AutoScriptActivity> mScriptActivity;
}; };
/** /**

Просмотреть файл

@ -551,28 +551,49 @@ XPCJSContext::GetWatchdogTimestamp(WatchdogTimestampCategory aCategory)
mWatchdogManager->GetTimestamp(aCategory, lock); mWatchdogManager->GetTimestamp(aCategory, lock);
} }
void
xpc::SimulateActivityCallback(bool aActive)
{
XPCJSContext::ActivityCallback(XPCJSContext::Get(), aActive);
}
// static // static
void bool
XPCJSContext::ActivityCallback(void* arg, bool active) XPCJSContext::RecordScriptActivity(bool aActive)
{ {
MOZ_ASSERT(NS_IsMainThread());
XPCJSContext* xpccx = XPCJSContext::Get();
if (!xpccx) {
// mozilla::SpinEventLoopUntil may use AutoScriptActivity(false) after
// we destroyed the XPCJSContext.
MOZ_ASSERT(!aActive);
return false;
}
bool oldValue = xpccx->SetHasScriptActivity(aActive);
if (aActive == oldValue) {
// Nothing to do.
return oldValue;
}
// Since the slow script dialog never activates if we are recording or // Since the slow script dialog never activates if we are recording or
// replaying, don't record/replay JS activity notifications. // replaying, don't record/replay JS activity notifications.
if (recordreplay::IsRecordingOrReplaying()) { if (recordreplay::IsRecordingOrReplaying()) {
return; return oldValue;
} }
if (!active) { if (!aActive) {
ProcessHangMonitor::ClearHang(); ProcessHangMonitor::ClearHang();
} }
xpccx->mWatchdogManager->RecordContextActivity(xpccx, aActive);
XPCJSContext* self = static_cast<XPCJSContext*>(arg); return oldValue;
self->mWatchdogManager->RecordContextActivity(self, active); }
AutoScriptActivity::AutoScriptActivity(bool aActive)
: mActive(aActive)
, mOldValue(XPCJSContext::RecordScriptActivity(aActive))
{
}
AutoScriptActivity::~AutoScriptActivity()
{
MOZ_ALWAYS_TRUE(mActive == XPCJSContext::RecordScriptActivity(mOldValue));
} }
// static // static
@ -890,8 +911,6 @@ XPCJSContext::~XPCJSContext()
Preferences::UnregisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this); Preferences::UnregisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this);
#endif #endif
js::SetActivityCallback(Context(), nullptr, nullptr);
// Clear any pending exception. It might be an XPCWrappedJS, and if we try // Clear any pending exception. It might be an XPCWrappedJS, and if we try
// to destroy it later we will crash. // to destroy it later we will crash.
SetPendingException(nullptr); SetPendingException(nullptr);
@ -926,6 +945,7 @@ XPCJSContext::XPCJSContext()
mWatchdogManager(GetWatchdogManager()), mWatchdogManager(GetWatchdogManager()),
mSlowScriptSecondHalf(false), mSlowScriptSecondHalf(false),
mTimeoutAccumulated(false), mTimeoutAccumulated(false),
mHasScriptActivity(false),
mPendingResult(NS_OK), mPendingResult(NS_OK),
mActive(CONTEXT_INACTIVE), mActive(CONTEXT_INACTIVE),
mLastStateChange(PR_Now()) mLastStateChange(PR_Now())
@ -1140,7 +1160,6 @@ XPCJSContext::Initialize(XPCJSContext* aPrimaryContext)
PROFILER_SET_JS_CONTEXT(cx); PROFILER_SET_JS_CONTEXT(cx);
js::SetActivityCallback(cx, ActivityCallback, this);
JS_AddInterruptCallback(cx, InterruptCallback); JS_AddInterruptCallback(cx, InterruptCallback);
if (!aPrimaryContext) { if (!aPrimaryContext) {

Просмотреть файл

@ -590,15 +590,21 @@ SetInterruptCallback(JSContext* cx, unsigned argc, Value* vp)
} }
static bool static bool
SimulateActivityCallback(JSContext* cx, unsigned argc, Value* vp) SimulateNoScriptActivity(JSContext* cx, unsigned argc, Value* vp)
{ {
// Sanity-check args. // Sanity-check args.
JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
if (args.length() != 1 || !args[0].isBoolean()) { if (args.length() != 1 || !args[0].isInt32() || args[0].toInt32() < 0) {
JS_ReportErrorASCII(cx, "Wrong number of arguments"); JS_ReportErrorASCII(cx, "Expected a positive integer argument");
return false; return false;
} }
xpc::SimulateActivityCallback(args[0].toBoolean());
// This mimics mozilla::SpinEventLoopUntil but instead of spinning the
// event loop we sleep, to make sure we don't run script.
xpc::AutoScriptActivity asa(false);
PR_Sleep(PR_SecondsToInterval(args[0].toInt32()));
args.rval().setUndefined();
return true; return true;
} }
@ -665,7 +671,7 @@ static const JSFunctionSpec glob_functions[] = {
JS_FN("atob", xpc::Atob, 1,0), JS_FN("atob", xpc::Atob, 1,0),
JS_FN("btoa", xpc::Btoa, 1,0), JS_FN("btoa", xpc::Btoa, 1,0),
JS_FN("setInterruptCallback", SetInterruptCallback, 1,0), JS_FN("setInterruptCallback", SetInterruptCallback, 1,0),
JS_FN("simulateActivityCallback", SimulateActivityCallback, 1,0), JS_FN("simulateNoScriptActivity", SimulateNoScriptActivity, 1,0),
JS_FN("registerAppManifest", RegisterAppManifest, 1, 0), JS_FN("registerAppManifest", RegisterAppManifest, 1, 0),
#ifdef ENABLE_TESTS #ifdef ENABLE_TESTS
JS_FN("registerXPCTestComponents", RegisterXPCTestComponents, 0, 0), JS_FN("registerXPCTestComponents", RegisterXPCTestComponents, 0, 0),

Просмотреть файл

@ -373,7 +373,14 @@ public:
PRTime GetWatchdogTimestamp(WatchdogTimestampCategory aCategory); PRTime GetWatchdogTimestamp(WatchdogTimestampCategory aCategory);
static void ActivityCallback(void* arg, bool active); static bool RecordScriptActivity(bool aActive);
bool SetHasScriptActivity(bool aActive) {
bool oldValue = mHasScriptActivity;
mHasScriptActivity = aActive;
return oldValue;
}
static bool InterruptCallback(JSContext* cx); static bool InterruptCallback(JSContext* cx);
// Mapping of often used strings to jsid atoms that live 'forever'. // Mapping of often used strings to jsid atoms that live 'forever'.
@ -461,6 +468,8 @@ private:
mozilla::TimeDuration mSlowScriptActualWait; mozilla::TimeDuration mSlowScriptActualWait;
bool mTimeoutAccumulated; bool mTimeoutAccumulated;
bool mHasScriptActivity;
// mPendingResult is used to implement Components.returnCode. Only really // mPendingResult is used to implement Components.returnCode. Only really
// meaningful while calling through XPCWrappedJS. // meaningful while calling through XPCWrappedJS.
nsresult mPendingResult; nsresult mPendingResult;

Просмотреть файл

@ -550,8 +550,14 @@ WindowGlobalOrNull(JSObject* aObj);
nsGlobalWindowInner* nsGlobalWindowInner*
CurrentWindowOrNull(JSContext* cx); CurrentWindowOrNull(JSContext* cx);
void class MOZ_RAII AutoScriptActivity
SimulateActivityCallback(bool aActive); {
bool mActive;
bool mOldValue;
public:
explicit AutoScriptActivity(bool aActive);
~AutoScriptActivity();
};
// This function may be used off-main-thread, in which case it is benignly // This function may be used off-main-thread, in which case it is benignly
// racey. // racey.

Просмотреть файл

@ -23,13 +23,11 @@ async function testBody() {
// scheduling, we should never have more than 3 seconds of inactivity without // scheduling, we should never have more than 3 seconds of inactivity without
// hibernating. To add some padding for automation, we mandate that hibernation // hibernating. To add some padding for automation, we mandate that hibernation
// must begin between 2 and 5 seconds from now. // must begin between 2 and 5 seconds from now.
await new Promise(resolve => {
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
timer.initWithCallback(resolve, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
simulateActivityCallback(false);
});
simulateActivityCallback(true); // Sleep for 10 seconds. Note: we don't use nsITimer here because then we may run
// arbitrary (idle) events involving script before it fires.
simulateNoScriptActivity(10);
busyWait(1000); // Give the watchdog time to wake up on the condvar. busyWait(1000); // Give the watchdog time to wake up on the condvar.
var stateChange = Cu.getWatchdogTimestamp("ContextStateChange"); var stateChange = Cu.getWatchdogTimestamp("ContextStateChange");
startHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStart"); startHibernation = Cu.getWatchdogTimestamp("WatchdogHibernateStart");

Просмотреть файл

@ -21,8 +21,10 @@
#include "nsString.h" #include "nsString.h"
#include "nsCOMPtr.h" #include "nsCOMPtr.h"
#include "nsAutoPtr.h" #include "nsAutoPtr.h"
#include "xpcpublic.h"
#include "mozilla/Atomics.h" #include "mozilla/Atomics.h"
#include "mozilla/Likely.h" #include "mozilla/Likely.h"
#include "mozilla/Maybe.h"
#include "mozilla/Move.h" #include "mozilla/Move.h"
#include "mozilla/TimeStamp.h" #include "mozilla/TimeStamp.h"
#include "mozilla/Tuple.h" #include "mozilla/Tuple.h"
@ -320,6 +322,14 @@ SpinEventLoopUntil(Pred&& aPredicate, nsIThread* aThread = nullptr)
{ {
nsIThread* thread = aThread ? aThread : NS_GetCurrentThread(); nsIThread* thread = aThread ? aThread : NS_GetCurrentThread();
// From a latency perspective, spinning the event loop is like leaving script
// and returning to the event loop. Tell the watchdog we stopped running
// script (until we return).
mozilla::Maybe<xpc::AutoScriptActivity> asa;
if (NS_IsMainThread()) {
asa.emplace(false);
}
while (!aPredicate()) { while (!aPredicate()) {
bool didSomething = NS_ProcessNextEvent(thread, true); bool didSomething = NS_ProcessNextEvent(thread, true);