SharedWorkerManager.cpp (10820B)
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 "SharedWorkerManager.h" 8 9 #include "SharedWorkerParent.h" 10 #include "SharedWorkerService.h" 11 #include "mozilla/AppShutdown.h" 12 #include "mozilla/dom/MessagePort.h" 13 #include "mozilla/dom/PSharedWorker.h" 14 #include "mozilla/dom/RemoteWorkerController.h" 15 #include "mozilla/ipc/BackgroundParent.h" 16 #include "mozilla/ipc/URIUtils.h" 17 #include "nsIConsoleReportCollector.h" 18 #include "nsIPrincipal.h" 19 #include "nsProxyRelease.h" 20 21 namespace mozilla::dom { 22 23 // static 24 already_AddRefed<SharedWorkerManagerHolder> SharedWorkerManager::Create( 25 SharedWorkerService* aService, nsIEventTarget* aPBackgroundEventTarget, 26 const RemoteWorkerData& aData, nsIPrincipal* aLoadingPrincipal, 27 const OriginAttributes& aEffectiveStoragePrincipalAttrs) { 28 MOZ_ASSERT(NS_IsMainThread()); 29 30 RefPtr<SharedWorkerManager> manager = 31 new SharedWorkerManager(aPBackgroundEventTarget, aData, aLoadingPrincipal, 32 aEffectiveStoragePrincipalAttrs); 33 34 RefPtr<SharedWorkerManagerHolder> holder = 35 new SharedWorkerManagerHolder(manager, aService); 36 return holder.forget(); 37 } 38 39 SharedWorkerManager::SharedWorkerManager( 40 nsIEventTarget* aPBackgroundEventTarget, const RemoteWorkerData& aData, 41 nsIPrincipal* aLoadingPrincipal, 42 const OriginAttributes& aEffectiveStoragePrincipalAttrs) 43 : mPBackgroundEventTarget(aPBackgroundEventTarget), 44 mLoadingPrincipal(aLoadingPrincipal), 45 mDomain(aData.domain()), 46 mEffectiveStoragePrincipalAttrs(aEffectiveStoragePrincipalAttrs), 47 mResolvedScriptURL(DeserializeURI(aData.resolvedScriptURL())), 48 mWorkerOptions(aData.workerOptions()), 49 mIsSecureContext(aData.isSecureContext()), 50 mSuspended(false), 51 mFrozen(false) { 52 MOZ_ASSERT(NS_IsMainThread()); 53 MOZ_ASSERT(aLoadingPrincipal); 54 } 55 56 SharedWorkerManager::~SharedWorkerManager() { 57 NS_ReleaseOnMainThread("SharedWorkerManager::mLoadingPrincipal", 58 mLoadingPrincipal.forget()); 59 NS_ProxyRelease("SharedWorkerManager::mRemoteWorkerController", 60 mPBackgroundEventTarget, mRemoteWorkerController.forget()); 61 } 62 63 bool SharedWorkerManager::MaybeCreateRemoteWorker( 64 const RemoteWorkerData& aData, uint64_t aWindowID, 65 UniqueMessagePortId& aPortIdentifier, base::ProcessId aProcessId) { 66 ::mozilla::ipc::AssertIsOnBackgroundThread(); 67 68 // Creating remote workers may result in creating new processes, but during 69 // parent shutdown that would add just noise, so better bail out. 70 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 71 return false; 72 } 73 74 if (!mRemoteWorkerController) { 75 mRemoteWorkerController = 76 RemoteWorkerController::Create(aData, this, aProcessId); 77 if (NS_WARN_IF(!mRemoteWorkerController)) { 78 return false; 79 } 80 } 81 82 if (aWindowID) { 83 mRemoteWorkerController->AddWindowID(aWindowID); 84 } 85 86 mRemoteWorkerController->AddPortIdentifier(aPortIdentifier.release()); 87 return true; 88 } 89 90 already_AddRefed<SharedWorkerManagerHolder> 91 SharedWorkerManager::MatchOnMainThread( 92 SharedWorkerService* aService, const RemoteWorkerData& aData, 93 nsIURI* aScriptURL, nsIPrincipal* aLoadingPrincipal, 94 const OriginAttributes& aEffectiveStoragePrincipalAttrs, 95 bool* aMatchNameButNotOptions) { 96 MOZ_ASSERT(NS_IsMainThread()); 97 MOZ_ASSERT(aMatchNameButNotOptions); 98 99 bool urlEquals; 100 if (NS_FAILED(aScriptURL->Equals(mResolvedScriptURL, &urlEquals))) { 101 return nullptr; 102 } 103 104 bool match = 105 aData.domain() == mDomain && urlEquals && 106 aData.workerOptions().mName == mWorkerOptions.mName && 107 // We want to be sure that the window's principal subsumes the 108 // SharedWorker's loading principal and vice versa. 109 mLoadingPrincipal->Subsumes(aLoadingPrincipal) && 110 aLoadingPrincipal->Subsumes(mLoadingPrincipal) && 111 mEffectiveStoragePrincipalAttrs == aEffectiveStoragePrincipalAttrs; 112 if (!match) { 113 return nullptr; 114 } 115 116 *aMatchNameButNotOptions = 117 aData.workerOptions().mType != mWorkerOptions.mType || 118 aData.workerOptions().mCredentials != mWorkerOptions.mCredentials; 119 120 if (*aMatchNameButNotOptions) { 121 return nullptr; 122 } 123 124 RefPtr<SharedWorkerManagerHolder> holder = 125 new SharedWorkerManagerHolder(this, aService); 126 return holder.forget(); 127 } 128 129 void SharedWorkerManager::AddActor(SharedWorkerParent* aParent) { 130 ::mozilla::ipc::AssertIsOnBackgroundThread(); 131 MOZ_ASSERT(aParent); 132 MOZ_ASSERT(!mActors.Contains(aParent)); 133 134 mActors.AppendElement(aParent); 135 136 if (mLockCount) { 137 (void)aParent->SendNotifyLock(true); 138 } 139 140 if (mWebTransportCount) { 141 (void)aParent->SendNotifyWebTransport(true); 142 } 143 144 // NB: We don't update our Suspended/Frozen state here, yet. The aParent is 145 // responsible for doing so from SharedWorkerParent::ManagerCreated. 146 // XXX But we could avoid iterating all of our actors because if aParent is 147 // not frozen and we are, we would just need to thaw ourselves. 148 } 149 150 void SharedWorkerManager::RemoveActor(SharedWorkerParent* aParent) { 151 ::mozilla::ipc::AssertIsOnBackgroundThread(); 152 MOZ_ASSERT(aParent); 153 MOZ_ASSERT(mActors.Contains(aParent)); 154 155 uint64_t windowID = aParent->WindowID(); 156 if (windowID) { 157 mRemoteWorkerController->RemoveWindowID(windowID); 158 } 159 160 mActors.RemoveElement(aParent); 161 162 if (!mActors.IsEmpty()) { 163 // Our remaining actors could be all suspended or frozen. 164 UpdateSuspend(); 165 UpdateFrozen(); 166 return; 167 } 168 } 169 170 void SharedWorkerManager::Terminate() { 171 ::mozilla::ipc::AssertIsOnBackgroundThread(); 172 MOZ_ASSERT(mActors.IsEmpty()); 173 MOZ_ASSERT(mHolders.IsEmpty()); 174 175 // mRemoteWorkerController creation can fail. If the creation fails 176 // mRemoteWorkerController is nullptr and we should stop termination here. 177 if (!mRemoteWorkerController) { 178 return; 179 } 180 181 mRemoteWorkerController->Terminate(); 182 mRemoteWorkerController = nullptr; 183 } 184 185 void SharedWorkerManager::UpdateSuspend() { 186 ::mozilla::ipc::AssertIsOnBackgroundThread(); 187 MOZ_ASSERT(mRemoteWorkerController); 188 189 uint32_t suspended = 0; 190 191 for (SharedWorkerParent* actor : mActors) { 192 if (actor->IsSuspended()) { 193 ++suspended; 194 } 195 } 196 197 // Call Suspend only when all of our actors' windows are suspended and call 198 // Resume only when one of them resumes. 199 if ((mSuspended && suspended == mActors.Length()) || 200 (!mSuspended && suspended != mActors.Length())) { 201 return; 202 } 203 204 if (!mSuspended) { 205 mSuspended = true; 206 mRemoteWorkerController->Suspend(); 207 } else { 208 mSuspended = false; 209 mRemoteWorkerController->Resume(); 210 } 211 } 212 213 void SharedWorkerManager::UpdateFrozen() { 214 ::mozilla::ipc::AssertIsOnBackgroundThread(); 215 MOZ_ASSERT(mRemoteWorkerController); 216 217 uint32_t frozen = 0; 218 219 for (SharedWorkerParent* actor : mActors) { 220 if (actor->IsFrozen()) { 221 ++frozen; 222 } 223 } 224 225 // Similar to UpdateSuspend, above, we only want to be frozen when all of our 226 // actors are frozen. 227 if ((mFrozen && frozen == mActors.Length()) || 228 (!mFrozen && frozen != mActors.Length())) { 229 return; 230 } 231 232 if (!mFrozen) { 233 mFrozen = true; 234 mRemoteWorkerController->Freeze(); 235 } else { 236 mFrozen = false; 237 mRemoteWorkerController->Thaw(); 238 } 239 } 240 241 bool SharedWorkerManager::IsSecureContext() const { return mIsSecureContext; } 242 243 void SharedWorkerManager::CreationFailed() { 244 ::mozilla::ipc::AssertIsOnBackgroundThread(); 245 246 for (SharedWorkerParent* actor : mActors) { 247 (void)actor->SendError(NS_ERROR_FAILURE); 248 } 249 } 250 251 void SharedWorkerManager::CreationSucceeded() { 252 ::mozilla::ipc::AssertIsOnBackgroundThread(); 253 // Nothing to do here. 254 } 255 256 void SharedWorkerManager::ErrorReceived(const ErrorValue& aValue) { 257 ::mozilla::ipc::AssertIsOnBackgroundThread(); 258 259 for (SharedWorkerParent* actor : mActors) { 260 (void)actor->SendError(aValue); 261 } 262 } 263 264 void SharedWorkerManager::LockNotified(bool aCreated) { 265 ::mozilla::ipc::AssertIsOnBackgroundThread(); 266 MOZ_ASSERT_IF(!aCreated, mLockCount > 0); 267 268 mLockCount += aCreated ? 1 : -1; 269 270 // Notify only when we either: 271 // 1. Got a new lock when nothing were there 272 // 2. Lost all locks 273 if ((aCreated && mLockCount == 1) || !mLockCount) { 274 for (SharedWorkerParent* actor : mActors) { 275 (void)actor->SendNotifyLock(aCreated); 276 } 277 } 278 }; 279 280 void SharedWorkerManager::WebTransportNotified(bool aCreated) { 281 ::mozilla::ipc::AssertIsOnBackgroundThread(); 282 MOZ_ASSERT_IF(!aCreated, mWebTransportCount > 0); 283 284 mWebTransportCount += aCreated ? 1 : -1; 285 286 // Notify only when we either: 287 // 1. Got a first WebTransport 288 // 2. The last WebTransport goes away 289 if ((aCreated && mWebTransportCount == 1) || mWebTransportCount == 0) { 290 for (SharedWorkerParent* actor : mActors) { 291 (void)actor->SendNotifyWebTransport(aCreated); 292 } 293 } 294 }; 295 296 void SharedWorkerManager::Terminated() { 297 ::mozilla::ipc::AssertIsOnBackgroundThread(); 298 299 for (SharedWorkerParent* actor : mActors) { 300 (void)actor->SendTerminate(); 301 } 302 } 303 304 void SharedWorkerManager::RegisterHolder(SharedWorkerManagerHolder* aHolder) { 305 MOZ_ASSERT(NS_IsMainThread()); 306 MOZ_ASSERT(aHolder); 307 MOZ_ASSERT(!mHolders.Contains(aHolder)); 308 309 mHolders.AppendElement(aHolder); 310 } 311 312 void SharedWorkerManager::UnregisterHolder(SharedWorkerManagerHolder* aHolder) { 313 MOZ_ASSERT(NS_IsMainThread()); 314 MOZ_ASSERT(aHolder); 315 MOZ_ASSERT(mHolders.Contains(aHolder)); 316 317 mHolders.RemoveElement(aHolder); 318 319 if (!mHolders.IsEmpty()) { 320 return; 321 } 322 323 // Time to go. 324 325 aHolder->Service()->RemoveWorkerManagerOnMainThread(this); 326 327 RefPtr<SharedWorkerManager> self = this; 328 mPBackgroundEventTarget->Dispatch( 329 NS_NewRunnableFunction( 330 "SharedWorkerService::RemoveWorkerManagerOnMainThread", 331 [self]() { self->Terminate(); }), 332 NS_DISPATCH_NORMAL); 333 } 334 335 SharedWorkerManagerHolder::SharedWorkerManagerHolder( 336 SharedWorkerManager* aManager, SharedWorkerService* aService) 337 : mManager(aManager), mService(aService) { 338 MOZ_ASSERT(NS_IsMainThread()); 339 MOZ_ASSERT(aManager); 340 MOZ_ASSERT(aService); 341 342 aManager->RegisterHolder(this); 343 } 344 345 SharedWorkerManagerHolder::~SharedWorkerManagerHolder() { 346 MOZ_ASSERT(NS_IsMainThread()); 347 mManager->UnregisterHolder(this); 348 } 349 350 SharedWorkerManagerWrapper::SharedWorkerManagerWrapper( 351 already_AddRefed<SharedWorkerManagerHolder> aHolder) 352 : mHolder(aHolder) { 353 MOZ_ASSERT(NS_IsMainThread()); 354 } 355 356 SharedWorkerManagerWrapper::~SharedWorkerManagerWrapper() { 357 NS_ReleaseOnMainThread("SharedWorkerManagerWrapper::mHolder", 358 mHolder.forget()); 359 } 360 361 } // namespace mozilla::dom