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

#include <array>


#include <memory>
#include <string>
#include <vector>

#include "base/command_line.h"
#include "base/files/file_path.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/stringprintf.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/media/webrtc/webrtc_browsertest_base.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/permissions/permission_request_manager.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_base.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/prerender_test_util.h"
#include "media/capture/mojom/video_capture_types.mojom.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "third_party/blink/public/common/features.h"
#include "ui/gl/gl_switches.h"

namespace {

using ::content::WebContents;
using ::media::mojom::SubCaptureTargetType;
using ::testing::Bool;
using ::testing::Combine;
using ::testing::TestParamInfo;
using ::testing::Values;
using ::testing::WithParamInterface;

// Comparisons with sub-capture targets (CropTargets/RestrictionTargets) are
// limited. We can only know that a one was returned by the test. However, we
// keep this matcher around in anticipation of targets being made either
// comparable or stringifiable. Then we can do more interesting comparisons,
// like ensuring uniqueness or checking that repeatedly calling
// CropTarget.fromElement() or RestrictionTarget.fromElement() on the same
// Element yields either the same target, or an equivalent one.
MATCHER_P(IsExpectedTarget, expected_target_index, "") {
  static_assert(std::is_same<decltype(arg), const std::string&>::value, "");
  return arg == expected_target_index;
}

const char kMainPageTitle[] = "Region/Element Capture Test - Page 1 (Main)";
const char kOtherPageTitle[] = "Region/Element Capture Test - Page 2 (Main)";

// Tracks SubCaptureTargetIdWebContentsHelper::kMaxIdsPerWebContents.
constexpr size_t kMaxIdsPerWebContents = 100;

enum {
  kMainPageTopLevelDocument,
  kMainPageEmbeddedDocument,
  kOtherPageTopLevelDocument,
  kOtherPageEmbeddedDocument,
  kMailmanServer,
  kServerCount  // Must be last.
};

enum Tab {
  kMainTab,
  kOtherTab,
  kTabCount  // Must be last.
};

enum class Frame {
  kNone,
  kTopLevelDocument,
  kEmbeddedFrame,
};

enum class Track { kOriginal, kClone, kSecond };

const char* ToString(Frame frame) {
  switch (frame) {
    case Frame::kNone:
      return "none";
    case Frame::kTopLevelDocument:
      return "top-level";
    case Frame::kEmbeddedFrame:
      return "embedded";
  }
  NOTREACHED();
}

const char* ToString(Track track) {
  switch (track) {
    case Track::kOriginal:
      return "original";
    case Track::kClone:
      return "clone";
    case Track::kSecond:
      return "second";
  }
  NOTREACHED();
}

const char* ToString(SubCaptureTargetType type) {
  switch (type) {
    case SubCaptureTargetType::kCropTarget:
      return "crop-target";
    case SubCaptureTargetType::kRestrictionTarget:
      return "restriction-target";
  }
  NOTREACHED();
}

// Conveniently pack together all relevant information about a tab and
// conveniently expose test controls on it.
struct TabInfo {
  void StartEmbeddingFrame(const GURL& url) {
    EXPECT_EQ(content::EvalJs(web_contents->GetPrimaryMainFrame(),
                              base::StringPrintf("startEmbeddingFrame('%s');",
                                                 url.spec().c_str())),
              "embedding-done");
  }

  void SetUpMailman(const GURL& url) {
    EXPECT_EQ(content::EvalJs(web_contents->GetPrimaryMainFrame(),
                              base::StringPrintf("setUpMailman('%s');",
                                                 url.spec().c_str())),
              "mailman-ready");
  }

  void StartCapture() {
    // Bring the tab into focus. This avoids getDisplayMedia rejection.
    browser->tab_strip_model()->ActivateTabAt(tab_strip_index);

    EXPECT_EQ(
        content::EvalJs(web_contents->GetPrimaryMainFrame(), "startCapture();"),
        "top-level-capture-success");
  }

  void StartCaptureFromEmbeddedFrame() {
    // Bring the tab into focus. This avoids getDisplayMedia rejection.
    browser->tab_strip_model()->ActivateTabAt(tab_strip_index);

    EXPECT_EQ(content::EvalJs(web_contents->GetPrimaryMainFrame(),
                              "startCaptureFromEmbeddedFrame();"),
              "embedded-capture-success");
  }

  bool StartSecondCapture(Frame frame) {
    // Bring the tab into focus. This avoids getDisplayMedia rejection.
    browser->tab_strip_model()->ActivateTabAt(tab_strip_index);

    return content::EvalJs(
               web_contents->GetPrimaryMainFrame(),
               base::StringPrintf("startSecondCapture('%s');", ToString(frame)))
               .ExtractString() ==
           base::StrCat({ToString(frame), "-second-capture-success"});
  }

  bool StopCapture(Frame frame, Track track) {
    return content::EvalJs(web_contents->GetPrimaryMainFrame(),
                           base::StringPrintf("stopCapture('%s', '%s');",
                                              ToString(frame), ToString(track)))
               .ExtractString() ==
           base::StrCat({ToString(frame), "-stop-success"});
  }

  std::string SubCaptureTargetFromElement(
      SubCaptureTargetType type,
      Frame frame,
      const std::string& element_id = "div") {
    CHECK_NE(frame, Frame::kNone);

    const std::string js_instruction =
        base::StringPrintf("subCaptureTargetFromElement('%s', '%s', '%s');",
                           ToString(type), ToString(frame), element_id.c_str());

    return content::EvalJs(web_contents->GetPrimaryMainFrame(), js_instruction)
        .ExtractString();
  }

  // Takes as input either the sub-capture target[*], or "undefined" if the test
  // wants track.cropTo(undefined) or track.restrictTo(undefined) to be invoked.
  //
  // [*] Actually, because CropTargets/RestrictionTargets are not stringifiable,
  // an index of the CropTarget/RestrictionTarget is used, and translated by JS
  // code back into the token it had previously stored.
  bool ApplySubCaptureTarget(const std::string& target,
                             SubCaptureTargetType sub_capture_type,
                             Frame frame,
                             Track track = Track::kOriginal) {
    CHECK(frame == Frame::kTopLevelDocument || frame == Frame::kEmbeddedFrame);

    std::string script_result =
        content::EvalJs(web_contents->GetPrimaryMainFrame(),
                        base::StringPrintf(
                            "applySubCaptureByIndex('%s', '%s', '%s', '%s');",
                            target.c_str(), ToString(sub_capture_type),
                            ToString(frame), ToString(track)))
            .ExtractString();

    EXPECT_EQ(0u, script_result.rfind(ToString(frame), 0)) << script_result;
    return script_result == base::StringPrintf("%s-%s-success", ToString(frame),
                                               ToString(sub_capture_type));
  }

