// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/media/webrtc/display_media_access_handler.h"

#include <array>
#include <memory>
#include <string>
#include <utility>

#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/mock_callback.h"
#include "base/test/with_feature_override.h"
#include "base/types/expected.h"
#include "build/build_config.h"
#include "chrome/browser/media/webrtc/fake_desktop_media_picker_factory.h"
#include "chrome/browser/web_applications/test/web_app_test.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/desktop_media_id.h"
#include "content/public/browser/media_stream_request.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/navigation_simulator.h"
#include "media/audio/audio_features.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/mediastream/media_stream_request.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "url/origin.h"

#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/chromeos/policy/dlp/test/mock_dlp_content_manager.h"
#endif  // BUILDFLAG(IS_CHROMEOS)

#if !BUILDFLAG(IS_ANDROID)
#include "base/test/gmock_expected_support.h"
#include "chrome/browser/ui/web_applications/test/isolated_web_app_test_utils.h"
#include "chrome/browser/web_applications/isolated_web_apps/isolated_web_app_url_info.h"
#include "chrome/browser/web_applications/isolated_web_apps/iwa_permissions_policy_cache.h"
#include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "components/webapps/isolated_web_apps/types/iwa_origin.h"
#include "content/public/test/web_contents_tester.h"
#include "net/http/http_response_headers.h"
#include "services/data_decoder/public/cpp/test_support/in_process_data_decoder.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
#include "third_party/blink/public/common/permissions_policy/policy_helper_public.h"
#endif  // !BUILDFLAG(IS_ANDROID)

#if BUILDFLAG(IS_WIN)
#include "base/process/process.h"
#include "base/test/bind.h"
#include "base/win/message_window.h"
#include "media/audio/application_loopback_device_helper.h"
#endif  // BUILDFLAG(IS_WIN)

class DisplayMediaAccessHandlerTest : public WebAppTest {
 public:
  DisplayMediaAccessHandlerTest() = default;
  ~DisplayMediaAccessHandlerTest() override = default;

  void SetUp() override {
    WebAppTest::SetUp();
    std::unique_ptr<content::NavigationSimulator> navigation =
        content::NavigationSimulator::CreateBrowserInitiated(
            GURL("http://origin/"), web_contents());
    navigation->Commit();

    auto picker_factory = std::make_unique<FakeDesktopMediaPickerFactory>();
    picker_factory_ = picker_factory.get();
    access_handler_ = std::make_unique<DisplayMediaAccessHandler>(
        std::move(picker_factory), false /* display_notification */);
  }

  content::WebContentsMediaCaptureId GetWebContentsMediaCaptureId() {
    return content::WebContentsMediaCaptureId(
        web_contents()->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID(),
        web_contents()->GetPrimaryMainFrame()->GetRoutingID());
  }

  FakeDesktopMediaPickerFactory::TestFlags MakePickerTestFlags(
      bool request_audio) {
    return FakeDesktopMediaPickerFactory::TestFlags(
        {.expect_screens = true,
         .expect_windows = true,
         .expect_tabs = true,
         .expect_current_tab = false,
         .expect_audio = request_audio,
         .picker_result =
             content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
                                     content::DesktopMediaID::kFakeId)});
  }

  content::MediaStreamRequest MakeRequest(bool request_audio) {
    return content::MediaStreamRequest(
        web_contents()->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID(),
        web_contents()->GetPrimaryMainFrame()->GetRoutingID(), 0,
        web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin(), false,
        blink::MEDIA_GENERATE_STREAM, /*requested_audio_device_ids=*/{},
        /*requested_video_device_ids=*/{},
        request_audio ? blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE
                      : blink::mojom::MediaStreamType::NO_SERVICE,
        blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
        /*disable_local_echo=*/false,
        /*request_pan_tilt_zoom_permission=*/false,
        /*captured_surface_control_active=*/false);
  }

  content::MediaStreamRequest MakeMediaDeviceUpdateRequest(bool request_audio) {
    content::MediaStreamRequest request =
        MakeRequest(request_audio /* request_audio */);
    request.request_type = blink::MEDIA_DEVICE_UPDATE;
    request.requested_video_device_ids = {
        GetWebContentsMediaCaptureId().ToString()};
    return request;
  }

  content::MediaStreamRequest MakeExcludeSelfBrowserSurfaceRequest(
      bool exclude_self_browser_surface) {
    content::MediaStreamRequest request = MakeRequest(/*request_audio=*/false);
    request.exclude_self_browser_surface = exclude_self_browser_surface;
    return request;
  }

  content::MediaStreamRequest MakeExcludeMonitorTypeSurfacesRequest(
      bool exclude_monitor_type_surfaces) {
    content::MediaStreamRequest request = MakeRequest(/*request_audio=*/false);
    request.exclude_monitor_type_surfaces = exclude_monitor_type_surfaces;
    return request;
  }

  content::MediaResponseCallback MakeCallback(
      base::RunLoop* wait_loop,
      blink::mojom::MediaStreamRequestResult* request_result,
      blink::mojom::StreamDevices& devices_result) {
    return base::BindOnce(
        [](base::RunLoop* wait_loop,
           blink::mojom::MediaStreamRequestResult* request_result,
           blink::mojom::StreamDevices* devices_result,
           const blink::mojom::StreamDevicesSet& stream_devices_set,
           blink::mojom::MediaStreamRequestResult result,
           std::unique_ptr<content::MediaStreamUI> ui) {
          *request_result = result;
          if (result == blink::mojom::MediaStreamRequestResult::OK) {
            ASSERT_EQ(stream_devices_set.stream_devices.size(), 1u);
            *devices_result = *stream_devices_set.stream_devices[0];
          } else {
            ASSERT_TRUE(stream_devices_set.stream_devices.empty());
            *devices_result = blink::mojom::StreamDevices();
          }
          wait_loop->Quit();
        },
        wait_loop, request_result, &devices_result);
  }

  void HandleRequest(const content::MediaStreamRequest& request,
                     base::RunLoop* wait_loop,
                     blink::mojom::MediaStreamRequestResult* request_result,
                     blink::mojom::StreamDevices& devices_result) {
    access_handler_->HandleRequest(
        web_contents(), request,
        MakeCallback(wait_loop, request_result, devices_result),
        nullptr /* extension */);
  }

  void SetTestFlags(
      std::vector<FakeDesktopMediaPickerFactory::TestFlags> test_flags_vector) {
    test_flags_ = std::move(test_flags_vector);
    picker_factory_->SetTestFlags(test_flags_);
  }

  void ProcessRequest(
      base::expected<content::DesktopMediaID,
                     blink::mojom::MediaStreamRequestResult> response,
      blink::mojom::MediaStreamRequestResult* request_result,
      blink::mojom::StreamDevices& devices_result,
      bool request_audio,
      bool expect_result = true,
      bool expect_picker = true,
      std::optional<content::MediaStreamRequest> request = std::nullopt) {
    SetTestFlags({{.expect_screens = true,
                   .expect_windows = true,
                   .expect_tabs = true,
                   .expect_current_tab = false,
                   .expect_audio = request_audio,
                   .picker_result = response}});

    if (!request.has_value()) {
      request = MakeRequest(request_audio);
    }

    base::RunLoop wait_loop;
    content::MediaResponseCallback callback;
    if (expect_result) {
      callback = MakeCallback(&wait_loop, request_result, devices_result);
    } else {
      base::MockCallback<content::MediaResponseCallback> mock_callback =
          base::MockCallback<content::MediaResponseCallback>();
      EXPECT_CALL(mock_callback, Run).Times(0);
      callback = mock_callback.Get();
    }

    access_handler_->HandleRequest(
        web_contents(), *request, std::move(callback), nullptr /* extension */);
    if (expect_result) {
      wait_loop.Run();
    } else {
      wait_loop.RunUntilIdle();
    }

    EXPECT_EQ(test_flags_[0].picker_created, expect_picker);

    picker_factory_ = nullptr;
    access_handler_.reset();
    EXPECT_EQ(test_flags_[0].picker_deleted, expect_picker);
  }

  void NotifyWebContentsDestroyed() {
    access_handler_->WebContentsDestroyed(web_contents());
  }

  bool IsWebContentsExcluded() const {
    return picker_factory_->IsWebContentsExcluded();
  }

  DesktopMediaPicker::Params GetParams() {
    FakeDesktopMediaPicker* picker = picker_factory_->picker();
    if (!picker) {
      ADD_FAILURE() << "Picker was destroyed prematurely!";
      return DesktopMediaPicker::Params();
    }
    return picker->GetParams();
  }

  const DisplayMediaAccessHandler::RequestsQueues& GetRequestQueues() {
    return access_handler_->pending_requests_;
  }

  void ChangeSourceRequestTest(
      bool with_audio,
      blink::mojom::MediaStreamRequestResult expected_result,
      size_t expected_number_of_devices) {
    blink::mojom::MediaStreamRequestResult result;
    blink::mojom::StreamDevices devices;
    SetTestFlags({MakePickerTestFlags(with_audio /*request_audio*/)});

    base::RunLoop wait_loop;
    HandleRequest(MakeMediaDeviceUpdateRequest(with_audio /* request_audio */),
                  &wait_loop, &result, devices);
    wait_loop.Run();
    EXPECT_FALSE(test_flags_[0].picker_created);

    picker_factory_ = nullptr;
    access_handler_.reset();
    EXPECT_EQ(expected_result, result);

    ASSERT_EQ(expected_number_of_devices, blink::CountDevices(devices));
    if (expected_number_of_devices >= 1) {
      EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
                devices.video_device.value().type);
    }
    if (expected_number_of_devices >= 2) {
      EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE,
                devices.audio_device.value().type);
    }
  }

  std::vector<FakeDesktopMediaPickerFactory::TestFlags> test_flags_;

 protected:
  // `access_handler` owns `picker_factory` and must outlive it.
  std::unique_ptr<DisplayMediaAccessHandler> access_handler_;
  raw_ptr<FakeDesktopMediaPickerFactory> picker_factory_;
};

