Bug 1527203 Part 1 - Add interface to delay execution of debuggee content in workers during debugger registration, r=asuth.

--HG--
extra : rebase_source : 7baebe68b424d99d468c06b230228e019780a123
This commit is contained in:
Brian Hackett 2019-02-12 13:04:18 -10:00
Родитель 899ee8e0cc
Коммит 2561d82269
4 изменённых файлов: 131 добавлений и 42 удалений

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

@ -88,6 +88,17 @@ class CompileDebuggerScriptRunnable final : public WorkerDebuggerRunnable {
return false; return false;
} }
if (NS_WARN_IF(!aWorkerPrivate->EnsureCSPEventListener())) {
return false;
}
// Initialize performance state which might be used on the main thread, as
// in CompileScriptRunnable. This runnable might execute first.
aWorkerPrivate->EnsurePerformanceStorage();
if (mozilla::StaticPrefs::dom_performance_enable_scheduler_timing()) {
aWorkerPrivate->EnsurePerformanceCounter();
}
JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper()); JS::Rooted<JSObject*> global(aCx, globalScope->GetWrapper());
ErrorResult rv; ErrorResult rv;
@ -368,6 +379,12 @@ WorkerDebugger::RemoveListener(nsIWorkerDebuggerListener* aListener) {
return NS_OK; return NS_OK;
} }
NS_IMETHODIMP
WorkerDebugger::SetDebuggerReady(bool aReady)
{
return mWorkerPrivate->SetIsDebuggerReady(aReady);
}
void WorkerDebugger::Close() { void WorkerDebugger::Close() {
MOZ_ASSERT(mWorkerPrivate); MOZ_ASSERT(mWorkerPrivate);
mWorkerPrivate = nullptr; mWorkerPrivate = nullptr;

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

@ -299,13 +299,14 @@ class ModifyBusyCountRunnable final : public WorkerControlRunnable {
} }
}; };
class CompileScriptRunnable final : public WorkerRunnable { class CompileScriptRunnable final : public WorkerDebuggeeRunnable {
nsString mScriptURL; nsString mScriptURL;
public: public:
explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate, explicit CompileScriptRunnable(WorkerPrivate* aWorkerPrivate,
const nsAString& aScriptURL) const nsAString& aScriptURL)
: WorkerRunnable(aWorkerPrivate), mScriptURL(aScriptURL) {} : WorkerDebuggeeRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
mScriptURL(aScriptURL) {}
private: private:
// We can't implement PreRun effectively, because at the point when that would // We can't implement PreRun effectively, because at the point when that would
@ -1332,51 +1333,60 @@ void WorkerPrivate::Traverse(nsCycleCollectionTraversalCallback& aCb) {
nsresult WorkerPrivate::Dispatch(already_AddRefed<WorkerRunnable> aRunnable, nsresult WorkerPrivate::Dispatch(already_AddRefed<WorkerRunnable> aRunnable,
nsIEventTarget* aSyncLoopTarget) { nsIEventTarget* aSyncLoopTarget) {
// May be called on any thread! // May be called on any thread!
MutexAutoLock lock(mMutex);
return DispatchLockHeld(std::move(aRunnable), aSyncLoopTarget, lock);
}
nsresult WorkerPrivate::DispatchLockHeld(already_AddRefed<WorkerRunnable> aRunnable,
nsIEventTarget* aSyncLoopTarget,
const MutexAutoLock& aProofOfLock) {
// May be called on any thread!
RefPtr<WorkerRunnable> runnable(aRunnable); RefPtr<WorkerRunnable> runnable(aRunnable);
{ MOZ_ASSERT_IF(aSyncLoopTarget, mThread);
MutexAutoLock lock(mMutex);
MOZ_ASSERT_IF(aSyncLoopTarget, mThread); if (mStatus == Dead || (!aSyncLoopTarget && ParentStatus() > Running)) {
NS_WARNING(
if (!mThread) { "A runnable was posted to a worker that is already shutting "
if (ParentStatus() == Pending || mStatus == Pending) { "down!");
mPreStartRunnables.AppendElement(runnable); return NS_ERROR_UNEXPECTED;
return NS_OK;
}
NS_WARNING(
"Using a worker event target after the thread has already"
"been released!");
return NS_ERROR_UNEXPECTED;
}
if (mStatus == Dead || (!aSyncLoopTarget && ParentStatus() > Running)) {
NS_WARNING(
"A runnable was posted to a worker that is already shutting "
"down!");
return NS_ERROR_UNEXPECTED;
}
nsresult rv;
if (aSyncLoopTarget) {
rv = aSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
} else {
// WorkerDebuggeeRunnables don't need any special treatment here. True,
// they should not be delivered to a frozen worker. But frozen workers
// aren't drawing from the thread's main event queue anyway, only from
// mControlQueue.
rv = mThread->DispatchAnyThread(WorkerThreadFriendKey(),
runnable.forget());
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mCondVar.Notify();
} }
if (runnable->IsDebuggeeRunnable() && !mDebuggerReady) {
MOZ_RELEASE_ASSERT(!aSyncLoopTarget);
mDelayedDebuggeeRunnables.AppendElement(runnable);
return NS_OK;
}
if (!mThread) {
if (ParentStatus() == Pending || mStatus == Pending) {
mPreStartRunnables.AppendElement(runnable);
return NS_OK;
}
NS_WARNING(
"Using a worker event target after the thread has already"
"been released!");
return NS_ERROR_UNEXPECTED;
}
nsresult rv;
if (aSyncLoopTarget) {
rv = aSyncLoopTarget->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
} else {
// WorkerDebuggeeRunnables don't need any special treatment here. True,
// they should not be delivered to a frozen worker. But frozen workers
// aren't drawing from the thread's main event queue anyway, only from
// mControlQueue.
rv = mThread->DispatchAnyThread(WorkerThreadFriendKey(),
runnable.forget());
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
mCondVar.Notify();
return NS_OK; return NS_OK;
} }
@ -2061,6 +2071,7 @@ WorkerPrivate::WorkerPrivate(WorkerPrivate* aParent,
mIsSecureContext( mIsSecureContext(
IsNewWorkerSecureContext(mParent, mWorkerType, mLoadInfo)), IsNewWorkerSecureContext(mParent, mWorkerType, mLoadInfo)),
mDebuggerRegistered(false), mDebuggerRegistered(false),
mDebuggerReady(true),
mIsInAutomation(false), mIsInAutomation(false),
mPerformanceCounter(nullptr) { mPerformanceCounter(nullptr) {
MOZ_ASSERT_IF(!IsDedicatedWorker(), NS_IsMainThread()); MOZ_ASSERT_IF(!IsDedicatedWorker(), NS_IsMainThread());
@ -2243,6 +2254,39 @@ already_AddRefed<WorkerPrivate> WorkerPrivate::Constructor(
return worker.forget(); return worker.forget();
} }
nsresult
WorkerPrivate::SetIsDebuggerReady(bool aReady)
{
AssertIsOnParentThread();
MutexAutoLock lock(mMutex);
if (mDebuggerReady == aReady) {
return NS_OK;
}
if (!aReady && mDebuggerRegistered) {
// The debugger can only be marked as not ready during registration.
return NS_ERROR_FAILURE;
}
mDebuggerReady = aReady;
if (aReady && mDebuggerRegistered) {
// Dispatch all the delayed runnables without releasing the lock, to ensure
// that the order in which debuggee runnables execute is the same as the
// order in which they were originally dispatched.
auto pending = std::move(mDelayedDebuggeeRunnables);
for (uint32_t i = 0; i < pending.Length(); i++) {
RefPtr<WorkerRunnable> runnable = pending[i].forget();
nsresult rv = DispatchLockHeld(runnable.forget(), nullptr, lock);
NS_ENSURE_SUCCESS(rv, rv);
}
MOZ_RELEASE_ASSERT(mDelayedDebuggeeRunnables.IsEmpty());
}
return NS_OK;
}
// static // static
nsresult WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindowInner* aWindow, nsresult WorkerPrivate::GetLoadInfo(JSContext* aCx, nsPIDOMWindowInner* aWindow,
WorkerPrivate* aParent, WorkerPrivate* aParent,

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

@ -170,6 +170,8 @@ class WorkerPrivate : public RelativeTimeline {
} }
} }
nsresult SetIsDebuggerReady(bool aReady);
WorkerDebugger* Debugger() const { WorkerDebugger* Debugger() const {
AssertIsOnMainThread(); AssertIsOnMainThread();
@ -907,6 +909,12 @@ class WorkerPrivate : public RelativeTimeline {
data->mHolders.IsEmpty()); data->mHolders.IsEmpty());
} }
// Internal logic to dispatch a runnable. This is separate from Dispatch()
// to allow runnables to be atomically dispatched in bulk.
nsresult DispatchLockHeld(already_AddRefed<WorkerRunnable> aRunnable,
nsIEventTarget* aSyncLoopTarget,
const MutexAutoLock& aProofOfLock);
class EventTarget; class EventTarget;
friend class EventTarget; friend class EventTarget;
friend class mozilla::dom::WorkerHolder; friend class mozilla::dom::WorkerHolder;
@ -1074,6 +1082,13 @@ class WorkerPrivate : public RelativeTimeline {
bool mDebuggerRegistered; bool mDebuggerRegistered;
// During registration, this worker may be marked as not being ready to
// execute debuggee runnables or content.
//
// Protected by mMutex.
bool mDebuggerReady;
nsTArray<RefPtr<WorkerRunnable>> mDelayedDebuggeeRunnables;
// mIsInAutomation is true when we're running in test automation. // mIsInAutomation is true when we're running in test automation.
// We expose some extra testing functions in that case. // We expose some extra testing functions in that case.
bool mIsInAutomation; bool mIsInAutomation;

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

@ -47,4 +47,17 @@ interface nsIWorkerDebugger : nsISupports
void addListener(in nsIWorkerDebuggerListener listener); void addListener(in nsIWorkerDebuggerListener listener);
void removeListener(in nsIWorkerDebuggerListener listener); void removeListener(in nsIWorkerDebuggerListener listener);
// Indicate whether the debugger has finished initializing. By default the
// debugger will be considered initialized when the onRegister hooks in all
// nsIWorkerDebuggerManagerListener have been called.
//
// setDebuggerReady(false) can be called during an onRegister hook to mark
// the debugger as not being ready yet. This will prevent all content from
// running in the worker, including the worker's main script and any messages
// posted to it. Other runnables will still execute in the worker as normal.
//
// When the debugger is ready, setDebuggerReady(true) should then be called
// to allow the worker to begin executing content.
void setDebuggerReady(in boolean ready);
}; };