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