blob: dcaaa3de9dceaaca6716067d62768c8d7d0c3022 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/media/captured_surface_controller.h"
#include <limits>
#include <list>
#include <memory>
#include "base/functional/bind.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "content/browser/host_zoom_map_impl.h"
#include "content/browser/media/captured_surface_control_permission_manager.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/test/browser_task_environment.h"
#include "content/test/test_web_contents.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/page/page_zoom.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "url/gurl.h"
namespace content {
namespace {
using ::blink::mojom::CapturedWheelAction;
using ::blink::mojom::CapturedWheelActionPtr;
using ::blink::mojom::ZoomLevelAction;
using CSCResult = ::blink::mojom::CapturedSurfaceControlResult;
using CSCPermissionResult = CapturedSurfaceControlPermissionManager::
CapturedSurfaceControlPermissionStatus;
const char* const kUrlString = "http://www.example.com/";
enum class Boundary {
kMin,
kMax,
};
enum class CapturedSurfaceControlAPI {
kSendWheel,
kIncreaseZoomLevel,
kDecreaseZoomLevel,
kResetZoomLevel,
kRequestPermission,
};
ZoomLevelAction ToZoomLevelAction(CapturedSurfaceControlAPI input) {
switch (input) {
case CapturedSurfaceControlAPI::kIncreaseZoomLevel:
return ZoomLevelAction::kIncrease;
case CapturedSurfaceControlAPI::kDecreaseZoomLevel:
return ZoomLevelAction::kDecrease;
case CapturedSurfaceControlAPI::kResetZoomLevel:
return ZoomLevelAction::kReset;
case CapturedSurfaceControlAPI::kSendWheel:
case CapturedSurfaceControlAPI::kRequestPermission:
break;
}
NOTREACHED() << "Not a ZoomLevelAction.";
}
// The concept of "zoom level" is overloaded. For clarity, when we mean the
// "factor times 100," we'll just name it "zoom level percentage," at least
// in tests.
double GetZoomLevelPercentageFor(WebContents* wc) {
CHECK(wc);
return 100 * blink::ZoomLevelToZoomFactor(HostZoomMap::GetZoomLevel(wc));
}
// Make an arbitrary valid CapturedWheelAction.
CapturedWheelActionPtr MakeCapturedWheelActionPtr() {
return CapturedWheelAction::New(
/*x=*/0,
/*y=*/0,
/*wheel_delta_x=*/0,
/*wheel_delta_y=*/0);
}
class InputObserver : public RenderWidgetHost::InputEventObserver {
public:
struct ExpectedWheelEvent {
double x = 0;
double y = 0;
double delta_x = 0;
double delta_y = 0;
};
~InputObserver() override { EXPECT_TRUE(expected_events_.empty()); }
void OnInputEvent(const RenderWidgetHost& widget,
const blink::WebInputEvent& event) override {
if (event.GetType() != blink::WebInputEvent::Type::kMouseWheel) {
return;
}
const blink::WebMouseWheelEvent& wheel_event =
static_cast<const blink::WebMouseWheelEvent&>(event);
CHECK(!expected_events_.empty());
ExpectedWheelEvent expected_event = expected_events_.front();
expected_events_.pop_front();
EXPECT_EQ(expected_event.x, wheel_event.PositionInWidget().x());
EXPECT_EQ(expected_event.y, wheel_event.PositionInWidget().y());
EXPECT_EQ(expected_event.delta_x, wheel_event.delta_x);
EXPECT_EQ(expected_event.delta_y, wheel_event.delta_y);
}
void AddExpectation(ExpectedWheelEvent expected_event) {
expected_events_.push_back(expected_event);
// The wheel event chains are closed with a scroll of zero
// magnitude in the same location.
expected_events_.push_back(ExpectedWheelEvent{.x = expected_event.x,
.y = expected_event.y,
.delta_x = 0,
.delta_y = 0});
}
private:
std::list<ExpectedWheelEvent> expected_events_;
};
class TestView : public TestRenderWidgetHostView {
public:
explicit TestView(RenderWidgetHostImpl* rwhi)
: TestRenderWidgetHostView(rwhi) {}
~TestView() override = default;
void SetSize(const gfx::Size& size) override { size_ = size; }
gfx::Size GetVisibleViewportSize() override { return size_; }
private:
gfx::Size size_;
};
// Simulates a tab.
//
// Wraps a `WebContents`, which is the main object of interest, along with a
// `TestView`, which is essentially a `RenderWidgetHostView` that allows us to
// set a custom size, which is needed when testing SendWheel().
//
// This object records the original `RenderWidgetHostView` and injects it back
// from the destructor. This prevents LSAN/ASAN failures. This functionality is
// the main value proposition of this class.
class TestTab {
public:
static constexpr gfx::Size kDefaultViewportSize = gfx::Size(100, 400);
explicit TestTab(BrowserContext* browser_context,
std::optional<GURL> gurl = std::nullopt)
: web_contents_(MakeTestWebContents(browser_context)) {
// Store the original RenderWidgetHost, allowing it to be injected back from
// the destructor.
original_rwhv_ = GetRenderWidgetHostImpl()->GetView();
// Set a new RenderWidgetHost that allows us control over its size.
rwhv_ = std::make_unique<TestView>(GetRenderWidgetHostImpl());
SetView(rwhv_.get());
SetSize(kDefaultViewportSize);
if (gurl.has_value()) {
web_contents_->NavigateAndCommit(gurl.value());
}
}
virtual ~TestTab() {
SetView(original_rwhv_);
original_rwhv_ = nullptr;
}
TestWebContents* web_contents() const { return web_contents_.get(); }
WebContentsMediaCaptureId GetWebContentsMediaCaptureId() const {
RenderFrameHost* const rfh = web_contents_->GetPrimaryMainFrame();
return WebContentsMediaCaptureId(rfh->GetProcess()->GetDeprecatedID(),
rfh->GetRoutingID());
}
void SetSize(const gfx::Size& size) { rwhv_->SetSize(size); }
RenderWidgetHostImpl* GetRenderWidgetHostImpl() const {
return RenderWidgetHostImpl::From(
web_contents_->GetPrimaryMainFrame()->GetRenderWidgetHost());
}
void Focus() {
web_contents_->GetPrimaryMainFrame()->GetRenderWidgetHost()->Focus();
FrameTree& frame_tree = web_contents_->GetPrimaryFrameTree();
FrameTreeNode* const root = frame_tree.root();
frame_tree.SetFocusedFrame(
root, root->current_frame_host()->GetSiteInstance()->group());
}
double GetZoomLevelPercentage() {
return GetZoomLevelPercentageFor(web_contents_.get());
}
protected:
static std::unique_ptr<TestWebContents> MakeTestWebContents(
BrowserContext* browser_context) {
scoped_refptr<SiteInstance> instance =
SiteInstance::Create(browser_context);
instance->GetOrCreateProcessForTesting()->Init();
return TestWebContents::Create(browser_context, std::move(instance));
}
void SetView(RenderWidgetHostViewBase* rwhv) {
GetRenderWidgetHostImpl()->SetView(rwhv);
}
private:
const std::unique_ptr<TestWebContents> web_contents_;
std::unique_ptr<TestView> rwhv_;
raw_ptr<RenderWidgetHostViewBase> original_rwhv_ = nullptr;
};
class MockCapturedSurfaceControlPermissionManager
: public CapturedSurfaceControlPermissionManager {
public:
MockCapturedSurfaceControlPermissionManager(
GlobalRenderFrameHostId capturer_rfh_id)
: CapturedSurfaceControlPermissionManager(capturer_rfh_id) {}
~MockCapturedSurfaceControlPermissionManager() override = default;
void SetPermissionResult(CSCPermissionResult result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
result_ = result;
}
void CheckPermission(
base::OnceCallback<void(CSCPermissionResult)> callback) override {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
CHECK(result_.has_value());
std::move(callback).Run(result_.value());
}
private:
std::optional<CSCPermissionResult> result_;
};
using MockPermissionManager = MockCapturedSurfaceControlPermissionManager;
class MockObserver : public content::WebContentsObserver {
public:
explicit MockObserver(content::WebContents* web_contents)
: content::WebContentsObserver(web_contents) {}
~MockObserver() override = default;
// content::WebContentsObserver:
MOCK_METHOD(void, OnCapturedSurfaceControl, (), (override));
};
// Make a callback that expects `result` and then unblock `run_loop`.
base::OnceCallback<void(CSCResult)> MakeCallbackExpectingResult(
base::RunLoop* run_loop,
CSCResult expected_result,
MockWidgetInputHandler* mock_widget_input_handler) {
return base::BindOnce(
[](base::RunLoop* run_loop, CSCResult expected_result,
MockWidgetInputHandler* mock_widget_input_handler, CSCResult result) {
EXPECT_EQ(result, expected_result);
// Run callbacks corresponding to `DispatchEvent` method in
// `WidgetInputHandler` to allow processing of inputs in
// `MouseWheelEventQueue`.
if (mock_widget_input_handler) {
MockWidgetInputHandler::MessageVector messages =
mock_widget_input_handler->GetAndResetDispatchedMessages();
if (!messages.empty()) {
messages.clear();
mock_widget_input_handler->FlushReceiverForTesting();
}
}
run_loop->Quit();
},
run_loop, expected_result, mock_widget_input_handler);
}
class CapturedSurfaceControllerTestBase : public RenderViewHostTestHarness {
public:
~CapturedSurfaceControllerTestBase() override = default;
void SetUp() override {
RenderViewHostTestHarness::SetUp();
SetUpTestTabs();
StartCaptureOf(*capturee_);
AwaitWebContentsResolution();
auto* test_host = static_cast<TestRenderWidgetHost*>(
capturee_->GetRenderWidgetHostImpl());
mojo::Remote<blink::mojom::WidgetInputHandler> remote;
mock_widget_input_handler_ = std::make_unique<MockWidgetInputHandler>(
remote.BindNewPipeAndPassReceiver(), mojo::NullRemote());
test_host->GetRenderInputRouter()->SetWidgetInputHandlerForTesting(
std::move(remote));
}
void SetUpTestTabs(bool focus_capturer = true) {
capturer_ = std::make_unique<TestTab>(GetBrowserContext());
capturee_ =
std::make_unique<TestTab>(GetBrowserContext(), GURL(kUrlString));
if (focus_capturer) {
capturer_->Focus();
}
}
void StartCaptureOf(const TestTab& tab) {
auto permission_manager = std::make_unique<MockPermissionManager>(
capturer_->web_contents()->GetPrimaryMainFrame()->GetGlobalId());
permission_manager_ = permission_manager.get();
// `base::Unretained(this)` is safe because `this` owns `controller_` and
// therefore has a longer lifetime.
controller_ = CapturedSurfaceController::CreateForTesting(
capturer_->web_contents()->GetPrimaryMainFrame()->GetGlobalId(),
tab.GetWebContentsMediaCaptureId(), std::move(permission_manager),
base::BindRepeating(
&CapturedSurfaceControllerTestBase::OnZoomLevelChange,
base::Unretained(this)),
base::BindRepeating(
&CapturedSurfaceControllerTestBase::OnWebContentsResolved,
base::Unretained(this)));
}
void TearDown() override {
mock_widget_input_handler_.reset();
permission_manager_ = nullptr;
controller_.reset();
capturer_.reset();
capturee_.reset();
RenderViewHostTestHarness::TearDown();
}
void SetZoomFactor(std::unique_ptr<TestTab>& tab, double zoom_factor) {
WebContents* const wc = tab->web_contents();
content::HostZoomMap* const host_zoom_map =
content::HostZoomMap::GetForWebContents(wc);
CHECK(host_zoom_map);
host_zoom_map->SetTemporaryZoomLevel(
wc->GetPrimaryMainFrame()->GetGlobalId(),
blink::ZoomFactorToZoomLevel(zoom_factor));
if (!blink::ZoomValuesEqual(GetZoomLevelPercentageFor(wc),
100 * zoom_factor)) {
FAIL(); // Abort test, not just the helper method.
}
}
void AwaitWebContentsResolution() {
CHECK(!wc_resolution_run_loop_);
wc_resolution_run_loop_ = std::make_unique<base::RunLoop>();
wc_resolution_run_loop_->Run();
wc_resolution_run_loop_.reset();
}
void AwaitOnZoomLevelChange() {
CHECK(!on_zoom_level_change_run_loop_);
on_zoom_level_change_run_loop_ = std::make_unique<base::RunLoop>();
on_zoom_level_change_run_loop_->Run();
on_zoom_level_change_run_loop_.reset();
}
void OnZoomLevelChange(int zoom_level) {
if (on_zoom_level_change_run_loop_) {
on_zoom_level_change_run_loop_->Quit();
}
zoom_level_ = zoom_level;
}
void OnWebContentsResolved(base::WeakPtr<WebContents> wc) {
if (wc_resolution_run_loop_) {
wc_resolution_run_loop_->Quit();
}
last_resolved_web_contents_ = wc;
}
protected:
std::unique_ptr<CapturedSurfaceController> controller_;
raw_ptr<MockPermissionManager> permission_manager_ = nullptr;
std::unique_ptr<TestTab> capturer_;
std::unique_ptr<TestTab> capturee_;
std::unique_ptr<base::RunLoop> wc_resolution_run_loop_;
std::unique_ptr<base::RunLoop> on_zoom_level_change_run_loop_;
std::optional<base::WeakPtr<WebContents>> last_resolved_web_contents_;
std::optional<int> zoom_level_;
std::unique_ptr<MockWidgetInputHandler> mock_widget_input_handler_ = nullptr;
};
class CapturedSurfaceControllerSendWheelTest
: public CapturedSurfaceControllerTestBase {
public:
~CapturedSurfaceControllerSendWheelTest() override = default;
void SetUp() override {
CapturedSurfaceControllerTestBase::SetUp();
input_observer_ = std::make_unique<InputObserver>();
capturee_->GetRenderWidgetHostImpl()->AddInputEventObserver(
input_observer_.get());
}
void TearDown() override {
capturee_->GetRenderWidgetHostImpl()->RemoveInputEventObserver(
input_observer_.get());
CapturedSurfaceControllerTestBase::TearDown();
}
protected:
std::unique_ptr<InputObserver> input_observer_;
};
TEST_F(CapturedSurfaceControllerSendWheelTest, CorrectScaling) {
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
capturee_->SetSize(gfx::Size(256, 4096));
base::RunLoop run_loop;
input_observer_->AddExpectation(InputObserver::ExpectedWheelEvent{
.x = 256 * 0.25, .y = 4096 * 0.5, .delta_x = 300, .delta_y = 400});
controller_->SendWheel(
CapturedWheelAction::New(
/*x=*/0.25,
/*y=*/0.5,
/*wheel_delta_x=*/300,
/*wheel_delta_y=*/400),
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
}
TEST_F(CapturedSurfaceControllerSendWheelTest,
GracefullyHandleZeroWidthCapturedSurface) {
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
capturee_->SetSize(gfx::Size(0, 4096));
base::RunLoop run_loop;
// Note absence of call to input_observer_->AddExpectation().
controller_->SendWheel(
CapturedWheelAction::New(
/*x=*/0.25,
/*y=*/0.5,
/*wheel_delta_x=*/300,
/*wheel_delta_y=*/400),
MakeCallbackExpectingResult(&run_loop, CSCResult::kUnknownError,
mock_widget_input_handler_.get()));
run_loop.Run();
}
TEST_F(CapturedSurfaceControllerSendWheelTest,
GracefullyHandleZeroHeightCapturedSurface) {
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
capturee_->SetSize(gfx::Size(256, 0));
base::RunLoop run_loop;
// Note absence of call to input_observer_->AddExpectation().
controller_->SendWheel(
CapturedWheelAction::New(
/*x=*/0.25,
/*y=*/0.5,
/*wheel_delta_x=*/300,
/*wheel_delta_y=*/400),
MakeCallbackExpectingResult(&run_loop, CSCResult::kUnknownError,
mock_widget_input_handler_.get()));
run_loop.Run();
}
TEST_F(CapturedSurfaceControllerSendWheelTest,
GracefullyHandleExtremelyNarrowCapturedSurface) {
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
capturee_->SetSize(gfx::Size(1, 4096));
base::RunLoop run_loop;
input_observer_->AddExpectation(InputObserver::ExpectedWheelEvent{
.x = 0, .y = 4096 * 0.5, .delta_x = 300, .delta_y = 400});
controller_->SendWheel(
CapturedWheelAction::New(
/*x=*/0.25,
/*y=*/0.5,
/*wheel_delta_x=*/300,
/*wheel_delta_y=*/400),
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
}
TEST_F(CapturedSurfaceControllerSendWheelTest,
GracefullyHandleExtremelyShortCapturedSurface) {
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
capturee_->SetSize(gfx::Size(256, 1));
base::RunLoop run_loop;
input_observer_->AddExpectation(InputObserver::ExpectedWheelEvent{
.x = 256 * 0.25, .y = 0, .delta_x = 300, .delta_y = 400});
controller_->SendWheel(
CapturedWheelAction::New(
/*x=*/0.25,
/*y=*/0.5,
/*wheel_delta_x=*/300,
/*wheel_delta_y=*/400),
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
}
class CapturedSurfaceControllerZoomEventTest
: public CapturedSurfaceControllerTestBase {
public:
~CapturedSurfaceControllerZoomEventTest() override = default;
void SetUp() override {
CapturedSurfaceControllerTestBase::SetUp();
new_capturee_ = std::make_unique<TestTab>(GetBrowserContext());
}
void TearDown() override {
new_capturee_.reset();
CapturedSurfaceControllerTestBase::TearDown();
}
protected:
std::unique_ptr<TestTab> new_capturee_;
};
TEST_F(CapturedSurfaceControllerZoomEventTest, ZoomEventProducedByZoomChange) {
HostZoomMap::SetZoomLevel(capturee_->web_contents(),
blink::ZoomFactorToZoomLevel(0.9));
AwaitOnZoomLevelChange();
ASSERT_TRUE(zoom_level_);
EXPECT_EQ(zoom_level_, 90);
}
TEST_F(CapturedSurfaceControllerZoomEventTest,
ZoomEventProducedByTemporaryZoomChange) {
content::HostZoomMap* const host_zoom_map =
content::HostZoomMap::GetForWebContents(capturee_->web_contents());
CHECK(host_zoom_map);
host_zoom_map->SetTemporaryZoomLevel(
capturee_->web_contents()->GetPrimaryMainFrame()->GetGlobalId(),
blink::ZoomFactorToZoomLevel(0.9));
AwaitOnZoomLevelChange();
ASSERT_TRUE(zoom_level_);
EXPECT_EQ(zoom_level_, 90);
}
TEST_F(CapturedSurfaceControllerZoomEventTest, ZoomEventUpdateTarget) {
const RenderFrameHost* const new_main_rfh =
new_capturee_->web_contents()->GetPrimaryMainFrame();
const WebContentsMediaCaptureId new_wc_id(
new_main_rfh->GetProcess()->GetDeprecatedID(),
new_main_rfh->GetRoutingID());
controller_->UpdateCaptureTarget(new_wc_id);
AwaitWebContentsResolution();
// Set a temporary zoom level so only the second WebContents is affected.
HostZoomMapImpl* mock_widget_input_handler_zoom_map =
static_cast<HostZoomMapImpl*>(
HostZoomMap::GetForWebContents(new_capturee_->web_contents()));
mock_widget_input_handler_zoom_map->SetTemporaryZoomLevel(
new_main_rfh->GetGlobalId(), blink::ZoomFactorToZoomLevel(1.1));
AwaitOnZoomLevelChange();
ASSERT_TRUE(zoom_level_);
EXPECT_EQ(zoom_level_, 110);
}
class CapturedSurfaceControllerUpdateZoomLevelTest
: public CapturedSurfaceControllerTestBase {
public:
CapturedSurfaceControllerUpdateZoomLevelTest()
: min_zoom_factor_(blink::kPresetBrowserZoomFactors.front()),
max_zoom_factor_(blink::kPresetBrowserZoomFactors.back()) {}
~CapturedSurfaceControllerUpdateZoomLevelTest() override = default;
void SetUp() override {
CapturedSurfaceControllerTestBase::SetUp();
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
}
protected:
const double min_zoom_factor_;
const double max_zoom_factor_;
};
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
IncreaseZoomLevelSucceedsUntilMaxLevel) {
SetZoomFactor(capturee_, min_zoom_factor_);
for (int i = 1; i < blink::kPresetBrowserZoomFactors.size(); ++i) {
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kIncrease,
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
EXPECT_TRUE(
blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(),
100 * blink::kPresetBrowserZoomFactors[i]));
}
}
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
IncreaseZoomLevelSucceedsBetweenCanonicalValues) {
for (int i = 0; i < blink::kPresetBrowserZoomFactors.size() - 1; ++i) {
// Average the two factors and set the zoom level to that,
// thereby getting a non-canonical zoom level.
const double mid_zoom_factor = (blink::kPresetBrowserZoomFactors[i] +
blink::kPresetBrowserZoomFactors[i + 1]) /
2;
SetZoomFactor(capturee_, mid_zoom_factor);
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kIncrease,
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
EXPECT_TRUE(
blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(),
100 * blink::kPresetBrowserZoomFactors[i + 1]));
}
}
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
DecreaseZoomLevelSucceedsUntilMinLevel) {
SetZoomFactor(capturee_, max_zoom_factor_);
for (int i = blink::kPresetBrowserZoomFactors.size() - 2; i >= 0; --i) {
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kDecrease,
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
EXPECT_TRUE(
blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(),
100 * blink::kPresetBrowserZoomFactors[i]));
}
}
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
DecreaseZoomLevelSucceedsBetweenCanonicalValues) {
for (int i = 0; i < blink::kPresetBrowserZoomFactors.size() - 1; ++i) {
// Average the two factors and set the zoom level to that,
// thereby getting a non-canonical zoom level.
const double mid_zoom_factor = (blink::kPresetBrowserZoomFactors[i] +
blink::kPresetBrowserZoomFactors[i + 1]) /
2;
SetZoomFactor(capturee_, mid_zoom_factor);
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kDecrease,
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
EXPECT_TRUE(
blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(),
100 * blink::kPresetBrowserZoomFactors[i]));
}
}
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
IncreaseZoomLevelFailsWhenAtMaxLevel) {
SetZoomFactor(capturee_, max_zoom_factor_);
// Main expectation - the call to UpdateZoomLevel() fails.
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kIncrease,
MakeCallbackExpectingResult(&run_loop, CSCResult::kMaxZoomLevel,
mock_widget_input_handler_.get()));
run_loop.Run();
// Secondary expectation - zoom level remains unchanged.
EXPECT_TRUE(blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(),
100 * max_zoom_factor_));
}
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
DecreaseZoomLevelFailsWhenAtMinLevel) {
SetZoomFactor(capturee_, min_zoom_factor_);
// Main expectation - the call to UpdateZoomLevel() fails.
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kDecrease,
MakeCallbackExpectingResult(&run_loop, CSCResult::kMinZoomLevel,
mock_widget_input_handler_.get()));
run_loop.Run();
// Secondary expectation - zoom level remains unchanged.
EXPECT_TRUE(blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(),
100 * min_zoom_factor_));
}
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
IncreaseZoomLevelFailsWhenWithinEpsilonOfMaxLevel) {
// Start out within epsilon of the maximum zoom level.
// (Note that this has to be even smaller than the value within
// blink::ZoomValuesEqual()).
constexpr double kEpsilon = 0.000001;
SetZoomFactor(capturee_, max_zoom_factor_ - kEpsilon);
// Secondary expectation - zoom level remains unchanged.
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kIncrease,
MakeCallbackExpectingResult(&run_loop, CSCResult::kMaxZoomLevel,
mock_widget_input_handler_.get()));
run_loop.Run();
// Secondary expectation - zoom level updated to actual maximum.
EXPECT_TRUE(blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(),
100 * max_zoom_factor_));
}
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
DecreaseZoomLevelFailsWhenWithinEpsilonOfMinLevel) {
// Start out within epsilon of the minimum zoom level.
// (Note that this has to be even smaller than the value within
// blink::ZoomValuesEqual()).
constexpr double kEpsilon = 0.000001;
SetZoomFactor(capturee_, min_zoom_factor_ + kEpsilon);
// Secondary expectation - zoom level remains unchanged.
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kDecrease,
MakeCallbackExpectingResult(&run_loop, CSCResult::kMinZoomLevel,
mock_widget_input_handler_.get()));
run_loop.Run();
// Secondary expectation - zoom level updated to actual maximum.
EXPECT_TRUE(blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(),
100 * min_zoom_factor_));
}
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
IncreaseZoomLevelSucceedsWhenWithinEpsilonOfMaxLevel) {
// Set the captured tab to a zoom level that would appear to the user as
// roughly 1% less than the maximum.
SetZoomFactor(capturee_, max_zoom_factor_ - 0.01);
// Main expectation - the call to UpdateZoomLevel() succeeds.
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kIncrease,
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
// Secondary expectation - zoom level updated to maximum.
EXPECT_TRUE(blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(),
100 * max_zoom_factor_));
}
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
DecreaseZoomLevelSucceedsWhenWithinEpsilonOfMinLevel) {
// Set the captured tab to a zoom level that would appear to the user as
// roughly 1% more than the minimum.
SetZoomFactor(capturee_, min_zoom_factor_ + 0.01);
// Main expectation - the call to UpdateZoomLevel() succeeds.
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kDecrease,
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
// Secondary expectation - zoom level updated to minimum.
EXPECT_TRUE(blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(),
100 * min_zoom_factor_));
}
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
ResetZoomLevelSucceedsAtAllCanonicalLevels) {
for (double zoom_factor : blink::kPresetBrowserZoomFactors) {
SetZoomFactor(capturee_, zoom_factor);
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kReset,
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
EXPECT_TRUE(
blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(), 100));
}
}
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
ResetZoomLevelSucceedsBetweenCanonicalLevels) {
for (int i = 1; i < blink::kPresetBrowserZoomFactors.size() - 1; ++i) {
// Average the two factors and set the zoom level to that,
// thereby getting a non-canonical zoom level.
const double mid_zoom_factor = (blink::kPresetBrowserZoomFactors[i] +
blink::kPresetBrowserZoomFactors[i + 1]) /
2;
SetZoomFactor(capturee_, mid_zoom_factor);
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kReset,
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
EXPECT_TRUE(
blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(), 100));
}
}
// This is vicariously tested by ResetZoomLevelSucceedsAtAllCanonicalLevels,
// but it is important enough a use case to merit its own explicit test.
TEST_F(CapturedSurfaceControllerUpdateZoomLevelTest,
ResetZoomLevelSucceedsEvenWhenAlreadyAtDefaultZoom) {
SetZoomFactor(capturee_, 1.0);
ASSERT_TRUE(blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(), 100));
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kReset,
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
EXPECT_TRUE(blink::ZoomValuesEqual(capturee_->GetZoomLevelPercentage(), 100));
}
class CapturedSurfaceControllerUpdateZoomLevelImpermanenceTest
: public CapturedSurfaceControllerTestBase {
public:
~CapturedSurfaceControllerUpdateZoomLevelImpermanenceTest() override =
default;
};
// Ensure the effect does not extend to other tabs, even if they are dialed
// to the same origin.
TEST_F(CapturedSurfaceControllerUpdateZoomLevelImpermanenceTest,
UpdateZoomLevelOnlyAffectsCapturedTab) {
ASSERT_EQ(capturee_->GetZoomLevelPercentage(), 100);
// Create another tab and navigate it to the same URL as the captured tab.
auto duplicate_tab =
std::make_unique<TestTab>(GetBrowserContext(), GURL(kUrlString));
ASSERT_EQ(duplicate_tab->GetZoomLevelPercentage(), 100);
// Change the zoom-level on the captured tab.
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kIncrease,
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
ASSERT_GT(capturee_->GetZoomLevelPercentage(), 100);
// Setting the zoom level only affected the captured tab, not the
// Chrome-level settings for the origin.
EXPECT_EQ(duplicate_tab->GetZoomLevelPercentage(), 100);
}
// Ensure the effect does not get persisted and does not affect newly
// opened tabs later, even if they are navigated to the same URL.
TEST_F(CapturedSurfaceControllerUpdateZoomLevelImpermanenceTest,
UpdateZoomLevelEffectsDoNotPersistAfterClosed) {
ASSERT_EQ(capturee_->GetZoomLevelPercentage(), 100);
// Change the zoom-level on the captured tab.
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ZoomLevelAction::kIncrease,
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
ASSERT_GT(capturee_->GetZoomLevelPercentage(), 100);
// Close the tab.
capturee_.reset();
// Create another tab and navigate it to the same URL as the captured tab.
auto new_tab =
std::make_unique<TestTab>(GetBrowserContext(), GURL(kUrlString));
// Setting the zoom level only affected the captured tab, not the
// Chrome-level settings for the origin.
EXPECT_EQ(new_tab->GetZoomLevelPercentage(), 100);
}
class CapturedSurfaceControllerInterfaceTestBase
: public CapturedSurfaceControllerTestBase {
public:
explicit CapturedSurfaceControllerInterfaceTestBase(
CapturedSurfaceControlAPI tested_interface)
: tested_interface_(tested_interface) {}
~CapturedSurfaceControllerInterfaceTestBase() override = default;
void RunTestedActionAndExpect(base::RunLoop* run_loop,
CSCResult expected_result) {
switch (tested_interface_) {
case CapturedSurfaceControlAPI::kSendWheel:
controller_->SendWheel(
MakeCapturedWheelActionPtr(),
MakeCallbackExpectingResult(run_loop, expected_result,
mock_widget_input_handler_.get()));
return;
case CapturedSurfaceControlAPI::kIncreaseZoomLevel:
case CapturedSurfaceControlAPI::kDecreaseZoomLevel:
case CapturedSurfaceControlAPI::kResetZoomLevel:
controller_->UpdateZoomLevel(
ToZoomLevelAction(tested_interface_),
MakeCallbackExpectingResult(run_loop, expected_result,
/*mock_widget_input_handler=*/nullptr));
return;
case CapturedSurfaceControlAPI::kRequestPermission:
controller_->RequestPermission(MakeCallbackExpectingResult(
run_loop, expected_result, /*mock_widget_input_handler=*/nullptr));
return;
}
NOTREACHED();
}
protected:
const CapturedSurfaceControlAPI tested_interface_;
};
class CapturedSurfaceControllerInterfaceTest
: public CapturedSurfaceControllerInterfaceTestBase,
public ::testing::WithParamInterface<CapturedSurfaceControlAPI> {
public:
CapturedSurfaceControllerInterfaceTest()
: CapturedSurfaceControllerInterfaceTestBase(GetParam()) {}
~CapturedSurfaceControllerInterfaceTest() override = default;
};
INSTANTIATE_TEST_SUITE_P(
,
CapturedSurfaceControllerInterfaceTest,
::testing::Values(CapturedSurfaceControlAPI::kSendWheel,
CapturedSurfaceControlAPI::kIncreaseZoomLevel,
CapturedSurfaceControlAPI::kDecreaseZoomLevel,
CapturedSurfaceControlAPI::kResetZoomLevel,
CapturedSurfaceControlAPI::kRequestPermission));
TEST_P(CapturedSurfaceControllerInterfaceTest, SuccessReportedIfPermitted) {
base::RunLoop run_loop;
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
RunTestedActionAndExpect(&run_loop, CSCResult::kSuccess);
run_loop.Run();
}
TEST_P(CapturedSurfaceControllerInterfaceTest, NoPermissionReportedIfDenied) {
base::RunLoop run_loop;
permission_manager_->SetPermissionResult(CSCPermissionResult::kDenied);
RunTestedActionAndExpect(&run_loop, CSCResult::kNoPermissionError);
run_loop.Run();
}
TEST_P(CapturedSurfaceControllerInterfaceTest,
UnknownErrorReportedIfPermissionError) {
base::RunLoop run_loop;
permission_manager_->SetPermissionResult(CSCPermissionResult::kError);
RunTestedActionAndExpect(&run_loop, CSCResult::kUnknownError);
run_loop.Run();
}
// Simulate the captured tab being closed after permission is granted but before
// the controller has time to process the response from the permission manager.
TEST_P(CapturedSurfaceControllerInterfaceTest,
SurfaceNotFoundReportedIfTabClosedBeforePromptResponseHandled) {
base::RunLoop run_loop;
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
capturee_.reset();
RunTestedActionAndExpect(&run_loop, CSCResult::kCapturedSurfaceNotFoundError);
run_loop.Run();
}
TEST_P(CapturedSurfaceControllerInterfaceTest,
SurfaceNotFoundReportedIfCaptureTargetUpdatedToNonTabSurface) {
base::RunLoop run_loop;
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
controller_->UpdateCaptureTarget(WebContentsMediaCaptureId());
RunTestedActionAndExpect(&run_loop, CSCResult::kCapturedSurfaceNotFoundError);
run_loop.Run();
}
TEST_P(CapturedSurfaceControllerInterfaceTest,
CapturerNotFoundErrorReportedIfCapturerClosed) {
base::RunLoop run_loop;
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
capturer_.reset();
RunTestedActionAndExpect(&run_loop, CSCResult::kCapturerNotFoundError);
run_loop.Run();
}
// Test suite ensuring that API calls before/after the WebContents ID is
// resolved to a base::WeakPtr<WebContents> behave as expected.
class CapturedSurfaceControllerWebContentsResolutionTest
: public CapturedSurfaceControllerInterfaceTestBase,
public ::testing::WithParamInterface<CapturedSurfaceControlAPI> {
public:
CapturedSurfaceControllerWebContentsResolutionTest()
: CapturedSurfaceControllerInterfaceTestBase(GetParam()) {}
~CapturedSurfaceControllerWebContentsResolutionTest() override = default;
void SetUp() override {
// Intentionally skip CapturedSurfaceControllerInterfaceTestBase's SetUp(),
// and therefore also CapturedSurfaceControllerTestBase's SetUp().
RenderViewHostTestHarness::SetUp();
// Prepare a new tab to capture instead of the original one.
new_capturee_ = std::make_unique<TestTab>(GetBrowserContext());
}
void TearDown() override {
new_capturee_.reset();
CapturedSurfaceControllerInterfaceTestBase::TearDown();
}
protected:
std::unique_ptr<TestTab> new_capturee_;
};
INSTANTIATE_TEST_SUITE_P(
,
CapturedSurfaceControllerWebContentsResolutionTest,
::testing::Values(CapturedSurfaceControlAPI::kSendWheel,
CapturedSurfaceControlAPI::kIncreaseZoomLevel,
CapturedSurfaceControlAPI::kDecreaseZoomLevel,
CapturedSurfaceControlAPI::kResetZoomLevel));
TEST_P(CapturedSurfaceControllerWebContentsResolutionTest,
ApiInvocationAfterWebContentsResolutionSucceeds) {
SetUpTestTabs(); // Triggers resolution but does not await it.
StartCaptureOf(*capturee_);
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
AwaitWebContentsResolution();
base::RunLoop run_loop;
RunTestedActionAndExpect(&run_loop, CSCResult::kSuccess);
run_loop.Run();
}
TEST_P(CapturedSurfaceControllerWebContentsResolutionTest,
ApiInvocationPriorToWebContentsResolutionFails) {
SetUpTestTabs(); // Triggers resolution but does not await it.
StartCaptureOf(*capturee_);
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
base::RunLoop run_loop;
RunTestedActionAndExpect(&run_loop, CSCResult::kCapturedSurfaceNotFoundError);
run_loop.Run();
AwaitWebContentsResolution();
}
TEST_P(
CapturedSurfaceControllerWebContentsResolutionTest,
ApiInvocationPriorToWebContentsResolutionFailsButSubsequentCallsAreNotBlocked) {
// Setup - repeat ApiInvocationPriorToWebContentsResolutionFails.
{
SetUpTestTabs(); // Triggers resolution but does not await it.
StartCaptureOf(*capturee_);
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
base::RunLoop run_loop;
RunTestedActionAndExpect(&run_loop,
CSCResult::kCapturedSurfaceNotFoundError);
run_loop.Run();
AwaitWebContentsResolution();
}
// After AwaitWebContentsResolution() is called, subsequent API calls succeed.
base::RunLoop run_loop;
RunTestedActionAndExpect(&run_loop, CSCResult::kSuccess);
run_loop.Run();
}
TEST_P(CapturedSurfaceControllerWebContentsResolutionTest,
MultiplePendingResolutions) {
SetUpTestTabs(); // Triggers resolution but does not await it.
StartCaptureOf(*capturee_);
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
// The original resolution has not yet resolved.
ASSERT_FALSE(last_resolved_web_contents_.has_value());
// Updating to capture another tab schedules a new task to resolve.
controller_->UpdateCaptureTarget(
new_capturee_->GetWebContentsMediaCaptureId());
// Neither resolutions has completed at this point.
ASSERT_FALSE(last_resolved_web_contents_.has_value());
// We await the resolution to be considered complete.
// This should only happen after the last pending task resolves.
// In our cases, that is for the new tab. The first response
// should be ignored.
AwaitWebContentsResolution();
ASSERT_TRUE(last_resolved_web_contents_.has_value());
EXPECT_EQ(last_resolved_web_contents_.value().get(),
new_capturee_->web_contents());
}
// Similar to CapturedSurfaceControllerWebContentsResolutionTest,
// but focuses on calls to UpdateCaptureTarget(), which also trigger resolution.
class CapturedSurfaceControllerWebContentsResolutionOfUpdatesTest
: public CapturedSurfaceControllerInterfaceTestBase,
public ::testing::WithParamInterface<CapturedSurfaceControlAPI> {
public:
CapturedSurfaceControllerWebContentsResolutionOfUpdatesTest()
: CapturedSurfaceControllerInterfaceTestBase(GetParam()) {}
~CapturedSurfaceControllerWebContentsResolutionOfUpdatesTest() override =
default;
void SetUp() override {
// Unlike CapturedSurfaceControllerWebContentsResolutionTest, the current
// test works well with the parent's SetUp(), which awaits the resolution
// of the *first* ID. This is due to the current test's focus on what
// happens before/after the call to UpdateCaptureTarget().
CapturedSurfaceControllerInterfaceTestBase::SetUp();
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
// Prepare a new tab to capture instead of the original one.
new_capturee_ = std::make_unique<TestTab>(GetBrowserContext());
}
void TearDown() override {
new_capturee_.reset();
CapturedSurfaceControllerInterfaceTestBase::TearDown();
}
protected:
std::unique_ptr<TestTab> new_capturee_;
};
INSTANTIATE_TEST_SUITE_P(
,
CapturedSurfaceControllerWebContentsResolutionOfUpdatesTest,
::testing::Values(CapturedSurfaceControlAPI::kSendWheel,
CapturedSurfaceControlAPI::kIncreaseZoomLevel,
CapturedSurfaceControlAPI::kDecreaseZoomLevel,
CapturedSurfaceControlAPI::kResetZoomLevel));
TEST_P(
CapturedSurfaceControllerWebContentsResolutionOfUpdatesTest,
AfterUpdateCaptureTargetApiInvocationAfterToWebContentsResolutionSucceeds) {
// Call UpdateCaptureTarget() - capturing a new tab.
controller_->UpdateCaptureTarget(
new_capturee_->GetWebContentsMediaCaptureId());
AwaitWebContentsResolution();
base::RunLoop run_loop;
RunTestedActionAndExpect(&run_loop, CSCResult::kSuccess);
run_loop.Run();
}
TEST_P(CapturedSurfaceControllerWebContentsResolutionOfUpdatesTest,
AfterUpdateCaptureTargetApiInvocationPriorToWebContentsResolutionFails) {
// Call UpdateCaptureTarget() - capturing a new tab.
controller_->UpdateCaptureTarget(
new_capturee_->GetWebContentsMediaCaptureId());
// Note absence of call to AwaitWebContentsResolution().
base::RunLoop run_loop;
RunTestedActionAndExpect(&run_loop, CSCResult::kCapturedSurfaceNotFoundError);
run_loop.Run();
AwaitWebContentsResolution();
}
// Test suite ensuring that API calls before/after the WebContents ID is
// resolved to a base::WeakPtr<WebContents> behave as expected.
class CapturedSurfaceControllerSelfCaptureTest
: public CapturedSurfaceControllerInterfaceTestBase,
public ::testing::WithParamInterface<CapturedSurfaceControlAPI> {
public:
CapturedSurfaceControllerSelfCaptureTest()
: CapturedSurfaceControllerInterfaceTestBase(GetParam()) {}
~CapturedSurfaceControllerSelfCaptureTest() override = default;
void SetUp() override {
// Intentionally skip CapturedSurfaceControllerInterfaceTestBase's SetUp(),
// and therefore also CapturedSurfaceControllerTestBase's SetUp().
RenderViewHostTestHarness::SetUp();
SetUpTestTabs();
}
};
INSTANTIATE_TEST_SUITE_P(
,
CapturedSurfaceControllerSelfCaptureTest,
::testing::Values(CapturedSurfaceControlAPI::kSendWheel,
CapturedSurfaceControlAPI::kIncreaseZoomLevel,
CapturedSurfaceControlAPI::kDecreaseZoomLevel,
CapturedSurfaceControlAPI::kResetZoomLevel));
TEST_P(CapturedSurfaceControllerSelfCaptureTest, SelfCaptureDisallowed) {
StartCaptureOf(*capturer_);
AwaitWebContentsResolution();
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
base::RunLoop run_loop;
RunTestedActionAndExpect(&run_loop,
CSCResult::kDisallowedForSelfCaptureError);
run_loop.Run();
}
TEST_P(CapturedSurfaceControllerSelfCaptureTest,
UpdateCaptureTargetToOtherTabEnablesCapturedSurfaceControl) {
StartCaptureOf(*capturer_);
AwaitWebContentsResolution();
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
controller_->UpdateCaptureTarget(capturee_->GetWebContentsMediaCaptureId());
AwaitWebContentsResolution();
base::RunLoop run_loop;
RunTestedActionAndExpect(&run_loop, CSCResult::kSuccess);
run_loop.Run();
}
TEST_P(CapturedSurfaceControllerSelfCaptureTest,
UpdateCaptureTargetToCapturingTabDisablesCapturedSurfaceControl) {
StartCaptureOf(*capturee_);
AwaitWebContentsResolution();
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
{
base::RunLoop run_loop;
RunTestedActionAndExpect(&run_loop, CSCResult::kSuccess);
run_loop.Run();
}
controller_->UpdateCaptureTarget(capturer_->GetWebContentsMediaCaptureId());
AwaitWebContentsResolution();
base::RunLoop run_loop;
RunTestedActionAndExpect(&run_loop,
CSCResult::kDisallowedForSelfCaptureError);
run_loop.Run();
}
// This test suite checks correct clamping of x/y wheel-deltas to min/max.
//
// The suite is parameterized on the *zoom* level because that affects
// the values that will ultimately be fed into the UI system, and checking
// at both the min/max zoom levels increases coverage somewhat.
//
// The suite is *not* parameterized on the wheel deltas themselves, as that
// would increase test complexity and reduce confidence in test correctness.
class CapturedSurfaceControllerSendWheelClampTest
: public CapturedSurfaceControllerSendWheelTest,
public ::testing::WithParamInterface<Boundary> {
public:
CapturedSurfaceControllerSendWheelClampTest()
: zoom_level_boundary_(GetParam()) {}
~CapturedSurfaceControllerSendWheelClampTest() override = default;
protected:
int zoom_level() const {
static const double kMin = 100 * blink::kMaximumBrowserZoomFactor;
static const double kMax = 100 * blink::kMinimumBrowserZoomFactor;
switch (zoom_level_boundary_) {
case Boundary::kMin:
return static_cast<int>(std::ceil(kMin));
case Boundary::kMax:
return static_cast<int>(std::floor(kMax));
}
NOTREACHED();
}
private:
const Boundary zoom_level_boundary_;
};
INSTANTIATE_TEST_SUITE_P(,
CapturedSurfaceControllerSendWheelClampTest,
::testing::Values(Boundary::kMin, Boundary::kMax));
TEST_P(CapturedSurfaceControllerSendWheelClampTest, ClampMinWheelDeltaX) {
using WheelDeltaType = decltype(CapturedWheelAction::wheel_delta_x);
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
base::RunLoop run_loop;
input_observer_->AddExpectation(InputObserver::ExpectedWheelEvent{
.x = 0,
.y = 0,
.delta_x = -CapturedSurfaceController::kMaxWheelDeltaMagnitude,
.delta_y = 0});
controller_->SendWheel(
CapturedWheelAction::New(
/*x=*/0,
/*y=*/0,
/*wheel_delta_x=*/std::numeric_limits<WheelDeltaType>::min(),
/*wheel_delta_y=*/0),
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
}
TEST_P(CapturedSurfaceControllerSendWheelClampTest, ClampMaxWheelDeltaX) {
using WheelDeltaType = decltype(CapturedWheelAction::wheel_delta_x);
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
base::RunLoop run_loop;
input_observer_->AddExpectation(InputObserver::ExpectedWheelEvent{
.x = 0,
.y = 0,
.delta_x = CapturedSurfaceController::kMaxWheelDeltaMagnitude,
.delta_y = 0});
controller_->SendWheel(
CapturedWheelAction::New(
/*x=*/0,
/*y=*/0,
/*wheel_delta_x=*/std::numeric_limits<WheelDeltaType>::max(),
/*wheel_delta_y=*/0),
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
}
TEST_P(CapturedSurfaceControllerSendWheelClampTest, ClampMinWheelDeltaY) {
using WheelDeltaType = decltype(CapturedWheelAction::wheel_delta_y);
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
base::RunLoop run_loop;
input_observer_->AddExpectation(InputObserver::ExpectedWheelEvent{
.x = 0,
.y = 0,
.delta_x = 0,
.delta_y = -CapturedSurfaceController::kMaxWheelDeltaMagnitude});
controller_->SendWheel(
CapturedWheelAction::New(
/*x=*/0,
/*y=*/0,
/*wheel_delta_x=*/0,
/*wheel_delta_y=*/std::numeric_limits<WheelDeltaType>::min()),
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
}
TEST_P(CapturedSurfaceControllerSendWheelClampTest, ClampMaxWheelDeltaY) {
using WheelDeltaType = decltype(CapturedWheelAction::wheel_delta_y);
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
base::RunLoop run_loop;
input_observer_->AddExpectation(InputObserver::ExpectedWheelEvent{
.x = 0,
.y = 0,
.delta_x = 0,
.delta_y = CapturedSurfaceController::kMaxWheelDeltaMagnitude});
controller_->SendWheel(
CapturedWheelAction::New(
/*x=*/0,
/*y=*/0,
/*wheel_delta_x=*/0,
/*wheel_delta_y=*/std::numeric_limits<WheelDeltaType>::max()),
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
}
class WebContentsObserverCscNotifiedBySendWheelTest
: public CapturedSurfaceControllerTestBase {
public:
~WebContentsObserverCscNotifiedBySendWheelTest() override = default;
};
TEST_F(WebContentsObserverCscNotifiedBySendWheelTest,
NotifiedBySendWheelIfSuccessful) {
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
testing::StrictMock<MockObserver> observer(capturer_->web_contents());
EXPECT_CALL(observer, OnCapturedSurfaceControl()).Times(1);
base::RunLoop run_loop;
controller_->SendWheel(
CapturedWheelAction::New(
/*x=*/0.25,
/*y=*/0.5,
/*wheel_delta_x=*/300,
/*wheel_delta_y=*/400),
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
}
TEST_F(WebContentsObserverCscNotifiedBySendWheelTest,
NotNotifiedBySendWheelIfUnsuccessful) {
permission_manager_->SetPermissionResult(CSCPermissionResult::kDenied);
testing::StrictMock<MockObserver> observer(capturer_->web_contents());
EXPECT_CALL(observer, OnCapturedSurfaceControl()).Times(0);
base::RunLoop run_loop;
controller_->SendWheel(
CapturedWheelAction::New(
/*x=*/0.25,
/*y=*/0.5,
/*wheel_delta_x=*/300,
/*wheel_delta_y=*/400),
MakeCallbackExpectingResult(&run_loop, CSCResult::kNoPermissionError,
mock_widget_input_handler_.get()));
run_loop.Run();
}
class WebContentsObserverCscNotifiedByUpdateZoomLevelTest
: public CapturedSurfaceControllerTestBase,
public ::testing::WithParamInterface<CapturedSurfaceControlAPI> {
public:
WebContentsObserverCscNotifiedByUpdateZoomLevelTest()
: tested_interface_(GetParam()) {}
~WebContentsObserverCscNotifiedByUpdateZoomLevelTest() override = default;
protected:
const CapturedSurfaceControlAPI tested_interface_;
};
INSTANTIATE_TEST_SUITE_P(
,
WebContentsObserverCscNotifiedByUpdateZoomLevelTest,
::testing::Values(CapturedSurfaceControlAPI::kIncreaseZoomLevel,
CapturedSurfaceControlAPI::kDecreaseZoomLevel,
CapturedSurfaceControlAPI::kResetZoomLevel));
TEST_P(WebContentsObserverCscNotifiedByUpdateZoomLevelTest,
NotifiedByUpdateZoomLevelIfSuccessful) {
permission_manager_->SetPermissionResult(CSCPermissionResult::kGranted);
testing::StrictMock<MockObserver> observer(capturer_->web_contents());
EXPECT_CALL(observer, OnCapturedSurfaceControl()).Times(1);
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ToZoomLevelAction(tested_interface_),
MakeCallbackExpectingResult(&run_loop, CSCResult::kSuccess,
mock_widget_input_handler_.get()));
run_loop.Run();
}
TEST_P(WebContentsObserverCscNotifiedByUpdateZoomLevelTest,
NotNotifiedByUpdateZoomLevelIfUnsuccessful) {
permission_manager_->SetPermissionResult(CSCPermissionResult::kDenied);
testing::StrictMock<MockObserver> observer(capturer_->web_contents());
EXPECT_CALL(observer, OnCapturedSurfaceControl()).Times(0);
base::RunLoop run_loop;
controller_->UpdateZoomLevel(
ToZoomLevelAction(tested_interface_),
MakeCallbackExpectingResult(&run_loop, CSCResult::kNoPermissionError,
mock_widget_input_handler_.get()));
run_loop.Run();
}
} // namespace
} // namespace content