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