1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/media/webrtc/tab_desktop_media_list.h"
6
7 #include "base/command_line.h"
8 #include "base/files/file_util.h"
9 #include "base/files/scoped_temp_dir.h"
10 #include "base/location.h"
11 #include "base/run_loop.h"
12 #include "base/single_thread_task_runner.h"
13 #include "base/stl_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "base/threading/thread_task_runner_handle.h"
16 #include "chrome/browser/media/webrtc/desktop_media_list.h"
17 #include "chrome/browser/profiles/profile_manager.h"
18 #include "chrome/browser/ui/browser_list.h"
19 #include "chrome/browser/ui/tabs/tab_strip_model.h"
20 #include "chrome/test/base/scoped_testing_local_state.h"
21 #include "chrome/test/base/test_browser_window.h"
22 #include "chrome/test/base/testing_browser_process.h"
23 #include "chrome/test/base/testing_profile.h"
24 #include "content/public/browser/favicon_status.h"
25 #include "content/public/browser/navigation_entry.h"
26 #include "content/public/browser/web_contents.h"
27 #include "content/public/common/content_switches.h"
28 #include "content/public/test/browser_task_environment.h"
29 #include "content/public/test/navigation_simulator.h"
30 #include "content/public/test/test_renderer_host.h"
31 #include "content/public/test/web_contents_tester.h"
32 #include "testing/gmock/include/gmock/gmock.h"
33
34 #if defined(OS_CHROMEOS)
35 #include "chrome/browser/chromeos/login/users/scoped_test_user_manager.h"
36 #include "chrome/browser/chromeos/settings/scoped_cros_settings_test_helper.h"
37 #endif // defined(OS_CHROMEOS)
38
39 using content::WebContents;
40 using content::WebContentsTester;
41
42 namespace {
43
44 constexpr int kDefaultSourceCount = 2;
45 constexpr int kThumbnailSize = 50;
46
47 class UnittestProfileManager : public ::ProfileManagerWithoutInit {
48 public:
UnittestProfileManager(const base::FilePath & user_data_dir)49 explicit UnittestProfileManager(const base::FilePath& user_data_dir)
50 : ::ProfileManagerWithoutInit(user_data_dir) {}
51
52 protected:
CreateProfileHelper(const base::FilePath & path)53 std::unique_ptr<Profile> CreateProfileHelper(
54 const base::FilePath& path) override {
55 if (!base::PathExists(path) && !base::CreateDirectory(path))
56 return nullptr;
57 return std::make_unique<TestingProfile>(path);
58 }
59 };
60
61 // Create a greyscale image with certain size and grayscale value.
CreateGrayscaleImage(gfx::Size size,uint8_t greyscale_value)62 gfx::Image CreateGrayscaleImage(gfx::Size size, uint8_t greyscale_value) {
63 SkBitmap result;
64 result.allocN32Pixels(size.width(), size.height(), true);
65
66 uint8_t* pixels_data = reinterpret_cast<uint8_t*>(result.getPixels());
67
68 // Set greyscale value for all pixels.
69 for (int y = 0; y < result.height(); ++y) {
70 for (int x = 0; x < result.width(); ++x) {
71 pixels_data[result.rowBytes() * y + x * result.bytesPerPixel()] =
72 greyscale_value;
73 pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 1] =
74 greyscale_value;
75 pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 2] =
76 greyscale_value;
77 pixels_data[result.rowBytes() * y + x * result.bytesPerPixel() + 3] =
78 0xff;
79 }
80 }
81
82 return gfx::Image::CreateFrom1xBitmap(result);
83 }
84
85 } // namespace
86
87 class MockObserver : public DesktopMediaListObserver {
88 public:
89 MOCK_METHOD2(OnSourceAdded, void(DesktopMediaList* list, int index));
90 MOCK_METHOD2(OnSourceRemoved, void(DesktopMediaList* list, int index));
91 MOCK_METHOD3(OnSourceMoved,
92 void(DesktopMediaList* list, int old_index, int new_index));
93 MOCK_METHOD2(OnSourceNameChanged, void(DesktopMediaList* list, int index));
94 MOCK_METHOD2(OnSourceThumbnailChanged,
95 void(DesktopMediaList* list, int index));
96 MOCK_METHOD1(OnAllSourcesFound, void(DesktopMediaList* list));
97
VerifyAndClearExpectations()98 void VerifyAndClearExpectations() {
99 testing::Mock::VerifyAndClearExpectations(this);
100 }
101 };
102
ACTION_P2(CheckListSize,list,expected_list_size)103 ACTION_P2(CheckListSize, list, expected_list_size) {
104 EXPECT_EQ(expected_list_size, list->GetSourceCount());
105 }
106
ACTION(QuitMessageLoop)107 ACTION(QuitMessageLoop) {
108 base::ThreadTaskRunnerHandle::Get()->PostTask(
109 FROM_HERE, base::RunLoop::QuitCurrentWhenIdleClosureDeprecated());
110 }
111
112 class TabDesktopMediaListTest : public testing::Test {
113 protected:
TabDesktopMediaListTest()114 TabDesktopMediaListTest()
115 : local_state_(TestingBrowserProcess::GetGlobal()) {}
116
AddWebcontents(int favicon_greyscale)117 void AddWebcontents(int favicon_greyscale) {
118 TabStripModel* tab_strip_model = browser_->tab_strip_model();
119 ASSERT_TRUE(tab_strip_model);
120 std::unique_ptr<WebContents> contents(
121 content::WebContentsTester::CreateTestWebContents(
122 profile_, content::SiteInstance::Create(profile_)));
123 ASSERT_TRUE(contents);
124
125 WebContentsTester::For(contents.get())
126 ->SetLastActiveTime(base::TimeTicks::Now());
127
128 // Get or create a NavigationEntry and add a title and a favicon to it.
129 content::NavigationEntry* entry =
130 contents->GetController().GetLastCommittedEntry();
131 if (!entry) {
132 content::NavigationSimulator::NavigateAndCommitFromBrowser(
133 contents.get(), GURL("chrome://blank"));
134 entry = contents->GetController().GetLastCommittedEntry();
135 }
136
137 contents->UpdateTitleForEntry(entry, base::ASCIIToUTF16("Test tab"));
138
139 content::FaviconStatus favicon_info;
140 favicon_info.image =
141 CreateGrayscaleImage(gfx::Size(10, 10), favicon_greyscale);
142 entry->GetFavicon() = favicon_info;
143
144 manually_added_web_contents_.push_back(contents.get());
145 tab_strip_model->AppendWebContents(std::move(contents), true);
146 }
147
SetUp()148 void SetUp() override {
149 rvh_test_enabler_.reset(new content::RenderViewHostTestEnabler());
150 // Create a new temporary directory, and store the path.
151 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
152 TestingBrowserProcess::GetGlobal()->SetProfileManager(
153 new UnittestProfileManager(temp_dir_.GetPath()));
154
155 #if defined(OS_CHROMEOS)
156 base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
157 cl->AppendSwitch(switches::kTestType);
158 #endif
159
160 // Create profile.
161 ProfileManager* profile_manager = g_browser_process->profile_manager();
162 ASSERT_TRUE(profile_manager);
163
164 profile_ = profile_manager->GetLastUsedProfileAllowedByPolicy();
165 ASSERT_TRUE(profile_);
166
167 // Create browser.
168 Browser::CreateParams profile_params(profile_, true);
169 browser_ = CreateBrowserWithTestWindowForParams(profile_params);
170 ASSERT_TRUE(browser_);
171 for (int i = 0; i < kDefaultSourceCount; i++) {
172 AddWebcontents(i + 1);
173 }
174 }
175
TearDown()176 void TearDown() override {
177 // TODO(erikchen): Tearing down the TabStripModel should just delete all its
178 // owned WebContents. Then |manually_added_web_contents_| won't be
179 // necessary. https://crbug.com/832879.
180 TabStripModel* tab_strip_model = browser_->tab_strip_model();
181 for (WebContents* contents : manually_added_web_contents_) {
182 tab_strip_model->DetachWebContentsAt(
183 tab_strip_model->GetIndexOfWebContents(contents));
184 }
185 manually_added_web_contents_.clear();
186
187 browser_.reset();
188 TestingBrowserProcess::GetGlobal()->SetProfileManager(NULL);
189 base::RunLoop().RunUntilIdle();
190 rvh_test_enabler_.reset();
191 }
192
CreateDefaultList()193 void CreateDefaultList() {
194 list_.reset(new TabDesktopMediaList());
195 list_->SetThumbnailSize(gfx::Size(kThumbnailSize, kThumbnailSize));
196
197 // Set update period to reduce the time it takes to run tests.
198 // >0 to avoid unit test failure.
199 list_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(1));
200 }
201
InitializeAndVerify()202 void InitializeAndVerify() {
203 CreateDefaultList();
204
205 // The tabs in media source list are sorted in decreasing time order. The
206 // latest one is listed first. However, tabs are added to TabStripModel in
207 // increasing time order, the oldest one is added first.
208 {
209 testing::InSequence dummy;
210
211 for (int i = 0; i < kDefaultSourceCount; i++) {
212 EXPECT_CALL(observer_, OnSourceAdded(list_.get(), i))
213 .WillOnce(CheckListSize(list_.get(), i + 1));
214 }
215
216 for (int i = 0; i < kDefaultSourceCount - 1; i++) {
217 EXPECT_CALL(observer_, OnSourceThumbnailChanged(
218 list_.get(), kDefaultSourceCount - 1 - i));
219 }
220 EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
221 .WillOnce(QuitMessageLoop());
222 }
223
224 list_->StartUpdating(&observer_);
225 base::RunLoop().Run();
226
227 for (int i = 0; i < kDefaultSourceCount; ++i) {
228 EXPECT_EQ(list_->GetSource(i).id.type,
229 content::DesktopMediaID::TYPE_WEB_CONTENTS);
230 }
231
232 observer_.VerifyAndClearExpectations();
233 }
234
235 // The path to temporary directory used to contain the test operations.
236 base::ScopedTempDir temp_dir_;
237 ScopedTestingLocalState local_state_;
238
239 std::unique_ptr<content::RenderViewHostTestEnabler> rvh_test_enabler_;
240 Profile* profile_;
241 std::unique_ptr<Browser> browser_;
242
243 // Must be listed before |list_|, so it's destroyed last.
244 MockObserver observer_;
245 std::unique_ptr<TabDesktopMediaList> list_;
246 std::vector<WebContents*> manually_added_web_contents_;
247
248 content::BrowserTaskEnvironment task_environment_;
249
250 #if defined(OS_CHROMEOS)
251 chromeos::ScopedCrosSettingsTestHelper cros_settings_test_helper_;
252 chromeos::ScopedTestUserManager test_user_manager_;
253 #endif
254
255 DISALLOW_COPY_AND_ASSIGN(TabDesktopMediaListTest);
256 };
257
TEST_F(TabDesktopMediaListTest,AddTab)258 TEST_F(TabDesktopMediaListTest, AddTab) {
259 InitializeAndVerify();
260
261 AddWebcontents(10);
262
263 EXPECT_CALL(observer_, OnSourceAdded(list_.get(), 0))
264 .WillOnce(CheckListSize(list_.get(), kDefaultSourceCount + 1));
265 EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
266 .WillOnce(QuitMessageLoop());
267
268 base::RunLoop().Run();
269
270 list_.reset();
271 }
272
TEST_F(TabDesktopMediaListTest,RemoveTab)273 TEST_F(TabDesktopMediaListTest, RemoveTab) {
274 InitializeAndVerify();
275
276 TabStripModel* tab_strip_model = browser_->tab_strip_model();
277 ASSERT_TRUE(tab_strip_model);
278 std::unique_ptr<WebContents> released_web_contents =
279 tab_strip_model->DetachWebContentsAt(kDefaultSourceCount - 1);
280 base::Erase(manually_added_web_contents_, released_web_contents.get());
281
282 EXPECT_CALL(observer_, OnSourceRemoved(list_.get(), 0))
283 .WillOnce(
284 testing::DoAll(CheckListSize(list_.get(), kDefaultSourceCount - 1),
285 QuitMessageLoop()));
286
287 base::RunLoop().Run();
288
289 list_.reset();
290 }
291
TEST_F(TabDesktopMediaListTest,MoveTab)292 TEST_F(TabDesktopMediaListTest, MoveTab) {
293 InitializeAndVerify();
294
295 // Swap the two media sources by swap their time stamps.
296 TabStripModel* tab_strip_model = browser_->tab_strip_model();
297 ASSERT_TRUE(tab_strip_model);
298
299 WebContents* contents0 = tab_strip_model->GetWebContentsAt(0);
300 ASSERT_TRUE(contents0);
301 base::TimeTicks t0 = contents0->GetLastActiveTime();
302 WebContents* contents1 = tab_strip_model->GetWebContentsAt(1);
303 ASSERT_TRUE(contents1);
304 base::TimeTicks t1 = contents1->GetLastActiveTime();
305
306 WebContentsTester::For(contents0)->SetLastActiveTime(t1);
307 WebContentsTester::For(contents1)->SetLastActiveTime(t0);
308
309 EXPECT_CALL(observer_, OnSourceMoved(list_.get(), 1, 0))
310 .WillOnce(testing::DoAll(CheckListSize(list_.get(), kDefaultSourceCount),
311 QuitMessageLoop()));
312
313 base::RunLoop().Run();
314
315 list_.reset();
316 }
317
TEST_F(TabDesktopMediaListTest,UpdateTitle)318 TEST_F(TabDesktopMediaListTest, UpdateTitle) {
319 InitializeAndVerify();
320
321 // Change tab's title.
322 TabStripModel* tab_strip_model = browser_->tab_strip_model();
323 ASSERT_TRUE(tab_strip_model);
324 WebContents* contents =
325 tab_strip_model->GetWebContentsAt(kDefaultSourceCount - 1);
326 ASSERT_TRUE(contents);
327 content::NavigationController& controller = contents->GetController();
328 contents->UpdateTitleForEntry(controller.GetLastCommittedEntry(),
329 base::ASCIIToUTF16("New test tab"));
330
331 EXPECT_CALL(observer_, OnSourceNameChanged(list_.get(), 0))
332 .WillOnce(QuitMessageLoop());
333
334 base::RunLoop().Run();
335
336 EXPECT_EQ(list_->GetSource(0).name, base::UTF8ToUTF16("New test tab"));
337
338 list_.reset();
339 }
340
TEST_F(TabDesktopMediaListTest,UpdateThumbnail)341 TEST_F(TabDesktopMediaListTest, UpdateThumbnail) {
342 InitializeAndVerify();
343
344 // Change tab's favicon.
345 TabStripModel* tab_strip_model = browser_->tab_strip_model();
346 ASSERT_TRUE(tab_strip_model);
347 WebContents* contents =
348 tab_strip_model->GetWebContentsAt(kDefaultSourceCount - 1);
349 ASSERT_TRUE(contents);
350
351 content::FaviconStatus favicon_info;
352 favicon_info.image = CreateGrayscaleImage(gfx::Size(10, 10), 100);
353 contents->GetController().GetLastCommittedEntry()->GetFavicon() =
354 favicon_info;
355
356 EXPECT_CALL(observer_, OnSourceThumbnailChanged(list_.get(), 0))
357 .WillOnce(QuitMessageLoop());
358
359 base::RunLoop().Run();
360
361 list_.reset();
362 }
363