TEST_F(DisplayMediaAccessHandlerTest, PermissionGiven) {
  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;
  ProcessRequest(content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN,
                                         content::DesktopMediaID::kFakeId),
                 &result, devices, false /* request_audio */);
// TODO(crbug.com/40802122): Fix screen-capture tests on macOS.
#if BUILDFLAG(IS_MAC)
  // On macOS, screen capture requires system permissions that are disabled by
  // default.
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED_BY_SYSTEM,
            result);
  return;
#endif

  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
  EXPECT_EQ(1u, blink::CountDevices(devices));
  EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
            devices.video_device.value().type);
  EXPECT_TRUE(devices.video_device.value().display_media_info);
}

#if BUILDFLAG(IS_MAC)
TEST_F(DisplayMediaAccessHandlerTest, WindowPermissionGiven) {
  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;
  content::DesktopMediaID desktop_media_id(content::DesktopMediaID::TYPE_WINDOW,
                                           content::DesktopMediaID::kFakeId);

  // Requests with a window_id will skip macOS screen share permission checks.
  desktop_media_id.window_id = content::DesktopMediaID::kFakeId;
  ProcessRequest(desktop_media_id, &result, devices, false /* request_audio */);

  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
  EXPECT_EQ(1u, blink::CountDevices(devices));
  EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
            devices.video_device.value().type);
  EXPECT_TRUE(devices.video_device.value().display_media_info);
}
#endif  // BUILDFLAG(IS_MAC)

TEST_F(DisplayMediaAccessHandlerTest, PermissionGivenToRequestWithAudio) {
  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;
  content::DesktopMediaID fake_media_id(content::DesktopMediaID::TYPE_SCREEN,
                                        content::DesktopMediaID::kFakeId,
                                        true /* audio_share */);
  ProcessRequest(fake_media_id, &result, devices, true /* request_audio */);
// TODO(crbug.com/40802122): Fix screen-capture tests on macOS.
#if BUILDFLAG(IS_MAC)
  // On macOS, screen capture requires system permissions that are disabled by
  // default.
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED_BY_SYSTEM,
            result);
  return;
#endif
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
  EXPECT_EQ(2u, blink::CountDevices(devices));
  EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
            devices.video_device.value().type);
  EXPECT_TRUE(devices.video_device.value().display_media_info);
  EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE,
            devices.audio_device.value().type);
  EXPECT_TRUE(devices.audio_device.value().input.IsValid());
}

TEST_F(DisplayMediaAccessHandlerTest, PermissionDenied) {
  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;
  ProcessRequest(base::unexpected(
                     blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED),
                 &result, devices, true /* request_audio */);
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED, result);
  EXPECT_EQ(0u, blink::CountDevices(devices));
}

TEST_F(DisplayMediaAccessHandlerTest, MaxLengthDomainAccepted) {
  std::unique_ptr<content::NavigationSimulator> navigation =
      content::NavigationSimulator::CreateBrowserInitiated(
          GURL("https://" + std::string(255, 'a')), web_contents());
  navigation->Commit();

  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;
  ProcessRequest(content::DesktopMediaID(content::DesktopMediaID::TYPE_WINDOW,
                                         content::DesktopMediaID::kFakeId),
                 &result, devices, false /* request_audio */);

  EXPECT_THAT(
      result,
      testing::AnyOf(
#if BUILDFLAG(IS_MAC)
          // TODO(crbug.com/40802122): Fix screen-capture permissions on mac.
          blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED_BY_SYSTEM,
#endif
          blink::mojom::MediaStreamRequestResult::OK));
}

