Commit f1b59127 authored by Chris Hamilton's avatar Chris Hamilton Committed by Commit Bot

Create DecisionDetails class.

This will be used to encode detailed reasons describing decision taken by
the resource_coordinator policy engine. These will drive web UI and UKM
reporting.

BUG=753486

Change-Id: Iadf7620d75710ed36ce334cc5e4e116cd8ed6c0b
Reviewed-on: https://chromium-review.googlesource.com/1064700
Commit-Queue: Chris Hamilton <chrisha@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/master@{#560774}
parent 91589a91
......@@ -2634,6 +2634,8 @@ jumbo_split_static_library("browser") {
"repost_form_warning_controller.h",
"resource_coordinator/background_tab_navigation_throttle.cc",
"resource_coordinator/background_tab_navigation_throttle.h",
"resource_coordinator/decision_details.cc",
"resource_coordinator/decision_details.h",
"resource_coordinator/discard_metrics_lifecycle_unit_observer.cc",
"resource_coordinator/discard_metrics_lifecycle_unit_observer.h",
"resource_coordinator/discard_reason.h",
......
// Copyright 2018 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/resource_coordinator/decision_details.h"
#include "base/stl_util.h"
#include "services/metrics/public/cpp/ukm_builders.h"
namespace resource_coordinator {
namespace {
// These are intended to be human readable descriptions of the various failure
// reasons. They don't need to be localized as they are for a developer-only
// WebUI.
const char* kDecisionFailureReasonStrings[] = {
"Browser opted out via enterprise policy",
"Origin opted out via feature policy",
"Origin is in global blacklist",
"Origin has been observed playing audio while backgrounded",
"Origin has been observed updating favicon while backgrounded",
"Origin is temporarily protected while under observation",
"Origin has been observed emitting notifications while backgrounded",
"Origin has been observed updating title while backgrounded",
"Tab is currently capturing the camera and/or microphone",
"Tab has been protected by an extension",
"Tab contains unsubmitted text form entry",
"Tab contains a PDF",
"Tab content is being mirrored/cast",
"Tab is currently emitting audio",
"Tab is currently using WebSockets",
"Tab is currently using WebUSB",
"Tab is currently visible",
};
static_assert(base::size(kDecisionFailureReasonStrings) ==
static_cast<size_t>(DecisionFailureReason::MAX),
"kDecisionFailureReasonStrings not up to date with enum");
const char* kDecisionSuccessReasonStrings[] = {
"Origin opted in via feature policy", "Origin is in global whitelist",
"Origin has locally been observed to be safe via heuristic logic",
};
static_assert(base::size(kDecisionSuccessReasonStrings) ==
static_cast<size_t>(DecisionSuccessReason::MAX),
"kDecisionSuccessReasonStrings not up to date with enum");
void PopulateSuccessReason(
DecisionSuccessReason success_reason,
ukm::builders::TabManager_LifecycleStateChange* ukm) {
switch (success_reason) {
case DecisionSuccessReason::INVALID:
break;
case DecisionSuccessReason::LIFECYCLES_FEATURE_POLICY_OPT_IN:
ukm->SetSuccessLifecyclesFeaturePolicyOptIn(1);
break;
case DecisionSuccessReason::GLOBAL_WHITELIST:
ukm->SetSuccessGlobalWhitelist(1);
break;
case DecisionSuccessReason::HEURISTIC_OBSERVED_TO_BE_SAFE:
ukm->SetSuccessHeuristic(1);
break;
case DecisionSuccessReason::MAX:
break;
}
}
void PopulateFailureReason(
DecisionFailureReason failure_reason,
ukm::builders::TabManager_LifecycleStateChange* ukm) {
switch (failure_reason) {
case DecisionFailureReason::INVALID:
break;
case DecisionFailureReason::LIFECYCLES_ENTERPRISE_POLICY_OPT_OUT:
ukm->SetFailureLifecyclesEnterprisePolicyOptOut(1);
break;
case DecisionFailureReason::LIFECYCLES_FEATURE_POLICY_OPT_OUT:
ukm->SetFailureLifecyclesFeaturePolicyOptOut(1);
break;
case DecisionFailureReason::GLOBAL_BLACKLIST:
ukm->SetFailureGlobalBlacklist(1);
break;
case DecisionFailureReason::HEURISTIC_AUDIO:
ukm->SetFailureHeuristicAudio(1);
break;
case DecisionFailureReason::HEURISTIC_FAVICON:
ukm->SetFailureHeuristicFavicon(1);
break;
case DecisionFailureReason::HEURISTIC_INSUFFICIENT_OBSERVATION:
ukm->SetFailureHeuristicInsufficientObservation(1);
break;
case DecisionFailureReason::HEURISTIC_NOTIFICATIONS:
ukm->SetFailureHeuristicNotifications(1);
break;
case DecisionFailureReason::HEURISTIC_TITLE:
ukm->SetFailureHeuristicTitle(1);
break;
case DecisionFailureReason::LIVE_STATE_CAPTURING:
ukm->SetFailureLiveStateCapturing(1);
break;
case DecisionFailureReason::LIVE_STATE_EXTENSION_DISALLOWED:
ukm->SetFailureLiveStateExtensionDisallowed(1);
break;
case DecisionFailureReason::LIVE_STATE_FORM_ENTRY:
ukm->SetFailureLiveStateFormEntry(1);
break;
case DecisionFailureReason::LIVE_STATE_IS_PDF:
ukm->SetFailureLiveStateIsPDF(1);
break;
case DecisionFailureReason::LIVE_STATE_MIRRORING:
ukm->SetFailureLiveStateMirroring(1);
break;
case DecisionFailureReason::LIVE_STATE_PLAYING_AUDIO:
ukm->SetFailureLiveStatePlayingAudio(1);
break;
case DecisionFailureReason::LIVE_STATE_USING_WEB_SOCKETS:
ukm->SetFailureLiveStateUsingWebSockets(1);
break;
case DecisionFailureReason::LIVE_STATE_USING_WEB_USB:
ukm->SetFailureLiveStateUsingWebUSB(1);
break;
case DecisionFailureReason::LIVE_STATE_VISIBLE:
ukm->SetFailureLiveStateVisible(1);
break;
case DecisionFailureReason::MAX:
break;
}
}
} // namespace
const char* ToString(DecisionFailureReason failure_reason) {
if (failure_reason == DecisionFailureReason::INVALID ||
failure_reason == DecisionFailureReason::MAX)
return nullptr;
return kDecisionFailureReasonStrings[static_cast<size_t>(failure_reason)];
}
const char* ToString(DecisionSuccessReason success_reason) {
if (success_reason == DecisionSuccessReason::INVALID ||
success_reason == DecisionSuccessReason::MAX)
return nullptr;
return kDecisionSuccessReasonStrings[static_cast<size_t>(success_reason)];
}
DecisionDetails::Reason::Reason()
: success_reason_(DecisionSuccessReason::INVALID),
failure_reason_(DecisionFailureReason::INVALID) {}
DecisionDetails::Reason::Reason(DecisionSuccessReason success_reason)
: success_reason_(success_reason),
failure_reason_(DecisionFailureReason::INVALID) {
DCHECK(IsSuccess());
}
DecisionDetails::Reason::Reason(DecisionFailureReason failure_reason)
: success_reason_(DecisionSuccessReason::INVALID),
failure_reason_(failure_reason) {
DCHECK(IsFailure());
}
DecisionDetails::Reason::Reason(const Reason& rhs) = default;
DecisionDetails::Reason::~Reason() = default;
DecisionDetails::Reason& DecisionDetails::Reason::operator=(const Reason& rhs) =
default;
bool DecisionDetails::Reason::IsValid() const {
return IsSuccess() || IsFailure();
}
bool DecisionDetails::Reason::IsSuccess() const {
if (success_reason_ == DecisionSuccessReason::INVALID ||
success_reason_ == DecisionSuccessReason::MAX ||
failure_reason_ != DecisionFailureReason::INVALID)
return false;
return true;
}
bool DecisionDetails::Reason::IsFailure() const {
if (failure_reason_ == DecisionFailureReason::INVALID ||
failure_reason_ == DecisionFailureReason::MAX ||
success_reason_ != DecisionSuccessReason::INVALID)
return false;
return true;
}
DecisionSuccessReason DecisionDetails::Reason::success_reason() const {
DCHECK(IsSuccess());
return success_reason_;
}
DecisionFailureReason DecisionDetails::Reason::failure_reason() const {
DCHECK(IsFailure());
return failure_reason_;
}
const char* DecisionDetails::Reason::ToString() const {
if (!IsValid())
return nullptr;
if (IsSuccess())
return ::resource_coordinator::ToString(success_reason_);
DCHECK(IsFailure());
return ::resource_coordinator::ToString(failure_reason_);
}
bool DecisionDetails::Reason::operator==(const Reason& rhs) const {
return success_reason_ == rhs.success_reason_ &&
failure_reason_ == rhs.failure_reason_;
}
bool DecisionDetails::Reason::operator!=(const Reason& rhs) const {
return !(*this == rhs);
}
DecisionDetails::DecisionDetails() : toggled_(false) {}
DecisionDetails::~DecisionDetails() = default;
bool DecisionDetails::AddReason(const Reason& reason) {
reasons_.push_back(reason);
return CheckIfToggled();
}
bool DecisionDetails::AddReason(DecisionFailureReason failure_reason) {
reasons_.push_back(Reason(failure_reason));
return CheckIfToggled();
}
bool DecisionDetails::AddReason(DecisionSuccessReason success_reason) {
reasons_.push_back(Reason(success_reason));
return CheckIfToggled();
}
bool DecisionDetails::IsPositive() const {
// A decision without supporting reasons is negative by default.
if (reasons_.empty())
return false;
return reasons_.front().IsSuccess();
}
DecisionSuccessReason DecisionDetails::SuccessReason() const {
DCHECK(!reasons_.empty());
return reasons_.front().success_reason();
}
DecisionFailureReason DecisionDetails::FailureReason() const {
DCHECK(!reasons_.empty());
return reasons_.front().failure_reason();
}
void DecisionDetails::Populate(
ukm::builders::TabManager_LifecycleStateChange* ukm) const {
DCHECK(!reasons_.empty());
bool positive = IsPositive();
ukm->SetOutcome(positive);
for (const auto& reason : reasons_) {
// Stop adding reasons once all of the initial reasons of the same type
// have been added.
bool success = reason.IsSuccess();
if (success != positive)
break;
if (success) {
PopulateSuccessReason(reason.success_reason(), ukm);
} else {
PopulateFailureReason(reason.failure_reason(), ukm);
}
}
}
std::vector<std::string> DecisionDetails::GetFailureReasonStrings() const {
std::vector<std::string> reasons;
for (const auto& reason : reasons_) {
if (reason.IsSuccess())
break;
reasons.push_back(reason.ToString());
}
return reasons;
}
void DecisionDetails::ClearForTesting() {
reasons_.clear();
toggled_ = false;
}
bool DecisionDetails::CheckIfToggled() {
if (toggled_)
return true;
if (reasons_.size() <= 1)
return false;
// Determine if the last reason is of a different type than the one before. If
// so, then the toggle has occurred.
toggled_ = reasons_[reasons_.size() - 1].IsSuccess() !=
reasons_[reasons_.size() - 2].IsSuccess();
return toggled_;
}
} // namespace resource_coordinator
// Copyright 2018 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.
#ifndef CHROME_BROWSER_RESOURCE_COORDINATOR_DECISION_DETAILS_H_
#define CHROME_BROWSER_RESOURCE_COORDINATOR_DECISION_DETAILS_H_
#include <string>
#include <vector>
#include "base/macros.h"
namespace ukm {
namespace builders {
class TabManager_LifecycleStateChange;
}
} // namespace ukm
namespace resource_coordinator {
// An enumeration of reasons why a particular intervention or lifecycle state
// changes can be denied. This is a superset of all failure reasons that can
// apply for any particular intervention. New reasons can freely be added to
// this enum as necessary, but UKM plumbing and string conversion needs to be
// maintained as well.
enum class DecisionFailureReason : int32_t {
// An invalid failure reason. This must remain first.
INVALID = -1,
// The browser was opted out of the intervention via enterprise policy.
LIFECYCLES_ENTERPRISE_POLICY_OPT_OUT,
// The origin opted itself out of the intervention via feature policy.
LIFECYCLES_FEATURE_POLICY_OPT_OUT,
// The origin was opted out of the intervention in the global blacklist.
GLOBAL_BLACKLIST,
// The local heuristic opted the origin out of the intervention due to its use
// of audio while in the background.
HEURISTIC_AUDIO,
// The local heuristic opted the origin out of the intervention due to its use
// of favicon updates while in the background.
HEURISTIC_FAVICON,
// The local heuristic is temporarily opting the origin out of the
// intervention due to a lack of sufficient observation time.
HEURISTIC_INSUFFICIENT_OBSERVATION,
// The local heuristic opted the origin out of the intervention due to its use
// of notifications while in the background.
HEURISTIC_NOTIFICATIONS,
// The local heuristic opted the origin out of the intervention due to its use
// of title updates while in the background.
HEURISTIC_TITLE,
// The tab is opted out of the intervention as it is currently capturing user
// media (webcam, microphone, etc).
LIVE_STATE_CAPTURING,
// The tab is opted out of the intervention by an extension.
LIVE_STATE_EXTENSION_DISALLOWED,
// The tab is opted out of the intervention as it contains text form entry.
LIVE_STATE_FORM_ENTRY,
// The tab is opted out of the intervention as it is currently hosting a PDF.
LIVE_STATE_IS_PDF,
// The tab is opted out of the intervention as it is currently being mirrored
// (casting, etc).
LIVE_STATE_MIRRORING,
// The tab is opted out of the intervention as it is currently playing audio.
LIVE_STATE_PLAYING_AUDIO,
// The tab is opted out of the intervention as it is currently using
// WebSockets.
LIVE_STATE_USING_WEB_SOCKETS,
// The tab is opted out of the intervention as it is currently WebUSB.
LIVE_STATE_USING_WEB_USB,
// The tab is opted out of the intervention as it is currently visible.
LIVE_STATE_VISIBLE,
// This must remain last.
MAX,
};
// An enumeration of reasons why a particular intervention or lifecycle state
// change can be approved. The fact that no "live state" failures are blocking
// the intervention is implicit, and doesn't need to be explicitly encoded.
enum class DecisionSuccessReason : int32_t {
// An invalid failure reason. This must remain first.
INVALID = -1,
// The origin opted itself into the intervention via lifecycles feature
// policy.
LIFECYCLES_FEATURE_POLICY_OPT_IN,
// The origin was opted into the intervention via the global whitelist.
GLOBAL_WHITELIST,
// The origin has been observed to be safe for the intervention using local
// database observations.
HEURISTIC_OBSERVED_TO_BE_SAFE,
// This must remain last.
MAX,
};
// Helper function for converting a reason to a string representation.
const char* ToString(DecisionFailureReason failure_reason);
const char* ToString(DecisionSuccessReason success_reason);
// Describes the detailed reasons why a particular intervention decision was
// made. This is populated by the various policy bits of policy logic that
// decide whether a particular intervention or lifecycle state transition can be
// performed. It can populate various related UKM builders and also be converted
// to a collection of user readable strings for the purposes of displaying in
// in web UI.
//
// A decision can contain multiple reasons for success or failure, and policy
// allows some success reasons to override some failure reasons and vice versa.
// The first reason posted to this object determines whether or not the overall
// outcome is positive or negative. It is assumed that reasons are posted in
// order of decreasing priority.
//
// For logging and inspection it is useful to know of all possible failure
// reasons blocking a success. Similarly, it is interesting to know of all of
// the possible success reasons blocking a failure. To this end, policy logic
// should continue populating the decision with details until is has "toggled".
// That is, a success reason has followed a chain of failures or vice versa.
// The "toggling" of the decision chain is indicated by the return value from
// AddReason. This allows writing code like the following:
//
// bool DecideIfCanDoSomething(DecisionDetails* details) {
// if (some_condition_is_false) {
// if (details->AddReason(kSomeFailureReason))
// return details->IsPositive();
// }
// if (some_other_condition) {
// if (details->AddReason(kSomeOtherFailureReason))
// return details->IsPositive();
// }
// ...
// }
class DecisionDetails {
public:
// A union of success/failure reasons. This is allowed to be copied in order
// to be compatible with STL containers.
class Reason {
public:
Reason();
explicit Reason(DecisionSuccessReason success_reason);
explicit Reason(DecisionFailureReason failure_reason);
Reason(const Reason& rhs);
~Reason();
Reason& operator=(const Reason& rhs);
bool IsValid() const;
bool IsSuccess() const;
bool IsFailure() const;
DecisionSuccessReason success_reason() const;
DecisionFailureReason failure_reason() const;
const char* ToString() const;
bool operator==(const Reason& rhs) const;
bool operator!=(const Reason& rhs) const;
private:
DecisionSuccessReason success_reason_;
DecisionFailureReason failure_reason_;
};
DecisionDetails();
~DecisionDetails();
// Adds a success or failure reason. Returns true if the chain of reasons has
// "toggled", false otherwise.
bool AddReason(const Reason& reason);
bool AddReason(DecisionFailureReason failure_reason);
bool AddReason(DecisionSuccessReason success_reason);
// Returns the outcome of the decision. This is implicit from the reasons that
// have been posted to this object.
bool IsPositive() const;
// Returns the main success reason. This is only valid to call if IsPositive
// is true.
DecisionSuccessReason SuccessReason() const;
// Returns the main failure reason. This is only valid to call if IsPositive
// is false.
DecisionFailureReason FailureReason() const;
// Returns the full vector of reasons.
const std::vector<Reason>& reasons() const { return reasons_; }
// Returns whether or not the chain of reasons has toggled.
bool toggled() const { return toggled_; }
// Populates the provided "TabManager.LifecycleStateChange" UKM builder with
// information from this object.
void Populate(ukm::builders::TabManager_LifecycleStateChange* ukm) const;
// Returns a collection of failure reason strings, from most important failure
// reason to least important. This is empty if the outcome is positive, and
// will only be populated with failure reasons that are not overridden by any
// success reasons.
std::vector<std::string> GetFailureReasonStrings() const;
void ClearForTesting();
private:
bool CheckIfToggled();
// This is true if the vector of success reasons has "toggled" from all
// failures to some successes, or vice versa. Continuing to collect additional
// reasons after this toggle isn't very informative.
bool toggled_;
std::vector<Reason> reasons_;
DISALLOW_COPY_AND_ASSIGN(DecisionDetails);
};
} // namespace resource_coordinator
#endif // CHROME_BROWSER_RESOURCE_COORDINATOR_DECISION_DETAILS_H_
......@@ -2918,6 +2918,7 @@ test("unit_tests") {
"../browser/metrics/tab_stats_tracker_unittest.cc",
"../browser/page_load_metrics/observers/session_restore_page_load_metrics_observer_unittest.cc",
"../browser/resource_coordinator/background_tab_navigation_throttle_unittest.cc",
"../browser/resource_coordinator/decision_details_unittest.cc",
"../browser/resource_coordinator/discard_metrics_lifecycle_unit_observer_unittest.cc",
"../browser/resource_coordinator/leveldb_site_characteristics_database_unittest.cc",
"../browser/resource_coordinator/lifecycle_unit_base_unittest.cc",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment