// 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 <optional>
#include <string>
#include <tuple>
#include <vector>

#include "base/base_switches.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/memory/raw_ptr.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/to_string.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/config/chromebox_for_meetings/buildflags.h"  // PLATFORM_CFM
#include "chrome/browser/apps/platform_apps/app_browsertest_util.h"
#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
#include "chrome/browser/ui/tab_sharing/tab_sharing_infobar_delegate.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/infobars/content/content_infobar_manager.h"
#include "components/infobars/core/infobar.h"
#include "components/permissions/permission_request_manager.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/policy_constants.h"
#include "components/prefs/pref_service.h"
#include "components/url_formatter/elide_url.h"
#include "content/public/browser/audio_service.h"
#include "content/public/browser/host_zoom_map.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/mock_captured_surface_controller.h"
#include "media/audio/audio_features.h"
#include "media/audio/audio_system.h"
#include "media/base/media_switches.h"
#include "net/base/filename_util.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/common/page/page_zoom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/gl/gl_switches.h"

#if BUILDFLAG(IS_MAC)
#include "base/mac/mac_util.h"
#include "chrome/browser/media/webrtc/system_media_capture_permissions_mac.h"
#endif

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

namespace {

using ::base::test::FeatureRef;
using ::content::BrowserThread;
using ::content::CapturedSurfaceController;
using ::content::GlobalRenderFrameHostId;
using ::content::MockCapturedSurfaceController;
using ::content::WebContents;
using ::content::WebContentsMediaCaptureId;
using ::testing::_;
using ::testing::Bool;
using ::testing::Combine;
using ::testing::HasSubstr;
using ::testing::Mock;
using ::testing::Values;
using TabSharingInfoBarButton =
    ::TabSharingInfoBarDelegate::TabSharingInfoBarButton;

using CapturedSurfaceControllerFactoryCallback =
    ::base::RepeatingCallback<std::unique_ptr<MockCapturedSurfaceController>(
        GlobalRenderFrameHostId,
        WebContentsMediaCaptureId)>;

static const char kMainHtmlPage[] = "/webrtc/webrtc_getdisplaymedia_test.html";
static const char kMainHtmlFileName[] = "webrtc_getdisplaymedia_test.html";
static const char kSameOriginRenamedTitle[] = "Renamed Same Origin Tab";
static const char kMainHtmlTitle[] = "WebRTC Automated Test";
// The captured tab is identified by its title.
static const char kCapturedTabTitle[] = "totally-unique-captured-page-title";
static const char kCapturedPageMain[] = "/webrtc/captured_page_main.html";
static const std::u16string kShareThisTabInsteadMessage =
    u"Share this tab instead";

constexpr TabSharingInfoBarButton kCscIndicator =
    TabSharingInfoBarButton::kCapturedSurfaceControlIndicator;

enum class DisplaySurfaceType { kTab, kWindow, kScreen };

enum class GetDisplayMediaVariant : int {
  kStandard = 0,
  kPreferCurrentTab = 1
};

struct TestConfigForPicker {
  TestConfigForPicker(bool should_prefer_current_tab,
                      bool accept_this_tab_capture)
      : should_prefer_current_tab(should_prefer_current_tab),
        accept_this_tab_capture(accept_this_tab_capture) {}

  explicit TestConfigForPicker(std::tuple<bool, bool> input_tuple)
      : TestConfigForPicker(std::get<0>(input_tuple),
                            std::get<1>(input_tuple)) {}

  // If true, specify {preferCurrentTab: true}.
  // Otherwise, either don't specify it, or set it to false.
  bool should_prefer_current_tab;

  // |accept_this_tab_capture| is only applicable if
  // |should_prefer_current_tab| is set to true. Then, setting
  // |accept_this_tab_capture| to true accepts the current tab, and
  // |accept_this_tab_capture| set to false implies dismissing the media picker.
  bool accept_this_tab_capture;
};

struct TestConfigForFakeUI {
  bool should_prefer_current_tab;
  const char* display_surface;
};

struct TestConfigForMediaResolution {
  int constraint_width;
  int constraint_height;
};

struct TestConfigForRestrictOwnAudio {
  TestConfigForRestrictOwnAudio(bool restrict_own_audio,
                                bool suppress_local_audio_playback)
      : restrict_own_audio(restrict_own_audio),
        suppress_local_audio_playback(suppress_local_audio_playback) {}

  explicit TestConfigForRestrictOwnAudio(std::tuple<bool, bool> input_tuple)
      : TestConfigForRestrictOwnAudio(std::get<0>(input_tuple),
                                      std::get<1>(input_tuple)) {}
  bool restrict_own_audio;
  bool suppress_local_audio_playback;
};

constexpr char kAppWindowTitle[] = "AppWindow Display Capture Test";

constexpr char kEmbeddedTestServerOrigin[] = "http://127.0.0.1";
constexpr char kOtherOrigin[] = "https://other-origin.com";

std::string DisplaySurfaceTypeAsString(
    DisplaySurfaceType display_surface_type) {
  switch (display_surface_type) {
    case DisplaySurfaceType::kTab:
      return "browser";
    case DisplaySurfaceType::kWindow:
      return "window";
    case DisplaySurfaceType::kScreen:
      return "screen";
  }
  NOTREACHED();
}

void RunGetDisplayMedia(content::WebContents* tab,
                        const std::string& constraints,
                        bool is_fake_ui,
                        bool expect_success,
                        bool is_tab_capture,
                        const std::string& expected_error = "",
                        bool with_user_gesture = true) {
  DCHECK(!expect_success || expected_error.empty());

  const content::ToRenderFrameHost& adapter = tab->GetPrimaryMainFrame();
  const std::string script = base::StringPrintf(
      "runGetDisplayMedia(%s, \"top-level-document\", \"%s\");",
      constraints.c_str(), expected_error.c_str());
  std::string result =
      content::EvalJs(adapter, script,
                      with_user_gesture
                          ? content::EXECUTE_SCRIPT_DEFAULT_OPTIONS
                          : content::EXECUTE_SCRIPT_NO_USER_GESTURE)
          .ExtractString();

#if BUILDFLAG(IS_MAC)
  if (!is_fake_ui && !is_tab_capture &&
      system_media_permissions::CheckSystemScreenCapturePermission() !=
          system_permission_settings::SystemPermission::kAllowed) {
    expect_success = false;
  }
#endif

  EXPECT_EQ(result, expect_success           ? "capture-success"
                    : expected_error.empty() ? "capture-failure"
                                             : "expected-error");
}

void RunGetUserMedia(content::WebContents* tab,
                     const std::string& constraints) {
  const std::string script =
      base::StrCat({"runGetUserMedia(", constraints, ");"});
  std::string result = content::EvalJs(tab->GetPrimaryMainFrame(), script,
                                       content::EXECUTE_SCRIPT_NO_USER_GESTURE)
                           .ExtractString();
  EXPECT_EQ(result, "gum-success");
}

void StopAllTracks(content::WebContents* tab) {
  EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "stopAllTracks();"),
            "stopped");
}

void UpdateWebContentsTitle(content::WebContents* contents,
                            const std::u16string& title) {
  content::NavigationEntry* entry =
      contents->GetController().GetLastCommittedEntry();
  ASSERT_TRUE(entry);
  contents->UpdateTitleForEntry(entry, title);
}

GURL GetFileURL(const char* filename) {
  base::ScopedAllowBlockingForTesting allow_blocking;
  base::FilePath path;
  base::PathService::Get(chrome::DIR_TEST_DATA, &path);
  path = path.AppendASCII("webrtc").AppendASCII(filename);
  CHECK(base::PathExists(path));
  return net::FilePathToFileURL(path);
}

infobars::ContentInfoBarManager* GetInfoBarManager(
    content::WebContents* web_contents) {
  return infobars::ContentInfoBarManager::FromWebContents(web_contents);
}

TabSharingInfoBarDelegate* GetDelegate(content::WebContents* web_contents,
                                       size_t infobar_index = 0) {
  return static_cast<TabSharingInfoBarDelegate*>(
      GetInfoBarManager(web_contents)->infobars()[infobar_index]->delegate());
}

bool HasCscIndicator(content::WebContents* web_contents) {
  return GetDelegate(web_contents)->GetButtons() & kCscIndicator;
}

bool HasShareThisTabInsteadButton(content::WebContents* web_contents) {
  return GetDelegate(web_contents)->GetButtons() &
         TabSharingInfoBarButton::kShareThisTabInstead;
}

std::u16string GetShareThisTabInsteadButtonLabel(
    content::WebContents* web_contents) {
  DCHECK(HasShareThisTabInsteadButton(web_contents));  // Test error otherwise.
  return GetDelegate(web_contents)
      ->GetButtonLabel(TabSharingInfoBarButton::kShareThisTabInstead);
}

void AdjustCommandLineForZeroCopyCapture(base::CommandLine* command_line) {
  CHECK(command_line);

  // MSan and GL do not get along so avoid using the GPU with MSan.
  // TODO(crbug.com/40260482): Remove this after fixing feature
  // detection in 0c tab capture path as it'll no longer be needed.
#if !BUILDFLAG(IS_CHROMEOS) && !defined(MEMORY_SANITIZER)
  command_line->AppendSwitch(switches::kUseGpuInTests);
#endif
}

// The concept of "zoom level" is overloaded. For clarity, when we mean the
// "factor times 100," we'll just name it "zoom level percentage," at least
// in tests.
int GetZoomLevelPercentage(WebContents* wc) {
  return std::round(100 * blink::ZoomLevelToZoomFactor(
                              content::HostZoomMap::GetZoomLevel(wc)));
}

void SetZoomFactor(WebContents* wc, double zoom_factor) {
  content::HostZoomMap* const host_zoom_map =
      content::HostZoomMap::GetForWebContents(wc);
  CHECK(host_zoom_map);

  host_zoom_map->SetTemporaryZoomLevel(
      wc->GetPrimaryMainFrame()->GetGlobalId(),
      blink::ZoomFactorToZoomLevel(zoom_factor));

  if (!blink::ZoomValuesEqual(GetZoomLevelPercentage(wc), 100 * zoom_factor)) {
    FAIL();  // Abort test, not just the helper method.
  }
}

}  // namespace

// Base class for top level tests for getDisplayMedia().
class WebRtcScreenCaptureBrowserTest : public WebRtcTestBase {
 public:
  ~WebRtcScreenCaptureBrowserTest() override = default;

  void SetUpInProcessBrowserTestFixture() override {
    DetectErrorsInJavaScript();
  }

  virtual bool PreferCurrentTab() const = 0;

  std::string GetConstraints(bool video,
                             bool audio,
                             bool prefer_current_tab) const {
    return base::StringPrintf("{video: %s, audio: %s, preferCurrentTab: %s}",
                              base::ToString(video), base::ToString(audio),
                              base::ToString(prefer_current_tab));
  }

  std::string GetConstraints(bool video,
                             bool audio,
                             GetDisplayMediaVariant variant) const {
    return GetConstraints(video, audio,
                          variant == GetDisplayMediaVariant::kPreferCurrentTab);
  }

  std::string GetConstraints(bool video, bool audio) const {
    return GetConstraints(video, audio, PreferCurrentTab());
  }
};

// Top level test for getDisplayMedia().
// Pops picker UI and shares by default.
class WebRtcScreenCaptureBrowserTestWithPicker
    : public WebRtcScreenCaptureBrowserTest,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  WebRtcScreenCaptureBrowserTestWithPicker() : test_config_(GetParam()) {}

  void SetUpOnMainThread() override {
    WebRtcScreenCaptureBrowserTest::SetUpOnMainThread();
#if BUILDFLAG(PLATFORM_CFM)
    if (test_config_.should_prefer_current_tab &&
        !test_config_.accept_this_tab_capture) {
      GTEST_SKIP() << "Skipping test: CFMs always automatically accept "
                      "current-tab captures.";
    }
#endif

#if BUILDFLAG(IS_WIN)
    if (!test_config_.should_prefer_current_tab) {
      // Win bots don't usually have GPU adapter, therefore DXGI capturers
      // aren't available. WGC capturers aren't available on older platforms.
      // Therefore we can't capture screens.
      GTEST_SKIP() << "Skipping desktop capture tests";
    }
#endif
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
    if (test_config_.should_prefer_current_tab) {
      command_line->AppendSwitch(test_config_.accept_this_tab_capture
                                     ? switches::kThisTabCaptureAutoAccept
                                     : switches::kThisTabCaptureAutoReject);
    } else {
#if BUILDFLAG(IS_CHROMEOS)
      command_line->AppendSwitchASCII(switches::kAutoSelectDesktopCaptureSource,
                                      "Display");
#else
      command_line->AppendSwitchASCII(switches::kAutoSelectDesktopCaptureSource,
                                      "Entire screen");
#endif  // BUILDFLAG(IS_CHROMEOS)
    }
  }

  bool PreferCurrentTab() const override {
    return test_config_.should_prefer_current_tab;
  }

  const TestConfigForPicker test_config_;
};

INSTANTIATE_TEST_SUITE_P(
    All,
    WebRtcScreenCaptureBrowserTestWithPicker,
    Combine(
        /*should_prefer_current_tab=*/Bool(),
        /*accept_this_tab_capture=*/Bool()),
    [](const testing::TestParamInfo<
        WebRtcScreenCaptureBrowserTestWithPicker::ParamType>& info) {
      return base::JoinString(
          {std::get<0>(info.param) ? "PreferCurrentTab" : "Standard",
           std::get<1>(info.param) ? "Accept" : "Reject"},
          "_");
    });

// TODO(crbug.com/40744542): Real desktop capture is flaky on below platforms.
// TODO(crbug.com/41493366): enable this flaky test.
// TODO(crbug.com/461258716): enable this flaky test.
#if BUILDFLAG(IS_MAC) || (BUILDFLAG(IS_LINUX) && defined(MEMORY_SANITIZER))
#define MAYBE_ScreenCaptureVideo DISABLED_ScreenCaptureVideo
#else
#define MAYBE_ScreenCaptureVideo ScreenCaptureVideo
#endif  // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC) || (BUILDFLAG(IS_LINUX) &&
        // defined(MEMORY_SANITIZER))
IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestWithPicker,
                       MAYBE_ScreenCaptureVideo) {
  if (!test_config_.should_prefer_current_tab &&
      !test_config_.accept_this_tab_capture) {
    GTEST_SKIP();
  }

  ASSERT_TRUE(embedded_test_server()->Start());

  content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
  RunGetDisplayMedia(tab,
                     GetConstraints(
                         /*video=*/true, /*audio=*/false),
                     /*is_fake_ui=*/false,
                     /*expect_success=*/test_config_.accept_this_tab_capture,
                     /*is_tab_capture=*/PreferCurrentTab());
}

#if BUILDFLAG(IS_CHROMEOS)
IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestWithPicker,
                       ScreenCaptureVideoWithDlp) {
  if (!test_config_.should_prefer_current_tab &&
      !test_config_.accept_this_tab_capture) {
    GTEST_SKIP();
  }

  ASSERT_TRUE(embedded_test_server()->Start());

  policy::DlpContentManagerTestHelper helper;
  content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
  RunGetDisplayMedia(tab,
                     GetConstraints(
                         /*video=*/true, /*audio=*/false),
                     /*is_fake_ui=*/false,
                     /*expect_success=*/test_config_.accept_this_tab_capture,
                     /*is_tab_capture=*/PreferCurrentTab());

  if (!test_config_.accept_this_tab_capture) {
    // This test is not relevant for this parameterized test case because it
    // does not capture the tab/display surface.
    return;
  }

  EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "waitVideoUnmuted();"),
            "unmuted");

  const policy::DlpContentRestrictionSet kScreenShareRestricted(
      policy::DlpContentRestriction::kScreenShare,
      policy::DlpRulesManager::Level::kBlock);

  helper.ChangeConfidentiality(tab, kScreenShareRestricted);
  content::WaitForLoadStop(tab);

  EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "waitVideoMuted();"),
            "muted");

  const policy::DlpContentRestrictionSet kEmptyRestrictionSet;
  helper.ChangeConfidentiality(tab, kEmptyRestrictionSet);

  EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "waitVideoUnmuted();"),
            "unmuted");
}
#endif  // BUILDFLAG(IS_CHROMEOS)

// TODO(crbug.com/40744542): Real desktop capture is flaky on below platforms.
// TODO(crbug.com/41493366): enable this flaky test.
// TODO(crbug.com/461258716): enable this flaky test.
#if BUILDFLAG(IS_MAC)
#define MAYBE_ScreenCaptureVideoAndAudio DISABLED_ScreenCaptureVideoAndAudio
// On linux debug bots, it's flaky as well.
#elif BUILDFLAG(IS_LINUX) && !defined(NDEBUG)
#define MAYBE_ScreenCaptureVideoAndAudio DISABLED_ScreenCaptureVideoAndAudio
// On linux asan and msan bots, it's flaky as well.
#elif BUILDFLAG(IS_LINUX) && \
    (defined(ADDRESS_SANITIZER) || defined(MEMORY_SANITIZER))
#define MAYBE_ScreenCaptureVideoAndAudio DISABLED_ScreenCaptureVideoAndAudio
#else
#define MAYBE_ScreenCaptureVideoAndAudio ScreenCaptureVideoAndAudio
#endif  // BUILDFLAG(IS_WIN)
IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestWithPicker,
                       MAYBE_ScreenCaptureVideoAndAudio) {
  if (!test_config_.should_prefer_current_tab &&
      !test_config_.accept_this_tab_capture) {
    GTEST_SKIP();
  }

  ASSERT_TRUE(embedded_test_server()->Start());

  content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
  RunGetDisplayMedia(tab,
                     GetConstraints(
                         /*video=*/true, /*audio=*/true),
                     /*is_fake_ui=*/false,
                     /*expect_success=*/test_config_.accept_this_tab_capture,
                     /*is_tab_capture=*/PreferCurrentTab());
}

// Top level test for getDisplayMedia().
// Skips picker UI and uses fake device with specified type.
class WebRtcScreenCaptureBrowserTestWithFakeUI
    : public WebRtcScreenCaptureBrowserTest,
      public testing::WithParamInterface<TestConfigForFakeUI> {
 public:
  WebRtcScreenCaptureBrowserTestWithFakeUI() : test_config_(GetParam()) {}

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
    command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
    command_line->RemoveSwitch(switches::kUseFakeDeviceForMediaStream);
    command_line->AppendSwitchASCII(
        switches::kUseFakeDeviceForMediaStream,
        base::StringPrintf("display-media-type=%s",
                           test_config_.display_surface));

    AdjustCommandLineForZeroCopyCapture(command_line);
  }

  bool PreferCurrentTab() const override {
    return test_config_.should_prefer_current_tab;
  }

 protected:
  const TestConfigForFakeUI test_config_;
};

// TODO(crbug.com/461258716): enable this flaky test.
#if (BUILDFLAG(IS_LINUX) && defined(MEMORY_SANITIZER))
#define MAYBE_ScreenCaptureVideo_1 DISABLED_ScreenCaptureVideo
#else
#define MAYBE_ScreenCaptureVideo_1 ScreenCaptureVideo
#endif  // (BUILDFLAG(IS_LINUX) && defined(MEMORY_SANITIZER))
IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestWithFakeUI,
                       MAYBE_ScreenCaptureVideo_1) {
  ASSERT_TRUE(embedded_test_server()->Start());

  content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
  RunGetDisplayMedia(tab,
                     GetConstraints(
                         /*video=*/true, /*audio=*/false),
                     /*is_fake_ui=*/true, /*expect_success=*/true,
                     /*is_tab_capture=*/PreferCurrentTab());

  EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(),
                            "getDisplaySurfaceSetting();"),
            test_config_.display_surface);

  EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(),
                            "getLogicalSurfaceSetting();"),
            "true");

  EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "getCursorSetting();"),
            "never");
}

IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestWithFakeUI,
                       ScreenCaptureVideoAndAudio) {
  ASSERT_TRUE(embedded_test_server()->Start());

  content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
  RunGetDisplayMedia(tab,
                     GetConstraints(
                         /*video=*/true, /*audio=*/true),
                     /*is_fake_ui=*/true, /*expect_success=*/true,
                     /*is_tab_capture=*/PreferCurrentTab());

  EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "hasAudioTrack();"),
            "true");
}

// TODO(crbug.com/461258716): enable this flaky test.
#if (BUILDFLAG(IS_LINUX) && defined(MEMORY_SANITIZER))
#define MAYBE_ScreenCaptureWithConstraints DISABLED_ScreenCaptureWithConstraints
#else
#define MAYBE_ScreenCaptureWithConstraints ScreenCaptureWithConstraints
#endif  // (BUILDFLAG(IS_LINUX) && defined(MEMORY_SANITIZER))
IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestWithFakeUI,
                       MAYBE_ScreenCaptureWithConstraints) {
  ASSERT_TRUE(embedded_test_server()->Start());

  content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
  const int kMaxWidth = 200;
  const int kMaxFrameRate = 6;
  const std::string constraints = base::StringPrintf(
      "{video: {width: {max: %d}, frameRate: {max: %d}}, "
      "should_prefer_current_tab: "
      "%s}",
      kMaxWidth, kMaxFrameRate,
      base::ToString(test_config_.should_prefer_current_tab));
  RunGetDisplayMedia(tab, constraints,
                     /*is_fake_ui=*/true, /*expect_success=*/true,
                     /*is_tab_capture=*/PreferCurrentTab());
  auto result =
      content::EvalJs(tab->GetPrimaryMainFrame(), "getWidthSetting();")
          .ExtractString();
  int value;
  EXPECT_TRUE(base::StringToInt(result, &value));
  EXPECT_LE(value, kMaxWidth);

  EXPECT_EQ(
      content::EvalJs(tab->GetPrimaryMainFrame(), "getFrameRateSetting();"),
      base::StringPrintf("%d", kMaxFrameRate));
}

INSTANTIATE_TEST_SUITE_P(
    All,
    WebRtcScreenCaptureBrowserTestWithFakeUI,
    Values(TestConfigForFakeUI{/*should_prefer_current_tab=*/false,
                               /*display_surface=*/"monitor"},
           TestConfigForFakeUI{/*should_prefer_current_tab=*/false,
                               /*display_surface=*/"window"},
           TestConfigForFakeUI{/*should_prefer_current_tab=*/false,
                               /*display_surface=*/"browser"},
           TestConfigForFakeUI{/*should_prefer_current_tab=*/true,
                               /*display_surface=*/"browser"}),
    [](const testing::TestParamInfo<
        WebRtcScreenCaptureBrowserTestWithFakeUI::ParamType>& info) {
      return base::JoinString(
          {info.param.should_prefer_current_tab ? "PreferCurrentTab"
                                                : "Standard",
           info.param.display_surface},
          "_");
    });

class WebRtcScreenCapturePermissionPolicyBrowserTest
    : public WebRtcScreenCaptureBrowserTest,
      public testing::WithParamInterface<
          std::tuple<GetDisplayMediaVariant, bool>> {
 public:
  WebRtcScreenCapturePermissionPolicyBrowserTest()
      : tested_variant_(std::get<0>(GetParam())),
        allowlisted_by_policy_(std::get<1>(GetParam())) {}

  ~WebRtcScreenCapturePermissionPolicyBrowserTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
    command_line->AppendSwitchASCII(
        switches::kAutoSelectTabCaptureSourceByTitle, kMainHtmlTitle);
  }

  bool PreferCurrentTab() const override {
    return tested_variant_ == GetDisplayMediaVariant::kPreferCurrentTab;
  }

 protected:
  const GetDisplayMediaVariant tested_variant_;
  const bool allowlisted_by_policy_;
};

INSTANTIATE_TEST_SUITE_P(
    All,
    WebRtcScreenCapturePermissionPolicyBrowserTest,
    Combine(Values(GetDisplayMediaVariant::kStandard,
                   GetDisplayMediaVariant::kPreferCurrentTab),
            /*allowlisted_by_policy=*/Bool()),
    [](const testing::TestParamInfo<
        WebRtcScreenCapturePermissionPolicyBrowserTest::ParamType>& info) {
      return base::JoinString(
          {std::get<0>(info.param) == GetDisplayMediaVariant::kPreferCurrentTab
               ? "PreferCurrentTab"
               : "Standard",
           std::get<1>(info.param) ? "Allowlisted" : "NotAllowlisted"},
          "_");
    });

// Flaky on Win bots http://crbug.com/40800993
#if BUILDFLAG(IS_WIN)
#define MAYBE_ScreenShareFromEmbedded DISABLED_ScreenShareFromEmbedded
#else
#define MAYBE_ScreenShareFromEmbedded ScreenShareFromEmbedded
#endif
IN_PROC_BROWSER_TEST_P(WebRtcScreenCapturePermissionPolicyBrowserTest,
                       MAYBE_ScreenShareFromEmbedded) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // The use of selfBrowserSurface is in order to simplify the test by
  // using just one tab. It is orthogonal to the test's purpose.
  const std::string constraints = base::StringPrintf(
      "{video: true, selfBrowserSurface: 'include', preferCurrentTab: %s}",
      base::ToString(PreferCurrentTab()));

  EXPECT_EQ(
      content::EvalJs(
          OpenTestPageInNewTab(kMainHtmlPage)->GetPrimaryMainFrame(),
          base::StringPrintf(
              "runGetDisplayMedia(%s, \"%s\");", constraints.c_str(),
              allowlisted_by_policy_ ? "allowedFrame" : "disallowedFrame")),
      allowlisted_by_policy_ ? "embedded-capture-success"
                             : "embedded-capture-failure");
}

// Test class used to test WebRTC with App Windows. Unfortunately, due to
// creating a diamond pattern of inheritance, we can only inherit from one of
// the PlatformAppBrowserTest and WebRtcBrowserTestBase (or it's children).
// We need a lot more heavy lifting on creating the AppWindow than we would get
// from WebRtcBrowserTestBase; so we inherit from PlatformAppBrowserTest to
// minimize the code duplication.
class WebRtcAppWindowCaptureBrowserTestWithPicker
    : public extensions::PlatformAppBrowserTest {
 public:
  WebRtcAppWindowCaptureBrowserTestWithPicker() = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    PlatformAppBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
    command_line->AppendSwitchASCII(
        switches::kAutoSelectTabCaptureSourceByTitle, kAppWindowTitle);

    AdjustCommandLineForZeroCopyCapture(command_line);
  }

  void SetUpOnMainThread() override {
    extensions::PlatformAppBrowserTest::SetUpOnMainThread();
    ASSERT_TRUE(StartEmbeddedTestServer());

    // We will restrict all pages to "Tab Capture" only. This should force App
    // Windows to show up in the tabs list, and thus make it selectable.
    base::ListValue matchlist;
    matchlist.Append("*");
    browser()->GetProfile()->GetPrefs()->SetList(
        prefs::kTabCaptureAllowedByOrigins, std::move(matchlist));
  }

  void TearDownOnMainThread() override {
    extensions::PlatformAppBrowserTest::TearDownOnMainThread();
    browser()->GetProfile()->GetPrefs()->SetList(
        prefs::kTabCaptureAllowedByOrigins, base::ListValue());
  }

  extensions::AppWindow* CreateAppWindowWithTitle(const std::u16string& title) {
    extensions::AppWindow* app_window = CreateTestAppWindow("{}");
    EXPECT_TRUE(app_window);
    UpdateWebContentsTitle(app_window->web_contents(), title);

    return app_window;
  }

  // This is mostly lifted from WebRtcBrowserTestBase, with the exception that
  // because we know we're setting the auto-accept switches, we don't need to
  // set the PermissionsManager auto accept.
  content::WebContents* OpenTestPageInNewTab(const std::string& test_url) {
    chrome::AddTabAt(browser(), GURL(url::kAboutBlankURL), -1, true);
    GURL url = embedded_test_server()->GetURL(test_url);
    EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
    return browser()->GetTabStripModel()->GetActiveWebContents();
  }
};

