blob: 82eb0cbe00d3e556d9eb3724be6850bf20fa87bf [file] [log] [blame]
// 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)