blob: 7d0737dd1a73fcfed92bd3577f922602f7cf4c60 [file] [log] [blame]
// Copyright 2016 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/media/webrtc/tab_desktop_media_list.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/location.h"
#include "base/message_loop/message_loop.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/browser/media/webrtc/desktop_media_list_observer.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/test/base/scoped_testing_local_state.h"
#include "chrome/test/base/test_browser_window.h"
#include "chrome/test/base/testing_browser_process.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/browser/favicon_status.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "testing/gmock/include/gmock/gmock.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h"
#include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
#endif // defined(OS_CHROMEOS)
using content::WebContents;
using content::WebContentsTester;
namespace {
static const int kDefaultSourceCount = 2;
static const int kThumbnailSize = 50;
class UnittestProfileManager : public ::ProfileManagerWithoutInit {
public:
explicit UnittestProfileManager(const base::FilePath& user_data_dir)
: ::ProfileManagerWithoutInit(user_data_dir) {}
protected:
Profile* CreateProfileHelper(const base::FilePath& file_path) override {
if (!base::PathExists(file_path)) {
if (!base::CreateDirectory(file_path))
return NULL;
}
return new TestingProfile(file_path, NULL);
}
};
// Create a greyscale image with certain size and grayscale value.
gfx::Image CreateGrayscaleImage(gfx::Size size, uint8_t greyscale_value) {
SkBitmap result;
result.allocN32Pixels(size.width(), size.height(), true);
uint8_t* pixels_data = reinterpret_cast<uint8_t*>(result.getPixels());
// Set greyscale value for all pixels.
for (int y = 0; y < result.height(); ++y) {
for (int x = 0; x < result.width(); ++x) {
pixels_data[result.rowBytes() * y + x * result.bytesPerPixel()] =
greyscale_value;
pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 1] =
greyscale_value;
pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 2] =
greyscale_value;
pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] =
0xff;
}
}
return gfx::Image::CreateFrom1xBitmap(result);
}
} // namespace
class MockObserver : public DesktopMediaListObserver {
public:
MOCK_METHOD2(OnSourceAdded, void(DesktopMediaList* list, int index));
MOCK_METHOD2(OnSourceRemoved, void(DesktopMediaList* list, int index));
MOCK_METHOD3(OnSourceMoved,
void(DesktopMediaList* list, int old_index, int new_index));
MOCK_METHOD2(OnSourceNameChanged, void(DesktopMediaList* list, int index));
MOCK_METHOD2(OnSourceThumbnailChanged,
void(DesktopMediaList* list, int index));
void VerifyAndClearExpectations() {
testing::Mock::VerifyAndClearExpectations(this);
}
};
ACTION_P2(CheckListSize, list, expected_list_size) {
EXPECT_EQ(expected_list_size, list->GetSourceCount());
}
ACTION(QuitMessageLoop) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated());
}
class TabDesktopMediaListTest : public testing::Test {
protected:
TabDesktopMediaListTest()
: local_state_(TestingBrowserProcess::GetGlobal()) {}
void AddWebcontents(int favicon_greyscale) {
TabStripModel* tab_strip_model = browser_->tab_strip_model();
ASSERT_TRUE(tab_strip_model);
std::unique_ptr<WebContents> contents(
content::WebContentsTester::CreateTestWebContents(
profile_, content::SiteInstance::Create(profile_)));
ASSERT_TRUE(contents);
WebContentsTester::For(contents.get())
->SetLastActiveTime(base::TimeTicks::Now());
// Get or create the transient NavigationEntry and add a title and a
// favicon to it.
content::NavigationEntry* entry =
contents->GetController().GetTransientEntry();
if (!entry) {
std::unique_ptr<content::NavigationEntry> entry_new =
content::NavigationController::CreateNavigationEntry(
GURL("chrome://blank"), content::Referrer(),
ui::PAGE_TRANSITION_LINK, false, std::string(), profile_,
nullptr /* blob_url_loader_factory */);
contents->GetController().SetTransientEntry(std::move(entry_new));
entry = contents->GetController().GetTransientEntry();
}
contents->UpdateTitleForEntry(entry, base::ASCIIToUTF16("Test tab"));
content::FaviconStatus favicon_info;
favicon_info.image =
CreateGrayscaleImage(gfx::Size(10, 10), favicon_greyscale);
entry->GetFavicon() = favicon_info;
manually_added_web_contents_.push_back(contents.get());
tab_strip_model->AppendWebContents(std::move(contents), true);
}
void SetUp() override {
manually_added_web_contents_.clear();
rvh_test_enabler_.reset(new content::RenderViewHostTestEnabler());
// Create a new temporary directory, and store the path.
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
TestingBrowserProcess::GetGlobal()->SetProfileManager(
new UnittestProfileManager(temp_dir_.GetPath()));
#if defined(OS_CHROMEOS)
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
cl->AppendSwitch(switches::kTestType);
#endif
// Create profile.
ProfileManager* profile_manager = g_browser_process->profile_manager();
ASSERT_TRUE(profile_manager);
profile_ = profile_manager->GetLastUsedProfileAllowedByPolicy();
ASSERT_TRUE(profile_);
// Create browser.
Browser::CreateParams profile_params(profile_, true);
browser_ = CreateBrowserWithTestWindowForParams(&profile_params);
ASSERT_TRUE(browser_);
for (int i = 0; i < kDefaultSourceCount; i++) {
AddWebcontents(i + 1);
}
}
void TearDown() override {
// TODO(erikchen): Tearing down the TabStripModel should just delete all its
// owned WebContents. Then |manually_added_web_contents_| won't be
// necessary. https://crbug.com/832879.
TabStripModel* tab_strip_model = browser_->tab_strip_model();
for (WebContents* contents : manually_added_web_contents_) {
tab_strip_model->DetachWebContentsAt(
tab_strip_model->GetIndexOfWebContents(contents));
}
manually_added_web_contents_.clear();
browser_.reset();
TestingBrowserProcess::GetGlobal()->SetProfileManager(NULL);
base::RunLoop().RunUntilIdle();
rvh_test_enabler_.reset();
}
void CreateDefaultList() {
list_.reset(new TabDesktopMediaList());
list_->SetThumbnailSize(gfx::Size(kThumbnailSize, kThumbnailSize));
// Set update period to reduce the time it takes to run tests.
// >0 to avoid unit test failure.
list_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(1));
}
void InitializeAndVerify() {
CreateDefaultList();
// The tabs in media source list are sorted in decreasing time order. The
// latest one is listed first. However, tabs are added to TabStripModel in
// increasing time order, the oldest one is added first.
{
testing::InSequence dummy;
for (int i = 0; i < kDefaultSourceCount; i++) {
EXPECT_CALL(observer_, OnSourceAdded(list_.get(), i))
.WillOnce(CheckListSize(list_.get(), i + 1));
}
for (int i = 0; i < kDefaultSourceCount - 1; i++) {
EXPECT_CALL(observer_, OnSourceThumbnailChanged(
list_.get(), kDefaultSourceCount - 1 - i));
}
EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
.WillOnce(QuitMessageLoop());
}
list_->StartUpdating(&observer_);
base::RunLoop().Run();
for (int i = 0; i < kDefaultSourceCount; ++i) {
EXPECT_EQ(list_->GetSource(i).id.type,
content::DesktopMediaID::TYPE_WEB_CONTENTS);
}
observer_.VerifyAndClearExpectations();
}
// The path to temporary directory used to contain the test operations.
base::ScopedTempDir temp_dir_;
ScopedTestingLocalState local_state_;
std::unique_ptr<content::RenderViewHostTestEnabler> rvh_test_enabler_;
Profile* profile_;
std::unique_ptr<Browser> browser_;
// Must be listed before |list_|, so it's destroyed last.
MockObserver observer_;
std::unique_ptr<TabDesktopMediaList> list_;
std::vector<WebContents*> manually_added_web_contents_;
content::TestBrowserThreadBundle thread_bundle_;
#if defined(OS_CHROMEOS)
chromeos::ScopedCrosSettingsTestHelper cros_settings_test_helper_;
chromeos::ScopedTestUserManager test_user_manager_;
#endif
DISALLOW_COPY_AND_ASSIGN(TabDesktopMediaListTest);
};
TEST_F(TabDesktopMediaListTest, AddTab) {
InitializeAndVerify();
AddWebcontents(10);
EXPECT_CALL(observer_, OnSourceAdded(list_.get(), 0))
.WillOnce(CheckListSize(list_.get(), kDefaultSourceCount + 1));
EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
.WillOnce(QuitMessageLoop());
base::RunLoop().Run();
list_.reset();
}
TEST_F(TabDesktopMediaListTest, RemoveTab) {
InitializeAndVerify();
TabStripModel* tab_strip_model = browser_->tab_strip_model();
ASSERT_TRUE(tab_strip_model);
std::unique_ptr<WebContents> released_web_contents =
tab_strip_model->DetachWebContentsAt(kDefaultSourceCount - 1);
for (auto it = manually_added_web_contents_.begin();
it != manually_added_web_contents_.end(); ++it) {
if (*it == released_web_contents.get()) {
manually_added_web_contents_.erase(it);
break;
}
}
EXPECT_CALL(observer_, OnSourceRemoved(list_.get(), 0))
.WillOnce(
testing::DoAll(CheckListSize(list_.get(), kDefaultSourceCount - 1),
QuitMessageLoop()));
base::RunLoop().Run();
list_.reset();
}
TEST_F(TabDesktopMediaListTest, MoveTab) {
InitializeAndVerify();
// Swap the two media sources by swap their time stamps.
TabStripModel* tab_strip_model = browser_->tab_strip_model();
ASSERT_TRUE(tab_strip_model);
WebContents* contents0 = tab_strip_model->GetWebContentsAt(0);
ASSERT_TRUE(contents0);
base::TimeTicks t0 = contents0->GetLastActiveTime();
WebContents* contents1 = tab_strip_model->GetWebContentsAt(1);
ASSERT_TRUE(contents1);
base::TimeTicks t1 = contents1->GetLastActiveTime();
WebContentsTester::For(contents0)->SetLastActiveTime(t1);
WebContentsTester::For(contents1)->SetLastActiveTime(t0);
EXPECT_CALL(observer_, OnSourceMoved(list_.get(), 1, 0))
.WillOnce(testing::DoAll(CheckListSize(list_.get(), kDefaultSourceCount),
QuitMessageLoop()));
base::RunLoop().Run();
list_.reset();
}
TEST_F(TabDesktopMediaListTest, UpdateTitle) {
InitializeAndVerify();
// Change tab's title.
TabStripModel* tab_strip_model = browser_->tab_strip_model();
ASSERT_TRUE(tab_strip_model);
WebContents* contents =
tab_strip_model->GetWebContentsAt(kDefaultSourceCount - 1);
ASSERT_TRUE(contents);
const content::NavigationController& controller = contents->GetController();
contents->UpdateTitleForEntry(controller.GetTransientEntry(),
base::ASCIIToUTF16("New test tab"));
EXPECT_CALL(observer_, OnSourceNameChanged(list_.get(), 0))
.WillOnce(QuitMessageLoop());
base::RunLoop().Run();
EXPECT_EQ(list_->GetSource(0).name, base::UTF8ToUTF16("New test tab"));
list_.reset();
}
TEST_F(TabDesktopMediaListTest, UpdateThumbnail) {
InitializeAndVerify();
// Change tab's favicon.
TabStripModel* tab_strip_model = browser_->tab_strip_model();
ASSERT_TRUE(tab_strip_model);
WebContents* contents =
tab_strip_model->GetWebContentsAt(kDefaultSourceCount - 1);
ASSERT_TRUE(contents);
content::FaviconStatus favicon_info;
favicon_info.image = CreateGrayscaleImage(gfx::Size(10, 10), 100);
contents->GetController().GetTransientEntry()->GetFavicon() = favicon_info;
EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
.WillOnce(QuitMessageLoop());
base::RunLoop().Run();
list_.reset();
}