| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/previews/previews_lite_page_decider.h" |
| |
| #include "base/callback.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/rand_util.h" |
| #include "base/time/default_tick_clock.h" |
| #include "chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.h" |
| #include "chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings_factory.h" |
| #include "chrome/browser/previews/previews_lite_page_infobar_delegate.h" |
| #include "chrome/browser/previews/previews_lite_page_navigation_throttle.h" |
| #include "chrome/browser/previews/previews_service.h" |
| #include "chrome/browser/previews/previews_service_factory.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/previews/core/previews_experiments.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/navigation_entry.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/navigation_throttle.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_user_data.h" |
| #include "net/base/net_errors.h" |
| |
| namespace { |
| const char kUserNeedsNotification[] = |
| "previews.litepage.user-needs-notification"; |
| } |
| |
| // This WebContentsObserver watches the rest of the current navigation shows a |
| // notification to the user that this preview now exists and will be used on |
| // future eligible page loads. This is only done if the navigations finishes on |
| // the same URL as the one when it began. After finishing the navigation, |this| |
| // will be removed as an observer. |
| class UserNotificationWebContentsObserver |
| : public content::WebContentsObserver, |
| public content::WebContentsUserData<UserNotificationWebContentsObserver> { |
| public: |
| void SetUIShownCallback(base::OnceClosure callback) { |
| ui_shown_callback_ = std::move(callback); |
| } |
| |
| private: |
| friend class content::WebContentsUserData< |
| UserNotificationWebContentsObserver>; |
| |
| explicit UserNotificationWebContentsObserver( |
| content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents) {} |
| |
| void DestroySelf() { |
| content::WebContents* old_web_contents = web_contents(); |
| Observe(nullptr); |
| old_web_contents->RemoveUserData(UserDataKey()); |
| // DO NOT add code past this point. |this| is destroyed. |
| } |
| |
| void DidRedirectNavigation( |
| content::NavigationHandle* navigation_handle) override { |
| DestroySelf(); |
| // DO NOT add code past this point. |this| is destroyed. |
| } |
| |
| void DidFinishNavigation(content::NavigationHandle* handle) override { |
| if (ui_shown_callback_ && handle->GetNetErrorCode() == net::OK) { |
| PreviewsLitePageInfoBarDelegate::Create(web_contents()); |
| std::move(ui_shown_callback_).Run(); |
| } |
| DestroySelf(); |
| // DO NOT add code past this point. |this| is destroyed. |
| } |
| |
| base::OnceClosure ui_shown_callback_; |
| }; |
| |
| PreviewsLitePageDecider::PreviewsLitePageDecider( |
| content::BrowserContext* browser_context) |
| : clock_(base::DefaultTickClock::GetInstance()), |
| page_id_(base::RandUint64()), |
| drp_settings_(nullptr), |
| pref_service_(nullptr) { |
| if (!browser_context) |
| return; |
| |
| DataReductionProxyChromeSettings* drp_settings = |
| DataReductionProxyChromeSettingsFactory::GetForBrowserContext( |
| browser_context); |
| if (!drp_settings) |
| return; |
| |
| DCHECK(!browser_context->IsOffTheRecord()); |
| |
| drp_settings_ = drp_settings; |
| drp_settings_->AddDataReductionProxySettingsObserver(this); |
| |
| Profile* profile = Profile::FromBrowserContext(browser_context); |
| pref_service_ = profile->GetPrefs(); |
| DCHECK(pref_service_); |
| } |
| |
| PreviewsLitePageDecider::~PreviewsLitePageDecider() = default; |
| |
| // static |
| void PreviewsLitePageDecider::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterBooleanPref(kUserNeedsNotification, true); |
| } |
| |
| // static |
| std::unique_ptr<content::NavigationThrottle> |
| PreviewsLitePageDecider::MaybeCreateThrottleFor( |
| content::NavigationHandle* handle) { |
| DCHECK(handle); |
| DCHECK(handle->GetWebContents()); |
| DCHECK(handle->GetWebContents()->GetBrowserContext()); |
| |
| content::BrowserContext* browser_context = |
| handle->GetWebContents()->GetBrowserContext(); |
| |
| PreviewsService* previews_service = PreviewsServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(browser_context)); |
| if (!previews_service) |
| return nullptr; |
| DCHECK(!browser_context->IsOffTheRecord()); |
| |
| PreviewsLitePageDecider* decider = |
| previews_service->previews_lite_page_decider(); |
| DCHECK(decider); |
| |
| // TODO(crbug/842233): Replace this logic with PreviewsState. |
| bool drp_enabled = decider->drp_settings_->IsDataReductionProxyEnabled(); |
| bool preview_enabled = previews::params::ArePreviewsAllowed() && |
| previews::params::IsLitePageServerPreviewsEnabled(); |
| |
| if (drp_enabled && preview_enabled) { |
| return std::make_unique<PreviewsLitePageNavigationThrottle>(handle, |
| decider); |
| } |
| return nullptr; |
| } |
| |
| void PreviewsLitePageDecider::OnProxyRequestHeadersChanged( |
| const net::HttpRequestHeaders& headers) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // This is done so that successive page ids cannot be used to track users |
| // across sessions. These sessions are contained in the chrome-proxy header. |
| page_id_ = base::RandUint64(); |
| } |
| |
| void PreviewsLitePageDecider::OnSettingsInitialized() { |
| // The notification only needs to be shown if the user has never seen it |
| // before, and is an existing Data Saver user. |
| if (!pref_service_->GetBoolean(kUserNeedsNotification)) { |
| need_to_show_notification_ = false; |
| } else if (drp_settings_->IsDataReductionProxyEnabled()) { |
| need_to_show_notification_ = true; |
| } else { |
| need_to_show_notification_ = false; |
| pref_service_->SetBoolean(kUserNeedsNotification, false); |
| } |
| } |
| |
| void PreviewsLitePageDecider::Shutdown() { |
| if (drp_settings_) |
| drp_settings_->RemoveDataReductionProxySettingsObserver(this); |
| } |
| |
| void PreviewsLitePageDecider::SetClockForTesting(const base::TickClock* clock) { |
| clock_ = clock; |
| } |
| |
| void PreviewsLitePageDecider::SetDRPSettingsForTesting( |
| data_reduction_proxy::DataReductionProxySettings* drp_settings) { |
| drp_settings_ = drp_settings; |
| drp_settings_->AddDataReductionProxySettingsObserver(this); |
| } |
| |
| void PreviewsLitePageDecider::ClearSingleBypassForTesting() { |
| single_bypass_.clear(); |
| } |
| |
| void PreviewsLitePageDecider::SetUserHasSeenUINotification() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(pref_service_); |
| need_to_show_notification_ = false; |
| pref_service_->SetBoolean(kUserNeedsNotification, false); |
| } |
| |
| void PreviewsLitePageDecider::SetServerUnavailableFor( |
| base::TimeDelta retry_after) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| base::TimeTicks retry_at = clock_->NowTicks() + retry_after; |
| if (!retry_at_.has_value() || retry_at > retry_at_) |
| retry_at_ = retry_at; |
| } |
| |
| bool PreviewsLitePageDecider::IsServerUnavailable() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (!retry_at_.has_value()) |
| return false; |
| bool server_loadshedding = retry_at_ > clock_->NowTicks(); |
| if (!server_loadshedding) |
| retry_at_.reset(); |
| return server_loadshedding; |
| } |
| |
| void PreviewsLitePageDecider::AddSingleBypass(std::string url) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // Garbage collect any old entries while looking for the one for |url|. |
| auto entry = single_bypass_.end(); |
| for (auto iter = single_bypass_.begin(); iter != single_bypass_.end(); |
| /* no increment */) { |
| if (iter->second < clock_->NowTicks()) { |
| iter = single_bypass_.erase(iter); |
| continue; |
| } |
| if (iter->first == url) |
| entry = iter; |
| ++iter; |
| } |
| |
| // Update the entry for |url|. |
| const base::TimeTicks ttl = |
| clock_->NowTicks() + base::TimeDelta::FromMinutes(5); |
| if (entry == single_bypass_.end()) { |
| single_bypass_.emplace(url, ttl); |
| return; |
| } |
| entry->second = ttl; |
| } |
| |
| bool PreviewsLitePageDecider::CheckSingleBypass(std::string url) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| auto entry = single_bypass_.find(url); |
| if (entry == single_bypass_.end()) |
| return false; |
| return entry->second >= clock_->NowTicks(); |
| } |
| |
| uint64_t PreviewsLitePageDecider::GeneratePageID() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return ++page_id_; |
| } |
| |
| bool PreviewsLitePageDecider::NeedsToNotifyUser() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return need_to_show_notification_; |
| } |
| |
| void PreviewsLitePageDecider::NotifyUser(content::WebContents* web_contents) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(need_to_show_notification_); |
| DCHECK(!UserNotificationWebContentsObserver::FromWebContents(web_contents)); |
| |
| UserNotificationWebContentsObserver::CreateForWebContents(web_contents); |
| UserNotificationWebContentsObserver* observer = |
| UserNotificationWebContentsObserver::FromWebContents(web_contents); |
| |
| // base::Unretained is safe here because |this| outlives |web_contents|. |
| observer->SetUIShownCallback( |
| base::BindOnce(&PreviewsLitePageDecider::SetUserHasSeenUINotification, |
| base::Unretained(this))); |
| } |