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 #include "mozilla/dom/WorkerCommon.h"
9 #include "mozilla/dom/locks/LockManagerChild.h"
10 #include "mozilla/dom/locks/LockRequestChild.h"
11 #include "mozilla/Assertions.h"
12 #include "mozilla/ErrorResult.h"
13 #include "mozilla/dom/LockManagerBinding.h"
14 #include "mozilla/dom/Promise.h"
15 #include "mozilla/dom/locks/PLockManager.h"
16 #include "mozilla/ipc/BackgroundChild.h"
17 #include "mozilla/ipc/BackgroundUtils.h"
18 #include "mozilla/ipc/PBackgroundChild.h"
19
20 namespace mozilla::dom {
21
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(LockManager,mOwner,mActor)22 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(LockManager, mOwner, mActor)
23 NS_IMPL_CYCLE_COLLECTING_ADDREF(LockManager)
24 NS_IMPL_CYCLE_COLLECTING_RELEASE(LockManager)
25 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LockManager)
26 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
27 NS_INTERFACE_MAP_ENTRY(nsISupports)
28 NS_INTERFACE_MAP_END
29
30 JSObject* LockManager::WrapObject(JSContext* aCx,
31 JS::Handle<JSObject*> aGivenProto) {
32 return LockManager_Binding::Wrap(aCx, this, aGivenProto);
33 }
34
LockManager(nsIGlobalObject * aGlobal)35 LockManager::LockManager(nsIGlobalObject* aGlobal) : mOwner(aGlobal) {
36 Maybe<ClientInfo> clientInfo = aGlobal->GetClientInfo();
37 if (!clientInfo) {
38 return;
39 }
40
41 const mozilla::ipc::PrincipalInfo& principalInfo =
42 clientInfo->PrincipalInfo();
43
44 if (principalInfo.type() !=
45 mozilla::ipc::PrincipalInfo::TContentPrincipalInfo) {
46 return;
47 }
48
49 mozilla::ipc::PBackgroundChild* backgroundActor =
50 mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
51 mActor = new locks::LockManagerChild(aGlobal);
52 backgroundActor->SendPLockManagerConstructor(mActor, principalInfo,
53 clientInfo->Id());
54
55 if (!NS_IsMainThread()) {
56 mWorkerRef = WeakWorkerRef::Create(GetCurrentThreadWorkerPrivate(),
57 [self = RefPtr(this)]() {
58 // Others may grab a strong reference
59 // and block immediate destruction.
60 // Shutdown early as we don't have to
61 // wait for them.
62 self->Shutdown();
63 self->mWorkerRef = nullptr;
64 });
65 }
66 }
67
ValidateRequestArguments(const nsAString & name,const LockOptions & options,ErrorResult & aRv)68 static bool ValidateRequestArguments(const nsAString& name,
69 const LockOptions& options,
70 ErrorResult& aRv) {
71 if (name.Length() > 0 && name.First() == u'-') {
72 aRv.ThrowNotSupportedError("Names starting with `-` are reserved");
73 return false;
74 }
75 if (options.mSteal) {
76 if (options.mIfAvailable) {
77 aRv.ThrowNotSupportedError(
78 "`steal` and `ifAvailable` cannot be used together");
79 return false;
80 }
81 if (options.mMode != LockMode::Exclusive) {
82 aRv.ThrowNotSupportedError(
83 "`steal` is only supported for exclusive lock requests");
84 return false;
85 }
86 }
87 if (options.mSignal.WasPassed()) {
88 if (options.mSteal) {
89 aRv.ThrowNotSupportedError(
90 "`steal` and `signal` cannot be used together");
91 return false;
92 }
93 if (options.mIfAvailable) {
94 aRv.ThrowNotSupportedError(
95 "`ifAvailable` and `signal` cannot be used together");
96 return false;
97 }
98 if (options.mSignal.Value().Aborted()) {
99 aRv.ThrowAbortError("The lock request is aborted");
100 return false;
101 }
102 }
103 return true;
104 }
105
Request(const nsAString & aName,LockGrantedCallback & aCallback,ErrorResult & aRv)106 already_AddRefed<Promise> LockManager::Request(const nsAString& aName,
107 LockGrantedCallback& aCallback,
108 ErrorResult& aRv) {
109 return Request(aName, LockOptions(), aCallback, aRv);
110 };
Request(const nsAString & aName,const LockOptions & aOptions,LockGrantedCallback & aCallback,ErrorResult & aRv)111 already_AddRefed<Promise> LockManager::Request(const nsAString& aName,
112 const LockOptions& aOptions,
113 LockGrantedCallback& aCallback,
114 ErrorResult& aRv) {
115 if (!mOwner->GetClientInfo()) {
116 // We do have nsPIDOMWindowInner::IsFullyActive for this kind of check,
117 // but this should be sufficient here as unloaded iframe is the only
118 // non-fully-active case that Web Locks should worry about (since it does
119 // not enter bfcache).
120 aRv.ThrowInvalidStateError(
121 "The document of the lock manager is not fully active");
122 return nullptr;
123 }
124
125 if (mOwner->GetStorageAccess() <= StorageAccess::eDeny) {
126 // Step 4: If origin is an opaque origin, then return a promise rejected
127 // with a "SecurityError" DOMException.
128 // But per https://wicg.github.io/web-locks/#lock-managers this really means
129 // whether it has storage access.
130 aRv.ThrowSecurityError("request() is not allowed in this context");
131 return nullptr;
132 }
133
134 if (!mActor) {
135 aRv.ThrowNotSupportedError(
136 "Web Locks API is not enabled for this kind of document");
137 return nullptr;
138 }
139
140 if (!ValidateRequestArguments(aName, aOptions, aRv)) {
141 return nullptr;
142 }
143
144 RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
145 if (aRv.Failed()) {
146 return nullptr;
147 }
148
149 mActor->RequestLock({nsString(aName), promise, &aCallback}, aOptions);
150 return promise.forget();
151 };
152
Query(ErrorResult & aRv)153 already_AddRefed<Promise> LockManager::Query(ErrorResult& aRv) {
154 if (!mOwner->GetClientInfo()) {
155 aRv.ThrowInvalidStateError(
156 "The document of the lock manager is not fully active");
157 return nullptr;
158 }
159
160 if (mOwner->GetStorageAccess() <= StorageAccess::eDeny) {
161 aRv.ThrowSecurityError("query() is not allowed in this context");
162 return nullptr;
163 }
164
165 if (!mActor) {
166 aRv.ThrowNotSupportedError(
167 "Web Locks API is not enabled for this kind of document");
168 return nullptr;
169 }
170
171 RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
172 if (aRv.Failed()) {
173 return nullptr;
174 }
175
176 mActor->SendQuery()->Then(
177 GetCurrentSerialEventTarget(), __func__,
178 [promise](locks::LockManagerChild::QueryPromise::ResolveOrRejectValue&&
179 aResult) {
180 if (aResult.IsResolve()) {
181 promise->MaybeResolve(aResult.ResolveValue());
182 } else {
183 promise->MaybeRejectWithUnknownError("Query failed");
184 }
185 });
186 return promise.forget();
187 };
188
Shutdown()189 void LockManager::Shutdown() {
190 if (mActor) {
191 locks::PLockManagerChild::Send__delete__(mActor);
192 mActor = nullptr;
193 }
194 }
195
196 } // namespace mozilla::dom
197