0

[PageLifecycle] Update the TabManager with the page freezing state

In this CL, each frame updates its FrameResourceCoordinator with any
updates the lifecycle state. If the main frame lifecycle state is
updated, then the page inherit the new state.

The PageSignalGeneratorImpl then updates the state in TabLifecycleUnit
which is used by the chrome:://discards UI.

The CL also disables freezing a Tab that is already frozen.

Bug: chromium:804976
Cq-Include-Trybots: master.tryserver.chromium.linux:closure_compilation
Change-Id: Iba82286a06af17ccb74a7f6fac18cdb7914c003a
Reviewed-on: https://chromium-review.googlesource.com/942022
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: François Doray <fdoray@chromium.org>
Reviewed-by: Chris Hamilton <chrisha@chromium.org>
Reviewed-by: Shubhie Panicker <panicker@chromium.org>
Commit-Queue: Fadi Meawad <fmeawad@chromium.org>
Cr-Commit-Position: refs/heads/master@{#556084}
This commit is contained in:
Fadi Meawad
2018-05-04 16:37:59 +00:00
committed by Commit Bot
parent 266b8bce04
commit 686a5f7b76
39 changed files with 341 additions and 91 deletions

@ -18,19 +18,20 @@ DiscardMetricsLifecycleUnitObserver::~DiscardMetricsLifecycleUnitObserver() =
default;
void DiscardMetricsLifecycleUnitObserver::OnLifecycleUnitStateChanged(
LifecycleUnit* lifecycle_unit) {
if (lifecycle_unit->GetState() == LifecycleUnit::State::DISCARDED)
LifecycleUnit* lifecycle_unit,
mojom::LifecycleState previous_state) {
if (lifecycle_unit->GetState() == mojom::LifecycleState::kDiscarded)
OnDiscard(lifecycle_unit);
else
else if (previous_state == mojom::LifecycleState::kDiscarded)
OnReload();
}
void DiscardMetricsLifecycleUnitObserver::OnLifecycleUnitDestroyed(
LifecycleUnit* lifecycle_unit) {
// If the browser is not shutting down and the tab is in a LOADED state after
// If the browser is not shutting down and the tab is loaded after
// being discarded, record TabManager.Discarding.ReloadToCloseTime.
if (g_browser_process && !g_browser_process->IsShuttingDown() &&
lifecycle_unit->GetState() == LifecycleUnit::State::LOADED &&
lifecycle_unit->GetState() != mojom::LifecycleState::kDiscarded &&
!reload_time_.is_null()) {
auto reload_to_close_time = NowTicks() - reload_time_;
UMA_HISTOGRAM_CUSTOM_TIMES(

@ -19,7 +19,9 @@ class DiscardMetricsLifecycleUnitObserver : public LifecycleUnitObserver {
~DiscardMetricsLifecycleUnitObserver() override;
// LifecycleUnitObserver:
void OnLifecycleUnitStateChanged(LifecycleUnit* lifecycle_unit) override;
void OnLifecycleUnitStateChanged(
LifecycleUnit* lifecycle_unit,
mojom::LifecycleState previous_state) override;
void OnLifecycleUnitDestroyed(LifecycleUnit* lifecycle_unit) override;
private:

@ -93,19 +93,19 @@ TEST_F(DiscardMetricsLifecycleUnitObserverTest, DiscardReloadCount) {
histograms_.ExpectTotalCount(kDiscardCountHistogram, 0);
histograms_.ExpectTotalCount(kReloadCountHistogram, 0);
lifecycle_unit_->SetState(LifecycleUnit::State::DISCARDED);
lifecycle_unit_->SetState(mojom::LifecycleState::kDiscarded);
histograms_.ExpectTotalCount(kDiscardCountHistogram, 1);
histograms_.ExpectTotalCount(kReloadCountHistogram, 0);
lifecycle_unit_->SetState(LifecycleUnit::State::LOADED);
lifecycle_unit_->SetState(mojom::LifecycleState::kRunning);
histograms_.ExpectTotalCount(kDiscardCountHistogram, 1);
histograms_.ExpectTotalCount(kReloadCountHistogram, 1);
lifecycle_unit_->SetState(LifecycleUnit::State::DISCARDED);
lifecycle_unit_->SetState(mojom::LifecycleState::kDiscarded);
histograms_.ExpectTotalCount(kDiscardCountHistogram, 2);
histograms_.ExpectTotalCount(kReloadCountHistogram, 1);
lifecycle_unit_->SetState(LifecycleUnit::State::LOADED);
lifecycle_unit_->SetState(mojom::LifecycleState::kRunning);
histograms_.ExpectTotalCount(kDiscardCountHistogram, 2);
histograms_.ExpectTotalCount(kReloadCountHistogram, 2);
}
@ -113,11 +113,11 @@ TEST_F(DiscardMetricsLifecycleUnitObserverTest, DiscardReloadCount) {
TEST_F(DiscardMetricsLifecycleUnitObserverTest, DiscardToReloadTime) {
histograms_.ExpectTotalCount(kDiscardToReloadTimeHistogram, 0);
lifecycle_unit_->SetState(LifecycleUnit::State::DISCARDED);
lifecycle_unit_->SetState(mojom::LifecycleState::kDiscarded);
test_clock_.Advance(kShortDelay);
histograms_.ExpectTotalCount(kDiscardToReloadTimeHistogram, 0);
lifecycle_unit_->SetState(LifecycleUnit::State::LOADED);
lifecycle_unit_->SetState(mojom::LifecycleState::kRunning);
histograms_.ExpectTimeBucketCount(kDiscardToReloadTimeHistogram, kShortDelay,
1);
}
@ -128,11 +128,11 @@ TEST_F(DiscardMetricsLifecycleUnitObserverTest, InactiveToReloadTime) {
const base::TimeTicks last_focused_time = NowTicks();
lifecycle_unit_->SetLastFocusedTime(last_focused_time);
test_clock_.Advance(kShortDelay);
lifecycle_unit_->SetState(LifecycleUnit::State::DISCARDED);
lifecycle_unit_->SetState(mojom::LifecycleState::kDiscarded);
test_clock_.Advance(kShortDelay);
histograms_.ExpectTotalCount(kInactiveToReloadTimeHistogram, 0);
lifecycle_unit_->SetState(LifecycleUnit::State::LOADED);
lifecycle_unit_->SetState(mojom::LifecycleState::kRunning);
histograms_.ExpectTimeBucketCount(kInactiveToReloadTimeHistogram,
2 * kShortDelay, 1);
}
@ -148,7 +148,7 @@ TEST_F(DiscardMetricsLifecycleUnitObserverTest,
ReloadToCloseTimeDiscardedButNotReloaded) {
histograms_.ExpectTotalCount(kReloadToCloseTimeHistogram, 0);
lifecycle_unit_->SetState(LifecycleUnit::State::DISCARDED);
lifecycle_unit_->SetState(mojom::LifecycleState::kDiscarded);
histograms_.ExpectTotalCount(kReloadToCloseTimeHistogram, 0);
lifecycle_unit_.reset();
@ -159,11 +159,11 @@ TEST_F(DiscardMetricsLifecycleUnitObserverTest, ReloadToCloseTime) {
histograms_.ExpectTotalCount(kReloadToCloseTimeHistogram, 0);
test_clock_.Advance(kShortDelay * 1);
lifecycle_unit_->SetState(LifecycleUnit::State::DISCARDED);
lifecycle_unit_->SetState(mojom::LifecycleState::kDiscarded);
histograms_.ExpectTotalCount(kReloadToCloseTimeHistogram, 0);
test_clock_.Advance(kShortDelay * 2);
lifecycle_unit_->SetState(LifecycleUnit::State::LOADED);
lifecycle_unit_->SetState(mojom::LifecycleState::kRunning);
histograms_.ExpectTotalCount(kReloadToCloseTimeHistogram, 0);
test_clock_.Advance(kShortDelay * 4);

@ -14,6 +14,7 @@
#include "base/time/time.h"
#include "chrome/browser/resource_coordinator/discard_reason.h"
#include "content/public/browser/visibility.h"
#include "services/resource_coordinator/public/mojom/lifecycle.mojom.h"
namespace resource_coordinator {
@ -26,13 +27,6 @@ class TabLifecycleUnitExternal;
// use any system resource.
class LifecycleUnit {
public:
enum class State {
// The LifecycleUnit is using system resources.
LOADED,
// The LifecycleUnit is not using system resources.
DISCARDED,
};
// Used to sort LifecycleUnit by importance. The most important LifecycleUnit
// has the greatest SortKey.
struct SortKey {
@ -78,7 +72,7 @@ class LifecycleUnit {
virtual SortKey GetSortKey() const = 0;
// Returns the current state of this LifecycleUnit.
virtual State GetState() const = 0;
virtual mojom::LifecycleState GetState() const = 0;
// Returns the current visibility of this LifecycleUnit.
virtual content::Visibility GetVisibility() const = 0;
@ -86,8 +80,8 @@ class LifecycleUnit {
// Returns the last time that the visibility of the LifecycleUnit changed.
virtual base::TimeTicks GetLastVisibilityChangeTime() const = 0;
// Freezes this LifecycleUnit, i.e. prevents it from using the CPU. Returns
// true on success.
// Request that the LifecycleUnit be frozen, return true if the request is
// successfully sent.
virtual bool Freeze() = 0;
// Returns the estimated number of kilobytes that would be freed if this

@ -18,7 +18,7 @@ int32_t LifecycleUnitBase::GetID() const {
return id_;
}
LifecycleUnit::State LifecycleUnitBase::GetState() const {
mojom::LifecycleState LifecycleUnitBase::GetState() const {
return state_;
}
@ -34,12 +34,13 @@ void LifecycleUnitBase::RemoveObserver(LifecycleUnitObserver* observer) {
observers_.RemoveObserver(observer);
}
void LifecycleUnitBase::SetState(State state) {
void LifecycleUnitBase::SetState(mojom::LifecycleState state) {
if (state == state_)
return;
mojom::LifecycleState previous_state = state_;
state_ = state;
for (auto& observer : observers_)
observer.OnLifecycleUnitStateChanged(this);
observer.OnLifecycleUnitStateChanged(this, previous_state);
}
void LifecycleUnitBase::OnLifecycleUnitVisibilityChanged(

@ -20,14 +20,14 @@ class LifecycleUnitBase : public LifecycleUnit {
// LifecycleUnit:
int32_t GetID() const override;
State GetState() const override;
mojom::LifecycleState GetState() const override;
base::TimeTicks GetLastVisibilityChangeTime() const override;
void AddObserver(LifecycleUnitObserver* observer) override;
void RemoveObserver(LifecycleUnitObserver* observer) override;
protected:
// Sets the state of this LifecycleUnit to |state| and notifies observers.
void SetState(State state);
void SetState(mojom::LifecycleState state);
// Notifies observers that the visibility of the LifecycleUnit has changed.
void OnLifecycleUnitVisibilityChanged(content::Visibility visibility);
@ -44,7 +44,7 @@ class LifecycleUnitBase : public LifecycleUnit {
const int32_t id_ = ++next_id_;
// Current state of this LifecycleUnit.
State state_ = State::LOADED;
mojom::LifecycleState state_ = mojom::LifecycleState::kRunning;
base::TimeTicks last_visibility_change_time_;

@ -20,7 +20,8 @@ class MockLifecycleUnitObserver : public LifecycleUnitObserver {
public:
MockLifecycleUnitObserver() = default;
MOCK_METHOD1(OnLifecycleUnitStateChanged, void(LifecycleUnit*));
MOCK_METHOD2(OnLifecycleUnitStateChanged,
void(LifecycleUnit*, mojom::LifecycleState));
MOCK_METHOD2(OnLifecycleUnitVisibilityChanged,
void(LifecycleUnit*, content::Visibility));
MOCK_METHOD1(OnLifecycleUnitDestroyed, void(LifecycleUnit*));
@ -46,7 +47,6 @@ class DummyLifecycleUnit : public LifecycleUnitBase {
return base::ProcessHandle();
}
SortKey GetSortKey() const override { return SortKey(); }
State GetState() const override { return State::LOADED; }
content::Visibility GetVisibility() const override {
return content::Visibility::VISIBLE;
}
@ -86,12 +86,13 @@ TEST(LifecycleUnitBaseTest, SetStateNotifiesObservers) {
lifecycle_unit.AddObserver(&observer);
// Observer is notified when the state changes.
EXPECT_CALL(observer, OnLifecycleUnitStateChanged(&lifecycle_unit));
lifecycle_unit.SetState(LifecycleUnit::State::DISCARDED);
EXPECT_CALL(observer, OnLifecycleUnitStateChanged(&lifecycle_unit,
lifecycle_unit.GetState()));
lifecycle_unit.SetState(mojom::LifecycleState::kDiscarded);
testing::Mock::VerifyAndClear(&observer);
// Observer isn't notified when the state stays the same.
lifecycle_unit.SetState(LifecycleUnit::State::DISCARDED);
lifecycle_unit.SetState(mojom::LifecycleState::kDiscarded);
lifecycle_unit.RemoveObserver(&observer);
}

@ -9,7 +9,8 @@ namespace resource_coordinator {
LifecycleUnitObserver::~LifecycleUnitObserver() = default;
void LifecycleUnitObserver::OnLifecycleUnitStateChanged(
LifecycleUnit* lifecycle_unit) {}
LifecycleUnit* lifecycle_unit,
mojom::LifecycleState previous_state) {}
void LifecycleUnitObserver::OnLifecycleUnitVisibilityChanged(
LifecycleUnit* lifecycle_unit,

@ -6,6 +6,7 @@
#define CHROME_BROWSER_RESOURCE_COORDINATOR_LIFECYCLE_UNIT_OBSERVER_H_
#include "content/public/browser/visibility.h"
#include "services/resource_coordinator/public/mojom/lifecycle.mojom.h"
namespace resource_coordinator {
@ -17,7 +18,9 @@ class LifecycleUnitObserver {
virtual ~LifecycleUnitObserver();
// Invoked when the state of the observed LifecycleUnit changes.
virtual void OnLifecycleUnitStateChanged(LifecycleUnit* lifecycle_unit);
virtual void OnLifecycleUnitStateChanged(
LifecycleUnit* lifecycle_unit,
mojom::LifecycleState previous_state);
// Invoked when the visibility of the observed LifecyleUnit changes.
virtual void OnLifecycleUnitVisibilityChanged(LifecycleUnit* lifecycle_unit,

@ -56,6 +56,15 @@ void PageSignalReceiver::SetExpectedTaskQueueingDuration(
duration);
}
void PageSignalReceiver::SetLifecycleState(const CoordinationUnitID& cu_id,
mojom::LifecycleState state) {
auto web_contents_iter = cu_id_web_contents_map_.find(cu_id);
if (web_contents_iter == cu_id_web_contents_map_.end())
return;
for (auto& observer : observers_)
observer.OnLifecycleStateChanged(web_contents_iter->second, state);
}
void PageSignalReceiver::AddObserver(PageSignalObserver* observer) {
// When PageSignalReceiver starts to have observer, construct the mojo
// channel.

@ -28,6 +28,8 @@ class PageSignalObserver {
virtual void OnExpectedTaskQueueingDurationSet(
content::WebContents* web_contents,
base::TimeDelta duration) {}
virtual void OnLifecycleStateChanged(content::WebContents* web_contents,
mojom::LifecycleState state) {}
};
// Implementation of resource_coordinator::mojom::PageSignalReceiver.
@ -50,6 +52,8 @@ class PageSignalReceiver : public mojom::PageSignalReceiver {
void NotifyPageAlmostIdle(const CoordinationUnitID& cu_id) override;
void SetExpectedTaskQueueingDuration(const CoordinationUnitID& cu_id,
base::TimeDelta duration) override;
void SetLifecycleState(const CoordinationUnitID& cu_id,
mojom::LifecycleState) override;
void AddObserver(PageSignalObserver* observer);
void RemoveObserver(PageSignalObserver* observer);

@ -56,8 +56,8 @@ void TabLifecycleUnitSource::TabLifecycleUnit::SetFocused(bool focused) {
return;
last_focused_time_ = focused ? base::TimeTicks::Max() : NowTicks();
if (focused && GetState() == State::DISCARDED) {
SetState(State::LOADED);
if (focused && GetState() == mojom::LifecycleState::kDiscarded) {
SetState(mojom::LifecycleState::kRunning);
// 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
@ -76,6 +76,12 @@ void TabLifecycleUnitSource::TabLifecycleUnit::SetRecentlyAudible(
recently_audible_time_ = NowTicks();
}
void TabLifecycleUnitSource::TabLifecycleUnit::UpdateLifecycleState(
mojom::LifecycleState state) {
DCHECK_NE(mojom::LifecycleState::kDiscarded, state);
SetState(state);
}
TabLifecycleUnitExternal*
TabLifecycleUnitSource::TabLifecycleUnit::AsTabLifecycleUnitExternal() {
return this;
@ -107,9 +113,8 @@ content::Visibility TabLifecycleUnitSource::TabLifecycleUnit::GetVisibility()
}
bool TabLifecycleUnitSource::TabLifecycleUnit::Freeze() {
// Can't freeze tabs that are already discarded or frozen.
// TODO(fmeawad): Don't freeze already frozen tabs.
if (GetState() != State::LOADED)
// Can't request to freeze a discarded tab.
if (IsDiscarded())
return false;
GetWebContents()->FreezePage();
@ -273,7 +278,7 @@ bool TabLifecycleUnitSource::TabLifecycleUnit::Discard(
// RenderFrameProxyHosts.
old_contents_deleter.reset();
SetState(State::DISCARDED);
SetState(mojom::LifecycleState::kDiscarded);
++discard_count_;
OnDiscardedStateChange();
@ -329,7 +334,11 @@ bool TabLifecycleUnitSource::TabLifecycleUnit::FreezeTab() {
}
bool TabLifecycleUnitSource::TabLifecycleUnit::IsDiscarded() const {
return GetState() == State::DISCARDED;
return GetState() == mojom::LifecycleState::kDiscarded;
}
bool TabLifecycleUnitSource::TabLifecycleUnit::IsFrozen() const {
return GetState() == mojom::LifecycleState::kFrozen;
}
int TabLifecycleUnitSource::TabLifecycleUnit::GetDiscardCount() const {
@ -347,8 +356,8 @@ TabLifecycleUnitSource::TabLifecycleUnit::GetRenderProcessHost() const {
}
void TabLifecycleUnitSource::TabLifecycleUnit::DidStartLoading() {
if (GetState() == State::DISCARDED) {
SetState(State::LOADED);
if (IsDiscarded()) {
SetState(mojom::LifecycleState::kRunning);
OnDiscardedStateChange();
}
}

@ -86,8 +86,17 @@ class TabLifecycleUnitSource::TabLifecycleUnit
bool FreezeTab() override;
bool DiscardTab() override;
bool IsDiscarded() const override;
bool IsFrozen() const override;
int GetDiscardCount() const override;
protected:
// TabLifecycleUnitSource needs to update the state when a external lifecycle
// state change is observed.
friend class TabLifecycleUnitSource;
// Updates the tab's lifecycle state when changed outside the tab lifecycle
// unit.
void UpdateLifecycleState(mojom::LifecycleState state);
private:
// Invoked when the state goes from DISCARDED to non-DISCARDED and vice-versa.
void OnDiscardedStateChange();

@ -51,6 +51,9 @@ class TabLifecycleUnitExternal {
// Returns true if the tab is discarded.
virtual bool IsDiscarded() const = 0;
// Returns true if the tab is frozen.
virtual bool IsFrozen() const = 0;
// Returns the number of times that the tab was discarded.
virtual int GetDiscardCount() const = 0;
};

@ -53,9 +53,15 @@ TabLifecycleUnitSource::TabLifecycleUnitSource()
DCHECK(BrowserList::GetInstance()->empty());
browser_tab_strip_tracker_.Init();
instance_ = this;
// TODO(chrisha): Create a ScopedPageSignalObserver helper class to clean up
// this manual lifetime management.
if (auto* page_signal_receiver = PageSignalReceiver::GetInstance())
page_signal_receiver->AddObserver(this);
}
TabLifecycleUnitSource::~TabLifecycleUnitSource() {
if (auto* page_signal_receiver = PageSignalReceiver::GetInstance())
page_signal_receiver->RemoveObserver(this);
DCHECK_EQ(instance_, this);
instance_ = nullptr;
}
@ -209,6 +215,17 @@ void TabLifecycleUnitSource::OnBrowserNoLongerActive(Browser* browser) {
UpdateFocusedTab();
}
void TabLifecycleUnitSource::OnLifecycleStateChanged(
content::WebContents* web_contents,
mojom::LifecycleState state) {
TabLifecycleUnit* lifecycle_unit = GetTabLifecycleUnit(web_contents);
// Some WebContents aren't attached to a tab, so there is no corresponding
// TabLifecycleUnit.
if (lifecycle_unit)
lifecycle_unit->UpdateLifecycleState(state);
}
} // namespace resource_coordinator
DEFINE_WEB_CONTENTS_USER_DATA_KEY(

@ -10,6 +10,7 @@
#include "base/macros.h"
#include "base/observer_list.h"
#include "chrome/browser/resource_coordinator/lifecycle_unit_source_base.h"
#include "chrome/browser/resource_coordinator/page_signal_receiver.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/browser_tab_strip_tracker.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
@ -28,6 +29,7 @@ class TabLifecycleUnitExternal;
// Creates and destroys LifecycleUnits as tabs are created and destroyed.
class TabLifecycleUnitSource : public BrowserListObserver,
public LifecycleUnitSourceBase,
public PageSignalObserver,
public TabStripModelObserver {
public:
TabLifecycleUnitSource();
@ -94,6 +96,10 @@ class TabLifecycleUnitSource : public BrowserListObserver,
void OnBrowserSetLastActive(Browser* browser) override;
void OnBrowserNoLongerActive(Browser* browser) override;
// PageSignalObserver:
void OnLifecycleStateChanged(content::WebContents* web_contents,
mojom::LifecycleState state) override;
// Tracks the BrowserList and all TabStripModels.
BrowserTabStripTracker browser_tab_strip_tracker_;

@ -69,8 +69,8 @@ class MockLifecycleUnitObserver : public LifecycleUnitObserver {
public:
MockLifecycleUnitObserver() = default;
MOCK_METHOD1(OnLifecycleUnitStateChanged,
void(LifecycleUnit* lifecycle_unit));
MOCK_METHOD2(OnLifecycleUnitStateChanged,
void(LifecycleUnit* lifecycle_unit, mojom::LifecycleState));
MOCK_METHOD2(OnLifecycleUnitVisibilityChanged,
void(LifecycleUnit* lifecycle_unit,
content::Visibility visibility));
@ -359,11 +359,12 @@ TEST_F(TabLifecycleUnitSourceTest, DetachWebContents) {
other_tab_strip_model.AppendWebContents(std::move(owned_contents), false);
EXPECT_FOR_ALL_DISCARD_REASONS(first_lifecycle_unit, CanDiscard, true);
EXPECT_EQ(LifecycleUnit::State::LOADED, first_lifecycle_unit->GetState());
EXPECT_EQ(mojom::LifecycleState::kRunning, first_lifecycle_unit->GetState());
EXPECT_CALL(tab_observer_, OnDiscardedStateChange(testing::_, true));
first_lifecycle_unit->Discard(DiscardReason::kProactive);
testing::Mock::VerifyAndClear(&tab_observer_);
EXPECT_EQ(LifecycleUnit::State::DISCARDED, first_lifecycle_unit->GetState());
EXPECT_EQ(mojom::LifecycleState::kDiscarded,
first_lifecycle_unit->GetState());
// Expect a notification when the tab is closed.
CloseTabsAndExpectNotifications(&other_tab_strip_model,
@ -409,14 +410,14 @@ TEST_F(TabLifecycleUnitSourceTest, Discard) {
initial_web_contents->SetLastActiveTime(kDummyLastActiveTime);
// Discard the tab.
EXPECT_EQ(LifecycleUnit::State::LOADED,
EXPECT_EQ(mojom::LifecycleState::kRunning,
background_lifecycle_unit->GetState());
EXPECT_CALL(tab_observer_, OnDiscardedStateChange(testing::_, true));
background_lifecycle_unit->Discard(DiscardReason::kProactive);
testing::Mock::VerifyAndClear(&tab_observer_);
// Expect the tab to be discarded and the last active time to be preserved.
EXPECT_EQ(LifecycleUnit::State::DISCARDED,
EXPECT_EQ(mojom::LifecycleState::kDiscarded,
background_lifecycle_unit->GetState());
EXPECT_NE(initial_web_contents, tab_strip_model_->GetWebContentsAt(0));
EXPECT_FALSE(
@ -436,12 +437,12 @@ TEST_F(TabLifecycleUnitSourceTest, DiscardAndActivate) {
tab_strip_model_->GetWebContentsAt(0);
// Discard the tab.
EXPECT_EQ(LifecycleUnit::State::LOADED,
EXPECT_EQ(mojom::LifecycleState::kRunning,
background_lifecycle_unit->GetState());
EXPECT_CALL(tab_observer_, OnDiscardedStateChange(testing::_, true));
background_lifecycle_unit->Discard(DiscardReason::kProactive);
testing::Mock::VerifyAndClear(&tab_observer_);
EXPECT_EQ(LifecycleUnit::State::DISCARDED,
EXPECT_EQ(mojom::LifecycleState::kDiscarded,
background_lifecycle_unit->GetState());
EXPECT_NE(initial_web_contents, tab_strip_model_->GetWebContentsAt(0));
EXPECT_FALSE(
@ -451,7 +452,7 @@ TEST_F(TabLifecycleUnitSourceTest, DiscardAndActivate) {
EXPECT_CALL(tab_observer_, OnDiscardedStateChange(testing::_, false));
tab_strip_model_->ActivateTabAt(0, true);
testing::Mock::VerifyAndClear(&tab_observer_);
EXPECT_EQ(LifecycleUnit::State::LOADED,
EXPECT_EQ(mojom::LifecycleState::kRunning,
background_lifecycle_unit->GetState());
EXPECT_TRUE(
tab_strip_model_->GetWebContentsAt(0)->GetController().GetPendingEntry());
@ -466,12 +467,12 @@ TEST_F(TabLifecycleUnitSourceTest, DiscardAndExplicitlyReload) {
tab_strip_model_->GetWebContentsAt(0);
// Discard the tab.
EXPECT_EQ(LifecycleUnit::State::LOADED,
EXPECT_EQ(mojom::LifecycleState::kRunning,
background_lifecycle_unit->GetState());
EXPECT_CALL(tab_observer_, OnDiscardedStateChange(testing::_, true));
background_lifecycle_unit->Discard(DiscardReason::kProactive);
testing::Mock::VerifyAndClear(&tab_observer_);
EXPECT_EQ(LifecycleUnit::State::DISCARDED,
EXPECT_EQ(mojom::LifecycleState::kDiscarded,
background_lifecycle_unit->GetState());
EXPECT_NE(initial_web_contents, tab_strip_model_->GetWebContentsAt(0));
EXPECT_FALSE(
@ -482,7 +483,7 @@ TEST_F(TabLifecycleUnitSourceTest, DiscardAndExplicitlyReload) {
tab_strip_model_->GetWebContentsAt(0)->GetController().Reload(
content::ReloadType::NORMAL, false);
testing::Mock::VerifyAndClear(&tab_observer_);
EXPECT_EQ(LifecycleUnit::State::LOADED,
EXPECT_EQ(mojom::LifecycleState::kRunning,
background_lifecycle_unit->GetState());
EXPECT_TRUE(
tab_strip_model_->GetWebContentsAt(0)->GetController().GetPendingEntry());
@ -500,12 +501,12 @@ TEST_F(TabLifecycleUnitSourceTest, CanOnlyDiscardOnce) {
EXPECT_FOR_ALL_DISCARD_REASONS(background_lifecycle_unit, CanDiscard, true);
// Discard the tab.
EXPECT_EQ(LifecycleUnit::State::LOADED,
EXPECT_EQ(mojom::LifecycleState::kRunning,
background_lifecycle_unit->GetState());
EXPECT_CALL(tab_observer_, OnDiscardedStateChange(testing::_, true));
background_lifecycle_unit->Discard(DiscardReason::kProactive);
testing::Mock::VerifyAndClear(&tab_observer_);
EXPECT_EQ(LifecycleUnit::State::DISCARDED,
EXPECT_EQ(mojom::LifecycleState::kDiscarded,
background_lifecycle_unit->GetState());
EXPECT_NE(initial_web_contents, tab_strip_model_->GetWebContentsAt(0));
EXPECT_FALSE(
@ -516,7 +517,7 @@ TEST_F(TabLifecycleUnitSourceTest, CanOnlyDiscardOnce) {
tab_strip_model_->GetWebContentsAt(0)->GetController().Reload(
content::ReloadType::NORMAL, false);
testing::Mock::VerifyAndClear(&tab_observer_);
EXPECT_EQ(LifecycleUnit::State::LOADED,
EXPECT_EQ(mojom::LifecycleState::kRunning,
background_lifecycle_unit->GetState());
EXPECT_TRUE(
tab_strip_model_->GetWebContentsAt(0)->GetController().GetPendingEntry());
@ -533,4 +534,44 @@ TEST_F(TabLifecycleUnitSourceTest, CanOnlyDiscardOnce) {
#endif
}
TEST_F(TabLifecycleUnitSourceTest, CannotFreezeADiscardedTab) {
LifecycleUnit* background_lifecycle_unit = nullptr;
LifecycleUnit* foreground_lifecycle_unit = nullptr;
CreateTwoTabs(true /* focus_tab_strip */, &background_lifecycle_unit,
&foreground_lifecycle_unit);
content::WebContents* initial_web_contents =
tab_strip_model_->GetWebContentsAt(0);
test_clock_.Advance(kShortDelay);
// It should be possible to discard the background tab.
EXPECT_FOR_ALL_DISCARD_REASONS(background_lifecycle_unit, CanDiscard, true);
// Discard the tab.
EXPECT_EQ(mojom::LifecycleState::kRunning,
background_lifecycle_unit->GetState());
EXPECT_CALL(tab_observer_, OnDiscardedStateChange(testing::_, true));
background_lifecycle_unit->Discard(DiscardReason::kProactive);
testing::Mock::VerifyAndClear(&tab_observer_);
EXPECT_EQ(mojom::LifecycleState::kDiscarded,
background_lifecycle_unit->GetState());
EXPECT_NE(initial_web_contents, tab_strip_model_->GetWebContentsAt(0));
EXPECT_FALSE(
tab_strip_model_->GetWebContentsAt(0)->GetController().GetPendingEntry());
EXPECT_FALSE(background_lifecycle_unit->Freeze());
// Explicitly reload the tab. Expect the state to be LOADED.
EXPECT_CALL(tab_observer_, OnDiscardedStateChange(testing::_, false));
tab_strip_model_->GetWebContentsAt(0)->GetController().Reload(
content::ReloadType::NORMAL, false);
testing::Mock::VerifyAndClear(&tab_observer_);
EXPECT_EQ(mojom::LifecycleState::kRunning,
background_lifecycle_unit->GetState());
EXPECT_TRUE(
tab_strip_model_->GetWebContentsAt(0)->GetController().GetPendingEntry());
// Should be able to freeze the reloaded tab.
EXPECT_TRUE(background_lifecycle_unit->Freeze());
}
} // namespace resource_coordinator

@ -52,7 +52,8 @@ class MockLifecycleUnitObserver : public LifecycleUnitObserver {
public:
MockLifecycleUnitObserver() = default;
MOCK_METHOD1(OnLifecycleUnitStateChanged, void(LifecycleUnit*));
MOCK_METHOD2(OnLifecycleUnitStateChanged,
void(LifecycleUnit*, mojom::LifecycleState));
MOCK_METHOD1(OnLifecycleUnitDestroyed, void(LifecycleUnit*));
MOCK_METHOD2(OnLifecycleUnitVisibilityChanged,
void(LifecycleUnit*, content::Visibility));

@ -119,7 +119,7 @@ std::unique_ptr<base::trace_event::ConvertableToTraceFormat> DataAsTraceValue(
int GetNumLoadedLifecycleUnits(LifecycleUnitSet lifecycle_unit_set) {
int num_loaded_lifecycle_units = 0;
for (auto* lifecycle_unit : lifecycle_unit_set)
if (lifecycle_unit->GetState() == LifecycleUnit::State::LOADED)
if (lifecycle_unit->GetState() != mojom::LifecycleState::kDiscarded)
num_loaded_lifecycle_units++;
return num_loaded_lifecycle_units;
}
@ -1026,11 +1026,13 @@ void TabManager::UpdateProactiveDiscardTimerIfNecessary() {
next_to_discard, this));
}
void TabManager::OnLifecycleUnitStateChanged(LifecycleUnit* lifecycle_unit) {
if (lifecycle_unit->GetState() == LifecycleUnit::State::LOADED)
num_loaded_lifecycle_units_++;
else
void TabManager::OnLifecycleUnitStateChanged(
LifecycleUnit* lifecycle_unit,
mojom::LifecycleState previous_state) {
if (lifecycle_unit->GetState() == mojom::LifecycleState::kDiscarded)
num_loaded_lifecycle_units_--;
else if (previous_state == mojom::LifecycleState::kDiscarded)
num_loaded_lifecycle_units_++;
DCHECK_EQ(num_loaded_lifecycle_units_,
GetNumLoadedLifecycleUnits(lifecycle_units_));
@ -1045,7 +1047,7 @@ void TabManager::OnLifecycleUnitVisibilityChanged(
}
void TabManager::OnLifecycleUnitDestroyed(LifecycleUnit* lifecycle_unit) {
if (lifecycle_unit->GetState() == LifecycleUnit::State::LOADED)
if (lifecycle_unit->GetState() != mojom::LifecycleState::kDiscarded)
num_loaded_lifecycle_units_--;
lifecycle_units_.erase(lifecycle_unit);
@ -1057,8 +1059,7 @@ void TabManager::OnLifecycleUnitDestroyed(LifecycleUnit* lifecycle_unit) {
void TabManager::OnLifecycleUnitCreated(LifecycleUnit* lifecycle_unit) {
lifecycle_units_.insert(lifecycle_unit);
if (lifecycle_unit->GetState() == LifecycleUnit::State::LOADED)
if (lifecycle_unit->GetState() != mojom::LifecycleState::kDiscarded)
num_loaded_lifecycle_units_++;
// Add an observer to be notified of destruction.

@ -427,7 +427,9 @@ class TabManager : public LifecycleUnitObserver,
LifecycleUnit* lifecycle_unit,
content::Visibility visibility) override;
void OnLifecycleUnitDestroyed(LifecycleUnit* lifecycle_unit) override;
void OnLifecycleUnitStateChanged(LifecycleUnit* lifecycle_unit) override;
void OnLifecycleUnitStateChanged(
LifecycleUnit* lifecycle_unit,
mojom::LifecycleState previous_state) override;
// LifecycleUnitSourceObserver:
void OnLifecycleUnitCreated(LifecycleUnit* lifecycle_unit) override;

@ -48,7 +48,6 @@ class DummyLifecycleUnit : public LifecycleUnitBase {
return nullptr;
}
SortKey GetSortKey() const override { return SortKey(last_focused_time_); }
State GetState() const override { return State::LOADED; }
content::Visibility GetVisibility() const override {
return content::Visibility::VISIBLE;
}

@ -37,6 +37,7 @@ general use and is not localized.
<th data-sort-key="tabUrl">Tab URL</th>
<th data-sort-key="visibility">Visibility</th>
<th data-sort-key="isMedia">Media</th>
<th data-sort-key="isFrozen">Frozen</th>
<th data-sort-key="isDiscarded">Discarded</th>
<th data-sort-key="discardCount">Discard Count</th>
<th data-sort-key="isAutoDiscardable">Auto Discardable</th>
@ -58,6 +59,7 @@ general use and is not localized.
<td class="tab-url-cell"></td>
<td class="visibility-cell"></td>
<td class="is-media-cell boolean-cell"></td>
<td class="is-frozen-cell boolean-cell"></td>
<td class="is-discarded-cell boolean-cell"></td>
<td class="discard-count-cell"></td>
<td class="is-auto-discardable-cell boolean-cell">

@ -64,7 +64,8 @@ cr.define('discards', function() {
}
// Compares boolean fields.
if (['isMedia', 'isDiscarded', 'isAutoDiscardable'].includes(sortKey)) {
if (['isMedia', 'isFrozen', 'isDiscarded', 'isAutoDiscardable'].includes(
sortKey)) {
if (val1 == val2)
return 0;
return val1 ? 1 : -1;
@ -254,8 +255,6 @@ cr.define('discards', function() {
let lifecycleListener = function(e) {
// Get the info backing this row.
let info = infos[getRowIndex(e.target)];
// TODO(fmeawad): Disable the action, and let the update function
// re-enable it. Blocked on acquiring freeze status.
// Perform the action.
uiHandler.freezeById(info.id);
};
@ -282,6 +281,8 @@ cr.define('discards', function() {
visibilityToString(info.visibility);
row.querySelector('.is-media-cell').textContent =
boolToString(info.isMedia);
row.querySelector('.is-frozen-cell').textContent =
boolToString(info.isFrozen);
row.querySelector('.is-discarded-cell').textContent =
boolToString(info.isDiscarded);
row.querySelector('.discard-count-cell').textContent =
@ -302,6 +303,12 @@ cr.define('discards', function() {
discardLink.removeAttribute('disabled');
discardUrgentLink.removeAttribute('disabled');
}
let freezeLink = row.querySelector('.freeze-link');
if (info.isFrozen)
freezeLink.setAttribute('disabled', '');
else
freezeLink.removeAttribute('disabled', '');
}
/**

@ -25,6 +25,11 @@ struct TabDiscardsInfo {
bool is_media;
// If the tab is currently discarded, this is true.
bool is_discarded;
// If the tab is currently frozen, this is true.
// TODO(fmeawad): is_discarded and is_frozen are mutually exclusive, instead
// of representing each individually, we should add a "lifecycle_state" field
// instead.
bool is_frozen;
// The number of times this tab has been discarded in the current browser
// session.
int32 discard_count;

@ -87,6 +87,7 @@ class DiscardsDetailsProviderImpl : public mojom::DiscardsDetailsProvider {
info->visibility =
GetLifecycleUnitVisibility(lifecycle_unit->GetVisibility());
info->is_media = tab_lifecycle_unit_external->IsMediaTab();
info->is_frozen = tab_lifecycle_unit_external->IsFrozen();
info->is_discarded = tab_lifecycle_unit_external->IsDiscarded();
info->discard_count = tab_lifecycle_unit_external->GetDiscardCount();
info->utility_rank = rank++;

@ -22,6 +22,7 @@ TEST_F('DiscardsTest', 'CompareTabDiscardsInfo', function() {
tabUrl: 'http://urlone.com',
visibility: 0,
isMedia: false,
isFrozen: false,
isDiscarded: false,
isAutoDiscardable: false,
discardCount: 0,
@ -33,6 +34,7 @@ TEST_F('DiscardsTest', 'CompareTabDiscardsInfo', function() {
tabUrl: 'http://urltwo.com',
visibility: 1,
isMedia: true,
isFrozen: true,
isDiscarded: true,
isAutoDiscardable: true,
discardCount: 1,
@ -40,7 +42,7 @@ TEST_F('DiscardsTest', 'CompareTabDiscardsInfo', function() {
lastActiveSeconds: 1
};
['title', 'tabUrl', 'visibility', 'isMedia', 'isDiscarded',
['title', 'tabUrl', 'visibility', 'isMedia', 'isFrozen', 'isDiscarded',
'isAutoDiscardable', 'discardCount', 'utilityRank', 'lastActiveSeconds']
.forEach((sortKey) => {
assertTrue(

@ -68,6 +68,16 @@ void FrameCoordinationUnitImpl::SetNetworkAlmostIdle(bool idle) {
SetProperty(mojom::PropertyType::kNetworkAlmostIdle, idle);
}
void FrameCoordinationUnitImpl::SetLifecycleState(mojom::LifecycleState state) {
SetProperty(mojom::PropertyType::kLifecycleState,
static_cast<int64_t>(state));
// The page will have the same lifecycle state as the main frame.
if (IsMainFrame() && GetPageCoordinationUnit()) {
GetPageCoordinationUnit()->SetProperty(mojom::PropertyType::kLifecycleState,
static_cast<int64_t>(state));
}
}
void FrameCoordinationUnitImpl::OnAlertFired() {
SendEvent(mojom::Event::kAlertFired);
}

@ -33,6 +33,7 @@ class FrameCoordinationUnitImpl
void RemoveChildFrame(const CoordinationUnitID& cu_id) override;
void SetAudibility(bool audible) override;
void SetNetworkAlmostIdle(bool idle) override;
void SetLifecycleState(mojom::LifecycleState state) override;
void OnAlertFired() override;
void OnNonPersistentNotificationCreated() override;

@ -7,6 +7,7 @@
#include "base/test/simple_test_tick_clock.h"
#include "services/resource_coordinator/coordination_unit/coordination_unit_test_harness.h"
#include "services/resource_coordinator/coordination_unit/mock_coordination_unit_graphs.h"
#include "services/resource_coordinator/coordination_unit/page_coordination_unit_impl.h"
#include "services/resource_coordinator/coordination_unit/process_coordination_unit_impl.h"
#include "services/resource_coordinator/resource_coordinator_clock.h"
#include "testing/gtest/include/gtest/gtest.h"
@ -123,4 +124,61 @@ TEST_F(FrameCoordinationUnitImplTest, LastAudibleTime) {
cu_graph.frame->last_audible_time());
}
int64_t GetLifecycleState(resource_coordinator::CoordinationUnitBase* cu) {
int64_t value;
if (cu->GetProperty(mojom::PropertyType::kLifecycleState, &value))
return value;
// Initial state is running.
return static_cast<int64_t>(mojom::LifecycleState::kRunning);
}
#define EXPECT_FROZEN(cu) \
EXPECT_EQ(static_cast<int64_t>(mojom::LifecycleState::kFrozen), \
GetLifecycleState(cu.get()))
#define EXPECT_RUNNING(cu) \
EXPECT_EQ(static_cast<int64_t>(mojom::LifecycleState::kRunning), \
GetLifecycleState(cu.get()))
TEST_F(FrameCoordinationUnitImplTest, LifecycleStatesTransitions) {
MockMultiplePagesWithMultipleProcessesCoordinationUnitGraph cu_graph;
// Verifying the model.
ASSERT_TRUE(cu_graph.frame->IsMainFrame());
ASSERT_TRUE(cu_graph.other_frame->IsMainFrame());
ASSERT_FALSE(cu_graph.child_frame->IsMainFrame());
ASSERT_EQ(cu_graph.child_frame->GetParentFrameCoordinationUnit(),
cu_graph.other_frame.get());
ASSERT_EQ(cu_graph.frame->GetPageCoordinationUnit(), cu_graph.page.get());
ASSERT_EQ(cu_graph.other_frame->GetPageCoordinationUnit(),
cu_graph.other_page.get());
// Freezing a child frame should not affect the page state.
cu_graph.child_frame->SetLifecycleState(mojom::LifecycleState::kFrozen);
// Verify that the frame is frozen.
EXPECT_FROZEN(cu_graph.child_frame);
// But all pages remain active.
EXPECT_RUNNING(cu_graph.page);
EXPECT_RUNNING(cu_graph.other_page);
// Freezing a page main frame should freeze that page.
cu_graph.frame->SetLifecycleState(mojom::LifecycleState::kFrozen);
EXPECT_FROZEN(cu_graph.frame);
EXPECT_FROZEN(cu_graph.page);
// Freezing the other page main frame.
cu_graph.other_frame->SetLifecycleState(mojom::LifecycleState::kFrozen);
EXPECT_FROZEN(cu_graph.other_frame);
EXPECT_FROZEN(cu_graph.other_page);
// Unfreezing subframe should have no effect.
cu_graph.child_frame->SetLifecycleState(mojom::LifecycleState::kRunning);
// Verify that the frame is unfrozen.
EXPECT_RUNNING(cu_graph.child_frame);
// But the page is still frozen
EXPECT_FROZEN(cu_graph.other_page);
// Unfreezing the main frame should unfreeze the page.
cu_graph.other_frame->SetLifecycleState(mojom::LifecycleState::kRunning);
EXPECT_RUNNING(cu_graph.other_page);
}
} // namespace resource_coordinator

@ -46,17 +46,24 @@ void PageSignalGeneratorImpl::AddReceiver(
receivers_.AddPtr(std::move(receiver));
}
// Frame CUs should be observed for:
// 1- kNetworkAlmostIdle property changes used for PageAlmostIdle detection
// Page CUs should be observed for:
// 1- kLoading property changes used for PageAlmostIdle detection
// 2- kLifecycleState property changes used to update the Tab lifecycle state
// 3- kNavigationCommitted events for PageAlmostIdle detection
// Process CUs should be observed for:
// 1- kExpectedTaskQueueingDuration property for reporting EQT
// 2- kMainThreadTaskLoadIsLow property changes for PageAlmostIdle detection
bool PageSignalGeneratorImpl::ShouldObserve(
const CoordinationUnitBase* coordination_unit) {
auto cu_type = coordination_unit->id().type;
// Always tracked process CUs. This is used for CPU utilization messages.
if (cu_type == CoordinationUnitType::kProcess)
if (cu_type == CoordinationUnitType::kProcess ||
cu_type == CoordinationUnitType::kPage)
return true;
if (!resource_coordinator::IsPageAlmostIdleSignalEnabled())
return false;
// Frame and page CUs are only used for PAI.
return cu_type == CoordinationUnitType::kFrame ||
cu_type == CoordinationUnitType::kPage;
return cu_type == CoordinationUnitType::kFrame;
}
void PageSignalGeneratorImpl::OnCoordinationUnitCreated(
@ -104,12 +111,12 @@ void PageSignalGeneratorImpl::OnPagePropertyChanged(
const PageCoordinationUnitImpl* page_cu,
const mojom::PropertyType property_type,
int64_t value) {
DCHECK(resource_coordinator::IsPageAlmostIdleSignalEnabled());
// Only the loading state of a page is of interest.
if (property_type != mojom::PropertyType::kIsLoading)
return;
UpdateLoadIdleStatePage(page_cu);
if (resource_coordinator::IsPageAlmostIdleSignalEnabled() &&
property_type == mojom::PropertyType::kIsLoading) {
UpdateLoadIdleStatePage(page_cu);
} else if (property_type == mojom::PropertyType::kLifecycleState) {
UpdateLifecycleState(page_cu, static_cast<mojom::LifecycleState>(value));
}
}
void PageSignalGeneratorImpl::OnProcessPropertyChanged(
@ -139,7 +146,9 @@ void PageSignalGeneratorImpl::OnProcessPropertyChanged(
void PageSignalGeneratorImpl::OnPageEventReceived(
const PageCoordinationUnitImpl* page_cu,
const mojom::Event event) {
DCHECK(resource_coordinator::IsPageAlmostIdleSignalEnabled());
// We only care about the events if network idle signal is enabled.
if (!resource_coordinator::IsPageAlmostIdleSignalEnabled())
return;
// Only the navigation committed event is of interest.
if (event != mojom::Event::kNavigationCommitted)
@ -268,6 +277,12 @@ void PageSignalGeneratorImpl::UpdateLoadIdleStateProcess(
UpdateLoadIdleStateFrame(frame_cu);
}
void PageSignalGeneratorImpl::UpdateLifecycleState(
const PageCoordinationUnitImpl* page_cu,
const mojom::LifecycleState state) {
DISPATCH_PAGE_SIGNAL(receivers_, SetLifecycleState, page_cu->id(), state);
}
void PageSignalGeneratorImpl::TransitionToLoadedAndIdle(
const PageCoordinationUnitImpl* page_cu) {
DCHECK(resource_coordinator::IsPageAlmostIdleSignalEnabled());

@ -126,6 +126,11 @@ class PageSignalGeneratorImpl : public CoordinationUnitGraphObserver,
void UpdateLoadIdleStateProcess(
const ProcessCoordinationUnitImpl* process_cu);
// This method is called when a property affecting the lifecycle state is
// observed.
void UpdateLifecycleState(const PageCoordinationUnitImpl* page_cu,
mojom::LifecycleState state);
// Helper function for transitioning to the final state.
void TransitionToLoadedAndIdle(const PageCoordinationUnitImpl* page_cu);

@ -12,6 +12,7 @@ mojom_component("mojom") {
"coordination_unit.mojom",
"coordination_unit_introspector.mojom",
"coordination_unit_provider.mojom",
"lifecycle.mojom",
"memory_instrumentation/constants.mojom",
"memory_instrumentation/memory_instrumentation.mojom",
"page_signal.mojom",

@ -6,6 +6,7 @@ module resource_coordinator.mojom;
import "mojo/public/mojom/base/process_id.mojom";
import "mojo/public/mojom/base/time.mojom";
import "services/resource_coordinator/public/mojom/lifecycle.mojom";
import "services/resource_coordinator/public/mojom/signals.mojom";
// Any new type here needs to be mirrored between coordination_unit_types.h and
@ -64,6 +65,7 @@ interface FrameCoordinationUnit {
// Property signals.
SetAudibility(bool audible);
SetNetworkAlmostIdle(bool idle);
SetLifecycleState(LifecycleState state);
// Event signals.
OnAlertFired();

@ -0,0 +1,13 @@
// 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.
module resource_coordinator.mojom;
// A lifecycle unit (a page, frame, tab or application) can be in one of
// these lifecycle states.
enum LifecycleState {
kRunning,
kFrozen,
kDiscarded
};

@ -5,6 +5,7 @@
module resource_coordinator.mojom;
import "services/resource_coordinator/public/mojom/coordination_unit.mojom";
import "services/resource_coordinator/public/mojom/lifecycle.mojom";
import "mojo/public/mojom/base/time.mojom";
// A PageSignalReceiver implementation receives page-scoped signal from
@ -17,6 +18,8 @@ interface PageSignalReceiver {
NotifyPageAlmostIdle(CoordinationUnitID cu_id);
SetExpectedTaskQueueingDuration(CoordinationUnitID cu_id,
mojo_base.mojom.TimeDelta duration);
SetLifecycleState(CoordinationUnitID cu_id,
LifecycleState state);
};
// A PageSignalGenerator implementation will be implemented inside GRC to receive

@ -36,4 +36,5 @@ enum PropertyType {
kUKMSourceId,
// Used by Page CUs to store current loading state.
kIsLoading,
kLifecycleState,
};

@ -525,6 +525,12 @@ void LocalFrame::DidFreeze() {
CustomCountHistogram, freeze_histogram,
("DocumentEventTiming.FreezeDuration", 0, 10000000, 50));
freeze_histogram.Count((freeze_event_end - freeze_event_start) * 1000000.0);
// TODO(fmeawad): Move the following logic to the page once we have a
// PageResourceCoordinator in Blink. http://crbug.com/838415
if (auto* frame_resource_coordinator = GetFrameResourceCoordinator()) {
frame_resource_coordinator->SetLifecycleState(
resource_coordinator::mojom::LifecycleState::kFrozen);
}
}
}
@ -538,6 +544,12 @@ void LocalFrame::DidResume() {
CustomCountHistogram, resume_histogram,
("DocumentEventTiming.ResumeDuration", 0, 10000000, 50));
resume_histogram.Count((resume_event_end - resume_event_start) * 1000000.0);
// TODO(fmeawad): Move the following logic to the page once we have a
// PageResourceCoordinator in Blink
if (auto* frame_resource_coordinator = GetFrameResourceCoordinator()) {
frame_resource_coordinator->SetLifecycleState(
resource_coordinator::mojom::LifecycleState::kRunning);
}
}
}

@ -27,6 +27,13 @@ void FrameResourceCoordinator::SetNetworkAlmostIdle(bool idle) {
service_->SetNetworkAlmostIdle(idle);
}
void FrameResourceCoordinator::SetLifecycleState(
resource_coordinator::mojom::LifecycleState state) {
if (!service_)
return;
service_->SetLifecycleState(state);
}
void FrameResourceCoordinator::OnNonPersistentNotificationCreated() {
if (!service_)
return;

@ -23,6 +23,7 @@ class PLATFORM_EXPORT FrameResourceCoordinator final
~FrameResourceCoordinator();
void SetNetworkAlmostIdle(bool);
void SetLifecycleState(resource_coordinator::mojom::LifecycleState);
void OnNonPersistentNotificationCreated();
private: