1 // Copyright 2018 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/desktop_capture_access_handler.h"
6
7 #include <memory>
8 #include <string>
9 #include <utility>
10
11 #include "base/bind.h"
12 #include "base/macros.h"
13 #include "base/run_loop.h"
14 #include "build/build_config.h"
15 #include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
16 #include "chrome/common/chrome_switches.h"
17 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
18 #include "content/public/browser/browser_context.h"
19 #include "content/public/browser/desktop_media_id.h"
20 #include "content/public/browser/desktop_streams_registry.h"
21 #include "content/public/browser/notification_service.h"
22 #include "content/public/browser/notification_types.h"
23 #include "content/public/browser/render_frame_host.h"
24 #include "content/public/browser/render_process_host.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/common/content_switches.h"
27 #include "extensions/common/extension.h"
28 #include "extensions/common/extension_builder.h"
29 #include "testing/gtest/include/gtest/gtest.h"
30 #include "third_party/blink/public/common/mediastream/media_stream_request.h"
31 #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h"
32
33 #if defined(OS_CHROMEOS)
34 #include "chrome/browser/chromeos/policy/dlp/mock_dlp_content_manager.h"
35 #include "ui/aura/window.h"
36 #endif
37
38 #if defined(OS_MAC)
39 #include "base/test/scoped_feature_list.h"
40 #include "chrome/common/chrome_features.h"
41 #endif
42
43 class DesktopCaptureAccessHandlerTest : public ChromeRenderViewHostTestHarness {
44 public:
DesktopCaptureAccessHandlerTest()45 DesktopCaptureAccessHandlerTest() {}
~DesktopCaptureAccessHandlerTest()46 ~DesktopCaptureAccessHandlerTest() override {}
47
SetUp()48 void SetUp() override {
49 ChromeRenderViewHostTestHarness::SetUp();
50 auto picker_factory = std::make_unique<FakeDesktopMediaPickerFactory>();
51 picker_factory_ = picker_factory.get();
52 access_handler_ = std::make_unique<DesktopCaptureAccessHandler>(
53 std::move(picker_factory));
54 }
55
ProcessGenerateStreamRequest(const std::string & requested_video_device_id,const GURL & origin,const extensions::Extension * extension,blink::mojom::MediaStreamRequestResult * request_result,blink::MediaStreamDevices * devices_result)56 void ProcessGenerateStreamRequest(
57 const std::string& requested_video_device_id,
58 const GURL& origin,
59 const extensions::Extension* extension,
60 blink::mojom::MediaStreamRequestResult* request_result,
61 blink::MediaStreamDevices* devices_result) {
62 #if defined(OS_MAC)
63 base::test::ScopedFeatureList scoped_feature_list;
64 scoped_feature_list.InitAndDisableFeature(
65 features::kMacSystemScreenCapturePermissionCheck);
66 #endif
67 content::MediaStreamRequest request(
68 web_contents()->GetMainFrame()->GetProcess()->GetID(),
69 web_contents()->GetMainFrame()->GetRoutingID(), /*page_request_id=*/0,
70 origin, /*user_gesture=*/false, blink::MEDIA_GENERATE_STREAM,
71 /*requested_audio_device_id=*/std::string(), requested_video_device_id,
72 blink::mojom::MediaStreamType::NO_SERVICE,
73 blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
74 /*disable_local_echo=*/false,
75 /*request_pan_tilt_zoom_permission=*/false);
76 base::RunLoop wait_loop;
77 content::MediaResponseCallback callback = base::BindOnce(
78 [](base::RunLoop* wait_loop,
79 blink::mojom::MediaStreamRequestResult* request_result,
80 blink::MediaStreamDevices* devices_result,
81 const blink::MediaStreamDevices& devices,
82 blink::mojom::MediaStreamRequestResult result,
83 std::unique_ptr<content::MediaStreamUI> ui) {
84 *request_result = result;
85 *devices_result = devices;
86 wait_loop->Quit();
87 },
88 &wait_loop, request_result, devices_result);
89 access_handler_->HandleRequest(web_contents(), request, std::move(callback),
90 extension);
91 wait_loop.Run();
92 }
93
ProcessDeviceUpdateRequest(const content::DesktopMediaID & fake_desktop_media_id_response,blink::mojom::MediaStreamRequestResult * request_result,blink::MediaStreamDevices * devices_result,blink::MediaStreamRequestType request_type,bool request_audio)94 void ProcessDeviceUpdateRequest(
95 const content::DesktopMediaID& fake_desktop_media_id_response,
96 blink::mojom::MediaStreamRequestResult* request_result,
97 blink::MediaStreamDevices* devices_result,
98 blink::MediaStreamRequestType request_type,
99 bool request_audio) {
100 FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
101 {false /* expect_screens */, false /* expect_windows*/,
102 true /* expect_tabs */, request_audio /* expect_audio */,
103 fake_desktop_media_id_response /* selected_source */}};
104 picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
105 blink::mojom::MediaStreamType audio_type =
106 request_audio ? blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE
107 : blink::mojom::MediaStreamType::NO_SERVICE;
108 content::MediaStreamRequest request(
109 0, 0, 0, GURL("http://origin/"), false, request_type, std::string(),
110 std::string(), audio_type,
111 blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
112 /*disable_local_echo=*/false,
113 /*request_pan_tilt_zoom_permission=*/false);
114
115 base::RunLoop wait_loop;
116 content::MediaResponseCallback callback = base::BindOnce(
117 [](base::RunLoop* wait_loop,
118 blink::mojom::MediaStreamRequestResult* request_result,
119 blink::MediaStreamDevices* devices_result,
120 const blink::MediaStreamDevices& devices,
121 blink::mojom::MediaStreamRequestResult result,
122 std::unique_ptr<content::MediaStreamUI> ui) {
123 *request_result = result;
124 *devices_result = devices;
125 wait_loop->Quit();
126 },
127 &wait_loop, request_result, devices_result);
128 access_handler_->HandleRequest(web_contents(), request, std::move(callback),
129 nullptr /* extension */);
130 wait_loop.Run();
131 EXPECT_TRUE(test_flags[0].picker_created);
132
133 access_handler_.reset();
134 EXPECT_TRUE(test_flags[0].picker_deleted);
135 }
136
NotifyWebContentsDestroyed()137 void NotifyWebContentsDestroyed() {
138 access_handler_->Observe(
139 content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
140 content::Source<content::WebContents>(web_contents()),
141 content::NotificationDetails());
142 }
143
GetRequestQueues()144 const DesktopCaptureAccessHandler::RequestsQueues& GetRequestQueues() {
145 return access_handler_->pending_requests_;
146 }
147
148 #if defined(OS_CHROMEOS)
SetPrimaryRootWindow(aura::Window * window)149 void SetPrimaryRootWindow(aura::Window* window) {
150 access_handler_->primary_root_window_for_testing_ = window;
151 }
152 #endif
153
154 protected:
155 FakeDesktopMediaPickerFactory* picker_factory_;
156 std::unique_ptr<DesktopCaptureAccessHandler> access_handler_;
157 };
158
TEST_F(DesktopCaptureAccessHandlerTest,ChangeSourceWithoutAudioRequestPermissionGiven)159 TEST_F(DesktopCaptureAccessHandlerTest,
160 ChangeSourceWithoutAudioRequestPermissionGiven) {
161 blink::mojom::MediaStreamRequestResult result;
162 blink::MediaStreamDevices devices;
163 ProcessDeviceUpdateRequest(
164 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
165 content::DesktopMediaID::kFakeId),
166 &result, &devices, blink::MEDIA_DEVICE_UPDATE, false /*request_audio*/);
167 EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
168 EXPECT_EQ(1u, devices.size());
169 EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
170 devices[0].type);
171 }
172
TEST_F(DesktopCaptureAccessHandlerTest,ChangeSourceWithAudioRequestPermissionGiven)173 TEST_F(DesktopCaptureAccessHandlerTest,
174 ChangeSourceWithAudioRequestPermissionGiven) {
175 blink::mojom::MediaStreamRequestResult result;
176 blink::MediaStreamDevices devices;
177 ProcessDeviceUpdateRequest(
178 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
179 content::DesktopMediaID::kFakeId,
180 true /* audio_share */),
181 &result, &devices, blink::MEDIA_DEVICE_UPDATE, true /* request_audio */);
182 EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
183 EXPECT_EQ(2u, devices.size());
184 EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
185 devices[0].type);
186 EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_AUDIO_CAPTURE,
187 devices[1].type);
188 }
189
TEST_F(DesktopCaptureAccessHandlerTest,ChangeSourcePermissionDenied)190 TEST_F(DesktopCaptureAccessHandlerTest, ChangeSourcePermissionDenied) {
191 blink::mojom::MediaStreamRequestResult result;
192 blink::MediaStreamDevices devices;
193 ProcessDeviceUpdateRequest(content::DesktopMediaID(), &result, &devices,
194 blink::MEDIA_DEVICE_UPDATE,
195 false /*request audio*/);
196 EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, result);
197 EXPECT_EQ(0u, devices.size());
198 }
199
TEST_F(DesktopCaptureAccessHandlerTest,ChangeSourceUpdateMediaRequestStateWithClosing)200 TEST_F(DesktopCaptureAccessHandlerTest,
201 ChangeSourceUpdateMediaRequestStateWithClosing) {
202 const int render_process_id = 0;
203 const int render_frame_id = 0;
204 const int page_request_id = 0;
205 const blink::mojom::MediaStreamType stream_type =
206 blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE;
207 FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
208 {false /* expect_screens */, false /* expect_windows*/,
209 true /* expect_tabs */, false /* expect_audio */,
210 content::DesktopMediaID(), true /* cancelled */}};
211 picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
212 content::MediaStreamRequest request(
213 render_process_id, render_frame_id, page_request_id,
214 GURL("http://origin/"), false, blink::MEDIA_DEVICE_UPDATE, std::string(),
215 std::string(), blink::mojom::MediaStreamType::NO_SERVICE, stream_type,
216 /*disable_local_echo=*/false, /*request_pan_tilt_zoom_permission=*/false);
217 content::MediaResponseCallback callback;
218 access_handler_->HandleRequest(web_contents(), request, std::move(callback),
219 nullptr /* extension */);
220 EXPECT_TRUE(test_flags[0].picker_created);
221 EXPECT_EQ(1u, GetRequestQueues().size());
222 auto queue_it = GetRequestQueues().find(web_contents());
223 EXPECT_TRUE(queue_it != GetRequestQueues().end());
224 EXPECT_EQ(1u, queue_it->second.size());
225
226 access_handler_->UpdateMediaRequestState(
227 render_process_id, render_frame_id, page_request_id, stream_type,
228 content::MEDIA_REQUEST_STATE_CLOSING);
229 EXPECT_EQ(1u, GetRequestQueues().size());
230 queue_it = GetRequestQueues().find(web_contents());
231 EXPECT_TRUE(queue_it != GetRequestQueues().end());
232 EXPECT_EQ(0u, queue_it->second.size());
233 EXPECT_TRUE(test_flags[0].picker_deleted);
234 access_handler_.reset();
235 }
236
TEST_F(DesktopCaptureAccessHandlerTest,ChangeSourceWebContentsDestroyed)237 TEST_F(DesktopCaptureAccessHandlerTest, ChangeSourceWebContentsDestroyed) {
238 FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
239 {false /* expect_screens */, false /* expect_windows*/,
240 true /* expect_tabs */, false /* expect_audio */,
241 content::DesktopMediaID(), true /* cancelled */}};
242 picker_factory_->SetTestFlags(test_flags, base::size(test_flags));
243 content::MediaStreamRequest request(
244 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_DEVICE_UPDATE,
245 std::string(), std::string(), blink::mojom::MediaStreamType::NO_SERVICE,
246 blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
247 /*disable_local_echo=*/false, /*request_pan_tilt_zoom_permission=*/false);
248 content::MediaResponseCallback callback;
249 access_handler_->HandleRequest(web_contents(), request, std::move(callback),
250 nullptr /* extension */);
251 EXPECT_TRUE(test_flags[0].picker_created);
252 EXPECT_EQ(1u, GetRequestQueues().size());
253 auto queue_it = GetRequestQueues().find(web_contents());
254 EXPECT_TRUE(queue_it != GetRequestQueues().end());
255 EXPECT_EQ(1u, queue_it->second.size());
256
257 NotifyWebContentsDestroyed();
258 EXPECT_EQ(0u, GetRequestQueues().size());
259 access_handler_.reset();
260 }
261
TEST_F(DesktopCaptureAccessHandlerTest,ChangeSourceMultipleRequests)262 TEST_F(DesktopCaptureAccessHandlerTest, ChangeSourceMultipleRequests) {
263 FakeDesktopMediaPickerFactory::TestFlags test_flags[] = {
264 {false /* expect_screens */, false /* expect_windows*/,
265 true /* expect_tabs */, false /* expect_audio */,
266 content::DesktopMediaID(
267 content::DesktopMediaID::TYPE_SCREEN,
268 content::DesktopMediaID::kFakeId) /* selected_source */},
269 {false /* expect_screens */, false /* expect_windows*/,
270 true /* expect_tabs */, false /* expect_audio */,
271 content::DesktopMediaID(
272 content::DesktopMediaID::TYPE_WINDOW,
273 content::DesktopMediaID::kNullId) /* selected_source */}};
274 const size_t kTestFlagCount = 2;
275 picker_factory_->SetTestFlags(test_flags, kTestFlagCount);
276
277 blink::mojom::MediaStreamRequestResult result;
278 blink::MediaStreamDevices devices;
279 base::RunLoop wait_loop[kTestFlagCount];
280 for (size_t i = 0; i < kTestFlagCount; ++i) {
281 content::MediaStreamRequest request(
282 0, 0, 0, GURL("http://origin/"), false, blink::MEDIA_DEVICE_UPDATE,
283 std::string(), std::string(), blink::mojom::MediaStreamType::NO_SERVICE,
284 blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
285 /*disable_local_echo=*/false,
286 /*request_pan_tilt_zoom_permission=*/false);
287 content::MediaResponseCallback callback = base::BindOnce(
288 [](base::RunLoop* wait_loop,
289 blink::mojom::MediaStreamRequestResult* request_result,
290 blink::MediaStreamDevices* devices_result,
291 const blink::MediaStreamDevices& devices,
292 blink::mojom::MediaStreamRequestResult result,
293 std::unique_ptr<content::MediaStreamUI> ui) {
294 *request_result = result;
295 *devices_result = devices;
296 wait_loop->Quit();
297 },
298 &wait_loop[i], &result, &devices);
299 access_handler_->HandleRequest(web_contents(), request, std::move(callback),
300 nullptr /* extension */);
301 }
302 wait_loop[0].Run();
303 EXPECT_TRUE(test_flags[0].picker_created);
304 EXPECT_TRUE(test_flags[0].picker_deleted);
305 EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
306 EXPECT_EQ(1u, devices.size());
307 EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
308 devices[0].type);
309
310 blink::MediaStreamDevice first_device = devices[0];
311 EXPECT_TRUE(test_flags[1].picker_created);
312 EXPECT_FALSE(test_flags[1].picker_deleted);
313 wait_loop[1].Run();
314 EXPECT_TRUE(test_flags[1].picker_deleted);
315 EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
316 EXPECT_EQ(1u, devices.size());
317 EXPECT_EQ(blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE,
318 devices[0].type);
319 EXPECT_FALSE(devices[0].IsSameDevice(first_device));
320
321 access_handler_.reset();
322 }
323
TEST_F(DesktopCaptureAccessHandlerTest,GenerateStreamSuccess)324 TEST_F(DesktopCaptureAccessHandlerTest, GenerateStreamSuccess) {
325 blink::mojom::MediaStreamRequestResult result;
326 blink::MediaStreamDevices devices;
327 const GURL origin("http://origin/");
328 const std::string id =
329 content::DesktopStreamsRegistry::GetInstance()->RegisterStream(
330 web_contents()->GetMainFrame()->GetProcess()->GetID(),
331 web_contents()->GetMainFrame()->GetRoutingID(),
332 url::Origin::Create(origin),
333 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
334 content::DesktopMediaID::kFakeId),
335 /*extension_name=*/"",
336 content::DesktopStreamRegistryType::kRegistryStreamTypeDesktop);
337
338 ProcessGenerateStreamRequest(id, origin, /*extension=*/nullptr, &result,
339 &devices);
340
341 EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
342 EXPECT_EQ(1u, devices.size());
343 }
344
TEST_F(DesktopCaptureAccessHandlerTest,ScreenCaptureAccessSuccess)345 TEST_F(DesktopCaptureAccessHandlerTest, ScreenCaptureAccessSuccess) {
346 base::CommandLine::ForCurrentProcess()->AppendSwitch(
347 switches::kEnableUserMediaScreenCapturing);
348 base::CommandLine::ForCurrentProcess()->AppendSwitch(
349 switches::kAllowHttpScreenCapture);
350
351 extensions::ExtensionBuilder extensionBuilder("Component Extension");
352 extensionBuilder.SetLocation(extensions::Manifest::COMPONENT);
353 auto extension = extensionBuilder.Build();
354
355 #if defined(OS_CHROMEOS)
356 std::unique_ptr<aura::Window> primary_root_window =
357 std::make_unique<aura::Window>(/*delegate=*/nullptr);
358 primary_root_window->Init(ui::LAYER_NOT_DRAWN);
359 SetPrimaryRootWindow(primary_root_window.get());
360 #endif
361
362 blink::mojom::MediaStreamRequestResult result;
363 blink::MediaStreamDevices devices;
364 const GURL origin("http://origin/");
365
366 ProcessGenerateStreamRequest(/*requested_video_device_id=*/std::string(),
367 origin, extension.get(), &result, &devices);
368
369 EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
370 EXPECT_EQ(1u, devices.size());
371 }
372
373 #if defined(OS_CHROMEOS)
TEST_F(DesktopCaptureAccessHandlerTest,ScreenCaptureAccessDlpRestricted)374 TEST_F(DesktopCaptureAccessHandlerTest, ScreenCaptureAccessDlpRestricted) {
375 // Setup Data Leak Prevention restriction.
376 policy::MockDlpContentManager mock_dlp_content_manager;
377 policy::DlpContentManager::SetDlpContentManagerForTesting(
378 &mock_dlp_content_manager);
379 EXPECT_CALL(mock_dlp_content_manager, IsScreenCaptureRestricted(testing::_))
380 .Times(1)
381 .WillOnce(testing::Return(true));
382
383 base::CommandLine::ForCurrentProcess()->AppendSwitch(
384 switches::kEnableUserMediaScreenCapturing);
385 base::CommandLine::ForCurrentProcess()->AppendSwitch(
386 switches::kAllowHttpScreenCapture);
387
388 extensions::ExtensionBuilder extensionBuilder("Component Extension");
389 extensionBuilder.SetLocation(extensions::Manifest::COMPONENT);
390 auto extension = extensionBuilder.Build();
391
392 std::unique_ptr<aura::Window> primary_root_window =
393 std::make_unique<aura::Window>(/*delegate=*/nullptr);
394 primary_root_window->Init(ui::LAYER_NOT_DRAWN);
395 SetPrimaryRootWindow(primary_root_window.get());
396
397 blink::mojom::MediaStreamRequestResult result;
398 blink::MediaStreamDevices devices;
399 const GURL origin("http://origin/");
400
401 ProcessGenerateStreamRequest(/*requested_video_device_id=*/std::string(),
402 origin, extension.get(), &result, &devices);
403
404 EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, result);
405 EXPECT_EQ(0u, devices.size());
406 policy::DlpContentManager::ResetDlpContentManagerForTesting();
407 }
408
TEST_F(DesktopCaptureAccessHandlerTest,GenerateStreamDlpRestricted)409 TEST_F(DesktopCaptureAccessHandlerTest, GenerateStreamDlpRestricted) {
410 // Setup Data Leak Prevention restriction.
411 policy::MockDlpContentManager mock_dlp_content_manager;
412 policy::DlpContentManager::SetDlpContentManagerForTesting(
413 &mock_dlp_content_manager);
414 EXPECT_CALL(mock_dlp_content_manager, IsScreenCaptureRestricted(testing::_))
415 .Times(1)
416 .WillOnce(testing::Return(true));
417
418 blink::mojom::MediaStreamRequestResult result;
419 blink::MediaStreamDevices devices;
420 const GURL origin("http://origin/");
421 const std::string id =
422 content::DesktopStreamsRegistry::GetInstance()->RegisterStream(
423 web_contents()->GetMainFrame()->GetProcess()->GetID(),
424 web_contents()->GetMainFrame()->GetRoutingID(),
425 url::Origin::Create(origin),
426 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
427 content::DesktopMediaID::kFakeId),
428 /*extension_name=*/"",
429 content::DesktopStreamRegistryType::kRegistryStreamTypeDesktop);
430
431 ProcessGenerateStreamRequest(id, origin, /*extension=*/nullptr, &result,
432 &devices);
433
434 EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, result);
435 EXPECT_EQ(0u, devices.size());
436 policy::DlpContentManager::ResetDlpContentManagerForTesting();
437 }
438 #endif
439