Backed out changeset 2450366cf7ca (bug 1891629) for causing win msix mochitest failures
[gecko.git] / dom / locks / LockManager.cpp
blobb19abcfdaf326ee6a0b32a83889f26613e82bf7d
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/ErrorResult.h"
14 #include "mozilla/dom/LockManagerBinding.h"
15 #include "mozilla/dom/Promise.h"
16 #include "mozilla/dom/locks/PLockManager.h"
17 #include "mozilla/ipc/BackgroundChild.h"
18 #include "mozilla/ipc/BackgroundUtils.h"
19 #include "mozilla/ipc/PBackgroundChild.h"
21 namespace mozilla::dom {
23 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(LockManager, mOwner)
24 NS_IMPL_CYCLE_COLLECTING_ADDREF(LockManager)
25 NS_IMPL_CYCLE_COLLECTING_RELEASE(LockManager)
26 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LockManager)
27 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
28 NS_INTERFACE_MAP_ENTRY(nsISupports)
29 NS_INTERFACE_MAP_END
31 JSObject* LockManager::WrapObject(JSContext* aCx,
32 JS::Handle<JSObject*> aGivenProto) {
33 return LockManager_Binding::Wrap(aCx, this, aGivenProto);
36 LockManager::LockManager(nsIGlobalObject* aGlobal) : mOwner(aGlobal) {
37 Maybe<ClientInfo> clientInfo = aGlobal->GetClientInfo();
38 if (!clientInfo) {
39 // Pass the nonworking object and let request()/query() throw.
40 return;
43 nsCOMPtr<nsIPrincipal> principal =
44 clientInfo->GetPrincipal().unwrapOr(nullptr);
45 if (!principal || !principal->GetIsContentPrincipal()) {
46 // Same, the methods will throw instead of the constructor.
47 return;
50 mozilla::ipc::PBackgroundChild* backgroundActor =
51 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
52 mActor = new locks::LockManagerChild(aGlobal);
54 if (!backgroundActor->SendPLockManagerConstructor(
55 mActor, WrapNotNull(principal), clientInfo->Id())) {
56 // Failed to construct the actor. Pass the nonworking object and let the
57 // methods throw.
58 mActor = nullptr;
59 return;
63 already_AddRefed<LockManager> LockManager::Create(nsIGlobalObject& aGlobal) {
64 RefPtr<LockManager> manager = new LockManager(&aGlobal);
66 if (!NS_IsMainThread()) {
67 // Grabbing WorkerRef may fail and that will cause the methods throw later.
68 manager->mWorkerRef =
69 WeakWorkerRef::Create(GetCurrentThreadWorkerPrivate(), [manager]() {
70 // Others may grab a strong reference and block immediate destruction.
71 // Shutdown early as we don't have to wait for them.
72 manager->Shutdown();
73 manager->mWorkerRef = nullptr;
74 });
77 return manager.forget();
80 static bool ValidateRequestArguments(const nsAString& name,
81 const LockOptions& options,
82 ErrorResult& aRv) {
83 if (name.Length() > 0 && name.First() == u'-') {
84 aRv.ThrowNotSupportedError("Names starting with `-` are reserved");
85 return false;
87 if (options.mSteal) {
88 if (options.mIfAvailable) {
89 aRv.ThrowNotSupportedError(
90 "`steal` and `ifAvailable` cannot be used together");
91 return false;
93 if (options.mMode != LockMode::Exclusive) {
94 aRv.ThrowNotSupportedError(
95 "`steal` is only supported for exclusive lock requests");
96 return false;
99 if (options.mSignal.WasPassed()) {
100 if (options.mSteal) {
101 aRv.ThrowNotSupportedError(
102 "`steal` and `signal` cannot be used together");
103 return false;
105 if (options.mIfAvailable) {
106 aRv.ThrowNotSupportedError(
107 "`ifAvailable` and `signal` cannot be used together");
108 return false;
110 if (options.mSignal.Value().Aborted()) {
111 AutoJSAPI jsapi;
112 if (!jsapi.Init(options.mSignal.Value().GetParentObject())) {
113 aRv.ThrowNotSupportedError("Signal's realm isn't active anymore.");
114 return false;
117 JSContext* cx = jsapi.cx();
118 JS::Rooted<JS::Value> reason(cx);
119 options.mSignal.Value().GetReason(cx, &reason);
120 aRv.MightThrowJSException();
121 aRv.ThrowJSException(cx, reason);
122 return false;
125 return true;
128 already_AddRefed<Promise> LockManager::Request(const nsAString& aName,
129 LockGrantedCallback& aCallback,
130 ErrorResult& aRv) {
131 return Request(aName, LockOptions(), aCallback, aRv);
133 already_AddRefed<Promise> LockManager::Request(const nsAString& aName,
134 const LockOptions& aOptions,
135 LockGrantedCallback& aCallback,
136 ErrorResult& aRv) {
137 if (!mOwner->GetClientInfo()) {
138 // We do have nsPIDOMWindowInner::IsFullyActive for this kind of check,
139 // but this should be sufficient here as unloaded iframe is the only
140 // non-fully-active case that Web Locks should worry about (since it does
141 // not enter bfcache).
142 aRv.ThrowInvalidStateError(
143 "The document of the lock manager is not fully active");
144 return nullptr;
147 const StorageAccess access = mOwner->GetStorageAccess();
148 bool allowed =
149 access > StorageAccess::eDeny ||
150 (StaticPrefs::
151 privacy_partition_always_partition_third_party_non_cookie_storage() &&
152 ShouldPartitionStorage(access));
153 if (!allowed) {
154 // Step 4: If origin is an opaque origin, then return a promise rejected
155 // with a "SecurityError" DOMException.
156 // But per https://w3c.github.io/web-locks/#lock-managers this really means
157 // whether it has storage access.
158 aRv.ThrowSecurityError("request() is not allowed in this context");
159 return nullptr;
162 if (!mActor) {
163 aRv.ThrowNotSupportedError(
164 "Web Locks API is not enabled for this kind of document");
165 return nullptr;
168 if (!NS_IsMainThread() && !mWorkerRef) {
169 aRv.ThrowInvalidStateError("request() is not allowed at this point");
170 return nullptr;
173 if (!ValidateRequestArguments(aName, aOptions, aRv)) {
174 return nullptr;
177 RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
178 if (aRv.Failed()) {
179 return nullptr;
182 mActor->RequestLock({nsString(aName), promise, &aCallback}, aOptions);
183 return promise.forget();
186 already_AddRefed<Promise> LockManager::Query(ErrorResult& aRv) {
187 if (!mOwner->GetClientInfo()) {
188 aRv.ThrowInvalidStateError(
189 "The document of the lock manager is not fully active");
190 return nullptr;
193 if (mOwner->GetStorageAccess() <= StorageAccess::eDeny) {
194 aRv.ThrowSecurityError("query() is not allowed in this context");
195 return nullptr;
198 if (!mActor) {
199 aRv.ThrowNotSupportedError(
200 "Web Locks API is not enabled for this kind of document");
201 return nullptr;
204 if (!NS_IsMainThread() && !mWorkerRef) {
205 aRv.ThrowInvalidStateError("query() is not allowed at this point");
206 return nullptr;
209 RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
210 if (aRv.Failed()) {
211 return nullptr;
214 mActor->SendQuery()->Then(
215 GetCurrentSerialEventTarget(), __func__,
216 [promise](locks::LockManagerChild::QueryPromise::ResolveOrRejectValue&&
217 aResult) {
218 if (aResult.IsResolve()) {
219 promise->MaybeResolve(aResult.ResolveValue());
220 } else {
221 promise->MaybeRejectWithUnknownError("Query failed");
224 return promise.forget();
227 void LockManager::Shutdown() {
228 if (mActor) {
229 locks::PLockManagerChild::Send__delete__(mActor);
230 mActor = nullptr;
234 } // namespace mozilla::dom