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) {
}
}
void CheckFeatureUsage(SiteFeatureUsage feature_usage,
DecisionDetails* details,
DecisionFailureReason heuristic_failure_reason) {
DCHECK(details);
struct FeatureUsageEntry {
SiteFeatureUsage usage;
DecisionFailureReason failure_reason;
};
void CheckFeatureUsage(const SiteCharacteristicsDataReader* reader,
DecisionDetails* details) {
const FeatureUsageEntry features[] = {
{reader->UsesAudioInBackground(), DecisionFailureReason::HEURISTIC_AUDIO},
{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;
}
}
switch (feature_usage) {
case SiteFeatureUsage::kSiteFeatureInUse:
details->AddReason(heuristic_failure_reason);
return;
case SiteFeatureUsage::kSiteFeatureUsageUnknown:
details->AddReason(
DecisionFailureReason::HEURISTIC_INSUFFICIENT_OBSERVATION);
return;
case SiteFeatureUsage::kSiteFeatureNotInUse:
return;
if (insufficient_observation) {
details->AddReason(
DecisionFailureReason::HEURISTIC_INSUFFICIENT_OBSERVATION);
}
}
......@@ -209,17 +225,7 @@ void CheckIfTabCanCommunicateWithUserWhileInBackground(
// TODO(sebmarchand): Add a failure reason for when the data isn't ready yet.
CheckFeatureUsage(reader->UsesAudioInBackground(), 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);
CheckFeatureUsage(reader.get(), details);
}
InterventionPolicyDatabase* GetInterventionPolicyDatabase() {
......
......@@ -183,6 +183,10 @@ void TabLifecycleUnitTest::TestCannotDiscardBasedOnHeuristicUsage(
&decision_details));
EXPECT_FALSE(decision_details.IsPositive());
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.
......
......@@ -92,7 +92,7 @@ table td.utility-rank-cell {
text-align: center;
}
table td div.is-auto-discardable-link,
table td div[is=action-link],
table td.actions-cell {
font-size: 0.6rem;
}
......@@ -115,3 +115,26 @@ th[data-sort-reverse].sort-column div.header-cell-container::after {
content: '▼';
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.
</div>
</div>
</th>
<th data-sort-key="isMedia">
<th data-sort-key="canFreeze">
<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>
</th>
<th data-sort-key="discardCount">
......@@ -136,7 +145,20 @@ general use and is not localized.
<td class="visibility-cell"></td>
<td class="loading-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="is-auto-discardable-cell boolean-cell">
<div class="is-auto-discardable-div"></div>
......
......@@ -64,7 +64,7 @@ cr.define('discards', function() {
}
// Compares boolean fields.
if (['isMedia', 'isAutoDiscardable'].includes(sortKey)) {
if (['canFreeze', 'canDiscard', 'isAutoDiscardable'].includes(sortKey)) {
if (val1 == val2)
return 0;
return val1 ? 1 : -1;
......@@ -185,7 +185,7 @@ cr.define('discards', function() {
* @return {string} A string representing the bool.
*/
function boolToString(bool) {
return bool ? '' : '\xa0';
return bool ? '' : '✘️';
}
/**
......@@ -276,7 +276,6 @@ cr.define('discards', function() {
.then(stableUpdateTabDiscardsInfoTable());
});
// Set up the listeners for load links.
let loadListener = function(e) {
// Get the info backing this row.
let info = infos[getRowIndex(e.target)];
......@@ -348,8 +347,10 @@ cr.define('discards', function() {
visibilityToString(info.visibility);
row.querySelector('.loading-state-cell').textContent =
loadingStateToString(info.loadingState);
row.querySelector('.is-media-cell').textContent =
boolToString(info.isMedia);
row.querySelector('.can-freeze-div').textContent =
boolToString(info.canFreeze);
row.querySelector('.can-discard-div').textContent =
boolToString(info.canDiscard);
// The lifecycle state is meaningless for 'unloaded' tabs.
row.querySelector('.state-cell').textContent =
(info.loadingState != mojom.LifecycleUnitLoadingState.UNLOADED) ?
......@@ -362,7 +363,17 @@ cr.define('discards', function() {
row.querySelector('.last-active-cell').textContent =
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');
setActionLinkEnabled(
row.querySelector('.can-freeze-link'), !info.canFreeze);
setActionLinkEnabled(
row.querySelector('.can-discard-link'), !info.canDiscard);
let loadLink = row.querySelector('.load-link');
let freezeLink = row.querySelector('.freeze-link');
let discardLink = row.querySelector('.discard-link');
......
......@@ -26,9 +26,14 @@ struct TabDiscardsInfo {
LifecycleUnitLoadingState loading_state;
// The state of the LifecycleUnit.
LifecycleUnitState state;
// If the tab is currently using media functionality (casting, WebRTC, playing
// audio, etc) this is true.
bool is_media;
// Whether the tab can be frozen.
bool can_freeze;
// 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
// session.
int32 discard_count;
......
......@@ -122,7 +122,13 @@ class DiscardsDetailsProviderImpl : public mojom::DiscardsDetailsProvider {
GetLifecycleUnitVisibility(lifecycle_unit->GetVisibility());
info->loading_state = lifecycle_unit->GetLoadingState();
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->utility_rank = rank++;
const base::TimeTicks last_focused_time =
......
......@@ -22,7 +22,8 @@ TEST_F('DiscardsTest', 'CompareTabDiscardsInfo', function() {
tabUrl: 'http://urlone.com',
visibility: 0, // hidden
state: 0, // active
isMedia: false,
canFreeze: false,
canDiscard: false,
isAutoDiscardable: false,
discardCount: 0,
utilityRank: 0,
......@@ -33,7 +34,8 @@ TEST_F('DiscardsTest', 'CompareTabDiscardsInfo', function() {
tabUrl: 'http://urltwo.com',
visibility: 1, // occluded
state: 3, // frozen
isMedia: true,
canFreeze: true,
canDiscard: true,
isFrozen: true,
isDiscarded: true,
isAutoDiscardable: true,
......@@ -42,8 +44,8 @@ TEST_F('DiscardsTest', 'CompareTabDiscardsInfo', function() {
lastActiveSeconds: 1
};
['title', 'tabUrl', 'visibility', 'state', 'isMedia', 'isAutoDiscardable',
'discardCount', 'utilityRank', 'lastActiveSeconds']
['title', 'tabUrl', 'visibility', 'state', 'canFreeze', 'canDiscard',
'isAutoDiscardable', 'discardCount', 'utilityRank', 'lastActiveSeconds']
.forEach((sortKey) => {
assertTrue(
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