blob: 6273487313e0f29446705509d47372eb555a3a3a [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 "chrome/browser/downgrade/user_data_downgrade.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/files/file_util.h"
#include "base/path_service.h"
#include "base/strings/string_piece.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/cancelable_task_tracker.h"
#include "base/test/mock_callback.h"
#include "base/time/time.h"
#include "base/version.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/browser/bookmarks/bookmark_model_factory.h"
#include "chrome/browser/downgrade/downgrade_manager.h"
#include "chrome/browser/first_run/scoped_relaunch_chrome_browser_override.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_result_codes.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/test/bookmark_test_helpers.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/browser/history_types.h"
#include "components/keyed_service/core/service_access_type.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/referrer.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/page_transition_types.h"
#include "ui/base/window_open_disposition.h"
#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
#include "base/threading/thread_restrictions.h"
#include "chrome/install_static/install_modes.h"
#include "chrome/install_static/test/scoped_install_details.h"
#endif
namespace downgrade {
namespace {
// A gMock matcher that is satisfied when its argument is a command line
// containing a given switch.
MATCHER_P(HasSwitch, switch_name, "") {
return arg.HasSwitch(switch_name);
}
int GetPrePrefixCount(base::StringPiece test_name) {
static constexpr base::StringPiece kPrePrefix("PRE_");
int pre_count = 0;
while (
base::StartsWith(test_name, kPrePrefix, base::CompareCase::SENSITIVE)) {
++pre_count;
test_name = test_name.substr(kPrePrefix.size());
}
return pre_count;
}
} // namespace
// A base test fixture for User data snapshot tests. This test that the browser
// behaves the same way after restoring a snapshot. First some user actions are
// simulated using the pure virtual function SimulateUserActions(). Second, an
// update is simulated to trigger a snapshot. Third a downgrade is simulated in
// order to trigger the downgrade processing and snapshot restoration. Last, the
// browser is started to verify that the browser behave the same as before the
// downgrade. At each step the pure virtual function ValidateUserActions() is
// called to verify that the browser behaves the same at each version. To test
// that a feature is supported by snapshots, create a test fixture that inherits
// UserDataSnapshotBrowserTestBase and overload SimulateUserActions() and
// ValidateUserActions() to validate the browser's behavior after a snapshot.
class UserDataSnapshotBrowserTestBase : public InProcessBrowserTest {
protected:
static bool IsInitialVersion() {
return GetPrePrefixCount(::testing::UnitTest::GetInstance()
->current_test_info()
->name()) == 3;
}
static bool IsUpdatedVersion() {
return GetPrePrefixCount(::testing::UnitTest::GetInstance()
->current_test_info()
->name()) == 2;
}
static bool IsRolledbackVersion() {
return GetPrePrefixCount(::testing::UnitTest::GetInstance()
->current_test_info()
->name()) == 1;
}
// Returns the next Chrome milestone version.
static std::string GetNextChromeVersion() {
return base::NumberToString(version_info::GetVersion().components()[0] + 1);
}
static std::string GetPreviousChromeVersion() {
return base::NumberToString(version_info::GetVersion().components()[0] - 1);
}
UserDataSnapshotBrowserTestBase() {
DowngradeManager::EnableSnapshotsForTesting(true);
}
~UserDataSnapshotBrowserTestBase() override {
DowngradeManager::EnableSnapshotsForTesting(false);
}
// Returns the path to the User Data dir.
const base::FilePath& user_data_dir() const { return user_data_dir_; }
bool SetUpUserDataDirectory() override {
if (!base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir_))
return false;
if (IsUpdatedVersion()) {
// Pretend that a lower version of Chrome previously wrote User Data.
const std::string last_version = GetPreviousChromeVersion();
base::WriteFile(user_data_dir_.Append(kDowngradeLastVersionFile),
last_version);
}
if (IsRolledbackVersion()) {
// Pretend that a higher version of Chrome previously wrote User Data.
const std::string last_version = GetNextChromeVersion();
base::WriteFile(user_data_dir_.Append(kDowngradeLastVersionFile),
last_version);
}
return true;
}
void SetUpInProcessBrowserTestFixture() override {
if (IsRolledbackVersion()) {
// Expect a browser relaunch late in browser shutdown.
mock_relaunch_callback_ = std::make_unique<::testing::StrictMock<
base::MockCallback<upgrade_util::RelaunchChromeBrowserCallback>>>();
EXPECT_CALL(*mock_relaunch_callback_,
Run(HasSwitch(switches::kUserDataMigrated)));
relaunch_chrome_override_ =
std::make_unique<upgrade_util::ScopedRelaunchChromeBrowserOverride>(
mock_relaunch_callback_->Get());
// Expect that browser startup short-circuits into a relaunch.
set_expected_exit_code(chrome::RESULT_CODE_DOWNGRADE_AND_RELAUNCH);
}
}
// Validates the state of chrome after a few user actions.
void SetUpOnMainThread() override {
if (IsInitialVersion())
SimulateUserActions();
ValidateUserActions();
}
// Simulates some user actions related to feature to test.
virtual void SimulateUserActions() = 0;
// Validates that the state of the browser is the same one after the user
// actions, update and rollback.
virtual void ValidateUserActions() = 0;
private:
// The location of User Data.
base::FilePath user_data_dir_;
std::unique_ptr<
base::MockCallback<upgrade_util::RelaunchChromeBrowserCallback>>
mock_relaunch_callback_;
std::unique_ptr<upgrade_util::ScopedRelaunchChromeBrowserOverride>
relaunch_chrome_override_;
};
class BookmarksSnapshotTest : public UserDataSnapshotBrowserTestBase {
protected:
// Bookmarks as such
// bookmark_bar_node
// |_ folder
// | |_ subfolder
// | | |_www.subfolder.com
// | |_ www.folder.com
// |_ www.thaturl.com
// other_node
// |_ www.other.com
// |_ www.thatotherurl.com
// mobile_node
// |_ www.mobile.com
void SimulateUserActions() override {
bookmarks::BookmarkModel* bookmark_model =
BookmarkModelFactory::GetForBrowserContext(browser()->profile());
bookmarks::test::WaitForBookmarkModelToLoad(bookmark_model);
auto* folder = bookmark_model->AddFolder(
bookmark_model->bookmark_bar_node(), 0, folder_title_);
auto* sub_folder = bookmark_model->AddFolder(folder, 0, sub_folder_title_);
bookmark_model->AddURL(bookmark_model->bookmark_bar_node(), 1,
that_url_title_, that_url_);
bookmark_model->AddURL(sub_folder, 0, sub_folder_url_title_,
sub_folder_url_);
bookmark_model->AddURL(folder, 1, folder_url_title_, folder_url_);
bookmark_model->AddURL(bookmark_model->other_node(), 0, other_url_title_,
other_url_);
bookmark_model->AddURL(bookmark_model->other_node(), 1,
that_other_url_title_, that_other_url_);
bookmark_model->AddURL(bookmark_model->mobile_node(), 0, mobile_url_title_,
mobile_url_);
// This is necessary to ensure the save completes.
content::RunAllTasksUntilIdle();
}
void ValidateUserActions() override {
bookmarks::BookmarkModel* bookmark_model =
BookmarkModelFactory::GetForBrowserContext(browser()->profile());
bookmarks::test::WaitForBookmarkModelToLoad(bookmark_model);
ASSERT_TRUE(bookmark_model->bookmark_bar_node()->children().size() == 2);
const bookmarks::BookmarkNode* folder =
bookmark_model->bookmark_bar_node()->children()[0].get();
EXPECT_EQ(folder->GetTitledUrlNodeTitle(), folder_title_);
ASSERT_TRUE(folder->children().size() == 2);
const bookmarks::BookmarkNode* sub_folder = folder->children()[0].get();
EXPECT_EQ(sub_folder->GetTitledUrlNodeTitle(), sub_folder_title_);
ASSERT_TRUE(sub_folder->children().size() == 1);
const bookmarks::BookmarkNode* sub_folder_url =
sub_folder->children()[0].get();
EXPECT_EQ(sub_folder_url->GetTitledUrlNodeTitle(), sub_folder_url_title_);
EXPECT_EQ(sub_folder_url->url(), sub_folder_url_);
ASSERT_TRUE(bookmark_model->other_node()->children().size() == 2);
const bookmarks::BookmarkNode* other_url =
bookmark_model->other_node()->children()[0].get();
EXPECT_EQ(other_url->GetTitledUrlNodeTitle(), other_url_title_);
EXPECT_EQ(other_url->url(), other_url_);
const bookmarks::BookmarkNode* that_other_url =
bookmark_model->other_node()->children()[1].get();
EXPECT_EQ(that_other_url->GetTitledUrlNodeTitle(), that_other_url_title_);
EXPECT_EQ(that_other_url->url(), that_other_url_);
ASSERT_TRUE(bookmark_model->mobile_node()->children().size() == 1);
const bookmarks::BookmarkNode* mobile_url =
bookmark_model->mobile_node()->children()[0].get();
EXPECT_EQ(mobile_url->GetTitledUrlNodeTitle(), mobile_url_title_);
EXPECT_EQ(mobile_url->url(), mobile_url_);
}
private:
const GURL that_url_{"https://www.thaturl.com"};
const GURL sub_folder_url_{"https://www.subfolder.com"};
const GURL folder_url_{"https://www.folder.com"};
const GURL other_url_{"https://www.other.com"};
const GURL that_other_url_{"https://www.thatotherul.com"};
const GURL mobile_url_{"https://www.mobile.com"};
const std::u16string folder_title_ = u"Folder";
const std::u16string sub_folder_title_ = u"Sub Folder";
const std::u16string sub_folder_url_title_ = u"Sub Folder URL";
const std::u16string that_url_title_ = u"That URL";
const std::u16string folder_url_title_ = u"Folder URL";
const std::u16string other_url_title_ = u"Other URL";
const std::u16string that_other_url_title_ = u"That Other URL";
const std::u16string mobile_url_title_ = u"Mobile URL";
};
IN_PROC_BROWSER_TEST_F(BookmarksSnapshotTest, PRE_PRE_PRE_Test) {}
IN_PROC_BROWSER_TEST_F(BookmarksSnapshotTest, PRE_PRE_Test) {}
IN_PROC_BROWSER_TEST_F(BookmarksSnapshotTest, PRE_Test) {}
IN_PROC_BROWSER_TEST_F(BookmarksSnapshotTest, Test) {}
class HistorySnapshotTest : public UserDataSnapshotBrowserTestBase {
struct HistoryEntry {
HistoryEntry(base::StringPiece url,
base::StringPiece title,
base::Time time,
history::VisitSource source)
: url(url),
title(base::ASCIIToUTF16(title)),
time(time),
source(source) {}
~HistoryEntry() = default;
GURL url;
std::u16string title;
base::Time time;
history::VisitSource source;
};
protected:
void SimulateUserActions() override {
auto* history_service = HistoryServiceFactory::GetForProfile(
browser()->profile(), ServiceAccessType::EXPLICIT_ACCESS);
for (const auto& entry : history_entries_) {
history_service->AddPage(entry.url, entry.time, entry.source);
history_service->SetPageTitle(entry.url, entry.title);
}
}
void ValidateUserActions() override {
auto* history_service = HistoryServiceFactory::GetForProfile(
browser()->profile(), ServiceAccessType::EXPLICIT_ACCESS);
history::QueryResults history_query_results;
base::RunLoop run_loop;
base::CancelableTaskTracker tracker;
history_service->QueryHistory(
std::u16string(), history::QueryOptions(),
base::BindOnce(&HistorySnapshotTest::SetQueryResults,
base::Unretained(this), run_loop.QuitClosure()),
&tracker);
run_loop.Run();
EXPECT_EQ(history_query_results_.size(), history_entries_.size());
std::map<GURL, const HistoryEntry*> expected_results_map;
for (const auto& entry : history_entries_)
expected_results_map[entry.url] = &entry;
for (const auto& result : history_query_results_) {
auto it = expected_results_map.find(result.url());
if (it == expected_results_map.end()) {
ADD_FAILURE() << "found an unexpected result for url " << result.url();
continue;
}
const auto* expected = it->second;
EXPECT_EQ(expected->time, result.visit_time());
EXPECT_EQ(expected->title, result.title());
}
}
private:
void SetQueryResults(base::RepeatingClosure continuation,
history::QueryResults results) {
history_query_results_ = std::move(results);
continuation.Run();
}
history::QueryResults history_query_results_;
const std::vector<HistoryEntry> history_entries_{
HistoryEntry("https://www.website.com",
"website",
base::Time::FromSecondsSinceUnixEpoch(1000),
history::VisitSource::SOURCE_BROWSED),
HistoryEntry("https://www.website1.com",
"website1",
base::Time::FromSecondsSinceUnixEpoch(10001),
history::VisitSource::SOURCE_EXTENSION),
HistoryEntry("https://www.website2.com",
"website2",
base::Time::FromSecondsSinceUnixEpoch(10002),
history::VisitSource::SOURCE_FIREFOX_IMPORTED),
HistoryEntry("https://www.website3.com",
"website3",
base::Time::FromSecondsSinceUnixEpoch(10003),
history::VisitSource::SOURCE_IE_IMPORTED),
HistoryEntry("https://www.website4.com",
"website4",
base::Time::FromSecondsSinceUnixEpoch(10004),
history::VisitSource::SOURCE_SAFARI_IMPORTED),
HistoryEntry("https://www.website5.com",
"website5",
base::Time::FromSecondsSinceUnixEpoch(10005),
history::VisitSource::SOURCE_SYNCED),
};
};
IN_PROC_BROWSER_TEST_F(HistorySnapshotTest, PRE_PRE_PRE_Test) {}
IN_PROC_BROWSER_TEST_F(HistorySnapshotTest, PRE_PRE_Test) {}
IN_PROC_BROWSER_TEST_F(HistorySnapshotTest, PRE_Test) {}
IN_PROC_BROWSER_TEST_F(HistorySnapshotTest, Test) {}
class TabsSnapshotTest : public UserDataSnapshotBrowserTestBase {
protected:
void SetUp() override {
ASSERT_TRUE(embedded_test_server()->Start());
UserDataSnapshotBrowserTestBase::SetUp();
}
void SimulateUserActions() override {
browser()->profile()->GetPrefs()->SetInteger(prefs::kRestoreOnStartup, 1);
browser()->OpenURL(content::OpenURLParams(
embedded_test_server()->GetURL("/title1.html"), content::Referrer(),
WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false));
browser()->OpenURL(content::OpenURLParams(
embedded_test_server()->GetURL("/title2.html"), content::Referrer(),
WindowOpenDisposition::NEW_FOREGROUND_TAB, ui::PAGE_TRANSITION_LINK,
false));
}
void ValidateUserActions() override {
EXPECT_EQ(
browser()->profile()->GetPrefs()->GetInteger(prefs::kRestoreOnStartup),
1);
auto* tab_strip = browser()->tab_strip_model();
// There are 2 tabs that need to be preserved. There might be a 3rd tab,
// about:blank that is opened by the test itself. That 3rd tab may or may
// not have been opened at this time.
ASSERT_GE(tab_strip->count(), 2);
ASSERT_LE(tab_strip->count(), 3);
if (tab_strip->count() == 3) {
EXPECT_TRUE(content::WaitForLoadStop(tab_strip->GetWebContentsAt(2)));
EXPECT_EQ(tab_strip->GetWebContentsAt(2)->GetLastCommittedURL(),
GURL("about:blank"));
}
// embedded_test_server() might return a different hostname.
content::WaitForLoadStop(tab_strip->GetWebContentsAt(0));
EXPECT_EQ(tab_strip->GetWebContentsAt(0)->GetLastCommittedURL().path(),
"/title1.html");
content::WaitForLoadStop(tab_strip->GetWebContentsAt(1));
EXPECT_EQ(tab_strip->GetWebContentsAt(1)->GetLastCommittedURL().path(),
"/title2.html");
}
};
IN_PROC_BROWSER_TEST_F(TabsSnapshotTest, PRE_PRE_PRE_Test) {}
IN_PROC_BROWSER_TEST_F(TabsSnapshotTest, PRE_PRE_Test) {}
IN_PROC_BROWSER_TEST_F(TabsSnapshotTest, PRE_Test) {}
IN_PROC_BROWSER_TEST_F(TabsSnapshotTest, Test) {}
// Tests that Google Chrome does not takes snapshots on mid-milestone updates.
IN_PROC_BROWSER_TEST_F(InProcessBrowserTest, SameMilestoneSnapshot) {
DowngradeManager::EnableSnapshotsForTesting(true);
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
base::FilePath user_data_dir;
ASSERT_TRUE(base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
auto current_version = version_info::GetVersion().GetString();
downgrade::DowngradeManager downgrade_manager;
// No snapshots for same version.
base::WriteFile(user_data_dir.Append(kDowngradeLastVersionFile),
current_version);
EXPECT_FALSE(downgrade_manager.PrepareUserDataDirectoryForCurrentVersion(
user_data_dir));
EXPECT_FALSE(
base::PathExists(user_data_dir.Append(downgrade::kSnapshotsDir)));
// Snapshot taken for minor update
std::vector<uint32_t> last_minor_version_components;
for (const auto& component : version_info::GetVersion().components()) {
// Decrement all but the major version.
last_minor_version_components.push_back(
!last_minor_version_components.empty() && component > 0 ? component - 1
: component);
}
auto last_minor_version =
base::Version(last_minor_version_components).GetString();
base::WriteFile(user_data_dir.Append(kDowngradeLastVersionFile),
last_minor_version);
EXPECT_FALSE(downgrade_manager.PrepareUserDataDirectoryForCurrentVersion(
user_data_dir));
EXPECT_FALSE(
base::PathExists(user_data_dir.Append(downgrade::kSnapshotsDir)));
}
#if BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
// Tests that Google Chrome canary takes snapshots on mid-milestone updates.
IN_PROC_BROWSER_TEST_F(InProcessBrowserTest, CanarySameMilestoneSnapshot) {
DowngradeManager::EnableSnapshotsForTesting(true);
install_static::ScopedInstallDetails install_details(
/*system_level=*/false, install_static::CANARY_INDEX);
base::ScopedAllowBlockingForTesting scoped_allow_blocking;
base::FilePath user_data_dir;
ASSERT_TRUE(base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir));
auto current_version = version_info::GetVersion().GetString();
downgrade::DowngradeManager downgrade_manager;
// No snapshots for same version.
base::WriteFile(user_data_dir.Append(kDowngradeLastVersionFile),
current_version);
EXPECT_FALSE(downgrade_manager.PrepareUserDataDirectoryForCurrentVersion(
user_data_dir));
EXPECT_FALSE(
base::PathExists(user_data_dir.Append(downgrade::kSnapshotsDir)));
// Snapshot taken for minor update
std::vector<uint32_t> last_minor_version_components;
for (const auto& component : version_info::GetVersion().components()) {
// Decrement all but the major version.
last_minor_version_components.push_back(
!last_minor_version_components.empty() && component > 0 ? component - 1
: component);
}
auto last_minor_version =
base::Version(last_minor_version_components).GetString();
base::WriteFile(user_data_dir.Append(kDowngradeLastVersionFile),
last_minor_version);
EXPECT_FALSE(downgrade_manager.PrepareUserDataDirectoryForCurrentVersion(
user_data_dir));
EXPECT_TRUE(base::PathExists(user_data_dir.Append(downgrade::kSnapshotsDir)));
}
#endif // BUILDFLAG(IS_WIN) && BUILDFLAG(GOOGLE_CHROME_BRANDING)
} // namespace downgrade