michael@0: /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "WorkerRunnable.h" michael@0: michael@0: #include "nsIEventTarget.h" michael@0: #include "nsIRunnable.h" michael@0: #include "nsThreadUtils.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "js/RootingAPI.h" michael@0: #include "js/Value.h" michael@0: michael@0: #include "WorkerPrivate.h" michael@0: michael@0: USING_WORKERS_NAMESPACE michael@0: michael@0: namespace { michael@0: michael@0: const nsIID kWorkerRunnableIID = { michael@0: 0x320cc0b5, 0xef12, 0x4084, { 0x88, 0x6e, 0xca, 0x6a, 0x81, 0xe4, 0x1d, 0x68 } michael@0: }; michael@0: michael@0: void michael@0: MaybeReportMainThreadException(JSContext* aCx, bool aResult) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (aCx && !aResult) { michael@0: JS_ReportPendingException(aCx); michael@0: } michael@0: } michael@0: michael@0: } // anonymous namespace michael@0: michael@0: #ifdef DEBUG michael@0: WorkerRunnable::WorkerRunnable(WorkerPrivate* aWorkerPrivate, michael@0: TargetAndBusyBehavior aBehavior) michael@0: : mWorkerPrivate(aWorkerPrivate), mBehavior(aBehavior), mCanceled(0), michael@0: mCallingCancelWithinRun(false) michael@0: { michael@0: MOZ_ASSERT(aWorkerPrivate); michael@0: } michael@0: #endif michael@0: michael@0: bool michael@0: WorkerRunnable::PreDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate) michael@0: { michael@0: #ifdef DEBUG michael@0: MOZ_ASSERT(aWorkerPrivate); michael@0: michael@0: switch (mBehavior) { michael@0: case ParentThreadUnchangedBusyCount: michael@0: aWorkerPrivate->AssertIsOnWorkerThread(); michael@0: break; michael@0: michael@0: case WorkerThreadModifyBusyCount: michael@0: aWorkerPrivate->AssertIsOnParentThread(); michael@0: MOZ_ASSERT(aCx); michael@0: break; michael@0: michael@0: case WorkerThreadUnchangedBusyCount: michael@0: aWorkerPrivate->AssertIsOnParentThread(); michael@0: break; michael@0: michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("Unknown behavior!"); michael@0: } michael@0: #endif michael@0: michael@0: if (mBehavior == WorkerThreadModifyBusyCount) { michael@0: return aWorkerPrivate->ModifyBusyCount(aCx, true); michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: WorkerRunnable::Dispatch(JSContext* aCx) michael@0: { michael@0: bool ok; michael@0: michael@0: if (!aCx) { michael@0: ok = PreDispatch(nullptr, mWorkerPrivate); michael@0: if (ok) { michael@0: ok = DispatchInternal(); michael@0: } michael@0: PostDispatch(nullptr, mWorkerPrivate, ok); michael@0: return ok; michael@0: } michael@0: michael@0: JSAutoRequest ar(aCx); michael@0: michael@0: JS::Rooted global(aCx, JS::CurrentGlobalOrNull(aCx)); michael@0: michael@0: Maybe ac; michael@0: if (global) { michael@0: ac.construct(aCx, global); michael@0: } michael@0: michael@0: ok = PreDispatch(aCx, mWorkerPrivate); michael@0: michael@0: if (ok && !DispatchInternal()) { michael@0: ok = false; michael@0: } michael@0: michael@0: PostDispatch(aCx, mWorkerPrivate, ok); michael@0: michael@0: return ok; michael@0: } michael@0: michael@0: bool michael@0: WorkerRunnable::DispatchInternal() michael@0: { michael@0: if (mBehavior == WorkerThreadModifyBusyCount || michael@0: mBehavior == WorkerThreadUnchangedBusyCount) { michael@0: return NS_SUCCEEDED(mWorkerPrivate->Dispatch(this)); michael@0: } michael@0: michael@0: MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount); michael@0: michael@0: if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) { michael@0: return NS_SUCCEEDED(parent->Dispatch(this)); michael@0: } michael@0: michael@0: nsCOMPtr mainThread = do_GetMainThread(); michael@0: MOZ_ASSERT(mainThread); michael@0: michael@0: return NS_SUCCEEDED(mainThread->Dispatch(this, NS_DISPATCH_NORMAL)); michael@0: } michael@0: michael@0: void michael@0: WorkerRunnable::PostDispatch(JSContext* aCx, WorkerPrivate* aWorkerPrivate, michael@0: bool aDispatchResult) michael@0: { michael@0: MOZ_ASSERT(aWorkerPrivate); michael@0: michael@0: #ifdef DEBUG michael@0: switch (mBehavior) { michael@0: case ParentThreadUnchangedBusyCount: michael@0: aWorkerPrivate->AssertIsOnWorkerThread(); michael@0: break; michael@0: michael@0: case WorkerThreadModifyBusyCount: michael@0: aWorkerPrivate->AssertIsOnParentThread(); michael@0: MOZ_ASSERT(aCx); michael@0: break; michael@0: michael@0: case WorkerThreadUnchangedBusyCount: michael@0: aWorkerPrivate->AssertIsOnParentThread(); michael@0: break; michael@0: michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("Unknown behavior!"); michael@0: } michael@0: #endif michael@0: michael@0: if (!aDispatchResult) { michael@0: if (mBehavior == WorkerThreadModifyBusyCount) { michael@0: aWorkerPrivate->ModifyBusyCount(aCx, false); michael@0: } michael@0: if (aCx) { michael@0: JS_ReportPendingException(aCx); michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: WorkerRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, michael@0: bool aRunResult) michael@0: { michael@0: MOZ_ASSERT(aCx); michael@0: MOZ_ASSERT(aWorkerPrivate); michael@0: michael@0: #ifdef DEBUG michael@0: switch (mBehavior) { michael@0: case ParentThreadUnchangedBusyCount: michael@0: aWorkerPrivate->AssertIsOnParentThread(); michael@0: break; michael@0: michael@0: case WorkerThreadModifyBusyCount: michael@0: aWorkerPrivate->AssertIsOnWorkerThread(); michael@0: break; michael@0: michael@0: case WorkerThreadUnchangedBusyCount: michael@0: aWorkerPrivate->AssertIsOnWorkerThread(); michael@0: break; michael@0: michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("Unknown behavior!"); michael@0: } michael@0: #endif michael@0: michael@0: if (mBehavior == WorkerThreadModifyBusyCount) { michael@0: if (!aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false)) { michael@0: aRunResult = false; michael@0: } michael@0: } michael@0: michael@0: if (!aRunResult) { michael@0: JS_ReportPendingException(aCx); michael@0: } michael@0: } michael@0: michael@0: // static michael@0: WorkerRunnable* michael@0: WorkerRunnable::FromRunnable(nsIRunnable* aRunnable) michael@0: { michael@0: MOZ_ASSERT(aRunnable); michael@0: michael@0: WorkerRunnable* runnable; michael@0: nsresult rv = aRunnable->QueryInterface(kWorkerRunnableIID, michael@0: reinterpret_cast(&runnable)); michael@0: if (NS_FAILED(rv)) { michael@0: return nullptr; michael@0: } michael@0: michael@0: MOZ_ASSERT(runnable); michael@0: return runnable; michael@0: } michael@0: michael@0: NS_IMPL_ADDREF(WorkerRunnable) michael@0: NS_IMPL_RELEASE(WorkerRunnable) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(WorkerRunnable) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRunnable) michael@0: NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: // kWorkerRunnableIID is special in that it does not AddRef its result. michael@0: if (aIID.Equals(kWorkerRunnableIID)) { michael@0: *aInstancePtr = this; michael@0: return NS_OK; michael@0: } michael@0: else michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMETHODIMP michael@0: WorkerRunnable::Run() michael@0: { michael@0: bool targetIsWorkerThread = mBehavior == WorkerThreadModifyBusyCount || michael@0: mBehavior == WorkerThreadUnchangedBusyCount; michael@0: michael@0: #ifdef DEBUG michael@0: MOZ_ASSERT_IF(mCallingCancelWithinRun, targetIsWorkerThread); michael@0: if (targetIsWorkerThread) { michael@0: mWorkerPrivate->AssertIsOnWorkerThread(); michael@0: } michael@0: else { michael@0: MOZ_ASSERT(mBehavior == ParentThreadUnchangedBusyCount); michael@0: mWorkerPrivate->AssertIsOnParentThread(); michael@0: } michael@0: #endif michael@0: michael@0: if (IsCanceled() && !mCallingCancelWithinRun) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: JSContext* cx; michael@0: nsRefPtr kungFuDeathGrip; michael@0: nsCxPusher pusher; michael@0: michael@0: if (targetIsWorkerThread) { michael@0: if (mWorkerPrivate->AllPendingRunnablesShouldBeCanceled() && michael@0: !IsCanceled() && michael@0: !mCallingCancelWithinRun) { michael@0: michael@0: // Prevent recursion. michael@0: mCallingCancelWithinRun = true; michael@0: michael@0: Cancel(); michael@0: michael@0: MOZ_ASSERT(mCallingCancelWithinRun); michael@0: mCallingCancelWithinRun = false; michael@0: michael@0: MOZ_ASSERT(IsCanceled(), "Subclass Cancel() didn't set IsCanceled()!"); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: cx = mWorkerPrivate->GetJSContext(); michael@0: MOZ_ASSERT(cx); michael@0: } michael@0: else { michael@0: cx = mWorkerPrivate->ParentJSContext(); michael@0: MOZ_ASSERT(cx); michael@0: michael@0: kungFuDeathGrip = mWorkerPrivate; michael@0: michael@0: if (!mWorkerPrivate->GetParent()) { michael@0: AssertIsOnMainThread(); michael@0: pusher.Push(cx); michael@0: } michael@0: } michael@0: michael@0: JSAutoRequest ar(cx); michael@0: michael@0: JS::Rooted targetCompartmentObject(cx); michael@0: if (targetIsWorkerThread) { michael@0: targetCompartmentObject = JS::CurrentGlobalOrNull(cx); michael@0: } else { michael@0: targetCompartmentObject = mWorkerPrivate->GetWrapper(); michael@0: } michael@0: michael@0: Maybe ac; michael@0: if (targetCompartmentObject) { michael@0: ac.construct(cx, targetCompartmentObject); michael@0: } michael@0: michael@0: bool result = WorkerRun(cx, mWorkerPrivate); michael@0: michael@0: // In the case of CompileScriptRunnnable, WorkerRun above can cause us to michael@0: // lazily create a global, in which case we need to be in its compartment michael@0: // when calling PostRun() below. Maybe<> this time... michael@0: if (targetIsWorkerThread && michael@0: ac.empty() && michael@0: js::DefaultObjectForContextOrNull(cx)) { michael@0: ac.construct(cx, js::DefaultObjectForContextOrNull(cx)); michael@0: } michael@0: michael@0: PostRun(cx, mWorkerPrivate, result); michael@0: michael@0: return result ? NS_OK : NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: WorkerRunnable::Cancel() michael@0: { michael@0: uint32_t canceledCount = ++mCanceled; michael@0: michael@0: MOZ_ASSERT(canceledCount, "Cancel() overflow!"); michael@0: michael@0: // The docs say that Cancel() should not be called more than once and that we michael@0: // should throw NS_ERROR_UNEXPECTED if it is. michael@0: return (canceledCount == 1) ? NS_OK : NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: WorkerSyncRunnable::WorkerSyncRunnable(WorkerPrivate* aWorkerPrivate, michael@0: nsIEventTarget* aSyncLoopTarget) michael@0: : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), michael@0: mSyncLoopTarget(aSyncLoopTarget) michael@0: { michael@0: #ifdef DEBUG michael@0: if (mSyncLoopTarget) { michael@0: mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: WorkerSyncRunnable::WorkerSyncRunnable( michael@0: WorkerPrivate* aWorkerPrivate, michael@0: already_AddRefed&& aSyncLoopTarget) michael@0: : WorkerRunnable(aWorkerPrivate, WorkerThreadUnchangedBusyCount), michael@0: mSyncLoopTarget(aSyncLoopTarget) michael@0: { michael@0: #ifdef DEBUG michael@0: if (mSyncLoopTarget) { michael@0: mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: WorkerSyncRunnable::~WorkerSyncRunnable() michael@0: { michael@0: } michael@0: michael@0: bool michael@0: WorkerSyncRunnable::DispatchInternal() michael@0: { michael@0: if (mSyncLoopTarget) { michael@0: return NS_SUCCEEDED(mSyncLoopTarget->Dispatch(this, NS_DISPATCH_NORMAL)); michael@0: } michael@0: michael@0: return WorkerRunnable::DispatchInternal(); michael@0: } michael@0: michael@0: void michael@0: MainThreadWorkerSyncRunnable::PostDispatch(JSContext* aCx, michael@0: WorkerPrivate* aWorkerPrivate, michael@0: bool aDispatchResult) michael@0: { michael@0: MaybeReportMainThreadException(aCx, aDispatchResult); michael@0: } michael@0: michael@0: StopSyncLoopRunnable::StopSyncLoopRunnable( michael@0: WorkerPrivate* aWorkerPrivate, michael@0: already_AddRefed&& aSyncLoopTarget, michael@0: bool aResult) michael@0: : WorkerSyncRunnable(aWorkerPrivate, Move(aSyncLoopTarget)), mResult(aResult) michael@0: { michael@0: #ifdef DEBUG michael@0: mWorkerPrivate->AssertValidSyncLoop(mSyncLoopTarget); michael@0: #endif michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: StopSyncLoopRunnable::Cancel() michael@0: { michael@0: nsresult rv = Run(); michael@0: NS_WARN_IF_FALSE(NS_SUCCEEDED(rv), "Run() failed"); michael@0: michael@0: nsresult rv2 = WorkerSyncRunnable::Cancel(); michael@0: NS_WARN_IF_FALSE(NS_SUCCEEDED(rv2), "Cancel() failed"); michael@0: michael@0: return NS_FAILED(rv) ? rv : rv2; michael@0: } michael@0: michael@0: bool michael@0: StopSyncLoopRunnable::WorkerRun(JSContext* aCx, michael@0: WorkerPrivate* aWorkerPrivate) michael@0: { michael@0: aWorkerPrivate->AssertIsOnWorkerThread(); michael@0: MOZ_ASSERT(mSyncLoopTarget); michael@0: michael@0: nsCOMPtr syncLoopTarget; michael@0: mSyncLoopTarget.swap(syncLoopTarget); michael@0: michael@0: if (!mResult) { michael@0: MaybeSetException(aCx); michael@0: } michael@0: michael@0: aWorkerPrivate->StopSyncLoop(syncLoopTarget, mResult); michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: StopSyncLoopRunnable::DispatchInternal() michael@0: { michael@0: MOZ_ASSERT(mSyncLoopTarget); michael@0: michael@0: return NS_SUCCEEDED(mSyncLoopTarget->Dispatch(this, NS_DISPATCH_NORMAL)); michael@0: } michael@0: michael@0: void michael@0: MainThreadStopSyncLoopRunnable::PostDispatch(JSContext* aCx, michael@0: WorkerPrivate* aWorkerPrivate, michael@0: bool aDispatchResult) michael@0: { michael@0: MaybeReportMainThreadException(aCx, aDispatchResult); michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: WorkerControlRunnable::WorkerControlRunnable(WorkerPrivate* aWorkerPrivate, michael@0: TargetAndBusyBehavior aBehavior) michael@0: : WorkerRunnable(aWorkerPrivate, aBehavior) michael@0: { michael@0: MOZ_ASSERT(aWorkerPrivate); michael@0: MOZ_ASSERT(aBehavior == ParentThreadUnchangedBusyCount || michael@0: aBehavior == WorkerThreadUnchangedBusyCount, michael@0: "WorkerControlRunnables should not modify the busy count"); michael@0: } michael@0: #endif michael@0: michael@0: bool michael@0: WorkerControlRunnable::DispatchInternal() michael@0: { michael@0: if (mBehavior == WorkerThreadUnchangedBusyCount) { michael@0: return NS_SUCCEEDED(mWorkerPrivate->DispatchControlRunnable(this)); michael@0: } michael@0: michael@0: if (WorkerPrivate* parent = mWorkerPrivate->GetParent()) { michael@0: return NS_SUCCEEDED(parent->DispatchControlRunnable(this)); michael@0: } michael@0: michael@0: nsCOMPtr mainThread = do_GetMainThread(); michael@0: MOZ_ASSERT(mainThread); michael@0: michael@0: return NS_SUCCEEDED(mainThread->Dispatch(this, NS_DISPATCH_NORMAL)); michael@0: } michael@0: michael@0: void michael@0: MainThreadWorkerControlRunnable::PostDispatch(JSContext* aCx, michael@0: WorkerPrivate* aWorkerPrivate, michael@0: bool aDispatchResult) michael@0: { michael@0: AssertIsOnMainThread(); michael@0: michael@0: if (aCx && !aDispatchResult) { michael@0: JS_ReportPendingException(aCx); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED0(WorkerControlRunnable, WorkerRunnable) michael@0: michael@0: bool michael@0: WorkerSameThreadRunnable::PreDispatch(JSContext* aCx, michael@0: WorkerPrivate* aWorkerPrivate) michael@0: { michael@0: aWorkerPrivate->AssertIsOnWorkerThread(); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: WorkerSameThreadRunnable::PostDispatch(JSContext* aCx, michael@0: WorkerPrivate* aWorkerPrivate, michael@0: bool aDispatchResult) michael@0: { michael@0: aWorkerPrivate->AssertIsOnWorkerThread(); michael@0: if (aDispatchResult) { michael@0: DebugOnly willIncrement = aWorkerPrivate->ModifyBusyCountFromWorker(aCx, true); michael@0: // Should never fail since if this thread is still running, so should the michael@0: // parent and it should be able to process a control runnable. michael@0: MOZ_ASSERT(willIncrement); michael@0: } michael@0: } michael@0: michael@0: void michael@0: WorkerSameThreadRunnable::PostRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate, michael@0: bool aRunResult) michael@0: { michael@0: MOZ_ASSERT(aCx); michael@0: MOZ_ASSERT(aWorkerPrivate); michael@0: michael@0: aWorkerPrivate->AssertIsOnWorkerThread(); michael@0: michael@0: DebugOnly willDecrement = aWorkerPrivate->ModifyBusyCountFromWorker(aCx, false); michael@0: MOZ_ASSERT(willDecrement); michael@0: michael@0: if (!aRunResult) { michael@0: JS_ReportPendingException(aCx); michael@0: } michael@0: } michael@0: