blob: d20e0b4d01bb78cf23217d8180f97916c54f5473 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// 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/modules/webcodecs/reclaimable_codec.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/simple_test_tick_clock.h"
#include "base/time/default_tick_clock.h"
#include "base/time/time.h"
#include "media/base/test_helpers.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_binding_for_testing.h"
#include "third_party/blink/renderer/modules/webcodecs/codec_pressure_gauge.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/testing/task_environment.h"
namespace blink {
// Set a high theshold, so we can fake pressure threshold notifications.
static constexpr size_t kTestPressureThreshold = 100;
namespace {
constexpr base::TimeDelta kTimerPeriod =
ReclaimableCodec::kInactivityReclamationThreshold / 2;
class FakeReclaimableCodec final
: public GarbageCollected<FakeReclaimableCodec>,
public ReclaimableCodec {
public:
FakeReclaimableCodec(ReclaimableCodec::CodecType type,
ExecutionContext* context)
: ReclaimableCodec(type, context) {}
void SimulateActivity() {
MarkCodecActive();
reclaimed_ = false;
}
void SimulateReset() { ReleaseCodecPressure(); }
void SimulatePressureExceeded() {
ApplyCodecPressure();
SetGlobalPressureExceededFlag(true);
}
void OnCodecReclaimed(DOMException* ex) final { reclaimed_ = true; }
bool is_global_pressure_exceeded() {
return global_pressure_exceeded_for_testing();
}
// GarbageCollected override.
void Trace(Visitor* visitor) const override {
ReclaimableCodec::Trace(visitor);
}
bool reclaimed() const { return reclaimed_; }
private:
// ContextLifecycleObserver override.
void ContextDestroyed() override {}
bool reclaimed_ = false;
};
} // namespace
class ReclaimableCodecTest
: public testing::TestWithParam<ReclaimableCodec::CodecType> {
public:
FakeReclaimableCodec* CreateCodec(ExecutionContext* context) {
if (!is_gauge_threshold_set_) {
CodecPressureGauge::GetInstance(GetParam())
.set_pressure_threshold_for_testing(kTestPressureThreshold);
is_gauge_threshold_set_ = true;
}
return MakeGarbageCollected<FakeReclaimableCodec>(GetParam(), context);
}
private:
bool is_gauge_threshold_set_ = false;
test::TaskEnvironment task_environment_;
};
void TestBackgroundInactivityTimerStartStops(FakeReclaimableCodec* codec) {
EXPECT_FALSE(codec->is_backgrounded_for_testing());
codec->SimulateLifecycleStateForTesting(
scheduler::SchedulingLifecycleState::kHidden);
// Codecs should not be reclaimable for inactivity until pressure is exceeded.
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
codec->SimulateReset();
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
// Exceeding pressure should start the timer.
codec->SimulatePressureExceeded();
EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting());
// Activity should not stop the timer.
codec->SimulateActivity();
EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting());
// The timer should be stopped when asked.
codec->SimulateReset();
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
// It should be possible to restart the timer after stopping it.
codec->SimulatePressureExceeded();
EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting());
}
void TestBackgroundInactivityTimerWorks(FakeReclaimableCodec* codec) {
EXPECT_FALSE(codec->is_backgrounded_for_testing());
codec->SimulateLifecycleStateForTesting(
scheduler::SchedulingLifecycleState::kHidden);
// Codecs should not be reclaimable for inactivity until pressure is exceeded.
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
base::SimpleTestTickClock tick_clock;
codec->set_tick_clock_for_testing(&tick_clock);
// Exceeding pressure should start the timer.
codec->SimulatePressureExceeded();
EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting());
EXPECT_FALSE(codec->reclaimed());
// Fire when codec is fresh to ensure first tick isn't treated as idle.
codec->SimulateActivity();
codec->SimulateActivityTimerFiredForTesting();
EXPECT_FALSE(codec->reclaimed());
// One timer period should not be enough to reclaim the codec.
tick_clock.Advance(kTimerPeriod);
codec->SimulateActivityTimerFiredForTesting();
EXPECT_FALSE(codec->reclaimed());
// Advancing an additional timer period should be enough to trigger
// reclamation.
tick_clock.Advance(kTimerPeriod);
codec->SimulateActivityTimerFiredForTesting();
EXPECT_TRUE(codec->reclaimed());
// Restore default tick clock since |codec| is a garbage collected object that
// may outlive the scope of this function.
codec->set_tick_clock_for_testing(base::DefaultTickClock::GetInstance());
}
TEST_P(ReclaimableCodecTest, BackgroundInactivityTimerStartStops) {
V8TestingScope v8_scope;
// Only background reclamation permitted, so simulate backgrouding.
TestBackgroundInactivityTimerStartStops(
CreateCodec(v8_scope.GetExecutionContext()));
}
TEST_P(ReclaimableCodecTest, BackgroundInactivityTimerWorks) {
V8TestingScope v8_scope;
// Only background reclamation permitted, so simulate backgrouding.
TestBackgroundInactivityTimerWorks(
CreateCodec(v8_scope.GetExecutionContext()));
}
TEST_P(ReclaimableCodecTest, ForegroundInactivityTimerNeverStarts) {
V8TestingScope v8_scope;
auto* codec = CreateCodec(v8_scope.GetExecutionContext());
// Test codec should start in foreground when kOnlyReclaimBackgroundWebCodecs
// enabled.
EXPECT_FALSE(codec->is_backgrounded_for_testing());
// Codecs should not be reclaimable for inactivity until pressure is exceeded.
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
base::SimpleTestTickClock tick_clock;
codec->set_tick_clock_for_testing(&tick_clock);
// Exceeded pressure should not start timer while we remain in foreground.
codec->SimulatePressureExceeded();
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
EXPECT_FALSE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// First activity should not start timer while we remain in foreground.
codec->SimulateActivity();
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
EXPECT_FALSE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Advancing time by any amount shouldn't change the above.
tick_clock.Advance(kTimerPeriod * 100);
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
EXPECT_FALSE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Activity still shouldn't start the timer as we remain in foreground.
codec->SimulateActivity();
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
EXPECT_FALSE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Restore default tick clock since |codec| is a garbage collected object that
// may outlive the scope of this function.
codec->set_tick_clock_for_testing(base::DefaultTickClock::GetInstance());
}
TEST_P(ReclaimableCodecTest, ForegroundCodecReclaimedOnceBackgrounded) {
V8TestingScope v8_scope;
auto* codec = CreateCodec(v8_scope.GetExecutionContext());
// Test codec should start in foreground when kOnlyReclaimBackgroundWebCodecs
// enabled.
EXPECT_FALSE(codec->is_backgrounded_for_testing());
// Codecs should not be reclaimable for inactivity until pressure is exceeded.
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
base::SimpleTestTickClock tick_clock;
codec->set_tick_clock_for_testing(&tick_clock);
// Pressure should not start the timer while we are still in the foreground.
codec->SimulatePressureExceeded();
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
EXPECT_FALSE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Entering background should start timer.
codec->SimulateLifecycleStateForTesting(
scheduler::SchedulingLifecycleState::kHidden);
EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting());
EXPECT_TRUE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Advancing 1 period shouldn't reclaim (it takes 2).
tick_clock.Advance(kTimerPeriod);
codec->SimulateActivityTimerFiredForTesting();
EXPECT_FALSE(codec->reclaimed());
// Re-entering foreground should stop the timer.
codec->SimulateLifecycleStateForTesting(
scheduler::SchedulingLifecycleState::kNotThrottled);
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
EXPECT_FALSE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Advancing any amount of time shouldn't reclaim while in foreground.
tick_clock.Advance(kTimerPeriod * 100);
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
EXPECT_FALSE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Re-entering background should again start the timer.
codec->SimulateLifecycleStateForTesting(
scheduler::SchedulingLifecycleState::kHidden);
EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting());
EXPECT_TRUE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Fire newly backgrounded to ensure first tick isn't treated as idle.
codec->SimulateActivityTimerFiredForTesting();
EXPECT_FALSE(codec->reclaimed());
// Timer should be fresh such that one period is not enough to reclaim.
tick_clock.Advance(kTimerPeriod);
codec->SimulateActivityTimerFiredForTesting();
EXPECT_TRUE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Advancing twice through the period should finally reclaim.
tick_clock.Advance(kTimerPeriod);
codec->SimulateActivityTimerFiredForTesting();
EXPECT_TRUE(codec->is_backgrounded_for_testing());
EXPECT_TRUE(codec->reclaimed());
// Restore default tick clock since |codec| is a garbage collected object that
// may outlive the scope of this function.
codec->set_tick_clock_for_testing(base::DefaultTickClock::GetInstance());
}
TEST_P(ReclaimableCodecTest, RepeatLifecycleEventsDontBreakState) {
V8TestingScope v8_scope;
auto* codec = CreateCodec(v8_scope.GetExecutionContext());
// Test codec should start in foreground when kOnlyReclaimBackgroundWebCodecs
// enabled.
EXPECT_FALSE(codec->is_backgrounded_for_testing());
// Duplicate kNotThrottled (foreground) shouldn't affect codec state.
codec->SimulateLifecycleStateForTesting(
scheduler::SchedulingLifecycleState::kNotThrottled);
EXPECT_FALSE(codec->is_backgrounded_for_testing());
// Codecs should not be reclaimable until pressure is exceeded.
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
base::SimpleTestTickClock tick_clock;
codec->set_tick_clock_for_testing(&tick_clock);
// Applying pressure should not start the timer while we remain in the
// foreground.
codec->SimulatePressureExceeded();
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
EXPECT_FALSE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Entering background should start timer.
codec->SimulateLifecycleStateForTesting(
scheduler::SchedulingLifecycleState::kHidden);
EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting());
EXPECT_TRUE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Advancing 1 period shouldn't reclaim (it takes 2).
tick_clock.Advance(kTimerPeriod);
codec->SimulateActivityTimerFiredForTesting();
EXPECT_FALSE(codec->reclaimed());
// Further background lifecycle progression shouldn't affect codec state.
codec->SimulateLifecycleStateForTesting(
scheduler::SchedulingLifecycleState::kThrottled);
EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting());
EXPECT_TRUE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Further background lifecycle progression shouldn't affect codec state.
codec->SimulateLifecycleStateForTesting(
scheduler::SchedulingLifecycleState::kStopped);
EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting());
EXPECT_TRUE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->reclaimed());
// Advancing one final time through the period should finally reclaim.
tick_clock.Advance(kTimerPeriod);
codec->SimulateActivityTimerFiredForTesting();
EXPECT_TRUE(codec->is_backgrounded_for_testing());
EXPECT_TRUE(codec->reclaimed());
// Restore default tick clock since |codec| is a garbage collected object that
// may outlive the scope of this function.
codec->set_tick_clock_for_testing(base::DefaultTickClock::GetInstance());
}
TEST_P(ReclaimableCodecTest, PressureChangesUpdateTimer) {
V8TestingScope v8_scope;
auto* codec = CreateCodec(v8_scope.GetExecutionContext());
// Test codec should start in foreground when kOnlyReclaimBackgroundWebCodecs
// enabled.
EXPECT_FALSE(codec->is_backgrounded_for_testing());
// Codecs should not apply pressure by default.
EXPECT_FALSE(codec->is_applying_codec_pressure());
// Codecs should not be reclaimable by default.
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
// Pressure must be exceeded for the timer to be active.
codec->SimulateLifecycleStateForTesting(
scheduler::SchedulingLifecycleState::kHidden);
EXPECT_TRUE(codec->is_backgrounded_for_testing());
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
// Applying pressure isn't enough to start reclamation, global pressure must
// be exceeded.
codec->ApplyCodecPressure();
EXPECT_TRUE(codec->is_applying_codec_pressure());
EXPECT_FALSE(codec->is_global_pressure_exceeded());
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
// Setting/unsetting global pressure should start/stop idle reclamation.
codec->SetGlobalPressureExceededFlag(true);
EXPECT_TRUE(codec->is_applying_codec_pressure());
EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting());
codec->SetGlobalPressureExceededFlag(false);
EXPECT_TRUE(codec->is_applying_codec_pressure());
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
codec->SetGlobalPressureExceededFlag(true);
EXPECT_TRUE(codec->is_applying_codec_pressure());
EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting());
// Releasing codec pressure should stop the timer.
codec->ReleaseCodecPressure();
EXPECT_FALSE(codec->is_applying_codec_pressure());
EXPECT_FALSE(codec->is_global_pressure_exceeded());
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
// Re-applying codec pressure should not start the timer: the global pressure
// flag must be set again.
codec->ApplyCodecPressure();
EXPECT_TRUE(codec->is_applying_codec_pressure());
EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting());
codec->SetGlobalPressureExceededFlag(true);
EXPECT_TRUE(codec->is_applying_codec_pressure());
EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting());
}
INSTANTIATE_TEST_SUITE_P(
,
ReclaimableCodecTest,
testing::Values(ReclaimableCodec::CodecType::kDecoder,
ReclaimableCodec::CodecType::kEncoder));
} // namespace blink