blob: cc16e22fdc5f05379be937205de857c2fc055f74 [file] [log] [blame]
// Copyright 2017 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 "components/previews/content/previews_optimization_guide.h"
#include "base/base64.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/metrics/histogram_macros.h"
#include "base/task/post_task.h"
#include "base/task_runner_util.h"
#include "components/optimization_guide/hints_component_info.h"
#include "components/optimization_guide/optimization_guide_service.h"
#include "components/optimization_guide/proto/hints.pb.h"
#include "components/previews/content/hint_cache_leveldb_store.h"
#include "components/previews/content/previews_hints.h"
#include "components/previews/content/previews_hints_util.h"
#include "components/previews/content/previews_user_data.h"
#include "components/previews/core/previews_constants.h"
#include "components/previews/core/previews_switches.h"
#include "url/gurl.h"
namespace previews {
namespace {
// The component version used with a manual config. This ensures that any hint
// component received from the OptimizationGuideService on a subsequent startup
// will have a newer version than it.
constexpr char kManualConfigComponentVersion[] = "0.0.0";
// Hints are purged during startup if the explicit purge switch exists or if
// a proto override is being used--in which case the hints need to come from the
// override instead.
bool ShouldPurgeHintCacheStoreOnStartup() {
base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
return cmd_line->HasSwitch(switches::kHintsProtoOverride) ||
cmd_line->HasSwitch(switches::kPurgeHintCacheStore);
}
// Available hint components are only processed if a proto override isn't being
// used; otherwise, the hints from the proto override are used instead.
bool IsHintComponentProcessingDisabled() {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kHintsProtoOverride);
}
// Attempts to parse a base64 encoded Optimization Guide Configuration proto
// from the command line. If no proto is given or if it is encoded incorrectly,
// nullptr is returned.
std::unique_ptr<optimization_guide::proto::Configuration>
ParseHintsProtoFromCommandLine() {
base::CommandLine* cmd_line = base::CommandLine::ForCurrentProcess();
if (!cmd_line->HasSwitch(switches::kHintsProtoOverride))
return nullptr;
std::string b64_pb =
cmd_line->GetSwitchValueASCII(switches::kHintsProtoOverride);
std::string binary_pb;
if (!base::Base64Decode(b64_pb, &binary_pb)) {
LOG(ERROR) << "Invalid base64 encoding of the Hints Proto Override";
return nullptr;
}
std::unique_ptr<optimization_guide::proto::Configuration>
proto_configuration =
std::make_unique<optimization_guide::proto::Configuration>();
if (!proto_configuration->ParseFromString(binary_pb)) {
LOG(ERROR) << "Invalid proto provided to the Hints Proto Override";
return nullptr;
}
return proto_configuration;
}
} // namespace
PreviewsOptimizationGuide::PreviewsOptimizationGuide(
optimization_guide::OptimizationGuideService* optimization_guide_service,
const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
const base::FilePath& profile_path)
: optimization_guide_service_(optimization_guide_service),
ui_task_runner_(ui_task_runner),
background_task_runner_(base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT})),
hint_cache_(std::make_unique<HintCache>(
std::make_unique<HintCacheLevelDBStore>(profile_path,
background_task_runner_))),
ui_weak_ptr_factory_(this) {
DCHECK(optimization_guide_service_);
hint_cache_->Initialize(
ShouldPurgeHintCacheStoreOnStartup(),
base::BindOnce(&PreviewsOptimizationGuide::OnHintCacheInitialized,
ui_weak_ptr_factory_.GetWeakPtr()));
}
PreviewsOptimizationGuide::~PreviewsOptimizationGuide() {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
optimization_guide_service_->RemoveObserver(this);
}
bool PreviewsOptimizationGuide::IsWhitelisted(
PreviewsUserData* previews_data,
const GURL& url,
PreviewsType type,
net::EffectiveConnectionType* out_ect_threshold) const {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
if (!hints_) {
return false;
}
*out_ect_threshold = params::GetECTThresholdForPreview(type);
int inflation_percent = 0;
if (!hints_->IsWhitelisted(url, type, &inflation_percent,
out_ect_threshold)) {
return false;
}
if (inflation_percent != 0 && previews_data) {
previews_data->set_data_savings_inflation_percent(inflation_percent);
}
return true;
}
bool PreviewsOptimizationGuide::IsBlacklisted(const GURL& url,
PreviewsType type) const {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
if (!hints_) {
return false;
}
return hints_->IsBlacklisted(url, type);
}
void PreviewsOptimizationGuide::OnLoadedHint(
base::OnceClosure callback,
const GURL& document_url,
const optimization_guide::proto::Hint* loaded_hint) const {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
// Record that the hint finished loading. This is used as a signal during
// tests.
LOCAL_HISTOGRAM_BOOLEAN(
kPreviewsOptimizationGuideOnLoadedHintResultHistogramString, loaded_hint);
// Run the callback now that the hint is loaded. This is used as a signal by
// tests.
std::move(callback).Run();
}
bool PreviewsOptimizationGuide::MaybeLoadOptimizationHints(
const GURL& url,
base::OnceClosure callback) {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
if (!hints_) {
return false;
}
return hints_->MaybeLoadOptimizationHints(
url, base::BindOnce(&PreviewsOptimizationGuide::OnLoadedHint,
ui_weak_ptr_factory_.GetWeakPtr(),
std::move(callback), url));
}
bool PreviewsOptimizationGuide::GetResourceLoadingHints(
const GURL& url,
std::vector<std::string>* out_resource_patterns_to_block) const {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
if (!hints_)
return false;
return hints_->GetResourceLoadingHints(url, out_resource_patterns_to_block);
}
void PreviewsOptimizationGuide::LogHintCacheMatch(
const GURL& url,
bool is_committed,
net::EffectiveConnectionType ect) const {
if (!hints_) {
return;
}
hints_->LogHintCacheMatch(url, is_committed, ect);
}
void PreviewsOptimizationGuide::OnHintCacheInitialized() {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
// Check if there is a valid hint proto given on the command line first. We
// don't normally expect one, but if one is provided then use that and do not
// register as an observer as the opt_guide service.
std::unique_ptr<optimization_guide::proto::Configuration> manual_config =
ParseHintsProtoFromCommandLine();
if (manual_config) {
// Allow |UpdateHints| to block startup so that the first navigation gets
// the hints when a command line hint proto is provided.
UpdateHints(PreviewsHints::CreateFromHintsConfiguration(
std::move(manual_config),
hint_cache_->MaybeCreateComponentUpdateData(
base::Version(kManualConfigComponentVersion))));
}
// Register as an observer regardless of hint proto override usage. This is
// needed as a signal during testing.
optimization_guide_service_->AddObserver(this);
}
void PreviewsOptimizationGuide::OnHintsComponentAvailable(
const optimization_guide::HintsComponentInfo& info) {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
// Check for if hint component is disabled. This check is needed because the
// optimization guide still registers with the service as an observer for
// components as a signal during testing.
if (IsHintComponentProcessingDisabled()) {
return;
}
// Create PreviewsHints from the newly available component on a background
// thread, providing a ComponentUpdateData from the hint cache, so that each
// hint within the component can be moved into it. In the case where the
// component's version is not newer than the hint cache store's component
// version, ComponentUpdateData will be a nullptr and hint processing will be
// skipped. After PreviewsHints::Create() returns the newly created
// PreviewsHints, it is initialized in UpdateHints() on the UI thread.
base::PostTaskAndReplyWithResult(
background_task_runner_.get(), FROM_HERE,
base::BindOnce(&PreviewsHints::CreateFromHintsComponent, info,
hint_cache_->MaybeCreateComponentUpdateData(info.version)),
base::BindOnce(&PreviewsOptimizationGuide::UpdateHints,
ui_weak_ptr_factory_.GetWeakPtr()));
}
void PreviewsOptimizationGuide::UpdateHints(
std::unique_ptr<PreviewsHints> hints) {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
hints_ = std::move(hints);
if (hints_) {
hints_->Initialize(
hint_cache_.get(),
base::BindOnce(&PreviewsOptimizationGuide::OnHintsUpdated,
ui_weak_ptr_factory_.GetWeakPtr()));
} else {
OnHintsUpdated();
}
}
void PreviewsOptimizationGuide::OnHintsUpdated() {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
// Record the result of updating the hints. This is used as a signal for the
// hints being fully processed in testing.
LOCAL_HISTOGRAM_BOOLEAN(
kPreviewsOptimizationGuideUpdateHintsResultHistogramString,
hints_ != NULL);
}
} // namespace previews