  bool CloneTrack() {
    std::string script_result =
        content::EvalJs(web_contents->GetPrimaryMainFrame(), "clone();")
            .ExtractString();
    DCHECK(script_result == "clone-track-success" ||
           script_result == "clone-track-failure");
    return script_result == "clone-track-success";
  }

  bool Deallocate(Track track) {
    std::string script_result =
        content::EvalJs(
            web_contents->GetPrimaryMainFrame(),
            base::StringPrintf("deallocate('%s');", ToString(track)))
            .ExtractString();
    DCHECK(script_result == "deallocate-failure" ||
           script_result == "deallocate-success");
    return script_result == "deallocate-success";
  }

  bool HideElement(const char* element_id) {
    std::string script_result =
        content::EvalJs(web_contents->GetPrimaryMainFrame(),
                        base::StringPrintf("hideElement('%s');", element_id))
            .ExtractString();
    DCHECK(script_result == "hide-element-failure" ||
           script_result == "hide-element-success");
    return script_result == "hide-element-success";
  }

  bool CreateNewElement(Frame frame, const char* tag, const std::string& id) {
    DCHECK_NE(frame, Frame::kNone);
    std::string script_result =
        content::EvalJs(web_contents->GetPrimaryMainFrame(),
                        base::StrCat({"createNewElement(\"", ToString(frame),
                                      "\", \"", tag, "\", \"", id, "\");"}))
            .ExtractString();

    if (frame == Frame::kEmbeddedFrame) {
      EXPECT_EQ(0u, script_result.rfind("embedded-", 0)) << script_result;
      return script_result == "embedded-new-element-success";
    }
    DCHECK(frame == Frame::kTopLevelDocument);
    EXPECT_EQ(0u, script_result.rfind("top-level-", 0)) << script_result;
    return script_result == "top-level-new-element-success";
  }

  bool CreateNewDivElement(Frame frame, const std::string& id) {
    return CreateNewElement(frame, "div", id);
  }

  raw_ptr<Browser, AcrossTasksDanglingUntriaged> browser;
  raw_ptr<WebContents, AcrossTasksDanglingUntriaged> web_contents;
  int tab_strip_index;
};

}  // namespace

// Essentially depends on InProcessBrowserTest, but WebRtcTestBase provides
// detection of JS errors.
class SubCaptureBrowserTestBase : public WebRtcTestBase {
 public:
  SubCaptureBrowserTestBase() = default;
  ~SubCaptureBrowserTestBase() override = default;

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

    DetectErrorsInJavaScript();

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

    for (int i = 0; i < kServerCount; ++i) {
      servers_.emplace_back(std::make_unique<net::EmbeddedTestServer>());
      servers_[i]->ServeFilesFromDirectory(test_dir);
      ASSERT_TRUE(servers_[i]->Start());
    }
  }

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitch(
        switches::kEnableExperimentalWebPlatformFeatures);
    // 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
    command_line_ = command_line;
  }

  void TearDownOnMainThread() override {
    for (auto& server : servers_) {
      if (server) {
        ASSERT_TRUE(server->ShutdownAndWaitUntilComplete());
      }
    }

    WebRtcTestBase::TearDownOnMainThread();
  }

  // Same as WebRtcTestBase::OpenTestPageInNewTab, but does not assume
  // a single embedded server is used for all pages, and also embeds
  // a cross-origin iframe.
  void SetUpPage(const std::string& top_level_document_document,
                 net::EmbeddedTestServer* top_level_document_server,
                 const std::string& embedded_iframe_document,
                 net::EmbeddedTestServer* embedded_iframe_server,
                 TabInfo* tab_info) const {
    chrome::AddTabAt(browser(), GURL(url::kAboutBlankURL), -1, true);
    EXPECT_TRUE(ui_test_utils::NavigateToURL(
        browser(),
        top_level_document_server->GetURL(top_level_document_document)));

    WebContents* const web_contents =
        browser()->tab_strip_model()->GetActiveWebContents();
    permissions::PermissionRequestManager::FromWebContents(web_contents)
        ->set_auto_response_for_test(
            permissions::PermissionRequestManager::ACCEPT_ALL);

    *tab_info = {browser(), web_contents,
                 browser()->tab_strip_model()->active_index()};
    tab_info->StartEmbeddingFrame(
        embedded_iframe_server->GetURL(embedded_iframe_document));

    // Start embedding a frame whose sole purpose is to be same-origin
    // across all other documents and therefore allow communication
    // between them all over a shared BroadcastChannel.
    tab_info->SetUpMailman(
        servers_[kMailmanServer]->GetURL("/webrtc/sub_capture_mailman.html"));
  }

  // Set up all (necessary) tabs, loads iframes, and start capturing the
  // relevant tab.
  void SetUpTest(Frame capturing_entity, bool self_capture) {
    // Other page (for other-tab-capture).
    SetUpPage("/webrtc/sub_capture_other_main.html",
              servers_[kOtherPageTopLevelDocument].get(),
              "/webrtc/sub_capture_other_embedded.html",
              servers_[kOtherPageEmbeddedDocument].get(), &tabs_[kOtherTab]);

    // Main page (for self-capture). Instantiate it second to help it get focus.
    SetUpPage("/webrtc/sub_capture_main.html",
              servers_[kMainPageTopLevelDocument].get(),
              "/webrtc/sub_capture_embedded.html",
              servers_[kMainPageEmbeddedDocument].get(), &tabs_[kMainTab]);

    DCHECK(command_line_);
    command_line_->AppendSwitchASCII(
        switches::kAutoSelectTabCaptureSourceByTitle,
        self_capture ? kMainPageTitle : kOtherPageTitle);

    if (capturing_entity == Frame::kTopLevelDocument) {
      tabs_[kMainTab].StartCapture();
    } else if (capturing_entity == Frame::kEmbeddedFrame) {
      tabs_[kMainTab].StartCaptureFromEmbeddedFrame();
    }
  }

  // Manipulation after SetUpCommandLine, but before capture starts,
  // allows tests to set which tab to capture.
  raw_ptr<base::CommandLine> command_line_ = nullptr;

  // Holds the tabs manipulated by this test.
  std::array<TabInfo, kTabCount> tabs_;

  // Each page is served from a distinct origin, thereby proving that
  // cropping/restricting works irrespective of whether iframes are
  // in/out-of-process.
  std::vector<std::unique_ptr<net::EmbeddedTestServer>> servers_;

  base::test::ScopedFeatureList scoped_feature_list_;
};