IN_PROC_BROWSER_TEST_F(WebRtcAppWindowCaptureBrowserTestWithPicker,
                       CaptureAppWindow) {
  extensions::AppWindow* app_window =
      CreateAppWindowWithTitle(base::UTF8ToUTF16(std::string(kAppWindowTitle)));
  content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage);

  RunGetDisplayMedia(capturing_tab, "{video: true}", /*is_fake_ui=*/false,
                     /*expect_success=*/true,
                     /*is_tab_capture=*/true);
  CloseAppWindow(app_window);
}

// Base class for running tests with a SameOrigin policy applied.
class WebRtcSameOriginPolicyBrowserTest
    : public WebRtcScreenCaptureBrowserTest {
 public:
  ~WebRtcSameOriginPolicyBrowserTest() override = default;

  bool PreferCurrentTab() const override { return false; }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    WebRtcScreenCaptureBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
    command_line->AppendSwitchASCII(
        switches::kAutoSelectTabCaptureSourceByTitle, kSameOriginRenamedTitle);

    AdjustCommandLineForZeroCopyCapture(command_line);
  }

  void SetUpOnMainThread() override {
    WebRtcScreenCaptureBrowserTest::SetUpOnMainThread();
    ASSERT_TRUE(embedded_test_server()->Start());

    // Restrict all origins to SameOrigin tab capture only.
    base::ListValue matchlist;
    matchlist.Append("*");
    browser()->GetProfile()->GetPrefs()->SetList(
        prefs::kSameOriginTabCaptureAllowedByOrigins, std::move(matchlist));
  }

  void TearDownOnMainThread() override {
    WebRtcScreenCaptureBrowserTest::TearDownOnMainThread();
    browser()->GetProfile()->GetPrefs()->SetList(
        prefs::kSameOriginTabCaptureAllowedByOrigins, base::ListValue());
  }
};

IN_PROC_BROWSER_TEST_F(WebRtcSameOriginPolicyBrowserTest,
                       TerminateOnNavigationAwayFromSameOrigin) {
  // Open two pages, one to be captured, and one to do the capturing. Note that
  // we open the capturing page second so that is focused to allow the
  // getDisplayMedia request to succeed.
  content::WebContents* target_tab = OpenTestPageInNewTab(kMainHtmlPage);
  content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage);

  // Update the target tab to a unique title, so that we can ensure that it is
  // the one that gets captured via the autoselection.
  UpdateWebContentsTitle(
      target_tab, base::UTF8ToUTF16(std::string(kSameOriginRenamedTitle)));
  RunGetDisplayMedia(capturing_tab,
                     GetConstraints(
                         /*video=*/true, /*audio=*/true),
                     /*is_fake_ui=*/false, /*expect_success=*/true,
                     /*is_tab_capture=*/true);

  // Though the target tab should've been focused as a result of starting the
  // capture, we don't want to take a dependency on that behavior. Ensure that
  // the target tab is focused, so that we can navigate it easily. If it is
  // already focused, this will just no-op.
  int target_index =
      browser()->GetTabStripModel()->GetIndexOfWebContents(target_tab);
  browser()->GetTabStripModel()->ActivateTabAt(
      target_index, TabStripUserGestureDetails(
                        TabStripUserGestureDetails::GestureType::kOther));
  ASSERT_EQ(target_tab, browser()->GetTabStripModel()->GetActiveWebContents());

  // We navigate to a FileURL so that the origin will change, which should
  // trigger the capture to end.
  ASSERT_TRUE(
      ui_test_utils::NavigateToURL(browser(), GetFileURL(kMainHtmlFileName)));

  // Verify that the video stream has ended.
  EXPECT_EQ(content::EvalJs(capturing_tab->GetPrimaryMainFrame(),
                            "waitVideoEnded();"),
            "ended");
}

IN_PROC_BROWSER_TEST_F(WebRtcSameOriginPolicyBrowserTest,
                       ContinueCapturingForSameOriginNavigation) {
  // Open two pages, one to be captured, and one to do the capturing. Note that
  // we open the capturing page second so that is focused to allow the
  // getDisplayMedia request to succeed.
  content::WebContents* target_tab = OpenTestPageInNewTab(kMainHtmlPage);
  content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage);

  // Update the target tab to a unique title, so that we can ensure that it is
  // the one that gets captured via the autoselection.
  UpdateWebContentsTitle(
      target_tab, base::UTF8ToUTF16(std::string(kSameOriginRenamedTitle)));
  RunGetDisplayMedia(capturing_tab,
                     GetConstraints(
                         /*video=*/true, /*audio=*/true),
                     /*is_fake_ui=*/false, /*expect_success=*/true,
                     /*is_tab_capture=*/true);

  // Though the target tab should've been focused as a result of starting the
  // capture, we don't want to take a dependency on that behavior. Ensure that
  // the target tab is focused, so that we can navigate it easily. If it is
  // already focused, this will just no-op.
  int target_index =
      browser()->GetTabStripModel()->GetIndexOfWebContents(target_tab);
  browser()->GetTabStripModel()->ActivateTabAt(
      target_index, TabStripUserGestureDetails(
                        TabStripUserGestureDetails::GestureType::kOther));
  ASSERT_EQ(target_tab, browser()->GetTabStripModel()->GetActiveWebContents());

  // We navigate using the test server so that the origin doesn't change.
  ASSERT_TRUE(ui_test_utils::NavigateToURL(
      browser(),
      embedded_test_server()->GetURL("/webrtc/captured_page_main.html")));

  // Verify that the video hasn't been ended.
  EXPECT_EQ(content::EvalJs(capturing_tab->GetPrimaryMainFrame(),
                            "video_track.readyState;"),
            "live");
}

class GetDisplayMediaVideoTrackBrowserTest
    : public WebRtcTestBase,
      public testing::WithParamInterface<std::tuple<bool, DisplaySurfaceType>> {
 public:
  GetDisplayMediaVideoTrackBrowserTest()
      : region_capture_enabled_(std::get<0>(GetParam())),
        display_surface_type_(std::get<1>(GetParam())) {}

  ~GetDisplayMediaVideoTrackBrowserTest() override = default;

  void SetUpInProcessBrowserTestFixture() override {
    DetectErrorsInJavaScript();
  }

  void SetUpOnMainThread() override {
    WebRtcTestBase::SetUpOnMainThread();

    ASSERT_TRUE(embedded_test_server()->Start());
  }

  // Unlike SetUp(), this is called from the test body. This allows skipping
  // this test for (platform, test-case) combinations which are not supported.
  void SetupTest() {
    // Fire up the page.
    tab_ = OpenTestPageInNewTab(kMainHtmlPage);

    // Initiate the capture.
    ASSERT_EQ("capture-success",
              content::EvalJs(tab_->GetPrimaryMainFrame(),
                              "runGetDisplayMedia({video: true, audio: true}, "
                              "\"top-level-document\");"));
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    WebRtcTestBase::SetUpCommandLine(command_line);

    std::vector<std::string> enabled_blink_features;
    std::vector<std::string> disabled_blink_features;

    if (region_capture_enabled_) {
      enabled_blink_features.push_back("RegionCapture");
    } else {
      disabled_blink_features.push_back("RegionCapture");
    }

    if (!enabled_blink_features.empty()) {
      command_line->AppendSwitchASCII(
          switches::kEnableBlinkFeatures,
          base::JoinString(enabled_blink_features, ","));
    }

    if (!disabled_blink_features.empty()) {
      command_line->AppendSwitchASCII(
          switches::kDisableBlinkFeatures,
          base::JoinString(disabled_blink_features, ","));
    }

    command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
    command_line->AppendSwitchASCII(
        switches::kUseFakeDeviceForMediaStream,
        base::StrCat({"display-media-type=",
                      DisplaySurfaceTypeAsString(display_surface_type_)}));

    AdjustCommandLineForZeroCopyCapture(command_line);
  }

  std::string GetVideoTrackType() {
    return content::EvalJs(tab_->GetPrimaryMainFrame(), "getVideoTrackType();")
        .ExtractString();
  }

  std::string GetVideoCloneTrackType() {
    return content::EvalJs(tab_->GetPrimaryMainFrame(),
                           "getVideoCloneTrackType();")
        .ExtractString();
  }

  bool HasAudioTrack() {
    std::string result =
        content::EvalJs(tab_->GetPrimaryMainFrame(), "hasAudioTrack();")
            .ExtractString();
    EXPECT_TRUE(result == "true" || result == "false");
    return result == "true";
  }

  std::string GetAudioTrackType() {
    return content::EvalJs(tab_->GetPrimaryMainFrame(), "getAudioTrackType();")
        .ExtractString();
  }

  std::string ExpectedVideoTrackType() const {
    switch (display_surface_type_) {
      case DisplaySurfaceType::kTab:
        return region_capture_enabled_ ? "BrowserCaptureMediaStreamTrack"
                                       : "MediaStreamTrack";
      case DisplaySurfaceType::kWindow:
      case DisplaySurfaceType::kScreen:
        return "MediaStreamTrack";
    }
    NOTREACHED();
  }

 protected:
  const bool region_capture_enabled_;
  const DisplaySurfaceType display_surface_type_;

 private:
  raw_ptr<content::WebContents, AcrossTasksDanglingUntriaged> tab_ = nullptr;
};

INSTANTIATE_TEST_SUITE_P(
    _,
    GetDisplayMediaVideoTrackBrowserTest,
    Combine(/*region_capture_enabled=*/Bool(),
            /*display_surface_type=*/
            Values(DisplaySurfaceType::kTab,
                   DisplaySurfaceType::kWindow,
                   DisplaySurfaceType::kScreen)),
    [](const testing::TestParamInfo<
        GetDisplayMediaVideoTrackBrowserTest::ParamType>& info) {
      return base::JoinString(
          {std::get<0>(info.param) ? "RegionCapture" : "",
           std::get<1>(info.param) == DisplaySurfaceType::kTab      ? "Tab"
           : std::get<1>(info.param) == DisplaySurfaceType::kWindow ? "Window"
                                                                    : "Screen"},
          "_");
    });

// Normally, each of these these would have its own test, but the number of
// combinations and the setup time for browser-tests make this undesirable,
// especially given the simplicity of each of these tests.
// After both (a) Conditional Focus and (b) Region Capture ship, this can
// simpplified to three non-parameterized tests (tab/window/screen).
IN_PROC_BROWSER_TEST_P(GetDisplayMediaVideoTrackBrowserTest, RunCombinedTest) {
  SetupTest();

  // Test #1: The video track is of the expected type.
  EXPECT_EQ(GetVideoTrackType(), ExpectedVideoTrackType());

  // Test #2: Video clones are of the same type as the original.
  EXPECT_EQ(GetVideoTrackType(), GetVideoCloneTrackType());

  // Test #3: Audio tracks are all simply MediaStreamTrack.
  if (HasAudioTrack()) {
    EXPECT_EQ(GetAudioTrackType(), "MediaStreamTrack");
  }
}

// Flaky on Mac, Windows, and ChromeOS bots, https://crbug.com/40241323
// Also some flakes on Linux ASAN/MSAN builds.
#if BUILDFLAG(IS_LINUX) && \
    !(defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER))
class GetDisplayMediaHiDpiBrowserTest
    : public WebRtcTestBase,
      public testing::WithParamInterface<TestConfigForMediaResolution> {
 public:
  GetDisplayMediaHiDpiBrowserTest() : test_config_(GetParam()) {}

  // The browser window size must be consistent with the
  // INSTANTIATE_TEST_SUITE_P TestConfigForMediaResolution configurations below.
  // See the comments there for more details.
  static constexpr int kBrowserWindowWidth = 800;
  static constexpr int kBrowserWindowHeight = 600;

  int constraint_width() const { return test_config_.constraint_width; }
  int constraint_height() const { return test_config_.constraint_height; }

  void SetUpInProcessBrowserTestFixture() override {
    WebRtcTestBase::SetUpInProcessBrowserTestFixture();
    DetectErrorsInJavaScript();
  }

  void SetUpOnMainThread() override {
    WebRtcTestBase::SetUpOnMainThread();

    ASSERT_TRUE(embedded_test_server()->Start());

    // Fire up the page.
    tab_ = OpenTestPageInNewTab(kMainHtmlPage);
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    WebRtcTestBase::SetUpCommandLine(command_line);

    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);

    command_line->AppendSwitch(switches::kThisTabCaptureAutoAccept);

    command_line->AppendSwitchASCII(
        switches::kWindowSize,
        base::StringPrintf("%d,%d", kBrowserWindowWidth, kBrowserWindowHeight));

    // Optionally, in case the test isn't working correctly, you can turn on
    // debug logging for the feature to help track down problems. For example:
    // command_line->AppendSwitchASCII(switches::kVModule,
    //                                "*host_view*=1,*frame_tracker*=3");
  }

  std::string ResizeVideoForHiDpiCapture(int width, int height) {
    return RunJs(base::StringPrintf("resizeVideoForHiDpiCapture(%d, %d);",
                                    width, height));
  }

  double GetDevicePixelRatio() {
    std::string result = RunJs("getDevicePixelRatio();");
    double device_pixel_ratio;
    EXPECT_TRUE(base::StringToDouble(result, &device_pixel_ratio));
    return device_pixel_ratio;
  }

  std::string GetDisplaySurfaceSetting() {
    return RunJs("getDisplaySurfaceSetting();");
  }

  std::string GetLogicalSurfaceSetting() {
    return RunJs("getLogicalSurfaceSetting();");
  }

  content::WebContents* Tab() const { return tab_; }

 private:
  std::string RunJs(const std::string& command) {
    return content::EvalJs(tab_->GetPrimaryMainFrame(), command)
        .ExtractString();
  }

  const TestConfigForMediaResolution test_config_;
  raw_ptr<content::WebContents, AcrossTasksDanglingUntriaged> tab_ = nullptr;
};

IN_PROC_BROWSER_TEST_P(GetDisplayMediaHiDpiBrowserTest, Capture) {
  ASSERT_EQ(GetDevicePixelRatio(), 1.0);

  // Initiate the capture.
  RunGetDisplayMedia(
      Tab(),
      base::StringPrintf("{video: {width: {max: %d}, height: {max: %d}}, "
                         "preferCurrentTab: true}",
                         constraint_width(), constraint_height()),
      /*is_fake_ui=*/false, /*expect_success=*/true,
      /*is_tab_capture=*/true);

  // Ensure that the video is larger than the source tab to encourage use of a
  // higher-resolution video stream. The size is arbitrary, but it should be
  // significantly bigger than the switches::kWindowSize configured in this
  // test's setup.
  EXPECT_EQ(ResizeVideoForHiDpiCapture(kBrowserWindowWidth * 2,
                                       kBrowserWindowHeight * 2),
            "success");

  EXPECT_EQ(GetDisplaySurfaceSetting(), "browser");

  EXPECT_EQ(GetLogicalSurfaceSetting(), "true");

  // The HiDPI scale change only occurs once the capture has actually started
  // and the size information was propagated back to the browser process.
  // Waiting for the video to start playing helps ensure that this is the case.
  StartDetectingVideo(Tab(), "video");
  WaitForVideoToPlay(Tab());

  // If the video size is higher resolution than the browser window
  // size, expect that HiDPI mode should be active.
  bool expect_hidpi = constraint_width() > kBrowserWindowWidth &&
                      constraint_height() > kBrowserWindowHeight;

  double device_pixel_ratio = GetDevicePixelRatio();
  if (expect_hidpi) {
    EXPECT_GT(device_pixel_ratio, 1.0);
    EXPECT_LE(device_pixel_ratio, 2.0);
  } else {
    EXPECT_EQ(device_pixel_ratio, 1.0);
  }
}

INSTANTIATE_TEST_SUITE_P(
    All,
    GetDisplayMediaHiDpiBrowserTest,
    // The test configurations use both large and small constraint sizes. The
    // small constraint sizes must be smaller than the configured window size
    // (cf. kBrowserWindowWidth and kBrowserWindowHeight in
    // GetDisplayMediaHiDpiBrowserTest above), and the large sizes must be
    // significantly larger than the browser window size.
    Values(TestConfigForMediaResolution{/*constraint_width=*/640,
                                        /*constraint_height=*/480},
           TestConfigForMediaResolution{/*constraint_width=*/3840,
                                        /*constraint_height=*/2160}),
    [](const testing::TestParamInfo<TestConfigForMediaResolution>& info) {
      return base::StringPrintf("%dx%d", info.param.constraint_width,
                                info.param.constraint_height);
    });
#endif

class GetDisplayMediaChangeSourceBrowserTest
    : public WebRtcTestBase,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  GetDisplayMediaChangeSourceBrowserTest()
      : dynamic_surface_switching_requested_(std::get<0>(GetParam())),
        user_shared_audio_(std::get<1>(GetParam())) {}
  ~GetDisplayMediaChangeSourceBrowserTest() override = default;

  void SetUp() override {
    // TODO(crbug.com/40245399): Fix GetDisplayMediaChangeSourceBrowserTest with
    // audio requested on ChromeOS
#if BUILDFLAG(IS_CHROMEOS)
    if (dynamic_surface_switching_requested_ && user_shared_audio_) {
      GTEST_SKIP();
    }
#endif
    WebRtcTestBase::SetUp();
  }

  void SetUpInProcessBrowserTestFixture() override {
    WebRtcTestBase::SetUpInProcessBrowserTestFixture();

    DetectErrorsInJavaScript();

    base::FilePath test_dir;
    ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
    command_line->AppendSwitchASCII(
        switches::kAutoSelectTabCaptureSourceByTitle, kCapturedTabTitle);

    AdjustCommandLineForZeroCopyCapture(command_line);

    if (!user_shared_audio_) {
      command_line->AppendSwitch(switches::kTabCaptureAudioDefaultUnchecked);
    }
  }

  std::string GetConstraints() const {
    return base::StringPrintf(
        "{video: true, audio: true, surfaceSwitching: \"%s\"}",
        dynamic_surface_switching_requested_ ? "include" : "exclude");
  }

  bool ShouldShowShareThisTabInsteadButton() const {
    return dynamic_surface_switching_requested_;
  }

 private:
  const bool dynamic_surface_switching_requested_;
  const bool user_shared_audio_;
};

INSTANTIATE_TEST_SUITE_P(
    All,
    GetDisplayMediaChangeSourceBrowserTest,
    Combine(Bool(), Bool()),
    [](const testing::TestParamInfo<
        GetDisplayMediaChangeSourceBrowserTest::ParamType>& info) {
      return base::JoinString(
          {
              std::get<0>(info.param) ? "Dynamic" : "Static",
              std::get<1>(info.param) ? "UserSharedAudio"
                                      : "UserDidNotShareAudio",
          },
          "_");
    });

// TODO(crbug.com/40900706) Re-enable flaky test.
IN_PROC_BROWSER_TEST_P(GetDisplayMediaChangeSourceBrowserTest,
                       DISABLED_ChangeSource) {
  ASSERT_TRUE(embedded_test_server()->Start());
  content::WebContents* captured_tab = OpenTestPageInNewTab(kCapturedPageMain);
  content::WebContents* other_tab = OpenTestPageInNewTab(kMainHtmlPage);
  content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage);

  RunGetDisplayMedia(capturing_tab, GetConstraints(), /*is_fake_ui=*/false,
                     /*expect_success=*/true,
                     /*is_tab_capture=*/true);

  EXPECT_TRUE(captured_tab->IsBeingCaptured());
  EXPECT_FALSE(other_tab->IsBeingCaptured());
  EXPECT_FALSE(capturing_tab->IsBeingCaptured());
  EXPECT_EQ(
      GetShareThisTabInsteadButtonLabel(captured_tab),
      l10n_util::GetStringFUTF16(
          IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON,
          url_formatter::FormatOriginForSecurityDisplay(
              captured_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
              url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS)));
  EXPECT_EQ(
      GetShareThisTabInsteadButtonLabel(capturing_tab),
      l10n_util::GetStringFUTF16(
          IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON,
          url_formatter::FormatOriginForSecurityDisplay(
              capturing_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
              url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS)));
  if (!ShouldShowShareThisTabInsteadButton()) {
    EXPECT_FALSE(HasShareThisTabInsteadButton(other_tab));
    return;
  }
  EXPECT_EQ(GetShareThisTabInsteadButtonLabel(other_tab),
            kShareThisTabInsteadMessage);

  // Click the share-this-tab-instead secondary button.
  GetDelegate(other_tab)->ShareThisTabInstead();

  // Wait until the capture of the other tab has started.
  while (!other_tab->IsBeingCaptured()) {
    base::RunLoop().RunUntilIdle();
  }

  EXPECT_FALSE(captured_tab->IsBeingCaptured());
  EXPECT_TRUE(other_tab->IsBeingCaptured());
  EXPECT_FALSE(capturing_tab->IsBeingCaptured());
  EXPECT_EQ(GetShareThisTabInsteadButtonLabel(captured_tab),
            kShareThisTabInsteadMessage);
  EXPECT_EQ(GetShareThisTabInsteadButtonLabel(other_tab),
            l10n_util::GetStringFUTF16(
                IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON,
                url_formatter::FormatOriginForSecurityDisplay(
                    other_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
                    url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS)));
  EXPECT_EQ(
      GetShareThisTabInsteadButtonLabel(capturing_tab),
      l10n_util::GetStringFUTF16(
          IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON,
          url_formatter::FormatOriginForSecurityDisplay(
              capturing_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
              url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS)));
}

// TODO(crbug.com/40900706) Re-enable flaky test.
IN_PROC_BROWSER_TEST_P(GetDisplayMediaChangeSourceBrowserTest,
                       DISABLED_ChangeSourceThenStopTracksRemovesIndicators) {
  if (!ShouldShowShareThisTabInsteadButton()) {
    GTEST_SKIP();
  }

  ASSERT_TRUE(embedded_test_server()->Start());
  OpenTestPageInNewTab(kCapturedPageMain);
  content::WebContents* other_tab = OpenTestPageInNewTab(kMainHtmlPage);
  content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage);

  RunGetDisplayMedia(capturing_tab, GetConstraints(), /*is_fake_ui=*/false,
                     /*expect_success=*/true,
                     /*is_tab_capture=*/true);

  // Click the share-this-tab-instead secondary button.
  GetDelegate(other_tab)->ShareThisTabInstead();

  // Wait until the capture of the other tab has started.
  while (!other_tab->IsBeingCaptured()) {
    base::RunLoop().RunUntilIdle();
  }

  ASSERT_EQ(GetInfoBarManager(capturing_tab)->infobars().size(), 1u);
  StopAllTracks(capturing_tab);
  do {
    base::RunLoop().RunUntilIdle();
  } while (GetInfoBarManager(capturing_tab)->infobars().size() > 0u);
}

// TODO(crbug.com/40900706) Re-enable flaky test.
IN_PROC_BROWSER_TEST_P(GetDisplayMediaChangeSourceBrowserTest,
                       DISABLED_ChangeSourceReject) {
  ASSERT_TRUE(embedded_test_server()->Start());
  content::WebContents* captured_tab = OpenTestPageInNewTab(kCapturedPageMain);
  content::WebContents* other_tab = OpenTestPageInNewTab(kMainHtmlPage);
  content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage);

  RunGetDisplayMedia(capturing_tab, GetConstraints(), /*is_fake_ui=*/false,
                     /*expect_success=*/true,
                     /*is_tab_capture=*/true);

  EXPECT_TRUE(captured_tab->IsBeingCaptured());
  EXPECT_FALSE(other_tab->IsBeingCaptured());
  EXPECT_FALSE(capturing_tab->IsBeingCaptured());
  EXPECT_EQ(
      GetShareThisTabInsteadButtonLabel(captured_tab),
      l10n_util::GetStringFUTF16(
          IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON,
          url_formatter::FormatOriginForSecurityDisplay(
              captured_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
              url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS)));
  EXPECT_EQ(
      GetShareThisTabInsteadButtonLabel(capturing_tab),
      l10n_util::GetStringFUTF16(
          IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON,
          url_formatter::FormatOriginForSecurityDisplay(
              capturing_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
              url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS)));
  if (!ShouldShowShareThisTabInsteadButton()) {
    EXPECT_FALSE(HasShareThisTabInsteadButton(other_tab));
    return;
  }
  EXPECT_EQ(GetShareThisTabInsteadButtonLabel(other_tab),
            kShareThisTabInsteadMessage);

  browser()->GetTabStripModel()->ActivateTabAt(
      browser()->GetTabStripModel()->GetIndexOfWebContents(other_tab));
  while (browser()->GetTabStripModel()->GetActiveWebContents() != other_tab) {
    base::RunLoop().RunUntilIdle();
  }

  browser()->GetProfile()->GetPrefs()->SetBoolean(prefs::kScreenCaptureAllowed,
                                                  false);

  // Click the share-this-tab-instead secondary button. This is rejected since
  // screen capture is not allowed by the above policy.
  GetDelegate(other_tab)->ShareThisTabInstead();

  // When "Share this tab instead" fails for other_tab, the focus goes back to
  // the captured tab. Wait until that happens:
  while (browser()->GetTabStripModel()->GetActiveWebContents() !=
         captured_tab) {
    base::RunLoop().RunUntilIdle();
  }

  EXPECT_TRUE(captured_tab->IsBeingCaptured());
  EXPECT_FALSE(other_tab->IsBeingCaptured());
  EXPECT_FALSE(capturing_tab->IsBeingCaptured());
  EXPECT_EQ(
      GetShareThisTabInsteadButtonLabel(captured_tab),
      l10n_util::GetStringFUTF16(
          IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON,
          url_formatter::FormatOriginForSecurityDisplay(
              captured_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
              url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS)));
  EXPECT_EQ(GetShareThisTabInsteadButtonLabel(other_tab),
            kShareThisTabInsteadMessage);
  EXPECT_EQ(
      GetShareThisTabInsteadButtonLabel(capturing_tab),
      l10n_util::GetStringFUTF16(
          IDS_TAB_SHARING_INFOBAR_SWITCH_TO_BUTTON,
          url_formatter::FormatOriginForSecurityDisplay(
              capturing_tab->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
              url_formatter::SchemeDisplay::OMIT_HTTP_AND_HTTPS)));
}

class GetDisplayMediaSelfBrowserSurfaceBrowserTest
    : public WebRtcTestBase,
      public testing::WithParamInterface<std::string> {
 public:
  GetDisplayMediaSelfBrowserSurfaceBrowserTest()
      : self_browser_surface_(GetParam()) {}

  void SetUpInProcessBrowserTestFixture() override {
    WebRtcTestBase::SetUpInProcessBrowserTestFixture();

    DetectErrorsInJavaScript();

    base::FilePath test_dir;
    ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);

    command_line->AppendSwitchASCII(
        switches::kAutoSelectTabCaptureSourceByTitle, kMainHtmlTitle);

    AdjustCommandLineForZeroCopyCapture(command_line);
  }

  std::string GetConstraints(bool prefer_current_tab = false) {
    std::vector<std::string> constraints = {"video: true"};
    if (!self_browser_surface_.empty()) {
      constraints.push_back(base::StringPrintf("selfBrowserSurface: \"%s\"",
                                               self_browser_surface_.c_str()));
    }
    if (prefer_current_tab) {
      constraints.push_back("preferCurrentTab: true");
    }
    prefer_current_tab_ = prefer_current_tab;
    return "{" + base::JoinString(constraints, ",") + "}";
  }

  bool IsSelfBrowserSurfaceExclude() const {
    if (self_browser_surface_ == "" && !prefer_current_tab_) {
      // Special case - when using the new order, selfBrowserSurface
      // defaults to "exclude", unless {preferCurrentTab: true} is specified.
      return true;
    }
    return self_browser_surface_ == "exclude";
  }

 protected:
  // If empty, the constraint is unused. Otherwise, the value is either
  // "include" or "exclude"
  const std::string self_browser_surface_;

  // Whether {preferCurrentTab: true} will be specified by the test.
  bool prefer_current_tab_ = false;
};

