blob: 9cf8b3cf972b73d83d659e1ca6839b4de306f460 [file] [log] [blame]
// Copyright 2024 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/ui/webui/whats_new/whats_new_fetcher.h"
#include <optional>
#include <string>
#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/global_features.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/webui/whats_new/whats_new_util.h"
#include "chrome/common/chrome_version.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/reduce_accept_language_controller_delegate.h"
#include "net/base/url_util.h"
#include "net/http/http_util.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "url/gurl.h"
namespace whats_new {
const char kChromeWhatsNewURL[] = "https://www.google.com/chrome/whats-new/";
const char kChromeWhatsNewRefreshURL[] =
"https://www.google.com/chrome/wn-2025/whats-new/";
const char kChromeWhatsNewStagingURL[] =
"https://chrome-staging.corp.google.com/chrome/whats-new/";
const char kChromeWhatsNewRefreshStagingURL[] =
"https://chrome-staging.corp.google.com/chrome/wn-2025/whats-new/";
const int64_t kMaxDownloadBytes = 1024 * 1024;
GURL AddVersionParameter(const GURL& base_url) {
return net::AppendQueryParameter(base_url, "version",
base::NumberToString(CHROME_VERSION_MAJOR));
}
GURL GetServerLegacyURL(bool is_staging) {
const GURL base_url =
is_staging ? GURL(kChromeWhatsNewStagingURL) : GURL(kChromeWhatsNewURL);
return AddVersionParameter(base_url);
}
GURL GetServerRefreshURL(bool is_staging) {
const GURL base_url = is_staging ? GURL(kChromeWhatsNewRefreshStagingURL)
: GURL(kChromeWhatsNewRefreshURL);
return AddVersionParameter(base_url);
}
GURL GetServerURL(bool is_staging) {
return base::FeatureList::IsEnabled(features::kWhatsNewDesktopRefresh)
? GetServerRefreshURL(is_staging)
: GetServerLegacyURL(is_staging);
}
GURL GetServerURLForRender(const WhatsNewRegistry& whats_new_registry,
bool is_staging) {
GURL url = GetServerURL(is_staging);
const auto active_features = whats_new_registry.GetActiveFeatureNames();
if (!active_features.empty()) {
url = net::AppendQueryParameter(
url, "enabled", base::JoinString(active_features, std::string(",")));
}
const auto rolled_features = whats_new_registry.GetRolledFeatureNames();
if (!rolled_features.empty()) {
url = net::AppendQueryParameter(
url, "rolled", base::JoinString(rolled_features, std::string(",")));
}
const auto customizations = whats_new_registry.GetCustomizations();
if (!customizations.empty()) {
url = net::AppendQueryParameter(
url, "customization",
base::JoinString(customizations, std::string(",")));
}
return net::AppendQueryParameter(url, "internal", "true");
}
namespace {
class WhatsNewFetcher : public BrowserListObserver {
public:
explicit WhatsNewFetcher(Browser* browser) : browser_(browser) {
BrowserList::AddObserver(this);
browser_did_become_active_subscription_ = browser_->RegisterDidBecomeActive(
base::BindRepeating(&WhatsNewFetcher::OnBrowserDidBecomeActive,
base::Unretained(this)));
browser_did_become_inactive_subscription_ =
browser_->RegisterDidBecomeActive(
base::BindRepeating(&WhatsNewFetcher::OnBrowserDidBecomeInactive,
base::Unretained(this)));
GURL server_url = GetServerURL();
startup_url_ = GetWebUIStartupURL();
if (IsRemoteContentDisabled()) {
// Don't fetch network content if this is the case, just pretend the tab
// was retrieved successfully. Do so asynchronously to simulate the
// production code better.
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&WhatsNewFetcher::OpenWhatsNewTabForTest,
base::Unretained(this)));
return;
}
LogLoadEvent(LoadEvent::kLoadStart);
auto traffic_annotation =
net::DefineNetworkTrafficAnnotation("whats_new_fetcher", R"(
semantics {
sender: "What's New Page"
description: "Attempts to fetch the content for the What's New "
"page to ensure it loads successfully."
trigger:
"Restarting Chrome after an update. Desktop only."
data:
"No data sent, other than URL of What's New. "
"Data does not contain PII."
destination: GOOGLE_OWNED_SERVICE
internal {
contacts {
email: "frizzle-team@google.com"
}
}
user_data {
type: NONE
}
last_reviewed: "2024-05-22"
}
policy {
cookies_allowed: NO
setting:
"None"
chrome_policy {
PromotionalTabsEnabled {
PromotionalTabsEnabled: false
}
}
})");
network::mojom::URLLoaderFactory* loader_factory =
g_browser_process->system_network_context_manager()
->GetURLLoaderFactory();
auto request = std::make_unique<network::ResourceRequest>();
// Inform the server of the top browser language via the
// Accept-Language header.
if (auto* profile = browser->profile()) {
if (auto* delegate =
profile->GetReduceAcceptLanguageControllerDelegate()) {
auto languages = delegate->GetUserAcceptLanguages();
if (!languages.empty()) {
request->headers.SetHeader(request->headers.kAcceptLanguage,
languages.front());
}
}
}
// Don't allow redirects when checking if the page is valid for the current
// milestone.
request->url = server_url;
simple_loader_ = network::SimpleURLLoader::Create(std::move(request),
traffic_annotation);
// base::Unretained is safe here because only OnResponseLoaded deletes
// |this|.
simple_loader_->DownloadToString(
loader_factory,
base::BindOnce(&WhatsNewFetcher::OnResponseLoaded,
base::Unretained(this)),
kMaxDownloadBytes);
}
~WhatsNewFetcher() override { BrowserList::RemoveObserver(this); }
void OnBrowserDidBecomeActive(BrowserWindowInterface* browser) {
browser_closed_or_inactive_ = false;
}
void OnBrowserDidBecomeInactive(BrowserWindowInterface* browser) {
browser_closed_or_inactive_ = true;
}
// BrowserListObserver:
void OnBrowserRemoved(Browser* browser) override {
if (browser != browser_) {
return;
}
browser_closed_or_inactive_ = true;
BrowserList::RemoveObserver(this);
browser_did_become_active_subscription_ = {};
browser_did_become_inactive_subscription_ = {};
browser_ = nullptr;
}
private:
void AddWhatsNewTab(Browser* browser) {
chrome::AddTabAt(browser, startup_url_, 0, true);
browser->tab_strip_model()->ActivateTabAt(
browser->tab_strip_model()->IndexOfFirstNonPinnedTab());
}
static void LogLoadEvent(LoadEvent event) {
base::UmaHistogramEnumeration("WhatsNew.LoadEvent", event);
}
void OpenWhatsNewTabForTest() {
if (browser_closed_or_inactive_) {
return;
}
AddWhatsNewTab(browser_);
delete this;
}
void OnResponseLoaded(std::optional<std::string> body) {
int error_or_response_code = simple_loader_->NetError();
const auto& headers = simple_loader_->ResponseInfo()
? simple_loader_->ResponseInfo()->headers
: nullptr;
bool success = error_or_response_code == net::OK && headers;
if (headers) {
error_or_response_code =
net::HttpUtil::MapStatusCodeForHistogram(headers->response_code());
}
base::UmaHistogramSparse("WhatsNew.LoadResponseCode",
error_or_response_code);
// The server may respond with a 302 to indicate the requested
// page version does not exist but a suitable page has been found.
// This should not result in an error since the auto-opened page
// can still access a relevant resource.
success = success && error_or_response_code >= 200 &&
error_or_response_code <= 302 && body;
// If the browser was closed or moved to the background while What's New was
// loading, return early before recording that the user saw the page.
if (browser_closed_or_inactive_) {
LogLoadEvent(LoadEvent::kLoadAbort);
return;
}
DCHECK(browser_);
LogLoadEvent(success ? LoadEvent::kLoadSuccess
: LoadEvent::kLoadFailAndDoNotShow);
if (success) {
AddWhatsNewTab(browser_);
}
delete this;
}
std::unique_ptr<network::SimpleURLLoader> simple_loader_;
raw_ptr<Browser> browser_;
bool browser_closed_or_inactive_ = false;
GURL startup_url_;
base::CallbackListSubscription browser_did_become_active_subscription_;
base::CallbackListSubscription browser_did_become_inactive_subscription_;
};
} // namespace
void StartWhatsNewFetch(Browser* browser) {
new WhatsNewFetcher(browser);
}
} // namespace whats_new