// Test that cropTo() and restrictTo() work on some expected element types.
class SubCaptureBrowserTargetElementTest
    : public SubCaptureBrowserTestBase,
      public WithParamInterface<std::tuple<SubCaptureTargetType, const char*>> {
 public:
  SubCaptureBrowserTargetElementTest()
      : sub_capture_type_(std::get<0>(GetParam())),
        target_element_(std::get<1>(GetParam())) {}
  ~SubCaptureBrowserTargetElementTest() override = default;

  void TestCanApplyToElementTag(const char* tag) {
    TabInfo& tab = tabs_[kMainTab];

    const std::string element_id = base::StrCat({"new_id_", tag});
    ASSERT_TRUE(
        tab.CreateNewElement(Frame::kTopLevelDocument, tag, element_id));
    const std::string target = tab.SubCaptureTargetFromElement(
        sub_capture_type_, Frame::kTopLevelDocument, element_id);
    ASSERT_THAT(target, IsExpectedTarget("0"));

    EXPECT_TRUE(tab.ApplySubCaptureTarget(target, sub_capture_type_,
                                          Frame::kTopLevelDocument));
  }

 protected:
  const SubCaptureTargetType sub_capture_type_;
  const char* const target_element_;
};

std::string SubCaptureBrowserTargetElementTestParamsToString(
    const TestParamInfo<SubCaptureBrowserTargetElementTest::ParamType>& info) {
  const std::string api =
      std::get<0>(info.param) == SubCaptureTargetType::kCropTarget
          ? "RegionCapture"
          : "ElementCapture";

  std::string element = std::get<1>(info.param);
  element[0] = std::toupper(element[0]);

  return api + element;
}

INSTANTIATE_TEST_SUITE_P(
    ,
    SubCaptureBrowserTargetElementTest,
    Combine(Values(SubCaptureTargetType::kCropTarget,
                   SubCaptureTargetType::kRestrictionTarget),
            Values("a",
                   "blockquote",
                   "body",
                   "button",
                   "canvas",
                   "col",
                   "div",
                   "fieldset",
                   "form",
                   "h1",
                   "header",
                   "hr"
                   "iframe",
                   "img",
                   "input",
                   "output",
                   "span",
                   "svg",
                   "video")),
    SubCaptureBrowserTargetElementTestParamsToString);

IN_PROC_BROWSER_TEST_P(SubCaptureBrowserTargetElementTest, ApplyToElement) {
  SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
  TestCanApplyToElementTag(target_element_);
}

class SubCaptureBrowserTest : public SubCaptureBrowserTestBase,
                              public WithParamInterface<SubCaptureTargetType> {
 public:
  SubCaptureBrowserTest() : type_(GetParam()) {}
  ~SubCaptureBrowserTest() override = default;

 protected:
  const SubCaptureTargetType type_;
};

std::string SubCaptureBrowserTestParamsToString(
    const TestParamInfo<SubCaptureBrowserTest::ParamType>& info) {
  return info.param == SubCaptureTargetType::kCropTarget ? "RegionCapture"
                                                         : "ElementCapture";
}

INSTANTIATE_TEST_SUITE_P(,
                         SubCaptureBrowserTest,
                         Values(SubCaptureTargetType::kCropTarget,
                                SubCaptureTargetType::kRestrictionTarget),
                         SubCaptureBrowserTestParamsToString);

IN_PROC_BROWSER_TEST_P(SubCaptureBrowserTest,
                       FromElementReturnsValidIdInMainPage) {
  SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
  EXPECT_THAT(tabs_[kMainTab].SubCaptureTargetFromElement(
                  type_, Frame::kTopLevelDocument),
              IsExpectedTarget("0"));
}

IN_PROC_BROWSER_TEST_P(SubCaptureBrowserTest,
                       FromElementReturnsValidIdInCrossOriginIframe) {
  SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
  EXPECT_THAT(
      tabs_[kMainTab].SubCaptureTargetFromElement(type_, Frame::kEmbeddedFrame),
      IsExpectedTarget("0"));
}

IN_PROC_BROWSER_TEST_P(SubCaptureBrowserTest,
                       SubCaptureAllowedIfTopLevelAppliesToElementInTopLevel) {
  SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
  TabInfo& tab = tabs_[kMainTab];

  const std::string target =
      tab.SubCaptureTargetFromElement(type_, Frame::kTopLevelDocument);
  ASSERT_THAT(target, IsExpectedTarget("0"));
  EXPECT_TRUE(
      tab.ApplySubCaptureTarget(target, type_, Frame::kTopLevelDocument));
}

IN_PROC_BROWSER_TEST_P(SubCaptureBrowserTest,
                       SubCaptureAllowedIfTopLevelAppliesToElementInEmbedded) {
  SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
  TabInfo& tab = tabs_[kMainTab];

  const std::string target =
      tab.SubCaptureTargetFromElement(type_, Frame::kEmbeddedFrame);
  ASSERT_THAT(target, IsExpectedTarget("0"));
  EXPECT_TRUE(
      tab.ApplySubCaptureTarget(target, type_, Frame::kTopLevelDocument));
}

IN_PROC_BROWSER_TEST_P(
    SubCaptureBrowserTest,
    SubCaptureAllowedIfEmbeddedFrameAppliesToElementInTopLevel) {
  SetUpTest(Frame::kEmbeddedFrame, /*self_capture=*/true);
  TabInfo& tab = tabs_[kMainTab];

  const std::string target =
      tab.SubCaptureTargetFromElement(type_, Frame::kTopLevelDocument);
  ASSERT_THAT(target, IsExpectedTarget("0"));
  EXPECT_TRUE(tab.ApplySubCaptureTarget(target, type_, Frame::kEmbeddedFrame));
}

IN_PROC_BROWSER_TEST_P(
    SubCaptureBrowserTest,
    SubCaptureAllowedIfEmbeddedFrameAppliesToElementInEmbedded) {
  SetUpTest(Frame::kEmbeddedFrame, /*self_capture=*/true);
  TabInfo& tab = tabs_[kMainTab];

  const std::string target =
      tab.SubCaptureTargetFromElement(type_, Frame::kEmbeddedFrame);
  ASSERT_THAT(target, IsExpectedTarget("0"));
  EXPECT_TRUE(tab.ApplySubCaptureTarget(target, type_, Frame::kEmbeddedFrame));
}

