blob: 4e25f4709de1109fe5cf21a190d94b6dafd9fe19 [file] [log] [blame]
// Copyright 2022 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 "content/browser/preloading_attempt_impl.h"
#include "content/common/state_transitions.h"
#include "content/public/browser/preloading.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
namespace content {
namespace {
void DCHECKTriggeringOutcomeTransitions(PreloadingTriggeringOutcome old_state,
PreloadingTriggeringOutcome new_state) {
#if DCHECK_IS_ON()
static const base::NoDestructor<StateTransitions<PreloadingTriggeringOutcome>>
allowed_transitions(StateTransitions<PreloadingTriggeringOutcome>({
{PreloadingTriggeringOutcome::kUnspecified,
{PreloadingTriggeringOutcome::kDuplicate,
PreloadingTriggeringOutcome::kRunning,
PreloadingTriggeringOutcome::kReady,
PreloadingTriggeringOutcome::kSuccess,
PreloadingTriggeringOutcome::kFailure,
PreloadingTriggeringOutcome::kTriggeredButOutcomeUnknown}},
{PreloadingTriggeringOutcome::kDuplicate, {}},
{PreloadingTriggeringOutcome::kRunning,
{PreloadingTriggeringOutcome::kReady,
PreloadingTriggeringOutcome::kFailure}},
// It can be possible that the preloading attempt may end up failing
// after being ready to use, for cases where we have to cancel the
// attempt for performance and security reasons.
{PreloadingTriggeringOutcome::kReady,
{PreloadingTriggeringOutcome::kSuccess,
PreloadingTriggeringOutcome::kFailure}},
{PreloadingTriggeringOutcome::kSuccess, {}},
{PreloadingTriggeringOutcome::kFailure, {}},
{PreloadingTriggeringOutcome::kTriggeredButOutcomeUnknown, {}},
}));
DCHECK_STATE_TRANSITION(allowed_transitions,
/*old_state=*/old_state,
/*new_state=*/new_state);
#endif // DCHECK_IS_ON()
}
} // namespace
void PreloadingAttemptImpl::SetEligibility(PreloadingEligibility eligibility) {
// Ensure that eligiblity is only set once and that it's set before the
// holdback status and the triggering outcome.
DCHECK_EQ(eligibility_, PreloadingEligibility::kUnspecified);
DCHECK_EQ(holdback_status_, PreloadingHoldbackStatus::kUnspecified);
DCHECK_EQ(triggering_outcome_, PreloadingTriggeringOutcome::kUnspecified);
DCHECK_NE(eligibility, PreloadingEligibility::kUnspecified);
eligibility_ = eligibility;
}
void PreloadingAttemptImpl::SetHoldbackStatus(
PreloadingHoldbackStatus holdback_status) {
// Ensure that the holdback status is only set once and that it's set for
// eligible attempts and before the triggering outcome.
DCHECK_EQ(eligibility_, PreloadingEligibility::kEligible);
DCHECK_EQ(holdback_status_, PreloadingHoldbackStatus::kUnspecified);
DCHECK_EQ(triggering_outcome_, PreloadingTriggeringOutcome::kUnspecified);
DCHECK_NE(holdback_status, PreloadingHoldbackStatus::kUnspecified);
holdback_status_ = holdback_status;
}
void PreloadingAttemptImpl::SetTriggeringOutcome(
PreloadingTriggeringOutcome triggering_outcome) {
// Ensure that the triggering outcome is only set for eligible and
// non-holdback attempts.
DCHECK_EQ(eligibility_, PreloadingEligibility::kEligible);
DCHECK_EQ(holdback_status_, PreloadingHoldbackStatus::kAllowed);
// Check that we do the correct transition before setting
// `triggering_outcome_`.
DCHECKTriggeringOutcomeTransitions(/*old_state=*/triggering_outcome_,
/*new_state=*/triggering_outcome);
triggering_outcome_ = triggering_outcome;
}
void PreloadingAttemptImpl::SetFailureReason(PreloadingFailureReason reason) {
// Ensure that the failure reason is only set once and is only set for
// eligible and non-holdback attempts.
DCHECK_EQ(eligibility_, PreloadingEligibility::kEligible);
DCHECK_EQ(holdback_status_, PreloadingHoldbackStatus::kAllowed);
DCHECK_EQ(failure_reason_, PreloadingFailureReason::kUnspecified);
DCHECK_NE(reason, PreloadingFailureReason::kUnspecified);
SetTriggeringOutcome(PreloadingTriggeringOutcome::kFailure);
failure_reason_ = reason;
}
PreloadingAttemptImpl::PreloadingAttemptImpl(
PreloadingPredictor predictor,
PreloadingType preloading_type,
ukm::SourceId triggered_primary_page_source_id,
base::RepeatingCallback<bool(const GURL&)> url_match_predicate)
: predictor_type_(predictor),
preloading_type_(preloading_type),
triggered_primary_page_source_id_(triggered_primary_page_source_id),
url_match_predicate_(std::move(url_match_predicate)) {}
PreloadingAttemptImpl::~PreloadingAttemptImpl() = default;
void PreloadingAttemptImpl::RecordPreloadingAttemptUKMs(
ukm::SourceId navigated_page_source_id,
const GURL& navigated_url) {
ukm::UkmRecorder* ukm_recorder = ukm::UkmRecorder::Get();
DCHECK(url_match_predicate_);
// Use the predicate to match the URLs as the matching logic varies for each
// predictor.
bool accurate_triggering = url_match_predicate_.Run(navigated_url);
// Ensure that when the `triggering_outcome_` is kSuccess, then the
// accurate_triggering should be true.
if (triggering_outcome_ == PreloadingTriggeringOutcome::kSuccess) {
DCHECK(accurate_triggering)
<< "TriggeringOutcome set to kSuccess without correct prediction\n";
}
// Don't log when the source id is invalid.
if (navigated_page_source_id != ukm::kInvalidSourceId) {
ukm::builders::Preloading_Attempt(navigated_page_source_id)
.SetPreloadingType(static_cast<int64_t>(preloading_type_))
.SetPreloadingPredictor(static_cast<int64_t>(predictor_type_))
.SetEligibility(static_cast<int64_t>(eligibility_))
.SetHoldbackStatus(static_cast<int64_t>(holdback_status_))
.SetTriggeringOutcome(static_cast<int64_t>(triggering_outcome_))
.SetFailureReason(static_cast<int64_t>(failure_reason_))
.SetAccurateTriggering(accurate_triggering)
.Record(ukm_recorder);
}
if (triggered_primary_page_source_id_ != ukm::kInvalidSourceId) {
ukm::builders::Preloading_Attempt_PreviousPrimaryPage(
triggered_primary_page_source_id_)
.SetPreloadingType(static_cast<int64_t>(preloading_type_))
.SetPreloadingPredictor(static_cast<int64_t>(predictor_type_))
.SetEligibility(static_cast<int64_t>(eligibility_))
.SetHoldbackStatus(static_cast<int64_t>(holdback_status_))
.SetTriggeringOutcome(static_cast<int64_t>(triggering_outcome_))
.SetFailureReason(static_cast<int64_t>(failure_reason_))
.SetAccurateTriggering(accurate_triggering)
.Record(ukm_recorder);
}
}
// Used for StateTransitions matching.
std::ostream& operator<<(std::ostream& os,
const PreloadingTriggeringOutcome& outcome) {
switch (outcome) {
case PreloadingTriggeringOutcome::kUnspecified:
os << "Unspecified";
break;
case PreloadingTriggeringOutcome::kDuplicate:
os << "Duplicate";
break;
case PreloadingTriggeringOutcome::kRunning:
os << "Running";
break;
case PreloadingTriggeringOutcome::kReady:
os << "Ready";
break;
case PreloadingTriggeringOutcome::kSuccess:
os << "Success";
break;
case PreloadingTriggeringOutcome::kFailure:
os << "Failure";
break;
case PreloadingTriggeringOutcome::kTriggeredButOutcomeUnknown:
os << "TriggeredButOutcomeUnknown";
break;
}
return os;
}
} // namespace content