blob: f0539a6da088c37c9689a47b6f7c43c3274470f8 [file] [log] [blame]
Foromo Daniel Soromou67860ff2025-05-05 15:38:331// Copyright 2025 The Chromium Authors
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/win/installer_downloader/installer_downloader_controller.h"
6
7#include <memory>
8#include <optional>
9#include <utility>
10
Foromo Daniel Soromou3646aa82025-05-22 17:07:2711#include "base/base_paths.h"
Foromo Daniel Soromou615461c2025-05-16 14:05:3112#include "base/check_deref.h"
Foromo Daniel Soromoucfa8f7b2025-05-23 19:25:3813#include "base/check_is_test.h"
Foromo Daniel Soromou67860ff2025-05-05 15:38:3314#include "base/files/file_path.h"
Foromo Daniel Soromoubadfb802025-05-05 16:22:0415#include "base/functional/bind.h"
16#include "base/functional/callback.h"
Foromo Daniel Soromoucfa8f7b2025-05-23 19:25:3817#include "base/metrics/histogram_functions.h"
Foromo Daniel Soromou3646aa82025-05-22 17:07:2718#include "base/path_service.h"
Foromo Daniel Soromouce65be02025-05-22 14:14:0619#include "base/strings/string_util.h"
20#include "base/uuid.h"
Foromo Daniel Soromou7b9475a2025-06-07 05:20:4321#include "chrome/browser/browser_process.h"
22#include "chrome/browser/global_features.h"
Foromo Daniel Soromoued9d5882025-05-22 14:23:5223#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
24#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
25#include "chrome/browser/profiles/profile.h"
Foromo Daniel Soromou615461c2025-05-16 14:05:3126#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
27#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
Foromo Daniel Soromouce65be02025-05-22 14:14:0628#include "chrome/browser/win/installer_downloader/installer_downloader_feature.h"
Foromo Daniel Soromou9c116012025-05-23 16:02:0929#include "chrome/browser/win/installer_downloader/installer_downloader_infobar_window_active_tab_tracker.h"
Foromo Daniel Soromou67860ff2025-05-05 15:38:3330#include "chrome/browser/win/installer_downloader/installer_downloader_model.h"
31#include "chrome/browser/win/installer_downloader/installer_downloader_model_impl.h"
Foromo Daniel Soromouf0d01d52025-05-08 15:25:4132#include "chrome/browser/win/installer_downloader/system_info_provider_impl.h"
Foromo Daniel Soromou7b9475a2025-06-07 05:20:4333#include "components/application_locale_storage/application_locale_storage.h"
Foromo Daniel Soromou9c116012025-05-23 16:02:0934#include "components/infobars/content/content_infobar_manager.h"
35#include "components/infobars/core/confirm_infobar_delegate.h"
36#include "components/infobars/core/infobar.h"
Foromo Daniel Soromou615461c2025-05-16 14:05:3137#include "components/tabs/public/tab_interface.h"
Foromo Daniel Soromouce65be02025-05-22 14:14:0638#include "content/public/browser/browser_context.h"
39#include "content/public/browser/download_manager.h"
Foromo Daniel Soromou615461c2025-05-16 14:05:3140#include "content/public/browser/web_contents.h"
Foromo Daniel Soromou7b9475a2025-06-07 05:20:4341#include "ui/base/l10n/l10n_util.h"
Foromo Daniel Soromouce65be02025-05-22 14:14:0642#include "url/gurl.h"
Foromo Daniel Soromou67860ff2025-05-05 15:38:3343
44namespace installer_downloader {
45
Foromo Daniel Soromou615461c2025-05-16 14:05:3146namespace {
47
48content::WebContents* GetActiveWebContents() {
49 for (BrowserWindowInterface* browser : GetAllBrowserWindowInterfaces()) {
50 if (!browser->IsActive() ||
51 browser->GetType() != BrowserWindowInterface::Type::TYPE_NORMAL) {
52 continue;
53 }
54
55 return CHECK_DEREF(browser->GetActiveTabInterface()).GetContents();
56 }
57
58 return nullptr;
Foromo Daniel Soromou67860ff2025-05-05 15:38:3359}
60
Foromo Daniel Soromouce65be02025-05-22 14:14:0661std::optional<GURL> BuildInstallerDownloadUrl(bool is_metrics_enabled) {
62 std::string installer_url_template = kInstallerUrlTemplateParam.Get();
63
64 base::ReplaceFirstSubstringAfterOffset(
65 &installer_url_template, /*start_offset=*/0, "IIDGUID",
66 base::Uuid::GenerateRandomV4().AsLowercaseString());
67
68 base::ReplaceFirstSubstringAfterOffset(&installer_url_template,
69 /*start_offset=*/0, "STATS",
70 is_metrics_enabled ? "1" : "0");
71
Jan Keitel86180162025-06-13 18:04:4272 std::string_view language_code = l10n_util::GetLanguage(
73 g_browser_process->GetFeatures()->application_locale_storage()->Get());
Foromo Daniel Soromou7b9475a2025-06-07 05:20:4374 CHECK(!language_code.empty());
75
76 base::ReplaceFirstSubstringAfterOffset(
77 &installer_url_template, /*start_offset=*/0, "LANGUAGE", language_code);
78
Foromo Daniel Soromouce65be02025-05-22 14:14:0679 GURL installer_url(installer_url_template);
80
81 return installer_url.is_valid()
82 ? std::optional<GURL>(std::move(installer_url))
83 : std::nullopt;
84}
85
Foromo Daniel Soromou615461c2025-05-16 14:05:3186} // namespace
87
88InstallerDownloaderController::InstallerDownloaderController(
Foromo Daniel Soromouce65be02025-05-22 14:14:0689 ShowInfobarCallback show_infobar_callback,
90 base::RepeatingCallback<bool()> is_metrics_enabled_callback)
91 : is_metrics_enabled_callback_(std::move(is_metrics_enabled_callback)),
92 show_infobar_callback_(std::move(show_infobar_callback)),
Foromo Daniel Soromou615461c2025-05-16 14:05:3193 model_(std::make_unique<InstallerDownloaderModelImpl>(
94 std::make_unique<SystemInfoProviderImpl>())),
95 get_active_web_contents_callback_(
Foromo Daniel Soromou9c116012025-05-23 16:02:0996 base::BindRepeating(&GetActiveWebContents)) {
97 RegisterBrowserWindowEvents();
98}
Foromo Daniel Soromou615461c2025-05-16 14:05:3199
100InstallerDownloaderController::InstallerDownloaderController(
101 ShowInfobarCallback show_infobar_callback,
Foromo Daniel Soromouce65be02025-05-22 14:14:06102 base::RepeatingCallback<bool()> is_metrics_enabled_callback,
Foromo Daniel Soromou615461c2025-05-16 14:05:31103 std::unique_ptr<InstallerDownloaderModel> model)
Foromo Daniel Soromouce65be02025-05-22 14:14:06104 : is_metrics_enabled_callback_(std::move(is_metrics_enabled_callback)),
105 show_infobar_callback_(std::move(show_infobar_callback)),
Foromo Daniel Soromou615461c2025-05-16 14:05:31106 model_(std::move(model)),
107 get_active_web_contents_callback_(
Foromo Daniel Soromou9c116012025-05-23 16:02:09108 base::BindRepeating(&GetActiveWebContents)) {
109 RegisterBrowserWindowEvents();
110}
111
112void InstallerDownloaderController::RegisterBrowserWindowEvents() {
113 active_window_subscription_ =
114 window_tracker_.RegisterActiveWindowChangedCallback(base::BindRepeating(
115 &InstallerDownloaderController::OnActiveBrowserWindowChanged,
116 base::Unretained(this)));
117
118 removed_window_subscription_ =
119 window_tracker_.RegisterRemovedWindowCallback(base::BindRepeating(
120 &InstallerDownloaderController::OnRemovedBrowserWindow,
121 base::Unretained(this)));
122}
Foromo Daniel Soromou615461c2025-05-16 14:05:31123
Foromo Daniel Soromou67860ff2025-05-05 15:38:33124InstallerDownloaderController::~InstallerDownloaderController() = default;
125
Foromo Daniel Soromou9c116012025-05-23 16:02:09126void InstallerDownloaderController::OnActiveBrowserWindowChanged(
127 BrowserWindowInterface* bwi) {
128 // This can be null during the startup or when the last window is closed.
129 if (!bwi) {
130 return;
131 }
Foromo Daniel Soromoubadfb802025-05-05 16:22:04132
Foromo Daniel Soromou9c116012025-05-23 16:02:09133 if (bwi_and_active_tab_tracker_map_.contains(bwi)) {
134 return;
135 }
136
137 bwi_and_active_tab_tracker_map_[bwi] =
138 std::make_unique<InstallerDownloaderInfobarWindowActiveTabTracker>(
139 bwi,
140 base::BindRepeating(&InstallerDownloaderController::MaybeShowInfoBar,
141 base::Unretained(this)));
142}
143
144void InstallerDownloaderController::OnRemovedBrowserWindow(
145 BrowserWindowInterface* bwi) {
146 if (!bwi_and_active_tab_tracker_map_.contains(bwi)) {
147 return;
148 }
149
150 bwi_and_active_tab_tracker_map_.erase(bwi);
151}
152
153void InstallerDownloaderController::MaybeShowInfoBar() {
Foromo Daniel Soromoubadfb802025-05-05 16:22:04154 // The max show count of the infobar have been reached. Eligibility check is
155 // no longer needed.
Foromo Daniel Soromou06301a12025-06-04 05:36:49156 if (!model_->CanShowInfobar()) {
Foromo Daniel Soromoubadfb802025-05-05 16:22:04157 return;
158 }
159
160 model_->CheckEligibility(
161 base::BindOnce(&InstallerDownloaderController::OnEligibilityReady,
162 base::Unretained(this)));
Foromo Daniel Soromou67860ff2025-05-05 15:38:33163}
164
165void InstallerDownloaderController::OnEligibilityReady(
Foromo Daniel Soromou3646aa82025-05-22 17:07:27166 std::optional<base::FilePath> destination) {
Foromo Daniel Soromou9c116012025-05-23 16:02:09167 if (infobar_closed_) {
168 return;
169 }
170
171 auto* contents = get_active_web_contents_callback_.Run();
172
173 if (!contents) {
174 return;
175 }
176
Muhammad Salmaan8ccd61c2025-07-03 18:38:51177 // The infobar should not be shown on guest profiles.
178 if (Profile::FromBrowserContext(contents->GetBrowserContext())
179 ->IsGuestSession()) {
180 return;
181 }
182
Foromo Daniel Soromou9c116012025-05-23 16:02:09183 if (visible_infobars_web_contents_.contains(contents)) {
184 return;
185 }
186
Foromo Daniel Soromou3646aa82025-05-22 17:07:27187 // Early return when we have no destination and bypass is not allowed.
188 if (!destination.has_value() && !model_->ShouldByPassEligibilityCheck()) {
Foromo Daniel Soromou615461c2025-05-16 14:05:31189 return;
190 }
191
Foromo Daniel Soromou3646aa82025-05-22 17:07:27192 // Compute a fallback destination (user’s Desktop) when bypassing eligibility.
193 if (!destination) {
194 base::FilePath desktop_path;
195 if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) {
196 return;
197 }
198
199 destination = std::move(desktop_path);
200 }
201
Foromo Daniel Soromou9c116012025-05-23 16:02:09202 infobars::ContentInfoBarManager* infobar_manager =
203 infobars::ContentInfoBarManager::FromWebContents(contents);
Foromo Daniel Soromou615461c2025-05-16 14:05:31204
205 // Installer Downloader is a global feature, therefore it's guaranteed that
206 // InstallerDownloaderController will be alive at any point during the browser
207 // runtime.
Foromo Daniel Soromou9c116012025-05-23 16:02:09208 infobars::InfoBar* infobar = show_infobar_callback_.Run(
209 infobar_manager,
Muhammad Salmaandadc22982025-05-20 20:31:26210 base::BindOnce(&InstallerDownloaderController::OnDownloadRequestAccepted,
Foromo Daniel Soromouce65be02025-05-22 14:14:06211 base::Unretained(this), destination.value()),
Muhammad Salmaandadc22982025-05-20 20:31:26212 base::BindOnce(&InstallerDownloaderController::OnInfoBarDismissed,
213 base::Unretained(this)));
Foromo Daniel Soromou9c116012025-05-23 16:02:09214
215 if (!infobar) {
216 return;
217 }
218
Foromo Daniel Soromoucfa8f7b2025-05-23 19:25:38219 if (infobar_manager) {
220 infobar_manager->AddObserver(this);
221 } else {
222 CHECK_IS_TEST();
223 }
224
Foromo Daniel Soromou9c116012025-05-23 16:02:09225 visible_infobars_web_contents_[contents] = infobar;
Foromo Daniel Soromoucfa8f7b2025-05-23 19:25:38226
227 // This is the first show in this browser session.
228 if (visible_infobars_web_contents_.size() == 1u) {
229 model_->IncrementShowCount();
230 base::UmaHistogramBoolean("Windows.InstallerDownloader.InfobarShown",
231 /*shown=*/true);
232 }
Foromo Daniel Soromou9c116012025-05-23 16:02:09233}
234
235void InstallerDownloaderController::OnInfoBarRemoved(infobars::InfoBar* infobar,
236 bool animate) {
237 auto it = std::find_if(
238 visible_infobars_web_contents_.begin(),
239 visible_infobars_web_contents_.end(),
240 [infobar](const auto& entry) { return entry.second == infobar; });
241
242 if (it == visible_infobars_web_contents_.end()) {
243 return;
244 }
245
246 it->second->owner()->RemoveObserver(this);
247 visible_infobars_web_contents_.erase(it);
248
249 if (!user_initiated_info_bar_close_pending_) {
250 return;
251 }
252
253 for (auto [contents, infobar_instance] : visible_infobars_web_contents_) {
254 infobar_instance->owner()->RemoveObserver(this);
255 infobar_instance->RemoveSelf();
256 }
257
258 visible_infobars_web_contents_.clear();
259 infobar_closed_ = true;
Foromo Daniel Soromou67860ff2025-05-05 15:38:33260}
261
Foromo Daniel Soromouce65be02025-05-22 14:14:06262void InstallerDownloaderController::OnDownloadRequestAccepted(
263 const base::FilePath& destination) {
Foromo Daniel Soromoucfa8f7b2025-05-23 19:25:38264 base::UmaHistogramBoolean("Windows.InstallerDownloader.RequestAccepted",
265 true);
266
Foromo Daniel Soromou9c116012025-05-23 16:02:09267 user_initiated_info_bar_close_pending_ = true;
Foromo Daniel Soromou67860ff2025-05-05 15:38:33268 // User have explicitly gave download consent. Therefore, a background
269 // download should be issued.
Foromo Daniel Soromouce65be02025-05-22 14:14:06270 auto* contents = get_active_web_contents_callback_.Run();
271
272 if (!contents) {
273 return;
274 }
275
276 std::optional<GURL> installer_url =
277 BuildInstallerDownloadUrl(is_metrics_enabled_callback_.Run());
278
279 if (!installer_url.has_value()) {
280 return;
281 }
282
Foromo Daniel Soromoued9d5882025-05-22 14:23:52283 // Keep the profile alive until the download completes.
284 auto* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
285 auto keep_alive = std::make_unique<ScopedProfileKeepAlive>(
286 profile, ProfileKeepAliveOrigin::kDownloadInProgress);
287
Foromo Daniel Soromouce65be02025-05-22 14:14:06288 model_->StartDownload(
Foromo Daniel Soromou773ecc02025-06-03 21:11:58289 installer_url.value(),
290 destination.AppendASCII(kDownloadedInstallerFileName.Get()),
Foromo Daniel Soromoued9d5882025-05-22 14:23:52291 CHECK_DEREF(profile->GetDownloadManager()),
Foromo Daniel Soromouce65be02025-05-22 14:14:06292 base::BindOnce(&InstallerDownloaderController::OnDownloadCompleted,
Foromo Daniel Soromoued9d5882025-05-22 14:23:52293 base::Unretained(this), std::move(keep_alive)));
Foromo Daniel Soromou67860ff2025-05-05 15:38:33294}
295
Foromo Daniel Soromoued9d5882025-05-22 14:23:52296void InstallerDownloaderController::OnDownloadCompleted(
297 std::unique_ptr<ScopedProfileKeepAlive> keep_alive,
298 bool success) {
Foromo Daniel Soromoucfa8f7b2025-05-23 19:25:38299 base::UmaHistogramBoolean("Windows.InstallerDownloader.DownloadSucceed",
300 success);
Foromo Daniel Soromou06301a12025-06-04 05:36:49301 model_->PreventFutureDisplay();
Foromo Daniel Soromou67860ff2025-05-05 15:38:33302}
303
Foromo Daniel Soromou615461c2025-05-16 14:05:31304void InstallerDownloaderController::SetActiveWebContentsCallbackForTesting(
305 GetActiveWebContentsCallback callback) {
306 get_active_web_contents_callback_ = std::move(callback);
307}
308
Muhammad Salmaandadc22982025-05-20 20:31:26309void InstallerDownloaderController::OnInfoBarDismissed() {
Foromo Daniel Soromoucfa8f7b2025-05-23 19:25:38310 base::UmaHistogramBoolean("Windows.InstallerDownloader.RequestAccepted",
311 false);
Foromo Daniel Soromou9c116012025-05-23 16:02:09312 user_initiated_info_bar_close_pending_ = true;
Foromo Daniel Soromou06301a12025-06-04 05:36:49313 model_->PreventFutureDisplay();
Muhammad Salmaandadc22982025-05-20 20:31:26314}
315
Foromo Daniel Soromou67860ff2025-05-05 15:38:33316} // namespace installer_downloader