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