blob: 3b0d4878141a0b65314e525d9e1d690f503d694e [file] [log] [blame]
// Copyright 2016 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_io_data.h"
#include <algorithm>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/stringprintf.h"
#include "base/time/default_clock.h"
#include "components/previews/content/previews_ui_service.h"
#include "components/previews/core/previews_experiments.h"
#include "components/previews/core/previews_opt_out_store.h"
#include "net/base/load_flags.h"
#include "net/nqe/network_quality_estimator.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_context.h"
#include "url/gurl.h"
namespace previews {
namespace {
void LogPreviewsEligibilityReason(PreviewsEligibilityReason status,
PreviewsType type) {
int32_t max_limit = static_cast<int32_t>(PreviewsEligibilityReason::LAST);
base::LinearHistogram::FactoryGet(
base::StringPrintf("Previews.EligibilityReason.%s",
GetStringNameForType(type).c_str()),
1, max_limit, max_limit + 1,
base::HistogramBase::kUmaTargetedHistogramFlag)
->Add(static_cast<int>(status));
}
bool AllowedOnReload(PreviewsType type) {
switch (type) {
// These types return new content on refresh.
case PreviewsType::LITE_PAGE:
case PreviewsType::LOFI:
case PreviewsType::AMP_REDIRECTION:
case PreviewsType::NOSCRIPT:
return true;
// Loading these types will always be stale when refreshed.
case PreviewsType::OFFLINE:
return false;
case PreviewsType::NONE:
case PreviewsType::LAST:
break;
}
NOTREACHED();
return false;
}
bool IsServerWhitelistedType(PreviewsType type) {
switch (type) {
// These types check server whitelist, if available.
case PreviewsType::NOSCRIPT:
return true;
case PreviewsType::OFFLINE:
case PreviewsType::LITE_PAGE:
case PreviewsType::LOFI:
case PreviewsType::AMP_REDIRECTION:
return false;
case PreviewsType::NONE:
case PreviewsType::LAST:
break;
}
NOTREACHED();
return false;
}
} // namespace
PreviewsIOData::PreviewsIOData(
const scoped_refptr<base::SingleThreadTaskRunner>& ui_task_runner,
const scoped_refptr<base::SingleThreadTaskRunner>& io_task_runner)
: blacklist_ignored_(false),
ui_task_runner_(ui_task_runner),
io_task_runner_(io_task_runner),
page_id_(1u),
weak_factory_(this) {}
PreviewsIOData::~PreviewsIOData() {}
void PreviewsIOData::Initialize(
base::WeakPtr<PreviewsUIService> previews_ui_service,
std::unique_ptr<PreviewsOptOutStore> previews_opt_out_store,
std::unique_ptr<PreviewsOptimizationGuide> previews_opt_guide,
const PreviewsIsEnabledCallback& is_enabled_callback) {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
is_enabled_callback_ = is_enabled_callback;
previews_ui_service_ = previews_ui_service;
previews_opt_guide_ = std::move(previews_opt_guide);
// Set up the IO thread portion of |this|.
io_task_runner_->PostTask(
FROM_HERE,
base::Bind(&PreviewsIOData::InitializeOnIOThread, base::Unretained(this),
base::Passed(&previews_opt_out_store)));
}
void PreviewsIOData::OnNewBlacklistedHost(const std::string& host,
base::Time time) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
ui_task_runner_->PostTask(FROM_HERE,
base::Bind(&PreviewsUIService::OnNewBlacklistedHost,
previews_ui_service_, host, time));
}
void PreviewsIOData::OnUserBlacklistedStatusChange(bool blacklisted) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
ui_task_runner_->PostTask(
FROM_HERE, base::Bind(&PreviewsUIService::OnUserBlacklistedStatusChange,
previews_ui_service_, blacklisted));
}
void PreviewsIOData::OnBlacklistCleared(base::Time time) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
ui_task_runner_->PostTask(FROM_HERE,
base::Bind(&PreviewsUIService::OnBlacklistCleared,
previews_ui_service_, time));
}
void PreviewsIOData::InitializeOnIOThread(
std::unique_ptr<PreviewsOptOutStore> previews_opt_out_store) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
previews_black_list_.reset(
new PreviewsBlackList(std::move(previews_opt_out_store),
base::MakeUnique<base::DefaultClock>(), this));
ui_task_runner_->PostTask(
FROM_HERE, base::Bind(&PreviewsUIService::SetIOData, previews_ui_service_,
weak_factory_.GetWeakPtr()));
}
void PreviewsIOData::SetPreviewsBlacklistForTesting(
std::unique_ptr<PreviewsBlackList> previews_back_list) {
previews_black_list_ = std::move(previews_back_list);
}
void PreviewsIOData::LogPreviewNavigation(const GURL& url,
bool opt_out,
PreviewsType type,
base::Time time) const {
ui_task_runner_->PostTask(
FROM_HERE, base::Bind(&PreviewsUIService::LogPreviewNavigation,
previews_ui_service_, url, type, opt_out, time));
}
void PreviewsIOData::LogPreviewDecisionMade(PreviewsEligibilityReason reason,
const GURL& url,
base::Time time,
PreviewsType type) const {
LogPreviewsEligibilityReason(reason, type);
ui_task_runner_->PostTask(
FROM_HERE, base::Bind(&PreviewsUIService::LogPreviewDecisionMade,
previews_ui_service_, reason, url, time, type));
}
void PreviewsIOData::AddPreviewNavigation(const GURL& url,
bool opt_out,
PreviewsType type) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
base::Time time =
previews_black_list_->AddPreviewNavigation(url, opt_out, type);
LogPreviewNavigation(url, opt_out, type, time);
}
void PreviewsIOData::ClearBlackList(base::Time begin_time,
base::Time end_time) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
previews_black_list_->ClearBlackList(begin_time, end_time);
}
void PreviewsIOData::SetIgnorePreviewsBlacklistDecision(bool ignored) {
DCHECK(io_task_runner_->BelongsToCurrentThread());
blacklist_ignored_ = ignored;
ui_task_runner_->PostTask(
FROM_HERE,
base::Bind(&PreviewsUIService::OnIgnoreBlacklistDecisionStatusChanged,
previews_ui_service_, blacklist_ignored_));
}
bool PreviewsIOData::ShouldAllowPreview(const net::URLRequest& request,
PreviewsType type) const {
return ShouldAllowPreviewAtECT(request, type,
params::GetECTThresholdForPreview(type),
std::vector<std::string>());
}
bool PreviewsIOData::ShouldAllowPreviewAtECT(
const net::URLRequest& request,
PreviewsType type,
net::EffectiveConnectionType effective_connection_type_threshold,
const std::vector<std::string>& host_blacklist_from_server) const {
if (!request.url().has_host()) {
// Don't capture UMA on this case, as it is not important and can happen
// when navigating to files on disk, etc.
return false;
}
if (is_enabled_callback_.is_null() || !previews_black_list_) {
LogPreviewDecisionMade(PreviewsEligibilityReason::BLACKLIST_UNAVAILABLE,
request.url(), base::Time::Now(), type);
return false;
}
if (!is_enabled_callback_.Run(type))
return false;
if (!blacklist_ignored_) {
// The blacklist will disallow certain hosts for periods of time based on
// user's opting out of the preview.
PreviewsEligibilityReason status =
previews_black_list_->IsLoadedAndAllowed(request.url(), type);
if (status != PreviewsEligibilityReason::ALLOWED) {
LogPreviewDecisionMade(status, request.url(), base::Time::Now(), type);
return false;
}
}
if (effective_connection_type_threshold !=
net::EFFECTIVE_CONNECTION_TYPE_LAST) {
net::NetworkQualityEstimator* network_quality_estimator =
request.context()->network_quality_estimator();
if (!network_quality_estimator ||
network_quality_estimator->GetEffectiveConnectionType() <
net::EFFECTIVE_CONNECTION_TYPE_OFFLINE) {
LogPreviewDecisionMade(
PreviewsEligibilityReason::NETWORK_QUALITY_UNAVAILABLE, request.url(),
base::Time::Now(), type);
return false;
}
if (network_quality_estimator->GetEffectiveConnectionType() >
effective_connection_type_threshold) {
LogPreviewDecisionMade(PreviewsEligibilityReason::NETWORK_NOT_SLOW,
request.url(), base::Time::Now(), type);
return false;
}
}
// LOAD_VALIDATE_CACHE or LOAD_BYPASS_CACHE mean the user reloaded the page.
// If this is a query for offline previews, reloads should be disallowed.
if (!AllowedOnReload(type) &&
request.load_flags() &
(net::LOAD_VALIDATE_CACHE | net::LOAD_BYPASS_CACHE)) {
LogPreviewDecisionMade(PreviewsEligibilityReason::RELOAD_DISALLOWED,
request.url(), base::Time::Now(), type);
return false;
}
// Check provided blacklist, if any. This type of blacklist was added for
// Finch provided blacklist for Client LoFi.
if (std::find(host_blacklist_from_server.begin(),
host_blacklist_from_server.end(), request.url().host_piece()) !=
host_blacklist_from_server.end()) {
LogPreviewDecisionMade(
PreviewsEligibilityReason::HOST_BLACKLISTED_BY_SERVER, request.url(),
base::Time::Now(), type);
return false;
}
// Check whitelist from the server, if provided.
if (IsServerWhitelistedType(type)) {
if (params::IsOptimizationHintsEnabled()) {
// Optimization hints are configured, so require whitelist match.
if (!previews_opt_guide_ ||
!previews_opt_guide_->IsWhitelisted(request, type)) {
LogPreviewDecisionMade(
PreviewsEligibilityReason::HOST_NOT_WHITELISTED_BY_SERVER,
request.url(), base::Time::Now(), type);
return false;
}
} else {
// Since server optimization guidance not configure, allow the preview
// but with qualified eligibility reason.
LogPreviewDecisionMade(
PreviewsEligibilityReason::ALLOWED_WITHOUT_OPTIMIZATION_HINTS,
request.url(), base::Time::Now(), type);
return true;
}
}
LogPreviewDecisionMade(PreviewsEligibilityReason::ALLOWED, request.url(),
base::Time::Now(), type);
return true;
}
uint64_t PreviewsIOData::GeneratePageId() {
return ++page_id_;
}
} // namespace previews