blob: 99df35f70d8bd9785a1d8d2c4ffdf65268a9bc0f [file] [log] [blame]
// 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 <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 "build/chromeos_buildflags.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 "testing/gmock/include/gmock/gmock-matchers.h"
#include "third_party/blink/public/common/features.h"
#include "ui/gl/gl_switches.h"
// TODO(crbug.com/1215089): Enable this test suite on Lacros.
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
namespace {
using content::WebContents;
using testing::Bool;
using testing::Combine;
using testing::TestParamInfo;
using testing::Values;
using testing::WithParamInterface;
// Comparisons with CropTargets are limited. We can only know that a one was
// returned by the test. However, we keep this matcher around in anticipation
// of CropTargets being made either comparable or stringifiable. Then we
// can do more interesting comparisons, like ensuring uniqueness or checking
// that repeatedly calling CropTarget.fromElement() on the same Element yields
// either the same CropTarget, or an equivalent one.
MATCHER_P(IsExpectedCropTarget, expected_crop_target_index, "") {
static_assert(std::is_same<decltype(arg), const std::string&>::value, "");
return arg == expected_crop_target_index;
}
const char kMainPageTitle[] = "Region Capture Test - Page 1 (Main)";
const char kOtherPageTitle[] = "Region Capture Test - Page 2 (Main)";
// Tracks CropIdWebContentsHelper::kMaxCropIdsPerWebContents.
constexpr size_t kMaxCropIdsPerWebContents = 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) {
return frame == Frame::kNone ? "none"
: frame == Frame::kTopLevelDocument ? "top-level"
: frame == Frame::kEmbeddedFrame ? "embedded"
: "error";
}
const char* ToString(Track track) {
return track == Track::kOriginal ? "original"
: track == Track::kClone ? "clone"
: track == Track::kSecond ? "second"
: "error";
}
// 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 CropTargetFromElement(Frame frame,
const std::string& element_id = "div") {
DCHECK_NE(frame, Frame::kNone);
return content::EvalJs(
web_contents->GetPrimaryMainFrame(),
base::StrCat({"cropTargetFromElement(\"", ToString(frame),
"\", \"" + element_id + "\");"}))
.ExtractString();
}
// Takes as input either the CropTarget[*], or "undefined" if the test
// wants track.cropTo(undefined) to be invoked.
// [*] Actually, because `CropTarget`s are not stringifiable, an index
// of the CropTarget is used, and translated by JS code back into
// the CropTarget it had stored.
bool CropTo(const std::string& crop_target,
Frame frame,
Track track = Track::kOriginal) {
std::string script_result =
content::EvalJs(web_contents->GetPrimaryMainFrame(),
base::StringPrintf("cropToByIndex('%s', '%s', '%s');",
crop_target.c_str(), ToString(frame),
ToString(track)))
.ExtractString();
if (frame == Frame::kTopLevelDocument) {
EXPECT_EQ(0u, script_result.rfind("top-level-", 0)) << script_result;
return script_result == "top-level-crop-success";
}
if (frame == Frame::kEmbeddedFrame) {
EXPECT_EQ(0u, script_result.rfind("embedded-", 0)) << script_result;
return script_result == "embedded-crop-success";
}
return false;
}
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, DanglingUntriaged> browser;
raw_ptr<WebContents, DanglingUntriaged> web_contents;
int tab_strip_index;
};
} // namespace
// Essentially depends on InProcessBrowserTest, but WebRtcTestBase provides
// detection of JS errors.
class RegionCaptureBrowserTest : public WebRtcTestBase {
public:
RegionCaptureBrowserTest() = default;
~RegionCaptureBrowserTest() 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);
// TODO(https://crbug.com/1424557): Remove this after fixing feature
// detection in 0c tab capture path as it'll no longer be needed.
if constexpr (!BUILDFLAG(IS_CHROMEOS)) {
command_line->AppendSwitch(switches::kUseGpuInTests);
}
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/region_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/region_capture_other_main.html",
servers_[kOtherPageTopLevelDocument].get(),
"/webrtc/region_capture_other_embedded.html",
servers_[kOtherPageEmbeddedDocument].get(), &tabs_[kOtherTab]);
// Main page (for self-capture). Instantiate it second to help it get focus.
SetUpPage("/webrtc/region_capture_main.html",
servers_[kMainPageTopLevelDocument].get(),
"/webrtc/region_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.
TabInfo tabs_[kTabCount];
// Each page is served from a distinct origin, thereby proving that cropping
// works irrespective of whether iframes are in/out-of-process.
std::vector<std::unique_ptr<net::EmbeddedTestServer>> servers_;
base::test::ScopedFeatureList scoped_feature_list_;
};
class RegionCaptureBrowserCropTest
: public RegionCaptureBrowserTest,
public testing::WithParamInterface<const char*> {
public:
~RegionCaptureBrowserCropTest() override = default;
void TestCanCropToElementTag(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 crop_target =
tab.CropTargetFromElement(Frame::kTopLevelDocument, element_id);
ASSERT_THAT(crop_target, IsExpectedCropTarget("0"));
EXPECT_TRUE(tab.CropTo(crop_target, Frame::kTopLevelDocument));
}
};
INSTANTIATE_TEST_SUITE_P(RegionCaptureBrowserCropTestInstantiation,
RegionCaptureBrowserCropTest,
Values("a",
"blockquote",
"body",
"button",
"canvas",
"col",
"div",
"fieldset",
"form",
"h1",
"header",
"hr"
"iframe",
"img",
"input",
"output",
"span",
"svg",
"video"));
IN_PROC_BROWSER_TEST_P(RegionCaptureBrowserCropTest, CanCropTo) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TestCanCropToElementTag(GetParam());
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropTargetFromElementReturnsValidIdInMainPage) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
EXPECT_THAT(tabs_[kMainTab].CropTargetFromElement(Frame::kTopLevelDocument),
IsExpectedCropTarget("0"));
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropTargetFromElementReturnsValidIdInCrossOriginIframe) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
EXPECT_THAT(tabs_[kMainTab].CropTargetFromElement(Frame::kEmbeddedFrame),
IsExpectedCropTarget("0"));
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToAllowedIfTopLevelCropsToElementInTopLevel) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_target =
tab.CropTargetFromElement(Frame::kTopLevelDocument);
ASSERT_THAT(crop_target, IsExpectedCropTarget("0"));
EXPECT_TRUE(tab.CropTo(crop_target, Frame::kTopLevelDocument));
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToAllowedIfTopLevelCropsToElementInEmbedded) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_target =
tab.CropTargetFromElement(Frame::kEmbeddedFrame);
ASSERT_THAT(crop_target, IsExpectedCropTarget("0"));
EXPECT_TRUE(tab.CropTo(crop_target, Frame::kTopLevelDocument));
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToAllowedIfEmbeddedFrameCropsToElementInTopLevel) {
SetUpTest(Frame::kEmbeddedFrame, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_target =
tab.CropTargetFromElement(Frame::kTopLevelDocument);
ASSERT_THAT(crop_target, IsExpectedCropTarget("0"));
EXPECT_TRUE(tab.CropTo(crop_target, Frame::kEmbeddedFrame));
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToAllowedIfEmbeddedFrameCropsToElementInEmbedded) {
SetUpTest(Frame::kEmbeddedFrame, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_target =
tab.CropTargetFromElement(Frame::kEmbeddedFrame);
ASSERT_THAT(crop_target, IsExpectedCropTarget("0"));
EXPECT_TRUE(tab.CropTo(crop_target, Frame::kEmbeddedFrame));
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, CropToAllowedToUncrop) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_target =
tab.CropTargetFromElement(Frame::kTopLevelDocument);
ASSERT_THAT(crop_target, IsExpectedCropTarget("0"));
ASSERT_TRUE(tab.CropTo(crop_target, Frame::kTopLevelDocument));
EXPECT_TRUE(tab.CropTo("undefined", Frame::kTopLevelDocument));
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToForUncroppingAllowedOnUncroppedTracks) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_target =
tab.CropTargetFromElement(Frame::kTopLevelDocument);
ASSERT_THAT(crop_target, IsExpectedCropTarget("0"));
// CropTo(cropTarget) is intentionally not called.
// Instead, the test immediately calls CropTo(undefined) on a still-uncropped
// track, attempting to stop cropping when no cropping was ever specified.
EXPECT_TRUE(tab.CropTo("undefined", Frame::kTopLevelDocument));
}
// The Promise resolves when it's guaranteed that no additional frames will
// be issued with an earlier crop version. That an actual frame be issued
// at all, let alone with the new crop version, is not actually required,
// or else these promises could languish unfulfilled indefinitely.
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToOfInvisibleElementResolvesInTimelyFashion) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
ASSERT_TRUE(tab.HideElement("div"));
const std::string crop_target =
tab.CropTargetFromElement(Frame::kTopLevelDocument, "div");
ASSERT_THAT(crop_target, IsExpectedCropTarget("0"));
EXPECT_TRUE(tab.CropTo(crop_target, Frame::kTopLevelDocument));
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, MaxCropIdsInTopLevelDocument) {
SetUpTest(Frame::kNone, /*self_capture=*/false);
TabInfo& tab = tabs_[kMainTab];
for (size_t i = 0; i < kMaxCropIdsPerWebContents; ++i) {
const std::string element_id = ("new_id_" + base::NumberToString(i));
ASSERT_TRUE(tab.CreateNewDivElement(Frame::kTopLevelDocument, element_id));
const std::string crop_target =
tab.CropTargetFromElement(Frame::kTopLevelDocument, element_id);
ASSERT_THAT(crop_target, IsExpectedCropTarget(base::NumberToString(i)));
}
// Create one more element - this one won't get a crop-target.
const std::string element_id =
("new_id_" + base::NumberToString(kMaxCropIdsPerWebContents));
ASSERT_TRUE(tab.CreateNewDivElement(Frame::kTopLevelDocument, element_id));
EXPECT_EQ(tab.CropTargetFromElement(Frame::kTopLevelDocument, element_id),
"top-level-produce-crop-target-error");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, MaxCropIdsInEmbeddedFrame) {
SetUpTest(Frame::kNone, /*self_capture=*/false);
TabInfo& tab = tabs_[kMainTab];
for (size_t i = 0; i < kMaxCropIdsPerWebContents; ++i) {
const std::string element_id = ("new_id_" + base::NumberToString(i));
ASSERT_TRUE(tab.CreateNewDivElement(Frame::kEmbeddedFrame, element_id));
const std::string crop_target =
tab.CropTargetFromElement(Frame::kEmbeddedFrame, element_id);
ASSERT_THAT(crop_target, IsExpectedCropTarget(base::NumberToString(i)));
}
// Create one more element - this one won't get a crop-target.
const std::string element_id =
("new_id_" + base::NumberToString(kMaxCropIdsPerWebContents));
ASSERT_TRUE(tab.CreateNewDivElement(Frame::kEmbeddedFrame, element_id));
EXPECT_EQ(tab.CropTargetFromElement(Frame::kEmbeddedFrame, element_id),
"embedded-produce-crop-target-error");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
MaxCropIdsSharedBetweenFramesInTab) {
SetUpTest(Frame::kNone, /*self_capture=*/false);
TabInfo& tab = tabs_[kMainTab];
static_assert(kMaxCropIdsPerWebContents > 1, "");
// Create (kMaxCropIdsPerWebContents - 1) new elements and assign each a
// crop-target.
for (size_t i = 0; i < kMaxCropIdsPerWebContents - 1; ++i) {
const std::string element_id = ("new_id_" + base::NumberToString(i));
ASSERT_TRUE(tab.CreateNewDivElement(Frame::kTopLevelDocument, element_id));
const std::string crop_target =
tab.CropTargetFromElement(Frame::kTopLevelDocument, element_id);
ASSERT_THAT(crop_target, IsExpectedCropTarget(base::NumberToString(i)));
}
// One more in the embedded frame is possible.
std::string element_id =
("new_id_" + base::NumberToString(kMaxCropIdsPerWebContents - 1));
ASSERT_TRUE(tab.CreateNewDivElement(Frame::kEmbeddedFrame, element_id));
const std::string crop_target =
tab.CropTargetFromElement(Frame::kEmbeddedFrame, element_id);
EXPECT_THAT(crop_target, IsExpectedCropTarget(base::NumberToString(
kMaxCropIdsPerWebContents - 1)));
// Create one more element - this one won't get a crop-target.
element_id = ("new_id_" + base::NumberToString(kMaxCropIdsPerWebContents));
ASSERT_TRUE(tab.CreateNewDivElement(Frame::kTopLevelDocument, element_id));
EXPECT_EQ(tab.CropTargetFromElement(Frame::kTopLevelDocument, element_id),
"top-level-produce-crop-target-error");
// Neither in the top-level nor in the embedded frame.
element_id =
("new_id_" + base::NumberToString(kMaxCropIdsPerWebContents + 1));
ASSERT_TRUE(tab.CreateNewDivElement(Frame::kEmbeddedFrame, element_id));
EXPECT_EQ(tab.CropTargetFromElement(Frame::kEmbeddedFrame, element_id),
"embedded-produce-crop-target-error");
}
// Tests related to behavior when cloning.
class RegionCaptureClonesBrowserTest : public RegionCaptureBrowserTest {
public:
static const std::string kCropTarget0;
static const std::string kCropTarget1;
~RegionCaptureClonesBrowserTest() override = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
command_line->AppendSwitchASCII("js-flags", "--expose-gc");
RegionCaptureBrowserTest::SetUpCommandLine(command_line);
}
// Has to be called from within the test's body.
void ManualSetUp() {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
EXPECT_THAT(
tabs_[kMainTab].CropTargetFromElement(Frame::kTopLevelDocument, "div"),
IsExpectedCropTarget(kCropTarget0));
EXPECT_THAT(tabs_[kMainTab].CropTargetFromElement(Frame::kTopLevelDocument,
"embedded_frame"),
IsExpectedCropTarget(kCropTarget1));
}
bool CropTo(const std::string& crop_target, Frame frame, Track track) {
return tabs_[kMainTab].CropTo(crop_target, frame, track);
}
bool CloneTrack() { return tabs_[kMainTab].CloneTrack(); }
bool Deallocate(Track track) { return tabs_[kMainTab].Deallocate(track); }
};
const std::string RegionCaptureClonesBrowserTest::kCropTarget0 = "0";
const std::string RegionCaptureClonesBrowserTest::kCropTarget1 = "1";
// Sanity cloning 1/2.
IN_PROC_BROWSER_TEST_F(RegionCaptureClonesBrowserTest,
CanCloneUncroppedTracks) {
ManualSetUp();
EXPECT_TRUE(CloneTrack());
}
// Sanity cloning 2/2.
IN_PROC_BROWSER_TEST_F(RegionCaptureClonesBrowserTest, CanCloneCroppedTracks) {
ManualSetUp();
ASSERT_TRUE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kOriginal));
EXPECT_TRUE(CloneTrack());
}
// Restrictions on cloned tracked 1/3.
IN_PROC_BROWSER_TEST_F(RegionCaptureClonesBrowserTest, CannotCropClone) {
ManualSetUp();
ASSERT_TRUE(CloneTrack());
EXPECT_FALSE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kClone));
}
// Restrictions on cloned tracked 2/3.
IN_PROC_BROWSER_TEST_F(RegionCaptureClonesBrowserTest, CannotRecropClone) {
ManualSetUp();
ASSERT_TRUE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(CloneTrack());
EXPECT_FALSE(CropTo(kCropTarget1, Frame::kTopLevelDocument, Track::kClone));
}
// Restrictions on cloned tracked 3/3.
IN_PROC_BROWSER_TEST_F(RegionCaptureClonesBrowserTest, CannotUncropClone) {
ManualSetUp();
ASSERT_TRUE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(CloneTrack());
EXPECT_FALSE(CropTo("undefined", Frame::kTopLevelDocument, Track::kClone));
}
// Restrictions on original track that has a clone 1/3.
IN_PROC_BROWSER_TEST_F(RegionCaptureClonesBrowserTest,
CannotCropTrackThatHasClone) {
ManualSetUp();
ASSERT_TRUE(CloneTrack());
EXPECT_FALSE(
CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kOriginal));
}
// Restrictions on original track that has a clone 2/3.
IN_PROC_BROWSER_TEST_F(RegionCaptureClonesBrowserTest,
CannotRecropTrackThatHasClone) {
ManualSetUp();
ASSERT_TRUE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(CloneTrack());
EXPECT_FALSE(
CropTo(kCropTarget1, Frame::kTopLevelDocument, Track::kOriginal));
}
// Restrictions on original track that has a clone 3/3.
IN_PROC_BROWSER_TEST_F(RegionCaptureClonesBrowserTest,
CannotUncropTrackThatHasClone) {
ManualSetUp();
ASSERT_TRUE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(CloneTrack());
EXPECT_FALSE(CropTo("undefined", Frame::kTopLevelDocument, Track::kOriginal));
}
// Original track becomes unblocked for cropping after clone is GCed 1/3.
// TODO(crbug.com/1353349) Re-enable for macOS after flakes are resolved.
#if BUILDFLAG(IS_MAC)
#define MAYBE_CanCropOriginalTrackAfterCloneIsGarbageCollected \
DISABLED_CanCropOriginalTrackAfterCloneIsGarbageCollected
#else
#define MAYBE_CanCropOriginalTrackAfterCloneIsGarbageCollected \
CanCropOriginalTrackAfterCloneIsGarbageCollected
#endif
IN_PROC_BROWSER_TEST_F(RegionCaptureClonesBrowserTest,
MAYBE_CanCropOriginalTrackAfterCloneIsGarbageCollected) {
ManualSetUp();
ASSERT_TRUE(CloneTrack());
ASSERT_FALSE(
CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(Deallocate(Track::kClone));
EXPECT_TRUE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kOriginal));
}
// Original track becomes unblocked for cropping after clone is GCed 2/3.
// TODO(crbug.com/1353349) Re-enable after flakes are resolved.
IN_PROC_BROWSER_TEST_F(
RegionCaptureClonesBrowserTest,
DISABLED_CanRecropOriginalTrackAfterCloneIsGarbageCollected) {
ManualSetUp();
ASSERT_TRUE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(CloneTrack());
ASSERT_FALSE(
CropTo(kCropTarget1, Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(Deallocate(Track::kClone));
EXPECT_TRUE(CropTo(kCropTarget1, Frame::kTopLevelDocument, Track::kOriginal));
}
// Original track becomes unblocked for cropping after clone is GCed 3/3.
IN_PROC_BROWSER_TEST_F(
RegionCaptureClonesBrowserTest,
// TODO(crbug.com/1356788): Re-enable this test
DISABLED_CanUncropOriginalTrackAfterCloneIsGarbageCollected) {
ManualSetUp();
ASSERT_TRUE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(CloneTrack());
ASSERT_FALSE(CropTo("undefined", Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(Deallocate(Track::kClone));
EXPECT_TRUE(CropTo("undefined", Frame::kTopLevelDocument, Track::kOriginal));
}
// The following tests are 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/1333594): Uncomment after fixing the aforementioned issue.
#if 0
// Cloned track becomes unblocked for cropping after original is GCed 1/3.
IN_PROC_BROWSER_TEST_F(RegionCaptureClonesBrowserTest,
CanCropCloneAfterOriginalTrackIsGarbageCollected) {
ManualSetUp();
ASSERT_TRUE(CloneTrack());
ASSERT_FALSE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kClone));
ASSERT_TRUE(Deallocate(Track::kOriginal));
EXPECT_TRUE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kClone));
}
// Cloned track becomes unblocked for cropping after original is GCed 2/3.
IN_PROC_BROWSER_TEST_F(RegionCaptureClonesBrowserTest,
CanRecropCloneAfterOriginalTrackIsGarbageCollected) {
ManualSetUp();
ASSERT_TRUE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(CloneTrack());
ASSERT_FALSE(CropTo(kCropTarget1, Frame::kTopLevelDocument, Track::kClone));
ASSERT_TRUE(Deallocate(Track::kOriginal));
EXPECT_TRUE(CropTo(kCropTarget1, Frame::kTopLevelDocument, Track::kClone));
}
// Cloned track becomes unblocked for cropping after original is GCed 3/3.
IN_PROC_BROWSER_TEST_F(RegionCaptureClonesBrowserTest,
CanUncropCloneAfterOriginalTrackIsGarbageCollected) {
ManualSetUp();
ASSERT_TRUE(CropTo(kCropTarget0, Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(CloneTrack());
ASSERT_FALSE(CropTo("undefined", Frame::kTopLevelDocument, Track::kClone));
ASSERT_TRUE(Deallocate(Track::kOriginal));
EXPECT_TRUE(CropTo("undefined", Frame::kTopLevelDocument, Track::kClone));
}
#endif
// Tests similar issues to cloning, but with multiple captures triggering
// these issues instead.
class RegionCaptureMultiCaptureBrowserTest
: public RegionCaptureClonesBrowserTest,
public WithParamInterface<Frame> {
public:
RegionCaptureMultiCaptureBrowserTest() : second_capture_frame_(GetParam()) {}
~RegionCaptureMultiCaptureBrowserTest() override = default;
// Has to be called from within the test's body.
void ManualSetUp() {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
EXPECT_THAT(
tabs_[kMainTab].CropTargetFromElement(Frame::kTopLevelDocument, "div"),
IsExpectedCropTarget(kCropTarget0));
EXPECT_THAT(tabs_[kMainTab].CropTargetFromElement(Frame::kTopLevelDocument,
"embedded_frame"),
IsExpectedCropTarget(kCropTarget1));
}
bool StartSecondCapture() {
return tabs_[kMainTab].StartSecondCapture(second_capture_frame_);
}
bool StopCapture(Track track) {
return tabs_[kMainTab].StopCapture(Frame::kTopLevelDocument, track);
}
const Frame second_capture_frame_;
};
INSTANTIATE_TEST_SUITE_P(_,
RegionCaptureMultiCaptureBrowserTest,
Values(Frame::kTopLevelDocument,
Frame::kEmbeddedFrame));
IN_PROC_BROWSER_TEST_P(RegionCaptureMultiCaptureBrowserTest,
CanSelfCaptureAgainIfNeverCropped) {
ManualSetUp();
EXPECT_TRUE(StartSecondCapture());
}
IN_PROC_BROWSER_TEST_P(RegionCaptureMultiCaptureBrowserTest,
CannotSelfCaptureAgainIfCropped) {
ManualSetUp();
ASSERT_TRUE(CropTo(kCropTarget1, Frame::kTopLevelDocument, Track::kOriginal));
EXPECT_FALSE(StartSecondCapture());
}
IN_PROC_BROWSER_TEST_P(RegionCaptureMultiCaptureBrowserTest,
CannotSelfCaptureAgainIfCroppedAndUncropped) {
ManualSetUp();
ASSERT_TRUE(CropTo(kCropTarget1, Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(CropTo("undefined", Frame::kTopLevelDocument, Track::kOriginal));
EXPECT_FALSE(StartSecondCapture());
}
IN_PROC_BROWSER_TEST_P(RegionCaptureMultiCaptureBrowserTest,
CanSelfCaptureAgainIfCroppedSessionStopped) {
ManualSetUp();
ASSERT_TRUE(CropTo(kCropTarget1, Frame::kTopLevelDocument, Track::kOriginal));
ASSERT_TRUE(StopCapture(Track::kOriginal));
EXPECT_TRUE(StartSecondCapture());
}
IN_PROC_BROWSER_TEST_P(RegionCaptureMultiCaptureBrowserTest,
CannotCropIfMultipleSelfCaptureSessionsExist) {
ManualSetUp();
ASSERT_TRUE(StartSecondCapture());
EXPECT_FALSE(
CropTo(kCropTarget1, Frame::kTopLevelDocument, Track::kOriginal));
EXPECT_FALSE(CropTo(kCropTarget1, second_capture_frame_, Track::kSecond));
}
// Suite of tests ensuring that only self-capture may crop, and that it may
// only crop to targets in its own tab, but that any target in its own tab
// is permitted.
class RegionCaptureSelfCaptureOnlyBrowserTest
: public RegionCaptureBrowserTest,
public WithParamInterface<std::tuple<Frame, bool, Tab, Frame>> {
public:
RegionCaptureSelfCaptureOnlyBrowserTest()
: capturing_entity_(std::get<0>(GetParam())),
self_capture_(std::get<1>(GetParam())),
target_element_tab_(std::get<2>(GetParam())),
target_frame_(std::get<3>(GetParam())) {}
~RegionCaptureSelfCaptureOnlyBrowserTest() override = default;
protected:
// 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_;
// Whether the element on whose crop-target we'll call cropTo():
// * |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.
};
std::string ParamsToString(
const TestParamInfo<RegionCaptureSelfCaptureOnlyBrowserTest::ParamType>&
info) {
return base::StrCat(
{std::get<0>(info.param) == Frame::kTopLevelDocument ? "TopLevel"
: "EmbeddedFrame",
std::get<1>(info.param) ? "SelfCapturing" : "CapturingOtherTab",
"AndCroppingToElementIn",
std::get<2>(info.param) == kMainTab ? "OwnTabs" : "OtherTabs",
std::get<3>(info.param) == Frame::kTopLevelDocument ? "TopLevel"
: "EmbeddedFrame"});
}
INSTANTIATE_TEST_SUITE_P(
_,
RegionCaptureSelfCaptureOnlyBrowserTest,
Combine(Values(Frame::kTopLevelDocument, Frame::kEmbeddedFrame),
Bool(),
Values(kMainTab, kOtherTab),
Values(Frame::kTopLevelDocument, Frame::kEmbeddedFrame)),
ParamsToString);
IN_PROC_BROWSER_TEST_P(RegionCaptureSelfCaptureOnlyBrowserTest, CropTo) {
SetUpTest(capturing_entity_, self_capture_);
// Prevent test false-positive - ensure that both tabs participating in the
// test have at least one associated crop-target, or otherwise they would not
// have a CropIdWebContentsHelper.
// To make things even clearer, ensure both the top-level and the embedded
// frame have produced crop-targets. (This should not be necessary, but is
// done as an extra buffer against false-positives.)
ASSERT_THAT(tabs_[kMainTab].CropTargetFromElement(Frame::kTopLevelDocument),
IsExpectedCropTarget("0"));
ASSERT_THAT(tabs_[kMainTab].CropTargetFromElement(Frame::kEmbeddedFrame),
IsExpectedCropTarget("1"));
ASSERT_THAT(tabs_[kOtherTab].CropTargetFromElement(Frame::kTopLevelDocument),
IsExpectedCropTarget("2"));
ASSERT_THAT(tabs_[kOtherTab].CropTargetFromElement(Frame::kEmbeddedFrame),
IsExpectedCropTarget("3"));
const std::string crop_target =
tabs_[target_element_tab_].CropTargetFromElement(target_frame_);
ASSERT_THAT(crop_target, IsExpectedCropTarget("4"));
// Cropping only permitted if both conditions hold.
const bool expect_permitted =
(self_capture_ && target_element_tab_ == kMainTab);
EXPECT_EQ(expect_permitted,
tabs_[kMainTab].CropTo(crop_target, capturing_entity_));
}
#endif // !BUILDFLAG(IS_CHROMEOS_LACROS)