| // 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 |