TEST_F(DisplayMediaAccessHandlerTest, OverMaxLengthDomainRejected) {
  std::unique_ptr<content::NavigationSimulator> navigation =
      content::NavigationSimulator::CreateBrowserInitiated(
          GURL("https://" + std::string(256, 'a')), web_contents());
  navigation->Commit();

  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;
  ProcessRequest(content::DesktopMediaID(content::DesktopMediaID::TYPE_WINDOW,
                                         content::DesktopMediaID::kFakeId),
                 &result, devices, false /* request_audio */);
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::
                CAPTURE_NOT_ALLOWED_FOR_LONG_DOMAINS,
            result);
}

class DisplayMediaAccessHandlerActiveRfhTest
    : public DisplayMediaAccessHandlerTest,
      public testing::WithParamInterface<bool> {
 public:
  DisplayMediaAccessHandlerActiveRfhTest() : active_rfh_(GetParam()) {}
  ~DisplayMediaAccessHandlerActiveRfhTest() override = default;

  void SetUp() override {
    DisplayMediaAccessHandlerTest::SetUp();
    Navigate("https://a.com");
  }

  void Navigate(const std::string& url) {
    std::unique_ptr<content::NavigationSimulator> navigation =
        content::NavigationSimulator::CreateBrowserInitiated(GURL(url),
                                                             web_contents());
    navigation->Commit();
  }

  void DeactivateMainRfh() {
    // Cross-origin navigation will deactivate the previous RFH.
    Navigate("https://b.com");
  }

 protected:
  const bool active_rfh_;
};

INSTANTIATE_TEST_SUITE_P(,
                         DisplayMediaAccessHandlerActiveRfhTest,
                         testing::Bool());

TEST_P(DisplayMediaAccessHandlerActiveRfhTest, ProcessRequest) {
  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;
  const content::DesktopMediaID media_id(
      content::DesktopMediaID::TYPE_WEB_CONTENTS,
      content::DesktopMediaID::kNullId, GetWebContentsMediaCaptureId());

  // Lock in the RFH for use after deactivation. (If `!active_rfh_`; otherwise
  // it stays active.)
  const bool request_audio = false;
  content::MediaStreamRequest request = MakeRequest(request_audio);
  request.render_process_id =
      web_contents()->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID();
  request.render_frame_id =
      web_contents()->GetPrimaryMainFrame()->GetRoutingID();

  if (!active_rfh_) {
    DeactivateMainRfh();
  }

  ProcessRequest(media_id, &result, devices, request_audio,
                 /*expect_result=*/true, /*expect_picker=*/active_rfh_,
                 request);

  EXPECT_EQ(result, active_rfh_ ? blink::mojom::MediaStreamRequestResult::OK
                                : blink::mojom::MediaStreamRequestResult::
                                      FAILED_DUE_TO_SHUTDOWN_NO_RFH_IN_HANDLER);
}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(DisplayMediaAccessHandlerTest, DlpRestricted) {
  const content::DesktopMediaID media_id(content::DesktopMediaID::TYPE_SCREEN,
                                         content::DesktopMediaID::kFakeId);

  // Setup Data Leak Prevention restriction.
  policy::MockDlpContentManager mock_dlp_content_manager;
  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_observer(
      &mock_dlp_content_manager);
  EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
      .WillOnce([](const content::DesktopMediaID& media_id,
                   const std::u16string& application_title,
                   base::OnceCallback<void(bool)> callback) {
        std::move(callback).Run(/*should_proceed=*/false);
      });

  blink::mojom::MediaStreamRequestResult result =
      blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED;
  blink::mojom::StreamDevices devices;
  ProcessRequest(media_id, &result, devices, /*request_audio=*/false);

  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::DLP_PERMISSION_DENIED,
            result);
  EXPECT_EQ(0u, blink::CountDevices(devices));
}

TEST_F(DisplayMediaAccessHandlerTest, DlpNotRestricted) {
  const content::DesktopMediaID media_id(content::DesktopMediaID::TYPE_SCREEN,
                                         content::DesktopMediaID::kFakeId);

  // Setup Data Leak Prevention restriction.
  policy::MockDlpContentManager mock_dlp_content_manager;
  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
      &mock_dlp_content_manager);
  EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
      .WillOnce([](const content::DesktopMediaID& media_id,
                   const std::u16string& application_title,
                   base::OnceCallback<void(bool)> callback) {
        std::move(callback).Run(/*should_proceed=*/true);
      });

  blink::mojom::MediaStreamRequestResult result =
      blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED;
  blink::mojom::StreamDevices devices;
  ProcessRequest(media_id, &result, devices, /*request_audio=*/false);

  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
  EXPECT_EQ(1u, blink::CountDevices(devices));
}

TEST_F(DisplayMediaAccessHandlerTest, DlpWebContentsDestroyed) {
  const content::DesktopMediaID media_id(content::DesktopMediaID::TYPE_SCREEN,
                                         content::DesktopMediaID::kFakeId);

  // Setup Data Leak Prevention restriction.
  policy::MockDlpContentManager mock_dlp_content_manager;
  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
      &mock_dlp_content_manager);
  EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
      .WillOnce([&](const content::DesktopMediaID& media_id,
                    const std::u16string& application_title,
                    base::OnceCallback<void(bool)> callback) {
        DeleteContents();
        std::move(callback).Run(/*should_proceed=*/true);
      });

  blink::mojom::MediaStreamRequestResult result =
      blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED;
  blink::mojom::StreamDevices devices;
  ProcessRequest(media_id, &result, devices, /*request_audio=*/false,
                 /*expect_result=*/false);

  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED, result);
  EXPECT_EQ(0u, blink::CountDevices(devices));
}
#endif  // BUILDFLAG(IS_CHROMEOS)

TEST_F(DisplayMediaAccessHandlerTest, UpdateMediaRequestStateWithClosing) {
  const int render_process_id =
      web_contents()->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID();
  const int render_frame_id =
      web_contents()->GetPrimaryMainFrame()->GetRoutingID();
  const int page_request_id = 0;
  const blink::mojom::MediaStreamType video_stream_type =
      blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
  const blink::mojom::MediaStreamType audio_stream_type =
      blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE;
  SetTestFlags({{true /* expect_screens */, true /* expect_windows*/,
                 true /* expect_tabs */, false /* expect_current_tab */,
                 true /* expect_audio */, content::DesktopMediaID(),
                 true /* cancelled */}});
  content::MediaStreamRequest request(
      render_process_id, render_frame_id, page_request_id,
      url::Origin::Create(GURL("http://origin/")), false,
      blink::MEDIA_GENERATE_STREAM, /*requested_audio_device_ids=*/{},
      /*requested_video_device_ids=*/{}, audio_stream_type, video_stream_type,
      /*disable_local_echo=*/false, /*request_pan_tilt_zoom_permission=*/false,
      /*captured_surface_control_active=*/false);
  content::MediaResponseCallback callback;
  access_handler_->HandleRequest(web_contents(), request, std::move(callback),
                                 nullptr /* extension */);
  EXPECT_TRUE(test_flags_[0].picker_created);
  EXPECT_EQ(1u, GetRequestQueues().size());
  auto queue_it = GetRequestQueues().find(web_contents());
  EXPECT_TRUE(queue_it != GetRequestQueues().end());
  EXPECT_EQ(1u, queue_it->second.size());

  access_handler_->UpdateMediaRequestState(
      render_process_id, render_frame_id, page_request_id, video_stream_type,
      content::MEDIA_REQUEST_STATE_CLOSING);
  EXPECT_EQ(1u, GetRequestQueues().size());
  queue_it = GetRequestQueues().find(web_contents());
  EXPECT_TRUE(queue_it != GetRequestQueues().end());
  EXPECT_EQ(0u, queue_it->second.size());
  EXPECT_TRUE(test_flags_[0].picker_deleted);
}

