blob: cb3c6189cd379ed0ee7478a38d7549adcde4dfc9 [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.
#ifndef CHROME_BROWSER_WEB_APPLICATIONS_COMMANDS_WEB_APP_COMMAND_H_
#define CHROME_BROWSER_WEB_APPLICATIONS_COMMANDS_WEB_APP_COMMAND_H_
#include <iterator>
#include <memory>
#include <optional>
#include <tuple>
#include <type_traits>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/sequence_checker.h"
#include "base/strings/to_string.h"
#include "base/types/pass_key.h"
#include "base/values.h"
#include "chrome/browser/web_applications/commands/command_result.h"
#include "chrome/browser/web_applications/commands/internal/command_internal.h"
#include "components/webapps/common/web_app_id.h"
namespace content {
class WebContents;
}
namespace web_app {
class LockDescription;
class WebAppCommandManager;
class WebAppLockManager;
// Encapsulates code that reads or modifies the WebAppProvider system. All
// reading or writing to the system should occur in a WebAppCommand to ensure
// that it is isolated. Reading can also happen in any WebAppRegistrar observer.
//
// Commands allow an operation to:
// - Ensure that resources are not being used by another operation (e.g. no
// other operation is operating on the given app id).
// - Automatically handles edge cases like profile shutdown.
// - Prevent any possible re-entry bugs by allowing any final callback to be
// called after the command is destructed.
// - Record detailed logs that are exposed in chrome://web-app-internals.
//
// For simple operations that require holding on to lock only for single
// synchronous function calls, WebAppCommandScheduler::ScheduleCallback*
// can be used instead of creating a sub-class.
//
// To create a command sub-class, extend the below type `WebAppCommand,
// which allows specification of the type of lock to retrieve. For example:
//
// class GetAppInformationForMySystem
// : public WebAppCommand<AppLock, CallbackArgType> {
// GetAppInformationForMySystem(ReportBackInformationCallback callback)
// : WebAppCommand(std::move(callback),
// /*args_for_shutdown*/=CallbackArgType::kShutdownValue) {}
// ...
// void StartWithLock(std::unique_ptr<AppLock> lock) {
// ...
//
// ...
// CompleteAndSelfDestruct(
// CommandResult::kSuccess,
// lock.<information>());
// }
// ...
//
// // Implement this if installing from an external web contents.
// WebContents* GetInstallingWebContents(...) override;
// };
//
// See the `WebAppLockManager` for information about the available locks & how
// they work.
//
// Commands can only be started by enqueueing the command in the
// WebAppCommandManager, which is done by the WebAppCommandScheduler. When a
// command is complete, it can call `CompleteAndSelfDestruct` to signal
// completion and self-destruct.
//
// Call pattern of commands:
// - StartWithLock(),
// - <subclass stuff>
// - <subclass calls CompleteAndSelfDestruct()>.
//
// The command can use the following optional features:
// - Populate the `GetMutableDebugValue()` with information that is useful for
// debugging - this shown in chrome://web-app-internals and printed in failed
// tests.
// - To prevent multiple installs occurring at the same time for a given
// `WebContents`, installations that install from an external `WebContents`
// should override `GetInstallingWebContents()` and return that WebContents.
// - `OnShutdown()` can be overridden to do stateless tasks like recording
// metrics.
//
// Invariants:
// * Destruction can occur without `StartWithLock()` being called. If the system
// shuts down and the command was never started, then it will simply be
// destructed and the `callback` will be called with the
// `args_for_shutdown`, if they exist.
//
// TODO(dmurph): Add an example of a CL that creates a command.
template <typename LockType, typename... CallbackArgs>
class WebAppCommand : public internal::CommandWithLock<LockType> {
public:
using PassKey = base::PassKey<WebAppCommand>;
using LockDescription = LockType::LockDescription;
using CallbackType = base::OnceCallback<void(CallbackArgs...)>;
using ShutdownArgumentsTuple = std::tuple<std::decay_t<CallbackArgs>...>;
// Special constructor if the callback doesn't take any arguments. There is no
// need to specify an empty tuple.
template <std::size_t i = sizeof...(CallbackArgs)>
requires(i == 0)
WebAppCommand(const std::string& name,
LockDescription initial_lock_request,
CallbackType callback)
: internal::CommandWithLock<LockType>(name,
std::move(initial_lock_request)),
callback_(std::move(callback)) {
CHECK(!callback_.is_null());
}
template <std::size_t i = sizeof...(CallbackArgs)>
requires(i >= 1)
WebAppCommand(const std::string& name,
LockDescription initial_lock_request,
CallbackType callback,
ShutdownArgumentsTuple args_for_shutdown)
: internal::CommandWithLock<LockType>(name,
std::move(initial_lock_request)),
callback_(std::move(callback)),
args_for_shutdown_(std::move(args_for_shutdown)) {
CHECK(!callback_.is_null());
}
~WebAppCommand() override = default;
base::OnceClosure TakeCallbackWithShutdownArgs(
base::PassKey<WebAppCommandManager>) override {
DCHECK_CALLED_ON_VALID_SEQUENCE(
internal::CommandBase::command_sequence_checker_);
CHECK(!callback_.is_null());
internal::CommandBase::GetMutableDebugValue().Set("!command_result",
"kShutdown");
if constexpr (sizeof...(CallbackArgs) == 0) {
return std::move(callback_);
} else {
internal::CommandBase::GetMutableDebugValue().Set(
"!result", base::ToString(args_for_shutdown_));
// We need to call BindOnce with both the callback and the shutdown args,
// so they must be concatenated into a tuple with both before calling
// std::apply.
// The below code is the C++ equivalent of
// `callback_.bind(...args_used_on_shutdown_)` in JavaScript.
std::tuple<CallbackType, CallbackArgs...> bind_arguments =
std::tuple_cat<std::tuple<CallbackType>, std::tuple<CallbackArgs...>>(
/*tuple1=*/{std::move(callback_)},
/*tuple2=*/std::move(args_for_shutdown_));
return std::apply(
&base::BindOnce<base::OnceCallback<void(CallbackArgs...)>,
CallbackArgs...>,
std::move(bind_arguments));
}
}
protected:
// Calling this will destroy the command and allow the next command in the
// queue to run. This will do the following in order:
// 1) Destroy this object.
// 2) Call this command's `callback` with the given `args`.
// The `result` reports if the command encountered any unknown errors.
// TODO(dmurph): Use `result` in metrics. https://b/304553492.
void CompleteAndSelfDestruct(CommandResult result,
CallbackArgs... args_for_callback,
const base::Location& location = FROM_HERE) {
DCHECK_CALLED_ON_VALID_SEQUENCE(
internal::CommandBase::command_sequence_checker_);
base::Value::Dict* metadata =
internal::CommandBase::GetMutableDebugValue().EnsureDict("!metadata");
CHECK(internal::CommandBase::command_manager())
<< "Command was never given to the command manager: "
<< internal::CommandBase::GetMutableDebugValue().DebugString();
metadata->Set("command_result",
result == CommandResult::kSuccess ? "kSuccess" : "kFailure");
metadata->Set(
"result",
base::ToString(std::tie<CallbackArgs&...>(args_for_callback...)));
metadata->Set("completion_location", base::ToString(location));
// Note: `BindOnce` should correctly handle copying any ref or move
// arguments internally. This allows the callback arguments to contain ref
// types (which are standard for mojo callbacks) or move-only types.
internal::CommandBase::CompleteAndSelfDestructInternal(
result,
base::BindOnce(std::move(callback_),
std::forward<CallbackArgs>(args_for_callback)...));
}
private:
CallbackType callback_;
ShutdownArgumentsTuple args_for_shutdown_;
};
} // namespace web_app
#endif // CHROME_BROWSER_WEB_APPLICATIONS_COMMANDS_WEB_APP_COMMAND_H_