| // Copyright 2020 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/performance_hints/performance_hints_observer.h" |
| |
| #include "base/metrics/histogram_functions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h" |
| #include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h" |
| #include "chrome/browser/optimization_guide/optimization_guide_permissions_util.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "components/optimization_guide/optimization_guide_decider.h" |
| #include "components/optimization_guide/url_pattern_with_wildcards.h" |
| #include "content/public/browser/navigation_handle.h" |
| #include "content/public/browser/web_contents.h" |
| #include "url/gurl.h" |
| |
| #if defined(OS_ANDROID) |
| #include "base/android/jni_string.h" |
| #include "chrome/browser/performance_hints/android/jni_headers/PerformanceHintsObserver_jni.h" |
| #endif // OS_ANDROID |
| |
| using optimization_guide::OptimizationGuideDecision; |
| using optimization_guide::URLPatternWithWildcards; |
| using optimization_guide::proto::PerformanceClass; |
| using optimization_guide::proto::PerformanceHint; |
| |
| namespace { |
| |
| // These values are logged to UMA. Entries should not be renumbered and numeric |
| // values should never be reused. Please keep in sync with: |
| // - "PerformanceHintsPerformanceClass" in |
| // src/tools/metrics/histograms/enums.xml |
| // - "PerformanceClass" in |
| // src/components/optimization_guide/proto/performance_hints_metadata.proto |
| enum class UmaPerformanceClass { |
| kUnknown = 0, |
| kSlow = 1, |
| kFast = 2, |
| kNormal = 3, |
| kMaxValue = kNormal, |
| }; |
| |
| UmaPerformanceClass ToUmaPerformanceClass(PerformanceClass performance_class) { |
| if (static_cast<int>(performance_class) < 0) { |
| NOTREACHED(); |
| return UmaPerformanceClass::kUnknown; |
| } else if (static_cast<int>(performance_class) > |
| static_cast<int>(UmaPerformanceClass::kMaxValue)) { |
| NOTREACHED(); |
| return UmaPerformanceClass::kUnknown; |
| } else { |
| return static_cast<UmaPerformanceClass>(performance_class); |
| } |
| } |
| |
| } // namespace |
| |
| #if defined(OS_ANDROID) |
| static jint JNI_PerformanceHintsObserver_GetPerformanceClassForURL( |
| JNIEnv* env, |
| const base::android::JavaParamRef<jobject>& java_web_contents, |
| const base::android::JavaParamRef<jstring>& url) { |
| content::WebContents* web_contents = |
| content::WebContents::FromJavaWebContents(java_web_contents); |
| return PerformanceHintsObserver::PerformanceClassForURLInternal( |
| web_contents, GURL(base::android::ConvertJavaStringToUTF8(url)), |
| /*record_metrics=*/false); |
| } |
| #endif // OS_ANDROID |
| |
| const base::Feature kPerformanceHintsObserver{ |
| "PerformanceHintsObserver", base::FEATURE_DISABLED_BY_DEFAULT}; |
| const base::Feature kPerformanceHintsTreatUnknownAsFast{ |
| "PerformanceHintsTreatUnknownAsFast", base::FEATURE_DISABLED_BY_DEFAULT}; |
| |
| PerformanceHintsObserver::PerformanceHintsObserver( |
| content::WebContents* web_contents) |
| : content::WebContentsObserver(web_contents) { |
| optimization_guide_decider_ = |
| OptimizationGuideKeyedServiceFactory::GetForProfile( |
| Profile::FromBrowserContext(web_contents->GetBrowserContext())); |
| if (optimization_guide_decider_) { |
| optimization_guide_decider_->RegisterOptimizationTypesAndTargets( |
| {optimization_guide::proto::PERFORMANCE_HINTS}, {}); |
| } |
| } |
| |
| PerformanceHintsObserver::~PerformanceHintsObserver() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| // static |
| PerformanceClass PerformanceHintsObserver::PerformanceClassForURL( |
| content::WebContents* web_contents, |
| const GURL& url) { |
| return PerformanceClassForURLInternal(web_contents, url, |
| /*record_metrics=*/true); |
| } |
| |
| // static |
| PerformanceClass PerformanceHintsObserver::PerformanceClassForURLInternal( |
| content::WebContents* web_contents, |
| const GURL& url, |
| bool record_metrics) { |
| if (web_contents == nullptr) { |
| return PerformanceClass::PERFORMANCE_UNKNOWN; |
| } |
| |
| Profile* profile = |
| Profile::FromBrowserContext(web_contents->GetBrowserContext()); |
| if (!profile || !IsUserPermittedToFetchFromRemoteOptimizationGuide(profile)) { |
| // We can't get performance hints if OptimizationGuide can't fetch them. |
| return PerformanceClass::PERFORMANCE_UNKNOWN; |
| } |
| |
| PerformanceHintsObserver* performance_hints_observer = |
| PerformanceHintsObserver::FromWebContents(web_contents); |
| if (performance_hints_observer == nullptr) { |
| return PerformanceClass::PERFORMANCE_UNKNOWN; |
| } |
| |
| HintForURLResult hint_result; |
| base::Optional<PerformanceHint> hint; |
| std::tie(hint_result, hint) = performance_hints_observer->HintForURL(url); |
| if (record_metrics) { |
| base::UmaHistogramEnumeration("PerformanceHints.Observer.HintForURLResult", |
| hint_result); |
| } |
| |
| PerformanceClass performance_class; |
| switch (hint_result) { |
| case HintForURLResult::kHintFound: |
| case HintForURLResult::kHintNotFound: |
| case HintForURLResult::kHintNotReady: |
| performance_class = hint ? hint->performance_class() |
| : PerformanceClass::PERFORMANCE_UNKNOWN; |
| break; |
| case HintForURLResult::kInvalidURL: |
| default: |
| // Error or unknown case. Don't allow the override. |
| return PerformanceClass::PERFORMANCE_UNKNOWN; |
| } |
| |
| if (record_metrics) { |
| // Log to UMA before the override logic so we can determine how often the |
| // override is happening. |
| base::UmaHistogramEnumeration( |
| "PerformanceHints.Observer.PerformanceClassForURL", |
| ToUmaPerformanceClass(performance_class)); |
| } |
| |
| if (performance_class == PerformanceClass::PERFORMANCE_UNKNOWN && |
| base::FeatureList::IsEnabled(kPerformanceHintsTreatUnknownAsFast)) { |
| // If we couldn't get the hint or we didn't expect it on this page, give it |
| // the benefit of the doubt. |
| return PerformanceClass::PERFORMANCE_FAST; |
| } |
| |
| return performance_class; |
| } |
| |
| // static |
| void PerformanceHintsObserver::RecordPerformanceUMAForURL( |
| content::WebContents* web_contents, |
| const GURL& url) { |
| PerformanceClassForURLInternal(web_contents, url, |
| /*record_metrics=*/true); |
| } |
| |
| std::tuple<PerformanceHintsObserver::HintForURLResult, |
| base::Optional<PerformanceHint>> |
| PerformanceHintsObserver::HintForURL(const GURL& url) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| base::Optional<PerformanceHint> hint; |
| HintForURLResult hint_result = HintForURLResult::kHintNotFound; |
| |
| if (!hint_processed_) { |
| hint_result = HintForURLResult::kHintNotReady; |
| } else if (!url.is_valid() || !url.SchemeIsHTTPOrHTTPS()) { |
| hint_result = HintForURLResult::kInvalidURL; |
| } else { |
| for (const auto& pattern_hint : hints_) { |
| if (pattern_hint.first.Matches(url.spec())) { |
| hint_result = HintForURLResult::kHintFound; |
| hint = pattern_hint.second; |
| break; |
| } |
| } |
| } |
| |
| return {hint_result, hint}; |
| } |
| |
| void PerformanceHintsObserver::DidFinishNavigation( |
| content::NavigationHandle* navigation_handle) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(navigation_handle); |
| if (!navigation_handle->IsInMainFrame() || |
| navigation_handle->IsSameDocument() || |
| !navigation_handle->HasCommitted()) { |
| // Use the same hints if the main frame hasn't changed. |
| return; |
| } |
| |
| // We've navigated to a new page, so clear out any hints from the previous |
| // page. |
| hints_.clear(); |
| hint_processed_ = false; |
| |
| if (!optimization_guide_decider_) { |
| return; |
| } |
| if (navigation_handle->IsErrorPage()) { |
| // Don't provide hints on Chrome error pages. |
| return; |
| } |
| |
| optimization_guide_decider_->CanApplyOptimizationAsync( |
| navigation_handle, optimization_guide::proto::PERFORMANCE_HINTS, |
| base::BindOnce(&PerformanceHintsObserver::ProcessPerformanceHint, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| void PerformanceHintsObserver::ProcessPerformanceHint( |
| OptimizationGuideDecision decision, |
| const optimization_guide::OptimizationMetadata& optimization_metadata) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| hint_processed_ = true; |
| |
| if (decision != OptimizationGuideDecision::kTrue) { |
| // Apply results are counted under |
| // OptimizationGuide.ApplyDecision.PerformanceHints. |
| return; |
| } |
| |
| if (!optimization_metadata.performance_hints_metadata()) |
| return; |
| |
| const optimization_guide::proto::PerformanceHintsMetadata |
| performance_hints_metadata = |
| optimization_metadata.performance_hints_metadata().value(); |
| for (const PerformanceHint& hint : |
| performance_hints_metadata.performance_hints()) { |
| hints_.emplace_back(URLPatternWithWildcards(hint.wildcard_pattern()), hint); |
| } |
| } |
| |
| WEB_CONTENTS_USER_DATA_KEY_IMPL(PerformanceHintsObserver) |