summaryrefslogtreecommitdiffstats
path: root/dom/locks
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/locks
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--dom/locks/IPCUtils.h28
-rw-r--r--dom/locks/Lock.cpp69
-rw-r--r--dom/locks/Lock.h72
-rw-r--r--dom/locks/LockManager.cpp234
-rw-r--r--dom/locks/LockManager.h75
-rw-r--r--dom/locks/LockManagerChild.cpp104
-rw-r--r--dom/locks/LockManagerChild.h64
-rw-r--r--dom/locks/LockManagerParent.cpp173
-rw-r--r--dom/locks/LockManagerParent.h61
-rw-r--r--dom/locks/LockRequestChild.cpp120
-rw-r--r--dom/locks/LockRequestChild.h66
-rw-r--r--dom/locks/LockRequestParent.cpp38
-rw-r--r--dom/locks/LockRequestParent.h35
-rw-r--r--dom/locks/PLockManager.ipdl38
-rw-r--r--dom/locks/PLockRequest.ipdl27
-rw-r--r--dom/locks/moz.build41
-rw-r--r--dom/locks/test/file_strongworker.js5
-rw-r--r--dom/locks/test/mochitest.toml5
-rw-r--r--dom/locks/test/test_strongworker.html44
19 files changed, 1299 insertions, 0 deletions
diff --git a/dom/locks/IPCUtils.h b/dom/locks/IPCUtils.h
new file mode 100644
index 0000000000..ef34ed1068
--- /dev/null
+++ b/dom/locks/IPCUtils.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DOM_LOCKS_IPCUTILS_H_
+#define DOM_LOCKS_IPCUTILS_H_
+
+#include "ipc/EnumSerializer.h"
+#include "ipc/IPCMessageUtilsSpecializations.h"
+#include "mozilla/dom/LockManagerBinding.h"
+
+namespace IPC {
+using LockMode = mozilla::dom::LockMode;
+template <>
+struct ParamTraits<LockMode>
+ : public ContiguousEnumSerializerInclusive<LockMode, LockMode::Shared,
+ LockMode::Exclusive> {};
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::LockInfo, mName, mMode,
+ mClientId);
+
+DEFINE_IPC_SERIALIZER_WITH_FIELDS(mozilla::dom::LockManagerSnapshot, mHeld,
+ mPending);
+} // namespace IPC
+
+#endif // DOM_LOCKS_IPCUTILS_H_
diff --git a/dom/locks/Lock.cpp b/dom/locks/Lock.cpp
new file mode 100644
index 0000000000..24d5456c54
--- /dev/null
+++ b/dom/locks/Lock.cpp
@@ -0,0 +1,69 @@
+/* -*- 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/Lock.h"
+#include "mozilla/dom/LockBinding.h"
+#include "mozilla/dom/LockManager.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/locks/LockRequestChild.h"
+
+namespace mozilla::dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Lock, mOwner, mWaitingPromise,
+ mReleasedPromise)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Lock)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Lock)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Lock)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+Lock::Lock(nsIGlobalObject* aGlobal,
+ const WeakPtr<locks::LockRequestChild>& aLockRequestChild,
+ const nsString& aName, LockMode aMode,
+ const RefPtr<Promise>& aReleasedPromise, ErrorResult& aRv)
+ : mOwner(aGlobal),
+ mLockRequestChild(aLockRequestChild),
+ mName(aName),
+ mMode(aMode),
+ mWaitingPromise(Promise::Create(aGlobal, aRv)),
+ mReleasedPromise(aReleasedPromise) {
+ MOZ_ASSERT(mLockRequestChild);
+ MOZ_ASSERT(aReleasedPromise);
+}
+
+JSObject* Lock::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
+ return Lock_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+void Lock::GetName(nsString& aRetVal) const { aRetVal = mName; }
+
+LockMode Lock::Mode() const { return mMode; }
+
+Promise& Lock::GetWaitingPromise() {
+ MOZ_ASSERT(mWaitingPromise);
+ return *mWaitingPromise;
+}
+
+void Lock::ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ if (mLockRequestChild) {
+ locks::PLockRequestChild::Send__delete__(mLockRequestChild, false);
+ mLockRequestChild = nullptr;
+ }
+ mReleasedPromise->MaybeResolve(aValue);
+}
+
+void Lock::RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) {
+ if (mLockRequestChild) {
+ locks::PLockRequestChild::Send__delete__(mLockRequestChild, false);
+ mLockRequestChild = nullptr;
+ }
+ mReleasedPromise->MaybeReject(aValue);
+}
+
+} // namespace mozilla::dom
diff --git a/dom/locks/Lock.h b/dom/locks/Lock.h
new file mode 100644
index 0000000000..ced0fc3785
--- /dev/null
+++ b/dom/locks/Lock.h
@@ -0,0 +1,72 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_Lock_h
+#define mozilla_dom_Lock_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/LockManagerBinding.h"
+#include "mozilla/dom/PromiseNativeHandler.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla::dom {
+
+class LockManager;
+namespace locks {
+class LockRequestChild;
+}
+
+class Lock final : public PromiseNativeHandler, public nsWrapperCache {
+ friend class LockManager;
+
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(Lock)
+
+ Lock(nsIGlobalObject* aGlobal,
+ const WeakPtr<locks::LockRequestChild>& aLockRequestChild,
+ const nsString& aName, LockMode aMode,
+ const RefPtr<Promise>& aReleasedPromise, ErrorResult& aRv);
+
+ protected:
+ ~Lock() = default;
+
+ public:
+ nsIGlobalObject* GetParentObject() const { return mOwner; };
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ void GetName(nsString& aRetVal) const;
+
+ LockMode Mode() const;
+
+ Promise& GetWaitingPromise();
+
+ // PromiseNativeHandler
+ virtual void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+ virtual void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue,
+ ErrorResult& aRv) override;
+
+ private:
+ nsCOMPtr<nsIGlobalObject> mOwner;
+ WeakPtr<locks::LockRequestChild> mLockRequestChild;
+
+ nsString mName;
+ LockMode mMode;
+ RefPtr<Promise> mWaitingPromise;
+ RefPtr<Promise> mReleasedPromise;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_Lock_h
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
diff --git a/dom/locks/LockManager.h b/dom/locks/LockManager.h
new file mode 100644
index 0000000000..19313ef6b8
--- /dev/null
+++ b/dom/locks/LockManager.h
@@ -0,0 +1,75 @@
+/* -*- 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/. */
+
+#ifndef mozilla_dom_LockManager_h
+#define mozilla_dom_LockManager_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Lock.h"
+#include "mozilla/dom/LockManagerBinding.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsHashKeys.h"
+#include "nsTHashMap.h"
+#include "nsTHashSet.h"
+#include "nsWrapperCache.h"
+
+class nsIGlobalObject;
+
+namespace mozilla::dom {
+
+class LockGrantedCallback;
+struct LockOptions;
+
+namespace locks {
+class LockManagerChild;
+}
+
+class LockManager final : public nsISupports, public nsWrapperCache {
+ public:
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(LockManager)
+
+ private:
+ explicit LockManager(nsIGlobalObject* aGlobal);
+
+ public:
+ static already_AddRefed<LockManager> Create(nsIGlobalObject& aGlobal);
+
+ nsIGlobalObject* GetParentObject() const { return mOwner; }
+
+ JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ already_AddRefed<Promise> Request(const nsAString& aName,
+ LockGrantedCallback& aCallback,
+ ErrorResult& aRv);
+ already_AddRefed<Promise> Request(const nsAString& aName,
+ const LockOptions& aOptions,
+ LockGrantedCallback& aCallback,
+ ErrorResult& aRv);
+
+ already_AddRefed<Promise> Query(ErrorResult& aRv);
+
+ void Shutdown();
+
+ private:
+ ~LockManager() = default;
+
+ nsCOMPtr<nsIGlobalObject> mOwner;
+ RefPtr<locks::LockManagerChild> mActor;
+
+ // Revokes itself and triggers LockManagerChild deletion on worker shutdown
+ // callback.
+ RefPtr<WeakWorkerRef> mWorkerRef;
+};
+
+} // namespace mozilla::dom
+
+#endif // mozilla_dom_LockManager_h
diff --git a/dom/locks/LockManagerChild.cpp b/dom/locks/LockManagerChild.cpp
new file mode 100644
index 0000000000..560fa21879
--- /dev/null
+++ b/dom/locks/LockManagerChild.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "LockManagerChild.h"
+#include "LockRequestChild.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/RemoteWorkerChild.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+
+namespace mozilla::dom::locks {
+
+LockManagerChild::LockManagerChild(nsIGlobalObject* aOwner) : mOwner(aOwner) {
+ if (!NS_IsMainThread()) {
+ mWorkerRef = IPCWorkerRef::Create(GetCurrentThreadWorkerPrivate(),
+ "LockManagerChild");
+ }
+}
+
+void LockManagerChild::NotifyBFCacheOnMainThread(nsPIDOMWindowInner* aInner,
+ bool aCreated) {
+ AssertIsOnMainThread();
+ if (!aInner) {
+ return;
+ }
+ if (aCreated) {
+ aInner->RemoveFromBFCacheSync();
+ }
+
+ uint32_t count = aInner->UpdateLockCount(aCreated);
+ // It's okay for WindowGlobalChild to not exist, as it should mean it already
+ // is destroyed and can't enter bfcache anyway.
+ if (WindowGlobalChild* child = aInner->GetWindowGlobalChild()) {
+ if (aCreated && count == 1) {
+ // The first lock is active.
+ child->BlockBFCacheFor(BFCacheStatus::ACTIVE_LOCK);
+ } else if (count == 0) {
+ child->UnblockBFCacheFor(BFCacheStatus::ACTIVE_LOCK);
+ }
+ }
+}
+
+class BFCacheNotifyLockRunnable final : public WorkerProxyToMainThreadRunnable {
+ public:
+ explicit BFCacheNotifyLockRunnable(bool aCreated) : mCreated(aCreated) {}
+
+ void RunOnMainThread(WorkerPrivate* aWorkerPrivate) override {
+ MOZ_ASSERT(aWorkerPrivate);
+ AssertIsOnMainThread();
+ if (aWorkerPrivate->IsDedicatedWorker()) {
+ LockManagerChild::NotifyBFCacheOnMainThread(
+ aWorkerPrivate->GetAncestorWindow(), mCreated);
+ return;
+ }
+ if (aWorkerPrivate->IsSharedWorker()) {
+ aWorkerPrivate->GetRemoteWorkerController()->NotifyLock(mCreated);
+ return;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unexpected worker type");
+ }
+
+ void RunBackOnWorkerThreadForCleanup(WorkerPrivate* aWorkerPrivate) override {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+ }
+
+ private:
+ bool mCreated;
+};
+
+void LockManagerChild::RequestLock(const LockRequest& aRequest,
+ const LockOptions& aOptions) {
+ auto requestActor = MakeRefPtr<LockRequestChild>(aRequest, aOptions.mSignal);
+ requestActor->MaybeSetWorkerRef();
+ SendPLockRequestConstructor(
+ requestActor, IPCLockRequest(nsString(aRequest.mName), aOptions.mMode,
+ aOptions.mIfAvailable, aOptions.mSteal));
+ NotifyToWindow(true);
+}
+
+void LockManagerChild::NotifyRequestDestroy() const { NotifyToWindow(false); }
+
+void LockManagerChild::NotifyToWindow(bool aCreated) const {
+ if (NS_IsMainThread()) {
+ NotifyBFCacheOnMainThread(GetParentObject()->GetAsInnerWindow(), aCreated);
+ return;
+ }
+
+ WorkerPrivate* wp = GetCurrentThreadWorkerPrivate();
+ if (wp->IsDedicatedWorker() || wp->IsSharedWorker()) {
+ RefPtr<BFCacheNotifyLockRunnable> runnable =
+ new BFCacheNotifyLockRunnable(aCreated);
+
+ runnable->Dispatch(wp);
+ }
+};
+
+} // namespace mozilla::dom::locks
diff --git a/dom/locks/LockManagerChild.h b/dom/locks/LockManagerChild.h
new file mode 100644
index 0000000000..79ae8390e3
--- /dev/null
+++ b/dom/locks/LockManagerChild.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DOM_LOCKS_LOCKMANAGERCHILD_H_
+#define DOM_LOCKS_LOCKMANAGERCHILD_H_
+
+#include "mozilla/dom/locks/PLockManagerChild.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "nsIUUIDGenerator.h"
+
+namespace mozilla::dom::locks {
+
+struct LockRequest;
+
+class LockManagerChild final : public PLockManagerChild {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(LockManagerChild)
+
+ static void NotifyBFCacheOnMainThread(nsPIDOMWindowInner* aInner,
+ bool aCreated);
+
+ explicit LockManagerChild(nsIGlobalObject* aOwner);
+
+ nsIGlobalObject* GetParentObject() const { return mOwner; };
+
+ void RequestLock(const LockRequest& aRequest, const LockOptions& aOptions);
+
+ void NotifyRequestDestroy() const;
+
+ void NotifyToWindow(bool aCreated) const;
+
+ private:
+ ~LockManagerChild() = default;
+
+ nsCOMPtr<nsIGlobalObject> mOwner;
+
+ // This WorkerRef is deleted by destructor.
+ //
+ // Here we want to make sure the IPC layer deletes the actor before allowing
+ // WorkerPrivate deletion, since managed LockRequestChild holds a reference to
+ // global object (currently via mRequest member variable) and thus deleting
+ // the worker first causes an assertion failure. Having this ensures that all
+ // LockRequestChild instances must first have been deleted.
+ //
+ // (Because each LockRequestChild's ActorLifecycleProxy will hold
+ // a strong reference to its manager LockManagerChild's ActorLifecycleProxy,
+ // which means that the LockManagerChild will only be destroyed once all
+ // LockRequestChild instances have been destroyed. At that point, all
+ // references to the global should have been dropped, and then the
+ // LockManagerChild IPCWorkerRef will be dropped and the worker will be able
+ // to transition to the Killing state.)
+ //
+ // ActorDestroy can't release this since it does not guarantee to destruct and
+ // GC the managees (LockRequestChild) immediately.
+ RefPtr<IPCWorkerRef> mWorkerRef;
+};
+
+} // namespace mozilla::dom::locks
+
+#endif // DOM_LOCKS_LOCKMANAGERCHILD_H_
diff --git a/dom/locks/LockManagerParent.cpp b/dom/locks/LockManagerParent.cpp
new file mode 100644
index 0000000000..4af7fe2129
--- /dev/null
+++ b/dom/locks/LockManagerParent.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "LockManagerParent.h"
+#include "LockRequestParent.h"
+
+#include "mozilla/PrincipalHashKey.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/dom/locks/PLockManager.h"
+#include "mozilla/media/MediaUtils.h"
+#include "nsIDUtils.h"
+#include "nsTHashMap.h"
+
+namespace mozilla::dom::locks {
+
+static StaticAutoPtr<nsTHashMap<PrincipalHashKey, WeakPtr<ManagedLocks>>>
+ sManagedLocksMap;
+
+using IPCResult = mozilla::ipc::IPCResult;
+
+LockManagerParent::LockManagerParent(NotNull<nsIPrincipal*> aPrincipal,
+ const nsID& aClientId)
+ : mClientId(NSID_TrimBracketsUTF16(aClientId)), mPrincipal(aPrincipal) {
+ if (!sManagedLocksMap) {
+ sManagedLocksMap =
+ new nsTHashMap<PrincipalHashKey, WeakPtr<ManagedLocks>>();
+ } else {
+ mManagedLocks = sManagedLocksMap->Get(aPrincipal);
+ }
+
+ if (!mManagedLocks) {
+ mManagedLocks = new ManagedLocks();
+ sManagedLocksMap->LookupOrInsert(aPrincipal, mManagedLocks);
+ }
+}
+
+void LockManagerParent::ActorDestroy(ActorDestroyReason aWhy) {
+ if (!mManagedLocks) {
+ return;
+ }
+
+ nsTArray<nsString> affectedResourceNames;
+
+ mManagedLocks->mHeldLocks.RemoveElementsBy(
+ [this, &affectedResourceNames](const RefPtr<LockRequestParent>& request) {
+ bool equals = request->Manager() == this;
+ if (equals) {
+ affectedResourceNames.AppendElement(request->Data().name());
+ }
+ return equals;
+ });
+
+ for (auto& queue : mManagedLocks->mQueueMap) {
+ queue.GetModifiableData()->RemoveElementsBy(
+ [this, &name = queue.GetKey(),
+ &affectedResourceNames](const RefPtr<LockRequestParent>& request) {
+ bool equals = request->Manager() == this;
+ if (equals) {
+ affectedResourceNames.AppendElement(name);
+ }
+ return equals;
+ });
+ }
+
+ for (const nsString& name : affectedResourceNames) {
+ if (auto queue = mManagedLocks->mQueueMap.Lookup(name)) {
+ ProcessRequestQueue(queue.Data());
+ }
+ }
+
+ mManagedLocks = nullptr;
+ // We just decreased the refcount and potentially deleted it, so check whether
+ // the weak pointer still points to anything and remove the entry if not.
+ if (!sManagedLocksMap->Get(mPrincipal)) {
+ sManagedLocksMap->Remove(mPrincipal);
+ }
+}
+
+void LockManagerParent::ProcessRequestQueue(
+ nsTArray<RefPtr<LockRequestParent>>& aQueue) {
+ while (aQueue.Length()) {
+ RefPtr<LockRequestParent> first = aQueue[0];
+ if (!IsGrantableRequest(first->Data())) {
+ break;
+ }
+ aQueue.RemoveElementAt(0);
+ mManagedLocks->mHeldLocks.AppendElement(first);
+ Unused << NS_WARN_IF(!first->SendResolve(first->Data().lockMode(), true));
+ }
+}
+
+bool LockManagerParent::IsGrantableRequest(const IPCLockRequest& aRequest) {
+ for (const auto& held : mManagedLocks->mHeldLocks) {
+ if (held->Data().name() == aRequest.name()) {
+ if (aRequest.lockMode() == LockMode::Exclusive) {
+ return false;
+ }
+ MOZ_ASSERT(aRequest.lockMode() == LockMode::Shared);
+ if (held->Data().lockMode() == LockMode::Exclusive) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+IPCResult LockManagerParent::RecvQuery(QueryResolver&& aResolver) {
+ LockManagerSnapshot snapshot;
+ snapshot.mHeld.Construct();
+ snapshot.mPending.Construct();
+ for (const auto& queueMapEntry : mManagedLocks->mQueueMap) {
+ for (const RefPtr<LockRequestParent>& request : queueMapEntry.GetData()) {
+ LockInfo info;
+ info.mMode.Construct(request->Data().lockMode());
+ info.mName.Construct(request->Data().name());
+ info.mClientId.Construct(
+ static_cast<LockManagerParent*>(request->Manager())->mClientId);
+ if (!snapshot.mPending.Value().AppendElement(info, mozilla::fallible)) {
+ return IPC_FAIL(this, "Out of memory");
+ };
+ }
+ }
+ for (const RefPtr<LockRequestParent>& request : mManagedLocks->mHeldLocks) {
+ LockInfo info;
+ info.mMode.Construct(request->Data().lockMode());
+ info.mName.Construct(request->Data().name());
+ info.mClientId.Construct(
+ static_cast<LockManagerParent*>(request->Manager())->mClientId);
+ if (!snapshot.mHeld.Value().AppendElement(info, mozilla::fallible)) {
+ return IPC_FAIL(this, "Out of memory");
+ };
+ }
+ aResolver(snapshot);
+ return IPC_OK();
+};
+
+already_AddRefed<PLockRequestParent> LockManagerParent::AllocPLockRequestParent(
+ const IPCLockRequest& aRequest) {
+ return MakeAndAddRef<LockRequestParent>(aRequest);
+}
+
+IPCResult LockManagerParent::RecvPLockRequestConstructor(
+ PLockRequestParent* aActor, const IPCLockRequest& aRequest) {
+ RefPtr<LockRequestParent> actor = static_cast<LockRequestParent*>(aActor);
+ nsTArray<RefPtr<LockRequestParent>>& queue =
+ mManagedLocks->mQueueMap.LookupOrInsert(aRequest.name());
+ if (aRequest.steal()) {
+ mManagedLocks->mHeldLocks.RemoveElementsBy(
+ [&aRequest](const RefPtr<LockRequestParent>& aHeld) {
+ if (aHeld->Data().name() == aRequest.name()) {
+ Unused << NS_WARN_IF(
+ !PLockRequestParent::Send__delete__(aHeld, true));
+ return true;
+ }
+ return false;
+ });
+ queue.InsertElementAt(0, actor);
+ } else if (aRequest.ifAvailable() &&
+ (!queue.IsEmpty() || !IsGrantableRequest(actor->Data()))) {
+ Unused << NS_WARN_IF(!aActor->SendResolve(aRequest.lockMode(), false));
+ return IPC_OK();
+ } else {
+ queue.AppendElement(actor);
+ }
+ ProcessRequestQueue(queue);
+ return IPC_OK();
+}
+
+} // namespace mozilla::dom::locks
diff --git a/dom/locks/LockManagerParent.h b/dom/locks/LockManagerParent.h
new file mode 100644
index 0000000000..0e0f4affb2
--- /dev/null
+++ b/dom/locks/LockManagerParent.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DOM_LOCKS_LOCKMANAGERPARENT_H_
+#define DOM_LOCKS_LOCKMANAGERPARENT_H_
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/dom/locks/PLockManagerParent.h"
+#include "mozilla/dom/locks/LockRequestParent.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+#include "mozilla/WeakPtr.h"
+
+namespace mozilla::dom::locks {
+
+class ManagedLocks : public SupportsWeakPtr {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(ManagedLocks)
+
+ nsTArray<RefPtr<LockRequestParent>> mHeldLocks;
+ nsTHashMap<nsStringHashKey, nsTArray<RefPtr<LockRequestParent>>> mQueueMap;
+
+ private:
+ ~ManagedLocks() = default;
+};
+
+class LockManagerParent final : public PLockManagerParent {
+ using IPCResult = mozilla::ipc::IPCResult;
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(LockManagerParent)
+
+ LockManagerParent(NotNull<nsIPrincipal*> aPrincipal, const nsID& aClientId);
+
+ void ProcessRequestQueue(nsTArray<RefPtr<LockRequestParent>>& aQueue);
+ bool IsGrantableRequest(const IPCLockRequest& aRequest);
+
+ IPCResult RecvQuery(QueryResolver&& aResolver);
+
+ already_AddRefed<PLockRequestParent> AllocPLockRequestParent(
+ const IPCLockRequest& aRequest);
+ IPCResult RecvPLockRequestConstructor(PLockRequestParent* aActor,
+ const IPCLockRequest& aRequest) final;
+
+ ManagedLocks& Locks() { return *mManagedLocks; }
+
+ private:
+ ~LockManagerParent() = default;
+
+ void ActorDestroy(ActorDestroyReason aWhy) final;
+
+ RefPtr<ManagedLocks> mManagedLocks;
+ nsString mClientId;
+ NotNull<nsCOMPtr<nsIPrincipal>> mPrincipal;
+};
+
+} // namespace mozilla::dom::locks
+
+#endif // DOM_LOCKS_LOCKMANAGERPARENT_H_
diff --git a/dom/locks/LockRequestChild.cpp b/dom/locks/LockRequestChild.cpp
new file mode 100644
index 0000000000..51731c5eed
--- /dev/null
+++ b/dom/locks/LockRequestChild.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "LockManagerChild.h"
+#include "LockRequestChild.h"
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/WorkerPrivate.h"
+
+namespace mozilla::dom::locks {
+
+using IPCResult = mozilla::ipc::IPCResult;
+
+NS_IMPL_ISUPPORTS(LockRequestChild, nsISupports)
+
+MOZ_CAN_RUN_SCRIPT static void RunCallbackAndSettlePromise(
+ LockGrantedCallback& aCallback, mozilla::dom::Lock* lock,
+ Promise& aPromise) {
+ ErrorResult rv;
+ if (RefPtr<Promise> result = aCallback.Call(
+ lock, rv, nullptr, CallbackObject::eRethrowExceptions)) {
+ aPromise.MaybeResolve(result);
+ } else if (rv.Failed() && !rv.IsUncatchableException()) {
+ aPromise.MaybeReject(std::move(rv));
+ return;
+ } else {
+ aPromise.MaybeResolveWithUndefined();
+ }
+ // This is required even with no failure. IgnoredErrorResult is not an option
+ // since MaybeReject does not accept it.
+ rv.WouldReportJSException();
+ if (NS_WARN_IF(rv.IsUncatchableException())) {
+ rv.SuppressException(); // XXX: Why does this happen anyway?
+ }
+ MOZ_ASSERT(!rv.Failed());
+}
+
+LockRequestChild::LockRequestChild(
+ const LockRequest& aRequest,
+ const Optional<OwningNonNull<AbortSignal>>& aSignal)
+ : mRequest(aRequest) {
+ if (aSignal.WasPassed()) {
+ Follow(&aSignal.Value());
+ }
+}
+
+void LockRequestChild::MaybeSetWorkerRef() {
+ if (!NS_IsMainThread()) {
+ mWorkerRef = StrongWorkerRef::Create(
+ GetCurrentThreadWorkerPrivate(), "LockManager",
+ [self = RefPtr(this)]() { self->mWorkerRef = nullptr; });
+ }
+}
+
+void LockRequestChild::ActorDestroy(ActorDestroyReason aReason) {
+ CastedManager()->NotifyRequestDestroy();
+}
+
+IPCResult LockRequestChild::RecvResolve(const LockMode& aLockMode,
+ bool aIsAvailable) {
+ Unfollow();
+
+ RefPtr<Lock> lock;
+ RefPtr<Promise> promise;
+ if (aIsAvailable) {
+ IgnoredErrorResult err;
+ lock = new Lock(CastedManager()->GetParentObject(), this, mRequest.mName,
+ aLockMode, mRequest.mPromise, err);
+ if (MOZ_UNLIKELY(err.Failed())) {
+ mRequest.mPromise->MaybeRejectWithUnknownError(
+ "Failed to allocate a lock");
+ return IPC_OK();
+ }
+ lock->GetWaitingPromise().AppendNativeHandler(lock);
+ promise = &lock->GetWaitingPromise();
+ } else {
+ // We are in `ifAvailable: true` mode and the lock is not available.
+ // There is no waitingPromise since there is no lock, so settle the promise
+ // from the request instead.
+ // This matches "If ifAvailable is true and request is not grantable" step.
+ promise = mRequest.mPromise;
+ }
+
+ // XXX(krosylight): MOZ_KnownLive shouldn't be needed here, mRequest is const
+ RunCallbackAndSettlePromise(MOZ_KnownLive(*mRequest.mCallback), lock,
+ *promise);
+ return IPC_OK();
+}
+
+IPCResult LockRequestChild::Recv__delete__(bool aAborted) {
+ MOZ_ASSERT(aAborted, "__delete__ is currently only for abort");
+ Unfollow();
+ mRequest.mPromise->MaybeRejectWithAbortError("The lock request is aborted");
+ return IPC_OK();
+}
+
+void LockRequestChild::RunAbortAlgorithm() {
+ AutoJSAPI jsapi;
+ if (NS_WARN_IF(
+ !jsapi.Init(static_cast<AbortSignal*>(Signal())->GetOwnerGlobal()))) {
+ mRequest.mPromise->MaybeRejectWithAbortError("The lock request is aborted");
+ } else {
+ JSContext* cx = jsapi.cx();
+ JS::Rooted<JS::Value> reason(cx);
+ Signal()->GetReason(cx, &reason);
+ mRequest.mPromise->MaybeReject(reason);
+ }
+
+ Unfollow();
+ Send__delete__(this, true);
+}
+
+inline LockManagerChild* LockRequestChild::CastedManager() const {
+ return static_cast<LockManagerChild*>(Manager());
+};
+
+} // namespace mozilla::dom::locks
diff --git a/dom/locks/LockRequestChild.h b/dom/locks/LockRequestChild.h
new file mode 100644
index 0000000000..8ea3b5f6e5
--- /dev/null
+++ b/dom/locks/LockRequestChild.h
@@ -0,0 +1,66 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DOM_LOCKS_LOCKREQUESTCHILD_H_
+#define DOM_LOCKS_LOCKREQUESTCHILD_H_
+
+#include "LockManagerChild.h"
+#include "mozilla/dom/locks/PLockRequestChild.h"
+#include "mozilla/dom/Lock.h"
+#include "mozilla/dom/WorkerRef.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom::locks {
+
+struct LockRequest {
+ nsString mName;
+ RefPtr<Promise> mPromise;
+ RefPtr<LockGrantedCallback> mCallback;
+};
+
+class LockRequestChild final : public PLockRequestChild,
+ public AbortFollower,
+ public SupportsWeakPtr {
+ using IPCResult = mozilla::ipc::IPCResult;
+
+ NS_DECL_ISUPPORTS
+
+ public:
+ explicit LockRequestChild(
+ const LockRequest& aRequest,
+ const Optional<OwningNonNull<AbortSignal>>& aSignal);
+
+ void MaybeSetWorkerRef();
+
+ // TODO: Use MOZ_CAN_RUN_SCRIPT when it gains IPDL support (bug 1539864)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY IPCResult RecvResolve(const LockMode& aLockMode,
+ bool aIsAvailable);
+ IPCResult Recv__delete__(bool aAborted);
+
+ void ActorDestroy(ActorDestroyReason aReason) final;
+
+ void RunAbortAlgorithm() final;
+
+ private:
+ ~LockRequestChild() = default;
+
+ LockManagerChild* CastedManager() const;
+
+ const LockRequest mRequest;
+
+ // This prevents the worker from being GC'ed when the caller is waiting to
+ // acquire the lock and when the lock is held.
+ //
+ // The StrongWorkerRef is dropped immediately in the shutdown notification
+ // callback, and thus does not ensure any cleanup before the worker advances
+ // to the Killing state. That is ensured instead by
+ // LockManagerChild::mWorkerRef, see also the details there.
+ RefPtr<StrongWorkerRef> mWorkerRef;
+};
+
+} // namespace mozilla::dom::locks
+
+#endif // DOM_LOCKS_LOCKREQUESTCHILD_H_
diff --git a/dom/locks/LockRequestParent.cpp b/dom/locks/LockRequestParent.cpp
new file mode 100644
index 0000000000..60afa0679a
--- /dev/null
+++ b/dom/locks/LockRequestParent.cpp
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "LockManagerParent.h"
+#include "LockRequestParent.h"
+
+#include "mozilla/dom/Promise.h"
+
+namespace mozilla::dom::locks {
+
+mozilla::ipc::IPCResult LockRequestParent::Recv__delete__(bool aAborted) {
+ RefPtr<LockManagerParent> manager =
+ static_cast<LockManagerParent*>(Manager());
+ ManagedLocks& managed = manager->Locks();
+
+ DebugOnly<bool> unheld = managed.mHeldLocks.RemoveElement(this);
+ MOZ_ASSERT_IF(!aAborted, unheld);
+
+ if (auto queue = managed.mQueueMap.Lookup(mRequest.name())) {
+ if (aAborted) {
+ DebugOnly<bool> dequeued = queue.Data().RemoveElement(this);
+ MOZ_ASSERT_IF(!unheld, dequeued);
+ }
+ manager->ProcessRequestQueue(queue.Data());
+ if (queue.Data().IsEmpty()) {
+ // Remove if empty, to prevent the queue map from growing forever
+ queue.Remove();
+ }
+ }
+ // or else, the queue is removed during the previous lock release (since
+ // multiple held locks are possible with `shared: true`)
+ return IPC_OK();
+}
+
+} // namespace mozilla::dom::locks
diff --git a/dom/locks/LockRequestParent.h b/dom/locks/LockRequestParent.h
new file mode 100644
index 0000000000..cdb4fc1127
--- /dev/null
+++ b/dom/locks/LockRequestParent.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef DOM_LOCKS_LOCKREQUESTPARENT_H_
+#define DOM_LOCKS_LOCKREQUESTPARENT_H_
+
+#include "mozilla/dom/locks/PLockManager.h"
+#include "mozilla/dom/locks/PLockRequestParent.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom::locks {
+
+class LockRequestParent final : public PLockRequestParent {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(LockRequestParent)
+
+ explicit LockRequestParent(const IPCLockRequest& aRequest)
+ : mRequest(aRequest){};
+
+ const IPCLockRequest& Data() { return mRequest; }
+
+ mozilla::ipc::IPCResult Recv__delete__(bool aAborted);
+
+ private:
+ ~LockRequestParent() = default;
+
+ IPCLockRequest mRequest;
+};
+
+} // namespace mozilla::dom::locks
+
+#endif // DOM_LOCKS_LOCKREQUESTPARENT_H_
diff --git a/dom/locks/PLockManager.ipdl b/dom/locks/PLockManager.ipdl
new file mode 100644
index 0000000000..fdcf7101a9
--- /dev/null
+++ b/dom/locks/PLockManager.ipdl
@@ -0,0 +1,38 @@
+/* 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 protocol PBackground;
+include protocol PLockRequest;
+
+include "mozilla/dom/locks/IPCUtils.h";
+
+using mozilla::dom::LockMode from "mozilla/dom/LockManagerBinding.h";
+using mozilla::dom::LockManagerSnapshot from "mozilla/dom/LockManagerBinding.h";
+
+namespace mozilla {
+namespace dom {
+namespace locks {
+
+struct IPCLockRequest {
+ nsString name;
+ LockMode lockMode;
+ bool ifAvailable;
+ bool steal;
+};
+
+protocol PLockManager {
+ manager PBackground;
+ manages PLockRequest;
+
+ parent:
+ async Query() returns (LockManagerSnapshot snapshot);
+
+ async PLockRequest(IPCLockRequest aRequest);
+
+ async __delete__();
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/locks/PLockRequest.ipdl b/dom/locks/PLockRequest.ipdl
new file mode 100644
index 0000000000..e7b663eeaa
--- /dev/null
+++ b/dom/locks/PLockRequest.ipdl
@@ -0,0 +1,27 @@
+/* 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 protocol PLockManager;
+
+include "mozilla/dom/locks/IPCUtils.h";
+
+using mozilla::dom::LockMode from "mozilla/dom/LockManagerBinding.h";
+
+namespace mozilla {
+namespace dom {
+namespace locks {
+
+protocol PLockRequest {
+ manager PLockManager;
+
+ child:
+ async Resolve(LockMode aMode, bool aIsAvailable);
+
+ both:
+ async __delete__(bool aAborted);
+};
+
+} // namespace cache
+} // namespace dom
+} // namespace mozilla
diff --git a/dom/locks/moz.build b/dom/locks/moz.build
new file mode 100644
index 0000000000..f6302e99e2
--- /dev/null
+++ b/dom/locks/moz.build
@@ -0,0 +1,41 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DOM: Core & HTML")
+
+MOCHITEST_MANIFESTS += ["test/mochitest.toml"]
+
+EXPORTS.mozilla.dom += [
+ "Lock.h",
+ "LockManager.h",
+]
+
+EXPORTS.mozilla.dom.locks += [
+ "IPCUtils.h",
+ "LockManagerChild.h",
+ "LockManagerParent.h",
+ "LockRequestChild.h",
+ "LockRequestParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "Lock.cpp",
+ "LockManager.cpp",
+ "LockManagerChild.cpp",
+ "LockManagerParent.cpp",
+ "LockRequestChild.cpp",
+ "LockRequestParent.cpp",
+]
+
+IPDL_SOURCES += [
+ "PLockManager.ipdl",
+ "PLockRequest.ipdl",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
diff --git a/dom/locks/test/file_strongworker.js b/dom/locks/test/file_strongworker.js
new file mode 100644
index 0000000000..36871ad31a
--- /dev/null
+++ b/dom/locks/test/file_strongworker.js
@@ -0,0 +1,5 @@
+navigator.locks.request("exclusive", () => {
+ const channel = new BroadcastChannel("strongworker");
+ channel.postMessage("lock acquired");
+});
+postMessage("onload");
diff --git a/dom/locks/test/mochitest.toml b/dom/locks/test/mochitest.toml
new file mode 100644
index 0000000000..f0dc745c0a
--- /dev/null
+++ b/dom/locks/test/mochitest.toml
@@ -0,0 +1,5 @@
+[DEFAULT]
+scheme = "https"
+
+["test_strongworker.html"]
+support-files = ["file_strongworker.js"]
diff --git a/dom/locks/test/test_strongworker.html b/dom/locks/test/test_strongworker.html
new file mode 100644
index 0000000000..9c6905919c
--- /dev/null
+++ b/dom/locks/test/test_strongworker.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+
+<script>
+ SimpleTest.waitForExplicitFinish();
+
+ async function run() {
+ // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5)
+ // Acquire storage access permission here so that the BroadcastChannel used to
+ // communicate with the opened windows works in xorigin tests. Otherwise,
+ // the iframe containing this page is isolated from first-party storage access,
+ // which isolates BroadcastChannel communication.
+ if (isXOrigin) {
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ await SpecialPowers.addPermission(
+ "storageAccessAPI",
+ true,
+ window.location.href
+ );
+ await SpecialPowers.wrap(document).requestStorageAccess();
+ }
+ const channel = new BroadcastChannel("strongworker");
+ await navigator.locks.request("exclusive", async () => {
+ await new Promise(resolve => {
+ let worker = new Worker("./file_strongworker.js");
+ worker.onmessage = resolve; // onload
+ });
+ const query = await navigator.locks.query();
+ is(query.pending.length, 1, "Pending request exists");
+
+ // Garbage collect the worker
+ SpecialPowers.DOMWindowUtils.garbageCollect();
+ });
+
+ channel.onmessage = async event => {
+ const query = await navigator.locks.query();
+ is(query.pending.length, 0, "No pending request");
+ SimpleTest.finish();
+ };
+ }
+ run();
+</script>