INSTANTIATE_TEST_SUITE_P(All,
                         GetDisplayMediaSelfBrowserSurfaceBrowserTest,
                         Values("", "include", "exclude"),
                         [](const testing::TestParamInfo<std::string>& info) {
                           return info.param.empty() ? "Unset" : info.param;
                         });

IN_PROC_BROWSER_TEST_P(GetDisplayMediaSelfBrowserSurfaceBrowserTest,
                       SelfBrowserSurfaceChangesCapturedTab) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // This test relies on |capturing_tab| appearing earlier in the media picker,
  // and being auto-selected earlier if it is offered.
  content::WebContents* other_tab = OpenTestPageInNewTab(kMainHtmlPage);
  content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage);

  // Success expected either way, with the *other* tab being captured
  // when selfBrowserCapture is set to "exclude".
  RunGetDisplayMedia(capturing_tab, GetConstraints(), /*is_fake_ui=*/false,
                     /*expect_success=*/true, /*is_tab_capture=*/true);

  EXPECT_EQ(!IsSelfBrowserSurfaceExclude(), capturing_tab->IsBeingCaptured());
  EXPECT_EQ(IsSelfBrowserSurfaceExclude(), other_tab->IsBeingCaptured());
}

IN_PROC_BROWSER_TEST_P(GetDisplayMediaSelfBrowserSurfaceBrowserTest,
                       SelfBrowserSurfaceInteractionWithPreferCurrentTab) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // This test relies on |capturing_tab| appearing earlier in the media picker,
  // and being auto-selected earlier if it is offered.
  content::WebContents* other_tab = OpenTestPageInNewTab(kMainHtmlPage);
  content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage);

  // Test focal point - getDisplayMedia() rejects if preferCurrentTab
  // and exclude-current-tab are simultaneously specified.
  // Note that preferCurrentTab is hard-coded in this test while
  // exclude-current-tab is parameterized.
  const bool expect_success = (self_browser_surface_ != "exclude");
  const std::string expected_error =
      expect_success ? ""
                     : "TypeError: Failed to execute 'getDisplayMedia' on "
                       "'MediaDevices': Self-contradictory configuration "
                       "(preferCurrentTab and selfBrowserSurface=exclude).";
  RunGetDisplayMedia(capturing_tab, GetConstraints(/*prefer_current_tab=*/true),
                     /*is_fake_ui=*/false, expect_success,
                     /*is_tab_capture=*/true, expected_error);

  EXPECT_EQ(!IsSelfBrowserSurfaceExclude(), capturing_tab->IsBeingCaptured());
  EXPECT_FALSE(other_tab->IsBeingCaptured());
}

// Covers whether transient activation is required to call getDisplayMedia.
class GetDisplayMediaTransientActivationRequiredTest
    : public WebRtcScreenCaptureBrowserTest,
      public testing::WithParamInterface<
          std::tuple<bool, bool, bool, std::optional<std::string>>> {
 public:
  GetDisplayMediaTransientActivationRequiredTest()
      : with_user_gesture_(std::get<0>(GetParam())),
        require_gesture_feature_enabled_(std::get<1>(GetParam())),
        prefer_current_tab_(std::get<2>(GetParam())),
        policy_allowlist_value_(std::get<3>(GetParam())) {}
  ~GetDisplayMediaTransientActivationRequiredTest() override = default;

  static std::string GetDescription(
      const testing::TestParamInfo<
          GetDisplayMediaTransientActivationRequiredTest::ParamType>& info) {
    std::string name = base::JoinString(
        {std::get<0>(info.param) ? "WithUserGesture" : "WithoutUserGesture",
         std::get<1>(info.param) ? "RequireGestureFeatureEnabled"
                                 : "RequireGestureFeatureDisabled",
         std::get<2>(info.param) ? "PreferCurrentTab" : "DontPreferCurrentTab",
         std::get<3>(info.param).has_value()
             ? (*std::get<3>(info.param) == kEmbeddedTestServerOrigin)
                   ? "Allowlisted"
                   : "OtherAllowlisted"
             : "NoPolicySet"},
        "_");
    return name;
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kUseFakeUIForMediaStream);
  }

  void SetUpInProcessBrowserTestFixture() override {
    WebRtcScreenCaptureBrowserTest::SetUpInProcessBrowserTestFixture();

    if (require_gesture_feature_enabled_) {
      feature_list_.InitAndEnableFeature(
          blink::features::kGetDisplayMediaRequiresUserActivation);
    } else {
      feature_list_.InitAndDisableFeature(
          blink::features::kGetDisplayMediaRequiresUserActivation);
    }

    policy_provider_.SetDefaultReturns(
        /*is_initialization_complete_return=*/true,
        /*is_first_policy_load_complete_return=*/true);
    policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
        &policy_provider_);

    DetectErrorsInJavaScript();
  }

  bool PreferCurrentTab() const override { return prefer_current_tab_; }

 protected:
  const bool with_user_gesture_;
  const bool require_gesture_feature_enabled_;
  const bool prefer_current_tab_;
  const std::optional<std::string> policy_allowlist_value_;
  base::test::ScopedFeatureList feature_list_;
  testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_;
};

IN_PROC_BROWSER_TEST_P(GetDisplayMediaTransientActivationRequiredTest, Check) {
  ASSERT_TRUE(embedded_test_server()->Start());

  if (policy_allowlist_value_.has_value()) {
    policy::PolicyMap policy_map;
    base::ListValue allowed_origins;
    allowed_origins.Append(base::Value(*policy_allowlist_value_));
    policy_map.Set(policy::key::kScreenCaptureWithoutGestureAllowedForOrigins,
                   policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
                   policy::POLICY_SOURCE_PLATFORM,
                   base::Value(std::move(allowed_origins)), nullptr);
    policy_provider_.UpdateChromePolicy(policy_map);
  }

  content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);

  const bool expect_success =
      with_user_gesture_ || !require_gesture_feature_enabled_ ||
      (policy_allowlist_value_ &&
       *policy_allowlist_value_ == kEmbeddedTestServerOrigin);
  const std::string expected_error =
      expect_success
          ? ""
          : "InvalidStateError: Failed to execute 'getDisplayMedia' on "
            "'MediaDevices': getDisplayMedia() requires transient activation "
            "(user gesture).";

  RunGetDisplayMedia(tab, GetConstraints(/*video=*/true, /*audio=*/true),
                     /*is_fake_ui=*/true, expect_success,
                     /*is_tab_capture=*/false, expected_error,
                     with_user_gesture_);
}

INSTANTIATE_TEST_SUITE_P(
    /* no prefix */,
    GetDisplayMediaTransientActivationRequiredTest,
    Combine(
        /*with_user_gesture=*/Bool(),
        /*require_gesture_feature_enabled=*/Bool(),
        /*prefer_current_tab=*/Bool(),
        /*policy_allowlist_value=*/
        Values(std::nullopt, kEmbeddedTestServerOrigin, kOtherOrigin)),
    &GetDisplayMediaTransientActivationRequiredTest::GetDescription);

// Covers whether transient activation is conferred by the user's interaction
// with the prompt shown by getDisplayMedia.
class GetDisplayMediaConfersTransientActivationTest
    : public WebRtcScreenCaptureBrowserTest,
      public testing::WithParamInterface<std::tuple<bool, bool, bool>> {
 public:
  static std::string GetDescription(
      const testing::TestParamInfo<
          GetDisplayMediaConfersTransientActivationTest::ParamType>& info) {
    const bool feature_enabled = std::get<0>(info.param);
    const bool prefer_current_tab = std::get<1>(info.param);
    const bool user_accepts = std::get<2>(info.param);
    return base::StrCat(
        {"WithFeature", feature_enabled ? "Enabled" : "Disabled", "_",
         prefer_current_tab ? "PreferCurrentTabVariant" : "StandardVariant",
         "_User", user_accepts ? "Accepts" : "Rejects", "Prompt"});
  }

  GetDisplayMediaConfersTransientActivationTest()
      : feature_enabled_(std::get<0>(GetParam())),
        prefer_current_tab_(std::get<1>(GetParam())),
        user_accepts_(std::get<2>(GetParam())) {}
  ~GetDisplayMediaConfersTransientActivationTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    if (prefer_current_tab_) {
      command_line->AppendSwitch(user_accepts_
                                     ? switches::kThisTabCaptureAutoAccept
                                     : switches::kThisTabCaptureAutoReject);
    } else {
      if (user_accepts_) {
        command_line->AppendSwitchASCII(
            switches::kAutoSelectTabCaptureSourceByTitle, kCapturedTabTitle);
      } else {
        command_line->AppendSwitch(switches::kCaptureAutoReject);
      }
    }
  }

  void SetUpInProcessBrowserTestFixture() override {
    WebRtcScreenCaptureBrowserTest::SetUpInProcessBrowserTestFixture();
    feature_list_.InitWithFeatureState(media::kGetDisplayMediaConfersActivation,
                                       feature_enabled_);
    DetectErrorsInJavaScript();
  }

  bool PreferCurrentTab() const override { return prefer_current_tab_; }

 protected:
  const bool feature_enabled_;
  const bool prefer_current_tab_;
  const bool user_accepts_;

 private:
  base::test::ScopedFeatureList feature_list_;
};

INSTANTIATE_TEST_SUITE_P(
    ,
    GetDisplayMediaConfersTransientActivationTest,
    Combine(
        /*feature_enabled=*/Bool(),
        /*prefer_current_tab=*/Bool(),
        /*user_accepts=*/Bool()),
    &GetDisplayMediaConfersTransientActivationTest::GetDescription);

// TODO(crbug.com/420406085): Re-enable the tests.
#if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER)
#define MAYBE_GdmActivation_RunTest DISABLED_RunTest
#else
#define MAYBE_GdmActivation_RunTest RunTest
#endif
IN_PROC_BROWSER_TEST_P(GetDisplayMediaConfersTransientActivationTest,
                       MAYBE_GdmActivation_RunTest) {
  // Setup
  ASSERT_TRUE(embedded_test_server()->Start());
  OpenTestPageInNewTab(kCapturedPageMain);
  content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage);

  ASSERT_FALSE(
      capturing_tab->GetPrimaryMainFrame()->HasTransientUserActivation());

  // `with_user_gesture` is set to `false` because `getDisplayMedia()` does not
  // currently consume the activation (nor requires it).
  RunGetDisplayMedia(
      capturing_tab,
      GetConstraints(/*video=*/true, /*audio=*/true, prefer_current_tab_),
      /*is_fake_ui=*/false, /*expect_success=*/user_accepts_,
      /*is_tab_capture=*/true, /*expected_error=*/"",
      /*with_user_gesture=*/false);

  EXPECT_EQ(capturing_tab->GetPrimaryMainFrame()->HasTransientUserActivation(),
            feature_enabled_ && user_accepts_);
}

// This test suite ensures that, no matter the combination of inputs,
// an interaction with getUserMedia() does not confer transient activation.
// That is, the code authored for gDM does not mistrigger and run for gUM.
class GetUserMediaDoesNotConferTransientActivationTest
    : public WebRtcTestBase,
      public testing::WithParamInterface<std::tuple<bool, bool, bool>> {
 public:
  static std::string GetDescription(
      const testing::TestParamInfo<
          GetUserMediaDoesNotConferTransientActivationTest::ParamType>& info) {
    const bool video = std::get<0>(info.param);
    const bool audio = std::get<1>(info.param);
    const bool user_accepts = std::get<2>(info.param);
    return base::StrCat({"Video", video ? "On" : "Off", "_Audio",
                         audio ? "On" : "Off", "_User",
                         user_accepts ? "Accepts" : "Rejects", "Prompt"});
  }

  GetUserMediaDoesNotConferTransientActivationTest()
      : video_(std::get<0>(GetParam())),
        audio_(std::get<1>(GetParam())),
        user_accepts_(std::get<2>(GetParam())) {}
  ~GetUserMediaDoesNotConferTransientActivationTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kUseFakeDeviceForMediaStream);
  }

  void SetUpInProcessBrowserTestFixture() override {
    WebRtcTestBase::SetUpInProcessBrowserTestFixture();
    DetectErrorsInJavaScript();
  }

 protected:
  const bool video_;
  const bool audio_;
  const bool user_accepts_;
};

INSTANTIATE_TEST_SUITE_P(
    ,
    GetUserMediaDoesNotConferTransientActivationTest,
    Combine(
        /*video=*/Bool(),
        /*audio=*/Bool(),
        /*user_accepts=*/Bool()),
    &GetUserMediaDoesNotConferTransientActivationTest::GetDescription);

// TODO(crbug.com/420406085): Re-enable the tests.
#if defined(MEMORY_SANITIZER) || defined(ADDRESS_SANITIZER)
#define MAYBE_GumActivation_RunTest DISABLED_RunTest
#else
#define MAYBE_GumActivation_RunTest RunTest
#endif
IN_PROC_BROWSER_TEST_P(GetUserMediaDoesNotConferTransientActivationTest,
                       MAYBE_GumActivation_RunTest) {
  if (!video_ && !audio_) {
    GTEST_SKIP();
  }

  // Setup
  ASSERT_TRUE(embedded_test_server()->Start());
  content::WebContents* const wc = OpenTestPageInNewTab(kMainHtmlPage);
  permissions::PermissionRequestManager::FromWebContents(wc)
      ->set_auto_response_for_test(
          permissions::PermissionRequestManager::ACCEPT_ALL);

  ASSERT_FALSE(wc->GetPrimaryMainFrame()->HasTransientUserActivation());

  const std::string constraints =
      base::StringPrintf("{video: %s, audio: %s}", video_ ? "true" : "false",
                         audio_ ? "true" : "false");
  RunGetUserMedia(wc, constraints);

  EXPECT_FALSE(wc->GetPrimaryMainFrame()->HasTransientUserActivation());
}

