#include "chrome/browser/ui/startup/web_app_startup_utils.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/check_is_test.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/fixed_flat_map.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_functions.h"
#include "base/no_destructor.h"
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "chrome/browser/apps/app_service/app_service_proxy.h"
#include "chrome/browser/apps/app_service/app_service_proxy_factory.h"
#include "chrome/browser/apps/app_service/launch_utils.h"
#include "chrome/browser/lifetime/termination_notification.h"
#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/startup/infobar_utils.h"
#include "chrome/browser/ui/startup/startup_browser_creator.h"
#include "chrome/browser/ui/startup/startup_browser_creator_impl.h"
#include "chrome/browser/ui/startup/startup_types.h"
#include "chrome/browser/ui/web_applications/web_app_dialogs.h"
#include "chrome/browser/web_applications/os_integration/os_integration_manager.h"
#include "chrome/browser/web_applications/os_integration/web_app_file_handler_manager.h"
#include "chrome/browser/web_applications/web_app.h"
#include "chrome/browser/web_applications/web_app_command_scheduler.h"
#include "chrome/browser/web_applications/web_app_constants.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/browser/web_applications/web_app_registry_update.h"
#include "chrome/browser/web_applications/web_app_utils.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "components/keep_alive_registry/keep_alive_registry.h"
#include "components/keep_alive_registry/keep_alive_types.h"
#include "components/keep_alive_registry/scoped_keep_alive.h"
#include "components/webapps/common/web_app_id.h"
#include "url/gurl.h"
namespace web_app::startup {
namespace {
base::OnceClosure& GetStartupDoneCallback() {
static base::NoDestructor<base::OnceClosure> instance;
return *instance;
}
base::OnceClosure& GetBrowserShutdownCompleteCallback() {
static base::NoDestructor<base::OnceClosure> instance;
return *instance;
}
class StartupWebAppCreator
: public base::RefCountedThreadSafe<StartupWebAppCreator> {
public:
static bool MaybeHandleWebAppLaunch(
const base::CommandLine& command_line,
const base::FilePath& cur_dir,
Profile* profile,
chrome::startup::IsFirstRun is_first_run) {
std::string app_id = command_line.GetSwitchValueASCII(switches::kAppId);
if (app_id.empty()) {
return false;
}
KeepAliveRegistry* keep_alive_registry = KeepAliveRegistry::GetInstance();
if (!keep_alive_registry || keep_alive_registry->IsShuttingDown()) {
return false;
}
scoped_refptr<StartupWebAppCreator> web_app_startup =
base::AdoptRef(new StartupWebAppCreator(command_line, cur_dir, profile,
is_first_run, app_id));
WebAppProvider::GetForWebApps(profile)->on_registry_ready().Post(
FROM_HERE,
base::BindOnce(&StartupWebAppCreator::Start, web_app_startup));
return true;
}
REQUIRE_ADOPTION_FOR_REFCOUNTED_TYPE();
private:
friend class base::RefCountedThreadSafe<StartupWebAppCreator>;
enum class LaunchResult {
kHandled,
kNotHandled,
};
StartupWebAppCreator(const base::CommandLine& command_line,
const base::FilePath& cur_dir,
Profile* profile,
chrome::startup::IsFirstRun is_first_run,
const webapps::AppId& app_id)
: command_line_(command_line),
cur_dir_(cur_dir),
profile_(profile),
is_first_run_(is_first_run),
app_id_(app_id),
provider_(WebAppProvider::GetForWebApps(profile_)),
profile_keep_alive_(std::make_unique<ScopedProfileKeepAlive>(
profile,
ProfileKeepAliveOrigin::kWebAppPermissionDialogWindow)),
keep_alive_(std::make_unique<ScopedKeepAlive>(
KeepAliveOrigin::WEB_APP_INTENT_PICKER,
KeepAliveRestartOption::DISABLED)),
subscription_(browser_shutdown::AddAppTerminatingCallback(
base::BindOnce(&StartupWebAppCreator::OnBrowserShutdown,
base::Unretained(this)))) {
CHECK(provider_);
}
~StartupWebAppCreator() {
auto startup_done = std::move(GetStartupDoneCallback());
if (startup_done) {
std::move(startup_done).Run();
}
}
void Start() {
if (MaybeLaunchProtocolHandler() == LaunchResult::kHandled) {
return;
}
DCHECK(protocol_url_.is_empty());
if (MaybeLaunchFileHandler() == LaunchResult::kHandled) {
return;
}
DCHECK(file_launch_infos_.empty());
open_mode_ = OpenMode::kInWindowByAppId;
LaunchApp();
}
void LaunchApp() {
if (file_launch_infos_.empty()) {
std::optional<GURL> protocol;
if (!protocol_url_.is_empty()) {
protocol = protocol_url_;
}
provider_->scheduler().LaunchApp(
app_id_, command_line_, cur_dir_, protocol,
std::nullopt, {},
base::BindOnce(&StartupWebAppCreator::OnAppLaunched,
base::WrapRefCounted(this)));
return;
}
for (const auto& [url, paths] : file_launch_infos_) {
provider_->scheduler().LaunchApp(
app_id_, command_line_, cur_dir_,
std::nullopt,
url, paths,
base::BindOnce(&StartupWebAppCreator::OnAppLaunched,
base::WrapRefCounted(this)));
}
}
LaunchResult MaybeLaunchProtocolHandler() {
GURL protocol_url;
base::CommandLine::StringVector args = command_line_.GetArgs();
CHECK(provider_->on_registry_ready().is_signaled());
WebAppRegistrar& registrar = provider_->registrar_unsafe();
for (const auto& arg : args) {
#if BUILDFLAG(IS_WIN)
GURL potential_protocol(base::AsStringPiece16(arg));
#else
GURL potential_protocol(arg);
#endif
if (potential_protocol.is_valid() &&
registrar.IsRegisteredLaunchProtocol(
app_id_, potential_protocol.GetScheme())) {
protocol_url = std::move(potential_protocol);
break;
}
}
if (protocol_url.is_empty()) {
return LaunchResult::kNotHandled;
}
if (registrar.IsDisallowedLaunchProtocol(app_id_,
protocol_url.GetScheme())) {
return LaunchResult::kHandled;
}
protocol_url_ = protocol_url;
auto launch_callback =
base::BindOnce(&StartupWebAppCreator::OnUserDecisionDialogCompleted,
base::WrapRefCounted(this));
if (registrar.IsAllowedLaunchProtocol(app_id_, protocol_url_.GetScheme())) {
std::move(launch_callback)
.Run(true, false);
} else {
ShowWebAppProtocolLaunchDialog(protocol_url_, profile_, app_id_,
std::move(launch_callback));
}
return LaunchResult::kHandled;
}
LaunchResult MaybeLaunchFileHandler() {
std::vector<base::FilePath> launch_files =
apps::GetLaunchFilesFromCommandLine(command_line_);
if (launch_files.empty()) {
return LaunchResult::kNotHandled;
}
file_launch_infos_ = provider_->os_integration_manager()
.file_handler_manager()
.GetMatchingFileHandlerUrls(app_id_, launch_files);
if (file_launch_infos_.empty()) {
return LaunchResult::kNotHandled;
}
const WebApp* web_app = provider_->registrar_unsafe().GetAppById(app_id_);
DCHECK(web_app);
auto launch_callback =
base::BindOnce(&StartupWebAppCreator::OnUserDecisionDialogCompleted,
base::WrapRefCounted(this));
switch (web_app->file_handler_approval_state()) {
case ApiApprovalState::kRequiresPrompt:
ShowWebAppFileLaunchDialog(launch_files, profile_, app_id_,
std::move(launch_callback));
break;
case ApiApprovalState::kAllowed:
std::move(launch_callback)
.Run(true, false);
break;
case ApiApprovalState::kDisallowed:
NOTREACHED();
}
return LaunchResult::kHandled;
}
void OnPersistUserChoiceCompleted(bool allowed) {
if (allowed) {
LaunchApp();
}
}
void OnUserDecisionDialogCompleted(bool allowed, bool remember_user_choice) {
auto persist_callback =
base::BindOnce(&StartupWebAppCreator::OnPersistUserChoiceCompleted,
base::WrapRefCounted(this), allowed);
if (remember_user_choice) {
if (!protocol_url_.is_empty()) {
ApiApprovalState approval_state = allowed
? ApiApprovalState::kAllowed
: ApiApprovalState::kDisallowed;
provider_->scheduler().UpdateProtocolHandlerUserApproval(
app_id_, protocol_url_.GetScheme(), approval_state,
std::move(persist_callback));
} else {
DCHECK(!file_launch_infos_.empty());
provider_->scheduler().PersistFileHandlersUserChoice(
app_id_, allowed, std::move(persist_callback));
}
} else {
std::move(persist_callback).Run();
}
}
void OnAppLaunched(base::WeakPtr<Browser> browser,
base::WeakPtr<content::WebContents> web_contents,
apps::LaunchContainer container) {
if (app_window_has_been_launched_) {
return;
}
FinalizeWebAppLaunch(open_mode_, command_line_, is_first_run_,
browser.get(), container);
app_window_has_been_launched_ = true;
}
void OnBrowserShutdown() {
profile_keep_alive_.reset();
keep_alive_.reset();
auto browser_shutdown_complete =
std::move(GetBrowserShutdownCompleteCallback());
if (browser_shutdown_complete) {
CHECK_IS_TEST();
std::move(browser_shutdown_complete).Run();
}
}
const base::CommandLine command_line_;
const base::FilePath cur_dir_;
const raw_ptr<Profile> profile_;
chrome::startup::IsFirstRun is_first_run_;
const webapps::AppId app_id_;
raw_ptr<WebAppProvider> provider_;
std::unique_ptr<ScopedProfileKeepAlive> profile_keep_alive_;
std::unique_ptr<ScopedKeepAlive> keep_alive_;
base::CallbackListSubscription subscription_;
std::optional<OpenMode> open_mode_;
GURL protocol_url_;
WebAppFileHandlerManager::LaunchInfos file_launch_infos_;
bool app_window_has_been_launched_ = false;
};
}
bool MaybeHandleWebAppLaunch(const base::CommandLine& command_line,
const base::FilePath& cur_dir,
Profile* profile,
chrome::startup::IsFirstRun is_first_run) {
return StartupWebAppCreator::MaybeHandleWebAppLaunch(command_line, cur_dir,
profile, is_first_run);
}
void FinalizeWebAppLaunch(std::optional<OpenMode> app_open_mode,
const base::CommandLine& command_line,
chrome::startup::IsFirstRun is_first_run,
BrowserWindowInterface* browser,
apps::LaunchContainer container) {
if (!browser) {
return;
}
OpenMode mode = OpenMode::kUnknown;
switch (container) {
case apps::LaunchContainer::kLaunchContainerWindow:
DCHECK(browser->GetType() == BrowserWindowInterface::TYPE_APP);
mode = app_open_mode.value_or(OpenMode::kInWindowOther);
break;
case apps::LaunchContainer::kLaunchContainerTab:
DCHECK(browser->GetType() != BrowserWindowInterface::TYPE_APP);
mode = OpenMode::kInTab;
break;
case apps::LaunchContainer::kLaunchContainerPanelDeprecated:
NOTREACHED();
case apps::LaunchContainer::kLaunchContainerNone:
DCHECK(browser->GetType() != BrowserWindowInterface::TYPE_APP);
break;
}
base::UmaHistogramEnumeration("WebApp.OpenMode", mode);
AddInfoBarsIfNecessary(
browser, browser->GetProfile(), command_line, is_first_run,
true, HasPendingUncleanExit(browser->GetProfile()),
StartupBrowserCreator::WasRestarted());
StartupBrowserCreatorImpl::MaybeToggleFullscreen(browser);
}
void SetStartupDoneCallbackForTesting(base::OnceClosure callback) {
GetStartupDoneCallback() = std::move(callback);
}
void SetBrowserShutdownCompleteCallbackForTesting(base::OnceClosure callback) {
CHECK_IS_TEST();
GetBrowserShutdownCompleteCallback() = std::move(callback);
}
}