blob: 1d22def8f11f3275963cb5396ad2e522b4aabc18 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <optional>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/performance_manager/public/background_tab_loading_policy.h"
#include "chrome/browser/performance_manager/test_support/page_discarding_utils.h"
#include "chrome/browser/sessions/tab_loader_tester.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_element_identifiers.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/interaction/browser_elements.h"
#include "chrome/browser/ui/performance_controls/test_support/memory_saver_interactive_test_mixin.h"
#include "chrome/browser/ui/thumbnails/thumbnail_image.h"
#include "chrome/browser/ui/thumbnails/thumbnail_tab_helper.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/common/url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/interaction/interactive_browser_test.h"
#include "components/performance_manager/public/features.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/test/browser_test.h"
#include "ui/base/interaction/state_observer.h"
#include "url/gurl.h"
#if BUILDFLAG(ENABLE_SESSION_SERVICE)
#include "chrome/browser/sessions/tab_loader.h"
#endif // BUILDFLAG(ENABLE_SESSION_SERVICE)
namespace {
class ThumbnailObserver : public ui::test::StateObserver<bool> {
public:
explicit ThumbnailObserver(content::WebContents* web_contents) {
auto* const thumbnail_tab_helper =
ThumbnailTabHelper::FromWebContents(web_contents);
auto* thumbnail = thumbnail_tab_helper->thumbnail().get();
subscription_ = thumbnail->Subscribe();
subscription_->SetUncompressedImageCallback(
base::BindRepeating(&ThumbnailObserver::ThumbnailImageCallback,
weak_ptr_factory_.GetWeakPtr()));
thumbnail->RequestThumbnailImage();
}
~ThumbnailObserver() override = default;
protected:
void ThumbnailImageCallback(gfx::ImageSkia thumbnail_image) {
OnStateObserverStateChanged(!thumbnail_image.isNull());
subscription_ = nullptr;
}
private:
std::unique_ptr<ThumbnailImage::Subscription> subscription_;
base::WeakPtrFactory<ThumbnailObserver> weak_ptr_factory_{this};
};
class BrowserRemovedObserver : public ui::test::StateObserver<bool>,
public BrowserListObserver {
public:
explicit BrowserRemovedObserver(Browser* browser) : browser_(browser) {
BrowserList::AddObserver(this);
}
~BrowserRemovedObserver() override = default;
protected:
void OnBrowserRemoved(Browser* browser) override {
if (browser_ == browser) {
OnStateObserverStateChanged(true);
browser_ = nullptr;
BrowserList::RemoveObserver(this);
}
}
private:
raw_ptr<Browser> browser_;
};
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kFirstTab);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kSecondTab);
DEFINE_LOCAL_ELEMENT_IDENTIFIER_VALUE(kThirdTab);
DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(ThumbnailObserver, kThumbnailCreatedState);
DEFINE_LOCAL_STATE_IDENTIFIER_VALUE(BrowserRemovedObserver,
kBrowserRemovedState);
using ::performance_manager::testing::ScopedSetAllPagesDiscardableForTesting;
} // anonymous namespace
class ThumbnailTabHelperUpdatedInteractiveTest
: public MemorySaverInteractiveTestMixin<InteractiveBrowserTest> {
public:
#if BUILDFLAG(ENABLE_SESSION_SERVICE)
void ConfigureTabLoader(TabLoader* tab_loader) {
TabLoaderTester tester(tab_loader);
tester.SetMaxSimultaneousLoadsForTesting(1);
tester.SetMaxLoadedTabCountForTesting(1);
}
#endif
protected:
void SetUp() override {
// This flag causes the thumbnail tab helper system to engage. Otherwise
// there is no ThumbnailTabHelper created. Note that there *are* other flags
// that also trigger the existence of the helper.
scoped_feature_list_.InitAndEnableFeature(features::kTabHoverCardImages);
InteractiveBrowserTest::SetUp();
}
void SetUpOnMainThread() override {
target_browser_ = browser();
MemorySaverInteractiveTestMixin<
InteractiveBrowserTest>::SetUpOnMainThread();
unconditionally_discard_pages_ =
std::make_unique<ScopedSetAllPagesDiscardableForTesting>();
}
void TearDownOnMainThread() override {
unconditionally_discard_pages_.reset();
MemorySaverInteractiveTestMixin<
InteractiveBrowserTest>::TearDownOnMainThread();
target_browser_ = nullptr;
}
int GetTabCount() { return target_browser_->GetTabStripModel()->count(); }
auto CheckTabHasThumbnailData(int tab_index, bool has_data) {
return CheckResult(
[=, this]() {
return ThumbnailTabHelper::FromWebContents(
target_browser_->GetTabStripModel()->GetWebContentsAt(
tab_index))
->thumbnail()
->has_data();
},
has_data,
base::StrCat({"Checking that tab ", base::NumberToString(tab_index),
(has_data ? " has" : " doesn't have"), " data"}));
}
auto WaitForAndVerifyThumbnail(int tab_index) {
return Steps(ObserveState(kThumbnailCreatedState,
[tab_index, this]() {
return target_browser_->GetTabStripModel()
->GetWebContentsAt(tab_index);
}),
WaitForState(kThumbnailCreatedState, true));
}
auto VerifyTabIsNotLoadedAndNeedsReloading(int tab_index) {
return Check(
[tab_index, this]() {
auto* contents =
target_browser_->GetTabStripModel()->GetWebContentsAt(tab_index);
return !contents->IsLoading() &&
contents->GetController().NeedsReload();
},
base::StrCat({"Checking that tab ", base::NumberToString(tab_index),
" is not loaded and needs reloading"}));
}
auto CheckTabCountInBrowser(int count) {
return CheckResult(
[this]() { return target_browser_->GetTabStripModel()->count(); },
count,
base::StrCat({"Checking that there are ", base::NumberToString(count),
" tabs"}));
}
auto CheckActiveTabInBrowser(int active_index) {
return CheckResult(
[this]() {
return target_browser_->GetTabStripModel()->active_index();
},
active_index,
base::StrCat({"Checking that the active tab is in index ",
base::NumberToString(active_index)}));
}
void set_target_browser(BrowserWindowInterface* target_browser) {
target_browser_ = target_browser;
}
BrowserWindowInterface* target_browser() { return target_browser_; }
private:
// The browser target for thumbnail tab helper tests.
raw_ptr<BrowserWindowInterface> target_browser_;
base::test::ScopedFeatureList scoped_feature_list_;
std::unique_ptr<ScopedSetAllPagesDiscardableForTesting>
unconditionally_discard_pages_;
};
IN_PROC_BROWSER_TEST_F(ThumbnailTabHelperUpdatedInteractiveTest,
TabLoadTriggersScreenshot) {
RunTestSequence(
AddInstrumentedTab(kFirstTab, GURL(chrome::kChromeUINewTabURL), 0),
WaitForWebContentsReady(kFirstTab), CheckTabHasThumbnailData(0, false),
SelectTab(kTabStripElementId, 1),
CheckResult([this]() { return GetTabCount(); }, 2,
"Checking that there are two tabs"),
WaitForAndVerifyThumbnail(0), CheckTabHasThumbnailData(0, true));
}
IN_PROC_BROWSER_TEST_F(ThumbnailTabHelperUpdatedInteractiveTest,
TabDiscardPreservesScreenshot) {
RunTestSequence(
AddInstrumentedTab(kFirstTab, GURL(chrome::kChromeUINewTabURL), 0),
WaitForWebContentsReady(kFirstTab), CheckTabHasThumbnailData(0, false),
SelectTab(kTabStripElementId, 1), WaitForAndVerifyThumbnail(0),
CheckTabHasThumbnailData(0, true), TryDiscardTab(0),
CheckTabIsDiscarded(0, true), CheckTabHasThumbnailData(0, true));
}
// TabLoader (used here) is available only when browser is built
// with ENABLE_SESSION_SERVICE.
#if BUILDFLAG(ENABLE_SESSION_SERVICE)
// On browser restore, some tabs may not be loaded. Requesting a
// thumbnail for one of these tabs should trigger load and capture.
IN_PROC_BROWSER_TEST_F(ThumbnailTabHelperUpdatedInteractiveTest,
CapturesRestoredTabWhenRequested) {
// Passed by address, so must live until the end of the test.
base::RepeatingCallback<void(TabLoader*)> construction_callback =
base::BindRepeating(
&ThumbnailTabHelperUpdatedInteractiveTest::ConfigureTabLoader,
base::Unretained(this));
// Target a new browser to allow testing the browser-restore codepath without
// triggering shutdown.
ui_test_utils::BrowserCreatedObserver browser_created_observer;
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL(chrome::kChromeUINewTabURL),
WindowOpenDisposition::NEW_WINDOW,
ui_test_utils::BROWSER_TEST_WAIT_FOR_BROWSER);
set_target_browser(browser_created_observer.Wait());
RunTestSequence(
InContext(
BrowserElements::From(target_browser())->GetContext(),
AddInstrumentedTab(kFirstTab, GURL(chrome::kChromeUINewTabURL), 1),
WaitForWebContentsReady(kFirstTab),
AddInstrumentedTab(kSecondTab, GURL(chrome::kChromeUINewTabURL), 2),
WaitForWebContentsReady(kSecondTab),
AddInstrumentedTab(kThirdTab, GURL(chrome::kChromeUINewTabURL), 3),
WaitForWebContentsReady(kThirdTab)),
CheckTabCountInBrowser(4), CheckActiveTabInBrowser(3),
ObserveState(
kBrowserRemovedState,
[this]() { return target_browser()->GetBrowserForMigrationOnly(); }),
// Can't close browser when WebContents is notifying observers.
Do([this]() {
// Override manual value set in MemorySaverInteractiveTestMixin to
// prepare for tab strip being destroyed along with the browser.
resource_coordinator::GetTabLifecycleUnitSource()
->SetFocusedTabStripModelForTesting(nullptr);
target_browser()->GetWindow()->Close();
set_target_browser(nullptr);
}),
WaitForState(kBrowserRemovedState, true),
Do([this, &construction_callback]() {
// Set up the tab loader to ensure tabs are left unloaded.
if (base::FeatureList::IsEnabled(
performance_manager::features::
kBackgroundTabLoadingFromPerformanceManager)) {
ASSERT_TRUE(
performance_manager::policies::CanScheduleLoadForRestoredTabs());
performance_manager::policies::
SetMaxSimultaneousBackgroundTabLoadsForTesting(1);
performance_manager::policies::
SetMaxLoadedBackgroundTabCountForTesting(1);
} else {
TabLoaderTester::SetConstructionCallbackForTesting(
&construction_callback);
}
// Restore recently closed window.
ui_test_utils::BrowserCreatedObserver browser_created_observer;
chrome::OpenWindowWithRestoredTabs(browser()->profile());
set_target_browser(browser_created_observer.Wait());
}),
CheckTabCountInBrowser(4), CheckActiveTabInBrowser(3),
VerifyTabIsNotLoadedAndNeedsReloading(1),
VerifyTabIsNotLoadedAndNeedsReloading(2), WaitForAndVerifyThumbnail(1));
if (!base::FeatureList::IsEnabled(
performance_manager::features::
kBackgroundTabLoadingFromPerformanceManager)) {
// Clean up the callback.
TabLoaderTester::SetConstructionCallbackForTesting(nullptr);
}
}
#endif // BUILDFLAG(ENABLE_SESSION_SERVICE)