| // Copyright 2021 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/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 "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/platform/heap/garbage_collected.h" |
| |
| namespace blink { |
| |
| 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 OnCodecReclaimed(DOMException* ex) final { reclaimed_ = true; } |
| |
| // 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 BaseReclaimableCodecTest |
| : public testing::TestWithParam<ReclaimableCodec::CodecType> { |
| public: |
| FakeReclaimableCodec* CreateCodec(ExecutionContext* context) { |
| return MakeGarbageCollected<FakeReclaimableCodec>(GetParam(), context); |
| } |
| }; |
| |
| // Testing w/ flags allowing only reclamation of background codecs. |
| class ReclaimBackgroundOnlyTest : public BaseReclaimableCodecTest { |
| public: |
| ReclaimBackgroundOnlyTest() { |
| std::vector<base::Feature> enabled_features{ |
| kReclaimInactiveWebCodecs, kOnlyReclaimBackgroundWebCodecs}; |
| std::vector<base::Feature> disabled_features{}; |
| feature_list_.InitWithFeatures(enabled_features, disabled_features); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // Testing w/ flags allowing reclamation of both foreground and background |
| // codecs. |
| class ReclaimForegroundSameAsBackgroundTest : public BaseReclaimableCodecTest { |
| public: |
| ReclaimForegroundSameAsBackgroundTest() { |
| std::vector<base::Feature> enabled_features{kReclaimInactiveWebCodecs}; |
| std::vector<base::Feature> disabled_features{ |
| kOnlyReclaimBackgroundWebCodecs}; |
| feature_list_.InitWithFeatures(enabled_features, disabled_features); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| // Testing kill-switch scenario w/ all flags disabled. |
| class ReclaimDisabledTest : public BaseReclaimableCodecTest { |
| public: |
| ReclaimDisabledTest() { |
| std::vector<base::Feature> enabled_features{}; |
| std::vector<base::Feature> disabled_features{ |
| kReclaimInactiveWebCodecs, kOnlyReclaimBackgroundWebCodecs}; |
| feature_list_.InitWithFeatures(enabled_features, disabled_features); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| enum class TestParam { |
| // Instructs test to use SimulateLifecycleStateForTesting(kHidden) to simulate |
| // a backgrounding scenario. |
| kSimulateBackgrounding, |
| // Instructs test to expect that codec is already backgrounded and to refrain |
| // from simulating backgrounding as above. |
| kExpectAlreadyInBackground, |
| }; |
| |
| void TestBackgroundInactivityTimerStartStops(TestParam background_type, |
| FakeReclaimableCodec* codec) { |
| if (background_type == TestParam::kSimulateBackgrounding) { |
| EXPECT_FALSE(codec->is_backgrounded_for_testing()); |
| codec->SimulateLifecycleStateForTesting( |
| scheduler::SchedulingLifecycleState::kHidden); |
| } else { |
| DCHECK_EQ(background_type, TestParam::kExpectAlreadyInBackground); |
| EXPECT_TRUE(codec->is_backgrounded_for_testing()); |
| } |
| |
| // Codecs should not be reclaimable for inactivity until pressure is applied. |
| EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting()); |
| |
| codec->SimulateReset(); |
| EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting()); |
| |
| // Applying pressure should start the timer. |
| codec->ApplyCodecPressure(); |
| 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->ApplyCodecPressure(); |
| EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting()); |
| } |
| |
| void TestBackgroundInactivityTimerWorks(TestParam background_type, |
| FakeReclaimableCodec* codec) { |
| if (background_type == TestParam::kSimulateBackgrounding) { |
| EXPECT_FALSE(codec->is_backgrounded_for_testing()); |
| codec->SimulateLifecycleStateForTesting( |
| scheduler::SchedulingLifecycleState::kHidden); |
| } else { |
| DCHECK_EQ(background_type, TestParam::kExpectAlreadyInBackground); |
| EXPECT_TRUE(codec->is_backgrounded_for_testing()); |
| } |
| |
| // Codecs should not be reclaimable for inactivity until pressure is applied. |
| EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting()); |
| |
| base::SimpleTestTickClock tick_clock; |
| codec->set_tick_clock_for_testing(&tick_clock); |
| |
| // Applying pressure should start the timer. |
| codec->ApplyCodecPressure(); |
| 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(ReclaimBackgroundOnlyTest, BackgroundInactivityTimerStartStops) { |
| V8TestingScope v8_scope; |
| |
| // Only background reclamation permitted, so simulate backgrouding. |
| TestBackgroundInactivityTimerStartStops( |
| TestParam::kSimulateBackgrounding, |
| CreateCodec(v8_scope.GetExecutionContext())); |
| } |
| |
| TEST_P(ReclaimBackgroundOnlyTest, BackgroundInactivityTimerWorks) { |
| V8TestingScope v8_scope; |
| |
| // Only background reclamation permitted, so simulate backgrouding. |
| TestBackgroundInactivityTimerWorks( |
| TestParam::kSimulateBackgrounding, |
| CreateCodec(v8_scope.GetExecutionContext())); |
| } |
| |
| TEST_P(ReclaimForegroundSameAsBackgroundTest, |
| BackgroundInactivityTimerStartStops) { |
| V8TestingScope v8_scope; |
| |
| // Foreground codecs are treated as always backgrounded w/ these feature flags |
| TestBackgroundInactivityTimerStartStops( |
| TestParam::kExpectAlreadyInBackground, |
| CreateCodec(v8_scope.GetExecutionContext())); |
| } |
| |
| TEST_P(ReclaimForegroundSameAsBackgroundTest, BackgroundInactivityTimerWorks) { |
| V8TestingScope v8_scope; |
| |
| // Foreground codecs are treated as always backgrounded w/ these feature flags |
| TestBackgroundInactivityTimerWorks( |
| TestParam::kExpectAlreadyInBackground, |
| CreateCodec(v8_scope.GetExecutionContext())); |
| } |
| |
| TEST_P(ReclaimBackgroundOnlyTest, 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 applied. |
| EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting()); |
| |
| base::SimpleTestTickClock tick_clock; |
| codec->set_tick_clock_for_testing(&tick_clock); |
| |
| // Applying pressure should not start timer while we remain in foreground. |
| codec->ApplyCodecPressure(); |
| 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(ReclaimBackgroundOnlyTest, 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 applied. |
| 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->ApplyCodecPressure(); |
| 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(ReclaimBackgroundOnlyTest, 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->ApplyCodecPressure(); |
| 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(ReclaimDisabledTest, ReclamationKillSwitch) { |
| V8TestingScope v8_scope; |
| |
| auto* codec = CreateCodec(v8_scope.GetExecutionContext()); |
| |
| // Test codec should start in background when kOnlyReclaimBackgroundWebCodecs |
| // is disabled. |
| EXPECT_TRUE(codec->is_backgrounded_for_testing()); |
| |
| // Codecs should not be reclaimable until pressure is applied. |
| EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting()); |
| |
| base::SimpleTestTickClock tick_clock; |
| codec->set_tick_clock_for_testing(&tick_clock); |
| |
| // Reclamation disabled, so applying pressure should not start the timer. |
| codec->ApplyCodecPressure(); |
| EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting()); |
| EXPECT_FALSE(codec->reclaimed()); |
| |
| // Advancing any period should not be enough to reclaim the codec. |
| tick_clock.Advance(kTimerPeriod * 10); |
| 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(ReclaimBackgroundOnlyTest, 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()); |
| |
| // The codec must be backgrounded for the timer to be active. |
| codec->SimulateLifecycleStateForTesting( |
| scheduler::SchedulingLifecycleState::kHidden); |
| EXPECT_TRUE(codec->is_backgrounded_for_testing()); |
| EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting()); |
| |
| // Applying codec pressure should start the timer. |
| codec->ApplyCodecPressure(); |
| 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->IsReclamationTimerActiveForTesting()); |
| |
| // Re-applying codec pressure should start the timer. |
| codec->ApplyCodecPressure(); |
| EXPECT_TRUE(codec->is_applying_codec_pressure()); |
| EXPECT_TRUE(codec->IsReclamationTimerActiveForTesting()); |
| |
| // Re-releasing codec pressure should stop the timer. |
| codec->ReleaseCodecPressure(); |
| EXPECT_FALSE(codec->is_applying_codec_pressure()); |
| EXPECT_FALSE(codec->IsReclamationTimerActiveForTesting()); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| ReclaimBackgroundOnlyTest, |
| testing::Values(ReclaimableCodec::CodecType::kDecoder, |
| ReclaimableCodec::CodecType::kEncoder)); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| ReclaimForegroundSameAsBackgroundTest, |
| testing::Values(ReclaimableCodec::CodecType::kDecoder, |
| ReclaimableCodec::CodecType::kEncoder)); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| , |
| ReclaimDisabledTest, |
| testing::Values(ReclaimableCodec::CodecType::kDecoder, |
| ReclaimableCodec::CodecType::kEncoder)); |
| |
| } // namespace blink |