TEST_F(DisplayMediaAccessHandlerTest, CorrectHostAsksForPermissions) {
  const int page_request_id = 0;
  const blink::mojom::MediaStreamType video_stream_type =
      blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
  const blink::mojom::MediaStreamType audio_stream_type =
      blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE;
  SetTestFlags({{true /* expect_screens */, true /* expect_windows*/,
                 true /* expect_tabs */, false /* expect_current_tab */,
                 true /* expect_audio */, content::DesktopMediaID(),
                 true /* cancelled */}});

  content::WebContents* test_web_contents = web_contents();
  std::unique_ptr<content::NavigationSimulator> navigation =
      content::NavigationSimulator::CreateBrowserInitiated(
          GURL("blob:http://127.0.0.1:8000/says: www.google.com"),
          test_web_contents);
  navigation->Commit();

  const int render_process_id =
      test_web_contents->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID();
  const int render_frame_id =
      test_web_contents->GetPrimaryMainFrame()->GetRoutingID();

  content::MediaStreamRequest request(
      render_process_id, render_frame_id, page_request_id,
      url::Origin::Create(GURL("http://127.0.0.1:8000")), false,
      blink::MEDIA_GENERATE_STREAM, /*requested_audio_device_ids=*/{},
      /*requested_video_device_ids=*/{}, audio_stream_type, video_stream_type,
      /*disable_local_echo=*/false, /*request_pan_tilt_zoom_permission=*/false,
      /*captured_surface_control_active=*/false);
  content::MediaResponseCallback callback;
  access_handler_->HandleRequest(test_web_contents, request,
                                 std::move(callback), nullptr /* extension */);
  DesktopMediaPicker::Params params = GetParams();
  access_handler_->UpdateMediaRequestState(
      render_process_id, render_frame_id, page_request_id, video_stream_type,
      content::MEDIA_REQUEST_STATE_CLOSING);
  EXPECT_EQ(u"http://127.0.0.1:8000", params.app_name);
}

TEST_F(DisplayMediaAccessHandlerTest, CorrectHostAsksForPermissionsNormalURLs) {
  const int page_request_id = 0;
  const blink::mojom::MediaStreamType video_stream_type =
      blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
  const blink::mojom::MediaStreamType audio_stream_type =
      blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE;
  SetTestFlags({{true /* expect_screens */, true /* expect_windows*/,
                 true /* expect_tabs */, false /* expect_current_tab */,
                 true /* expect_audio */, content::DesktopMediaID(),
                 true /* cancelled */}});

  content::WebContents* test_web_contents = web_contents();
  std::unique_ptr<content::NavigationSimulator> navigation =
      content::NavigationSimulator::CreateBrowserInitiated(
          GURL("https://www.google.com"), test_web_contents);
  navigation->Commit();

  const int render_process_id =
      test_web_contents->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID();
  const int render_frame_id =
      test_web_contents->GetPrimaryMainFrame()->GetRoutingID();

  content::MediaStreamRequest request(
      render_process_id, render_frame_id, page_request_id,
      url::Origin::Create(GURL("https://www.google.com")), false,
      blink::MEDIA_GENERATE_STREAM, /*requested_audio_device_ids=*/{},
      /*requested_video_device_ids=*/{}, audio_stream_type, video_stream_type,
      /*disable_local_echo=*/false, /*request_pan_tilt_zoom_permission=*/false,
      /*captured_surface_control_active=*/false);
  content::MediaResponseCallback callback;
  access_handler_->HandleRequest(test_web_contents, request,
                                 std::move(callback), nullptr /* extension */);
  DesktopMediaPicker::Params params = GetParams();
  access_handler_->UpdateMediaRequestState(
      render_process_id, render_frame_id, page_request_id, video_stream_type,
      content::MEDIA_REQUEST_STATE_CLOSING);
  EXPECT_EQ(u"www.google.com", params.app_name);
}

#if !BUILDFLAG(IS_ANDROID)