IN_PROC_BROWSER_TEST_P(SubCaptureBrowserTest, SubCaptureAllowedToUndo) {
  SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
  TabInfo& tab = tabs_[kMainTab];

  const std::string target =
      tab.SubCaptureTargetFromElement(type_, Frame::kTopLevelDocument);
  ASSERT_THAT(target, IsExpectedTarget("0"));
  ASSERT_TRUE(
      tab.ApplySubCaptureTarget(target, type_, Frame::kTopLevelDocument));
  EXPECT_TRUE(
      tab.ApplySubCaptureTarget("undefined", type_, Frame::kTopLevelDocument));
}

IN_PROC_BROWSER_TEST_P(
    SubCaptureBrowserTest,
    MayAttemptToUndoSubCaptureOnTracksWhereSubCaptureHasNotBeenApplied) {
  SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
  TabInfo& tab = tabs_[kMainTab];

  const std::string target =
      tab.SubCaptureTargetFromElement(type_, Frame::kTopLevelDocument);
  ASSERT_THAT(target, IsExpectedTarget("0"));
  // ApplySubCaptureTarget(target) is intentionally not called.
  // Instead, the test immediately calls ApplySubCaptureTarget(undefined) on a
  // still-uncropped-and-unrestricted track, attempting to undo sub-capture
  // although it's not been applied.
  EXPECT_TRUE(
      tab.ApplySubCaptureTarget("undefined", type_, Frame::kTopLevelDocument));
}

// The Promise resolves when it's guaranteed that no additional frames will be
// issued with an earlier sub-capture-target version. That an actual frame be
// issued at all, let alone with the new sub-capture-target version, is not
// actually required, or else these promises could languish unfulfilled
// indefinitely.
IN_PROC_BROWSER_TEST_P(SubCaptureBrowserTest,
                       SubCaptureOfInvisibleElementResolvesInTimelyFashion) {
  SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
  TabInfo& tab = tabs_[kMainTab];

  ASSERT_TRUE(tab.HideElement("div"));

  const std::string target =
      tab.SubCaptureTargetFromElement(type_, Frame::kTopLevelDocument, "div");
  ASSERT_THAT(target, IsExpectedTarget("0"));
  EXPECT_TRUE(
      tab.ApplySubCaptureTarget(target, type_, Frame::kTopLevelDocument));
}

// TODO(crbug.com/431852186): Re-enable after flakes are resolved.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_MaxIdsInTopLevelDocument DISABLED_MaxIdsInTopLevelDocument
#else
#define MAYBE_MaxIdsInTopLevelDocument MaxIdsInTopLevelDocument
#endif
IN_PROC_BROWSER_TEST_P(SubCaptureBrowserTest, MAYBE_MaxIdsInTopLevelDocument) {
  SetUpTest(Frame::kNone, /*self_capture=*/false);
  TabInfo& tab = tabs_[kMainTab];

  for (size_t i = 0; i < kMaxIdsPerWebContents; ++i) {
    const std::string element_id = ("new_id_" + base::NumberToString(i));
    ASSERT_TRUE(tab.CreateNewDivElement(Frame::kTopLevelDocument, element_id));
    const std::string target = tab.SubCaptureTargetFromElement(
        type_, Frame::kTopLevelDocument, element_id);
    ASSERT_THAT(target, IsExpectedTarget(base::NumberToString(i)));
  }

  // Create one more element - this one won't get a sub-capture-target.
  const std::string element_id =
      ("new_id_" + base::NumberToString(kMaxIdsPerWebContents));
  ASSERT_TRUE(tab.CreateNewDivElement(Frame::kTopLevelDocument, element_id));
  EXPECT_EQ(tab.SubCaptureTargetFromElement(type_, Frame::kTopLevelDocument,
                                            element_id),
            base::StringPrintf("top-level-produce-%s-error", ToString(type_)));
}

// TODO(crbug.com/431852186): Re-enable after flakes are resolved.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_MaxIdsInEmbeddedFrame DISABLED_MaxIdsInEmbeddedFrame
#else
#define MAYBE_MaxIdsInEmbeddedFrame MaxIdsInEmbeddedFrame
#endif
IN_PROC_BROWSER_TEST_P(SubCaptureBrowserTest, MAYBE_MaxIdsInEmbeddedFrame) {
  SetUpTest(Frame::kNone, /*self_capture=*/false);
  TabInfo& tab = tabs_[kMainTab];

  for (size_t i = 0; i < kMaxIdsPerWebContents; ++i) {
    const std::string element_id = ("new_id_" + base::NumberToString(i));
    ASSERT_TRUE(tab.CreateNewDivElement(Frame::kEmbeddedFrame, element_id));
    const std::string target = tab.SubCaptureTargetFromElement(
        type_, Frame::kEmbeddedFrame, element_id);
    ASSERT_THAT(target, IsExpectedTarget(base::NumberToString(i)));
  }

  // Create one more element - this one won't get a sub-capture-target.
  const std::string element_id =
      ("new_id_" + base::NumberToString(kMaxIdsPerWebContents));
  ASSERT_TRUE(tab.CreateNewDivElement(Frame::kEmbeddedFrame, element_id));
  EXPECT_EQ(
      tab.SubCaptureTargetFromElement(type_, Frame::kEmbeddedFrame, element_id),
      base::StringPrintf("embedded-produce-%s-error", ToString(type_)));
}

// TODO(crbug.com/431852186): Re-enable after flakes are resolved.
#if BUILDFLAG(IS_LINUX)
#define MAYBE_MaxIdsSharedBetweenFramesInTab \
  DISABLED_MaxIdsSharedBetweenFramesInTab
