blob: a7c8804b221b7ac1a78b1ecce3671cffc943a63b [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <memory>
#include <set>
#include <string>
#include <vector>
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/guid.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"
// TODO(crbug.com/1215089): Enable this test suite on Lacros.
#if !BUILDFLAG(IS_CHROMEOS_LACROS)
namespace {
using content::WebContents;
// TODO(crbug.com/1247761): Add tests that verify excessive calls to
// produceCropId() yield the empty string.
MATCHER(IsEmptyCropId, "") {
static_assert(std::is_same<decltype(arg), const std::string&>::value, "");
return arg == "empty-crop-id"; // See region_capture_main.html for rationale.
}
MATCHER(IsValidCropId, "") {
static_assert(std::is_same<decltype(arg), const std::string&>::value, "");
return base::GUID::ParseLowercase(arg).is_valid();
}
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,
kServerCount // Must be last.
};
enum {
kMainTab,
kOtherTab,
kTabCount // Must be last.
};
enum class Frame {
kNone,
kTopLevelDocument,
kEmbeddedFrame,
};
// Conveniently pack together all relevant information about a tab and
// conveniently expose test controls on it.
struct TabInfo {
void StartEmbeddingFrame(const GURL& url) {
std::string script_result;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents->GetMainFrame(),
base::StringPrintf("startEmbeddingFrame('%s');", url.spec().c_str()),
&script_result));
EXPECT_EQ(script_result, "embedding-done");
}
void StartCapture() {
// Bring the tab into focus. This avoids getDisplayMedia rejection.
browser->tab_strip_model()->ActivateTabAt(tab_strip_index);
std::string script_result;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents->GetMainFrame(), "startCapture();", &script_result));
EXPECT_EQ(script_result, "capture-success");
}
void StartCaptureFromEmbeddedFrame() {
std::string script_result;
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents->GetMainFrame(), "startCaptureFromEmbeddedFrame();",
&script_result));
EXPECT_EQ(script_result, "embedded-capture-success");
}
std::string ProduceCropId(Frame frame,
const std::string& element_id = "div") {
DCHECK_NE(frame, Frame::kNone);
const std::string frame_js =
(frame == Frame::kTopLevelDocument) ? "top" : "embedded";
std::string script_result = "error-not-modified";
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents->GetMainFrame(),
base::StrCat(
{"produceCropId(\"", frame_js, "\", \"" + element_id + "\");"}),
&script_result));
return script_result;
}
std::string CropTo(const std::string& crop_id) {
std::string script_result = "error-not-modified";
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents->GetMainFrame(),
base::StrCat({"cropTo(\"", crop_id, "\");"}), &script_result));
return script_result;
}
std::string CreateNewDivElement(Frame frame, const std::string& div_id) {
DCHECK_NE(frame, Frame::kNone);
const std::string frame_js =
(frame == Frame::kTopLevelDocument) ? "top" : "embedded";
std::string script_result = "error-not-modified";
EXPECT_TRUE(content::ExecuteScriptAndExtractString(
web_contents->GetMainFrame(),
base::StrCat(
{"createNewDivElement(\"", frame_js, "\", \"", div_id, "\");"}),
&script_result));
return script_result;
}
Browser* browser;
WebContents* web_contents;
int tab_strip_index;
};
} // namespace
// Essentially depends on InProcessBrowserTest, but WebRtcTestBase provides
// detection of JS errors.
class RegionCaptureBrowserTest : public WebRtcTestBase {
public:
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);
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();
// web_contents_vector_.push_back(web_contents);
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));
}
// Set up all (necessary) tabs, loads iframes, and start capturing the
// relevant tab.
void SetUpTest(Frame capturing_entity, bool self_capture) {
// Main page (for self-capture).
SetUpPage("/webrtc/region_capture_main.html",
servers_[kMainPageTopLevelDocument].get(),
"/webrtc/region_capture_embedded.html",
servers_[kMainPageEmbeddedDocument].get(), &tabs_[kMainTab]);
if (!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]);
}
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.
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_;
};
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
ProduceCropIdReturnsValidIdInMainPage) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
EXPECT_THAT(tabs_[kMainTab].ProduceCropId(Frame::kTopLevelDocument),
IsValidCropId());
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
ProduceCropIdReturnsValidIdInCrossOriginIframe) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
EXPECT_THAT(tabs_[kMainTab].ProduceCropId(Frame::kEmbeddedFrame),
IsValidCropId());
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
ProduceCropIdReturnsSameIdIfSameElement) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
EXPECT_EQ(tabs_[kMainTab].ProduceCropId(Frame::kTopLevelDocument),
tabs_[kMainTab].ProduceCropId(Frame::kTopLevelDocument));
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToAllowedIfTopLevelCropsToElementInTopLevel) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_id = tab.ProduceCropId(Frame::kTopLevelDocument);
ASSERT_THAT(crop_id, IsValidCropId());
EXPECT_EQ(tab.CropTo(crop_id), "top-level-crop-success");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToAllowedIfTopLevelCropsToElementInEmbedded) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_id = tab.ProduceCropId(Frame::kEmbeddedFrame);
ASSERT_THAT(crop_id, IsValidCropId());
EXPECT_EQ(tab.CropTo(crop_id), "top-level-crop-success");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToAllowedIfEmbeddedFrameCropsToElementInTopLevel) {
SetUpTest(Frame::kEmbeddedFrame, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_id = tab.ProduceCropId(Frame::kTopLevelDocument);
ASSERT_THAT(crop_id, IsValidCropId());
EXPECT_EQ(tab.CropTo(crop_id), "embedded-crop-success");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToAllowedIfEmbeddedFrameCropsToElementInEmbedded) {
SetUpTest(Frame::kEmbeddedFrame, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_id = tab.ProduceCropId(Frame::kEmbeddedFrame);
ASSERT_THAT(crop_id, IsValidCropId());
EXPECT_EQ(tab.CropTo(crop_id), "embedded-crop-success");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, CropToAllowedToUncrop) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_id = tab.ProduceCropId(Frame::kTopLevelDocument);
ASSERT_THAT(crop_id, IsValidCropId());
ASSERT_EQ(tab.CropTo(crop_id), "top-level-crop-success");
EXPECT_EQ(tab.CropTo(""), "top-level-crop-success");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, CropToRejectedIfUnknown) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_id = tab.ProduceCropId(Frame::kTopLevelDocument);
ASSERT_THAT(crop_id, IsValidCropId());
DCHECK(!crop_id.empty()); // Test sanity.
const std::string::value_type wrong_char = (crop_id[0] == 'a' ? 'b' : 'a');
const std::string wrong_crop_id = wrong_char + crop_id.substr(1);
EXPECT_EQ(tab.CropTo(wrong_crop_id), "top-level-crop-error");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, CropToRejectedIfInvalid) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_id = tab.ProduceCropId(Frame::kTopLevelDocument);
ASSERT_THAT(crop_id, IsValidCropId());
EXPECT_EQ(tab.CropTo("invalid-crop-id"), "top-level-crop-error");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToRejectedIfProduceCropIdWasNeverCalled) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_id = tab.ProduceCropId(Frame::kTopLevelDocument);
ASSERT_THAT(crop_id, IsValidCropId());
const std::string kValidCropId = "01234567-0123-0123-0123-0123456789ab";
ASSERT_THAT(kValidCropId, IsValidCropId()); // Test is sane.
EXPECT_EQ(tab.CropTo(kValidCropId), "top-level-crop-error");
}
IN_PROC_BROWSER_TEST_F(
RegionCaptureBrowserTest,
CropToForUncroppingRejectedIfProduceCropIdWasCalledButTrackUncropped) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/true);
TabInfo& tab = tabs_[kMainTab];
const std::string crop_id = tab.ProduceCropId(Frame::kTopLevelDocument);
ASSERT_THAT(crop_id, IsValidCropId());
// CropTo(crop_id) with a non-empty |crop_id| is intentionally not called.
// Instead, the test immediately calls CropTo("") on a still-uncropped track,
// attempting to stop cropping when no cropping was ever specified.
EXPECT_EQ(tab.CropTo(""), "top-level-crop-success");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToRejectedIfElementInAnotherTabTopLevel) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/false);
const std::string crop_id =
tabs_[kOtherTab].ProduceCropId(Frame::kTopLevelDocument);
ASSERT_THAT(crop_id, IsValidCropId());
EXPECT_EQ(tabs_[kMainTab].CropTo(crop_id), "top-level-crop-error");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest,
CropToRejectedIfElementInAnotherTabEmbeddedFrame) {
SetUpTest(Frame::kTopLevelDocument, /*self_capture=*/false);
const std::string crop_id =
tabs_[kOtherTab].ProduceCropId(Frame::kEmbeddedFrame);
ASSERT_THAT(crop_id, IsValidCropId());
EXPECT_EQ(tabs_[kMainTab].CropTo(crop_id), "top-level-crop-error");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, MaxCropIdsInTopLevelDocument) {
SetUpTest(Frame::kNone, /*self_capture=*/false);
TabInfo& tab = tabs_[kMainTab];
// Create kMaxCropIdsPerWebContents new elements and assign each a crop-ID.
std::set<std::string> crop_ids;
for (size_t i = 0; i < kMaxCropIdsPerWebContents; ++i) {
const std::string element_id = ("new_id_" + base::NumberToString(i));
ASSERT_EQ(tab.CreateNewDivElement(Frame::kTopLevelDocument, element_id),
"top-level-new-div-success");
const std::string crop_id =
tab.ProduceCropId(Frame::kTopLevelDocument, element_id);
ASSERT_THAT(crop_id, IsValidCropId());
crop_ids.insert(crop_id);
}
EXPECT_EQ(crop_ids.size(), kMaxCropIdsPerWebContents);
// Create one more element - this one won't get a crop-ID.
const std::string element_id =
("new_id_" + base::NumberToString(kMaxCropIdsPerWebContents));
ASSERT_EQ(tab.CreateNewDivElement(Frame::kTopLevelDocument, element_id),
"top-level-new-div-success");
EXPECT_EQ(tab.ProduceCropId(Frame::kTopLevelDocument, element_id),
"top-level-produce-crop-id-error");
}
IN_PROC_BROWSER_TEST_F(RegionCaptureBrowserTest, MaxCropIdsInEmbeddedFrame) {
SetUpTest(Frame::kNone, /*self_capture=*/false);
TabInfo& tab = tabs_[kMainTab];
// Create kMaxCropIdsPerWebContents new elements and assign each a crop-ID.
std::set<std::string> crop_ids;
for (size_t i = 0; i < kMaxCropIdsPerWebContents; ++i) {
const std::string element_id = ("new_id_" + base::NumberToString(i));
ASSERT_EQ(tab.CreateNewDivElement(Frame::kEmbeddedFrame, element_id),
"embedded-new-div-success");
const std::string crop_id =
tab.ProduceCropId(Frame::kEmbeddedFrame, element_id);
ASSERT_THAT(crop_id, IsValidCropId());
crop_ids.insert(crop_id);
}
EXPECT_EQ(crop_ids.size(), kMaxCropIdsPerWebContents);
// Create one more element - this one won't get a crop-ID.
const std::string element_id =
("new_id_" + base::NumberToString(kMaxCropIdsPerWebContents));
ASSERT_EQ(tab.CreateNewDivElement(Frame::kEmbeddedFrame, element_id),
"embedded-new-div-success");
EXPECT_EQ(tab.ProduceCropId(Frame::kEmbeddedFrame, element_id),
"embedded-produce-crop-id-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-ID.
std::set<std::string> crop_ids;
for (size_t i = 0; i < kMaxCropIdsPerWebContents - 1; ++i) {
const std::string element_id = ("new_id_" + base::NumberToString(i));
ASSERT_EQ(tab.CreateNewDivElement(Frame::kTopLevelDocument, element_id),
"top-level-new-div-success");
const std::string crop_id =
tab.ProduceCropId(Frame::kTopLevelDocument, element_id);
ASSERT_THAT(crop_id, IsValidCropId());
crop_ids.insert(crop_id);
}
EXPECT_EQ(crop_ids.size(), kMaxCropIdsPerWebContents - 1);
// One more in the embedded frame is possible.
std::string element_id =
("new_id_" + base::NumberToString(kMaxCropIdsPerWebContents - 1));
ASSERT_EQ(tab.CreateNewDivElement(Frame::kEmbeddedFrame, element_id),
"embedded-new-div-success");
std::string crop_id = tab.ProduceCropId(Frame::kEmbeddedFrame, element_id);
EXPECT_THAT(crop_id, IsValidCropId());
EXPECT_TRUE(crop_ids.find(crop_id) == crop_ids.end());
// Create one more element - this one won't get a crop-ID.
element_id = ("new_id_" + base::NumberToString(kMaxCropIdsPerWebContents));
ASSERT_EQ(tab.CreateNewDivElement(Frame::kTopLevelDocument, element_id),
"top-level-new-div-success");
EXPECT_EQ(tab.ProduceCropId(Frame::kTopLevelDocument, element_id),
"top-level-produce-crop-id-error");
// Neither in the top-level nor in the embedded frame.
element_id =
("new_id_" + base::NumberToString(kMaxCropIdsPerWebContents + 1));
ASSERT_EQ(tab.CreateNewDivElement(Frame::kEmbeddedFrame, element_id),
"embedded-new-div-success");
EXPECT_EQ(tab.ProduceCropId(Frame::kEmbeddedFrame, element_id),
"embedded-produce-crop-id-error");
}
#endif // !BUILDFLAG(IS_CHROMEOS_LACROS)