blob: 51d5f103113fd3fed4c5937ffd80c37336f2426c [file] [log] [blame]
// Copyright 2025 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/win/installer_downloader/installer_downloader_controller.h"
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include "base/base_paths.h"
#include "base/check_deref.h"
#include "base/check_is_test.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/metrics/histogram_functions.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/uuid.h"
#include "base/version_info/channel.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/global_features.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_window/public/browser_window_interface.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
#include "chrome/browser/win/installer_downloader/installer_downloader_feature.h"
#include "chrome/browser/win/installer_downloader/installer_downloader_infobar_window_active_tab_tracker.h"
#include "chrome/browser/win/installer_downloader/installer_downloader_model.h"
#include "chrome/browser/win/installer_downloader/installer_downloader_model_impl.h"
#include "chrome/browser/win/installer_downloader/system_info_provider_impl.h"
#include "chrome/common/channel_info.h"
#include "components/application_locale_storage/application_locale_storage.h"
#include "components/infobars/content/content_infobar_manager.h"
#include "components/infobars/core/confirm_infobar_delegate.h"
#include "components/infobars/core/infobar.h"
#include "components/tabs/public/tab_interface.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/download_manager.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/base_window.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
namespace installer_downloader {
namespace {
constexpr const char kUrlPrefix[] =
"https://dl.google.com/tag/s/appguid%3D%7B";
constexpr const char kUrlSuffix[] =
"%7D%26iid%3D%7BIIDGUID%7D%26lang%3DLANGUAGE%26browser%3D4%26usagestats%"
"3DSTATS%26appname%3DGoogle%2520Chrome%26needsadmin%3Dprefers%26ap%"
"3Dx64-statsdef_1%26brand%3DLMFN%26installdataindex%3Dempty/update2/"
"installers/ChromeSetup.exe";
constexpr const char kStableGuid[] = "8A69D345-D564-463C-AFF1-A69D9E530F96";
constexpr const char kBetaGuid[] = "8237E44A-0054-442C-B6B6-EA0509993955";
constexpr const char kDevGuid[] = "401C381F-E0DE-4B85-8BD8-3F3F14FBDA57";
constexpr const char kCanaryGuid[] = "4EA16AC7-FD5A-47C3-875B-DBF4A2008C20";
std::string MakeInstallerTemplate(const std::string& appGuid) {
return base::StrCat({kUrlPrefix, appGuid, kUrlSuffix});
}
std::string GetDefaultInstallerDownloadUrlTemplate() {
switch (chrome::GetChannel()) {
case version_info::Channel::CANARY:
return MakeInstallerTemplate(kCanaryGuid);
case version_info::Channel::DEV:
return MakeInstallerTemplate(kDevGuid);
case version_info::Channel::BETA:
return MakeInstallerTemplate(kBetaGuid);
default:
return MakeInstallerTemplate(kStableGuid);
}
NOTREACHED();
}
std::optional<GURL> BuildInstallerDownloadUrl(bool is_metrics_enabled) {
std::string installer_url_template = kInstallerUrlTemplateParam.Get();
if (installer_url_template.empty()) {
installer_url_template = GetDefaultInstallerDownloadUrlTemplate();
}
base::ReplaceFirstSubstringAfterOffset(
&installer_url_template, /*start_offset=*/0, "IIDGUID",
base::Uuid::GenerateRandomV4().AsLowercaseString());
base::ReplaceFirstSubstringAfterOffset(&installer_url_template,
/*start_offset=*/0, "STATS",
is_metrics_enabled ? "1" : "0");
std::string_view language_code = l10n_util::GetLanguage(
g_browser_process->GetFeatures()->application_locale_storage()->Get());
CHECK(!language_code.empty());
base::ReplaceFirstSubstringAfterOffset(
&installer_url_template, /*start_offset=*/0, "LANGUAGE", language_code);
GURL installer_url(installer_url_template);
return installer_url.is_valid()
? std::optional<GURL>(std::move(installer_url))
: std::nullopt;
}
} // namespace
InstallerDownloaderController::InstallerDownloaderController(
ShowInfobarCallback show_infobar_callback,
base::RepeatingCallback<bool()> is_metrics_enabled_callback)
: is_metrics_enabled_callback_(std::move(is_metrics_enabled_callback)),
show_infobar_callback_(std::move(show_infobar_callback)),
model_(std::make_unique<InstallerDownloaderModelImpl>(
std::make_unique<SystemInfoProviderImpl>())),
get_active_web_contents_callback_(base::BindRepeating(
&InstallerDownloaderController::GetActiveWebContents,
base::Unretained(this))),
should_show_infobar_for_profile_callback_(base::BindRepeating(
&InstallerDownloaderController::ShouldShowInfobarForCurrentProfile,
base::Unretained(this))) {
RegisterBrowserWindowEvents();
}
InstallerDownloaderController::InstallerDownloaderController(
ShowInfobarCallback show_infobar_callback,
base::RepeatingCallback<bool()> is_metrics_enabled_callback,
std::unique_ptr<InstallerDownloaderModel> model)
: is_metrics_enabled_callback_(std::move(is_metrics_enabled_callback)),
show_infobar_callback_(std::move(show_infobar_callback)),
model_(std::move(model)),
get_active_web_contents_callback_(base::BindRepeating(
&InstallerDownloaderController::GetActiveWebContents,
base::Unretained(this))),
should_show_infobar_for_profile_callback_(base::BindRepeating(
&InstallerDownloaderController::ShouldShowInfobarForCurrentProfile,
base::Unretained(this))) {
RegisterBrowserWindowEvents();
}
void InstallerDownloaderController::RegisterBrowserWindowEvents() {
active_window_subscription_ =
window_tracker_.RegisterActiveWindowChangedCallback(base::BindRepeating(
&InstallerDownloaderController::OnActiveBrowserWindowChanged,
base::Unretained(this)));
removed_window_subscription_ =
window_tracker_.RegisterRemovedWindowCallback(base::BindRepeating(
&InstallerDownloaderController::OnRemovedBrowserWindow,
base::Unretained(this)));
}
content::WebContents* InstallerDownloaderController::GetActiveWebContents() {
BrowserWindowInterface* last_active_window =
window_tracker_.get_last_active_window();
if (!last_active_window) {
return nullptr;
}
tabs::TabInterface* active_tab = last_active_window->GetActiveTabInterface();
if (!active_tab) {
return nullptr;
}
content::WebContents* web_contents = active_tab->GetContents();
if (!web_contents || web_contents->IsBeingDestroyed()) {
return nullptr;
}
return web_contents;
}
InstallerDownloaderController::~InstallerDownloaderController() = default;
void InstallerDownloaderController::OnActiveBrowserWindowChanged(
BrowserWindowInterface* bwi) {
// This can be null during the startup or when the last window is closed.
if (!bwi) {
return;
}
if (bwi_and_active_tab_tracker_map_.contains(bwi)) {
return;
}
bwi_and_active_tab_tracker_map_[bwi] =
std::make_unique<InstallerDownloaderInfobarWindowActiveTabTracker>(
bwi,
base::BindRepeating(&InstallerDownloaderController::MaybeShowInfoBar,
base::Unretained(this)));
}
void InstallerDownloaderController::OnRemovedBrowserWindow(
BrowserWindowInterface* bwi) {
if (!bwi_and_active_tab_tracker_map_.contains(bwi)) {
return;
}
bwi_and_active_tab_tracker_map_.erase(bwi);
}
bool InstallerDownloaderController::ShouldShowInfobarForCurrentProfile() {
// The infobar should not be shown on guest profiles.
BrowserWindowInterface* last_active_window =
window_tracker_.get_last_active_window();
if (!last_active_window ||
last_active_window->GetProfile()->IsGuestSession()) {
return false;
}
return true;
}
void InstallerDownloaderController::MaybeShowInfoBar() {
// The max show count of the infobar have been reached. Eligibility check is
// no longer needed.
if (!model_->CanShowInfobar()) {
return;
}
if (!should_show_infobar_for_profile_callback_.Run()) {
return;
}
model_->CheckEligibility(
base::BindOnce(&InstallerDownloaderController::OnEligibilityReady,
base::Unretained(this)));
}
void InstallerDownloaderController::OnEligibilityReady(
std::optional<base::FilePath> destination) {
if (infobar_closed_) {
return;
}
// Early return when we have no destination and bypass is not allowed.
if (!destination.has_value() && !model_->ShouldByPassEligibilityCheck()) {
return;
}
auto* contents = get_active_web_contents_callback_.Run();
if (!contents) {
return;
}
if (visible_infobars_web_contents_.contains(contents)) {
return;
}
// Compute a fallback destination (user’s Desktop) when bypassing eligibility.
if (!destination) {
base::FilePath desktop_path;
if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) {
return;
}
destination = std::move(desktop_path);
}
infobars::ContentInfoBarManager* infobar_manager =
infobars::ContentInfoBarManager::FromWebContents(contents);
// Installer Downloader is a global feature, therefore it's guaranteed that
// InstallerDownloaderController will be alive at any point during the browser
// runtime.
infobars::InfoBar* infobar = show_infobar_callback_.Run(
infobar_manager,
base::BindOnce(&InstallerDownloaderController::OnDownloadRequestAccepted,
base::Unretained(this), destination.value()),
base::BindOnce(&InstallerDownloaderController::OnInfoBarDismissed,
base::Unretained(this)));
if (!infobar) {
return;
}
if (infobar_manager) {
infobar_manager->AddObserver(this);
} else {
CHECK_IS_TEST();
}
visible_infobars_web_contents_[contents] = infobar;
// This is the first show in this browser session.
if (visible_infobars_web_contents_.size() == 1u) {
model_->IncrementShowCount();
base::UmaHistogramBoolean("Windows.InstallerDownloader.InfobarShown",
/*sample=*/true);
}
}
void InstallerDownloaderController::OnInfoBarRemoved(infobars::InfoBar* infobar,
bool animate) {
auto it = std::find_if(
visible_infobars_web_contents_.begin(),
visible_infobars_web_contents_.end(),
[infobar](const auto& entry) { return entry.second == infobar; });
if (it == visible_infobars_web_contents_.end()) {
return;
}
it->second->owner()->RemoveObserver(this);
visible_infobars_web_contents_.erase(it);
if (!user_initiated_info_bar_close_pending_) {
return;
}
for (auto [contents, infobar_instance] : visible_infobars_web_contents_) {
infobar_instance->owner()->RemoveObserver(this);
infobar_instance->RemoveSelf();
}
visible_infobars_web_contents_.clear();
infobar_closed_ = true;
}
void InstallerDownloaderController::OnDownloadRequestAccepted(
const base::FilePath& destination) {
base::UmaHistogramBoolean("Windows.InstallerDownloader.RequestAccepted",
true);
user_initiated_info_bar_close_pending_ = true;
// User have explicitly gave download consent. Therefore, a background
// download should be issued.
auto* contents = get_active_web_contents_callback_.Run();
if (!contents) {
return;
}
std::optional<GURL> installer_url =
BuildInstallerDownloadUrl(is_metrics_enabled_callback_.Run());
if (!installer_url.has_value()) {
return;
}
// Keep the profile alive until the download completes.
auto* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
auto keep_alive = std::make_unique<ScopedProfileKeepAlive>(
profile, ProfileKeepAliveOrigin::kDownloadInProgress);
model_->StartDownload(
installer_url.value(),
destination.AppendASCII(kDownloadedInstallerFileName.Get()),
CHECK_DEREF(profile->GetDownloadManager()),
base::BindOnce(&InstallerDownloaderController::OnDownloadCompleted,
base::Unretained(this), std::move(keep_alive)));
}
void InstallerDownloaderController::OnDownloadCompleted(
std::unique_ptr<ScopedProfileKeepAlive> keep_alive,
bool success) {
base::UmaHistogramBoolean("Windows.InstallerDownloader.DownloadSucceed",
success);
model_->PreventFutureDisplay();
}
void InstallerDownloaderController::SetActiveWebContentsCallbackForTesting(
GetActiveWebContentsCallback callback) {
get_active_web_contents_callback_ = std::move(callback);
}
void InstallerDownloaderController::
SetShouldShowInfobarForProfileCallbackForTesting(
ShouldShowInfobarForProfileCallback callback) {
should_show_infobar_for_profile_callback_ = std::move(callback);
}
void InstallerDownloaderController::OnInfoBarDismissed() {
base::UmaHistogramBoolean("Windows.InstallerDownloader.RequestAccepted",
false);
user_initiated_info_bar_close_pending_ = true;
model_->PreventFutureDisplay();
}
} // namespace installer_downloader