diff options
Diffstat (limited to 'dom/locks')
-rw-r--r-- | dom/locks/IPCUtils.h | 28 | ||||
-rw-r--r-- | dom/locks/Lock.cpp | 69 | ||||
-rw-r--r-- | dom/locks/Lock.h | 72 | ||||
-rw-r--r-- | dom/locks/LockManager.cpp | 234 | ||||
-rw-r--r-- | dom/locks/LockManager.h | 75 | ||||
-rw-r--r-- | dom/locks/LockManagerChild.cpp | 104 | ||||
-rw-r--r-- | dom/locks/LockManagerChild.h | 64 | ||||
-rw-r--r-- | dom/locks/LockManagerParent.cpp | 173 | ||||
-rw-r--r-- | dom/locks/LockManagerParent.h | 61 | ||||
-rw-r--r-- | dom/locks/LockRequestChild.cpp | 120 | ||||
-rw-r--r-- | dom/locks/LockRequestChild.h | 66 | ||||
-rw-r--r-- | dom/locks/LockRequestParent.cpp | 38 | ||||
-rw-r--r-- | dom/locks/LockRequestParent.h | 35 | ||||
-rw-r--r-- | dom/locks/PLockManager.ipdl | 38 | ||||
-rw-r--r-- | dom/locks/PLockRequest.ipdl | 27 | ||||
-rw-r--r-- | dom/locks/moz.build | 41 | ||||
-rw-r--r-- | dom/locks/test/file_strongworker.js | 5 | ||||
-rw-r--r-- | dom/locks/test/mochitest.toml | 5 | ||||
-rw-r--r-- | dom/locks/test/test_strongworker.html | 44 |
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> |