#else
#define MAYBE_MaxIdsSharedBetweenFramesInTab MaxIdsSharedBetweenFramesInTab
#endif
IN_PROC_BROWSER_TEST_P(SubCaptureBrowserTest,
                       MAYBE_MaxIdsSharedBetweenFramesInTab) {
  SetUpTest(Frame::kNone, /*self_capture=*/false);
  TabInfo& tab = tabs_[kMainTab];

  static_assert(kMaxIdsPerWebContents > 1, "");

  // Create (kMaxIdsPerWebContents - 1) new elements and assign each a
  // sub-capture-target.
  for (size_t i = 0; i < kMaxIdsPerWebContents - 1; ++i) {
    const std::string element_id = ("new_id_" + base::NumberToString(i));
    ASSERT_TRUE(tab.CreateNewDivElement(Frame::kTopLevelDocument, element_id));
    const std::string target = tab.SubCaptureTargetFromElement(
        type_, Frame::kTopLevelDocument, element_id);
    ASSERT_THAT(target, IsExpectedTarget(base::NumberToString(i)));
  }

  // One more in the embedded frame is possible.
  std::string element_id =
      ("new_id_" + base::NumberToString(kMaxIdsPerWebContents - 1));
  ASSERT_TRUE(tab.CreateNewDivElement(Frame::kEmbeddedFrame, element_id));
  const std::string target =
      tab.SubCaptureTargetFromElement(type_, Frame::kEmbeddedFrame, element_id);
  EXPECT_THAT(target, IsExpectedTarget(
                          base::NumberToString(kMaxIdsPerWebContents - 1)));

  // Create one more element - this one won't get a sub-capture-target.
  element_id = ("new_id_" + base::NumberToString(kMaxIdsPerWebContents));
  ASSERT_TRUE(tab.CreateNewDivElement(Frame::kTopLevelDocument, element_id));
  EXPECT_EQ(tab.SubCaptureTargetFromElement(type_, Frame::kTopLevelDocument,
                                            element_id),
            base::StringPrintf("top-level-produce-%s-error", ToString(type_)));
  // Neither in the top-level nor in the embedded frame.
  element_id = ("new_id_" + base::NumberToString(kMaxIdsPerWebContents + 1));
  ASSERT_TRUE(tab.CreateNewDivElement(Frame::kEmbeddedFrame, element_id));
  EXPECT_EQ(
      tab.SubCaptureTargetFromElement(type_, Frame::kEmbeddedFrame, element_id),
      base::StringPrintf("embedded-produce-%s-error", ToString(type_)));
}

// Tests related to behavior when cloning.
class SubCaptureClonesBrowserTestBase : public SubCaptureBrowserTestBase {
 public:
  static constexpr char kTarget0[] = "0";
  static constexpr char kTarget1[] = "1";

  ~SubCaptureClonesBrowserTestBase() override = default;

  void SetUpCommandLine(base::CommandLine* command_line) override {
    command_line->AppendSwitchASCII("js-flags", "--expose-gc");
    SubCaptureBrowserTestBase::SetUpCommandLine(command_line);
  }

  // Has to be called from within the test's body.
  void ManualSetUp(SubCaptureTargetType type) {
    SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
    EXPECT_THAT(tabs_[kMainTab].SubCaptureTargetFromElement(
                    type, Frame::kTopLevelDocument, "div"),
                IsExpectedTarget(kTarget0));
    EXPECT_THAT(tabs_[kMainTab].SubCaptureTargetFromElement(
                    type, Frame::kTopLevelDocument, "embedded_frame"),
                IsExpectedTarget(kTarget1));
  }

  bool ApplySubCaptureTarget(SubCaptureTargetType type,
                             const std::string& target,
                             Frame frame,
                             Track track) {
    return tabs_[kMainTab].ApplySubCaptureTarget(target, type, frame, track);
  }

  bool CloneTrack() { return tabs_[kMainTab].CloneTrack(); }

  bool Deallocate(Track track) { return tabs_[kMainTab].Deallocate(track); }
};

class SubCaptureClonesBrowserTest
    : public SubCaptureClonesBrowserTestBase,
      public WithParamInterface<SubCaptureTargetType> {
 public:
  SubCaptureClonesBrowserTest() : type_(GetParam()) {}
  ~SubCaptureClonesBrowserTest() override = default;

 protected:
  const SubCaptureTargetType type_;
};

std::string SubCaptureClonesBrowserTestParamsToString(
    const TestParamInfo<SubCaptureClonesBrowserTest::ParamType>& info) {
  return info.param == SubCaptureTargetType::kCropTarget ? "RegionCapture"
                                                         : "ElementCapture";
}

INSTANTIATE_TEST_SUITE_P(,
                         SubCaptureClonesBrowserTest,
                         Values(SubCaptureTargetType::kCropTarget,
                                SubCaptureTargetType::kRestrictionTarget),
                         SubCaptureClonesBrowserTestParamsToString);

// Sanity cloning 1/2.
IN_PROC_BROWSER_TEST_P(SubCaptureClonesBrowserTest,
                       CanCloneTracksWhichHadNoSubCaptureApplied) {
  ManualSetUp(type_);

  EXPECT_TRUE(CloneTrack());
}

// Sanity cloning 2/2.
IN_PROC_BROWSER_TEST_P(SubCaptureClonesBrowserTest,
                       CanCloneTrackWhichHaveSubCaptureApplied) {
  ManualSetUp(type_);

  ASSERT_TRUE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                    Track::kOriginal));

  EXPECT_TRUE(CloneTrack());
}

// Restrictions on cloned tracks 1/3.
IN_PROC_BROWSER_TEST_P(SubCaptureClonesBrowserTest,
                       CannotApplySubCaptureOnClones) {
  ManualSetUp(type_);

  ASSERT_TRUE(CloneTrack());

  EXPECT_FALSE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                     Track::kClone));
}

// Restrictions on cloned tracks 2/3.
IN_PROC_BROWSER_TEST_P(SubCaptureClonesBrowserTest,
                       CannotReapplySubCaptureOnClones) {
  ManualSetUp(type_);

  ASSERT_TRUE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                    Track::kOriginal));
  ASSERT_TRUE(CloneTrack());

  EXPECT_FALSE(ApplySubCaptureTarget(type_, kTarget1, Frame::kTopLevelDocument,
                                     Track::kClone));
}

// Restrictions on cloned tracks 3/3.
IN_PROC_BROWSER_TEST_P(SubCaptureClonesBrowserTest,
                       CannotUndoSubCaptureOnClones) {
  ManualSetUp(type_);

  ASSERT_TRUE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                    Track::kOriginal));
  ASSERT_TRUE(CloneTrack());

  EXPECT_FALSE(ApplySubCaptureTarget(type_, "undefined",
                                     Frame::kTopLevelDocument, Track::kClone));
}

// Restrictions on original track that has a clone 1/3.
IN_PROC_BROWSER_TEST_P(SubCaptureClonesBrowserTest,
                       CannotApplySubCaptureOnTracksThatHaveClones) {
  ManualSetUp(type_);

  ASSERT_TRUE(CloneTrack());

  EXPECT_FALSE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                     Track::kOriginal));
}

