summaryrefslogtreecommitdiffstats
path: root/dom/locks/LockManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/locks/LockManager.cpp')
-rw-r--r--dom/locks/LockManager.cpp234
1 files changed, 234 insertions, 0 deletions
diff --git a/dom/locks/LockManager.cpp b/dom/locks/LockManager.cpp
new file mode 100644
index 0000000000..b19abcfdaf
--- /dev/null
+++ b/dom/locks/LockManager.cpp
@@ -0,0 +1,234 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/LockManager.h"
+#include "mozilla/dom/AutoEntryScript.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/locks/LockManagerChild.h"
+#include "mozilla/dom/locks/LockRequestChild.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/LockManagerBinding.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/locks/PLockManager.h"
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/BackgroundUtils.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(LockManager, mOwner)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(LockManager)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(LockManager)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(LockManager)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* LockManager::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return LockManager_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+LockManager::LockManager(nsIGlobalObject* aGlobal) : mOwner(aGlobal) {
+ Maybe<ClientInfo> clientInfo = aGlobal->GetClientInfo();
+ if (!clientInfo) {
+ // Pass the nonworking object and let request()/query() throw.
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal =
+ clientInfo->GetPrincipal().unwrapOr(nullptr);
+ if (!principal || !principal->GetIsContentPrincipal()) {
+ // Same, the methods will throw instead of the constructor.
+ return;
+ }
+
+ mozilla::ipc::PBackgroundChild* backgroundActor =
+ mozilla::ipc::BackgroundChild::GetOrCreateForCurrentThread();
+ mActor = new locks::LockManagerChild(aGlobal);
+
+ if (!backgroundActor->SendPLockManagerConstructor(
+ mActor, WrapNotNull(principal), clientInfo->Id())) {
+ // Failed to construct the actor. Pass the nonworking object and let the
+ // methods throw.
+ mActor = nullptr;
+ return;
+ }
+}
+
+already_AddRefed<LockManager> LockManager::Create(nsIGlobalObject& aGlobal) {
+ RefPtr<LockManager> manager = new LockManager(&aGlobal);
+
+ if (!NS_IsMainThread()) {
+ // Grabbing WorkerRef may fail and that will cause the methods throw later.
+ manager->mWorkerRef =
+ WeakWorkerRef::Create(GetCurrentThreadWorkerPrivate(), [manager]() {
+ // Others may grab a strong reference and block immediate destruction.
+ // Shutdown early as we don't have to wait for them.
+ manager->Shutdown();
+ manager->mWorkerRef = nullptr;
+ });
+ }
+
+ return manager.forget();
+}
+
+static bool ValidateRequestArguments(const nsAString& name,
+ const LockOptions& options,
+ ErrorResult& aRv) {
+ if (name.Length() > 0 && name.First() == u'-') {
+ aRv.ThrowNotSupportedError("Names starting with `-` are reserved");
+ return false;
+ }
+ if (options.mSteal) {
+ if (options.mIfAvailable) {
+ aRv.ThrowNotSupportedError(
+ "`steal` and `ifAvailable` cannot be used together");
+ return false;
+ }
+ if (options.mMode != LockMode::Exclusive) {
+ aRv.ThrowNotSupportedError(
+ "`steal` is only supported for exclusive lock requests");
+ return false;
+ }
+ }
+ if (options.mSignal.WasPassed()) {
+ if (options.mSteal) {
+ aRv.ThrowNotSupportedError(
+ "`steal` and `signal` cannot be used together");
+ return false;
+ }
+ if (options.mIfAvailable) {
+ aRv.ThrowNotSupportedError(
+ "`ifAvailable` and `signal` cannot be used together");
+ return false;
+ }
+ if (options.mSignal.Value().Aborted()) {
+ AutoJSAPI jsapi;
+ if (!jsapi.Init(options.mSignal.Value().GetParentObject())) {
+ aRv.ThrowNotSupportedError("Signal's realm isn't active anymore.");
+ return false;
+ }
+
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> reason(cx);
+ options.mSignal.Value().GetReason(cx, &reason);
+ aRv.MightThrowJSException();
+ aRv.ThrowJSException(cx, reason);
+ return false;
+ }
+ }
+ return true;
+}
+
+already_AddRefed<Promise> LockManager::Request(const nsAString& aName,
+ LockGrantedCallback& aCallback,
+ ErrorResult& aRv) {
+ return Request(aName, LockOptions(), aCallback, aRv);
+};
+already_AddRefed<Promise> LockManager::Request(const nsAString& aName,
+ const LockOptions& aOptions,
+ LockGrantedCallback& aCallback,
+ ErrorResult& aRv) {
+ if (!mOwner->GetClientInfo()) {
+ // We do have nsPIDOMWindowInner::IsFullyActive for this kind of check,
+ // but this should be sufficient here as unloaded iframe is the only
+ // non-fully-active case that Web Locks should worry about (since it does
+ // not enter bfcache).
+ aRv.ThrowInvalidStateError(
+ "The document of the lock manager is not fully active");
+ return nullptr;
+ }
+
+ const StorageAccess access = mOwner->GetStorageAccess();
+ bool allowed =
+ access > StorageAccess::eDeny ||
+ (StaticPrefs::
+ privacy_partition_always_partition_third_party_non_cookie_storage() &&
+ ShouldPartitionStorage(access));
+ if (!allowed) {
+ // Step 4: If origin is an opaque origin, then return a promise rejected
+ // with a "SecurityError" DOMException.
+ // But per https://w3c.github.io/web-locks/#lock-managers this really means
+ // whether it has storage access.
+ aRv.ThrowSecurityError("request() is not allowed in this context");
+ return nullptr;
+ }
+
+ if (!mActor) {
+ aRv.ThrowNotSupportedError(
+ "Web Locks API is not enabled for this kind of document");
+ return nullptr;
+ }
+
+ if (!NS_IsMainThread() && !mWorkerRef) {
+ aRv.ThrowInvalidStateError("request() is not allowed at this point");
+ return nullptr;
+ }
+
+ if (!ValidateRequestArguments(aName, aOptions, aRv)) {
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ mActor->RequestLock({nsString(aName), promise, &aCallback}, aOptions);
+ return promise.forget();
+};
+
+already_AddRefed<Promise> LockManager::Query(ErrorResult& aRv) {
+ if (!mOwner->GetClientInfo()) {
+ aRv.ThrowInvalidStateError(
+ "The document of the lock manager is not fully active");
+ return nullptr;
+ }
+
+ if (mOwner->GetStorageAccess() <= StorageAccess::eDeny) {
+ aRv.ThrowSecurityError("query() is not allowed in this context");
+ return nullptr;
+ }
+
+ if (!mActor) {
+ aRv.ThrowNotSupportedError(
+ "Web Locks API is not enabled for this kind of document");
+ return nullptr;
+ }
+
+ if (!NS_IsMainThread() && !mWorkerRef) {
+ aRv.ThrowInvalidStateError("query() is not allowed at this point");
+ return nullptr;
+ }
+
+ RefPtr<Promise> promise = Promise::Create(mOwner, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+
+ mActor->SendQuery()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [promise](locks::LockManagerChild::QueryPromise::ResolveOrRejectValue&&
+ aResult) {
+ if (aResult.IsResolve()) {
+ promise->MaybeResolve(aResult.ResolveValue());
+ } else {
+ promise->MaybeRejectWithUnknownError("Query failed");
+ }
+ });
+ return promise.forget();
+};
+
+void LockManager::Shutdown() {
+ if (mActor) {
+ locks::PLockManagerChild::Send__delete__(mActor);
+ mActor = nullptr;
+ }
+}
+
+} // namespace mozilla::dom