// Encapsulates information about a capture-session in which one tab starts
// out capturing a specific other tab, and later possibly moves to capturing
// another tab. The encapsulation of this state allows for more succinct tests,
// especially when testing multiple concurrent capture-sessions.
class CaptureSessionDetails {
 public:
  enum class CapturedTab {
    kInitiallyCapturedTab,
    kOtherTab,
    kCapturingTab,  // Share-this-tab-instead can cause self-capture.
  };

  CaptureSessionDetails(std::string session_name,
                        WebContents* initially_captured_tab,
                        WebContents* other_tab,
                        WebContents* capturing_tab)
      : session_name_(std::move(session_name)),
        initially_captured_tab_(initially_captured_tab),
        other_tab_(other_tab),
        capturing_tab_(capturing_tab) {}

  std::unique_ptr<MockCapturedSurfaceController>
  MakeMockCapturedSurfaceController(
      blink::mojom::CapturedSurfaceControlResult permission_response,
      GlobalRenderFrameHostId gdm_rfhid,
      WebContentsMediaCaptureId captured_wc_id) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    EXPECT_FALSE(mock_captured_surface_controller_)
        << "Instantiated more CapturedSurfaceController than expected.";

    mock_captured_surface_controller_ =
        new MockCapturedSurfaceController(gdm_rfhid, captured_wc_id);
    mock_captured_surface_controller_->SetRequestPermissionResponse(
        permission_response);

    return base::WrapUnique(mock_captured_surface_controller_.get());
  }

  void RunGetDisplayMedia() {
    ::RunGetDisplayMedia(capturing_tab_,
                         "{video: true, surfaceSwitching: \"include\"}",
                         /*is_fake_ui=*/false,
                         /*expect_success=*/true,
                         /*is_tab_capture=*/true);
  }

  // Sets a factory that produces mock controllers for Captured Surface Control
  // and attaches them to `this` CaptureSessionDetails object. They will respond
  // to permission checks with the preconfigured `permission_response`.
  //
  // The factory is global. Tests that instantiate multiple capture sessions
  // should make sure to call this again from the new CaptureSessionDetails
  // object at the appropriate time, thereby replacing the factory after it's
  // used.
  //
  // This method is called on the UI thread. Hops to the IO thread and sets the
  // CSC-factory, then unblocks execution on the UI thread.
  void SetCapturedSurfaceControllerFactory(
      blink::mojom::CapturedSurfaceControlResult permission_response =
          blink::mojom::CapturedSurfaceControlResult::kSuccess) {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    base::RunLoop run_loop;
    content::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(
            &CaptureSessionDetails::SetCapturedSurfaceControllerFactoryOnIO,
            base::Unretained(this), permission_response,
            run_loop.QuitClosure()));
    run_loop.Run();
  }

  void SetExpectUpdateCaptureTarget() {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);

    base::RunLoop run_loop;
    content::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&CaptureSessionDetails::ExpectUpdateCaptureTargetOnIO,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
  }

  void VerifyAndClearExpectations() {
    DCHECK_CURRENTLY_ON(BrowserThread::UI);
    SCOPED_TRACE(session_name_);

    base::RunLoop run_loop;
    content::GetIOThreadTaskRunner({})->PostTask(
        FROM_HERE,
        base::BindOnce(&CaptureSessionDetails::VerifyAndClearExpectationsOnIO,
                       base::Unretained(this), run_loop.QuitClosure()));
    run_loop.Run();
  }

  WebContents* GetTab(CapturedTab captured_tab) {
    switch (captured_tab) {
      case CapturedTab::kInitiallyCapturedTab:
        return initially_captured_tab_;
      case CapturedTab::kOtherTab:
        return other_tab_;
      case CapturedTab::kCapturingTab:
        return capturing_tab_;
    }
    NOTREACHED();
  }

  WebContents* GetCapturedTab() {
    CHECK_EQ(static_cast<int>(capturing_tab_->IsBeingCaptured()) +
                 static_cast<int>(initially_captured_tab_->IsBeingCaptured()) +
                 static_cast<int>(other_tab_->IsBeingCaptured()),
             1);
    if (capturing_tab_->IsBeingCaptured()) {
      return capturing_tab_;
    } else if (initially_captured_tab_->IsBeingCaptured()) {
      return initially_captured_tab_;
    } else if (other_tab_->IsBeingCaptured()) {
      return other_tab_;
    }
    NOTREACHED();
  }

  // Get the tab that's neither capturing nor being captured.
  WebContents* GetNonCapturedTab() {
    CHECK(!capturing_tab_->IsBeingCaptured());
    CHECK_EQ(static_cast<int>(initially_captured_tab_->IsBeingCaptured()) +
                 static_cast<int>(other_tab_->IsBeingCaptured()),
             1);

    return initially_captured_tab_->IsBeingCaptured() ? other_tab_
                                                      : initially_captured_tab_;
  }

  void WaitForCaptureOf(CapturedTab expected_tab) {
    while (!GetTab(expected_tab)->IsBeingCaptured()) {
      base::RunLoop().RunUntilIdle();
    }
    ExpectCapturedTab(expected_tab);
  }

  void ExpectCapturedTab(CapturedTab captured) {
    EXPECT_EQ(initially_captured_tab_->IsBeingCaptured(),
              captured == CapturedTab::kInitiallyCapturedTab);
    EXPECT_EQ(other_tab_->IsBeingCaptured(),
              captured == CapturedTab::kOtherTab);
    EXPECT_EQ(capturing_tab_->IsBeingCaptured(),
              captured == CapturedTab::kCapturingTab);

    EXPECT_EQ(GetShareThisTabInsteadButtonLabel(GetNonCapturedTab()),
              kShareThisTabInsteadMessage);
  }

  // Forwards from the target element, or stops forwarding if target is "null".
  std::string ForwardWheel(const std::string& target) {
    return content::EvalJs(
               capturing_tab_->GetPrimaryMainFrame(),
               base::StringPrintf("forwardWheel(%s);", target.c_str()))
        .ExtractString();
  }

  void UpdateZoomLevel(const std::string& action, bool expect_success = true) {
    const std::string command =
        base::StringPrintf("updateZoomLevel(\"%s\");", action);
    const std::string expected_result = base::StringPrintf(
        "%s-zoom-level-%s", action, expect_success ? "resolved" : "error");

    EXPECT_EQ(content::EvalJs(capturing_tab_->GetPrimaryMainFrame(), command),
              expected_result);
  }

  std::optional<int> GetZoomLevel() {
    const content::EvalJsResult result = content::EvalJs(
        capturing_tab_->GetPrimaryMainFrame(), "getZoomLevel();");
    return (result == base::Value())
               ? std::nullopt
               : std::make_optional<int>(result.ExtractInt());
  }

  // Call `controller.getSupportedZoomLevels()`.
  // Returns the result if successful; the error otherwise.
  base::expected<std::vector<int>, std::string> GetSupportedZoomLevels() {
    content::EvalJsResult js_result = content::EvalJs(
        capturing_tab_->GetPrimaryMainFrame(), "getSupportedZoomLevels();");

    const base::ListValue& list = js_result.ExtractList();
    EXPECT_GE(list.size(), 1u);
    if (list.size() == 1u) {
      // Reserved for an error.
      return base::unexpected(list[0].GetString());
    }

    std::vector<int> result;
    result.reserve(list.size());
    for (const base::Value& val : list) {
      EXPECT_TRUE(val.is_int());
      result.push_back(val.GetInt());
    }
    return result;
  }

  int GetZoomLevelChangeEventsSinceLast() {
    // Note that ExtractInt() will implicitly ensure the script did not run into
    // an error.
    return content::EvalJs(capturing_tab_->GetPrimaryMainFrame(),
                           "zoomLevelChangeEventsSinceLast();")
        .ExtractInt();
  }

  WebContents* initially_captured_tab() const {
    return initially_captured_tab_;
  }
  WebContents* other_tab() const { return other_tab_; }
  WebContents* capturing_tab() const { return capturing_tab_; }

 private:
  void SetCapturedSurfaceControllerFactoryOnIO(
      blink::mojom::CapturedSurfaceControlResult permission_response,
      base::RepeatingClosure done_closure) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    CapturedSurfaceControllerFactoryCallback factory = base::BindRepeating(
        &CaptureSessionDetails::MakeMockCapturedSurfaceController,
        base::Unretained(this), permission_response);

    content::SetCapturedSurfaceControllerFactoryForTesting(factory);

    done_closure.Run();
  }

  void ExpectUpdateCaptureTargetOnIO(base::RepeatingClosure done_closure) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    CHECK(mock_captured_surface_controller_);
    EXPECT_CALL(*mock_captured_surface_controller_, UpdateCaptureTarget(_))
        .Times(1);

    done_closure.Run();
  }

  void VerifyAndClearExpectationsOnIO(base::RepeatingClosure done_closure) {
    DCHECK_CURRENTLY_ON(BrowserThread::IO);

    CHECK(mock_captured_surface_controller_);
    Mock::VerifyAndClearExpectations(mock_captured_surface_controller_);
    mock_captured_surface_controller_ = nullptr;

    done_closure.Run();
  }

  const std::string session_name_;

  // Handled on UI thread.
  const raw_ptr<WebContents> initially_captured_tab_;
  const raw_ptr<WebContents> other_tab_;
  const raw_ptr<WebContents> capturing_tab_;

  // Handled on the IO thread.
  raw_ptr<MockCapturedSurfaceController> mock_captured_surface_controller_;
};

class CapturedSurfaceControlTest : public WebRtcTestBase {
 public:
  enum class Action {
    kForwardWheel,      // forwardWheel(validElement)
    kForwardWheelNull,  // forwardWheel(null)
    kIncreaseZoomLevel,
    kDecreaseZoomLevel,
    kResetZoomLevel,
    kGetZoomLevel,
    kGetSupportedZoomLevels,
  };

  static const char* ToZoomLevelAction(Action input) {
    switch (input) {
      case Action::kIncreaseZoomLevel:
        return "increase";
      case Action::kDecreaseZoomLevel:
        return "decrease";
      case Action::kResetZoomLevel:
        return "reset";
      case Action::kForwardWheel:
      case Action::kForwardWheelNull:
      case Action::kGetZoomLevel:
      case Action::kGetSupportedZoomLevels:
        break;
    }
    NOTREACHED() << "Not a ZoomLevelAction.";
  }

  static bool ShouldTriggerCscIndicator(Action action) {
    switch (action) {
      case Action::kForwardWheel:
      case Action::kIncreaseZoomLevel:
      case Action::kDecreaseZoomLevel:
      case Action::kResetZoomLevel:
        return true;
      case Action::kGetZoomLevel:
      case Action::kGetSupportedZoomLevels:
      case Action::kForwardWheelNull:
        return false;
    }
    NOTREACHED();
  }

  static void MakeValidApiCall(CaptureSessionDetails& capture_session,
                               Action action) {
    switch (action) {
      case Action::kForwardWheel:
        EXPECT_EQ(capture_session.ForwardWheel("video"),
                  "forward-wheel-resolved");
        return;
      case Action::kForwardWheelNull:
        EXPECT_EQ(capture_session.ForwardWheel("null"),
                  "forward-wheel-resolved");
        return;
      case Action::kIncreaseZoomLevel:
      case Action::kDecreaseZoomLevel:
      case Action::kResetZoomLevel:
        capture_session.UpdateZoomLevel(ToZoomLevelAction(action));
        return;
      case Action::kGetZoomLevel:
        capture_session.GetZoomLevel();
        return;
      case Action::kGetSupportedZoomLevels:
        (void)capture_session.GetSupportedZoomLevels();
        return;
    }
    NOTREACHED();
  }

  CapturedSurfaceControlTest() = default;
  ~CapturedSurfaceControlTest() override = default;

  void SetUpInProcessBrowserTestFixture() override {
    WebRtcTestBase::SetUpInProcessBrowserTestFixture();

    DetectErrorsInJavaScript();

    base::FilePath test_dir;
    ASSERT_TRUE(base::PathService::Get(chrome::DIR_TEST_DATA, &test_dir));
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
    command_line->AppendSwitchASCII(
        switches::kAutoSelectTabCaptureSourceByTitle, kCapturedTabTitle);
    command_line->AppendSwitch(
        switches::kAutoGrantCapturedSurfaceControlPrompt);

    AdjustCommandLineForZeroCopyCapture(command_line);
  }

  // Runs the `ChangeSourceWorksOnCorrectCaptureSession` test.
  // This is defined as a method in order to test both the first/second
  // capture experiencing the share-this-tab-instead click, without having
  // to parameterize the entire test suite.
  void RunChangeSourceWorksOnCorrectCaptureSession(
      size_t session_experiencing_change);

  // Runs the `ChangingCapturedTabZoomChangeEventTest` test.
  // This is defined as a method in order to test both of the following tests
  // without the overhead and unclarity of parameterizing a test suite for it.
  // * ChangingCapturedTabIssuesEventIfDifferentZoomLevels
  // * ChangingCapturedTabDoesNotIssueEventIfSameZoomLevels
  void RunChangingCapturedTabZoomChangeEventTest(double zoom_level_first_tab,
                                                 double zoom_level_second_tab);

 protected:
  using CapturedTab = ::CaptureSessionDetails::CapturedTab;