// Restrictions on original track that has a clone 2/3.
IN_PROC_BROWSER_TEST_P(SubCaptureClonesBrowserTest,
                       CannotReapplySubCaptureOnTracksThatHaveClones) {
  ManualSetUp(type_);

  ASSERT_TRUE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                    Track::kOriginal));
  ASSERT_TRUE(CloneTrack());

  EXPECT_FALSE(ApplySubCaptureTarget(type_, kTarget1, Frame::kTopLevelDocument,
                                     Track::kOriginal));
}

// Restrictions on original track that has a clone 3/3.
IN_PROC_BROWSER_TEST_P(SubCaptureClonesBrowserTest,
                       CannotUndoSubCaptureOnTracksThatHaveClones) {
  ManualSetUp(type_);

  ASSERT_TRUE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                    Track::kOriginal));
  ASSERT_TRUE(CloneTrack());

  EXPECT_FALSE(ApplySubCaptureTarget(
      type_, "undefined", Frame::kTopLevelDocument, Track::kOriginal));
}

// Original track becomes unblocked for sub-capture after clone is GCed 1/3.
//
// TODO(crbug.com/40858400)  Re-enable for macOS and ChromeOS after flakes are
// resolved.
// TODO(crbug.com/40919381): Also flakes on linux-bfcache-rel, so turning the
// test off entirely.
IN_PROC_BROWSER_TEST_P(
    SubCaptureClonesBrowserTest,
    DISABLED_CanApplySubCaptureOnOriginalTrackAfterCloneIsGarbageCollected) {
  ManualSetUp(type_);

  ASSERT_TRUE(CloneTrack());
  ASSERT_FALSE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                     Track::kOriginal));
  ASSERT_TRUE(Deallocate(Track::kClone));

  EXPECT_TRUE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                    Track::kOriginal));
}

// Original track becomes unblocked for sub-capture after clone is GCed 2/3.
//
// TODO(crbug.com/40858400) Re-enable after flakes are resolved.
IN_PROC_BROWSER_TEST_P(
    SubCaptureClonesBrowserTest,
    DISABLED_CanReapplySubCaptureOnOriginalTrackAfterCloneIsGarbageCollected) {
  ManualSetUp(type_);

  ASSERT_TRUE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                    Track::kOriginal));
  ASSERT_TRUE(CloneTrack());
  ASSERT_FALSE(ApplySubCaptureTarget(type_, kTarget1, Frame::kTopLevelDocument,
                                     Track::kOriginal));
  ASSERT_TRUE(Deallocate(Track::kClone));

  EXPECT_TRUE(ApplySubCaptureTarget(type_, kTarget1, Frame::kTopLevelDocument,
                                    Track::kOriginal));
}

// Original track becomes unblocked for sub-capture clone is GCed 3/3.
//
// TODO(crbug.com/40860614): Re-enable this test.
IN_PROC_BROWSER_TEST_P(
    SubCaptureClonesBrowserTest,
    DISABLED_CanUndoSubCaptureOnOriginalTrackAfterCloneIsGarbageCollected) {
  ManualSetUp(type_);

  ASSERT_TRUE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                    Track::kOriginal));
  ASSERT_TRUE(CloneTrack());
  ASSERT_FALSE(ApplySubCaptureTarget(
      type_, "undefined", Frame::kTopLevelDocument, Track::kOriginal));
  ASSERT_TRUE(Deallocate(Track::kClone));

  EXPECT_TRUE(ApplySubCaptureTarget(
      type_, "undefined", Frame::kTopLevelDocument, Track::kOriginal));
}

// Cloned track becomes unblocked for sub-capture after original is GCed 1/3.
//
// The following test is disabled because of a loosely-related issue,
// where an original track is kept alive if a clone exists, but not vice versa.
// TODO(crbug.com/40845775): Uncomment after fixing the aforementioned issue.
IN_PROC_BROWSER_TEST_P(
    SubCaptureClonesBrowserTest,
    DISABLED_CanApplySubCaptureOnCloneAfterOriginalTrackIsGarbageCollected) {
  ManualSetUp(type_);

  ASSERT_TRUE(CloneTrack());
  ASSERT_FALSE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                     Track::kClone));
  ASSERT_TRUE(Deallocate(Track::kOriginal));

  EXPECT_TRUE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                    Track::kClone));
}

// Cloned track becomes unblocked for sub-capture after original is GCed 2/3.
//
// The following test is disabled because of a loosely-related issue,
// where an original track is kept alive if a clone exists, but not vice versa.
// TODO(crbug.com/40845775): Uncomment after fixing the aforementioned issue.
IN_PROC_BROWSER_TEST_P(
    SubCaptureClonesBrowserTest,
    DISABLED_CanReapplySubCaptureOnCloneAfterOriginalTrackIsGarbageCollected) {
  ManualSetUp(type_);

  ASSERT_TRUE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                    Track::kOriginal));
  ASSERT_TRUE(CloneTrack());
  ASSERT_FALSE(ApplySubCaptureTarget(type_, kTarget1, Frame::kTopLevelDocument,
                                     Track::kClone));
  ASSERT_TRUE(Deallocate(Track::kOriginal));

  EXPECT_TRUE(ApplySubCaptureTarget(type_, kTarget1, Frame::kTopLevelDocument,
                                    Track::kClone));
}

// Cloned track becomes unblocked for sub-capture after original is GCed 3/3.
//
// The following test is disabled because of a loosely-related issue,
// where an original track is kept alive if a clone exists, but not vice versa.
// TODO(crbug.com/40845775): Uncomment after fixing the aforementioned issue.
IN_PROC_BROWSER_TEST_P(
    SubCaptureClonesBrowserTest,
    DISABLED_CanUndoSubCaptureOnCloneAfterOriginalTrackIsGarbageCollected) {
  ManualSetUp(type_);

  ASSERT_TRUE(ApplySubCaptureTarget(type_, kTarget0, Frame::kTopLevelDocument,
                                    Track::kOriginal));
  ASSERT_TRUE(CloneTrack());
  ASSERT_FALSE(ApplySubCaptureTarget(type_, "undefined",
                                     Frame::kTopLevelDocument, Track::kClone));
  ASSERT_TRUE(Deallocate(Track::kOriginal));

  EXPECT_TRUE(ApplySubCaptureTarget(type_, "undefined",
                                    Frame::kTopLevelDocument, Track::kClone));
}

