| // Copyright 2021 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_util.h" |
| #include "base/check.h" |
| #include "base/command_line.h" |
| #include "base/feature_list.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/location.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/stringprintf.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "chrome/browser/browser_process.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/common/chrome_switches.h" |
| #include "chrome/common/chrome_version.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.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 "ui/base/ui_base_features.h" |
| #include "url/gurl.h" |
| |
| namespace whats_new { |
| const int64_t kMaxDownloadBytes = 1024 * 1024; |
| |
| const char kChromeWhatsNewURL[] = "https://www.google.com/chrome/whats-new/"; |
| const char kChromeWhatsNewURLShort[] = "google.com/chrome/whats-new/"; |
| // The /m117 URL is reserved for the chrome refresh page. |
| const char kChromeWhatsNewRefreshURL[] = |
| "https://www.google.com/chrome/whats-new/m117"; |
| |
| bool is_minimum_refresh_version = CHROME_VERSION_MAJOR >= 117; |
| bool is_refresh_version = |
| CHROME_VERSION_MAJOR == 117 || CHROME_VERSION_MAJOR == 118; |
| |
| bool g_is_remote_content_disabled = false; |
| |
| void DisableRemoteContentForTests() { |
| g_is_remote_content_disabled = true; |
| } |
| |
| bool IsMinimumRefreshVersion() { |
| return is_minimum_refresh_version; |
| } |
| |
| bool IsRefreshVersion() { |
| return is_refresh_version; |
| } |
| |
| void SetChromeVersionForTests(int chrome_version) { |
| is_minimum_refresh_version = chrome_version >= 117; |
| is_refresh_version = chrome_version == 117 || chrome_version == 118; |
| } |
| |
| void LogStartupType(StartupType type) { |
| base::UmaHistogramEnumeration("WhatsNew.StartupType", type); |
| } |
| |
| bool IsRemoteContentDisabled() { |
| return g_is_remote_content_disabled; |
| } |
| |
| bool HasShownRefreshWhatsNew(PrefService* local_state) { |
| return local_state->GetBoolean(prefs::kHasShownRefreshWhatsNew); |
| } |
| |
| bool ShouldShowRefresh(PrefService* local_state) { |
| // Check pref to see if user has seen refresh page. |
| if (HasShownRefreshWhatsNew(local_state)) { |
| return false; |
| } |
| |
| // Show refresh page if user has flag enabled. |
| return features::IsChromeRefresh2023(); |
| } |
| |
| bool ShouldShowForState(PrefService* local_state, |
| bool promotional_tabs_enabled) { |
| LogStartupType(StartupType::kCalledShouldShow); |
| |
| if (!promotional_tabs_enabled) { |
| whats_new::LogStartupType(whats_new::StartupType::kPromotionalTabsDisabled); |
| return false; |
| } |
| |
| if (!local_state || |
| !local_state->FindPreference(prefs::kLastWhatsNewVersion)) { |
| LogStartupType(StartupType::kInvalidState); |
| return false; |
| } |
| |
| // Allow disabling the What's New experience in tests using the standard |
| // kNoFirstRun switch. This behavior can be overridden using the |
| // kForceWhatsNew switch for the What's New experience integration tests. |
| const base::CommandLine* command_line = |
| base::CommandLine::ForCurrentProcess(); |
| if ((command_line->HasSwitch(switches::kNoFirstRun) && |
| !command_line->HasSwitch(switches::kForceWhatsNew)) || |
| !base::FeatureList::IsEnabled(features::kChromeWhatsNewUI)) { |
| LogStartupType(StartupType::kFeatureDisabled); |
| return false; |
| } |
| |
| // Prevent showing the refresh page before 117, even if the refresh |
| // flag is enabled. |
| if (IsMinimumRefreshVersion() && ShouldShowRefresh(local_state)) { |
| return true; |
| } |
| |
| // These releases are dedicated to the refresh page which is served on |
| // the milestone 117 URL. Avoid ever showing content on 117/118 |
| // milestone upgrades by returning early. |
| if (IsRefreshVersion()) { |
| if (HasShownRefreshWhatsNew(local_state)) { |
| LogStartupType(StartupType::kAlreadyShown); |
| } |
| return false; |
| } |
| |
| int last_version = local_state->GetInteger(prefs::kLastWhatsNewVersion); |
| |
| // Don't show What's New if it's already been shown for the current major |
| // milestone. |
| if (CHROME_VERSION_MAJOR <= last_version) { |
| LogStartupType(StartupType::kAlreadyShown); |
| return false; |
| } |
| |
| // Set the last version here to indicate that What's New should not attempt |
| // to display again for this milestone. This prevents the page from |
| // potentially displaying multiple times in a given milestone, e.g. for |
| // multiple profile relaunches (see https://crbug.com/1274313). |
| local_state->SetInteger(prefs::kLastWhatsNewVersion, CHROME_VERSION_MAJOR); |
| return true; |
| } |
| |
| GURL GetServerURL(bool may_redirect) { |
| const GURL url = |
| may_redirect |
| ? net::AppendQueryParameter( |
| GURL(kChromeWhatsNewURL), "version", |
| base::NumberToString(CHROME_VERSION_MAJOR)) |
| : GURL(kChromeWhatsNewURL) |
| .Resolve(base::StringPrintf("m%d", CHROME_VERSION_MAJOR)); |
| return net::AppendQueryParameter(url, "internal", "true"); |
| } |
| |
| GURL GetWebUIStartupURL() { |
| return net::AppendQueryParameter(GURL(chrome::kChromeUIWhatsNewURL), "auto", |
| "true"); |
| } |
| |
| namespace { |
| |
| void AddWhatsNewTab(Browser* browser) { |
| chrome::AddTabAt(browser, GetWebUIStartupURL(), 0, true); |
| browser->tab_strip_model()->ActivateTabAt( |
| browser->tab_strip_model()->IndexOfFirstNonPinnedTab()); |
| } |
| |
| class WhatsNewFetcher : public BrowserListObserver { |
| public: |
| explicit WhatsNewFetcher( |
| Browser* browser, |
| absl::optional<const GURL> override_url = absl::nullopt) |
| : browser_(browser) { |
| BrowserList::AddObserver(this); |
| 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_handler", 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 |
| } |
| 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>(); |
| // Don't allow redirects when checking if the page is valid for the current |
| // milestone. |
| request->url = |
| override_url.has_value() ? override_url.value() : GetServerURL(false); |
| 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); } |
| |
| // BrowserListObserver: |
| void OnBrowserRemoved(Browser* browser) override { |
| if (browser != browser_) |
| return; |
| |
| browser_closed_or_inactive_ = true; |
| BrowserList::RemoveObserver(this); |
| browser_ = nullptr; |
| } |
| |
| void OnBrowserNoLongerActive(Browser* browser) override { |
| if (browser == browser_) |
| browser_closed_or_inactive_ = true; |
| } |
| |
| void OnBrowserSetLastActive(Browser* browser) override { |
| if (browser == browser_) |
| browser_closed_or_inactive_ = false; |
| } |
| |
| private: |
| 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::unique_ptr<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); |
| success = success && error_or_response_code >= 200 && |
| error_or_response_code <= 299 && 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_) |
| 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; |
| }; |
| |
| } // namespace |
| |
| void StartWhatsNewFetch(Browser* browser) { |
| PrefService* local_state = g_browser_process->local_state(); |
| // Check whether to override the default Whats's New URL |
| if (IsMinimumRefreshVersion() && ShouldShowRefresh(local_state)) { |
| // Set pref to indicate that the refresh page should not attempt to |
| // display again. ShouldShowRefresh should not be called after this |
| // boolean is set to true. |
| local_state->SetBoolean(prefs::kHasShownRefreshWhatsNew, true); |
| new WhatsNewFetcher( |
| browser, net::AppendQueryParameter(GURL(kChromeWhatsNewRefreshURL), |
| "internal", "true")); |
| return; |
| } |
| new WhatsNewFetcher(browser); |
| } |
| |
| } // namespace whats_new |