  CaptureSessionDetails MakeCaptureSessionDetails(std::string session_name) {
    return CaptureSessionDetails(
        std::move(session_name),
        /*initially_captured_tab=*/OpenTestPageInNewTab(kCapturedPageMain),
        /*other_tab=*/(OpenTestPageInNewTab(kMainHtmlPage)),
        /*capturing_tab=*/(OpenTestPageInNewTab(kMainHtmlPage)));
  }
};

using CscAction = CapturedSurfaceControlTest::Action;

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       UnboundCaptureControllerReportNullZoomLevel) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Note absence of call to RunGetDisplayMedia().
  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");

  EXPECT_EQ(capture_session.GetZoomLevel(), std::nullopt);
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       CorrectlyReportDefaultCapturedSurfaceZoomLevel) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Note absence of call to SetZoomFactor().
  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();

  WebContents* const captured_tab = capture_session.GetCapturedTab();
  EXPECT_EQ(GetZoomLevelPercentage(captured_tab), 100);
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       CorrectlyReportNonDefaultCapturedSurfaceZoomLevel) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");

  // Set the zoom factor to something other than the default before
  // capture starts.
  WebContents* const captured_tab =
      capture_session.GetTab(CapturedTab::kInitiallyCapturedTab);
  SetZoomFactor(captured_tab, 0.5);

  // Start the capture.
  capture_session.RunGetDisplayMedia();
  ASSERT_EQ(capture_session.GetCapturedTab(), captured_tab);

  // The initially reported zoom level is as expected.
  EXPECT_EQ(GetZoomLevelPercentage(captured_tab), 50);
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       GetSupportedZoomLevelsFailsOnUnboundCaptureController) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Note absence of call to RunGetDisplayMedia().
  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");

  const base::expected<std::vector<int>, std::string> result =
      capture_session.GetSupportedZoomLevels();
  EXPECT_FALSE(result.has_value());
  EXPECT_NE(result.error().find("InvalidStateError"), std::string::npos);
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       GetSupportedZoomLevelsSucceedsIfCapturingTab) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();

  EXPECT_TRUE(capture_session.GetSupportedZoomLevels().has_value());
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       GetSupportedZoomLevelsMonotonouslyIncreasing) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();

  const base::expected<std::vector<int>, std::string> result =
      capture_session.GetSupportedZoomLevels();
  EXPECT_TRUE(result.has_value());
  const std::vector<int>& values = result.value();
  ASSERT_GE(values.size(), 2u);
  for (size_t i = 0; i < values.size() - 1; ++i) {
    EXPECT_GT(values[i + 1], values[i]);
  }
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       GetSupportedZoomLevelsFailsIfTracksStopped) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();

  ASSERT_TRUE(capture_session.GetSupportedZoomLevels().has_value());

  StopAllTracks(capture_session.capturing_tab());

  const base::expected<std::vector<int>, std::string> result =
      capture_session.GetSupportedZoomLevels();
  EXPECT_FALSE(result.has_value());
  EXPECT_NE(result.error().find("InvalidStateError"), std::string::npos);
}

IN_PROC_BROWSER_TEST_F(
    CapturedSurfaceControlTest,
    NoZoomLevelChangeEventFiredWhenCaptureStartsWithDefaultZoomLevel) {
  ASSERT_TRUE(embedded_test_server()->Start());

  // Note absence of call to SetZoomFactor().
  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();

  EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 0);
}

IN_PROC_BROWSER_TEST_F(
    CapturedSurfaceControlTest,
    NoZoomLevelChangeEventFiredWhenCaptureStartsWithNonDefaultZoomLevel) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");

  // Set the zoom factor to something other than the default before
  // capture starts.
  WebContents* const captured_tab =
      capture_session.GetTab(CapturedTab::kInitiallyCapturedTab);
  SetZoomFactor(captured_tab, 0.5);

  capture_session.RunGetDisplayMedia();
  ASSERT_EQ(capture_session.GetCapturedTab(), captured_tab);

  EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 0);
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       IncreaseZoomLevelSucceedsBelowMaxValue) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();

  WebContents* const captured_tab = capture_session.GetCapturedTab();
  ASSERT_EQ(GetZoomLevelPercentage(captured_tab), 100);

  capture_session.UpdateZoomLevel("increase");

  // Check both the actual zoom level as well as the one reported to JS.
  const int actual_zoom_level_percent = GetZoomLevelPercentage(captured_tab);
  EXPECT_GT(actual_zoom_level_percent, 100);
  EXPECT_EQ(actual_zoom_level_percent, capture_session.GetZoomLevel());
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       IncreaseZoomLevelFailsAtMaxValue) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();

  WebContents* const captured_tab = capture_session.GetCapturedTab();
  const double max_factor = blink::kPresetBrowserZoomFactors.back();
  SetZoomFactor(captured_tab, max_factor);
  ASSERT_EQ(GetZoomLevelPercentage(captured_tab), std::round(100 * max_factor));

  capture_session.UpdateZoomLevel("increase", /*expect_success=*/false);

  // Check both the actual zoom level as well as the one reported to JS.
  const int actual_zoom_level_percent = GetZoomLevelPercentage(captured_tab);
  EXPECT_EQ(actual_zoom_level_percent, std::round(100 * max_factor));
  EXPECT_EQ(actual_zoom_level_percent, capture_session.GetZoomLevel());
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       IncreaseZoomLevelIssuesEvent) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();

  capture_session.UpdateZoomLevel("increase");
  EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 1);
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       DecreaseZoomLevelSucceedsAboveMinValue) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();

  WebContents* const captured_tab = capture_session.GetCapturedTab();
  ASSERT_EQ(GetZoomLevelPercentage(captured_tab), 100);

  capture_session.UpdateZoomLevel("decrease");

  // Check both the actual zoom level as well as the one reported to JS.
  const int actual_zoom_level_percent = GetZoomLevelPercentage(captured_tab);
  EXPECT_LT(actual_zoom_level_percent, 100);
  EXPECT_EQ(actual_zoom_level_percent, capture_session.GetZoomLevel());
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       DecreaseZoomLevelFailsAtMinValue) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();

  WebContents* const captured_tab = capture_session.GetCapturedTab();
  const double min_factor = blink::kPresetBrowserZoomFactors.front();
  SetZoomFactor(captured_tab, min_factor);
  ASSERT_EQ(GetZoomLevelPercentage(captured_tab), std::round(100 * min_factor));

  capture_session.UpdateZoomLevel("decrease", /*expect_success=*/false);

  // Check both the actual zoom level as well as the one reported to JS.
  const int actual_zoom_level_percent = GetZoomLevelPercentage(captured_tab);
  EXPECT_EQ(actual_zoom_level_percent, std::round(100 * min_factor));
  EXPECT_EQ(actual_zoom_level_percent, capture_session.GetZoomLevel());
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       DecreaseZoomLevelIssuesEvent) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();

  capture_session.UpdateZoomLevel("decrease");
  EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 1);
}

// The "expected" case of resetZoomLevel() - changing *back* to
// the default value.
IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       ResetZoomLevelSucceedsIfNonDefaultLevel) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");

  // Set the zoom factor to something other than the default before
  // capture starts.
  WebContents* const captured_tab =
      capture_session.GetTab(CapturedTab::kInitiallyCapturedTab);
  SetZoomFactor(captured_tab, 0.5);

  // Start the capture.
  capture_session.RunGetDisplayMedia();
  ASSERT_EQ(capture_session.GetCapturedTab(), captured_tab);
  ASSERT_EQ(GetZoomLevelPercentage(captured_tab), 50);

  // Reset works as expected.
  capture_session.UpdateZoomLevel("reset");

  // Check both the actual zoom level as well as the one reported to JS.
  EXPECT_EQ(GetZoomLevelPercentage(captured_tab), 100);
  EXPECT_EQ(capture_session.GetZoomLevel(), 100);
}

// The less "expected" case of resetZoomLevel() - calling reset...()
// when already at the default value. Should be no-op but succeed.
IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       ResetZoomLevelSucceedsIfDefaultLevel) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");

  capture_session.RunGetDisplayMedia();
  WebContents* const captured_tab = capture_session.GetCapturedTab();
  ASSERT_EQ(GetZoomLevelPercentage(captured_tab), 100);

  // Reset works as expected.
  capture_session.UpdateZoomLevel("reset");

  // Check both the actual zoom level as well as the one reported to JS.
  EXPECT_EQ(GetZoomLevelPercentage(captured_tab), 100);
  EXPECT_EQ(capture_session.GetZoomLevel(), 100);
}

void CapturedSurfaceControlTest::RunChangingCapturedTabZoomChangeEventTest(
    double zoom_level_first_tab,
    double zoom_level_second_tab) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");

  WebContents* const first_captured_tab =
      capture_session.GetTab(CapturedTab::kInitiallyCapturedTab);
  SetZoomFactor(first_captured_tab, zoom_level_first_tab);

  WebContents* const second_captured_tab =
      capture_session.GetTab(CapturedTab::kOtherTab);
  SetZoomFactor(second_captured_tab, zoom_level_second_tab);

  capture_session.RunGetDisplayMedia();
  ASSERT_EQ(GetZoomLevelPercentage(first_captured_tab),
            100 * zoom_level_first_tab);
  ASSERT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 0);

  GetDelegate(second_captured_tab)->ShareThisTabInstead();
  capture_session.WaitForCaptureOf(CapturedTab::kOtherTab);
  ASSERT_EQ(capture_session.GetCapturedTab(), second_captured_tab);
  ASSERT_EQ(GetZoomLevelPercentage(second_captured_tab),
            100 * zoom_level_second_tab);

  const int expected_event_count =
      (zoom_level_first_tab != zoom_level_second_tab) ? 1 : 0;
  EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(),
            expected_event_count);
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       ChangingCapturedTabIssuesEventIfDifferentZoomLevels) {
  SCOPED_TRACE("ChangingCapturedTabIssuesEventIfDifferentZoomLevels");
  RunChangingCapturedTabZoomChangeEventTest(0.5, 0.75);
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       ChangingCapturedTabDoesNotIssueEventIfSameZoomLevels) {
  SCOPED_TRACE("ChangingCapturedTabDoesNotIssueEventIfSameZoomLevels");
  RunChangingCapturedTabZoomChangeEventTest(0.5, 0.5);
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       ResetZoomLevelOnlyIssuesEventsWhenZoomLevelChanges) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");

  // Set the zoom factor to something other than the default before
  // capture starts.
  WebContents* const captured_tab =
      capture_session.GetTab(CapturedTab::kInitiallyCapturedTab);
  SetZoomFactor(captured_tab, 0.5);

  // Start the capture.
  capture_session.RunGetDisplayMedia();
  ASSERT_EQ(capture_session.GetCapturedTab(), captured_tab);
  ASSERT_EQ(GetZoomLevelPercentage(captured_tab), 50);
  ASSERT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 0);

  // Expectation #1 - the initial reset issues an event.
  capture_session.UpdateZoomLevel("reset");
  EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 1);

  // Expectation #2 - additional resets don't issue an event.
  capture_session.UpdateZoomLevel("reset");
  EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 0);

  // Expectation #3 - events still generally issued, they just
  // require actual change of zoom level. (Test is sane.)
  capture_session.UpdateZoomLevel("increase");
  capture_session.UpdateZoomLevel("reset");
  EXPECT_EQ(capture_session.GetZoomLevelChangeEventsSinceLast(), 2);
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       ChangeSourceTriggersUpdateCaptureTarget) {
  SCOPED_TRACE("ChangeSourceTriggersUpdateCaptureTarget");

  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.SetCapturedSurfaceControllerFactory();
  capture_session.RunGetDisplayMedia();
  capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab);

  EXPECT_EQ(capture_session.ForwardWheel("video"), "forward-wheel-resolved");

  // Expect that clicking "share this tab instead" will pipe a notification of
  // the change to the captured surface controller.
  capture_session.SetExpectUpdateCaptureTarget();
  GetDelegate(capture_session.other_tab())->ShareThisTabInstead();
  capture_session.WaitForCaptureOf(CapturedTab::kOtherTab);

  capture_session.VerifyAndClearExpectations();
}

void CapturedSurfaceControlTest::RunChangeSourceWorksOnCorrectCaptureSession(
    size_t session_experiencing_change) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session_0 =
      MakeCaptureSessionDetails("capture_session_0");
  capture_session_0.SetCapturedSurfaceControllerFactory();
  capture_session_0.RunGetDisplayMedia();
  capture_session_0.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab);
  EXPECT_EQ(capture_session_0.ForwardWheel("video"), "forward-wheel-resolved");

  CaptureSessionDetails capture_session_1 =
      MakeCaptureSessionDetails("capture_session_1");
  capture_session_1.SetCapturedSurfaceControllerFactory();
  capture_session_1.RunGetDisplayMedia();
  capture_session_1.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab);
  EXPECT_EQ(capture_session_1.ForwardWheel("video"), "forward-wheel-resolved");

  // Expect that clicking "share this tab instead" will pipe a notification of
  // the change to the correct CapturedSurfaceController.
  CHECK(session_experiencing_change == 0 || session_experiencing_change == 1);
  CaptureSessionDetails& capture_session_experiencing_change =
      (session_experiencing_change == 0) ? capture_session_0
                                         : capture_session_1;
  capture_session_experiencing_change.SetExpectUpdateCaptureTarget();
  GetDelegate(capture_session_experiencing_change.other_tab(),
              /*infobar_index=*/session_experiencing_change)
      ->ShareThisTabInstead();
  capture_session_experiencing_change.WaitForCaptureOf(CapturedTab::kOtherTab);

  capture_session_0.VerifyAndClearExpectations();
  capture_session_1.VerifyAndClearExpectations();
}

// Test when the first of two capture sessions experiences the source-change.
IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       ChangeSourceWorksOnCorrectCaptureSession0) {
  SCOPED_TRACE("ChangeSourceWorksOnCorrectCaptureSession0");
  RunChangeSourceWorksOnCorrectCaptureSession(0);
}