// Tests similar issues to cloning, but with multiple captures triggering
// these issues instead.
class SubCaptureMultiCaptureBrowserTest
    : public SubCaptureClonesBrowserTestBase,
      public WithParamInterface<std::tuple<SubCaptureTargetType, Frame>> {
 public:
  SubCaptureMultiCaptureBrowserTest()
      : type_(std::get<0>(GetParam())),
        second_capture_frame_(std::get<1>(GetParam())) {}

  ~SubCaptureMultiCaptureBrowserTest() override = default;

  // Has to be called from within the test's body.
  void ManualSetUp() {
    SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
    EXPECT_THAT(tabs_[kMainTab].SubCaptureTargetFromElement(
                    type_, Frame::kTopLevelDocument, "div"),
                IsExpectedTarget(kTarget0));
    EXPECT_THAT(tabs_[kMainTab].SubCaptureTargetFromElement(
                    type_, Frame::kTopLevelDocument, "embedded_frame"),
                IsExpectedTarget(kTarget1));
  }

  bool StartSecondCapture() {
    return tabs_[kMainTab].StartSecondCapture(second_capture_frame_);
  }

  bool StopCapture(Track track) {
    return tabs_[kMainTab].StopCapture(Frame::kTopLevelDocument, track);
  }

 protected:
  const SubCaptureTargetType type_;
  const Frame second_capture_frame_;
};

std::string SubCaptureMultiCaptureBrowserTestParamsToString(
    const TestParamInfo<SubCaptureMultiCaptureBrowserTest::ParamType>& info) {
  const std::string api =
      std::get<0>(info.param) == SubCaptureTargetType::kCropTarget
          ? "RegionCapture"
          : "ElementCapture";

  const std::string frame = std::get<1>(info.param) == Frame::kTopLevelDocument
                                ? "TopLevelFrame"
                                : "EmbeddedFrame";

  return base::StrCat({api, "AndSecondCaptureFrameIs", frame});
}

INSTANTIATE_TEST_SUITE_P(
    ,
    SubCaptureMultiCaptureBrowserTest,
    Combine(Values(SubCaptureTargetType::kCropTarget,
                   SubCaptureTargetType::kRestrictionTarget),
            Values(Frame::kTopLevelDocument, Frame::kEmbeddedFrame)),
    SubCaptureMultiCaptureBrowserTestParamsToString);

IN_PROC_BROWSER_TEST_P(SubCaptureMultiCaptureBrowserTest,
                       CanSelfCaptureAgainIfNeverAppliedSubCapture) {
  ManualSetUp();

  EXPECT_TRUE(StartSecondCapture());
}

IN_PROC_BROWSER_TEST_P(SubCaptureMultiCaptureBrowserTest,
                       CannotSelfCaptureAgainIfAppliedSubCapture) {
  ManualSetUp();

  ASSERT_TRUE(ApplySubCaptureTarget(type_, kTarget1, Frame::kTopLevelDocument,
                                    Track::kOriginal));

  EXPECT_FALSE(StartSecondCapture());
}

IN_PROC_BROWSER_TEST_P(SubCaptureMultiCaptureBrowserTest,
                       CannotSelfCaptureAgainIfSubCapturedAppliedAndUnapplied) {
  ManualSetUp();

  ASSERT_TRUE(ApplySubCaptureTarget(type_, kTarget1, Frame::kTopLevelDocument,
                                    Track::kOriginal));
  ASSERT_TRUE(ApplySubCaptureTarget(
      type_, "undefined", Frame::kTopLevelDocument, Track::kOriginal));

  EXPECT_FALSE(StartSecondCapture());
}

IN_PROC_BROWSER_TEST_P(SubCaptureMultiCaptureBrowserTest,
                       CanSelfCaptureAgainIfSubCaptureSessionStopped) {
  ManualSetUp();

  ASSERT_TRUE(ApplySubCaptureTarget(type_, kTarget1, Frame::kTopLevelDocument,
                                    Track::kOriginal));
  ASSERT_TRUE(StopCapture(Track::kOriginal));

  EXPECT_TRUE(StartSecondCapture());
}

IN_PROC_BROWSER_TEST_P(
    SubCaptureMultiCaptureBrowserTest,
    CannotApplySubCaptureIfMultipleSelfCaptureSessionsExist) {
  ManualSetUp();

  ASSERT_TRUE(StartSecondCapture());

  EXPECT_FALSE(ApplySubCaptureTarget(type_, kTarget1, Frame::kTopLevelDocument,
                                     Track::kOriginal));
  EXPECT_FALSE(ApplySubCaptureTarget(type_, kTarget1, second_capture_frame_,
                                     Track::kSecond));
}

// Tests that sub-capture is:
// - Always allowed for self-capture (given evergreen conditions still apply,
//   such as that the target belongs to the captured tab).
// - Allowed for Region Capture if the relevant feature flag is enabled.
class SubCaptureSelfAndCrossCaptureBrowserTest
    : public SubCaptureBrowserTestBase,
      public WithParamInterface<
          std::tuple<SubCaptureTargetType, Frame, bool, bool, Tab, Frame>> {
 public:
  SubCaptureSelfAndCrossCaptureBrowserTest()
      : type_(std::get<0>(GetParam())),
        capturing_entity_(std::get<1>(GetParam())),
        self_capture_(std::get<2>(GetParam())),
        cross_tab_region_capture_allowed_(std::get<3>(GetParam())),
        target_element_tab_(std::get<4>(GetParam())),
        target_frame_(std::get<5>(GetParam())) {
    scoped_feature_list_.InitWithFeatureStates(
        {{features::kRegionCaptureOfOtherTabs,
          cross_tab_region_capture_allowed_}});
  }

  ~SubCaptureSelfAndCrossCaptureBrowserTest() override = default;

  bool CaptureTypeAllowed() const {
    const base::Feature* feature = nullptr;
    switch (type_) {
      case SubCaptureTargetType::kCropTarget:
        feature = &features::kRegionCaptureOfOtherTabs;
        break;
      case SubCaptureTargetType::kRestrictionTarget:
        feature = &features::kElementCaptureOfOtherTabs;
        break;
    }
    CHECK(feature);
    return self_capture_ || base::FeatureList::IsEnabled(*feature);
  }

  bool TargetBelongsToCapturedTab() const {
    return target_element_tab_ ==
           (self_capture_ ? Tab::kMainTab : Tab::kOtherTab);
  }

 protected:
  // Whether Region Capture or Element Capture is tested.
  const SubCaptureTargetType type_;

  // The capture is done from kMainTab in all instances of this parameterized
  // test. |capturing_entity_| controls whether the capture is initiated
  // from the top-level document of said tab, or an embedded frame.
  const Frame capturing_entity_;

  // Whether capturing self, or capturing the other tab.
  const bool self_capture_;

  // Associated with `kRegionCaptureOfOtherTabs`, which controls whether
  // sub-capture is only permitted to self-capture, or generally.
  const bool cross_tab_region_capture_allowed_;

  // Determines the the element on whose sub-capture-target we'll call cropTo()
  // or restrictTo():
  // * |target_element_tab_| - whether it's in kMainTab or in kOtherTab.
  // * |target_frame_| - whether it's in the top-level or an embedded frame.
  const Tab target_element_tab_;
  const Frame target_frame_;  // Top-level or embedded frame.

  base::test::ScopedFeatureList scoped_feature_list_;
};

