LockManager.cpp (8615B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=2 sw=2 sts=2 et cindent: */ 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 "mozilla/dom/LockManager.h" 8 9 #include "mozilla/BasePrincipal.h" 10 #include "mozilla/ErrorResult.h" 11 #include "mozilla/dom/AutoEntryScript.h" 12 #include "mozilla/dom/LockManagerBinding.h" 13 #include "mozilla/dom/Promise.h" 14 #include "mozilla/dom/WorkerCommon.h" 15 #include "mozilla/dom/locks/LockManagerChild.h" 16 #include "mozilla/dom/locks/LockRequestChild.h" 17 #include "mozilla/dom/locks/PLockManager.h" 18 #include "mozilla/ipc/BackgroundChild.h" 19 #include "mozilla/ipc/BackgroundUtils.h" 20 #include "mozilla/ipc/PBackgroundChild.h" 21 22 namespace mozilla::dom { 23 24 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(LockManager, mOwner) 25 NS_IMPL_CYCLE_COLLECTING_ADDREF(LockManager) 26 NS_IMPL_CYCLE_COLLECTING_RELEASE(LockManager) 27 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LockManager) 28 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 29 NS_INTERFACE_MAP_ENTRY(nsISupports) 30 NS_INTERFACE_MAP_END 31 32 JSObject* LockManager::WrapObject(JSContext* aCx, 33 JS::Handle<JSObject*> aGivenProto) { 34 return LockManager_Binding::Wrap(aCx, this, aGivenProto); 35 } 36 37 LockManager::LockManager(nsIGlobalObject* aGlobal) : mOwner(aGlobal) { 38 Maybe<nsID> clientID; 39 nsCOMPtr<nsIPrincipal> principal; 40 41 if (XRE_IsParentProcess() && aGlobal->PrincipalOrNull() && 42 aGlobal->PrincipalOrNull()->IsSystemPrincipal()) { 43 clientID = Nothing(); 44 principal = aGlobal->PrincipalOrNull(); 45 } else { 46 Maybe<ClientInfo> clientInfo = aGlobal->GetClientInfo(); 47 if (!clientInfo) { 48 // Pass the nonworking object and let request()/query() throw. 49 return; 50 } 51 52 principal = clientInfo->GetPrincipal().unwrapOr(nullptr); 53 if (!principal) { 54 return; 55 } 56 57 if (!principal->GetIsContentPrincipal()) { 58 // Same, the methods will throw instead of the constructor. 59 return; 60 } 61 62 clientID = Some(clientInfo->Id()); 63 } 64 65 mozilla::ipc::PBackgroundChild* backgroundActor = 66 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread(); 67 mActor = new locks::LockManagerChild(aGlobal); 68 69 if (!backgroundActor->SendPLockManagerConstructor( 70 mActor, WrapNotNull(principal), clientID)) { 71 // Failed to construct the actor. Pass the nonworking object and let the 72 // methods throw. 73 mActor = nullptr; 74 return; 75 } 76 } 77 78 already_AddRefed<LockManager> LockManager::Create(nsIGlobalObject& aGlobal) { 79 RefPtr<LockManager> manager = new LockManager(&aGlobal); 80 81 if (!NS_IsMainThread()) { 82 // Grabbing WorkerRef may fail and that will cause the methods throw later. 83 manager->mWorkerRef = 84 WeakWorkerRef::Create(GetCurrentThreadWorkerPrivate(), [manager]() { 85 // Others may grab a strong reference and block immediate destruction. 86 // Shutdown early as we don't have to wait for them. 87 manager->Shutdown(); 88 manager->mWorkerRef = nullptr; 89 }); 90 // Do not handle the WeakWorkerRef creation fail here. 91 // Suppose WorkerNavigator::Invalidate() should call LockManager::Shutdown() 92 // before set WorkerNavigator::mLocks as nullptr. 93 } 94 95 return manager.forget(); 96 } 97 98 static bool ValidateRequestArguments(const nsAString& name, 99 const LockOptions& options, 100 ErrorResult& aRv) { 101 if (name.Length() > 0 && name.First() == u'-') { 102 aRv.ThrowNotSupportedError("Names starting with `-` are reserved"); 103 return false; 104 } 105 if (options.mSteal) { 106 if (options.mIfAvailable) { 107 aRv.ThrowNotSupportedError( 108 "`steal` and `ifAvailable` cannot be used together"); 109 return false; 110 } 111 if (options.mMode != LockMode::Exclusive) { 112 aRv.ThrowNotSupportedError( 113 "`steal` is only supported for exclusive lock requests"); 114 return false; 115 } 116 } 117 if (options.mSignal.WasPassed()) { 118 if (options.mSteal) { 119 aRv.ThrowNotSupportedError( 120 "`steal` and `signal` cannot be used together"); 121 return false; 122 } 123 if (options.mIfAvailable) { 124 aRv.ThrowNotSupportedError( 125 "`ifAvailable` and `signal` cannot be used together"); 126 return false; 127 } 128 if (options.mSignal.Value().Aborted()) { 129 AutoJSAPI jsapi; 130 if (!jsapi.Init(options.mSignal.Value().GetParentObject())) { 131 aRv.ThrowNotSupportedError("Signal's realm isn't active anymore."); 132 return false; 133 } 134 135 JSContext* cx = jsapi.cx(); 136 JS::Rooted<JS::Value> reason(cx); 137 options.mSignal.Value().GetReason(cx, &reason); 138 aRv.MightThrowJSException(); 139 aRv.ThrowJSException(cx, reason); 140 return false; 141 } 142 } 143 return true; 144 } 145 146 already_AddRefed<Promise> LockManager::Request(const nsAString& aName, 147 LockGrantedCallback& aCallback, 148 ErrorResult& aRv) { 149 return Request(aName, LockOptions(), aCallback, aRv); 150 }; 151 already_AddRefed<Promise> LockManager::Request(const nsAString& aName, 152 const LockOptions& aOptions, 153 LockGrantedCallback& aCallback, 154 ErrorResult& aRv) { 155 if (!mOwner->PrincipalOrNull() || 156 !mOwner->PrincipalOrNull()->IsSystemPrincipal()) { 157 if (!mOwner->GetClientInfo()) { 158 // We do have nsPIDOMWindowInner::IsFullyActive for this kind of check, 159 // but this should be sufficient here as unloaded iframe is the only 160 // non-fully-active case that Web Locks should worry about (since it does 161 // not enter bfcache). 162 aRv.ThrowInvalidStateError( 163 "The document of the lock manager is not fully active"); 164 return nullptr; 165 } 166 } 167 168 const StorageAccess access = mOwner->GetStorageAccess(); 169 bool allowed = 170 access > StorageAccess::eDeny || 171 (StaticPrefs:: 172 privacy_partition_always_partition_third_party_non_cookie_storage() && 173 ShouldPartitionStorage(access)); 174 if (!allowed) { 175 // Step 4: If origin is an opaque origin, then return a promise rejected 176 // with a "SecurityError" DOMException. 177 // But per https://w3c.github.io/web-locks/#lock-managers this really means 178 // whether it has storage access. 179 aRv.ThrowSecurityError("request() is not allowed in this context"); 180 return nullptr; 181 } 182 183 if (!mActor) { 184 aRv.ThrowNotSupportedError( 185 "Web Locks API is not enabled for this kind of document"); 186 return nullptr; 187 } 188 189 if (!NS_IsMainThread() && !mWorkerRef) { 190 aRv.ThrowInvalidStateError("request() is not allowed at this point"); 191 return nullptr; 192 } 193 194 if (!ValidateRequestArguments(aName, aOptions, aRv)) { 195 return nullptr; 196 } 197 198 RefPtr<Promise> promise = Promise::Create(mOwner, aRv); 199 if (aRv.Failed()) { 200 return nullptr; 201 } 202 203 mActor->RequestLock({nsString(aName), promise, &aCallback}, aOptions); 204 return promise.forget(); 205 }; 206 207 already_AddRefed<Promise> LockManager::Query(ErrorResult& aRv) { 208 if (!mOwner->PrincipalOrNull() || 209 !mOwner->PrincipalOrNull()->IsSystemPrincipal()) { 210 if (!mOwner->GetClientInfo()) { 211 aRv.ThrowInvalidStateError( 212 "The document of the lock manager is not fully active"); 213 return nullptr; 214 } 215 } 216 217 if (mOwner->GetStorageAccess() <= StorageAccess::eDeny) { 218 aRv.ThrowSecurityError("query() is not allowed in this context"); 219 return nullptr; 220 } 221 222 if (!mActor) { 223 aRv.ThrowNotSupportedError( 224 "Web Locks API is not enabled for this kind of document"); 225 return nullptr; 226 } 227 228 if (!NS_IsMainThread() && !mWorkerRef) { 229 aRv.ThrowInvalidStateError("query() is not allowed at this point"); 230 return nullptr; 231 } 232 233 RefPtr<Promise> promise = Promise::Create(mOwner, aRv); 234 if (aRv.Failed()) { 235 return nullptr; 236 } 237 238 mActor->SendQuery()->Then( 239 GetCurrentSerialEventTarget(), __func__, 240 [promise](locks::LockManagerChild::QueryPromise::ResolveOrRejectValue&& 241 aResult) { 242 if (aResult.IsResolve()) { 243 promise->MaybeResolve(aResult.ResolveValue()); 244 } else { 245 promise->MaybeRejectWithUnknownError("Query failed"); 246 } 247 }); 248 return promise.forget(); 249 }; 250 251 void LockManager::Shutdown() { 252 if (mActor) { 253 locks::PLockManagerChild::Send__delete__(mActor); 254 mActor = nullptr; 255 } 256 } 257 258 } // namespace mozilla::dom