blob: 165d4f93ca7fe1038d746d8a6f68f33e44e9757d [file] [log] [blame]
// Copyright (c) 2012 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 "base/base_switches.h"
#include "base/command_line.h"
#include "base/memory/memory_pressure_listener.h"
#include "base/test/simple_test_tick_clock.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/media/media_capture_devices_dispatcher.h"
#include "chrome/browser/media/media_stream_capture_indicator.h"
#include "chrome/browser/memory/tab_manager.h"
#include "chrome/browser/memory/tab_manager_web_contents_data.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/find_bar/find_bar_controller.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/test_utils.h"
#include "url/gurl.h"
using content::OpenURLParams;
#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
namespace memory {
class TabManagerTest : public InProcessBrowserTest {
public:
// Tab discarding is enabled by default on CrOS. On other platforms, force it
// by turning on the corresponding experiment as some tests assume this
// behavior it turned on.
void SetUpCommandLine(base::CommandLine* command_line) override {
#if !defined(OS_CHROMEOS)
command_line->AppendSwitchASCII(switches::kEnableFeatures,
features::kAutomaticTabDiscarding.name);
#endif
}
};
IN_PROC_BROWSER_TEST_F(TabManagerTest, TabManagerBasics) {
using content::WindowedNotificationObserver;
TabManager* tab_manager = g_browser_process->GetTabManager();
ASSERT_TRUE(tab_manager);
EXPECT_FALSE(tab_manager->recent_tab_discard());
// Get three tabs open.
WindowedNotificationObserver load1(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
OpenURLParams open1(GURL(chrome::kChromeUIAboutURL), content::Referrer(),
CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false);
browser()->OpenURL(open1);
load1.Wait();
WindowedNotificationObserver load2(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
OpenURLParams open2(GURL(chrome::kChromeUICreditsURL), content::Referrer(),
NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_TYPED, false);
browser()->OpenURL(open2);
load2.Wait();
WindowedNotificationObserver load3(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
OpenURLParams open3(GURL(chrome::kChromeUITermsURL), content::Referrer(),
NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_TYPED, false);
browser()->OpenURL(open3);
load3.Wait();
auto tsm = browser()->tab_strip_model();
EXPECT_EQ(3, tsm->count());
// Navigate the current (third) tab to a different URL, so we can test
// back/forward later.
WindowedNotificationObserver load4(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
OpenURLParams open4(GURL(chrome::kChromeUIVersionURL), content::Referrer(),
CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false);
browser()->OpenURL(open4);
load4.Wait();
// Navigate the third tab again, such that we have three navigation entries.
WindowedNotificationObserver load5(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
OpenURLParams open5(GURL("chrome://dns"), content::Referrer(), CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false);
browser()->OpenURL(open5);
load5.Wait();
EXPECT_EQ(3, tsm->count());
// Discard a tab. It should kill the first tab, since it was the oldest
// and was not selected.
EXPECT_TRUE(tab_manager->DiscardTab());
EXPECT_EQ(3, tsm->count());
EXPECT_TRUE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(0)));
EXPECT_FALSE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(1)));
EXPECT_FALSE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(2)));
EXPECT_TRUE(tab_manager->recent_tab_discard());
// Run discard again, make sure it kills the second tab.
EXPECT_TRUE(tab_manager->DiscardTab());
EXPECT_EQ(3, tsm->count());
EXPECT_TRUE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(0)));
EXPECT_TRUE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(1)));
EXPECT_FALSE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(2)));
// Kill the third tab. It should not kill the last tab, since it is active
// tab.
EXPECT_FALSE(tab_manager->DiscardTab());
EXPECT_TRUE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(0)));
EXPECT_TRUE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(1)));
EXPECT_FALSE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(2)));
// Kill the third tab after making second tab active.
tsm->ActivateTabAt(1, true);
EXPECT_EQ(1, tsm->active_index());
EXPECT_FALSE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(1)));
tab_manager->DiscardWebContentsAt(2, tsm);
EXPECT_TRUE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(2)));
// Force creation of the FindBarController.
browser()->GetFindBarController();
// Select the first tab. It should reload.
WindowedNotificationObserver reload1(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
chrome::SelectNumberedTab(browser(), 0);
reload1.Wait();
// Make sure the FindBarController gets the right WebContents.
EXPECT_EQ(browser()->GetFindBarController()->web_contents(),
tsm->GetActiveWebContents());
EXPECT_EQ(0, tsm->active_index());
EXPECT_FALSE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(0)));
EXPECT_FALSE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(1)));
EXPECT_TRUE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(2)));
// Select the third tab. It should reload.
WindowedNotificationObserver reload2(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
chrome::SelectNumberedTab(browser(), 2);
reload2.Wait();
EXPECT_EQ(2, tsm->active_index());
EXPECT_FALSE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(0)));
EXPECT_FALSE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(1)));
EXPECT_FALSE(tab_manager->IsTabDiscarded(tsm->GetWebContentsAt(2)));
// Navigate the third tab back twice. We used to crash here due to
// crbug.com/121373.
EXPECT_TRUE(chrome::CanGoBack(browser()));
EXPECT_FALSE(chrome::CanGoForward(browser()));
WindowedNotificationObserver back1(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
chrome::GoBack(browser(), CURRENT_TAB);
back1.Wait();
EXPECT_TRUE(chrome::CanGoBack(browser()));
EXPECT_TRUE(chrome::CanGoForward(browser()));
WindowedNotificationObserver back2(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
chrome::GoBack(browser(), CURRENT_TAB);
back2.Wait();
EXPECT_FALSE(chrome::CanGoBack(browser()));
EXPECT_TRUE(chrome::CanGoForward(browser()));
}
// Test that the MemoryPressureListener event is properly triggering a tab
// discard upon |MEMORY_PRESSURE_LEVEL_CRITICAL| event.
IN_PROC_BROWSER_TEST_F(TabManagerTest, OomPressureListener) {
TabManager* tab_manager = g_browser_process->GetTabManager();
ASSERT_TRUE(tab_manager);
// Get three tabs open.
content::WindowedNotificationObserver load1(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
OpenURLParams open1(GURL(chrome::kChromeUIAboutURL), content::Referrer(),
CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false);
browser()->OpenURL(open1);
load1.Wait();
content::WindowedNotificationObserver load2(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
OpenURLParams open2(GURL(chrome::kChromeUICreditsURL), content::Referrer(),
NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_TYPED, false);
browser()->OpenURL(open2);
load2.Wait();
EXPECT_FALSE(tab_manager->recent_tab_discard());
// Nothing should happen with a moderate memory pressure event.
base::MemoryPressureListener::NotifyMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE);
EXPECT_FALSE(tab_manager->recent_tab_discard());
// A critical memory pressure event should discard a tab.
base::MemoryPressureListener::NotifyMemoryPressure(
base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL);
// Coming here, an asynchronous operation will collect system stats. Once in,
// a tab should get discarded. As such we need to give it 10s time to discard.
const int kTimeoutTimeInMS = 10000;
const int kIntervalTimeInMS = 5;
int timeout = kTimeoutTimeInMS / kIntervalTimeInMS;
while (--timeout) {
base::PlatformThread::Sleep(
base::TimeDelta::FromMilliseconds(kIntervalTimeInMS));
base::RunLoop().RunUntilIdle();
if (tab_manager->recent_tab_discard())
break;
}
EXPECT_TRUE(tab_manager->recent_tab_discard());
}
IN_PROC_BROWSER_TEST_F(TabManagerTest, InvalidOrEmptyURL) {
TabManager* tab_manager = g_browser_process->GetTabManager();
ASSERT_TRUE(tab_manager);
// Open two tabs. Wait for the foreground one to load but do not wait for the
// background one.
content::WindowedNotificationObserver load1(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
OpenURLParams open1(GURL(chrome::kChromeUIAboutURL), content::Referrer(),
CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false);
browser()->OpenURL(open1);
load1.Wait();
content::WindowedNotificationObserver load2(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
content::NotificationService::AllSources());
OpenURLParams open2(GURL(chrome::kChromeUICreditsURL), content::Referrer(),
NEW_BACKGROUND_TAB, ui::PAGE_TRANSITION_TYPED, false);
browser()->OpenURL(open2);
ASSERT_EQ(2, browser()->tab_strip_model()->count());
// This shouldn't be able to discard a tab as the background tab has not yet
// started loading (its URL is not committed).
EXPECT_FALSE(tab_manager->DiscardTab());
// Wait for the background tab to load which then allows it to be discarded.
load2.Wait();
EXPECT_TRUE(tab_manager->DiscardTab());
}
// Makes sure that PDF pages are protected.
IN_PROC_BROWSER_TEST_F(TabManagerTest, ProtectPDFPages) {
TabManager* tab_manager = g_browser_process->GetTabManager();
ASSERT_TRUE(tab_manager);
// Start the embedded test server so we can get served the required PDF page.
ASSERT_TRUE(embedded_test_server()->InitializeAndListen());
embedded_test_server()->StartAcceptingConnections();
// Get two tabs open, the first one being a PDF page and the second one being
// the foreground tab.
GURL url1 = embedded_test_server()->GetURL("/pdf/test.pdf");
ui_test_utils::NavigateToURL(browser(), url1);
GURL url2(chrome::kChromeUIAboutURL);
ui_test_utils::NavigateToURLWithDisposition(
browser(), url2, NEW_FOREGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
// No discarding should be possible as the only background tab is displaying a
// PDF page, hence protected.
EXPECT_FALSE(tab_manager->DiscardTab());
}
// Makes sure that recently used tabs are protected, depending on the value of
// of |minimum_protection_time_|.
// TODO(georgesak): Move this to a unit test instead (requires change to API).
IN_PROC_BROWSER_TEST_F(TabManagerTest, ProtectRecentlyUsedTabs) {
const int protection_time = 5;
TabManager* tab_manager = g_browser_process->GetTabManager();
ASSERT_TRUE(tab_manager);
base::SimpleTestTickClock test_clock_;
tab_manager->set_test_tick_clock(&test_clock_);
// Set the minimum time of protection.
tab_manager->minimum_protection_time_ =
base::TimeDelta::FromMinutes(protection_time);
// Open 2 tabs, the second one being in the background.
ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIAboutURL));
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUIAboutURL), NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
// Set the last inactive time of the background tab.
auto tab = browser()->tab_strip_model()->GetWebContentsAt(1);
tab_manager->GetWebContentsData(tab)
->SetLastInactiveTime(test_clock_.NowTicks());
// Advance the clock for less than the protection time.
test_clock_.Advance(base::TimeDelta::FromMinutes(protection_time / 2));
// Should not be able to discard a tab.
ASSERT_FALSE(tab_manager->DiscardTab());
// Advance the clock for more than the protection time.
test_clock_.Advance(base::TimeDelta::FromMinutes(protection_time / 2 + 2));
// Should be able to discard the background tab now.
EXPECT_TRUE(tab_manager->DiscardTab());
}
// Makes sure that tabs using media devices are protected.
IN_PROC_BROWSER_TEST_F(TabManagerTest, ProtectVideoTabs) {
TabManager* tab_manager = g_browser_process->GetTabManager();
ASSERT_TRUE(tab_manager);
// Open 2 tabs, the second one being in the background.
ui_test_utils::NavigateToURL(browser(), GURL(chrome::kChromeUIAboutURL));
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUIAboutURL), NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
auto tab = browser()->tab_strip_model()->GetWebContentsAt(1);
// Simulate that a video stream is now being captured.
content::MediaStreamDevice fake_media_device(
content::MEDIA_DEVICE_VIDEO_CAPTURE, "fake_media_device",
"fake_media_device");
content::MediaStreamDevices video_devices(1, fake_media_device);
MediaCaptureDevicesDispatcher* dispatcher =
MediaCaptureDevicesDispatcher::GetInstance();
dispatcher->SetTestVideoCaptureDevices(video_devices);
scoped_ptr<content::MediaStreamUI> video_stream_ui =
dispatcher->GetMediaStreamCaptureIndicator()->RegisterMediaStream(
tab, video_devices);
video_stream_ui->OnStarted(base::Closure());
// Should not be able to discard a tab.
ASSERT_FALSE(tab_manager->DiscardTab());
// Remove the video stream.
video_stream_ui.reset();
// Should be able to discard the background tab now.
EXPECT_TRUE(tab_manager->DiscardTab());
}
} // namespace memory
#endif // OS_WIN || OS_CHROMEOS