// Test when the second of two capture sessions experiences the source-change.
IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       ChangeSourceWorksOnCorrectCaptureSession1) {
  SCOPED_TRACE("ChangeSourceWorksOnCorrectCaptureSession1");
  RunChangeSourceWorksOnCorrectCaptureSession(1);
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       ForwardWheelElementFailsIfNoPermission) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.SetCapturedSurfaceControllerFactory(
      blink::mojom::CapturedSurfaceControlResult::kNoPermissionError);
  capture_session.RunGetDisplayMedia();
  capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab);

  EXPECT_THAT(capture_session.ForwardWheel("video"),
              HasSubstr("NotAllowedError"));
}

IN_PROC_BROWSER_TEST_F(CapturedSurfaceControlTest,
                       ForwardWheelNullSucceedsWithoutPermission) {
  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.SetCapturedSurfaceControllerFactory(
      blink::mojom::CapturedSurfaceControlResult::kNoPermissionError);
  capture_session.RunGetDisplayMedia();
  capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab);

  EXPECT_EQ(capture_session.ForwardWheel("null"), "forward-wheel-resolved");
}

class CapturedSurfaceControlIndicatorTest
    : public CapturedSurfaceControlTest,
      public testing::WithParamInterface<CscAction> {
 public:
  CapturedSurfaceControlIndicatorTest() : action_(GetParam()) {}
  ~CapturedSurfaceControlIndicatorTest() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    CapturedSurfaceControlTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(
        switches::kAutoGrantCapturedSurfaceControlPrompt);
  }

 protected:
  const CscAction action_;
};

INSTANTIATE_TEST_SUITE_P(,
                         CapturedSurfaceControlIndicatorTest,
                         Values(CscAction::kForwardWheel,
                                CscAction::kForwardWheelNull,
                                CscAction::kIncreaseZoomLevel,
                                CscAction::kDecreaseZoomLevel,
                                CscAction::kResetZoomLevel,
                                CscAction::kGetZoomLevel,
                                CscAction::kGetSupportedZoomLevels),
                         [](const testing::TestParamInfo<CscAction>& info) {
                           switch (info.param) {
                             case CscAction::kForwardWheel:
                               return "ForwardWheel";
                             case CscAction::kForwardWheelNull:
                               return "ForwardWheelNull";
                             case CscAction::kIncreaseZoomLevel:
                               return "IncreaseZoomLevel";
                             case CscAction::kDecreaseZoomLevel:
                               return "DecreaseZoomLevel";
                             case CscAction::kResetZoomLevel:
                               return "ResetZoomLevel";
                             case CscAction::kGetZoomLevel:
                               return "GetZoomLevel";
                             case CscAction::kGetSupportedZoomLevels:
                               return "GetSupportedZoomLevels";
                           }
                           NOTREACHED();
                         });

IN_PROC_BROWSER_TEST_P(CapturedSurfaceControlIndicatorTest,
                       IndicatorNotShownBeforeApiInvocation) {
  SCOPED_TRACE("IndicatorNotShownBeforeApiInvocation");

  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();
  capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab);

  // The CSC indicator is not shown anywhere.
  EXPECT_FALSE(HasCscIndicator(capture_session.capturing_tab()));
  EXPECT_FALSE(HasCscIndicator(capture_session.initially_captured_tab()));
  EXPECT_FALSE(HasCscIndicator(capture_session.other_tab()));
}

IN_PROC_BROWSER_TEST_P(CapturedSurfaceControlIndicatorTest,
                       IndicatorShownAfterWriteAccessApiInvocation) {
  SCOPED_TRACE("IndicatorShownAfterWriteAccessApiInvocation");

  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();
  capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab);

  MakeValidApiCall(capture_session, action_);

  // The capturing tab's infobar shows the CSC indicator, but only
  // if the action was a write-access action.
  EXPECT_EQ(HasCscIndicator(capture_session.capturing_tab()),
            ShouldTriggerCscIndicator(action_));

  // The CSC indicator is not shown on any other infobar.
  EXPECT_FALSE(HasCscIndicator(capture_session.initially_captured_tab()));
  EXPECT_FALSE(HasCscIndicator(capture_session.other_tab()));
}

IN_PROC_BROWSER_TEST_P(
    CapturedSurfaceControlIndicatorTest,
    IndicatorStateRetainedAfterShareThisTabInsteadNoCscBefore) {
  SCOPED_TRACE("IndicatorStateRetainedAfterShareThisTabInsteadNoCscBefore");

  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();
  capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab);

  // Note absence of call to MakeValidApiCall() before share-this-tab-instead.
  GetDelegate(capture_session.other_tab())->ShareThisTabInstead();

  // The capturing tab's infobar does not show the CSC indicator because
  // a write-access CSC action was not invoked.
  EXPECT_FALSE(HasCscIndicator(capture_session.capturing_tab()));

  // The CSC indicator is not shown on any other infobar.
  EXPECT_FALSE(HasCscIndicator(capture_session.initially_captured_tab()));
  EXPECT_FALSE(HasCscIndicator(capture_session.other_tab()));
}

IN_PROC_BROWSER_TEST_P(
    CapturedSurfaceControlIndicatorTest,
    IndicatorStateRetainedAfterShareThisTabInsteadAfterCscAction) {
  SCOPED_TRACE("IndicatorStateRetainedAfterShareThisTabInsteadAfterCscAction");

  ASSERT_TRUE(embedded_test_server()->Start());

  CaptureSessionDetails capture_session =
      MakeCaptureSessionDetails("capture_session");
  capture_session.RunGetDisplayMedia();
  capture_session.ExpectCapturedTab(CapturedTab::kInitiallyCapturedTab);

  MakeValidApiCall(capture_session, action_);
  GetDelegate(capture_session.other_tab())->ShareThisTabInstead();

  // The capturing tab's infobar show the CSC indicator if the action
  // was a write-access action.
  EXPECT_EQ(HasCscIndicator(capture_session.capturing_tab()),
            ShouldTriggerCscIndicator(action_));

  // The CSC indicator is not shown on any other infobar.
  EXPECT_FALSE(HasCscIndicator(capture_session.initially_captured_tab()));
  EXPECT_FALSE(HasCscIndicator(capture_session.other_tab()));
}

class WebRtcScreenCaptureBrowserTestUserRejection
    : public WebRtcScreenCaptureBrowserTest,
      public testing::WithParamInterface<bool> {
 public:
  WebRtcScreenCaptureBrowserTestUserRejection()
      : prefer_current_tab_(GetParam()) {}
  ~WebRtcScreenCaptureBrowserTestUserRejection() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    WebRtcScreenCaptureBrowserTest::SetUpCommandLine(command_line);
    command_line->AppendSwitch(prefer_current_tab_
                                   ? switches::kThisTabCaptureAutoReject
                                   : switches::kCaptureAutoReject);
  }

  bool PreferCurrentTab() const override { return prefer_current_tab_; }

 private:
  const bool prefer_current_tab_;
};

INSTANTIATE_TEST_SUITE_P(,
                         WebRtcScreenCaptureBrowserTestUserRejection,
                         Bool(),
                         [](const testing::TestParamInfo<bool>& info) {
                           return info.param ? "PreferCurrentTab" : "Standard";
                         });

IN_PROC_BROWSER_TEST_P(WebRtcScreenCaptureBrowserTestUserRejection,
                       CorrectErrorReported) {
  ASSERT_TRUE(embedded_test_server()->Start());
  OpenTestPageInNewTab(kCapturedPageMain);
  content::WebContents* capturing_tab = OpenTestPageInNewTab(kMainHtmlPage);

  RunGetDisplayMedia(
      capturing_tab,
      GetConstraints(
          /*video=*/true, /*audio=*/false),
      /*is_fake_ui=*/false,
      /*expect_success=*/false,
      /*is_tab_capture=*/true,
      /*expected_error=*/"NotAllowedError: Permission denied by user");
}

// RestrictOwnAudio is only supported on macOS and Windows.
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
class GetDisplayMediaRestrictOwnAudioTest
    : public WebRtcTestBase,
      public testing::WithParamInterface<std::tuple<bool, bool>> {
 public:
  GetDisplayMediaRestrictOwnAudioTest() : test_config_(GetParam()) {}

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(switches::kAutoSelectScreenCaptureSource);
    command_line->AppendSwitch(switches::kSystemAudioCaptureDefaultChecked);
  }

  void SetUpOnMainThread() override {
    WebRtcTestBase::SetUpOnMainThread();
    // Ensure that the bot has audio devices since we know that the test will
    // fail without it. getDisplayMedia() hangs on bots without audio devices
    // unless fake audio is used. A real audio track is required to verify that
    // both the device ID and track label are correct; hence using a fake audio
    // device will not work for this test.
    if (!HasAudioOutputDevices() || !HasAudioInputDevices()) {
      GTEST_SKIP() << "Missing audio devices: skipping test...";
    }
#if BUILDFLAG(IS_MAC)
    // The API for system audio sharing is available from macOS 14.2. If macOS
    // older then 14.2 is used, there will be no option in the UI for sharing
    // system audio, and we will not have an audio track in the test.
    int mac_os_version = base::mac::MacOSVersion();
    int major_mac_os_version = mac_os_version / 1'00'00;
    int minor_mac_os_version = (mac_os_version / 1'00) % 1'00;
    if (major_mac_os_version <= 13 ||
        (major_mac_os_version == 14 && minor_mac_os_version < 2)) {
      GTEST_SKIP() << "macOS version do not support system audio sharing: "
                      "skipping test...";
    }
#endif
  }

  void SetUpInProcessBrowserTestFixture() override {
#if BUILDFLAG(IS_MAC)
    feature_list_.InitWithFeatures({media::kMacCatapLoopbackAudioForCast,
                                    media::kMacCatapLoopbackAudioForScreenShare,
                                    blink::features::kRestrictOwnAudio},
                                   {media::kUseSCContentSharingPicker});
#elif BUILDFLAG(IS_WIN)
    feature_list_.InitWithFeatures({blink::features::kRestrictOwnAudio}, {});
#endif

    WebRtcTestBase::SetUpInProcessBrowserTestFixture();

    DetectErrorsInJavaScript();
  }

  // Synchronously checks if the system/bot has audio output devices.
  bool HasAudioOutputDevices() {
    bool has_devices = false;
    base::RunLoop run_loop;
    auto audio_system = content::CreateAudioSystemForAudioService();
    audio_system->HasOutputDevices(base::BindOnce(
        [](base::OnceClosure finished_callback, bool* result, bool received) {
          *result = received;
          std::move(finished_callback).Run();
        },
        run_loop.QuitClosure(), &has_devices));
    run_loop.Run();
    return has_devices;
  }
  // Synchronously checks if the system/bot has audio input devices.
  bool HasAudioInputDevices() {
    bool has_devices = false;
    base::RunLoop run_loop;
    auto audio_system = content::CreateAudioSystemForAudioService();
    audio_system->HasInputDevices(base::BindOnce(
        [](base::OnceClosure finished_callback, bool* result, bool received) {
          *result = received;
          std::move(finished_callback).Run();
        },
        run_loop.QuitClosure(), &has_devices));
    run_loop.Run();
    return has_devices;
  }

  std::string GetConstraintsSystemAudio(bool suppress_local_audio_playback,
                                        bool restrict_own_audio) const {
    return base::StringPrintf(
        "{video: true, audio: {suppressLocalAudioPlayback: %s, "
        "restrictOwnAudio: %s}, preferCurrentTab: false, systemAudio: "
        "\"include\"}",
        base::ToString(suppress_local_audio_playback),
        base::ToString(restrict_own_audio));
  }

  const TestConfigForRestrictOwnAudio test_config_;

 private:
  base::test::ScopedFeatureList feature_list_;
};

INSTANTIATE_TEST_SUITE_P(
    All,
    GetDisplayMediaRestrictOwnAudioTest,
    Combine(
        /*restrict_own_audio=*/Bool(),
        /*suppress_local_audio_playback=*/Bool()),
    [](const testing::TestParamInfo<
        GetDisplayMediaRestrictOwnAudioTest::ParamType>& info) {
      return base::JoinString(
          {
              std::get<0>(info.param) ? "RestrictOwnAudio"
                                      : "NoRestrictOwnAudio",
              std::get<1>(info.param) ? "SuppressLocalAudio"
                                      : "NoSuppressLocalAudio",
          },
          "_");
    });

IN_PROC_BROWSER_TEST_P(GetDisplayMediaRestrictOwnAudioTest,
                       ScreenCaptureWithRestrictOwnAudio) {
  ASSERT_TRUE(embedded_test_server()->Start());
  content::WebContents* tab = OpenTestPageInNewTab(kMainHtmlPage);
  RunGetDisplayMedia(
      tab,
      GetConstraintsSystemAudio(test_config_.suppress_local_audio_playback,
                                test_config_.restrict_own_audio),
      /*is_fake_ui=*/false,
      /*expect_success=*/true,
      /*is_tab_capture=*/false);
  EXPECT_EQ(content::EvalJs(tab->GetPrimaryMainFrame(), "hasAudioTrack();"),
            "true");
  EXPECT_EQ(
      content::EvalJs(tab->GetPrimaryMainFrame(), "getAudioTrackLabel();"),
      "System Audio");

  if (test_config_.restrict_own_audio) {
    EXPECT_EQ(
        content::EvalJs(tab->GetPrimaryMainFrame(), "getAudioDeviceId();"),
        "loopbackWithoutChrome");
  } else if (test_config_.suppress_local_audio_playback) {
    EXPECT_EQ(
        content::EvalJs(tab->GetPrimaryMainFrame(), "getAudioDeviceId();"),
        "loopbackWithMute");
  } else {
    EXPECT_EQ(
        content::EvalJs(tab->GetPrimaryMainFrame(), "getAudioDeviceId();"),
        "loopback");
  }
}

#endif  // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
