Commit 95c0df9a authored by Chris Hamilton's avatar Chris Hamilton Committed by Commit Bot

Add loading state / actions to chrome://discards web UI.

Cq-Include-Trybots: luci.chromium.try:closure_compilation
Change-Id: I3c25b29dfbb00edbb179003d17cfe061a32ebce3
Reviewed-on: https://chromium-review.googlesource.com/1100086
Commit-Queue: Chris Hamilton <chrisha@chromium.org>
Reviewed-by: default avatarWill Harris <wfh@chromium.org>
Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Cr-Commit-Position: refs/heads/master@{#568099}
parent a65fc33c
......@@ -21,6 +21,7 @@
namespace resource_coordinator {
using ::mojom::LifecycleUnitLoadingState;
using ::mojom::LifecycleUnitState;
class DecisionDetails;
......@@ -91,6 +92,9 @@ class LifecycleUnit {
// base::TimeTicks::Max() if the LifecycleUnit is currently visible.
virtual base::TimeTicks GetLastVisibleTime() const = 0;
// Returns the loading state associated with a LifecycleUnit.
virtual LifecycleUnitLoadingState GetLoadingState() const = 0;
// Returns the process hosting this LifecycleUnit. Used to distribute OOM
// scores.
//
......@@ -112,6 +116,10 @@ class LifecycleUnit {
// Returns the current state of this LifecycleUnit.
virtual LifecycleUnitState GetState() const = 0;
// Request that the LifecycleUnit be loaded, return true if the request is
// successful.
virtual bool Load() = 0;
// Request that the LifecycleUnit be frozen, return true if the request is
// successfully sent.
virtual bool Freeze() = 0;
......
......@@ -193,12 +193,8 @@ void TabLifecycleUnitSource::TabLifecycleUnit::SetFocused(bool focused) {
// Reload the tab.
SetState(LifecycleUnitState::ACTIVE,
StateChangeReason::BROWSER_INITIATED);
// See comment in Discard() for an explanation of why "needs reload" is
// false when a tab is discarded.
// TODO(fdoray): Remove NavigationControllerImpl::needs_reload_ once
// session restore is handled by LifecycleManager.
GetWebContents()->GetController().SetNeedsReload();
GetWebContents()->GetController().LoadIfNecessary();
bool loaded = Load();
DCHECK(loaded);
break;
}
......@@ -311,6 +307,27 @@ content::Visibility TabLifecycleUnitSource::TabLifecycleUnit::GetVisibility()
return GetWebContents()->GetVisibility();
}
LifecycleUnitLoadingState
TabLifecycleUnitSource::TabLifecycleUnit::GetLoadingState() const {
return TabLoadTracker::Get()->GetLoadingState(GetWebContents());
}
bool TabLifecycleUnitSource::TabLifecycleUnit::Load() {
if (GetLoadingState() != LifecycleUnitLoadingState::UNLOADED)
return false;
// TODO(chrisha): Make this work more elegantly in the case of background tab
// loading as well, which uses a NavigationThrottle that can be released.
// See comment in Discard() for an explanation of why "needs reload" is
// false when a tab is discarded.
// TODO(fdoray): Remove NavigationControllerImpl::needs_reload_ once
// session restore is handled by LifecycleManager.
GetWebContents()->GetController().SetNeedsReload();
GetWebContents()->GetController().LoadIfNecessary();
return true;
}
bool TabLifecycleUnitSource::TabLifecycleUnit::Freeze() {
if (!IsValidStateChange(GetState(), LifecycleUnitState::PENDING_FREEZE,
StateChangeReason::BROWSER_INITIATED)) {
......@@ -543,6 +560,13 @@ void TabLifecycleUnitSource::TabLifecycleUnit::FinishDiscard(
std::unique_ptr<content::WebContents> null_contents =
content::WebContents::Create(create_params);
content::WebContents* raw_null_contents = null_contents.get();
// Attach the ResourceCoordinatorTabHelper. In production code this has
// already been attached by now due to AttachTabHelpers, but there's a long
// tail of tests that don't add these helpers. This ensures that the various
// DCHECKs in the state transition machinery don't fail.
ResourceCoordinatorTabHelper::CreateForWebContents(raw_null_contents);
// Copy over the state from the navigation controller to preserve the
// back/forward history and to continue to display the correct title/favicon.
//
......@@ -599,6 +623,7 @@ void TabLifecycleUnitSource::TabLifecycleUnit::FinishDiscard(
SetState(LifecycleUnitState::DISCARDED,
DiscardReasonToStateChangeReason(discard_reason));
++discard_count_;
DCHECK_EQ(GetLoadingState(), LifecycleUnitLoadingState::UNLOADED);
}
content::WebContents* TabLifecycleUnitSource::TabLifecycleUnit::GetWebContents()
......
......@@ -89,6 +89,8 @@ class TabLifecycleUnitSource::TabLifecycleUnit
base::ProcessHandle GetProcessHandle() const override;
SortKey GetSortKey() const override;
content::Visibility GetVisibility() const override;
LifecycleUnitLoadingState GetLoadingState() const override;
bool Load() override;
bool Freeze() override;
int GetEstimatedMemoryFreedOnDiscardKB() const override;
bool CanPurge() const override;
......
......@@ -13,6 +13,7 @@
#include "build/build_config.h"
#include "chrome/browser/resource_coordinator/lifecycle_unit_observer.h"
#include "chrome/browser/resource_coordinator/lifecycle_unit_source_observer.h"
#include "chrome/browser/resource_coordinator/tab_helper.h"
#include "chrome/browser/resource_coordinator/tab_lifecycle_observer.h"
#include "chrome/browser/resource_coordinator/tab_lifecycle_unit.h"
#include "chrome/browser/resource_coordinator/test_lifecycle_unit.h"
......@@ -247,6 +248,8 @@ class TabLifecycleUnitSourceTest : public ChromeRenderViewHostTestHarness {
EXPECT_EQ(LifecycleUnitState::DISCARDED, lifecycle_unit->GetState());
}
void DiscardAndAttachTabHelpers(LifecycleUnit* lifecycle_unit) {}
void DetachWebContentsTest(DiscardReason reason) {
LifecycleUnit* first_lifecycle_unit = nullptr;
LifecycleUnit* second_lifecycle_unit = nullptr;
......@@ -454,6 +457,9 @@ class TabLifecycleUnitSourceTest : public ChromeRenderViewHostTestHarness {
std::unique_ptr<content::WebContents> CreateAndNavigateWebContents() {
std::unique_ptr<content::WebContents> web_contents =
CreateTestWebContents();
// Attach the RC tab helper. In production code the browser
// WebContentsDelegate takes care of this.
ResourceCoordinatorTabHelper::CreateForWebContents(web_contents.get());
// Commit an URL to allow discarding.
content::WebContentsTester::For(web_contents.get())
->NavigateAndCommit(GURL("https://www.example.com"));
......@@ -649,6 +655,8 @@ TEST_F(TabLifecycleUnitSourceTest, CannotFreezeADiscardedTab) {
background_lifecycle_unit->Discard(DiscardReason::kUrgent);
testing::Mock::VerifyAndClear(&tab_observer_);
TransitionFromPendingDiscardToDiscardedIfNeeded(DiscardReason::kUrgent,
background_lifecycle_unit);
EXPECT_EQ(LifecycleUnitState::DISCARDED,
background_lifecycle_unit->GetState());
EXPECT_NE(initial_web_contents, tab_strip_model_->GetWebContentsAt(0));
......@@ -707,7 +715,8 @@ TEST_F(TabLifecycleUnitSourceTest, TabProactiveDiscardedByFrozenTimeout) {
background_lifecycle_unit->Discard(DiscardReason::kProactive);
EXPECT_EQ(LifecycleUnitState::PENDING_DISCARD,
background_lifecycle_unit->GetState());
task_runner_->FastForwardBy(kProactiveDiscardFreezeTimeout);
TransitionFromPendingDiscardToDiscardedIfNeeded(DiscardReason::kProactive,
background_lifecycle_unit);
EXPECT_EQ(LifecycleUnitState::DISCARDED,
background_lifecycle_unit->GetState());
......
......@@ -44,6 +44,14 @@ content::Visibility TestLifecycleUnit::GetVisibility() const {
return content::Visibility::VISIBLE;
}
::mojom::LifecycleUnitLoadingState TestLifecycleUnit::GetLoadingState() const {
return ::mojom::LifecycleUnitLoadingState::LOADED;
}
bool TestLifecycleUnit::Load() {
return false;
}
bool TestLifecycleUnit::Freeze() {
return false;
}
......
......@@ -31,6 +31,8 @@ class TestLifecycleUnit : public LifecycleUnitBase {
base::ProcessHandle GetProcessHandle() const override;
SortKey GetSortKey() const override;
content::Visibility GetVisibility() const override;
LifecycleUnitLoadingState GetLoadingState() const override;
bool Load() override;
bool Freeze() override;
int GetEstimatedMemoryFreedOnDiscardKB() const override;
bool CanPurge() const override;
......
......@@ -16,12 +16,33 @@ table th {
table th {
-webkit-padding-end: 16px;
background: rgb(224, 236, 255);
cursor: pointer;
padding-bottom: 4px;
padding-top: 4px;
white-space: nowrap;
}
table th[data-sort-key] {
cursor: pointer;
}
table th div.header-cell-container {
align-items: center;
display: flex;
justify-content: flex-start;
/* Make sure mouse events pass through to the underlying <th>. */
pointer-events: none;
}
table th div.header-cell-container div {
/* Make sure mouse events pass through to the underlying <th>. */
pointer-events: none;
}
table th div.header-cell-container div div {
/* Make sure mouse events pass through to the underlying <th>. */
pointer-events: none;
}
table td.title-cell {
max-width: 200px;
overflow: hidden;
......@@ -51,13 +72,28 @@ table td.tab-url-cell {
white-space: nowrap;
}
table td.visibility-cell {
width: 6em;
}
table td.loading-state-cell {
width: 6em;
}
table td.state-cell {
width: 10em;
}
table td.boolean-cell,
table td.discard-count-cell {
table td.discard-count-cell,
table td.reactivation-score-cell,
table td.site-engagement-score-cell,
table td.utility-rank-cell {
text-align: center;
}
table td div.is-auto-discardable-link,
table td.discard-links-cell {
table td.actions-cell {
font-size: 0.6rem;
}
......@@ -65,12 +101,17 @@ table tr:hover {
background: rgb(255, 255, 187);
}
th.sort-column::after {
th div.header-cell-container::after {
content: '▲';
opacity: 0;
}
th.sort-column div.header-cell-container::after {
content: '▲';
position: absolute;
opacity: 1;
}
th[data-sort-reverse].sort-column::after {
th[data-sort-reverse].sort-column div.header-cell-container::after {
content: '▼';
position: absolute;
opacity: 1;
}
......@@ -32,16 +32,90 @@ general use and is not localized.
<table id="tab-discard-info-table">
<thead>
<tr id="tab-discards-info-table-header">
<th data-sort-key="utilityRank" class="sort-column">Utility Rank</th>
<th data-sort-key="reactivationScore">Reactivation Score</th>
<th data-sort-key="title">Tab Title</th>
<th data-sort-key="tabUrl">Tab URL</th>
<th data-sort-key="visibility">Visibility</th>
<th data-sort-key="state">State</th>
<th data-sort-key="isMedia">Media</th>
<th data-sort-key="discardCount">Discard Count</th>
<th data-sort-key="isAutoDiscardable">Auto Discardable</th>
<th data-sort-key="lastActiveSeconds">Last Active</th>
<th data-sort-key="utilityRank" class="sort-column">
<div class="header-cell-container">
<div>
<div>Utility</div>
<div>Rank<div>
</div>
</div>
</th>
<th data-sort-key="reactivationScore">
<div class="header-cell-container">
<div>
<div>Reactivation</div>
<div>Score</div>
</div>
</div>
</th>
<th data-sort-key="siteEngagementScore">
<div class="header-cell-container">
<div>
<div>Site</div>
<div>Engagement</div>
<div>Score</div>
</div>
</div>
</th>
<th data-sort-key="title">
<div class="header-cell-container">
Tab Title
</div>
</th>
<th data-sort-key="tabUrl">
<div class="header-cell-container">
Tab URL
</div>
</th>
<th data-sort-key="visibility">
<div class="header-cell-container">
Visibility
</div>
</th>
<th data-sort-key="loadingState">
<div class="header-cell-container">
Loading State
</div>
</th>
<th data-sort-key="state">
<div class="header-cell-container">
<div>
<div>Lifecycle</div>
<div>State</div>
</div>
</div>
</th>
<th data-sort-key="isMedia">
<div class="header-cell-container">
Media
</div>
</th>
<th data-sort-key="discardCount">
<div class="header-cell-container">
<div>
<div>Discard</div>
<div>Count</div>
</div>
</div>
</div></th>
<th data-sort-key="isAutoDiscardable">
<div class="header-cell-container">
<div>
<div>Auto</div>
<div>Discardable</div>
</div>
</div>
</th>
<th data-sort-key="lastActiveSeconds">
<div class="header-cell-container">
Last Active
</div>
</th>
<th>
<div class="header-cell-container">
Actions
</div>
</th>
</tr>
</thead>
<tbody id="tab-discards-info-table-body">
......@@ -51,6 +125,7 @@ general use and is not localized.
<tr>
<td class="utility-rank-cell"></td>
<td class="reactivation-score-cell"></td>
<td class="site-engagement-score-cell"></td>
<td class="title-cell">
<div class="title-cell-container">
<div class="favicon-div"></div>
......@@ -59,22 +134,24 @@ general use and is not localized.
</td>
<td class="tab-url-cell"></td>
<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="discard-count-cell"></td>
<td class="is-auto-discardable-cell boolean-cell">
<div class="is-auto-discardable-div"></div>
<div is="action-link" class="is-auto-discardable-link">
Toggle
[Toggle]
</div>
</td>
<td class="last-active-cell"></td>
<td class="discard-links-cell">
<td class="actions-cell">
<div is="action-link" class="load-link">[Load]</div>
<div is="action-link" class="freeze-link">[Freeze]</div>
<div is="action-link" class="discard-link">[Discard]</div>
<div is="action-link" class="discard-urgent-link">
[Urgent Discard]
</div>
<div is="action-link" class="freeze-link">[Freeze]</div>
</td>
</tr>
</template>
......
......@@ -71,14 +71,17 @@ cr.define('discards', function() {
}
// Compares numeric fields.
// Note: Visibility and state are represented as a numeric value.
// NOTE: visibility, loadingState and state are represented as a numeric
// value.
if ([
'visibility',
'loadingState',
'state',
'discardCount',
'utilityRank',
'reactivationScore',
'lastActiveSeconds',
'siteEngagementScore',
].includes(sortKey)) {
return val1 - val2;
}
......@@ -203,6 +206,24 @@ cr.define('discards', function() {
assertNotReached('Unsupported visibility: ' + visibility);
}
/**
* Returns a string representation of a loading state enum value for display
* in a table.
* @param {int} loadingState A value in LifecycleUnitLoadingState enum.
* @return {string} A string representation of the loading state.
*/
function loadingStateToString(loadingState) {
switch (loadingState) {
case 0:
return 'unloaded';
case 1:
return 'loading';
case 2:
return 'loaded';
}
assertNotReached('Unsupport loadingState: ' + loadingState);
}
function lifecycleStateToString(state) {
switch (state) {
case mojom.LifecycleUnitState.ACTIVE:
......@@ -253,6 +274,26 @@ 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)];
// Perform the action.
uiHandler.loadById(info.id);
};
let loadLink = row.querySelector('.load-link');
loadLink.addEventListener('click', loadListener);
// Set up the listeners for freeze links.
let freezeListener = function(e) {
// Get the info backing this row.
let info = infos[getRowIndex(e.target)];
// Perform the action.
uiHandler.freezeById(info.id);
};
let freezeLink = row.querySelector('.freeze-link');
freezeLink.addEventListener('click', freezeListener);
// Set up the listeners for discard links.
let discardListener = function(e) {
// Get the info backing this row.
......@@ -272,21 +313,19 @@ cr.define('discards', function() {
discardLink.addEventListener('click', discardListener);
discardUrgentLink.addEventListener('click', discardListener);
// Set up the listeners for freeze links.
let lifecycleListener = function(e) {
// Get the info backing this row.
let info = infos[getRowIndex(e.target)];
// Perform the action.
uiHandler.freezeById(info.id);
};
let freezeLink = row.querySelector('.freeze-link');
freezeLink.addEventListener('click', lifecycleListener);
return row;
}
/**
* Given an "action-link" element, enables or disables it.
*/
function setActionLinkEnabled(element, enabled) {
if (enabled)
element.removeAttribute('disabled');
else
element.setAttribute('disabled', '');
}
/**
* Updates a tab discards info table row in place. Sets/unsets 'disabled'
* attributes on action-links as necessary, and populates all contents.
......@@ -297,16 +336,23 @@ cr.define('discards', function() {
info.utilityRank.toString();
row.querySelector('.reactivation-score-cell').textContent =
info.hasReactivationScore ? info.reactivationScore.toFixed(4) : 'N/A';
row.querySelector('.site-engagement-score-cell').textContent =
info.siteEngagementScore.toFixed(1);
row.querySelector('.favicon-div').style.backgroundImage =
cr.icon.getFavicon(info.tabUrl);
row.querySelector('.title-div').textContent = info.title;
row.querySelector('.tab-url-cell').textContent = info.tabUrl;
row.querySelector('.visibility-cell').textContent =
visibilityToString(info.visibility);
row.querySelector('.loading-state-cell').textContent =
loadingStateToString(info.loadingState);
row.querySelector('.is-media-cell').textContent =
boolToString(info.isMedia);
// The lifecycle state is meaningless for 'unloaded' tabs.
row.querySelector('.state-cell').textContent =
lifecycleStateToString(info.state);
(info.loadingState != mojom.LifecycleUnitLoadingState.UNLOADED) ?
lifecycleStateToString(info.state) :
'';
row.querySelector('.discard-count-cell').textContent =
info.discardCount.toString();
row.querySelector('.is-auto-discardable-div').textContent =
......@@ -314,31 +360,45 @@ cr.define('discards', function() {
row.querySelector('.last-active-cell').textContent =
lastActiveToString(info.lastActiveSeconds);
// Enable/disable action links as appropriate.
row.querySelector('.is-auto-discardable-link').removeAttribute('disabled');
let loadLink = row.querySelector('.load-link');
let freezeLink = row.querySelector('.freeze-link');
let discardLink = row.querySelector('.discard-link');
let discardUrgentLink = row.querySelector('.discard-urgent-link');
let freezeLink = row.querySelector('.freeze-link');
switch (info.state) {
case mojom.LifecycleUnitState.ACTIVE:
discardLink.removeAttribute('disabled');
discardUrgentLink.removeAttribute('disabled');
freezeLink.removeAttribute('disabled');
break;
case mojom.LifecycleUnitState.THROTTLED:
case mojom.LifecycleUnitState.PENDING_FREEZE:
case mojom.LifecycleUnitState.FROZEN:
discardLink.removeAttribute('disabled');
discardUrgentLink.removeAttribute('disabled');
freezeLink.setAttribute('disabled', '');
break;
case mojom.LifecycleUnitState.PENDING_DISCARD:
case mojom.LifecycleUnitState.DISCARDED:
discardLink.setAttribute('disabled', '');
discardUrgentLink.setAttribute('disabled', '');
freezeLink.setAttribute('disabled', '');
break;
// Determine which action links should be enabled/disabled.
let loadEnabled = false;
let freezeEnabled = false;
let discardEnabled = false;
let discardUrgentEnabled = false;
if (info.loadingState == mojom.LifecycleUnitLoadingState.UNLOADED) {
loadEnabled = true;
} else {
freezeEnabled = true;
discardEnabled = true;
discardUrgentEnabled = true;
switch (info.state) {
case mojom.LifecycleUnitState.DISCARDED:
case mojom.LifecycleUnitState.PENDING_DISCARD:
discardUrgentEnabled = false;
discardEnabled = false;
// Deliberately fall through.
case mojom.LifecycleUnitState.FROZEN:
case mojom.LifecycleUnitState.PENDING_FREEZE:
freezeEnabled = false;
// Deliberately fall through.
case mojom.LifecycleUnitState.THROTTLED:
case mojom.LifecycleUnitState.ACTIVE:
// Everything stays enabled,
}
}
setActionLinkEnabled(loadLink, loadEnabled);
setActionLinkEnabled(freezeLink, freezeEnabled);
setActionLinkEnabled(discardLink, discardEnabled);
setActionLinkEnabled(discardUrgentLink, discardUrgentEnabled);
}
/**
......
......@@ -22,6 +22,8 @@ struct TabDiscardsInfo {
string title;
// The visibility of the LifecycleUnit.
LifecycleUnitVisibility visibility;
// The loading state of the LifecycleUnit.
LifecycleUnitLoadingState loading_state;
// The state of the LifecycleUnit.
LifecycleUnitState state;
// If the tab is currently using media functionality (casting, WebRTC, playing
......@@ -47,6 +49,8 @@ struct TabDiscardsInfo {
bool has_reactivation_score;
// Tab Ranker reactivation score, if |has_reactivation_score| is true.
double reactivation_score;
// Site engagement score.
double site_engagement_score;
};
// Interface for providing information about discards. Lives in the browser
......@@ -70,6 +74,9 @@ interface DiscardsDetailsProvider {
// Freezes a tab given its |tab_id|.
FreezeById(int32 tab_id);
// Loads a tab given its |tab_id|.
LoadById(int32 tab_id);
// Discards the least important tab. If |urgent| is specified the unload
// handlers will not be run, and the tab will be unloaded with prejudice.
// This can fail to discard a tab if no tabs are currently considered
......
......@@ -11,6 +11,7 @@
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/engagement/site_engagement_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/resource_coordinator/discard_reason.h"
#include "chrome/browser/resource_coordinator/lifecycle_unit.h"
......@@ -22,6 +23,8 @@
#include "chrome/browser/ui/webui/discards/discards.mojom.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/grit/browser_resources.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_ui.h"
#include "content/public/browser/web_ui_data_source.h"
......@@ -61,6 +64,21 @@ resource_coordinator::LifecycleUnit* GetLifecycleUnitById(int32_t id) {
return nullptr;
}
double GetSiteEngagementScore(content::WebContents* contents) {
// Get the active navigation entry. Restored tabs should always have one.
auto& controller = contents->GetController();
auto* nav_entry =
controller.GetEntryAtIndex(controller.GetCurrentEntryIndex());
DCHECK(nav_entry);
auto* engagement_svc = SiteEngagementService::Get(
Profile::FromBrowserContext(contents->GetBrowserContext()));
double engagement =
engagement_svc->GetDetails(nav_entry->GetURL()).total_score;
return engagement;
}
class DiscardsDetailsProviderImpl : public mojom::DiscardsDetailsProvider {
public:
// This instance is deleted when the supplied pipe is destroyed.
......@@ -97,6 +115,7 @@ class DiscardsDetailsProviderImpl : public mojom::DiscardsDetailsProvider {
info->title = base::UTF16ToUTF8(lifecycle_unit->GetTitle());
info->visibility =
GetLifecycleUnitVisibility(lifecycle_unit->GetVisibility());
info->loading_state = lifecycle_unit->GetLoadingState();
info->state = lifecycle_unit->GetState();
info->is_media = tab_lifecycle_unit_external->IsMediaTab();
info->discard_count = tab_lifecycle_unit_external->GetDiscardCount();
......@@ -117,6 +136,7 @@ class DiscardsDetailsProviderImpl : public mojom::DiscardsDetailsProvider {
info->has_reactivation_score = reactivation_score.has_value();
if (info->has_reactivation_score)
info->reactivation_score = reactivation_score.value();
info->site_engagement_score = GetSiteEngagementScore(contents);
infos.push_back(std::move(info));
}
......@@ -152,6 +172,12 @@ class DiscardsDetailsProviderImpl : public mojom::DiscardsDetailsProvider {
lifecycle_unit->Freeze();
}
void LoadById(int32_t id) override {
auto* lifecycle_unit = GetLifecycleUnitById(id);
if (lifecycle_unit)
lifecycle_unit->Load();
}
void Discard(bool urgent, DiscardCallback callback) override {
resource_coordinator::TabManager* tab_manager =
g_browser_process->GetTabManager();
......
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