Bug 1945643 - Update to mozilla-nimbus-schemas 2025.1.1 r=chumphreys
[gecko.git] / dom / locks / LockManager.cpp
blob5da6c2cc35385832d9ed3b317b440340bd6fa3d3
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/. */
7 #include "mozilla/dom/LockManager.h"
8 #include "mozilla/dom/AutoEntryScript.h"
9 #include "mozilla/dom/WorkerCommon.h"
10 #include "mozilla/dom/locks/LockManagerChild.h"
11 #include "mozilla/dom/locks/LockRequestChild.h"
12 #include "mozilla/Assertions.h"
13 #include "mozilla/BasePrincipal.h"
14 #include "mozilla/ErrorResult.h"
15 #include "mozilla/dom/LockManagerBinding.h"
16 #include "mozilla/dom/Promise.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"
22 namespace mozilla::dom {
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
32 JSObject* LockManager::WrapObject(JSContext* aCx,
33 JS::Handle<JSObject*> aGivenProto) {
34 return LockManager_Binding::Wrap(aCx, this, aGivenProto);
37 LockManager::LockManager(nsIGlobalObject* aGlobal) : mOwner(aGlobal) {
38 Maybe<nsID> clientID;
39 nsCOMPtr<nsIPrincipal> principal;
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;
52 principal = clientInfo->GetPrincipal().unwrapOr(nullptr);
53 if (!principal) {
54 return;
57 if (!principal->GetIsContentPrincipal()) {
58 // Same, the methods will throw instead of the constructor.
59 return;
62 clientID = Some(clientInfo->Id());
65 mozilla::ipc::PBackgroundChild* backgroundActor =
66 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
67 mActor = new locks::LockManagerChild(aGlobal);
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;
78 already_AddRefed<LockManager> LockManager::Create(nsIGlobalObject& aGlobal) {
79 RefPtr<LockManager> manager = new LockManager(&aGlobal);
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.
95 return manager.forget();
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;
105 if (options.mSteal) {
106 if (options.mIfAvailable) {
107 aRv.ThrowNotSupportedError(
108 "`steal` and `ifAvailable` cannot be used together");
109 return false;
111 if (options.mMode != LockMode::Exclusive) {
112 aRv.ThrowNotSupportedError(
113 "`steal` is only supported for exclusive lock requests");
114 return false;
117 if (options.mSignal.WasPassed()) {
118 if (options.mSteal) {
119 aRv.ThrowNotSupportedError(
120 "`steal` and `signal` cannot be used together");
121 return false;
123 if (options.mIfAvailable) {
124 aRv.ThrowNotSupportedError(
125 "`ifAvailable` and `signal` cannot be used together");
126 return false;
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;
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;
143 return true;
146 already_AddRefed<Promise> LockManager::Request(const nsAString& aName,
147 LockGrantedCallback& aCallback,
148 ErrorResult& aRv) {
149 return Request(aName, LockOptions(), aCallback, aRv);
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;
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;
183 if (!mActor) {
184 aRv.ThrowNotSupportedError(
185 "Web Locks API is not enabled for this kind of document");
186 return nullptr;
189 if (!NS_IsMainThread() && !mWorkerRef) {
190 aRv.ThrowInvalidStateError("request() is not allowed at this point");
191 return nullptr;
194 if (!ValidateRequestArguments(aName, aOptions, aRv)) {
195 return nullptr;
198 RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
199 if (aRv.Failed()) {
200 return nullptr;
203 mActor->RequestLock({nsString(aName), promise, &aCallback}, aOptions);
204 return promise.forget();
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;
217 if (mOwner->GetStorageAccess() <= StorageAccess::eDeny) {
218 aRv.ThrowSecurityError("query() is not allowed in this context");
219 return nullptr;
222 if (!mActor) {
223 aRv.ThrowNotSupportedError(
224 "Web Locks API is not enabled for this kind of document");
225 return nullptr;
228 if (!NS_IsMainThread() && !mWorkerRef) {
229 aRv.ThrowInvalidStateError("query() is not allowed at this point");
230 return nullptr;
233 RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
234 if (aRv.Failed()) {
235 return nullptr;
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");
248 return promise.forget();
251 void LockManager::Shutdown() {
252 if (mActor) {
253 locks::PLockManagerChild::Send__delete__(mActor);
254 mActor = nullptr;
258 } // namespace mozilla::dom