TEST_F(DisplayMediaAccessHandlerTest, IsolatedWebAppNameAsksForPermissions) {
  base::test::ScopedFeatureList scoped_feature_list{features::kIsolatedWebApps};
  data_decoder::test::InProcessDataDecoder in_process_data_decoder;
  web_app::test::AwaitStartWebAppProviderAndSubsystems(profile());

  // Create a fresh WebContents to avoid being blocked by the non-IWA state
  // committed by the default web_contents() in SetUp().
  std::unique_ptr<content::WebContents> test_web_contents =
      content::WebContentsTester::CreateTestWebContents(profile(), nullptr);

  const std::string app_name("Test IWA Name");
  std::unique_ptr<web_app::ScopedBundledIsolatedWebApp> iwa =
      web_app::IsolatedWebAppBuilder(
          web_app::ManifestBuilder()
              .SetName(app_name)
              .AddPermissionsPolicyWildcard(
                  network::mojom::PermissionsPolicyFeature::kDisplayCapture))
          .BuildBundle();
  iwa->TrustSigningKey();
  iwa->FakeInstallPageState(profile());
  ASSERT_OK_AND_ASSIGN(web_app::IsolatedWebAppUrlInfo url_info,
                       iwa->Install(profile()));

  web_app::IwaPermissionsPolicyCache::CacheEntry policy;
  policy.emplace_back("display-capture", std::vector<std::string>{"*"});
  web_app::IwaPermissionsPolicyCacheFactory::GetForProfile(profile())
      ->SetPolicyForTesting(web_app::IwaOrigin(url_info.web_bundle_id()),
                            std::move(policy));

  auto simulator = content::NavigationSimulator::CreateBrowserInitiated(
      url_info.origin().GetURL(), test_web_contents.get());
  simulator->SetTransition(ui::PAGE_TRANSITION_TYPED);

  simulator->SetResponseHeaders(
      net::HttpResponseHeaders::Builder(net::HttpVersion(1, 1), "200 OK")
          .AddHeader("Cross-Origin-Opener-Policy", "same-origin")
          .AddHeader("Cross-Origin-Embedder-Policy", "require-corp")
          .AddHeader("Cross-Origin-Resource-Policy", "same-origin")
          .Build());

  network::ParsedPermissionsPolicy parsed_policy;
  parsed_policy.emplace_back(
      network::mojom::PermissionsPolicyFeature::kDisplayCapture,
      std::vector<network::OriginWithPossibleWildcards>{}, url_info.origin(),
      /*matches_all_origins=*/true, /*matches_opaque_src=*/false);
  simulator->SetPermissionsPolicyHeader(std::move(parsed_policy));

  simulator->Start();
  simulator->Commit();

  const int render_process_id =
      test_web_contents->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID();
  const int render_frame_id =
      test_web_contents->GetPrimaryMainFrame()->GetRoutingID();
  const int page_request_id = 0;
  const blink::mojom::MediaStreamType video_stream_type =
      blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE;
  const blink::mojom::MediaStreamType audio_stream_type =
      blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE;
  SetTestFlags({{true /* expect_screens */, true /* expect_windows*/,
                 true /* expect_tabs */, false /* expect_current_tab */,
                 true /* expect_audio */, content::DesktopMediaID(),
                 true /* cancelled */}});
  content::MediaStreamRequest request(
      render_process_id, render_frame_id, page_request_id, url_info.origin(),
      false, blink::MEDIA_GENERATE_STREAM, /*requested_audio_device_ids=*/{},
      /*requested_video_device_ids=*/{}, audio_stream_type, video_stream_type,
      /*disable_local_echo=*/false, /*request_pan_tilt_zoom_permission=*/false,
      /*captured_surface_control_active=*/false);
  content::MediaResponseCallback callback;
  access_handler_->HandleRequest(test_web_contents.get(), request,
                                 std::move(callback), nullptr /* extension */);
  DesktopMediaPicker::Params params = GetParams();
  access_handler_->UpdateMediaRequestState(
      render_process_id, render_frame_id, page_request_id, video_stream_type,
      content::MEDIA_REQUEST_STATE_CLOSING);
  EXPECT_EQ(base::UTF8ToUTF16(app_name), params.app_name);
}
#endif  // !BUILDFLAG(IS_ANDROID)

TEST_F(DisplayMediaAccessHandlerTest, WebContentsDestroyed) {
  SetTestFlags({{true /* expect_screens */, true /* expect_windows*/,
                 true /* expect_tabs */, false /* expect_current_tab */,
                 false /* expect_audio */, content::DesktopMediaID(),
                 true /* cancelled */}});
  content::MediaStreamRequest request(
      web_contents()->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID(),
      web_contents()->GetPrimaryMainFrame()->GetRoutingID(), 0,
      url::Origin::Create(GURL("http://origin/")), false,
      blink::MEDIA_GENERATE_STREAM, /*requested_audio_device_ids=*/{},
      /*requested_video_device_ids=*/{},
      blink::mojom::MediaStreamType::NO_SERVICE,
      blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
      /*disable_local_echo=*/false, /*request_pan_tilt_zoom_permission=*/false,
      /*captured_surface_control_active=*/false);
  content::MediaResponseCallback callback;
  access_handler_->HandleRequest(web_contents(), request, std::move(callback),
                                 nullptr /* extension */);
  EXPECT_TRUE(test_flags_[0].picker_created);
  EXPECT_EQ(1u, GetRequestQueues().size());
  auto queue_it = GetRequestQueues().find(web_contents());
  EXPECT_TRUE(queue_it != GetRequestQueues().end());
  EXPECT_EQ(1u, queue_it->second.size());

  NotifyWebContentsDestroyed();
  EXPECT_EQ(0u, GetRequestQueues().size());
}

TEST_F(DisplayMediaAccessHandlerTest, MultipleRequests) {
  SetTestFlags({{true /* expect_screens */, true /* expect_windows*/,
                 true /* expect_tabs */, false /* expect_current_tab */,
                 false /* expect_audio */,
                 content::DesktopMediaID(
                     content::DesktopMediaID::TYPE_SCREEN,
                     content::DesktopMediaID::kFakeId) /* selected_source */},
                {true /* expect_screens */, true /* expect_windows*/,
                 true /* expect_tabs */, false /* expect_current_tab */,
                 false /* expect_audio */,
                 content::DesktopMediaID(
                     content::DesktopMediaID::TYPE_WINDOW,
                     content::DesktopMediaID::kNullId) /* selected_source */}});
  const size_t kTestFlagCount = 2;

  blink::mojom::MediaStreamRequestResult result;
  blink::MediaStreamDevices devices;
  std::array<base::RunLoop, kTestFlagCount> wait_loop;
  for (size_t i = 0; i < kTestFlagCount; ++i) {
    content::MediaStreamRequest request(
        web_contents()->GetPrimaryMainFrame()->GetProcess()->GetDeprecatedID(),
        web_contents()->GetPrimaryMainFrame()->GetRoutingID(), 0,
        url::Origin::Create(GURL("http://origin/")), false,
        blink::MEDIA_GENERATE_STREAM, /*requested_audio_device_ids=*/{},
        /*requested_video_device_ids=*/{},
        blink::mojom::MediaStreamType::NO_SERVICE,
        blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
        /*disable_local_echo=*/false,
        /*request_pan_tilt_zoom_permission=*/false,
        /*captured_surface_control_active=*/false);
    content::MediaResponseCallback callback = base::BindOnce(
        [](base::RunLoop* wait_loop,
           blink::mojom::MediaStreamRequestResult* request_result,
           blink::MediaStreamDevices* devices_result,
           const blink::mojom::StreamDevicesSet& stream_devices_set,
           blink::mojom::MediaStreamRequestResult result,
           std::unique_ptr<content::MediaStreamUI> ui) {
          *request_result = result;
          if (result == blink::mojom::MediaStreamRequestResult::OK) {
            ASSERT_EQ(stream_devices_set.stream_devices.size(), 1u);
            *devices_result =
                blink::ToMediaStreamDevicesList(stream_devices_set);
          } else {
            ASSERT_TRUE(stream_devices_set.stream_devices.empty());
          }
          wait_loop->Quit();
        },
        &wait_loop[i], &result, &devices);
    access_handler_->HandleRequest(web_contents(), request, std::move(callback),
                                   nullptr /* extension */);
  }
  wait_loop[0].Run();
  EXPECT_TRUE(test_flags_[0].picker_created);
  EXPECT_TRUE(test_flags_[0].picker_deleted);
// TODO(crbug.com/40802122): Fix screen-capture tests on macOS.
#if BUILDFLAG(IS_MAC)
  // On macOS, screen capture requires system permissions that are disabled by
  // default.
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED_BY_SYSTEM,
            result);
  return;
