blob: 870a47cf0550f80ee879244f33cde7f2881c0f13 [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 Soromoued9d5882025-05-22 14:23:5221#include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h"
22#include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h"
23#include "chrome/browser/profiles/profile.h"
Foromo Daniel Soromou615461c2025-05-16 14:05:3124#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
25#include "chrome/browser/ui/browser_window/public/browser_window_interface_iterator.h"
Foromo Daniel Soromouce65be02025-05-22 14:14:0626#include "chrome/browser/win/installer_downloader/installer_downloader_feature.h"
Foromo Daniel Soromou9c116012025-05-23 16:02:0927#include "chrome/browser/win/installer_downloader/installer_downloader_infobar_window_active_tab_tracker.h"
Foromo Daniel Soromou67860ff2025-05-05 15:38:3328#include "chrome/browser/win/installer_downloader/installer_downloader_model.h"
29#include "chrome/browser/win/installer_downloader/installer_downloader_model_impl.h"
Foromo Daniel Soromouf0d01d52025-05-08 15:25:4130#include "chrome/browser/win/installer_downloader/system_info_provider_impl.h"
Foromo Daniel Soromou9c116012025-05-23 16:02:0931#include "components/infobars/content/content_infobar_manager.h"
32#include "components/infobars/core/confirm_infobar_delegate.h"
33#include "components/infobars/core/infobar.h"
Foromo Daniel Soromou615461c2025-05-16 14:05:3134#include "components/tabs/public/tab_interface.h"
Foromo Daniel Soromouce65be02025-05-22 14:14:0635#include "content/public/browser/browser_context.h"
36#include "content/public/browser/download_manager.h"
Foromo Daniel Soromou615461c2025-05-16 14:05:3137#include "content/public/browser/web_contents.h"
Foromo Daniel Soromouce65be02025-05-22 14:14:0638#include "url/gurl.h"
Foromo Daniel Soromou67860ff2025-05-05 15:38:3339
40namespace installer_downloader {
41
Foromo Daniel Soromou615461c2025-05-16 14:05:3142namespace {
43
44content::WebContents* GetActiveWebContents() {
45 for (BrowserWindowInterface* browser : GetAllBrowserWindowInterfaces()) {
46 if (!browser->IsActive() ||
47 browser->GetType() != BrowserWindowInterface::Type::TYPE_NORMAL) {
48 continue;
49 }
50
51 return CHECK_DEREF(browser->GetActiveTabInterface()).GetContents();
52 }
53
54 return nullptr;
Foromo Daniel Soromou67860ff2025-05-05 15:38:3355}
56
Foromo Daniel Soromouce65be02025-05-22 14:14:0657std::optional<GURL> BuildInstallerDownloadUrl(bool is_metrics_enabled) {
58 std::string installer_url_template = kInstallerUrlTemplateParam.Get();
59
60 base::ReplaceFirstSubstringAfterOffset(
61 &installer_url_template, /*start_offset=*/0, "IIDGUID",
62 base::Uuid::GenerateRandomV4().AsLowercaseString());
63
64 base::ReplaceFirstSubstringAfterOffset(&installer_url_template,
65 /*start_offset=*/0, "STATS",
66 is_metrics_enabled ? "1" : "0");
67
68 GURL installer_url(installer_url_template);
69
70 return installer_url.is_valid()
71 ? std::optional<GURL>(std::move(installer_url))
72 : std::nullopt;
73}
74
Foromo Daniel Soromou615461c2025-05-16 14:05:3175} // namespace
76
77InstallerDownloaderController::InstallerDownloaderController(
Foromo Daniel Soromouce65be02025-05-22 14:14:0678 ShowInfobarCallback show_infobar_callback,
79 base::RepeatingCallback<bool()> is_metrics_enabled_callback)
80 : is_metrics_enabled_callback_(std::move(is_metrics_enabled_callback)),
81 show_infobar_callback_(std::move(show_infobar_callback)),
Foromo Daniel Soromou615461c2025-05-16 14:05:3182 model_(std::make_unique<InstallerDownloaderModelImpl>(
83 std::make_unique<SystemInfoProviderImpl>())),
84 get_active_web_contents_callback_(
Foromo Daniel Soromou9c116012025-05-23 16:02:0985 base::BindRepeating(&GetActiveWebContents)) {
86 RegisterBrowserWindowEvents();
87}
Foromo Daniel Soromou615461c2025-05-16 14:05:3188
89InstallerDownloaderController::InstallerDownloaderController(
90 ShowInfobarCallback show_infobar_callback,
Foromo Daniel Soromouce65be02025-05-22 14:14:0691 base::RepeatingCallback<bool()> is_metrics_enabled_callback,
Foromo Daniel Soromou615461c2025-05-16 14:05:3192 std::unique_ptr<InstallerDownloaderModel> model)
Foromo Daniel Soromouce65be02025-05-22 14:14:0693 : is_metrics_enabled_callback_(std::move(is_metrics_enabled_callback)),
94 show_infobar_callback_(std::move(show_infobar_callback)),
Foromo Daniel Soromou615461c2025-05-16 14:05:3195 model_(std::move(model)),
96 get_active_web_contents_callback_(
Foromo Daniel Soromou9c116012025-05-23 16:02:0997 base::BindRepeating(&GetActiveWebContents)) {
98 RegisterBrowserWindowEvents();
99}
100
101void InstallerDownloaderController::RegisterBrowserWindowEvents() {
102 active_window_subscription_ =
103 window_tracker_.RegisterActiveWindowChangedCallback(base::BindRepeating(
104 &InstallerDownloaderController::OnActiveBrowserWindowChanged,
105 base::Unretained(this)));
106
107 removed_window_subscription_ =
108 window_tracker_.RegisterRemovedWindowCallback(base::BindRepeating(
109 &InstallerDownloaderController::OnRemovedBrowserWindow,
110 base::Unretained(this)));
111}
Foromo Daniel Soromou615461c2025-05-16 14:05:31112
Foromo Daniel Soromou67860ff2025-05-05 15:38:33113InstallerDownloaderController::~InstallerDownloaderController() = default;
114
Foromo Daniel Soromou9c116012025-05-23 16:02:09115void InstallerDownloaderController::OnActiveBrowserWindowChanged(
116 BrowserWindowInterface* bwi) {
117 // This can be null during the startup or when the last window is closed.
118 if (!bwi) {
119 return;
120 }
Foromo Daniel Soromoubadfb802025-05-05 16:22:04121
Foromo Daniel Soromou9c116012025-05-23 16:02:09122 if (bwi_and_active_tab_tracker_map_.contains(bwi)) {
123 return;
124 }
125
126 bwi_and_active_tab_tracker_map_[bwi] =
127 std::make_unique<InstallerDownloaderInfobarWindowActiveTabTracker>(
128 bwi,
129 base::BindRepeating(&InstallerDownloaderController::MaybeShowInfoBar,
130 base::Unretained(this)));
131}
132
133void InstallerDownloaderController::OnRemovedBrowserWindow(
134 BrowserWindowInterface* bwi) {
135 if (!bwi_and_active_tab_tracker_map_.contains(bwi)) {
136 return;
137 }
138
139 bwi_and_active_tab_tracker_map_.erase(bwi);
140}
141
142void InstallerDownloaderController::MaybeShowInfoBar() {
Foromo Daniel Soromoubadfb802025-05-05 16:22:04143 // The max show count of the infobar have been reached. Eligibility check is
144 // no longer needed.
145 if (model_->IsMaxShowCountReached()) {
146 return;
147 }
148
149 model_->CheckEligibility(
150 base::BindOnce(&InstallerDownloaderController::OnEligibilityReady,
151 base::Unretained(this)));
Foromo Daniel Soromou67860ff2025-05-05 15:38:33152}
153
154void InstallerDownloaderController::OnEligibilityReady(
Foromo Daniel Soromou3646aa82025-05-22 17:07:27155 std::optional<base::FilePath> destination) {
Foromo Daniel Soromou9c116012025-05-23 16:02:09156 if (infobar_closed_) {
157 return;
158 }
159
160 auto* contents = get_active_web_contents_callback_.Run();
161
162 if (!contents) {
163 return;
164 }
165
166 if (visible_infobars_web_contents_.contains(contents)) {
167 return;
168 }
169
Foromo Daniel Soromou3646aa82025-05-22 17:07:27170 // Early return when we have no destination and bypass is not allowed.
171 if (!destination.has_value() && !model_->ShouldByPassEligibilityCheck()) {
Foromo Daniel Soromou615461c2025-05-16 14:05:31172 return;
173 }
174
Foromo Daniel Soromou3646aa82025-05-22 17:07:27175 // Compute a fallback destination (user’s Desktop) when bypassing eligibility.
176 if (!destination) {
177 base::FilePath desktop_path;
178 if (!base::PathService::Get(base::DIR_USER_DESKTOP, &desktop_path)) {
179 return;
180 }
181
182 destination = std::move(desktop_path);
183 }
184
Foromo Daniel Soromou9c116012025-05-23 16:02:09185 infobars::ContentInfoBarManager* infobar_manager =
186 infobars::ContentInfoBarManager::FromWebContents(contents);
Foromo Daniel Soromou615461c2025-05-16 14:05:31187
188 // Installer Downloader is a global feature, therefore it's guaranteed that
189 // InstallerDownloaderController will be alive at any point during the browser
190 // runtime.
Foromo Daniel Soromou9c116012025-05-23 16:02:09191 infobars::InfoBar* infobar = show_infobar_callback_.Run(
192 infobar_manager,
Muhammad Salmaandadc22982025-05-20 20:31:26193 base::BindOnce(&InstallerDownloaderController::OnDownloadRequestAccepted,
Foromo Daniel Soromouce65be02025-05-22 14:14:06194 base::Unretained(this), destination.value()),
Muhammad Salmaandadc22982025-05-20 20:31:26195 base::BindOnce(&InstallerDownloaderController::OnInfoBarDismissed,
196 base::Unretained(this)));
Foromo Daniel Soromou9c116012025-05-23 16:02:09197
198 if (!infobar) {
199 return;
200 }
201
Foromo Daniel Soromoucfa8f7b2025-05-23 19:25:38202 if (infobar_manager) {
203 infobar_manager->AddObserver(this);
204 } else {
205 CHECK_IS_TEST();
206 }
207
Foromo Daniel Soromou9c116012025-05-23 16:02:09208 visible_infobars_web_contents_[contents] = infobar;
Foromo Daniel Soromoucfa8f7b2025-05-23 19:25:38209
210 // This is the first show in this browser session.
211 if (visible_infobars_web_contents_.size() == 1u) {
212 model_->IncrementShowCount();
213 base::UmaHistogramBoolean("Windows.InstallerDownloader.InfobarShown",
214 /*shown=*/true);
215 }
Foromo Daniel Soromou9c116012025-05-23 16:02:09216}
217
218void InstallerDownloaderController::OnInfoBarRemoved(infobars::InfoBar* infobar,
219 bool animate) {
220 auto it = std::find_if(
221 visible_infobars_web_contents_.begin(),
222 visible_infobars_web_contents_.end(),
223 [infobar](const auto& entry) { return entry.second == infobar; });
224
225 if (it == visible_infobars_web_contents_.end()) {
226 return;
227 }
228
229 it->second->owner()->RemoveObserver(this);
230 visible_infobars_web_contents_.erase(it);
231
232 if (!user_initiated_info_bar_close_pending_) {
233 return;
234 }
235
236 for (auto [contents, infobar_instance] : visible_infobars_web_contents_) {
237 infobar_instance->owner()->RemoveObserver(this);
238 infobar_instance->RemoveSelf();
239 }
240
241 visible_infobars_web_contents_.clear();
242 infobar_closed_ = true;
Foromo Daniel Soromou67860ff2025-05-05 15:38:33243}
244
Foromo Daniel Soromouce65be02025-05-22 14:14:06245void InstallerDownloaderController::OnDownloadRequestAccepted(
246 const base::FilePath& destination) {
Foromo Daniel Soromoucfa8f7b2025-05-23 19:25:38247 base::UmaHistogramBoolean("Windows.InstallerDownloader.RequestAccepted",
248 true);
249
Foromo Daniel Soromou9c116012025-05-23 16:02:09250 user_initiated_info_bar_close_pending_ = true;
Foromo Daniel Soromou67860ff2025-05-05 15:38:33251 // User have explicitly gave download consent. Therefore, a background
252 // download should be issued.
Foromo Daniel Soromouce65be02025-05-22 14:14:06253 auto* contents = get_active_web_contents_callback_.Run();
254
255 if (!contents) {
256 return;
257 }
258
259 std::optional<GURL> installer_url =
260 BuildInstallerDownloadUrl(is_metrics_enabled_callback_.Run());
261
262 if (!installer_url.has_value()) {
263 return;
264 }
265
Foromo Daniel Soromoued9d5882025-05-22 14:23:52266 // Keep the profile alive until the download completes.
267 auto* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
268 auto keep_alive = std::make_unique<ScopedProfileKeepAlive>(
269 profile, ProfileKeepAliveOrigin::kDownloadInProgress);
270
Foromo Daniel Soromouce65be02025-05-22 14:14:06271 model_->StartDownload(
272 installer_url.value(), destination,
Foromo Daniel Soromoued9d5882025-05-22 14:23:52273 CHECK_DEREF(profile->GetDownloadManager()),
Foromo Daniel Soromouce65be02025-05-22 14:14:06274 base::BindOnce(&InstallerDownloaderController::OnDownloadCompleted,
Foromo Daniel Soromoued9d5882025-05-22 14:23:52275 base::Unretained(this), std::move(keep_alive)));
Foromo Daniel Soromou67860ff2025-05-05 15:38:33276}
277
Foromo Daniel Soromoued9d5882025-05-22 14:23:52278void InstallerDownloaderController::OnDownloadCompleted(
279 std::unique_ptr<ScopedProfileKeepAlive> keep_alive,
280 bool success) {
Foromo Daniel Soromoucfa8f7b2025-05-23 19:25:38281 base::UmaHistogramBoolean("Windows.InstallerDownloader.DownloadSucceed",
282 success);
Foromo Daniel Soromou67860ff2025-05-05 15:38:33283}
284
Foromo Daniel Soromou615461c2025-05-16 14:05:31285void InstallerDownloaderController::SetActiveWebContentsCallbackForTesting(
286 GetActiveWebContentsCallback callback) {
287 get_active_web_contents_callback_ = std::move(callback);
288}
289
Muhammad Salmaandadc22982025-05-20 20:31:26290void InstallerDownloaderController::OnInfoBarDismissed() {
Foromo Daniel Soromoucfa8f7b2025-05-23 19:25:38291 base::UmaHistogramBoolean("Windows.InstallerDownloader.RequestAccepted",
292 false);
Foromo Daniel Soromou9c116012025-05-23 16:02:09293 user_initiated_info_bar_close_pending_ = true;
Muhammad Salmaandadc22982025-05-20 20:31:26294}
295
Foromo Daniel Soromou67860ff2025-05-05 15:38:33296} // namespace installer_downloader