// 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/ash/phonehub/browser_tabs_metadata_fetcher_impl.h"
#include <deque>
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ash/sync/synced_session_client_ash.h"
#include "chrome/common/webui_url_constants.h"
#include "components/favicon/core/history_ui_favicon_request_handler.h"
#include "components/favicon_base/favicon_types.h"
#include "components/sessions/core/serialized_navigation_entry_test_helper.h"
#include "components/sync_sessions/synced_session.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/favicon_size.h"
#include "ui/gfx/image/image_unittest_util.h"
namespace ash {
namespace phonehub {
namespace {
using testing::_;
const base::Time kTimeA = base::Time::FromSecondsSinceUnixEpoch(1);
const base::Time kTimeB = base::Time::FromSecondsSinceUnixEpoch(2);
const base::Time kTimeC = base::Time::FromSecondsSinceUnixEpoch(3);
const base::Time kTimeD = base::Time::FromSecondsSinceUnixEpoch(4);
const base::Time kTimeE = base::Time::FromSecondsSinceUnixEpoch(5);
const base::Time kTimeF = base::Time::FromSecondsSinceUnixEpoch(6);
class MockHistoryUiFaviconRequestHandler
: public favicon::HistoryUiFaviconRequestHandler {
public:
MockHistoryUiFaviconRequestHandler() = default;
~MockHistoryUiFaviconRequestHandler() override = default;
MOCK_METHOD4(
GetRawFaviconForPageURL,
void(const GURL& page_url,
int desired_size_in_pixel,
favicon_base::FaviconRawBitmapCallback callback,
favicon::HistoryUiFaviconRequestOrigin request_origin_for_uma));
MOCK_METHOD3(
GetFaviconImageForPageURL,
void(const GURL& page_url,
favicon_base::FaviconImageCallback callback,
favicon::HistoryUiFaviconRequestOrigin request_origin_for_uma));
};
gfx::Image GetDummyImage() {
SkBitmap bitmap;
bitmap.allocN32Pixels(gfx::kFaviconSize, gfx::kFaviconSize);
bitmap.eraseColor(SK_ColorBLUE);
return gfx::Image::CreateFrom1xBitmap(bitmap);
}
favicon_base::FaviconImageResult GetDummyFaviconResult() {
favicon_base::FaviconImageResult result;
result.icon_url = GURL("http://example.com/favicon.ico");
result.image = GetDummyImage();
return result;
}
} // namespace
class BrowserTabsMetadataFetcherImplTest : public testing::Test {
public:
BrowserTabsMetadataFetcherImplTest()
: browser_tabs_metadata_job_(&favicon_request_handler_),
synced_session_(std::make_unique<sync_sessions::SyncedSession>()) {}
BrowserTabsMetadataFetcherImplTest(
const BrowserTabsMetadataFetcherImplTest&) = delete;
BrowserTabsMetadataFetcherImplTest& operator=(
const BrowserTabsMetadataFetcherImplTest&) = delete;
~BrowserTabsMetadataFetcherImplTest() override = default;
using BrowserTabMetadata = BrowserTabsModel::BrowserTabMetadata;
void OnBrowserTabMetadataFetched(
std::optional<std::vector<BrowserTabsModel::BrowserTabMetadata>>
browser_tab_metadatas) {
actual_browser_tabs_metadata_ = browser_tab_metadatas;
}
void AddTab(sync_sessions::SyncedSessionWindow* synced_session_window,
const std::u16string& title,
const GURL& url,
const base::Time& time) {
auto tab1 = std::make_unique<sessions::SessionTab>();
tab1->current_navigation_index = 0;
auto navigation = sessions::SerializedNavigationEntryTestHelper::
CreateNavigationForTest();
navigation.set_title(title);
navigation.set_virtual_url(url);
navigation.set_timestamp(time);
tab1->timestamp = time;
tab1->navigations.push_back(navigation);
tab1->navigations.back().set_encoded_page_state("");
synced_session_window->wrapped_window.tabs.push_back(std::move(tab1));
}
void AddWindow(std::unique_ptr<sync_sessions::SyncedSessionWindow>
synced_session_window) {
SessionID session_id = SessionID::NewUnique();
synced_session_->windows.insert(
std::pair<SessionID,
std::unique_ptr<sync_sessions::SyncedSessionWindow>>{
session_id, std::move(synced_session_window)});
}
void ExpectFaviconUrlFetchAttempt(const GURL& url) {
EXPECT_CALL(favicon_request_handler_,
GetFaviconImageForPageURL(url, /*callback=*/_,
/*request_origin_for_uma=*/_))
.WillRepeatedly(
[&](auto, favicon_base::FaviconImageCallback callback, auto) {
// Randomize the order in which callbacks may return.
if (std::rand() % 2) {
favicon_request_handler_responses_.emplace_front(
std::move(callback));
} else {
favicon_request_handler_responses_.emplace_back(
std::move(callback));
}
});
}
void AttemptFetch() {
browser_tabs_metadata_job_.Fetch(
synced_session_.get(),
base::BindOnce(
&BrowserTabsMetadataFetcherImplTest::OnBrowserTabMetadataFetched,
base::Unretained(this)));
}
void AttemptFetchForeignSyncedPhoneSessionMetadata(
const ForeignSyncedSessionAsh& session) {
browser_tabs_metadata_job_.FetchForeignSyncedPhoneSessionMetadata(
session, &synced_session_client_ash_,
base::BindOnce(
&BrowserTabsMetadataFetcherImplTest::OnBrowserTabMetadataFetched,
base::Unretained(this)));
}
void InvokeNextFaviconCallbacks(size_t num_successful_fetches) {
for (size_t i = 0; i < num_successful_fetches; i++) {
std::move(favicon_request_handler_responses_.front())
.Run(GetDummyFaviconResult());
favicon_request_handler_responses_.pop_front();
}
}
void CheckIsExpectedMetadata(
const std::vector<BrowserTabMetadata> browser_tabs_metadata) {
EXPECT_EQ(browser_tabs_metadata, actual_browser_tabs_metadata_);
// Check favicons as they are not includes in equality.
for (size_t i = 0; i < browser_tabs_metadata.size(); i++) {
EXPECT_TRUE(gfx::test::AreImagesEqual(
browser_tabs_metadata[i].favicon,
(*actual_browser_tabs_metadata_)[i].favicon));
}
}
const std::optional<std::vector<BrowserTabsModel::BrowserTabMetadata>>&
actual_browser_tabs_metadata() const {
return actual_browser_tabs_metadata_;
}
private:
testing::NiceMock<MockHistoryUiFaviconRequestHandler>
favicon_request_handler_;
BrowserTabsMetadataFetcherImpl browser_tabs_metadata_job_;
std::optional<std::vector<BrowserTabsModel::BrowserTabMetadata>>
actual_browser_tabs_metadata_;
SyncedSessionClientAsh synced_session_client_ash_;
std::map<SessionID, std::unique_ptr<sync_sessions::SyncedSessionWindow>>
windows;
std::unique_ptr<sync_sessions::SyncedSession> synced_session_;
std::deque<favicon_base::FaviconImageCallback>
favicon_request_handler_responses_;
};
TEST_F(BrowserTabsMetadataFetcherImplTest, NewFetchDuringOldFetchInProgress) {
const std::u16string kTitleA = u"A";
const GURL kUrlA = GURL("http://a.com");
const std::u16string kTitleB = u"B";
const GURL kUrlB = GURL("http://b.com");
const std::u16string kTitleC = u"C";
const GURL kUrlC = GURL("http://c.com");
const std::u16string kTitleD = u"D";
const GURL kUrlD = GURL("http://d.com");
auto synced_session_window =
std::make_unique<sync_sessions::SyncedSessionWindow>();
AddTab(synced_session_window.get(), kTitleB, kUrlB, kTimeB);
AddTab(synced_session_window.get(), kTitleA, kUrlA, kTimeA);
AddWindow(std::move(synced_session_window));
ExpectFaviconUrlFetchAttempt(kUrlB);
ExpectFaviconUrlFetchAttempt(kUrlA);
AttemptFetch();
InvokeNextFaviconCallbacks(/*num_successful_fetches=*/1);
auto synced_session_window_two =
std::make_unique<sync_sessions::SyncedSessionWindow>();
AddTab(synced_session_window_two.get(), kTitleD, kUrlD, kTimeD);
AddTab(synced_session_window_two.get(), kTitleC, kUrlC, kTimeC);
AddWindow(std::move(synced_session_window_two));
ExpectFaviconUrlFetchAttempt(kUrlD);
ExpectFaviconUrlFetchAttempt(kUrlC);
AttemptFetch();
EXPECT_FALSE(actual_browser_tabs_metadata());
// 3 callbacks called accounting for the additional missed one for tab A.
InvokeNextFaviconCallbacks(/*num_successful_fetches=*/3);
CheckIsExpectedMetadata(std::vector<BrowserTabMetadata>({
BrowserTabMetadata(kUrlD, kTitleD, kTimeD, GetDummyImage()),
BrowserTabMetadata(kUrlC, kTitleC, kTimeC, GetDummyImage()),
}));
}
TEST_F(BrowserTabsMetadataFetcherImplTest, NoTabsOpen) {
auto synced_session_window =
std::make_unique<sync_sessions::SyncedSessionWindow>();
AddWindow(std::move(synced_session_window));
AttemptFetch();
CheckIsExpectedMetadata({});
auto synced_session_window_two =
std::make_unique<sync_sessions::SyncedSessionWindow>();
// Add a tab without navigation(s), i.e no available metadata.
auto tab = std::make_unique<sessions::SessionTab>();
synced_session_window_two->wrapped_window.tabs.push_back(std::move(tab));
AddWindow(std::move(synced_session_window_two));
AttemptFetch();
CheckIsExpectedMetadata({});
}
TEST_F(BrowserTabsMetadataFetcherImplTest, BelowMaximumNumberOfTabs) {
const std::u16string kTitleC = u"C";
const GURL kUrlC = GURL("http://c.com");
const std::u16string kTitleD = u"D";
const GURL kUrlD = GURL("http://d.com");
auto synced_session_window =
std::make_unique<sync_sessions::SyncedSessionWindow>();
AddTab(synced_session_window.get(), kTitleD, kUrlD, kTimeD);
AddTab(synced_session_window.get(), kTitleC, kUrlC, kTimeC);
AddWindow(std::move(synced_session_window));
ExpectFaviconUrlFetchAttempt(kUrlC);
ExpectFaviconUrlFetchAttempt(kUrlD);
AttemptFetch();
InvokeNextFaviconCallbacks(/*num_successful_fetches=*/2);
CheckIsExpectedMetadata(std::vector<BrowserTabMetadata>({
BrowserTabMetadata(kUrlD, kTitleD, kTimeD, GetDummyImage()),
BrowserTabMetadata(kUrlC, kTitleC, kTimeC, GetDummyImage()),
}));
}
TEST_F(BrowserTabsMetadataFetcherImplTest, ExceedMaximumNumberOfTabs) {
const std::u16string kTitleA = u"A";
const GURL kUrlA = GURL("http://a.com");
const std::u16string kTitleB = u"B";
const GURL kUrlB = GURL("http://b.com");
const std::u16string kTitleC = u"C";
const GURL kUrlC = GURL("http://c.com");
const std::u16string kTitleD = u"D";
const GURL kUrlD = GURL("http://d.com");
const std::u16string kTitleE = u"E";
const GURL kUrlE = GURL(chrome::kChromeUINewTabURL);
const std::u16string kTitleF = u"F";
const GURL kUrlF = GURL("content://image.png");
auto synced_session_window =
std::make_unique<sync_sessions::SyncedSessionWindow>();
AddTab(synced_session_window.get(), kTitleA, kUrlA, kTimeA);
AddTab(synced_session_window.get(), kTitleE, kUrlE, kTimeE);
AddTab(synced_session_window.get(), kTitleB, kUrlB, kTimeB);
AddTab(synced_session_window.get(), kTitleD, kUrlD, kTimeD);
AddTab(synced_session_window.get(), kTitleF, kUrlF, kTimeF);
AddTab(synced_session_window.get(), kTitleC, kUrlC, kTimeC);
AddWindow(std::move(synced_session_window));
ExpectFaviconUrlFetchAttempt(kUrlD);
ExpectFaviconUrlFetchAttempt(kUrlC);
AttemptFetch();
InvokeNextFaviconCallbacks(/*num_successful_fetches=*/2);
// Tab A and Tab B are not present because they have the oldest timestamps,
// and the maximum number of BrowserTabMetadata has been met. Tabs E and F
// are not present because they have banned schemes.
CheckIsExpectedMetadata(std::vector<BrowserTabMetadata>({
BrowserTabMetadata(kUrlD, kTitleD, kTimeD, GetDummyImage()),
BrowserTabMetadata(kUrlC, kTitleC, kTimeC, GetDummyImage()),
}));
}
TEST_F(BrowserTabsMetadataFetcherImplTest, MultipleWindows) {
const std::u16string kTitleB = u"B";
const GURL kUrlB = GURL("http://b.com");
const std::u16string kTitleC = u"C";
const GURL kUrlC = GURL("http://c.com");
const std::u16string kTitleD = u"D";
const GURL kUrlD = GURL("http://d.com");
const std::u16string kTitleE = u"E";
const GURL kUrlE = GURL("http://e.com");
auto synced_session_window_one =
std::make_unique<sync_sessions::SyncedSessionWindow>();
AddTab(synced_session_window_one.get(), kTitleE, kUrlE, kTimeE);
AddTab(synced_session_window_one.get(), kTitleB, kUrlB, kTimeB);
AddWindow(std::move(synced_session_window_one));
auto synced_session_window_two =
std::make_unique<sync_sessions::SyncedSessionWindow>();
AddTab(synced_session_window_two.get(), kTitleD, kUrlD, kTimeD);
AddTab(synced_session_window_two.get(), kTitleC, kUrlC, kTimeC);
AddWindow(std::move(synced_session_window_two));
ExpectFaviconUrlFetchAttempt(kUrlD);
ExpectFaviconUrlFetchAttempt(kUrlE);
AttemptFetch();
InvokeNextFaviconCallbacks(/*num_successful_fetches=*/2);
CheckIsExpectedMetadata(std::vector<BrowserTabMetadata>({
BrowserTabMetadata(kUrlE, kTitleE, kTimeE, GetDummyImage()),
BrowserTabMetadata(kUrlD, kTitleD, kTimeD, GetDummyImage()),
}));
}
TEST_F(BrowserTabsMetadataFetcherImplTest,
FetchForeignSyncedPhoneSessionMetadata) {
ForeignSyncedSessionAsh test_session;
test_session.session_name = "testing";
test_session.modified_time = kTimeA;
ForeignSyncedSessionWindowAsh test_window;
const std::u16string kTitleC = u"C";
const GURL kUrlC = GURL("http://c.com");
const std::u16string kTitleD = u"D";
const GURL kUrlD = GURL("http://d.com");
ForeignSyncedSessionTabAsh test_tab_c;
test_tab_c.current_navigation_url = kUrlC;
test_tab_c.current_navigation_title = kTitleC;
test_tab_c.last_modified_timestamp = kTimeC;
ForeignSyncedSessionTabAsh test_tab_d;
test_tab_d.current_navigation_url = kUrlD;
test_tab_d.current_navigation_title = kTitleD;
test_tab_d.last_modified_timestamp = kTimeD;
test_window.tabs.push_back(std::move(test_tab_c));
test_window.tabs.push_back(std::move(test_tab_d));
test_session.windows.push_back(std::move(test_window));
ExpectFaviconUrlFetchAttempt(kUrlC);
ExpectFaviconUrlFetchAttempt(kUrlD);
AttemptFetchForeignSyncedPhoneSessionMetadata(test_session);
CheckIsExpectedMetadata(std::vector<BrowserTabMetadata>({
BrowserTabMetadata(kUrlD, kTitleD, kTimeD, gfx::Image()),
BrowserTabMetadata(kUrlC, kTitleC, kTimeC, gfx::Image()),
}));
}
} // namespace phonehub
} // namespace ash