#ifndef CONTENT_BROWSER_LOCKS_LOCK_MANAGER_H_
#define CONTENT_BROWSER_LOCKS_LOCK_MANAGER_H_
#include <algorithm>
#include <cstdint>
#include <list>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/uuid.h"
#include "content/common/content_export.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/content_client.h"
#include "mojo/public/cpp/bindings/associated_remote.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
#include "third_party/blink/public/mojom/locks/lock_manager.mojom.h"
#include "url/origin.h"
namespace content {
template <typename LockGroupIdType>
class CONTENT_EXPORT LockManager : public blink::mojom::LockManager {
public:
LockManager();
~LockManager() override;
LockManager(const LockManager&) = delete;
LockManager& operator=(const LockManager&) = delete;
void BindReceiver(LockGroupIdType lock_group_id,
mojo::PendingReceiver<blink::mojom::LockManager> receiver);
void RequestLock(const std::string& name,
blink::mojom::LockMode mode,
WaitMode wait,
mojo::PendingAssociatedRemote<blink::mojom::LockRequest>
request) override;
void ReleaseLock(LockGroupIdType lock_group_id, int64_t lock_id);
void QueryState(QueryStateCallback callback) override;
private:
class Lock;
class LockGroupState;
struct ReceiverState {
ReceiverState(std::string client_id, LockGroupIdType lock_group_id);
ReceiverState();
ReceiverState(const ReceiverState& other);
~ReceiverState();
std::string client_id;
LockGroupIdType lock_group_id;
};
int64_t NextLockId();
mojo::ReceiverSet<blink::mojom::LockManager, ReceiverState> receivers_;
int64_t next_lock_id_ = 0;
std::map<LockGroupIdType, LockGroupState> lock_groups_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<LockManager> weak_ptr_factory_{this};
};
namespace {
using blink::mojom::LockMode;
constexpr int64_t kPreemptiveLockId = 0;
template <typename LockGroupIdType>
class LockHandleImpl final : public blink::mojom::LockHandle {
public:
static mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::LockHandle> Create(
base::WeakPtr<LockManager<LockGroupIdType>> context,
LockGroupIdType lock_group_id,
int64_t lock_id,
mojo::PendingAssociatedRemote<blink::mojom::LockHandle>* remote) {
return mojo::MakeSelfOwnedAssociatedReceiver(
std::make_unique<LockHandleImpl<LockGroupIdType>>(
std::move(context), lock_group_id, lock_id),
remote->InitWithNewEndpointAndPassReceiver());
}
LockHandleImpl(base::WeakPtr<LockManager<LockGroupIdType>> context,
LockGroupIdType lock_group_id,
int64_t lock_id)
: context_(context), lock_group_id_(lock_group_id), lock_id_(lock_id) {}
LockHandleImpl(const LockHandleImpl&) = delete;
LockHandleImpl& operator=(const LockHandleImpl&) = delete;
~LockHandleImpl() override {
if (context_) {
context_->ReleaseLock(lock_group_id_, lock_id_);
}
}
void Close() { context_.reset(); }
private:
base::WeakPtr<LockManager<LockGroupIdType>> context_;
const LockGroupIdType lock_group_id_;
const int64_t lock_id_;
};
}
template <typename LockGroupIdType>
class LockManager<LockGroupIdType>::Lock {
public:
Lock(const std::string& name,
LockMode mode,
int64_t lock_id,
const ReceiverState& receiver_state,
mojo::AssociatedRemote<blink::mojom::LockRequest> request)
: name_(name),
mode_(mode),
lock_id_(lock_id),
client_id_(receiver_state.client_id),
request_(std::move(request)) {}
void Grant(LockManager<LockGroupIdType>* lock_manager,
LockGroupIdType lock_group_id) {
DCHECK(lock_manager);
DCHECK(!lock_manager_);
DCHECK(request_);
DCHECK(!handle_);
lock_manager_ = lock_manager->weak_ptr_factory_.GetWeakPtr();
mojo::PendingAssociatedRemote<blink::mojom::LockHandle> remote;
handle_ = LockHandleImpl<LockGroupIdType>::Create(
lock_manager_, lock_group_id, lock_id_, &remote);
request_->Granted(std::move(remote));
request_.reset();
}
void Break() {
DCHECK(!request_);
DCHECK(handle_);
DCHECK(lock_manager_);
LockHandleImpl<LockGroupIdType>* impl =
static_cast<LockHandleImpl<LockGroupIdType>*>(handle_->impl());
impl->Close();
handle_->Close();
}
const std::string& name() const { return name_; }
LockMode mode() const { return mode_; }
int64_t lock_id() const { return lock_id_; }
const std::string& client_id() const { return client_id_; }
bool is_granted() const { return !!handle_; }
private:
const std::string name_;
const LockMode mode_;
const int64_t lock_id_;
const std::string client_id_;
base::WeakPtr<LockManager<LockGroupIdType>> lock_manager_;
mojo::AssociatedRemote<blink::mojom::LockRequest> request_;
mojo::SelfOwnedAssociatedReceiverRef<blink::mojom::LockHandle> handle_;
};
template <typename LockGroupIdType>
LockManager<LockGroupIdType>::LockManager() = default;
template <typename LockGroupIdType>
LockManager<LockGroupIdType>::~LockManager() = default;
template <typename LockGroupIdType>
class LockManager<LockGroupIdType>::LockGroupState {
public:
explicit LockGroupState(LockManager<LockGroupIdType>* lock_manager)
: lock_manager_(lock_manager) {}
~LockGroupState() = default;
void BreakFront(std::list<Lock>& request_queue) {
Lock& broken_lock = request_queue.front();
lock_id_to_iterator_.erase(broken_lock.lock_id());
broken_lock.Break();
request_queue.pop_front();
}
void PreemptLock(int64_t lock_id,
const std::string& name,
LockMode mode,
mojo::AssociatedRemote<blink::mojom::LockRequest> request,
const ReceiverState& receiver_state) {
DCHECK_EQ(mode, LockMode::EXCLUSIVE);
std::list<Lock>& request_queue = resource_names_to_requests_[name];
while (!request_queue.empty() && request_queue.front().is_granted()) {
BreakFront(request_queue);
}
request_queue.emplace_front(name, mode, lock_id, receiver_state,
std::move(request));
auto it = request_queue.begin();
lock_id_to_iterator_.emplace(it->lock_id(), it);
it->Grant(lock_manager_, receiver_state.lock_group_id);
}
void AddRequest(int64_t lock_id,
const std::string& name,
LockMode mode,
mojo::AssociatedRemote<blink::mojom::LockRequest> request,
WaitMode wait,
const ReceiverState& receiver_state) {
DCHECK(wait != WaitMode::PREEMPT);
std::list<Lock>& request_queue = resource_names_to_requests_[name];
bool can_grant = request_queue.empty() ||
(request_queue.back().is_granted() &&
request_queue.back().mode() == LockMode::SHARED &&
mode == LockMode::SHARED);
if (!can_grant && wait == WaitMode::NO_WAIT) {
request->Failed();
return;
}
request_queue.emplace_back(name, mode, lock_id, receiver_state,
std::move(request));
auto it = --(request_queue.end());
lock_id_to_iterator_.emplace(it->lock_id(), it);
if (can_grant) {
it->Grant(lock_manager_, receiver_state.lock_group_id);
}
}
void EraseLock(int64_t lock_id, LockGroupIdType lock_group_id) {
auto iterator_it = lock_id_to_iterator_.find(lock_id);
if (iterator_it == lock_id_to_iterator_.end()) {
return;
}
auto lock_it = iterator_it->second;
lock_id_to_iterator_.erase(iterator_it);
auto request_it = resource_names_to_requests_.find(lock_it->name());
if (request_it == resource_names_to_requests_.end()) {
return;
}
std::list<Lock>& request_queue = request_it->second;
#if DCHECK_IS_ON()
auto check_it = request_queue.begin();
bool found = false;
for (; check_it != request_queue.end(); ++check_it) {
found = check_it == lock_it;
if (found) {
break;
}
}
DCHECK(found);
#endif
request_queue.erase(lock_it);
if (request_queue.empty()) {
resource_names_to_requests_.erase(request_it);
return;
}
if (request_queue.front().is_granted()) {
return;
}
if (request_queue.front().mode() == LockMode::EXCLUSIVE) {
request_queue.front().Grant(lock_manager_, lock_group_id);
} else {
DCHECK(request_queue.front().mode() == LockMode::SHARED);
for (auto grantee = request_queue.begin();
grantee != request_queue.end() &&
grantee->mode() == LockMode::SHARED;
++grantee) {
DCHECK(!grantee->is_granted());
grantee->Grant(lock_manager_, lock_group_id);
}
}
}
bool IsEmpty() const { return lock_id_to_iterator_.empty(); }
std::pair<std::vector<blink::mojom::LockInfoPtr>,
std::vector<blink::mojom::LockInfoPtr>>
Snapshot() const {
std::vector<blink::mojom::LockInfoPtr> requests;
std::vector<blink::mojom::LockInfoPtr> held;
for (const auto& name_queue_pair : resource_names_to_requests_) {
auto& request_queue = name_queue_pair.second;
if (request_queue.empty()) {
continue;
}
for (const auto& lock : request_queue) {
std::vector<blink::mojom::LockInfoPtr>& target =
lock.is_granted() ? held : requests;
target.emplace_back(std::in_place, lock.name(), lock.mode(),
lock.client_id());
}
}
return std::make_pair(std::move(requests), std::move(held));
}
private:
base::flat_map<std::string, std::list<Lock>> resource_names_to_requests_;
base::flat_map<int64_t, typename std::list<Lock>::iterator>
lock_id_to_iterator_;
const raw_ptr<LockManager<LockGroupIdType>> lock_manager_;
};
template <typename LockGroupIdType>
LockManager<LockGroupIdType>::ReceiverState::ReceiverState(
std::string client_id,
LockGroupIdType lock_group_id)
: client_id(std::move(client_id)), lock_group_id(lock_group_id) {}
template <typename LockGroupIdType>
LockManager<LockGroupIdType>::ReceiverState::ReceiverState() = default;
template <typename LockGroupIdType>
LockManager<LockGroupIdType>::ReceiverState::ReceiverState(
const ReceiverState& other) = default;
template <typename LockGroupIdType>
LockManager<LockGroupIdType>::ReceiverState::~ReceiverState() = default;
template <typename LockGroupIdType>
void LockManager<LockGroupIdType>::BindReceiver(
LockGroupIdType lock_group_id,
mojo::PendingReceiver<blink::mojom::LockManager> receiver) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const std::string client_id =
base::Uuid::GenerateRandomV4().AsLowercaseString();
receivers_.Add(this, std::move(receiver), {client_id, lock_group_id});
}
template <typename LockGroupIdType>
void LockManager<LockGroupIdType>::RequestLock(
const std::string& name,
LockMode mode,
WaitMode wait,
mojo::PendingAssociatedRemote<blink::mojom::LockRequest> request_remote) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (wait == WaitMode::PREEMPT && mode != LockMode::EXCLUSIVE) {
mojo::ReportBadMessage("Invalid option combination");
return;
}
if (name.length() > 0 && name[0] == '-') {
mojo::ReportBadMessage("Reserved name");
return;
}
mojo::AssociatedRemote<blink::mojom::LockRequest> request(
std::move(request_remote));
const auto& context = receivers_.current_context();
if (context.lock_group_id.is_null()) {
request->Failed();
return;
}
if (!base::Contains(lock_groups_, context.lock_group_id)) {
lock_groups_.emplace(context.lock_group_id, this);
}
int64_t lock_id = NextLockId();
request.set_disconnect_handler(
base::BindOnce(&LockManager<LockGroupIdType>::ReleaseLock,
base::Unretained(this), context.lock_group_id, lock_id));
LockGroupState& lock_group_state =
lock_groups_.find(context.lock_group_id)->second;
if (wait == WaitMode::PREEMPT) {
lock_group_state.PreemptLock(lock_id, name, mode, std::move(request),
context);
} else {
lock_group_state.AddRequest(lock_id, name, mode, std::move(request), wait,
context);
}
}
template <typename LockGroupIdType>
void LockManager<LockGroupIdType>::ReleaseLock(LockGroupIdType lock_group_id,
int64_t lock_id) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto lock_group_id_it = lock_groups_.find(lock_group_id);
if (lock_group_id_it == lock_groups_.end()) {
return;
}
LockGroupState& state = lock_group_id_it->second;
state.EraseLock(lock_id, lock_group_id);
if (state.IsEmpty()) {
lock_groups_.erase(lock_group_id);
}
}
template <typename LockGroupIdType>
void LockManager<LockGroupIdType>::QueryState(QueryStateCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
LockGroupIdType lock_group_id = receivers_.current_context().lock_group_id;
auto lock_group_id_it = lock_groups_.find(lock_group_id);
if (lock_group_id_it == lock_groups_.end()) {
std::move(callback).Run(std::vector<blink::mojom::LockInfoPtr>(),
std::vector<blink::mojom::LockInfoPtr>());
return;
}
DCHECK(!lock_group_id.is_null());
LockGroupState& state = lock_group_id_it->second;
auto requested_held_pair = state.Snapshot();
std::move(callback).Run(std::move(requested_held_pair.first),
std::move(requested_held_pair.second));
}
template <typename LockGroupIdType>
int64_t LockManager<LockGroupIdType>::NextLockId() {
int64_t lock_id = ++next_lock_id_;
DCHECK_GT(lock_id, kPreemptiveLockId);
return lock_id;
}
}
#endif