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