ServiceWorkerJob.cpp (7212B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "ServiceWorkerJob.h" 8 9 #include "ServiceWorkerManager.h" 10 #include "mozilla/dom/WorkerCommon.h" 11 #include "nsIPrincipal.h" 12 #include "nsProxyRelease.h" 13 #include "nsThreadUtils.h" 14 15 namespace mozilla::dom { 16 17 ServiceWorkerJob::Type ServiceWorkerJob::GetType() const { return mType; } 18 19 ServiceWorkerJob::State ServiceWorkerJob::GetState() const { return mState; } 20 21 bool ServiceWorkerJob::Canceled() const { return mCanceled; } 22 23 bool ServiceWorkerJob::ResultCallbacksInvoked() const { 24 return mResultCallbacksInvoked; 25 } 26 27 bool ServiceWorkerJob::IsEquivalentTo(ServiceWorkerJob* aJob) const { 28 MOZ_ASSERT(NS_IsMainThread()); 29 MOZ_ASSERT(aJob); 30 return mType == aJob->mType && mScope.Equals(aJob->mScope) && 31 mScriptSpec.Equals(aJob->mScriptSpec) && 32 mPrincipal->Equals(aJob->mPrincipal); 33 } 34 35 void ServiceWorkerJob::AppendResultCallback(Callback* aCallback) { 36 MOZ_ASSERT(NS_IsMainThread()); 37 MOZ_DIAGNOSTIC_ASSERT(mState != State::Finished); 38 MOZ_DIAGNOSTIC_ASSERT(aCallback); 39 MOZ_DIAGNOSTIC_ASSERT(mFinalCallback != aCallback); 40 MOZ_ASSERT(!mResultCallbackList.Contains(aCallback)); 41 MOZ_DIAGNOSTIC_ASSERT(!mResultCallbacksInvoked); 42 mResultCallbackList.AppendElement(aCallback); 43 } 44 45 void ServiceWorkerJob::StealResultCallbacksFrom(ServiceWorkerJob* aJob) { 46 MOZ_ASSERT(NS_IsMainThread()); 47 MOZ_ASSERT(aJob); 48 MOZ_ASSERT(aJob->mState == State::Initial); 49 50 // Take the callbacks from the other job immediately to avoid the 51 // any possibility of them existing on both jobs at once. 52 nsTArray<RefPtr<Callback>> callbackList = 53 std::move(aJob->mResultCallbackList); 54 55 for (RefPtr<Callback>& callback : callbackList) { 56 // Use AppendResultCallback() so that assertion checking is performed on 57 // each callback. 58 AppendResultCallback(callback); 59 } 60 } 61 62 void ServiceWorkerJob::Start(Callback* aFinalCallback) { 63 MOZ_ASSERT(NS_IsMainThread()); 64 MOZ_DIAGNOSTIC_ASSERT(!mCanceled); 65 66 MOZ_DIAGNOSTIC_ASSERT(aFinalCallback); 67 MOZ_DIAGNOSTIC_ASSERT(!mFinalCallback); 68 MOZ_ASSERT(!mResultCallbackList.Contains(aFinalCallback)); 69 mFinalCallback = aFinalCallback; 70 71 MOZ_DIAGNOSTIC_ASSERT(mState == State::Initial); 72 mState = State::Started; 73 74 nsCOMPtr<nsIRunnable> runnable = NewRunnableMethod( 75 "ServiceWorkerJob::AsyncExecute", this, &ServiceWorkerJob::AsyncExecute); 76 77 // We may have to wait for the PBackground actor to be initialized 78 // before proceeding. We should always be able to get a ServiceWorkerManager, 79 // however, since Start() should not be called during shutdown. 80 RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); 81 if (!swm) { 82 // browser shutdown 83 return; 84 } 85 86 // Otherwise start asynchronously. We should never run a job synchronously. 87 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable.forget()))); 88 } 89 90 void ServiceWorkerJob::Cancel() { 91 MOZ_ASSERT(NS_IsMainThread()); 92 MOZ_ASSERT(!mCanceled); 93 mCanceled = true; 94 95 if (GetState() != State::Started) { 96 MOZ_ASSERT(GetState() == State::Initial); 97 98 ErrorResult error(NS_ERROR_DOM_ABORT_ERR); 99 InvokeResultCallbacks(error); 100 101 // The callbacks might not consume the error, which is fine. 102 error.SuppressException(); 103 } 104 } 105 106 ServiceWorkerJob::ServiceWorkerJob(Type aType, nsIPrincipal* aPrincipal, 107 const nsACString& aScope, 108 nsCString aScriptSpec) 109 : mType(aType), 110 mPrincipal(aPrincipal), 111 mScope(aScope), 112 mScriptSpec(std::move(aScriptSpec)), 113 mState(State::Initial), 114 mCanceled(false), 115 mResultCallbacksInvoked(false) { 116 MOZ_ASSERT(NS_IsMainThread()); 117 MOZ_ASSERT(mPrincipal); 118 MOZ_ASSERT(!mScope.IsEmpty()); 119 120 // Empty script URL if and only if this is an unregister job. 121 MOZ_ASSERT((mType == Type::Unregister) == mScriptSpec.IsEmpty()); 122 } 123 124 ServiceWorkerJob::~ServiceWorkerJob() { 125 MOZ_ASSERT(NS_IsMainThread()); 126 // Jobs must finish or never be started. Destroying an actively running 127 // job is an error. 128 MOZ_ASSERT(mState != State::Started); 129 MOZ_ASSERT_IF(mState == State::Finished, mResultCallbacksInvoked); 130 } 131 132 void ServiceWorkerJob::InvokeResultCallbacks(ErrorResult& aRv) { 133 MOZ_ASSERT(NS_IsMainThread()); 134 MOZ_DIAGNOSTIC_ASSERT(mState != State::Finished); 135 MOZ_DIAGNOSTIC_ASSERT_IF(mState == State::Initial, Canceled()); 136 137 MOZ_DIAGNOSTIC_ASSERT(!mResultCallbacksInvoked); 138 mResultCallbacksInvoked = true; 139 140 nsTArray<RefPtr<Callback>> callbackList = std::move(mResultCallbackList); 141 142 for (RefPtr<Callback>& callback : callbackList) { 143 // The callback might consume an exception on the ErrorResult, so we need 144 // to clone in order to maintain the error for the next callback. 145 ErrorResult rv; 146 aRv.CloneTo(rv); 147 148 if (GetState() == State::Started) { 149 callback->JobFinished(this, rv); 150 } else { 151 callback->JobDiscarded(rv); 152 } 153 154 // The callback might not consume the error. 155 rv.SuppressException(); 156 } 157 } 158 159 void ServiceWorkerJob::InvokeResultCallbacks(nsresult aRv) { 160 ErrorResult converted(aRv); 161 InvokeResultCallbacks(converted); 162 } 163 164 void ServiceWorkerJob::Finish(ErrorResult& aRv) { 165 MOZ_ASSERT(NS_IsMainThread()); 166 167 // Finishing a job is idempotent and potentially expected by the error 168 // handling path for ServiceWorkerUpdateJob, so this is not an error. 169 if (mState != State::Started) { 170 return; 171 } 172 173 // Ensure that we only surface SecurityErr, TypeErr or InvalidStateErr to 174 // script. 175 if (aRv.Failed() && !aRv.ErrorCodeIs(NS_ERROR_DOM_SECURITY_ERR) && 176 !aRv.ErrorCodeIs(NS_ERROR_INTERNAL_ERRORRESULT_TYPEERROR) && 177 !aRv.ErrorCodeIs(NS_ERROR_DOM_INVALID_STATE_ERR)) { 178 // Remove the old error code so we can replace it with a TypeError. 179 aRv.SuppressException(); 180 181 // Throw the type error with a generic error message. We use a stack 182 // reference to bypass the normal static analysis for "return right after 183 // throwing", since it's not the right check here: this ErrorResult came in 184 // pre-thrown. 185 ErrorResult& rv = aRv; 186 rv.ThrowTypeError<MSG_SW_INSTALL_ERROR>(mScriptSpec, mScope); 187 } 188 189 // The final callback may drop the last ref to this object. 190 RefPtr<ServiceWorkerJob> kungFuDeathGrip = this; 191 192 if (!mResultCallbacksInvoked) { 193 InvokeResultCallbacks(aRv); 194 } 195 196 mState = State::Finished; 197 198 MOZ_DIAGNOSTIC_ASSERT(mFinalCallback); 199 if (mFinalCallback) { 200 mFinalCallback->JobFinished(this, aRv); 201 mFinalCallback = nullptr; 202 } 203 204 // The callback might not consume the error. 205 aRv.SuppressException(); 206 207 // Async release this object to ensure that our caller methods complete 208 // as well. 209 NS_ReleaseOnMainThread("ServiceWorkerJobProxyRunnable", 210 kungFuDeathGrip.forget(), true /* always proxy */); 211 } 212 213 void ServiceWorkerJob::Finish(nsresult aRv) { 214 ErrorResult converted(aRv); 215 Finish(converted); 216 } 217 218 } // namespace mozilla::dom