1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/resource_coordinator/decision_details.h"
6 
7 #include "base/stl_util.h"
8 #include "services/metrics/public/cpp/ukm_builders.h"
9 
10 namespace resource_coordinator {
11 
12 namespace {
13 
14 // These are intended to be human readable descriptions of the various failure
15 // reasons. They don't need to be localized as they are for a developer-only
16 // WebUI.
17 const char* kDecisionFailureReasonStrings[] = {
18     "Browser opted out via enterprise policy",
19     "Tab opted out via origin trial",
20     "Origin is in global disallowlist",
21     "Origin has been observed playing audio while backgrounded",
22     "Origin has been observed updating favicon while backgrounded",
23     "Origin is temporarily protected while under observation",
24     "Origin has been observed updating title while backgrounded",
25     "Tab is currently capturing the camera and/or microphone",
26     "Tab has been protected by an extension",
27     "Tab contains unsubmitted text form entry",
28     "Tab contains a PDF",
29     "Tab content is being mirrored/cast",
30     "Tab is currently emitting audio",
31     "Tab is currently using WebSockets",
32     "Tab is currently using WebUSB",
33     "Tab is currently visible",
34     "Tab is currently using DevTools",
35     "Tab is currently capturing a window or screen",
36     "Tab is sharing its BrowsingInstance with another tab",
37     "Tab is currently connected to a bluetooth device",
38     "Tab is currently holding a WebLock",
39     "Tab is currently holding an IndexedDB lock",
40     "Tab has notification permission ",
41     "Tab is a web application window",
42 };
43 static_assert(base::size(kDecisionFailureReasonStrings) ==
44                   static_cast<size_t>(DecisionFailureReason::MAX),
45               "kDecisionFailureReasonStrings not up to date with enum");
46 
47 const char* kDecisionSuccessReasonStrings[] = {
48     "Tab opted in via origin trial",
49     "Origin is in global allowlist",
50     "Origin has locally been observed to be safe via heuristic logic",
51 };
52 static_assert(base::size(kDecisionSuccessReasonStrings) ==
53                   static_cast<size_t>(DecisionSuccessReason::MAX),
54               "kDecisionSuccessReasonStrings not up to date with enum");
55 
PopulateSuccessReason(DecisionSuccessReason success_reason,ukm::builders::TabManager_LifecycleStateChange * ukm)56 void PopulateSuccessReason(
57     DecisionSuccessReason success_reason,
58     ukm::builders::TabManager_LifecycleStateChange* ukm) {
59   switch (success_reason) {
60     case DecisionSuccessReason::INVALID:
61       break;
62     case DecisionSuccessReason::ORIGIN_TRIAL_OPT_IN:
63       ukm->SetSuccessOriginTrialOptIn(1);
64       break;
65     case DecisionSuccessReason::GLOBAL_ALLOWLIST:
66       ukm->SetSuccessGlobalAllowlist(1);
67       break;
68     case DecisionSuccessReason::HEURISTIC_OBSERVED_TO_BE_SAFE:
69       ukm->SetSuccessHeuristic(1);
70       break;
71     case DecisionSuccessReason::MAX:
72       NOTREACHED();
73       break;
74   }
75 }
76 
PopulateFailureReason(DecisionFailureReason failure_reason,ukm::builders::TabManager_LifecycleStateChange * ukm)77 void PopulateFailureReason(
78     DecisionFailureReason failure_reason,
79     ukm::builders::TabManager_LifecycleStateChange* ukm) {
80   switch (failure_reason) {
81     case DecisionFailureReason::INVALID:
82       break;
83     case DecisionFailureReason::LIFECYCLES_ENTERPRISE_POLICY_OPT_OUT:
84       ukm->SetFailureLifecyclesEnterprisePolicyOptOut(1);
85       break;
86     case DecisionFailureReason::ORIGIN_TRIAL_OPT_OUT:
87       ukm->SetFailureOriginTrialOptOut(1);
88       break;
89     case DecisionFailureReason::GLOBAL_DISALLOWLIST:
90       ukm->SetFailureGlobalDisallowlist(1);
91       break;
92     case DecisionFailureReason::HEURISTIC_AUDIO:
93       ukm->SetFailureHeuristicAudio(1);
94       break;
95     case DecisionFailureReason::HEURISTIC_FAVICON:
96       ukm->SetFailureHeuristicFavicon(1);
97       break;
98     case DecisionFailureReason::HEURISTIC_INSUFFICIENT_OBSERVATION:
99       ukm->SetFailureHeuristicInsufficientObservation(1);
100       break;
101     case DecisionFailureReason::HEURISTIC_TITLE:
102       ukm->SetFailureHeuristicTitle(1);
103       break;
104     case DecisionFailureReason::LIVE_STATE_CAPTURING:
105       ukm->SetFailureLiveStateCapturing(1);
106       break;
107     case DecisionFailureReason::LIVE_STATE_EXTENSION_DISALLOWED:
108       ukm->SetFailureLiveStateExtensionDisallowed(1);
109       break;
110     case DecisionFailureReason::LIVE_STATE_FORM_ENTRY:
111       ukm->SetFailureLiveStateFormEntry(1);
112       break;
113     case DecisionFailureReason::LIVE_STATE_IS_PDF:
114       ukm->SetFailureLiveStateIsPDF(1);
115       break;
116     case DecisionFailureReason::LIVE_STATE_MIRRORING:
117       ukm->SetFailureLiveStateMirroring(1);
118       break;
119     case DecisionFailureReason::LIVE_STATE_PLAYING_AUDIO:
120       ukm->SetFailureLiveStatePlayingAudio(1);
121       break;
122     case DecisionFailureReason::LIVE_STATE_USING_WEB_SOCKETS:
123       ukm->SetFailureLiveStateUsingWebSockets(1);
124       break;
125     case DecisionFailureReason::LIVE_STATE_USING_WEB_USB:
126       ukm->SetFailureLiveStateUsingWebUSB(1);
127       break;
128     case DecisionFailureReason::LIVE_STATE_VISIBLE:
129       ukm->SetFailureLiveStateVisible(1);
130       break;
131     case DecisionFailureReason::LIVE_STATE_DEVTOOLS_OPEN:
132       ukm->SetFailureLiveStateDevToolsOpen(1);
133       break;
134     case DecisionFailureReason::LIVE_STATE_DESKTOP_CAPTURE:
135       ukm->SetFailureLiveStateDesktopCapture(1);
136       break;
137     case DecisionFailureReason::LIVE_STATE_SHARING_BROWSING_INSTANCE:
138       ukm->SetFailureLiveStateSharingBrowsingInstance(1);
139       break;
140     case DecisionFailureReason::LIVE_STATE_USING_BLUETOOTH:
141       ukm->SetFailureLiveStateUsingBluetooth(1);
142       break;
143     case DecisionFailureReason::LIVE_STATE_USING_WEBLOCK:
144       ukm->SetFailureLiveStateUsingWebLock(1);
145       break;
146     case DecisionFailureReason::LIVE_STATE_USING_INDEXEDDB_LOCK:
147       ukm->SetFailureLiveStateUsingIndexedDBLock(1);
148       break;
149     case DecisionFailureReason::LIVE_STATE_HAS_NOTIFICATIONS_PERMISSION:
150       ukm->SetFailureLiveStateHasNotificationsPermission(1);
151       break;
152     case DecisionFailureReason::LIVE_WEB_APP:
153       ukm->SetFailureLiveWebApp(1);
154       break;
155     case DecisionFailureReason::MAX:
156       NOTREACHED();
157       break;
158   }
159 }
160 
161 }  // namespace
162 
ToString(DecisionFailureReason failure_reason)163 const char* ToString(DecisionFailureReason failure_reason) {
164   if (failure_reason == DecisionFailureReason::INVALID ||
165       failure_reason == DecisionFailureReason::MAX)
166     return nullptr;
167   return kDecisionFailureReasonStrings[static_cast<size_t>(failure_reason)];
168 }
169 
ToString(DecisionSuccessReason success_reason)170 const char* ToString(DecisionSuccessReason success_reason) {
171   if (success_reason == DecisionSuccessReason::INVALID ||
172       success_reason == DecisionSuccessReason::MAX)
173     return nullptr;
174   return kDecisionSuccessReasonStrings[static_cast<size_t>(success_reason)];
175 }
176 
Reason()177 DecisionDetails::Reason::Reason()
178     : success_reason_(DecisionSuccessReason::INVALID),
179       failure_reason_(DecisionFailureReason::INVALID) {}
180 
Reason(DecisionSuccessReason success_reason)181 DecisionDetails::Reason::Reason(DecisionSuccessReason success_reason)
182     : success_reason_(success_reason),
183       failure_reason_(DecisionFailureReason::INVALID) {
184   DCHECK(IsSuccess());
185 }
186 
Reason(DecisionFailureReason failure_reason)187 DecisionDetails::Reason::Reason(DecisionFailureReason failure_reason)
188     : success_reason_(DecisionSuccessReason::INVALID),
189       failure_reason_(failure_reason) {
190   DCHECK(IsFailure());
191 }
192 
193 DecisionDetails::Reason::Reason(const Reason& rhs) = default;
194 DecisionDetails::Reason::~Reason() = default;
195 
196 DecisionDetails::Reason& DecisionDetails::Reason::operator=(const Reason& rhs) =
197     default;
198 
IsValid() const199 bool DecisionDetails::Reason::IsValid() const {
200   return IsSuccess() || IsFailure();
201 }
202 
IsSuccess() const203 bool DecisionDetails::Reason::IsSuccess() const {
204   if (success_reason_ == DecisionSuccessReason::INVALID ||
205       success_reason_ == DecisionSuccessReason::MAX ||
206       failure_reason_ != DecisionFailureReason::INVALID)
207     return false;
208   return true;
209 }
210 
IsFailure() const211 bool DecisionDetails::Reason::IsFailure() const {
212   if (failure_reason_ == DecisionFailureReason::INVALID ||
213       failure_reason_ == DecisionFailureReason::MAX ||
214       success_reason_ != DecisionSuccessReason::INVALID)
215     return false;
216   return true;
217 }
218 
success_reason() const219 DecisionSuccessReason DecisionDetails::Reason::success_reason() const {
220   DCHECK(IsSuccess());
221   return success_reason_;
222 }
223 
failure_reason() const224 DecisionFailureReason DecisionDetails::Reason::failure_reason() const {
225   DCHECK(IsFailure());
226   return failure_reason_;
227 }
228 
ToString() const229 const char* DecisionDetails::Reason::ToString() const {
230   if (!IsValid())
231     return nullptr;
232   if (IsSuccess())
233     return ::resource_coordinator::ToString(success_reason_);
234   DCHECK(IsFailure());
235   return ::resource_coordinator::ToString(failure_reason_);
236 }
237 
operator ==(const Reason & rhs) const238 bool DecisionDetails::Reason::operator==(const Reason& rhs) const {
239   return success_reason_ == rhs.success_reason_ &&
240          failure_reason_ == rhs.failure_reason_;
241 }
242 
operator !=(const Reason & rhs) const243 bool DecisionDetails::Reason::operator!=(const Reason& rhs) const {
244   return !(*this == rhs);
245 }
246 
DecisionDetails()247 DecisionDetails::DecisionDetails() : toggled_(false) {}
248 
249 DecisionDetails::~DecisionDetails() = default;
250 
operator =(DecisionDetails && rhs)251 DecisionDetails& DecisionDetails::operator=(DecisionDetails&& rhs) {
252   toggled_ = rhs.toggled_;
253   reasons_ = std::move(rhs.reasons_);
254   rhs.Clear();
255   return *this;
256 }
257 
AddReason(const Reason & reason)258 bool DecisionDetails::AddReason(const Reason& reason) {
259   reasons_.push_back(reason);
260   return CheckIfToggled();
261 }
262 
AddReason(DecisionFailureReason failure_reason)263 bool DecisionDetails::AddReason(DecisionFailureReason failure_reason) {
264   reasons_.push_back(Reason(failure_reason));
265   return CheckIfToggled();
266 }
267 
AddReason(DecisionSuccessReason success_reason)268 bool DecisionDetails::AddReason(DecisionSuccessReason success_reason) {
269   reasons_.push_back(Reason(success_reason));
270   return CheckIfToggled();
271 }
272 
IsPositive() const273 bool DecisionDetails::IsPositive() const {
274   // A decision without supporting reasons is negative by default.
275   if (reasons_.empty())
276     return false;
277   return reasons_.front().IsSuccess();
278 }
279 
SuccessReason() const280 DecisionSuccessReason DecisionDetails::SuccessReason() const {
281   DCHECK(!reasons_.empty());
282   return reasons_.front().success_reason();
283 }
284 
FailureReason() const285 DecisionFailureReason DecisionDetails::FailureReason() const {
286   DCHECK(!reasons_.empty());
287   return reasons_.front().failure_reason();
288 }
289 
Populate(ukm::builders::TabManager_LifecycleStateChange * ukm) const290 void DecisionDetails::Populate(
291     ukm::builders::TabManager_LifecycleStateChange* ukm) const {
292   DCHECK(!reasons_.empty());
293   bool positive = IsPositive();
294   ukm->SetOutcome(positive);
295   for (const auto& reason : reasons_) {
296     // Stop adding reasons once all of the initial reasons of the same type
297     // have been added.
298     bool success = reason.IsSuccess();
299     if (success != positive)
300       break;
301     if (success) {
302       PopulateSuccessReason(reason.success_reason(), ukm);
303     } else {
304       PopulateFailureReason(reason.failure_reason(), ukm);
305     }
306   }
307 }
308 
GetFailureReasonStrings() const309 std::vector<std::string> DecisionDetails::GetFailureReasonStrings() const {
310   std::vector<std::string> reasons;
311   for (const auto& reason : reasons_) {
312     if (reason.IsSuccess())
313       break;
314     reasons.push_back(reason.ToString());
315   }
316   return reasons;
317 }
318 
Clear()319 void DecisionDetails::Clear() {
320   reasons_.clear();
321   toggled_ = false;
322 }
323 
CheckIfToggled()324 bool DecisionDetails::CheckIfToggled() {
325   if (toggled_)
326     return true;
327   if (reasons_.size() <= 1)
328     return false;
329   // Determine if the last reason is of a different type than the one before. If
330   // so, then the toggle has occurred.
331   toggled_ = reasons_[reasons_.size() - 1].IsSuccess() !=
332              reasons_[reasons_.size() - 2].IsSuccess();
333   return toggled_;
334 }
335 
336 }  // namespace resource_coordinator
337