#endif
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
  EXPECT_EQ(1u, devices.size());
  EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
            devices[0].type);

  blink::MediaStreamDevice first_device = devices[0];
  EXPECT_TRUE(test_flags_[1].picker_created);
  EXPECT_FALSE(test_flags_[1].picker_deleted);
  wait_loop[1].Run();
  EXPECT_TRUE(test_flags_[1].picker_deleted);
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
  EXPECT_EQ(1u, devices.size());
  EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
            devices[0].type);
  EXPECT_FALSE(devices[0].IsSameDevice(first_device));
}

TEST_F(DisplayMediaAccessHandlerTest, QueuedRequestRejectedIfRfhNavigates) {
  // Navigate to initial origin.
  std::unique_ptr<content::NavigationSimulator> navigation =
      content::NavigationSimulator::CreateBrowserInitiated(
          GURL("https://origin-a.com"), web_contents());
  navigation->Commit();

  content::RenderFrameHost* rfh1 = web_contents()->GetPrimaryMainFrame();
  int process_id = rfh1->GetProcess()->GetDeprecatedID();
  int frame_id = rfh1->GetRoutingID();

  SetTestFlags({{true /* expect_screens */, true /* expect_windows*/,
                 true /* expect_tabs */, false /* expect_current_tab */,
                 false /* expect_audio */,
                 content::DesktopMediaID(
                     content::DesktopMediaID::TYPE_SCREEN,
                     content::DesktopMediaID::kFakeId) /* selected_source */},
                {true /* expect_screens */, true /* expect_windows*/,
                 true /* expect_tabs */, false /* expect_current_tab */,
                 false /* expect_audio */,
                 content::DesktopMediaID(
                     content::DesktopMediaID::TYPE_WINDOW,
                     content::DesktopMediaID::kNullId) /* selected_source */}});

  blink::mojom::MediaStreamRequestResult result1 =
      blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED;
  blink::mojom::MediaStreamRequestResult result2 =
      blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED;
  blink::mojom::StreamDevices devices1;
  blink::mojom::StreamDevices devices2;

  base::RunLoop wait_loop1;
  base::RunLoop wait_loop2;

  // Request 1
  content::MediaStreamRequest request1(
      process_id, frame_id, 0,
      url::Origin::Create(GURL("https://origin-a.com")), false,
      blink::MEDIA_GENERATE_STREAM, {}, {},
      blink::mojom::MediaStreamType::NO_SERVICE,
      blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
      /*disable_local_echo=*/false,
      /*request_pan_tilt_zoom_permission=*/false,
      /*captured_surface_control_active=*/false);

  // Request 2
  content::MediaStreamRequest request2(
      process_id, frame_id, 1,
      url::Origin::Create(GURL("https://origin-a.com")), false,
      blink::MEDIA_GENERATE_STREAM, {}, {},
      blink::mojom::MediaStreamType::NO_SERVICE,
      blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
      /*disable_local_echo=*/false,
      /*request_pan_tilt_zoom_permission=*/false,
      /*captured_surface_control_active=*/false);

  // Handle Request 1. It will show picker and wait.
  access_handler_->HandleRequest(web_contents(), request1,
                                 MakeCallback(&wait_loop1, &result1, devices1),
                                 nullptr /* extension */);

  // Handle Request 2. It will be queued.
  access_handler_->HandleRequest(web_contents(), request2,
                                 MakeCallback(&wait_loop2, &result2, devices2),
                                 nullptr /* extension */);

  // Navigate to a different origin. This will make rfh1 inactive.
  std::unique_ptr<content::NavigationSimulator> navigation2 =
      content::NavigationSimulator::CreateBrowserInitiated(
          GURL("https://origin-b.com"), web_contents());
  navigation2->Commit();

  // Run loop 1 to let Request 1 complete.
  wait_loop1.Run();

#if BUILDFLAG(IS_MAC)
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED_BY_SYSTEM,
            result1);
#else
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result1);
#endif

  // Now, after Request 1 completes, it will try to process Request 2.
  // Request 2 should be processed, but our new check should detect that rfh1 is
  // now inactive.
  // So Request 2 should be rejected with
  // FAILED_DUE_TO_SHUTDOWN_NO_RFH_IN_HANDLER.
  wait_loop2.Run();

  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::
                FAILED_DUE_TO_SHUTDOWN_NO_RFH_IN_HANDLER,
            result2);

  // Verify that the second picker was never shown.
  EXPECT_FALSE(test_flags_[1].picker_shown);
}

TEST_F(DisplayMediaAccessHandlerTest,
       ChangeSourceWithoutAudioRequestPermissionGiven) {
  ChangeSourceRequestTest(
      /*with_audio=*/false,
      /*expected_result=*/blink::mojom::MediaStreamRequestResult::OK,
      /*expected_number_of_devices=*/1u);
}

TEST_F(DisplayMediaAccessHandlerTest,
       ChangeSourceWithAudioRequestPermissionGiven) {
  blink::MediaStreamDevices devices;
  ChangeSourceRequestTest(
      /*with_audio=*/true,
      /*expected_result=*/blink::mojom::MediaStreamRequestResult::OK,
      /*expected_number_of_devices=*/2u);
}

#if BUILDFLAG(IS_CHROMEOS)
TEST_F(DisplayMediaAccessHandlerTest, ChangeSourceDlpRestricted) {
  const content::DesktopMediaID media_id(
      content::DesktopMediaID::TYPE_WEB_CONTENTS,
      content::DesktopMediaID::kNullId, GetWebContentsMediaCaptureId());

  // Setup Data Leak Prevention restriction.
  policy::MockDlpContentManager mock_dlp_content_manager;
  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_observer(
      &mock_dlp_content_manager);
  EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
      .WillOnce([](const content::DesktopMediaID& media_id,
                   const std::u16string& application_title,
                   base::OnceCallback<void(bool)> callback) {
        std::move(callback).Run(/*should_proceed=*/false);
      });

  ChangeSourceRequestTest(
      /*with_audio=*/false,
      /*expected_result=*/
      blink::mojom::MediaStreamRequestResult::DLP_PERMISSION_DENIED,
      /*expected_number_of_devices=*/0u);
}

TEST_F(DisplayMediaAccessHandlerTest, ChangeSourceDlpNotRestricted) {
  const content::DesktopMediaID media_id(
      content::DesktopMediaID::TYPE_WEB_CONTENTS,
      content::DesktopMediaID::kNullId, GetWebContentsMediaCaptureId());

  // Setup Data Leak Prevention restriction.
  policy::MockDlpContentManager mock_dlp_content_manager;
  policy::ScopedDlpContentObserverForTesting scoped_dlp_content_manager(
      &mock_dlp_content_manager);
  EXPECT_CALL(mock_dlp_content_manager, CheckScreenShareRestriction)
      .WillOnce([](const content::DesktopMediaID& media_id,
                   const std::u16string& application_title,
                   base::OnceCallback<void(bool)> callback) {
        std::move(callback).Run(/*should_proceed=*/true);
      });

  ChangeSourceRequestTest(
      /*with_audio=*/false,
      /*expected_result=*/
      blink::mojom::MediaStreamRequestResult::OK,
      /*expected_number_of_devices=*/1u);
}
#endif  // BUILDFLAG(IS_CHROMEOS)

