/* -*- Mode: C++; tab-width: 8; 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/. */ #include "ServiceWorkerJob.h" #include "nsProxyRelease.h" #include "nsThreadUtils.h" #include "Workers.h" #include "ServiceWorkerManager.h" namespace mozilla { namespace dom { namespace workers { ServiceWorkerJob::Type ServiceWorkerJob::GetType() const { return mType; } ServiceWorkerJob::State ServiceWorkerJob::GetState() const { return mState; } bool ServiceWorkerJob::Canceled() const { return mCanceled; } bool ServiceWorkerJob::ResultCallbacksInvoked() const { return mResultCallbacksInvoked; } bool ServiceWorkerJob::IsEquivalentTo(ServiceWorkerJob* aJob) const { AssertIsOnMainThread(); MOZ_ASSERT(aJob); return mType == aJob->mType && mScope.Equals(aJob->mScope) && mScriptSpec.Equals(aJob->mScriptSpec) && mPrincipal->Equals(aJob->mPrincipal); } void ServiceWorkerJob::AppendResultCallback(Callback* aCallback) { AssertIsOnMainThread(); MOZ_DIAGNOSTIC_ASSERT(mState != State::Finished); MOZ_DIAGNOSTIC_ASSERT(aCallback); MOZ_DIAGNOSTIC_ASSERT(mFinalCallback != aCallback); MOZ_ASSERT(!mResultCallbackList.Contains(aCallback)); MOZ_DIAGNOSTIC_ASSERT(!mResultCallbacksInvoked); mResultCallbackList.AppendElement(aCallback); } void ServiceWorkerJob::StealResultCallbacksFrom(ServiceWorkerJob* aJob) { AssertIsOnMainThread(); MOZ_ASSERT(aJob); MOZ_ASSERT(aJob->mState == State::Initial); // Take the callbacks from the other job immediately to avoid the // any possibility of them existing on both jobs at once. nsTArray> callbackList; callbackList.SwapElements(aJob->mResultCallbackList); for (RefPtr& callback : callbackList) { // Use AppendResultCallback() so that assertion checking is performed on // each callback. AppendResultCallback(callback); } } void ServiceWorkerJob::Start(Callback* aFinalCallback) { AssertIsOnMainThread(); MOZ_DIAGNOSTIC_ASSERT(!mCanceled); MOZ_DIAGNOSTIC_ASSERT(aFinalCallback); MOZ_DIAGNOSTIC_ASSERT(!mFinalCallback); MOZ_ASSERT(!mResultCallbackList.Contains(aFinalCallback)); mFinalCallback = aFinalCallback; MOZ_DIAGNOSTIC_ASSERT(mState == State::Initial); mState = State::Started; nsCOMPtr runnable = NewRunnableMethod(this, &ServiceWorkerJob::AsyncExecute); // We may have to wait for the PBackground actor to be initialized // before proceeding. We should always be able to get a ServiceWorkerManager, // however, since Start() should not be called during shutdown. RefPtr swm = ServiceWorkerManager::GetInstance(); if (!swm) { // browser shutdown return; } if (!swm->HasBackgroundActor()) { // waiting to initialize swm->AppendPendingOperation(runnable); return; } // Otherwise start asynchronously. We should never run a job synchronously. MOZ_ALWAYS_TRUE(NS_SUCCEEDED( NS_DispatchToMainThread(runnable.forget()))); } void ServiceWorkerJob::Cancel() { AssertIsOnMainThread(); MOZ_ASSERT(!mCanceled); mCanceled = true; } ServiceWorkerJob::ServiceWorkerJob(Type aType, nsIPrincipal* aPrincipal, const nsACString& aScope, const nsACString& aScriptSpec) : mType(aType) , mPrincipal(aPrincipal) , mScope(aScope) , mScriptSpec(aScriptSpec) , mState(State::Initial) , mCanceled(false) , mResultCallbacksInvoked(false) { AssertIsOnMainThread(); MOZ_ASSERT(mPrincipal); MOZ_ASSERT(!mScope.IsEmpty()); // Some job types may have an empty script spec } ServiceWorkerJob::~ServiceWorkerJob() { AssertIsOnMainThread(); // Jobs must finish or never be started. Destroying an actively running // job is an error. MOZ_ASSERT(mState != State::Started); MOZ_ASSERT_IF(mState == State::Finished, mResultCallbacksInvoked); } void ServiceWorkerJob::InvokeResultCallbacks(ErrorResult& aRv) { AssertIsOnMainThread(); MOZ_DIAGNOSTIC_ASSERT(mState == State::Started); MOZ_DIAGNOSTIC_ASSERT(!mResultCallbacksInvoked); mResultCallbacksInvoked = true; nsTArray> callbackList; callbackList.SwapElements(mResultCallbackList); for (RefPtr& callback : callbackList) { // The callback might consume an exception on the ErrorResult, so we need // to clone in order to maintain the error for the next callback. ErrorResult rv; aRv.CloneTo(rv); callback->JobFinished(this, rv); // The callback might not consume the error. rv.SuppressException(); } } void ServiceWorkerJob::InvokeResultCallbacks(nsresult aRv) { ErrorResult converted(aRv); InvokeResultCallbacks(converted); } void ServiceWorkerJob::Finish(ErrorResult& aRv) { AssertIsOnMainThread(); // Avoid double-completion because it can result on operating on cleaned // up data. This should not happen, though, so also assert to try to // narrow down the causes. MOZ_DIAGNOSTIC_ASSERT(mState == State::Started); if (mState != State::Started) { return; } // Ensure that we only surface SecurityErr, TypeErr or InvalidStateErr to script. if (aRv.Failed() && !aRv.ErrorCodeIs(NS_ERROR_DOM_SECURITY_ERR) && !aRv.ErrorCodeIs(NS_ERROR_DOM_TYPE_ERR) && !aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR)) { // Remove the old error code so we can replace it with a TypeError. aRv.SuppressException(); NS_ConvertUTF8toUTF16 scriptSpec(mScriptSpec); NS_ConvertUTF8toUTF16 scope(mScope); // Throw the type error with a generic error message. aRv.ThrowTypeError(scriptSpec, scope); } // The final callback may drop the last ref to this object. RefPtr kungFuDeathGrip = this; if (!mResultCallbacksInvoked) { InvokeResultCallbacks(aRv); } mState = State::Finished; MOZ_DIAGNOSTIC_ASSERT(mFinalCallback); if (mFinalCallback) { mFinalCallback->JobFinished(this, aRv); mFinalCallback = nullptr; } // The callback might not consume the error. aRv.SuppressException(); // Async release this object to ensure that our caller methods complete // as well. NS_ReleaseOnMainThread(kungFuDeathGrip.forget(), true /* always proxy */); } void ServiceWorkerJob::Finish(nsresult aRv) { ErrorResult converted(aRv); Finish(converted); } } // namespace workers } // namespace dom } // namespace mozilla