std::string SubCaptureSelfAndCrossCaptureBrowserTestParamsToString(
    const TestParamInfo<SubCaptureSelfAndCrossCaptureBrowserTest::ParamType>&
        info) {
  return base::StrCat(
      {std::get<0>(info.param) == SubCaptureTargetType::kCropTarget
           ? "RegionCapture"
           : "ElementCapture",
       std::get<1>(info.param) == Frame::kTopLevelDocument ? "TopLevel"
                                                           : "EmbeddedFrame",
       std::get<2>(info.param) ? "SelfCapturing" : "CapturingOtherTab",
       std::get<3>(info.param) ? "CrossTabRegionCaptureAllowed"
                               : "CrossTabRegionCaptureDisallowed"
                                 "AndApplyingToElementIn",
       std::get<4>(info.param) == kMainTab ? "OwnTabs" : "OtherTabs",
       std::get<5>(info.param) == Frame::kTopLevelDocument ? "TopLevel"
                                                           : "EmbeddedFrame"});
}

INSTANTIATE_TEST_SUITE_P(
    ,
    SubCaptureSelfAndCrossCaptureBrowserTest,
    Combine(Values(SubCaptureTargetType::kCropTarget,
                   SubCaptureTargetType::kRestrictionTarget),
            Values(Frame::kTopLevelDocument, Frame::kEmbeddedFrame),
            Bool(),
            Bool(),
            Values(kMainTab, kOtherTab),
            Values(Frame::kTopLevelDocument, Frame::kEmbeddedFrame)),
    SubCaptureSelfAndCrossCaptureBrowserTestParamsToString);

IN_PROC_BROWSER_TEST_P(SubCaptureSelfAndCrossCaptureBrowserTest,
                       ApplySubCapture) {
  SetUpTest(capturing_entity_, self_capture_);

  // Prevent test false-positive - ensure that both tabs participating in the
  // test have at least one associated sub-capture-target, or otherwise they
  // would not have a SubCaptureTargetIdWebContentsHelper.
  // To make things even clearer, ensure both the top-level and the embedded
  // frame have produced sub-capture-targets. (This should not be necessary,
  // but is done as an extra buffer against false-positives.)
  ASSERT_THAT(tabs_[kMainTab].SubCaptureTargetFromElement(
                  type_, Frame::kTopLevelDocument),
              IsExpectedTarget("0"));
  ASSERT_THAT(
      tabs_[kMainTab].SubCaptureTargetFromElement(type_, Frame::kEmbeddedFrame),
      IsExpectedTarget("1"));
  ASSERT_THAT(tabs_[kOtherTab].SubCaptureTargetFromElement(
                  type_, Frame::kTopLevelDocument),
              IsExpectedTarget("2"));
  ASSERT_THAT(tabs_[kOtherTab].SubCaptureTargetFromElement(
                  type_, Frame::kEmbeddedFrame),
              IsExpectedTarget("3"));

  const std::string target =
      tabs_[target_element_tab_].SubCaptureTargetFromElement(type_,
                                                             target_frame_);
  ASSERT_THAT(target, IsExpectedTarget("4"));

  const bool expect_permitted =
      CaptureTypeAllowed() && TargetBelongsToCapturedTab();
  EXPECT_EQ(expect_permitted, tabs_[kMainTab].ApplySubCaptureTarget(
                                  target, type_, capturing_entity_));
}

class SubCaptureUnapplyBrowserTest
    : public SubCaptureBrowserTestBase,
      public WithParamInterface<std::tuple<bool, bool, SubCaptureTargetType>> {
 public:
  SubCaptureUnapplyBrowserTest()
      : self_capture_(std::get<0>(GetParam())),
        cross_tab_sub_capture_allowed_(std::get<1>(GetParam())),
        type_(std::get<2>(GetParam())) {
    scoped_feature_list_.InitWithFeatureStates(
        {{features::kRegionCaptureOfOtherTabs, cross_tab_sub_capture_allowed_},
         {features::kElementCaptureOfOtherTabs,
          cross_tab_sub_capture_allowed_}});
  }
  ~SubCaptureUnapplyBrowserTest() override = default;

 protected:
  const bool self_capture_;
  const bool cross_tab_sub_capture_allowed_;
  const SubCaptureTargetType type_;
};

std::string SubCaptureUnapplyBrowserTestParamsToString(
    const TestParamInfo<SubCaptureUnapplyBrowserTest::ParamType>& info) {
  const bool self_capture = std::get<0>(info.param);
  const bool cross_tab_sub_capture_allowed = std::get<1>(info.param);
  const SubCaptureTargetType type = std::get<2>(info.param);
  return base::StrCat(
      {"Capturing", self_capture ? "Self" : "Other", "CrossTabSubCapture",
       cross_tab_sub_capture_allowed ? "Allowed" : "Disallowed",
       type == SubCaptureTargetType::kCropTarget ? "Region" : "Element",
       "Capture"});
}

INSTANTIATE_TEST_SUITE_P(
    ,
    SubCaptureUnapplyBrowserTest,
    Combine(Bool(),
            Bool(),
            Values(SubCaptureTargetType::kCropTarget,
                   SubCaptureTargetType::kRestrictionTarget)),
    SubCaptureUnapplyBrowserTestParamsToString);

IN_PROC_BROWSER_TEST_P(SubCaptureUnapplyBrowserTest, ApplyToElement) {
  const Frame relevant_frame = Frame::kTopLevelDocument;
  SetUpTest(relevant_frame, self_capture_);
  TabInfo& tab = tabs_[kMainTab];
  EXPECT_TRUE(tab.ApplySubCaptureTarget("undefined", type_, relevant_frame));
}