TEST_F(DisplayMediaAccessHandlerTest, ChangeSourceWithPendingPickerRequest) {
  SetTestFlags({MakePickerTestFlags(false /*request_audio*/),
                MakePickerTestFlags(false /*request_audio*/)});

  std::array<blink::mojom::MediaStreamRequestResult, 2> results;
  std::array<blink::mojom::StreamDevices, 2> devices;
  std::array<base::RunLoop, 2> wait_loop;

  HandleRequest(MakeRequest(false /* request_audio */), &wait_loop[0],
                &results[0], devices[0]);
  HandleRequest(MakeMediaDeviceUpdateRequest(false /* request_audio */),
                &wait_loop[1], &results[1], devices[1]);

  wait_loop[0].Run();
  EXPECT_TRUE(test_flags_[0].picker_created);
  EXPECT_TRUE(test_flags_[0].picker_deleted);
// TODO(crbug.com/40802122): Fix screen-capture tests on macOS.
#if BUILDFLAG(IS_MAC)
  // On macOS, screen capture requires system permissions that are disabled by
  // default.
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED_BY_SYSTEM,
            results[0]);
  return;
#endif
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, results[0]);
  EXPECT_FALSE(test_flags_[1].picker_created);
  EXPECT_FALSE(test_flags_[1].picker_deleted);
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, results[1]);
}

TEST_F(DisplayMediaAccessHandlerTest,
       ChangeSourcePolicyViolationWithPendingPickerRequest) {
  SetTestFlags({MakePickerTestFlags(false /*request_audio*/),
                MakePickerTestFlags(false /*request_audio*/)});

  std::array<blink::mojom::MediaStreamRequestResult, 2> results;
  std::array<blink::mojom::StreamDevices, 2> devices;
  std::array<base::RunLoop, 2> wait_loop;

  HandleRequest(MakeRequest(false /* request_audio */), &wait_loop[0],
                &results[0], devices[0]);
  HandleRequest(MakeMediaDeviceUpdateRequest(false /* request_audio */),
                &wait_loop[1], &results[1], devices[1]);

  // Policy is changed after the requests are received, but before they are
  // processed in the call to wait_loop.Run() below.
  Profile* profile =
      Profile::FromBrowserContext(web_contents()->GetBrowserContext());
  profile->GetPrefs()->SetBoolean(prefs::kScreenCaptureAllowed, false);

  wait_loop[0].Run();
// TODO(crbug.com/40802122): Fix screen-capture tests on macOS.
#if BUILDFLAG(IS_MAC)
  // On macOS, screen capture requires system permissions that are disabled by
  // default.
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED_BY_SYSTEM,
            results[0]);
  return;
#endif
  EXPECT_FALSE(test_flags_[1].picker_created);
  EXPECT_FALSE(test_flags_[1].picker_deleted);
  EXPECT_EQ(
      blink::mojom::MediaStreamRequestResult::CAPTURE_NOT_ALLOWED_BY_POLICY,
      results[1]);
}

TEST_F(DisplayMediaAccessHandlerTest,
       MalformedChangeSourceBetweenPickerRequests) {
  SetTestFlags({{MakePickerTestFlags(false /*request_audio*/)}});

  std::array<blink::mojom::MediaStreamRequestResult, 3> results;
  std::array<blink::mojom::StreamDevices, 3> devices;
  std::array<base::RunLoop, 3> wait_loop;

  HandleRequest(MakeRequest(false /* request_audio */), &wait_loop[0],
                &results[0], devices[0]);
  {
    content::MediaStreamRequest request =
        MakeMediaDeviceUpdateRequest(false /* request_audio */);
    request.requested_video_device_ids = {"MALFORMED"};
    HandleRequest(request, &wait_loop[1], &results[1], devices[1]);
  }
  HandleRequest(MakeMediaDeviceUpdateRequest(false /* request_audio */),
                &wait_loop[2], &results[2], devices[2]);

  wait_loop[0].Run();
  EXPECT_TRUE(test_flags_[0].picker_created);
  EXPECT_TRUE(test_flags_[0].picker_deleted);
// TODO(crbug.com/40802122): Fix screen-capture tests on macOS.
#if BUILDFLAG(IS_MAC)
  // On macOS, screen capture requires system permissions that are disabled by
  // default.
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED_BY_SYSTEM,
            results[0]);
  return;
#endif
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, results[0]);
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::INVALID_VIDEO_DEVICE_ID,
            results[1]);
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, results[2]);
}

TEST_F(DisplayMediaAccessHandlerTest, ScreenWithAudioDefaultsToSystemAudio) {
  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;

  ProcessRequest(
      content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN, 1,
                              /*audio_share=*/true),
      &result, devices, /*request_audio=*/true);

// TODO(crbug.com/40802122): Fix screen-capture tests on macOS.
#if BUILDFLAG(IS_MAC)
  // On macOS, screen capture requires system permissions that are disabled by
  // default.
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED_BY_SYSTEM,
            result);
  return;
#endif

  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
  EXPECT_EQ(2u, blink::CountDevices(devices));

  EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
            devices.video_device->type);
  EXPECT_TRUE(devices.video_device->display_media_info);

  EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE,
            devices.audio_device.value().type);
  EXPECT_EQ("loopback", devices.audio_device->id);
  EXPECT_EQ("System Audio", devices.audio_device->name);
}

TEST_F(DisplayMediaAccessHandlerTest, WindowWithAudioDefaultsToSystemAudio) {
  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;

  ProcessRequest(
      content::DesktopMediaID(content::DesktopMediaID::TYPE_WINDOW, 1234,
                              /*audio_share=*/true),
      &result, devices, /*request_audio=*/true);

// TODO(crbug.com/40802122): Fix screen-capture tests on macOS.
#if BUILDFLAG(IS_MAC)
  // On macOS, screen capture requires system permissions that are disabled by
  // default.
  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED_BY_SYSTEM,
            result);
  return;
#endif

  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
  EXPECT_EQ(2u, blink::CountDevices(devices));

  EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
            devices.video_device->type);
  EXPECT_TRUE(devices.video_device->display_media_info);

  EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE,
            devices.audio_device.value().type);
  EXPECT_EQ("loopback", devices.audio_device->id);
  EXPECT_EQ("System Audio", devices.audio_device->name);
}

class DisplayMediaAccessHandlerTestWithSelfBrowserSurface
    : public DisplayMediaAccessHandlerTest,
      public testing::WithParamInterface<bool> {
 public:
  DisplayMediaAccessHandlerTestWithSelfBrowserSurface()
      : exclude_self_browser_surface_(GetParam()) {}

  ~DisplayMediaAccessHandlerTestWithSelfBrowserSurface() override = default;

 protected:
  const bool exclude_self_browser_surface_;
};

