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
)
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
) {
39 nsCOMPtr
<nsIPrincipal
> principal
;
41 if (XRE_IsParentProcess() && aGlobal
->PrincipalOrNull() &&
42 aGlobal
->PrincipalOrNull()->IsSystemPrincipal()) {
44 principal
= aGlobal
->PrincipalOrNull();
46 Maybe
<ClientInfo
> clientInfo
= aGlobal
->GetClientInfo();
48 // Pass the nonworking object and let request()/query() throw.
52 principal
= clientInfo
->GetPrincipal().unwrapOr(nullptr);
57 if (!principal
->GetIsContentPrincipal()) {
58 // Same, the methods will throw instead of the constructor.
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
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.
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.
88 manager
->mWorkerRef
= nullptr;
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
,
101 if (name
.Length() > 0 && name
.First() == u
'-') {
102 aRv
.ThrowNotSupportedError("Names starting with `-` are reserved");
105 if (options
.mSteal
) {
106 if (options
.mIfAvailable
) {
107 aRv
.ThrowNotSupportedError(
108 "`steal` and `ifAvailable` cannot be used together");
111 if (options
.mMode
!= LockMode::Exclusive
) {
112 aRv
.ThrowNotSupportedError(
113 "`steal` is only supported for exclusive lock requests");
117 if (options
.mSignal
.WasPassed()) {
118 if (options
.mSteal
) {
119 aRv
.ThrowNotSupportedError(
120 "`steal` and `signal` cannot be used together");
123 if (options
.mIfAvailable
) {
124 aRv
.ThrowNotSupportedError(
125 "`ifAvailable` and `signal` cannot be used together");
128 if (options
.mSignal
.Value().Aborted()) {
130 if (!jsapi
.Init(options
.mSignal
.Value().GetParentObject())) {
131 aRv
.ThrowNotSupportedError("Signal's realm isn't active anymore.");
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
);
146 already_AddRefed
<Promise
> LockManager::Request(const nsAString
& aName
,
147 LockGrantedCallback
& aCallback
,
149 return Request(aName
, LockOptions(), aCallback
, aRv
);
151 already_AddRefed
<Promise
> LockManager::Request(const nsAString
& aName
,
152 const LockOptions
& aOptions
,
153 LockGrantedCallback
& aCallback
,
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");
168 const StorageAccess access
= mOwner
->GetStorageAccess();
170 access
> StorageAccess::eDeny
||
172 privacy_partition_always_partition_third_party_non_cookie_storage() &&
173 ShouldPartitionStorage(access
));
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");
184 aRv
.ThrowNotSupportedError(
185 "Web Locks API is not enabled for this kind of document");
189 if (!NS_IsMainThread() && !mWorkerRef
) {
190 aRv
.ThrowInvalidStateError("request() is not allowed at this point");
194 if (!ValidateRequestArguments(aName
, aOptions
, aRv
)) {
198 RefPtr
<Promise
> promise
= Promise::Create(mOwner
, aRv
);
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");
217 if (mOwner
->GetStorageAccess() <= StorageAccess::eDeny
) {
218 aRv
.ThrowSecurityError("query() is not allowed in this context");
223 aRv
.ThrowNotSupportedError(
224 "Web Locks API is not enabled for this kind of document");
228 if (!NS_IsMainThread() && !mWorkerRef
) {
229 aRv
.ThrowInvalidStateError("query() is not allowed at this point");
233 RefPtr
<Promise
> promise
= Promise::Create(mOwner
, aRv
);
238 mActor
->SendQuery()->Then(
239 GetCurrentSerialEventTarget(), __func__
,
240 [promise
](locks::LockManagerChild::QueryPromise::ResolveOrRejectValue
&&
242 if (aResult
.IsResolve()) {
243 promise
->MaybeResolve(aResult
.ResolveValue());
245 promise
->MaybeRejectWithUnknownError("Query failed");
248 return promise
.forget();
251 void LockManager::Shutdown() {
253 locks::PLockManagerChild::Send__delete__(mActor
);
258 } // namespace mozilla::dom