blob: 97afc849a0e48048322d28b167719f80413c195c [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/web_applications/locks/web_app_lock_manager.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/location.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/task_runner.h"
#include "base/values.h"
#include "chrome/browser/web_applications/locks/all_apps_lock.h"
#include "chrome/browser/web_applications/locks/app_lock.h"
#include "chrome/browser/web_applications/locks/lock.h"
#include "chrome/browser/web_applications/locks/noop_lock.h"
#include "chrome/browser/web_applications/locks/shared_web_contents_lock.h"
#include "chrome/browser/web_applications/locks/shared_web_contents_with_app_lock.h"
#include "chrome/browser/web_applications/web_app_command_manager.h"
#include "chrome/browser/web_applications/web_app_id.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "components/services/storage/indexed_db/locks/partitioned_lock_id.h"
#include "components/services/storage/indexed_db/locks/partitioned_lock_manager.h"
namespace web_app {
namespace {
enum class LockPartition {
kStatic = 0,
kApp = 1,
kMaxValue = kApp,
};
enum KeysOnStaticPartition {
kAllApps = 0,
kBackgroundWebContents = 1,
kMaxValue = kBackgroundWebContents,
};
const char* KeysOnStaticPartitionToString(KeysOnStaticPartition key) {
switch (key) {
case KeysOnStaticPartition::kAllApps:
return "AllApps";
case KeysOnStaticPartition::kBackgroundWebContents:
return "BackgroundWebContents";
}
}
content::PartitionedLockManager::PartitionedLockRequest GetAllAppsLock(
content::PartitionedLockManager::LockType type) {
return content::PartitionedLockManager::PartitionedLockRequest(
content::PartitionedLockId(
{static_cast<int>(LockPartition::kStatic),
base::NumberToString(KeysOnStaticPartition::kAllApps)}),
type);
}
content::PartitionedLockManager::PartitionedLockRequest
GetSharedWebContentsLock() {
return content::PartitionedLockManager::PartitionedLockRequest(
content::PartitionedLockId(
{static_cast<int>(LockPartition::kStatic),
base::NumberToString(
KeysOnStaticPartition::kBackgroundWebContents)}),
content::PartitionedLockManager::LockType::kExclusive);
}
std::vector<content::PartitionedLockManager::PartitionedLockRequest>
GetExclusiveAppIdLocks(const base::flat_set<AppId>& app_ids) {
std::vector<content::PartitionedLockManager::PartitionedLockRequest>
lock_requests;
for (const AppId& app_id : app_ids) {
lock_requests.emplace_back(
content::PartitionedLockId(
{static_cast<int>(LockPartition::kApp), app_id}),
content::PartitionedLockManager::LockType::kExclusive);
}
return lock_requests;
}
std::vector<content::PartitionedLockManager::PartitionedLockRequest>
GetLockRequestsForLock(const LockDescription& lock) {
std::vector<content::PartitionedLockManager::PartitionedLockRequest> requests;
switch (lock.type()) {
case LockDescription::Type::kNoOp:
return {};
case LockDescription::Type::kApp:
requests = GetExclusiveAppIdLocks(lock.app_ids());
requests.push_back(
GetAllAppsLock(content::PartitionedLockManager::LockType::kShared));
return requests;
case LockDescription::Type::kAllAppsLock:
return {GetAllAppsLock(
content::PartitionedLockManager::LockType::kExclusive)};
case LockDescription::Type::kAppAndWebContents:
requests = GetExclusiveAppIdLocks(lock.app_ids());
requests.push_back(
GetAllAppsLock(content::PartitionedLockManager::LockType::kShared));
requests.push_back(GetSharedWebContentsLock());
return requests;
case LockDescription::Type::kBackgroundWebContents:
return {GetSharedWebContentsLock()};
}
}
#if DCHECK_IS_ON()
void LogLockRequest(
const LockDescription& description,
const base::Location& location,
const std::vector<content::PartitionedLockManager::PartitionedLockRequest>&
requests,
const content::PartitionedLockManager& lock_manager) {
DVLOG(1) << "Requesting or upgrading to lock " << description
<< " for location " << location.ToString();
std::vector<base::Location> locations =
lock_manager.GetHeldAndQueuedLockLocations(requests);
if (!locations.empty()) {
DVLOG(1) << "Lock currently held (or queued to be held) by:";
for (const auto& held_location : locations) {
DVLOG(1) << " " << held_location.ToString();
}
}
}
#endif
} // namespace
WebAppLockManager::WebAppLockManager(WebAppProvider& provider)
: provider_(provider) {}
WebAppLockManager::~WebAppLockManager() = default;
bool WebAppLockManager::IsSharedWebContentsLockFree() {
return lock_manager_.TestLock(GetSharedWebContentsLock()) ==
content::PartitionedLockManager::TestLockResult::kFree;
}
template <>
void WebAppLockManager::AcquireLock(
const LockDescription& lock_description,
base::OnceCallback<void(std::unique_ptr<NoopLock>)> on_lock_acquired,
const base::Location& location) {
CHECK(lock_description.type() == LockDescription::Type::kNoOp);
auto lock = base::WrapUnique(
new NoopLock(std::make_unique<content::PartitionedLockHolder>()));
base::WeakPtr<content::PartitionedLockHolder> holder =
lock->holder_->AsWeakPtr();
AcquireLock(holder, lock_description,
base::BindOnce(std::move(on_lock_acquired), std::move(lock)),
location);
}
template <>
void WebAppLockManager::AcquireLock(
const LockDescription& lock_description,
base::OnceCallback<void(std::unique_ptr<SharedWebContentsLock>)>
on_lock_acquired,
const base::Location& location) {
CHECK(lock_description.type() ==
LockDescription::Type::kBackgroundWebContents);
auto lock = base::WrapUnique(new SharedWebContentsLock(
weak_factory_.GetWeakPtr(),
std::make_unique<content::PartitionedLockHolder>(),
*provider_->command_manager().EnsureWebContentsCreated(PassKey())));
base::WeakPtr<content::PartitionedLockHolder> holder =
lock->holder_->AsWeakPtr();
AcquireLock(holder, lock_description,
base::BindOnce(std::move(on_lock_acquired), std::move(lock)),
location);
}
template <>
void WebAppLockManager::AcquireLock(
const LockDescription& lock_description,
base::OnceCallback<void(std::unique_ptr<AppLock>)> on_lock_acquired,
const base::Location& location) {
CHECK(lock_description.type() == LockDescription::Type::kApp);
auto lock = base::WrapUnique(
new AppLock(weak_factory_.GetWeakPtr(),
std::make_unique<content::PartitionedLockHolder>()));
base::WeakPtr<content::PartitionedLockHolder> holder =
lock->holder_->AsWeakPtr();
AcquireLock(holder, lock_description,
base::BindOnce(std::move(on_lock_acquired), std::move(lock)),
location);
}
template <>
void WebAppLockManager::AcquireLock(
const LockDescription& lock_description,
base::OnceCallback<void(std::unique_ptr<SharedWebContentsWithAppLock>)>
on_lock_acquired,
const base::Location& location) {
CHECK(lock_description.type() == LockDescription::Type::kAppAndWebContents);
auto lock = base::WrapUnique(new SharedWebContentsWithAppLock(
weak_factory_.GetWeakPtr(),
std::make_unique<content::PartitionedLockHolder>(),
*provider_->command_manager().EnsureWebContentsCreated(PassKey())));
base::WeakPtr<content::PartitionedLockHolder> holder =
lock->holder_->AsWeakPtr();
AcquireLock(holder, lock_description,
base::BindOnce(std::move(on_lock_acquired), std::move(lock)),
location);
}
template <>
void WebAppLockManager::AcquireLock(
const LockDescription& lock_description,
base::OnceCallback<void(std::unique_ptr<AllAppsLock>)> on_lock_acquired,
const base::Location& location) {
CHECK(lock_description.type() == LockDescription::Type::kAllAppsLock);
auto lock = base::WrapUnique(
new AllAppsLock(weak_factory_.GetWeakPtr(),
std::make_unique<content::PartitionedLockHolder>()));
base::WeakPtr<content::PartitionedLockHolder> holder =
lock->holder_->AsWeakPtr();
AcquireLock(holder, lock_description,
base::BindOnce(std::move(on_lock_acquired), std::move(lock)),
location);
}
std::unique_ptr<SharedWebContentsWithAppLockDescription>
WebAppLockManager::UpgradeAndAcquireLock(
std::unique_ptr<SharedWebContentsLock> lock,
const base::flat_set<AppId>& app_ids,
base::OnceCallback<void(std::unique_ptr<SharedWebContentsWithAppLock>)>
on_lock_acquired,
const base::Location& location) {
std::unique_ptr<SharedWebContentsWithAppLockDescription>
result_lock_description =
std::make_unique<SharedWebContentsWithAppLockDescription>(app_ids);
auto result_lock = base::WrapUnique(new SharedWebContentsWithAppLock(
weak_factory_.GetWeakPtr(), std::move(lock->holder_),
*provider_->command_manager().EnsureWebContentsCreated(PassKey())));
base::WeakPtr<content::PartitionedLockHolder> holder =
result_lock->holder_->AsWeakPtr();
content::PartitionedLockManager::AcquireOptions options;
options.ensure_async = true;
#if DCHECK_IS_ON()
LogLockRequest(*result_lock_description, location,
GetExclusiveAppIdLocks(app_ids), lock_manager_);
#endif
lock_manager_.AcquireLocks(
GetExclusiveAppIdLocks(app_ids), holder,
base::BindOnce(std::move(on_lock_acquired), std::move(result_lock)),
options, location);
return result_lock_description;
}
std::unique_ptr<AppLockDescription> WebAppLockManager::UpgradeAndAcquireLock(
std::unique_ptr<NoopLock> lock,
const base::flat_set<AppId>& app_ids,
base::OnceCallback<void(std::unique_ptr<AppLock>)> on_lock_acquired,
const base::Location& location) {
std::unique_ptr<AppLockDescription> result_lock_description =
std::make_unique<AppLockDescription>(app_ids);
auto result_lock = base::WrapUnique(
new AppLock(weak_factory_.GetWeakPtr(), std::move(lock->holder_)));
base::WeakPtr<content::PartitionedLockHolder> holder =
result_lock->holder_->AsWeakPtr();
content::PartitionedLockManager::AcquireOptions options;
options.ensure_async = true;
#if DCHECK_IS_ON()
LogLockRequest(*result_lock_description, location,
GetExclusiveAppIdLocks(app_ids), lock_manager_);
#endif
lock_manager_.AcquireLocks(
GetExclusiveAppIdLocks(app_ids), holder,
base::BindOnce(std::move(on_lock_acquired), std::move(result_lock)),
options, location);
return result_lock_description;
}
base::Value WebAppLockManager::ToDebugValue() const {
return lock_manager_.ToDebugValue([](const content::PartitionedLockId& lock)
-> std::string {
// Out of bounds things should NOT happen here, but given this is being
// called from debug UI, it's better to return something reasonable
// instead of doing undefined behavior.
DCHECK_GE(lock.partition, 0);
DCHECK_LE(lock.partition, static_cast<int>(LockPartition::kMaxValue));
if (lock.partition < 0 ||
lock.partition > static_cast<int>(LockPartition::kMaxValue)) {
return base::StringPrintf("Invalid lock partition: %i", lock.partition);
}
LockPartition partition = static_cast<LockPartition>(lock.partition);
switch (partition) {
case LockPartition::kApp:
return base::StrCat({"App, ", lock.key});
case LockPartition::kStatic: {
int lock_key = -1;
if (!base::StringToInt(lock.key, &lock_key)) {
return base::StringPrintf("Static, invalid number '%s'",
lock.key.c_str());
}
DCHECK_GE(lock_key, 0);
DCHECK_LE(lock_key, static_cast<int>(KeysOnStaticPartition::kMaxValue));
if (lock_key < 0 ||
lock_key > static_cast<int>(KeysOnStaticPartition::kMaxValue)) {
return base::StringPrintf("Static, invalid key number: %i", lock_key);
}
KeysOnStaticPartition key =
static_cast<KeysOnStaticPartition>(lock_key);
return base::StringPrintf("Static, '%s'",
KeysOnStaticPartitionToString(key));
}
}
});
}
void WebAppLockManager::AcquireLock(
base::WeakPtr<content::PartitionedLockHolder> holder,
const LockDescription& lock_description,
base::OnceClosure on_lock_acquired,
const base::Location& location) {
std::vector<content::PartitionedLockManager::PartitionedLockRequest>
requests = GetLockRequestsForLock(lock_description);
content::PartitionedLockManager::AcquireOptions options;
options.ensure_async = true;
#if DCHECK_IS_ON()
LogLockRequest(lock_description, location, requests, lock_manager_);
#endif
lock_manager_.AcquireLocks(std::move(requests), holder,
std::move(on_lock_acquired), options, location);
}
} // namespace web_app