INSTANTIATE_TEST_SUITE_P(_,
                         DisplayMediaAccessHandlerTestWithSelfBrowserSurface,
                         ::testing::Bool());

TEST_P(DisplayMediaAccessHandlerTestWithSelfBrowserSurface,
       CheckIsWebContentsExcluded) {
  SetTestFlags({{MakePickerTestFlags(/*request_audio=*/false)}});
  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;
  base::RunLoop wait_loop;

  HandleRequest(
      MakeExcludeSelfBrowserSurfaceRequest(exclude_self_browser_surface_),
      &wait_loop, &result, devices);
  wait_loop.Run();
  EXPECT_EQ(exclude_self_browser_surface_, IsWebContentsExcluded());
}

class DisplayMediaAccessHandlerTestWithMonitorTypeSurfaces
    : public DisplayMediaAccessHandlerTest,
      public testing::WithParamInterface<bool> {
 public:
  DisplayMediaAccessHandlerTestWithMonitorTypeSurfaces()
      : exclude_monitor_type_surfaces_(GetParam()) {}

  ~DisplayMediaAccessHandlerTestWithMonitorTypeSurfaces() override = default;

 protected:
  const bool exclude_monitor_type_surfaces_;
};

INSTANTIATE_TEST_SUITE_P(_,
                         DisplayMediaAccessHandlerTestWithMonitorTypeSurfaces,
                         ::testing::Bool());

TEST_P(DisplayMediaAccessHandlerTestWithMonitorTypeSurfaces,
       CheckMonitorTypeSurfacesAreExcluded) {
  SetTestFlags({{/*expect_screens=*/!exclude_monitor_type_surfaces_,
                 /*expect_windows=*/true,
                 /*expect_tabs=*/true, /*expect_current_tab=*/false,
                 /*expect_audio=*/false,
                 base::unexpected(
                     blink::mojom::MediaStreamRequestResult::PERMISSION_DENIED),
                 /*cancelled=*/false}});
  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;
  base::RunLoop wait_loop;

  HandleRequest(
      MakeExcludeMonitorTypeSurfacesRequest(exclude_monitor_type_surfaces_),
      &wait_loop, &result, devices);
  wait_loop.Run();
}

#if BUILDFLAG(IS_WIN)
class DisplayMediaAccessHandlerWindowAudioCaptureWinTest
    : public DisplayMediaAccessHandlerTest,
      public testing::WithParamInterface<content::DesktopMediaID::AudioType> {};

TEST_P(DisplayMediaAccessHandlerWindowAudioCaptureWinTest, ValidWindowId) {
  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;

  // Create a fake window to simulate a valid window ID. Fortunately, we don't
  // need to actually show the window, just to have a valid HWND.
  base::win::MessageWindow window;
  EXPECT_TRUE(window.Create(
      base::BindLambdaForTesting([&](UINT message, WPARAM wparam, LPARAM lparam,
                                     LRESULT* result) { return true; })));

  const bool audio_share =
      GetParam() != content::DesktopMediaID::AudioType::kNone;
  content::DesktopMediaID fake_id(content::DesktopMediaID::TYPE_WINDOW,
                                  reinterpret_cast<intptr_t>(window.hwnd()),
                                  audio_share);
  fake_id.window_audio_type = GetParam();
  ProcessRequest(fake_id, &result, devices, audio_share);

  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);

  // If the audio type is `kNone`, we should not get an audio device.
  if (GetParam() == content::DesktopMediaID::AudioType::kNone) {
    EXPECT_EQ(1u, blink::CountDevices(devices));
  } else {
    EXPECT_EQ(2u, blink::CountDevices(devices));
    EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_AUDIO_CAPTURE,
              devices.audio_device.value().type);
  }

  EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
            devices.video_device.value().type);
  EXPECT_TRUE(devices.video_device.value().display_media_info);

  if (GetParam() == content::DesktopMediaID::AudioType::kApplication) {
    EXPECT_TRUE(devices.audio_device.value().input.IsValid());
    // Unit tests are executed in a child process that also use the same
    // executable image (unit_tests.exe) unless the --single-process flag is
    // passed. Therefore, the application process ID should match either the
    // parent's process PID or the child's process PID.
    uint32_t application_process_id =
        media::GetApplicationIdFromApplicationLoopbackDeviceId(
            devices.audio_device.value().id);
    EXPECT_THAT(application_process_id,
                testing::AnyOf(base::Process::Current().Pid(),
                               base::GetParentProcessId(
                                   base::Process::Current().Handle())));

    EXPECT_EQ("Application Audio", devices.audio_device->name);
  } else if (GetParam() == content::DesktopMediaID::AudioType::kSystem) {
    // System audio device ID and name are constant.
    EXPECT_EQ("loopback", devices.audio_device->id);
    EXPECT_EQ("System Audio", devices.audio_device->name);
  }
}

TEST_P(DisplayMediaAccessHandlerWindowAudioCaptureWinTest, InvalidWindowId) {
  blink::mojom::MediaStreamRequestResult result;
  blink::mojom::StreamDevices devices;
  const bool audio_share =
      GetParam() != content::DesktopMediaID::AudioType::kNone;
  content::DesktopMediaID fake_id(content::DesktopMediaID::TYPE_WINDOW,
                                  content::DesktopMediaID::kFakeId,
                                  audio_share);
  fake_id.window_audio_type = GetParam();
  ProcessRequest(fake_id, &result, devices, audio_share);

  EXPECT_EQ(blink::mojom::MediaStreamRequestResult::OK, result);
  // If the window ID is invalid, window audio should not be captured.
  if (!audio_share ||
      GetParam() == content::DesktopMediaID::AudioType::kApplication) {
    EXPECT_EQ(1u, blink::CountDevices(devices));
  } else {
    EXPECT_EQ(2u, blink::CountDevices(devices));
  }

  EXPECT_EQ(blink::mojom::MediaStreamType::DISPLAY_VIDEO_CAPTURE,
            devices.video_device.value().type);
  EXPECT_TRUE(devices.video_device.value().display_media_info);
}

INSTANTIATE_TEST_SUITE_P(
    ,
    DisplayMediaAccessHandlerWindowAudioCaptureWinTest,
    testing::Values(content::DesktopMediaID::AudioType::kNone,
                    content::DesktopMediaID::AudioType::kSystem,
                    content::DesktopMediaID::AudioType::kApplication),
    [](const testing::TestParamInfo<content::DesktopMediaID::AudioType>& info) {
      switch (info.param) {
        case content::DesktopMediaID::AudioType::kNone:
          return "NoAudio";
        case content::DesktopMediaID::AudioType::kSystem:
          return "SystemAudio";
        case content::DesktopMediaID::AudioType::kApplication:
          return "ApplicationAudio";
      }
      NOTREACHED();
    });

#endif  // BUILDFLAG(IS_WIN)
