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