Commit cb2dd325 authored by Nicolas Ouellet-payeur's avatar Nicolas Ouellet-payeur Committed by Commit Bot

In chrome://discards, add 'Can Freeze?'/'Can Discard?' columns

The 'Can Discard?' column indicates proactive discards, not urgent discards.

Each cell in those columns shows a checkmark or crossmark, with a
'[View Reason]' link. Hovering the link shows a tooltip with the reasons why
it can't be frozen/discarded.

The 'View Reason' button is disabled if the tab _can_ be frozen/discarded, since
there are no failure reasons to show.

Also, remove the 'Media' column, since it's redundant now.

Screenshot (apologies for external contributors):
https://screenshot.googleplex.com/n9oDCgM3ffj

Bug: 864149
Cq-Include-Trybots: luci.chromium.try:closure_compilation
Change-Id: Ied07835a5eae7a80989226a3662dc12d3e9ee9b2
Reviewed-on: https://chromium-review.googlesource.com/1140368
Commit-Queue: Sébastien Marchand <sebmarchand@chromium.org>
Reviewed-by: default avatarChris Hamilton <chrisha@chromium.org>
Reviewed-by: default avatarSébastien Marchand <sebmarchand@chromium.org>
Reviewed-by: default avatarKen Buchanan <kenrb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#576216}
parent eba27923
...@@ -167,21 +167,37 @@ StateChangeReason DiscardReasonToStateChangeReason(DiscardReason reason) { ...@@ -167,21 +167,37 @@ StateChangeReason DiscardReasonToStateChangeReason(DiscardReason reason) {
} }
} }
void CheckFeatureUsage(SiteFeatureUsage feature_usage, struct FeatureUsageEntry {
DecisionDetails* details, SiteFeatureUsage usage;
DecisionFailureReason heuristic_failure_reason) { DecisionFailureReason failure_reason;
DCHECK(details); };
switch (feature_usage) { void CheckFeatureUsage(const SiteCharacteristicsDataReader* reader,
case SiteFeatureUsage::kSiteFeatureInUse: DecisionDetails* details) {
details->AddReason(heuristic_failure_reason); const FeatureUsageEntry features[] = {
return; {reader->UsesAudioInBackground(), DecisionFailureReason::HEURISTIC_AUDIO},
case SiteFeatureUsage::kSiteFeatureUsageUnknown: {reader->UpdatesFaviconInBackground(),
DecisionFailureReason::HEURISTIC_FAVICON},
{reader->UsesNotificationsInBackground(),
DecisionFailureReason::HEURISTIC_NOTIFICATIONS},
{reader->UpdatesTitleInBackground(),
DecisionFailureReason::HEURISTIC_TITLE}};
// Avoid adding 'insufficient observation' reason multiple times.
bool insufficient_observation = false;
const auto* last = features + base::size(features);
for (const auto* f = features; f != last; f++) {
if (f->usage == SiteFeatureUsage::kSiteFeatureInUse) {
details->AddReason(f->failure_reason);
} else if (f->usage == SiteFeatureUsage::kSiteFeatureUsageUnknown) {
insufficient_observation = true;
}
}
if (insufficient_observation) {
details->AddReason( details->AddReason(
DecisionFailureReason::HEURISTIC_INSUFFICIENT_OBSERVATION); DecisionFailureReason::HEURISTIC_INSUFFICIENT_OBSERVATION);
return;
case SiteFeatureUsage::kSiteFeatureNotInUse:
return;
} }
} }
...@@ -209,17 +225,7 @@ void CheckIfTabCanCommunicateWithUserWhileInBackground( ...@@ -209,17 +225,7 @@ void CheckIfTabCanCommunicateWithUserWhileInBackground(
// TODO(sebmarchand): Add a failure reason for when the data isn't ready yet. // TODO(sebmarchand): Add a failure reason for when the data isn't ready yet.
CheckFeatureUsage(reader->UsesAudioInBackground(), details, CheckFeatureUsage(reader.get(), details);
DecisionFailureReason::HEURISTIC_AUDIO);
CheckFeatureUsage(reader->UpdatesFaviconInBackground(), details,
DecisionFailureReason::HEURISTIC_FAVICON);
CheckFeatureUsage(reader->UsesNotificationsInBackground(), details,
DecisionFailureReason::HEURISTIC_NOTIFICATIONS);
CheckFeatureUsage(reader->UpdatesTitleInBackground(), details,
DecisionFailureReason::HEURISTIC_TITLE);
} }
InterventionPolicyDatabase* GetInterventionPolicyDatabase() { InterventionPolicyDatabase* GetInterventionPolicyDatabase() {
......
...@@ -183,6 +183,10 @@ void TabLifecycleUnitTest::TestCannotDiscardBasedOnHeuristicUsage( ...@@ -183,6 +183,10 @@ void TabLifecycleUnitTest::TestCannotDiscardBasedOnHeuristicUsage(
&decision_details)); &decision_details));
EXPECT_FALSE(decision_details.IsPositive()); EXPECT_FALSE(decision_details.IsPositive());
EXPECT_EQ(failure_reason, decision_details.FailureReason()); EXPECT_EQ(failure_reason, decision_details.FailureReason());
// There should only be one reason (e.g. no duplicates).
EXPECT_THAT(
decision_details.reasons(),
::testing::ElementsAre(DecisionDetails::Reason(failure_reason)));
} }
// Heuristics shouldn't be considered for urgent or external tab discarding. // Heuristics shouldn't be considered for urgent or external tab discarding.
......
...@@ -92,7 +92,7 @@ table td.utility-rank-cell { ...@@ -92,7 +92,7 @@ table td.utility-rank-cell {
text-align: center; text-align: center;
} }
table td div.is-auto-discardable-link, table td div[is=action-link],
table td.actions-cell { table td.actions-cell {
font-size: 0.6rem; font-size: 0.6rem;
} }
...@@ -115,3 +115,26 @@ th[data-sort-reverse].sort-column div.header-cell-container::after { ...@@ -115,3 +115,26 @@ th[data-sort-reverse].sort-column div.header-cell-container::after {
content: '▼'; content: '▼';
opacity: 1; opacity: 1;
} }
.tooltip-container {
position: relative;
}
.tooltip {
background: black;
color: white;
display: none;
font-size: 0.75rem;
left: 50%;
margin-left: -100px;
margin-top: 4px;
padding: 4px;
position: absolute;
top: 100%;
width: 200px;
z-index: 1;
}
.tooltip-container:hover > .tooltip {
display: block;
}
...@@ -85,9 +85,18 @@ general use and is not localized. ...@@ -85,9 +85,18 @@ general use and is not localized.
</div> </div>
</div> </div>
</th> </th>
<th data-sort-key="isMedia"> <th data-sort-key="canFreeze">
<div class="header-cell-container"> <div class="header-cell-container">
Media <div>
<div>Can freeze?</div>
</div>
</div>
</th>
<th data-sort-key="canDiscard">
<div class="header-cell-container">
<div>
<div>Can discard?</div>
</div>
</div> </div>
</th> </th>
<th data-sort-key="discardCount"> <th data-sort-key="discardCount">
...@@ -136,7 +145,20 @@ general use and is not localized. ...@@ -136,7 +145,20 @@ general use and is not localized.
<td class="visibility-cell"></td> <td class="visibility-cell"></td>
<td class="loading-state-cell"></td> <td class="loading-state-cell"></td>
<td class="state-cell"></td> <td class="state-cell"></td>
<td class="is-media-cell boolean-cell"></td> <td class="can-freeze-cell boolean-cell">
<div class="can-freeze-div"></div>
<div is="action-link" class="tooltip-container can-freeze-link">
[View Reason]
<div class="tooltip can-freeze-tooltip"></div>
</div>
</td>
<td class="can-discard-cell boolean-cell">
<div class="can-discard-div"></div>
<div is="action-link" class="tooltip-container can-discard-link">
[View Reason]
<div class="tooltip can-discard-tooltip"></div>
</div>
</td>
<td class="discard-count-cell"></td> <td class="discard-count-cell"></td>
<td class="is-auto-discardable-cell boolean-cell"> <td class="is-auto-discardable-cell boolean-cell">
<div class="is-auto-discardable-div"></div> <div class="is-auto-discardable-div"></div>
......
...@@ -64,7 +64,7 @@ cr.define('discards', function() { ...@@ -64,7 +64,7 @@ cr.define('discards', function() {
} }
// Compares boolean fields. // Compares boolean fields.
if (['isMedia', 'isAutoDiscardable'].includes(sortKey)) { if (['canFreeze', 'canDiscard', 'isAutoDiscardable'].includes(sortKey)) {
if (val1 == val2) if (val1 == val2)
return 0; return 0;
return val1 ? 1 : -1; return val1 ? 1 : -1;
...@@ -185,7 +185,7 @@ cr.define('discards', function() { ...@@ -185,7 +185,7 @@ cr.define('discards', function() {
* @return {string} A string representing the bool. * @return {string} A string representing the bool.
*/ */
function boolToString(bool) { function boolToString(bool) {
return bool ? '' : '\xa0'; return bool ? '' : '✘️';
} }
/** /**
...@@ -276,7 +276,6 @@ cr.define('discards', function() { ...@@ -276,7 +276,6 @@ cr.define('discards', function() {
.then(stableUpdateTabDiscardsInfoTable()); .then(stableUpdateTabDiscardsInfoTable());
}); });
// Set up the listeners for load links.
let loadListener = function(e) { let loadListener = function(e) {
// Get the info backing this row. // Get the info backing this row.
let info = infos[getRowIndex(e.target)]; let info = infos[getRowIndex(e.target)];
...@@ -348,8 +347,10 @@ cr.define('discards', function() { ...@@ -348,8 +347,10 @@ cr.define('discards', function() {
visibilityToString(info.visibility); visibilityToString(info.visibility);
row.querySelector('.loading-state-cell').textContent = row.querySelector('.loading-state-cell').textContent =
loadingStateToString(info.loadingState); loadingStateToString(info.loadingState);
row.querySelector('.is-media-cell').textContent = row.querySelector('.can-freeze-div').textContent =
boolToString(info.isMedia); boolToString(info.canFreeze);
row.querySelector('.can-discard-div').textContent =
boolToString(info.canDiscard);
// The lifecycle state is meaningless for 'unloaded' tabs. // The lifecycle state is meaningless for 'unloaded' tabs.
row.querySelector('.state-cell').textContent = row.querySelector('.state-cell').textContent =
(info.loadingState != mojom.LifecycleUnitLoadingState.UNLOADED) ? (info.loadingState != mojom.LifecycleUnitLoadingState.UNLOADED) ?
...@@ -362,7 +363,17 @@ cr.define('discards', function() { ...@@ -362,7 +363,17 @@ cr.define('discards', function() {
row.querySelector('.last-active-cell').textContent = row.querySelector('.last-active-cell').textContent =
lastActiveToString(info.lastActiveSeconds); lastActiveToString(info.lastActiveSeconds);
// Update the tooltips with 'Can Freeze/Discard?' reasons.
row.querySelector('.can-freeze-tooltip').innerHTML =
info.cannotFreezeReasons.join('<br />');
row.querySelector('.can-discard-tooltip').innerHTML =
info.cannotDiscardReasons.join('<br />');
row.querySelector('.is-auto-discardable-link').removeAttribute('disabled'); row.querySelector('.is-auto-discardable-link').removeAttribute('disabled');
setActionLinkEnabled(
row.querySelector('.can-freeze-link'), !info.canFreeze);
setActionLinkEnabled(
row.querySelector('.can-discard-link'), !info.canDiscard);
let loadLink = row.querySelector('.load-link'); let loadLink = row.querySelector('.load-link');
let freezeLink = row.querySelector('.freeze-link'); let freezeLink = row.querySelector('.freeze-link');
let discardLink = row.querySelector('.discard-link'); let discardLink = row.querySelector('.discard-link');
......
...@@ -26,9 +26,14 @@ struct TabDiscardsInfo { ...@@ -26,9 +26,14 @@ struct TabDiscardsInfo {
LifecycleUnitLoadingState loading_state; LifecycleUnitLoadingState loading_state;
// The state of the LifecycleUnit. // The state of the LifecycleUnit.
LifecycleUnitState state; LifecycleUnitState state;
// If the tab is currently using media functionality (casting, WebRTC, playing // Whether the tab can be frozen.
// audio, etc) this is true. bool can_freeze;
bool is_media; // List of human-readable reasons why a tab can't be frozen.
array<string> cannot_freeze_reasons;
// Whether the tab can be discarded.
bool can_discard;
// List of human-readable reasons why a tab can't be discarded.
array<string> cannot_discard_reasons;
// The number of times this tab has been discarded in the current browser // The number of times this tab has been discarded in the current browser
// session. // session.
int32 discard_count; int32 discard_count;
......
...@@ -122,7 +122,13 @@ class DiscardsDetailsProviderImpl : public mojom::DiscardsDetailsProvider { ...@@ -122,7 +122,13 @@ class DiscardsDetailsProviderImpl : public mojom::DiscardsDetailsProvider {
GetLifecycleUnitVisibility(lifecycle_unit->GetVisibility()); GetLifecycleUnitVisibility(lifecycle_unit->GetVisibility());
info->loading_state = lifecycle_unit->GetLoadingState(); info->loading_state = lifecycle_unit->GetLoadingState();
info->state = lifecycle_unit->GetState(); info->state = lifecycle_unit->GetState();
info->is_media = tab_lifecycle_unit_external->IsMediaTab(); resource_coordinator::DecisionDetails freeze_details;
info->can_freeze = lifecycle_unit->CanFreeze(&freeze_details);
info->cannot_freeze_reasons = freeze_details.GetFailureReasonStrings();
resource_coordinator::DecisionDetails discard_details;
info->can_discard = lifecycle_unit->CanDiscard(
resource_coordinator::DiscardReason::kProactive, &discard_details);
info->cannot_discard_reasons = discard_details.GetFailureReasonStrings();
info->discard_count = tab_lifecycle_unit_external->GetDiscardCount(); info->discard_count = tab_lifecycle_unit_external->GetDiscardCount();
info->utility_rank = rank++; info->utility_rank = rank++;
const base::TimeTicks last_focused_time = const base::TimeTicks last_focused_time =
......
...@@ -22,7 +22,8 @@ TEST_F('DiscardsTest', 'CompareTabDiscardsInfo', function() { ...@@ -22,7 +22,8 @@ TEST_F('DiscardsTest', 'CompareTabDiscardsInfo', function() {
tabUrl: 'http://urlone.com', tabUrl: 'http://urlone.com',
visibility: 0, // hidden visibility: 0, // hidden
state: 0, // active state: 0, // active
isMedia: false, canFreeze: false,
canDiscard: false,
isAutoDiscardable: false, isAutoDiscardable: false,
discardCount: 0, discardCount: 0,
utilityRank: 0, utilityRank: 0,
...@@ -33,7 +34,8 @@ TEST_F('DiscardsTest', 'CompareTabDiscardsInfo', function() { ...@@ -33,7 +34,8 @@ TEST_F('DiscardsTest', 'CompareTabDiscardsInfo', function() {
tabUrl: 'http://urltwo.com', tabUrl: 'http://urltwo.com',
visibility: 1, // occluded visibility: 1, // occluded
state: 3, // frozen state: 3, // frozen
isMedia: true, canFreeze: true,
canDiscard: true,
isFrozen: true, isFrozen: true,
isDiscarded: true, isDiscarded: true,
isAutoDiscardable: true, isAutoDiscardable: true,
...@@ -42,8 +44,8 @@ TEST_F('DiscardsTest', 'CompareTabDiscardsInfo', function() { ...@@ -42,8 +44,8 @@ TEST_F('DiscardsTest', 'CompareTabDiscardsInfo', function() {
lastActiveSeconds: 1 lastActiveSeconds: 1
}; };
['title', 'tabUrl', 'visibility', 'state', 'isMedia', 'isAutoDiscardable', ['title', 'tabUrl', 'visibility', 'state', 'canFreeze', 'canDiscard',
'discardCount', 'utilityRank', 'lastActiveSeconds'] 'isAutoDiscardable', 'discardCount', 'utilityRank', 'lastActiveSeconds']
.forEach((sortKey) => { .forEach((sortKey) => {
assertTrue( assertTrue(
discards.compareTabDiscardsInfos(sortKey, dummy1, dummy2) < 0); discards.compareTabDiscardsInfos(sortKey, dummy1, dummy2) < 0);
......
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