| // Copyright 2017 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 "chrome/browser/resource_coordinator/tab_lifecycle_unit.h" |
| |
| #include <memory> |
| |
| #include "base/macros.h" |
| #include "base/observer_list.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/resource_coordinator/lifecycle_unit_observer.h" |
| #include "chrome/browser/resource_coordinator/tab_lifecycle_observer.h" |
| #include "chrome/browser/resource_coordinator/tab_lifecycle_unit_external.h" |
| #include "chrome/browser/resource_coordinator/tab_lifecycle_unit_source.h" |
| #include "chrome/browser/resource_coordinator/tab_load_tracker.h" |
| #include "chrome/browser/resource_coordinator/test_lifecycle_unit.h" |
| #include "chrome/browser/resource_coordinator/time.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/tabs/test_tab_strip_model_delegate.h" |
| #include "chrome/test/base/chrome_render_view_host_test_harness.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/web_contents_tester.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h" |
| #include "chrome/browser/media/webrtc/media_stream_capture_indicator.h" |
| |
| namespace resource_coordinator { |
| |
| namespace { |
| |
| constexpr base::TimeDelta kShortDelay = base::TimeDelta::FromSeconds(1); |
| |
| class MockTabLifecycleObserver : public TabLifecycleObserver { |
| public: |
| MockTabLifecycleObserver() = default; |
| |
| MOCK_METHOD2(OnDiscardedStateChange, |
| void(content::WebContents* contents, bool is_discarded)); |
| MOCK_METHOD2(OnAutoDiscardableStateChange, |
| void(content::WebContents* contents, bool is_auto_discardable)); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockTabLifecycleObserver); |
| }; |
| |
| } // namespace |
| |
| class MockLifecycleUnitObserver : public LifecycleUnitObserver { |
| public: |
| MockLifecycleUnitObserver() = default; |
| |
| MOCK_METHOD2(OnLifecycleUnitStateChanged, |
| void(LifecycleUnit*, LifecycleUnitState)); |
| MOCK_METHOD1(OnLifecycleUnitDestroyed, void(LifecycleUnit*)); |
| MOCK_METHOD2(OnLifecycleUnitVisibilityChanged, |
| void(LifecycleUnit*, content::Visibility)); |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(MockLifecycleUnitObserver); |
| }; |
| |
| class TabLifecycleUnitTest : public ChromeRenderViewHostTestHarness { |
| protected: |
| using TabLifecycleUnit = TabLifecycleUnitSource::TabLifecycleUnit; |
| |
| TabLifecycleUnitTest() : scoped_set_tick_clock_for_testing_(&test_clock_) { |
| observers_.AddObserver(&observer_); |
| } |
| |
| void SetUp() override { |
| ChromeRenderViewHostTestHarness::SetUp(); |
| |
| std::unique_ptr<content::WebContents> test_web_contents = |
| CreateTestWebContents(); |
| web_contents_ = test_web_contents.get(); |
| // Commit an URL to allow discarding. |
| content::WebContentsTester::For(web_contents_) |
| ->SetLastCommittedURL(GURL("https://www.example.com")); |
| |
| tab_strip_model_ = |
| std::make_unique<TabStripModel>(&tab_strip_model_delegate_, profile()); |
| tab_strip_model_->AppendWebContents(std::move(test_web_contents), false); |
| web_contents_->WasHidden(); |
| |
| std::unique_ptr<content::WebContents> second_web_contents = |
| CreateTestWebContents(); |
| content::WebContents* raw_second_web_contents = second_web_contents.get(); |
| tab_strip_model_->AppendWebContents(std::move(second_web_contents), |
| /*foreground=*/true); |
| raw_second_web_contents->WasHidden(); |
| } |
| |
| void TearDown() override { |
| while (!tab_strip_model_->empty()) |
| tab_strip_model_->DetachWebContentsAt(0); |
| tab_strip_model_.reset(); |
| ChromeRenderViewHostTestHarness::TearDown(); |
| } |
| |
| testing::StrictMock<MockTabLifecycleObserver> observer_; |
| base::ObserverList<TabLifecycleObserver> observers_; |
| content::WebContents* web_contents_; // Owned by tab_strip_model_. |
| std::unique_ptr<TabStripModel> tab_strip_model_; |
| base::SimpleTestTickClock test_clock_; |
| |
| private: |
| TestTabStripModelDelegate tab_strip_model_delegate_; |
| ScopedSetTickClockForTesting scoped_set_tick_clock_for_testing_; |
| |
| DISALLOW_COPY_AND_ASSIGN(TabLifecycleUnitTest); |
| }; |
| |
| TEST_F(TabLifecycleUnitTest, AsTabLifecycleUnitExternal) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| EXPECT_EQ(&tab_lifecycle_unit, |
| tab_lifecycle_unit.AsTabLifecycleUnitExternal()); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, CanDiscardByDefault) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| ExpectCanDiscardTrueAllReasons(&tab_lifecycle_unit); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, SetFocused) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| EXPECT_EQ(NowTicks(), tab_lifecycle_unit.GetLastFocusedTime()); |
| ExpectCanDiscardTrueAllReasons(&tab_lifecycle_unit); |
| |
| tab_lifecycle_unit.SetFocused(true); |
| tab_strip_model_->ActivateTabAt(0, false); |
| web_contents_->WasShown(); |
| EXPECT_EQ(base::TimeTicks::Max(), tab_lifecycle_unit.GetLastFocusedTime()); |
| ExpectCanDiscardFalseAllReasons(&tab_lifecycle_unit, |
| DecisionFailureReason::LIVE_STATE_VISIBLE); |
| |
| tab_lifecycle_unit.SetFocused(false); |
| tab_strip_model_->ActivateTabAt(1, false); |
| web_contents_->WasHidden(); |
| EXPECT_EQ(test_clock_.NowTicks(), tab_lifecycle_unit.GetLastFocusedTime()); |
| ExpectCanDiscardTrueAllReasons(&tab_lifecycle_unit); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, AutoDiscardable) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| |
| EXPECT_TRUE(tab_lifecycle_unit.IsAutoDiscardable()); |
| ExpectCanDiscardTrueAllReasons(&tab_lifecycle_unit); |
| |
| EXPECT_CALL(observer_, OnAutoDiscardableStateChange(web_contents_, false)); |
| tab_lifecycle_unit.SetAutoDiscardable(false); |
| testing::Mock::VerifyAndClear(&observer_); |
| EXPECT_FALSE(tab_lifecycle_unit.IsAutoDiscardable()); |
| ExpectCanDiscardFalseAllReasons( |
| &tab_lifecycle_unit, |
| DecisionFailureReason::LIVE_STATE_EXTENSION_DISALLOWED); |
| |
| EXPECT_CALL(observer_, OnAutoDiscardableStateChange(web_contents_, true)); |
| tab_lifecycle_unit.SetAutoDiscardable(true); |
| testing::Mock::VerifyAndClear(&observer_); |
| EXPECT_TRUE(tab_lifecycle_unit.IsAutoDiscardable()); |
| ExpectCanDiscardTrueAllReasons(&tab_lifecycle_unit); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, CannotDiscardCrashed) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| |
| web_contents_->SetIsCrashed(base::TERMINATION_STATUS_PROCESS_CRASHED, 0); |
| ExpectCanDiscardFalseTrivialAllReasons(&tab_lifecycle_unit); |
| } |
| |
| #if !defined(OS_CHROMEOS) |
| TEST_F(TabLifecycleUnitTest, CannotDiscardActive) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| |
| tab_strip_model_->ActivateTabAt(0, false); |
| |
| ExpectCanDiscardFalseAllReasons(&tab_lifecycle_unit, |
| DecisionFailureReason::LIVE_STATE_VISIBLE); |
| } |
| #endif // !defined(OS_CHROMEOS) |
| |
| TEST_F(TabLifecycleUnitTest, CannotDiscardInvalidURL) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| |
| content::WebContentsTester::For(web_contents_) |
| ->SetLastCommittedURL(GURL("invalid :)")); |
| ExpectCanDiscardFalseTrivialAllReasons(&tab_lifecycle_unit); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, CannotDiscardEmptyURL) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| |
| content::WebContentsTester::For(web_contents_)->SetLastCommittedURL(GURL()); |
| ExpectCanDiscardFalseTrivialAllReasons(&tab_lifecycle_unit); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, CannotDiscardVideoCapture) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| |
| content::MediaStreamDevices video_devices(1); |
| video_devices[0] = |
| content::MediaStreamDevice(content::MEDIA_DEVICE_VIDEO_CAPTURE, |
| "fake_media_device", "fake_media_device"); |
| MediaCaptureDevicesDispatcher* dispatcher = |
| MediaCaptureDevicesDispatcher::GetInstance(); |
| dispatcher->SetTestVideoCaptureDevices(video_devices); |
| std::unique_ptr<content::MediaStreamUI> video_stream_ui = |
| dispatcher->GetMediaStreamCaptureIndicator()->RegisterMediaStream( |
| web_contents_, video_devices); |
| video_stream_ui->OnStarted(base::RepeatingClosure()); |
| |
| ExpectCanDiscardFalseAllReasons(&tab_lifecycle_unit, |
| DecisionFailureReason::LIVE_STATE_CAPTURING); |
| |
| video_stream_ui.reset(); |
| |
| ExpectCanDiscardTrueAllReasons(&tab_lifecycle_unit); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, CannotDiscardRecentlyAudible) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| |
| // Cannot discard when the "recently audible" bit is set. |
| tab_lifecycle_unit.SetRecentlyAudible(true); |
| ExpectCanDiscardFalseAllReasons( |
| &tab_lifecycle_unit, DecisionFailureReason::LIVE_STATE_PLAYING_AUDIO); |
| |
| // The "recently audible" bit is still set. The tab cannot be discarded. |
| test_clock_.Advance(kTabAudioProtectionTime); |
| ExpectCanDiscardFalseAllReasons( |
| &tab_lifecycle_unit, DecisionFailureReason::LIVE_STATE_PLAYING_AUDIO); |
| |
| // The "recently audible" bit was unset less than |
| // kTabAudioProtectionTime ago. The tab cannot be discarded. |
| tab_lifecycle_unit.SetRecentlyAudible(false); |
| test_clock_.Advance(kShortDelay); |
| ExpectCanDiscardFalseAllReasons( |
| &tab_lifecycle_unit, DecisionFailureReason::LIVE_STATE_PLAYING_AUDIO); |
| |
| // The "recently audible" bit was unset kTabAudioProtectionTime ago. The tab |
| // can be discarded. |
| test_clock_.Advance(kTabAudioProtectionTime - kShortDelay); |
| ExpectCanDiscardTrueAllReasons(&tab_lifecycle_unit); |
| |
| // Calling SetRecentlyAudible(false) again does not change the fact that the |
| // tab can be discarded. |
| tab_lifecycle_unit.SetRecentlyAudible(false); |
| ExpectCanDiscardTrueAllReasons(&tab_lifecycle_unit); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, CanDiscardNeverAudibleTab) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| |
| tab_lifecycle_unit.SetRecentlyAudible(false); |
| // Since the tab was never audible, it should be possible to discard it, |
| // even if there was a recent call to SetRecentlyAudible(false). |
| ExpectCanDiscardTrueAllReasons(&tab_lifecycle_unit); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, CannotDiscardPDF) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| |
| content::WebContentsTester::For(web_contents_) |
| ->SetMainFrameMimeType("application/pdf"); |
| ExpectCanDiscardFalseAllReasons(&tab_lifecycle_unit, |
| DecisionFailureReason::LIVE_STATE_IS_PDF); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, CannotFreezeAFrozenTab) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| TabLoadTracker::Get()->StartTracking(web_contents_); |
| TabLoadTracker::Get()->TransitionStateForTesting(web_contents_, |
| TabLoadTracker::LOADED); |
| { |
| DecisionDetails decision_details; |
| EXPECT_TRUE(tab_lifecycle_unit.CanFreeze(&decision_details)); |
| } |
| tab_lifecycle_unit.Freeze(); |
| { |
| DecisionDetails decision_details; |
| EXPECT_FALSE(tab_lifecycle_unit.CanFreeze(&decision_details)); |
| } |
| TabLoadTracker::Get()->StopTracking(web_contents_); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, NotifiedOfWebContentsVisibilityChanges) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| |
| testing::StrictMock<MockLifecycleUnitObserver> observer; |
| tab_lifecycle_unit.AddObserver(&observer); |
| |
| EXPECT_CALL(observer, OnLifecycleUnitVisibilityChanged( |
| &tab_lifecycle_unit, content::Visibility::VISIBLE)); |
| web_contents_->WasShown(); |
| testing::Mock::VerifyAndClear(&observer); |
| |
| EXPECT_CALL(observer, OnLifecycleUnitVisibilityChanged( |
| &tab_lifecycle_unit, content::Visibility::HIDDEN)); |
| web_contents_->WasHidden(); |
| testing::Mock::VerifyAndClear(&observer); |
| |
| EXPECT_CALL(observer, OnLifecycleUnitVisibilityChanged( |
| &tab_lifecycle_unit, content::Visibility::VISIBLE)); |
| web_contents_->WasShown(); |
| testing::Mock::VerifyAndClear(&observer); |
| |
| EXPECT_CALL(observer, |
| OnLifecycleUnitVisibilityChanged(&tab_lifecycle_unit, |
| content::Visibility::OCCLUDED)); |
| web_contents_->WasOccluded(); |
| testing::Mock::VerifyAndClear(&observer); |
| |
| tab_lifecycle_unit.RemoveObserver(&observer); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, CanReloadBloatedTab) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| EXPECT_TRUE(tab_lifecycle_unit.CanReloadBloatedTab()); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, CannotReloadBloatedTabCrashed) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| |
| web_contents_->SetIsCrashed(base::TERMINATION_STATUS_PROCESS_CRASHED, 0); |
| |
| EXPECT_FALSE(tab_lifecycle_unit.CanReloadBloatedTab()); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, CannotReloadBloatedTabInvalidURL) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| |
| content::WebContentsTester::For(web_contents_) |
| ->SetLastCommittedURL(GURL("invalid :)")); |
| |
| EXPECT_FALSE(tab_lifecycle_unit.CanReloadBloatedTab()); |
| } |
| |
| TEST_F(TabLifecycleUnitTest, CannotReloadBloatedTabPendingUserInteraction) { |
| TabLifecycleUnit tab_lifecycle_unit(&observers_, web_contents_, |
| tab_strip_model_.get()); |
| content::PageImportanceSignals signals; |
| signals.had_form_interaction = true; |
| content::WebContentsTester::For(web_contents_) |
| ->SetPageImportanceSignals(signals); |
| EXPECT_FALSE(tab_lifecycle_unit.CanReloadBloatedTab()); |
| } |
| |
| } // namespace resource_coordinator |