1 // Copyright 2013 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/native_desktop_media_list.h"
6 
7 #include <stddef.h>
8 #include <stdint.h>
9 #include <string.h>
10 
11 #include <utility>
12 #include <vector>
13 
14 #include "base/location.h"
15 #include "base/macros.h"
16 #include "base/memory/ptr_util.h"
17 #include "base/run_loop.h"
18 #include "base/single_thread_task_runner.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/synchronization/lock.h"
21 #include "base/threading/thread_task_runner_handle.h"
22 #include "chrome/browser/media/webrtc/desktop_media_list.h"
23 #include "chrome/test/views/chrome_views_test_base.h"
24 #include "testing/gmock/include/gmock/gmock.h"
25 #include "testing/gtest/include/gtest/gtest.h"
26 #include "third_party/webrtc/modules/desktop_capture/desktop_capturer.h"
27 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
28 #include "ui/views/widget/widget.h"
29 
30 #if defined(USE_AURA)
31 #include "ui/aura/window.h"
32 #include "ui/aura/window_tree_host.h"
33 #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
34 #endif
35 
36 using content::DesktopMediaID;
37 using testing::_;
38 using testing::DoAll;
39 
40 namespace {
41 
42 // Aura window capture unit tests are not stable. crbug.com/602494 and
43 // crbug.com/603823.
44 // #define ENABLE_AURA_WINDOW_TESTS
45 
46 static const int kDefaultWindowCount = 2;
47 #if defined(ENABLE_AURA_WINDOW_TESTS)
48 static const int kDefaultAuraCount = 1;
49 #else
50 static const int kDefaultAuraCount = 0;
51 #endif
52 
53 class MockObserver : public DesktopMediaListObserver {
54  public:
55   MOCK_METHOD2(OnSourceAdded, void(DesktopMediaList* list, int index));
56   MOCK_METHOD2(OnSourceRemoved, void(DesktopMediaList* list, int index));
57   MOCK_METHOD3(OnSourceMoved,
58                void(DesktopMediaList* list, int old_index, int new_index));
59   MOCK_METHOD2(OnSourceNameChanged, void(DesktopMediaList* list, int index));
60   MOCK_METHOD2(OnSourceThumbnailChanged,
61                void(DesktopMediaList* list, int index));
62   MOCK_METHOD1(OnAllSourcesFound, void(DesktopMediaList* list));
63 };
64 
65 class FakeScreenCapturer : public webrtc::DesktopCapturer {
66  public:
FakeScreenCapturer()67   FakeScreenCapturer() {}
~FakeScreenCapturer()68   ~FakeScreenCapturer() override {}
69 
70   // webrtc::ScreenCapturer implementation.
Start(Callback * callback)71   void Start(Callback* callback) override { callback_ = callback; }
72 
CaptureFrame()73   void CaptureFrame() override {
74     DCHECK(callback_);
75     std::unique_ptr<webrtc::DesktopFrame> frame(
76         new webrtc::BasicDesktopFrame(webrtc::DesktopSize(10, 10)));
77     callback_->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS,
78                                std::move(frame));
79   }
80 
GetSourceList(SourceList * screens)81   bool GetSourceList(SourceList* screens) override {
82     screens->push_back({0});
83     return true;
84   }
85 
SelectSource(SourceId id)86   bool SelectSource(SourceId id) override {
87     EXPECT_EQ(0, id);
88     return true;
89   }
90 
91  protected:
92   Callback* callback_;
93 
94   DISALLOW_COPY_AND_ASSIGN(FakeScreenCapturer);
95 };
96 
97 class FakeWindowCapturer : public webrtc::DesktopCapturer {
98  public:
FakeWindowCapturer()99   FakeWindowCapturer() : callback_(nullptr) {}
~FakeWindowCapturer()100   ~FakeWindowCapturer() override {}
101 
SetWindowList(const SourceList & list)102   void SetWindowList(const SourceList& list) {
103     base::AutoLock lock(window_list_lock_);
104     window_list_ = list;
105   }
106 
107   // Sets |value| thats going to be used to memset() content of the frames
108   // generated for |window_id|. By default generated frames are set to zeros.
SetNextFrameValue(SourceId window_id,int8_t value)109   void SetNextFrameValue(SourceId window_id, int8_t value) {
110     base::AutoLock lock(frame_values_lock_);
111     frame_values_[window_id] = value;
112   }
113 
114   // webrtc::WindowCapturer implementation.
Start(Callback * callback)115   void Start(Callback* callback) override { callback_ = callback; }
116 
CaptureFrame()117   void CaptureFrame() override {
118     DCHECK(callback_);
119 
120     base::AutoLock lock(frame_values_lock_);
121 
122     auto it = frame_values_.find(selected_window_id_);
123     int8_t value = (it != frame_values_.end()) ? it->second : 0;
124     std::unique_ptr<webrtc::DesktopFrame> frame(
125         new webrtc::BasicDesktopFrame(webrtc::DesktopSize(10, 10)));
126     memset(frame->data(), value, frame->stride() * frame->size().height());
127     callback_->OnCaptureResult(webrtc::DesktopCapturer::Result::SUCCESS,
128                                std::move(frame));
129   }
130 
GetSourceList(SourceList * windows)131   bool GetSourceList(SourceList* windows) override {
132     base::AutoLock lock(window_list_lock_);
133     *windows = window_list_;
134     return true;
135   }
136 
SelectSource(SourceId id)137   bool SelectSource(SourceId id) override {
138     selected_window_id_ = id;
139     return true;
140   }
141 
FocusOnSelectedSource()142   bool FocusOnSelectedSource() override { return true; }
143 
144  private:
145   Callback* callback_;
146   SourceList window_list_;
147   base::Lock window_list_lock_;
148 
149   SourceId selected_window_id_;
150 
151   // Frames to be captured per window.
152   std::map<SourceId, int8_t> frame_values_;
153   base::Lock frame_values_lock_;
154 
155   DISALLOW_COPY_AND_ASSIGN(FakeWindowCapturer);
156 };
157 
158 }  // namespace
159 
ACTION_P2(CheckListSize,model,expected_list_size)160 ACTION_P2(CheckListSize, model, expected_list_size) {
161   EXPECT_EQ(expected_list_size, model->GetSourceCount());
162 }
163 
ACTION_P2(QuitRunLoop,task_runner,run_loop)164 ACTION_P2(QuitRunLoop, task_runner, run_loop) {
165   task_runner->PostTask(FROM_HERE, run_loop->QuitWhenIdleClosure());
166 }
167 
168 class NativeDesktopMediaListTest : public ChromeViewsTestBase {
169  public:
170   NativeDesktopMediaListTest() = default;
171 
TearDown()172   void TearDown() override {
173     for (size_t i = 0; i < desktop_widgets_.size(); i++)
174       desktop_widgets_[i].reset();
175 
176     ChromeViewsTestBase::TearDown();
177   }
178 
AddNativeWindow(int id)179   void AddNativeWindow(int id) {
180     webrtc::DesktopCapturer::Source window;
181     window.id = id;
182     window.title = "Test window";
183     window_list_.push_back(window);
184   }
185 
186 #if defined(USE_AURA)
CreateDesktopWidget()187   std::unique_ptr<views::Widget> CreateDesktopWidget() {
188     std::unique_ptr<views::Widget> widget(new views::Widget);
189     views::Widget::InitParams params;
190     params.type = views::Widget::InitParams::TYPE_WINDOW_FRAMELESS;
191     params.accept_events = false;
192     params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
193     params.native_widget = new views::DesktopNativeWidgetAura(widget.get());
194     params.bounds = gfx::Rect(0, 0, 20, 20);
195     widget->Init(std::move(params));
196     widget->Show();
197     return widget;
198   }
199 
AddAuraWindow()200   void AddAuraWindow() {
201     webrtc::DesktopCapturer::Source window;
202     window.title = "Test window";
203 
204     // Create a aura native widow through a widget.
205     desktop_widgets_.push_back(CreateDesktopWidget());
206     aura::WindowTreeHost* const host =
207         desktop_widgets_.back()->GetNativeWindow()->GetHost();
208     aura::Window* const aura_window = host->window();
209 
210     // Get the native window's id.
211     gfx::AcceleratedWidget widget = host->GetAcceleratedWidget();
212 #if defined(OS_WIN)
213     window.id = reinterpret_cast<DesktopMediaID::Id>(widget);
214 #else
215     window.id = widget;
216 #endif
217 
218     // Get the aura window's id.
219     DesktopMediaID aura_id = DesktopMediaID::RegisterNativeWindow(
220         DesktopMediaID::TYPE_WINDOW, aura_window);
221     native_aura_id_map_[window.id] = aura_id.window_id;
222 
223     window_list_.push_back(window);
224   }
225 
RemoveAuraWindow(int index)226   void RemoveAuraWindow(int index) {
227     DCHECK_LT(index, static_cast<int>(desktop_widgets_.size()));
228 
229     // Get the native window's id.
230     aura::Window* aura_window = desktop_widgets_[index]->GetNativeWindow();
231     gfx::AcceleratedWidget widget =
232         aura_window->GetHost()->GetAcceleratedWidget();
233 #if defined(OS_WIN)
234     int native_id = reinterpret_cast<DesktopMediaID::Id>(widget);
235 #else
236     int native_id = widget;
237 #endif
238     // Remove the widget and associated aura window.
239     desktop_widgets_.erase(desktop_widgets_.begin() + index);
240     // Remove the aura window from the window list.
241     size_t i;
242     for (i = 0; i < window_list_.size(); i++) {
243       if (window_list_[i].id == native_id)
244         break;
245     }
246     DCHECK_LT(i, window_list_.size());
247     window_list_.erase(window_list_.begin() + i);
248     native_aura_id_map_.erase(native_id);
249   }
250 
251 #endif  // defined(USE_AURA)
252 
AddWindowsAndVerify(bool has_view_dialog)253   void AddWindowsAndVerify(bool has_view_dialog) {
254     window_capturer_ = new FakeWindowCapturer();
255     model_ = std::make_unique<NativeDesktopMediaList>(
256         DesktopMediaID::TYPE_WINDOW, base::WrapUnique(window_capturer_));
257 
258     // Set update period to reduce the time it takes to run tests.
259     model_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(20));
260 
261     // Set up widows.
262     size_t aura_window_first_index = kDefaultWindowCount - kDefaultAuraCount;
263     for (size_t i = 0; i < kDefaultWindowCount; ++i) {
264       if (i < aura_window_first_index) {
265         AddNativeWindow(i);
266       } else {
267 #if defined(USE_AURA)
268         AddAuraWindow();
269 #endif
270       }
271     }
272 
273     if (window_capturer_)
274       window_capturer_->SetWindowList(window_list_);
275 
276     size_t window_count = kDefaultWindowCount;
277 
278     // Set view dialog window ID as the first window id.
279     if (has_view_dialog) {
280       DesktopMediaID dialog_window_id(DesktopMediaID::TYPE_WINDOW,
281                                       window_list_[0].id);
282       model_->SetViewDialogWindowId(dialog_window_id);
283       window_count--;
284       aura_window_first_index--;
285     }
286 
287     base::RunLoop run_loop;
288 
289     {
290       testing::InSequence dummy;
291       for (size_t i = 0; i < window_count; ++i) {
292         EXPECT_CALL(observer_, OnSourceAdded(model_.get(), i))
293             .WillOnce(CheckListSize(model_.get(), static_cast<int>(i + 1)));
294       }
295       for (size_t i = 0; i < window_count - 1; ++i) {
296         EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), i));
297       }
298       EXPECT_CALL(observer_,
299                   OnSourceThumbnailChanged(model_.get(), window_count - 1))
300           .WillOnce(
301               QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
302     }
303     model_->StartUpdating(&observer_);
304     run_loop.Run();
305 
306     for (size_t i = 0; i < window_count; ++i) {
307       EXPECT_EQ(model_->GetSource(i).id.type, DesktopMediaID::TYPE_WINDOW);
308       EXPECT_EQ(model_->GetSource(i).name, base::UTF8ToUTF16("Test window"));
309       int index = has_view_dialog ? i + 1 : i;
310       int native_id = window_list_[index].id;
311       EXPECT_EQ(model_->GetSource(i).id.id, native_id);
312 #if defined(USE_AURA)
313       if (i >= aura_window_first_index)
314         EXPECT_EQ(model_->GetSource(i).id.window_id,
315                   native_aura_id_map_[native_id]);
316 #endif
317     }
318     testing::Mock::VerifyAndClearExpectations(&observer_);
319   }
320 
321  protected:
322   // Must be listed before |model_|, so it's destroyed last.
323   MockObserver observer_;
324 
325   // Owned by |model_|;
326   FakeWindowCapturer* window_capturer_;
327 
328   webrtc::DesktopCapturer::SourceList window_list_;
329   std::vector<std::unique_ptr<views::Widget>> desktop_widgets_;
330   std::map<DesktopMediaID::Id, DesktopMediaID::Id> native_aura_id_map_;
331   std::unique_ptr<NativeDesktopMediaList> model_;
332 
333   DISALLOW_COPY_AND_ASSIGN(NativeDesktopMediaListTest);
334 };
335 
TEST_F(NativeDesktopMediaListTest,Windows)336 TEST_F(NativeDesktopMediaListTest, Windows) {
337   AddWindowsAndVerify(false);
338 }
339 
TEST_F(NativeDesktopMediaListTest,ScreenOnly)340 TEST_F(NativeDesktopMediaListTest, ScreenOnly) {
341   model_ = std::make_unique<NativeDesktopMediaList>(
342       DesktopMediaID::TYPE_SCREEN, std::make_unique<FakeScreenCapturer>());
343 
344   // Set update period to reduce the time it takes to run tests.
345   model_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(20));
346 
347   base::RunLoop run_loop;
348 
349   {
350     testing::InSequence dummy;
351     EXPECT_CALL(observer_, OnSourceAdded(model_.get(), 0))
352         .WillOnce(CheckListSize(model_.get(), 1));
353     EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), 0))
354         .WillOnce(QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
355   }
356   model_->StartUpdating(&observer_);
357   run_loop.Run();
358 
359   EXPECT_EQ(model_->GetSource(0).id.type, DesktopMediaID::TYPE_SCREEN);
360   EXPECT_EQ(model_->GetSource(0).id.id, 0);
361 }
362 
363 // Verifies that the window specified with SetViewDialogWindowId() is filtered
364 // from the results.
TEST_F(NativeDesktopMediaListTest,WindowFiltering)365 TEST_F(NativeDesktopMediaListTest, WindowFiltering) {
366   AddWindowsAndVerify(true);
367 }
368 
TEST_F(NativeDesktopMediaListTest,AddNativeWindow)369 TEST_F(NativeDesktopMediaListTest, AddNativeWindow) {
370   AddWindowsAndVerify(false);
371 
372   base::RunLoop run_loop;
373 
374   const int index = kDefaultWindowCount;
375   EXPECT_CALL(observer_, OnSourceAdded(model_.get(), index))
376       .WillOnce(
377           DoAll(CheckListSize(model_.get(), kDefaultWindowCount + 1),
378                 QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
379 
380   AddNativeWindow(index);
381   window_capturer_->SetWindowList(window_list_);
382 
383   run_loop.Run();
384 
385   EXPECT_EQ(model_->GetSource(index).id.type, DesktopMediaID::TYPE_WINDOW);
386   EXPECT_EQ(model_->GetSource(index).id.id, index);
387 }
388 
389 #if defined(ENABLE_AURA_WINDOW_TESTS)
TEST_F(NativeDesktopMediaListTest,AddAuraWindow)390 TEST_F(NativeDesktopMediaListTest, AddAuraWindow) {
391   AddWindowsAndVerify(false);
392 
393   base::RunLoop run_loop;
394 
395   const int index = kDefaultWindowCount;
396   EXPECT_CALL(observer_, OnSourceAdded(model_.get(), index))
397       .WillOnce(
398           DoAll(CheckListSize(model_.get(), kDefaultWindowCount + 1),
399                 QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
400 
401   AddAuraWindow();
402   window_capturer_->SetWindowList(window_list_);
403 
404   run_loop.Run();
405 
406   int native_id = window_list_.back().id;
407   EXPECT_EQ(model_->GetSource(index).id.type, DesktopMediaID::TYPE_WINDOW);
408   EXPECT_EQ(model_->GetSource(index).id.id, native_id);
409   EXPECT_EQ(model_->GetSource(index).id.window_id,
410             native_aura_id_map_[native_id]);
411 }
412 #endif  // defined(ENABLE_AURA_WINDOW_TESTS)
413 
TEST_F(NativeDesktopMediaListTest,RemoveNativeWindow)414 TEST_F(NativeDesktopMediaListTest, RemoveNativeWindow) {
415   AddWindowsAndVerify(false);
416 
417   base::RunLoop run_loop;
418 
419   EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), 0))
420       .WillOnce(
421           DoAll(CheckListSize(model_.get(), kDefaultWindowCount - 1),
422                 QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
423 
424   window_list_.erase(window_list_.begin());
425   window_capturer_->SetWindowList(window_list_);
426 
427   run_loop.Run();
428 }
429 
430 #if defined(ENABLE_AURA_WINDOW_TESTS)
TEST_F(NativeDesktopMediaListTest,RemoveAuraWindow)431 TEST_F(NativeDesktopMediaListTest, RemoveAuraWindow) {
432   AddWindowsAndVerify(false);
433 
434   base::RunLoop run_loop;
435 
436   int aura_window_start_index = kDefaultWindowCount - kDefaultAuraCount;
437   EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), aura_window_start_index))
438       .WillOnce(
439           DoAll(CheckListSize(model_.get(), kDefaultWindowCount - 1),
440                 QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
441 
442   RemoveAuraWindow(0);
443   window_capturer_->SetWindowList(window_list_);
444 
445   run_loop.Run();
446 }
447 #endif  // defined(ENABLE_AURA_WINDOW_TESTS)
448 
TEST_F(NativeDesktopMediaListTest,RemoveAllWindows)449 TEST_F(NativeDesktopMediaListTest, RemoveAllWindows) {
450   AddWindowsAndVerify(false);
451 
452   base::RunLoop run_loop;
453 
454   testing::InSequence seq;
455   for (int i = 0; i < kDefaultWindowCount - 1; i++) {
456     EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), 0))
457         .WillOnce(CheckListSize(model_.get(), kDefaultWindowCount - i - 1));
458   }
459   EXPECT_CALL(observer_, OnSourceRemoved(model_.get(), 0))
460       .WillOnce(
461           DoAll(CheckListSize(model_.get(), 0),
462                 QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
463 
464   window_list_.clear();
465   window_capturer_->SetWindowList(window_list_);
466 
467   run_loop.Run();
468 }
469 
TEST_F(NativeDesktopMediaListTest,UpdateTitle)470 TEST_F(NativeDesktopMediaListTest, UpdateTitle) {
471   AddWindowsAndVerify(false);
472 
473   base::RunLoop run_loop;
474 
475   EXPECT_CALL(observer_, OnSourceNameChanged(model_.get(), 0))
476       .WillOnce(QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
477 
478   const std::string kTestTitle = "New Title";
479   window_list_[0].title = kTestTitle;
480   window_capturer_->SetWindowList(window_list_);
481 
482   run_loop.Run();
483 
484   EXPECT_EQ(model_->GetSource(0).name, base::UTF8ToUTF16(kTestTitle));
485 }
486 
TEST_F(NativeDesktopMediaListTest,UpdateThumbnail)487 TEST_F(NativeDesktopMediaListTest, UpdateThumbnail) {
488   AddWindowsAndVerify(false);
489 
490   // Aura windows' thumbnails may unpredictably change over time.
491   for (size_t i = kDefaultWindowCount - kDefaultAuraCount;
492        i < kDefaultWindowCount; ++i) {
493     EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), i))
494         .Times(testing::AnyNumber());
495   }
496 
497   base::RunLoop run_loop;
498 
499   EXPECT_CALL(observer_, OnSourceThumbnailChanged(model_.get(), 0))
500       .WillOnce(QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop));
501 
502   // Update frame for the window and verify that we get notification about it.
503   window_capturer_->SetNextFrameValue(0, 10);
504 
505   run_loop.Run();
506 }
507 
TEST_F(NativeDesktopMediaListTest,MoveWindow)508 TEST_F(NativeDesktopMediaListTest, MoveWindow) {
509   AddWindowsAndVerify(false);
510 
511   base::RunLoop run_loop;
512 
513   EXPECT_CALL(observer_, OnSourceMoved(model_.get(), 1, 0))
514       .WillOnce(
515           DoAll(CheckListSize(model_.get(), kDefaultWindowCount),
516                 QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
517 
518   std::swap(window_list_[0], window_list_[1]);
519   window_capturer_->SetWindowList(window_list_);
520 
521   run_loop.Run();
522 }
523 
524 // This test verifies that webrtc::DesktopCapturer::CaptureFrame() is not
525 // called when the thumbnail size is empty.
TEST_F(NativeDesktopMediaListTest,EmptyThumbnail)526 TEST_F(NativeDesktopMediaListTest, EmptyThumbnail) {
527   window_capturer_ = new FakeWindowCapturer();
528   model_ = std::make_unique<NativeDesktopMediaList>(
529       DesktopMediaID::TYPE_WINDOW, base::WrapUnique(window_capturer_));
530   model_->SetThumbnailSize(gfx::Size());
531 
532   // Set update period to reduce the time it takes to run tests.
533   model_->SetUpdatePeriod(base::TimeDelta::FromMilliseconds(20));
534 
535   base::RunLoop run_loop;
536 
537   EXPECT_CALL(observer_, OnSourceAdded(model_.get(), 0))
538       .WillOnce(
539           DoAll(CheckListSize(model_.get(), 1),
540                 QuitRunLoop(base::ThreadTaskRunnerHandle::Get(), &run_loop)));
541   // Called upon webrtc::DesktopCapturer::CaptureFrame() call.
542   ON_CALL(observer_, OnSourceThumbnailChanged(_, _))
543       .WillByDefault(testing::InvokeWithoutArgs([]() { NOTREACHED(); }));
544 
545   model_->StartUpdating(&observer_);
546 
547   AddNativeWindow(0);
548   window_capturer_->SetWindowList(window_list_);
549 
550   run_loop.Run();
551 
552   EXPECT_EQ(model_->GetSource(0).id.type, DesktopMediaID::TYPE_WINDOW);
553   EXPECT_EQ(model_->GetSource(0).id.id, 0);
554   EXPECT_EQ(model_->GetSource(0).thumbnail.size(), gfx::Size());
555 }
556