blob: 9e5bb83e57f4bb52b88221b38e0b97cc92dd8521 [file] [log] [blame]
// Copyright 2019 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.
#include "third_party/blink/renderer/platform/scheduler/main_thread/memory_purge_manager.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/renderer/platform/scheduler/public/page_lifecycle_state.h"
namespace blink {
namespace {
constexpr base::TimeDelta kDelayForPurgeAfterFreeze =
base::TimeDelta::FromMinutes(1);
class MemoryPurgeManagerTest : public testing::Test {
public:
MemoryPurgeManagerTest()
: task_environment_(base::test::TaskEnvironment::MainThreadType::UI,
base::test::TaskEnvironment::TimeSource::MOCK_TIME),
memory_purge_manager_(task_environment_.GetMainThreadTaskRunner()) {}
void SetUp() override {
memory_pressure_listener_ = std::make_unique<base::MemoryPressureListener>(
FROM_HERE,
base::BindRepeating(&MemoryPurgeManagerTest::OnMemoryPressure,
base::Unretained(this)));
base::MemoryPressureListener::SetNotificationsSuppressed(false);
}
void TearDown() override {
memory_pressure_listener_.reset();
task_environment_.FastForwardUntilNoTasksRemain();
}
protected:
void SetupDelayedPurgeAfterFreezeExperiment() {
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kFreezePurgeMemoryAllPagesFrozen,
{{"delay-in-minutes",
base::NumberToString(kDelayForPurgeAfterFreeze.InMinutes())}}}},
{features::kPurgeRendererMemoryWhenBackgrounded});
}
void FastForwardBy(base::TimeDelta delta) {
task_environment_.FastForwardBy(delta);
}
unsigned MemoryPressureCount() const { return memory_pressure_count_; }
base::test::ScopedFeatureList scoped_feature_list_;
base::test::TaskEnvironment task_environment_;
std::unique_ptr<base::MemoryPressureListener> memory_pressure_listener_;
MemoryPurgeManager memory_purge_manager_;
unsigned memory_pressure_count_ = 0;
private:
void OnMemoryPressure(base::MemoryPressureListener::MemoryPressureLevel) {
memory_pressure_count_++;
}
DISALLOW_COPY_AND_ASSIGN(MemoryPurgeManagerTest);
};
// Verify that OnPageFrozen() triggers a memory pressure notification in a
// backgrounded renderer.
TEST_F(MemoryPurgeManagerTest, PageFrozenInBackgroundedRenderer) {
scoped_feature_list_.InitWithFeatures(
{} /* enabled */,
{features::kPurgeRendererMemoryWhenBackgrounded} /* disabled */);
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromMinutes(0));
EXPECT_EQ(1U, MemoryPressureCount());
}
// Verify that OnPageFrozen() does not trigger a memory pressure notification in
// a foregrounded renderer.
TEST_F(MemoryPurgeManagerTest, PageFrozenInForegroundedRenderer) {
scoped_feature_list_.InitWithFeatures(
{} /* enabled */,
{features::kPurgeRendererMemoryWhenBackgrounded} /* disabled */);
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.SetRendererBackgrounded(false);
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromMinutes(0));
EXPECT_EQ(0U, MemoryPressureCount());
}
TEST_F(MemoryPurgeManagerTest, PageResumedUndoMemoryPressureSuppression) {
scoped_feature_list_.InitWithFeatures(
{} /* enabled */,
{features::kPurgeRendererMemoryWhenBackgrounded} /* disabled */);
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromMinutes(0));
EXPECT_EQ(1U, MemoryPressureCount());
EXPECT_TRUE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageResumed();
EXPECT_FALSE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kActive);
}
TEST_F(MemoryPurgeManagerTest, PageFrozenPurgeMemoryAllPagesFrozenDisabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{} /* enabled */,
{features::kFreezePurgeMemoryAllPagesFrozen,
features::kPurgeRendererMemoryWhenBackgrounded} /* disabled */);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromMinutes(0));
EXPECT_EQ(1U, MemoryPressureCount());
EXPECT_FALSE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromMinutes(0));
EXPECT_EQ(2U, MemoryPressureCount());
EXPECT_FALSE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromMinutes(0));
EXPECT_EQ(3U, MemoryPressureCount());
EXPECT_TRUE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageResumed();
EXPECT_FALSE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kActive);
EXPECT_FALSE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
EXPECT_FALSE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kActive);
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kFrozen);
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kFrozen);
}
TEST_F(MemoryPurgeManagerTest, PageFrozenPurgeMemoryAllPagesFrozenEnabled) {
base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitWithFeatures(
{features::kFreezePurgeMemoryAllPagesFrozen} /* enabled */,
{features::kPurgeRendererMemoryWhenBackgrounded} /* disabled */);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromMinutes(0));
EXPECT_EQ(0U, MemoryPressureCount());
EXPECT_FALSE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromMinutes(0));
EXPECT_EQ(0U, MemoryPressureCount());
EXPECT_FALSE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromMinutes(0));
EXPECT_EQ(1U, MemoryPressureCount());
EXPECT_TRUE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageResumed();
EXPECT_FALSE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kActive);
EXPECT_FALSE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
EXPECT_FALSE(base::MemoryPressureListener::AreNotificationsSuppressed());
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kActive);
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kFrozen);
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kFrozen);
}
TEST_F(MemoryPurgeManagerTest, MemoryPurgeWithDelay) {
SetupDelayedPurgeAfterFreezeExperiment();
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageFrozen();
// The memory pressure notification should not occur immediately
FastForwardBy(base::TimeDelta::FromMinutes(0));
EXPECT_EQ(0U, MemoryPressureCount());
// The memory pressure notification should occur after 1 minute
FastForwardBy(kDelayForPurgeAfterFreeze);
EXPECT_EQ(1U, MemoryPressureCount());
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kFrozen);
}
TEST_F(MemoryPurgeManagerTest, CancelMemoryPurgeWithDelay) {
SetupDelayedPurgeAfterFreezeExperiment();
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromSeconds(40));
EXPECT_EQ(0U, MemoryPressureCount());
// If the page is resumed before the memory purge timer expires, the purge
// should be cancelled.
memory_purge_manager_.OnPageResumed();
FastForwardBy(base::TimeDelta::FromMinutes(0));
EXPECT_EQ(0U, MemoryPressureCount());
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kActive);
}
TEST_F(MemoryPurgeManagerTest, MemoryPurgeWithDelayNewActivePageCreated) {
SetupDelayedPurgeAfterFreezeExperiment();
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromSeconds(40));
EXPECT_EQ(0U, MemoryPressureCount());
// All pages are no longer frozen, the memory purge should be cancelled.
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
FastForwardBy(kDelayForPurgeAfterFreeze);
EXPECT_EQ(0U, MemoryPressureCount());
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kFrozen);
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kActive);
}
TEST_F(MemoryPurgeManagerTest, MemoryPurgeWithDelayNewFrozenPageCreated) {
SetupDelayedPurgeAfterFreezeExperiment();
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromSeconds(40));
EXPECT_EQ(0U, MemoryPressureCount());
// All pages are still frozen and the memory purge should occur.
memory_purge_manager_.OnPageCreated(PageLifecycleState::kFrozen);
FastForwardBy(kDelayForPurgeAfterFreeze);
EXPECT_EQ(1U, MemoryPressureCount());
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kFrozen);
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kFrozen);
}
TEST_F(MemoryPurgeManagerTest, PurgeRendererMemoryWhenBackgroundedEnabled) {
scoped_feature_list_.InitWithFeatures(
{features::kPurgeRendererMemoryWhenBackgrounded} /* enabled */,
{} /* disabled */);
memory_purge_manager_.SetRendererBackgrounded(true);
FastForwardBy(base::TimeDelta::FromMinutes(
MemoryPurgeManager::kDefaultMaxTimeToPurgeAfterBackgrounded));
// No page, no memory pressure.
EXPECT_EQ(0U, MemoryPressureCount());
}
TEST_F(MemoryPurgeManagerTest, PurgeRendererMemoryWhenBackgroundedDisabled) {
scoped_feature_list_.InitWithFeatures(
{} /* enabled */,
{features::kPurgeRendererMemoryWhenBackgrounded} /* disabled */);
memory_purge_manager_.SetRendererBackgrounded(true);
FastForwardBy(base::TimeDelta::Max());
EXPECT_EQ(0U, MemoryPressureCount());
}
TEST_F(MemoryPurgeManagerTest,
PurgeRendererMemoryWhenBackgroundedEnabledForegroundedBeforePurge) {
scoped_feature_list_.InitWithFeatures(
{features::kPurgeRendererMemoryWhenBackgrounded} /* enabled */,
{} /* disabled */);
memory_purge_manager_.SetRendererBackgrounded(true);
FastForwardBy(base::TimeDelta::FromSeconds(30));
EXPECT_EQ(0U, MemoryPressureCount());
memory_purge_manager_.SetRendererBackgrounded(false);
FastForwardBy(base::TimeDelta::Max());
EXPECT_EQ(0U, MemoryPressureCount());
}
TEST_F(MemoryPurgeManagerTest, PageFrozenAndResumedWhileBackgrounded) {
constexpr base::TimeDelta kFreezePurgeDelay =
base::TimeDelta::FromMinutes(10);
constexpr base::TimeDelta kBeforeBackgroundPurgeDelay =
base::TimeDelta::FromMinutes(
MemoryPurgeManager::kDefaultMinTimeToPurgeAfterBackgrounded) /
2;
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kFreezePurgeMemoryAllPagesFrozen,
{{"delay-in-minutes",
base::NumberToString(kFreezePurgeDelay.InMinutes())}}},
{features::kPurgeRendererMemoryWhenBackgrounded, {}}},
{});
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageFrozen();
FastForwardBy(kBeforeBackgroundPurgeDelay);
EXPECT_EQ(0U, MemoryPressureCount());
memory_purge_manager_.OnPageResumed();
FastForwardBy(
base::TimeDelta::FromMinutes(
MemoryPurgeManager::kDefaultMaxTimeToPurgeAfterBackgrounded) -
kBeforeBackgroundPurgeDelay);
// Since the renderer is still backgrounded, the memory purge should happen
// even though there are no frozen pages.
EXPECT_EQ(1U, MemoryPressureCount());
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kActive);
}
TEST_F(MemoryPurgeManagerTest,
PageFrozenAndRendererBackgroundedShorterBackgroundedDelay) {
constexpr base::TimeDelta kFreezePurgeDelay =
base::TimeDelta::FromMinutes(10);
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kFreezePurgeMemoryAllPagesFrozen,
{{"delay-in-minutes",
base::NumberToString(kFreezePurgeDelay.InMinutes())}}},
{features::kPurgeRendererMemoryWhenBackgrounded, {}}},
{});
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageFrozen();
FastForwardBy(base::TimeDelta::FromMinutes(
MemoryPurgeManager::kDefaultMaxTimeToPurgeAfterBackgrounded));
EXPECT_EQ(1U, MemoryPressureCount());
FastForwardBy(kFreezePurgeDelay);
EXPECT_EQ(1U, MemoryPressureCount());
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kFrozen);
}
TEST_F(MemoryPurgeManagerTest,
PageFrozenAndRendererBackgroundedShorterFreezeDelay) {
constexpr base::TimeDelta kFreezePurgeDelay = base::TimeDelta::FromMinutes(
MemoryPurgeManager::kDefaultMinTimeToPurgeAfterBackgrounded);
scoped_feature_list_.InitWithFeaturesAndParameters(
{{features::kFreezePurgeMemoryAllPagesFrozen,
{{"delay-in-minutes",
base::NumberToString(kFreezePurgeDelay.InMinutes())}}},
{features::kPurgeRendererMemoryWhenBackgrounded, {}}},
{});
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageFrozen();
FastForwardBy(kFreezePurgeDelay);
EXPECT_EQ(1U, MemoryPressureCount());
FastForwardBy(base::TimeDelta::Max());
EXPECT_EQ(1U, MemoryPressureCount());
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kFrozen);
}
TEST_F(MemoryPurgeManagerTest, NoMemoryPurgeIfNoPage) {
scoped_feature_list_.InitWithFeatures(
{features::kFreezePurgeMemoryAllPagesFrozen} /* enabled */,
{features::kPurgeRendererMemoryWhenBackgrounded} /* disabled */);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageCreated(PageLifecycleState::kActive);
memory_purge_manager_.SetRendererBackgrounded(true);
memory_purge_manager_.OnPageFrozen();
memory_purge_manager_.OnPageDestroyed(PageLifecycleState::kFrozen);
FastForwardBy(base::TimeDelta::FromMinutes(0));
EXPECT_EQ(0U, MemoryPressureCount());
}
} // namespace
} // namespace blink