| // Copyright 2026 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/contextual_tasks/contextual_tasks_extension_handler.h" |
| |
| #include <memory> |
| #include <variant> |
| |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/values.h" |
| #include "chrome/browser/contextual_search/contextual_search_web_contents_helper.h" |
| #include "chrome/browser/contextual_tasks/contextual_tasks_ui.h" |
| #include "chrome/browser/contextual_tasks/mock_contextual_tasks_page.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/webui/searchbox/searchbox_test_utils.h" |
| #include "chrome/common/webui_url_constants.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "chrome/test/permissions/permission_request_manager_test_api.h" |
| #include "components/contextual_search/mock_contextual_search_context_controller.h" |
| #include "components/contextual_search/mock_contextual_search_session_handle.h" |
| #include "components/contextual_tasks/public/features.h" |
| #include "components/permissions/request_type.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_ui.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/omnibox_proto/chrome_aim_entry_point.pb.h" |
| |
| namespace contextual_tasks { |
| |
| using testing::_; |
| using testing::NiceMock; |
| using testing::Return; |
| |
| class ContextualTasksExtensionHandlerBrowserTestBase |
| : public InProcessBrowserTest { |
| public: |
| ContextualTasksExtensionHandlerBrowserTestBase( |
| const std::vector<base::test::FeatureRef>& enabled_features, |
| const std::vector<base::test::FeatureRef>& disabled_features) { |
| feature_list_.InitWithFeatures(enabled_features, disabled_features); |
| } |
| ~ContextualTasksExtensionHandlerBrowserTestBase() override = default; |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| |
| web_contents_ = browser()->tab_strip_model()->GetActiveWebContents(); |
| content::RenderFrameHost* rfh = web_contents_->GetPrimaryMainFrame(); |
| |
| ContextualTasksExtensionHandler::CreateForCurrentDocument(rfh); |
| handler_ = ContextualTasksExtensionHandler::GetForCurrentDocument(rfh); |
| ASSERT_NE(handler_, nullptr); |
| |
| // Bind the mock page to the handler. |
| mojo::PendingReceiver<mojom::ExtensionPageHandler> page_handler_receiver; |
| handler_->CreateExtensionPageHandler(mock_page_.BindAndGetRemote(), |
| std::move(page_handler_receiver)); |
| |
| // Bind the mock searchbox page to the handler. |
| mojo::PendingReceiver<composebox::mojom::PageHandler> composebox_receiver; |
| mojo::PendingReceiver<searchbox::mojom::PageHandler> searchbox_receiver; |
| static_cast<composebox::mojom::PageHandlerFactory*>(handler_) |
| ->CreatePageHandler(std::move(composebox_receiver), |
| mock_searchbox_page_.BindAndGetRemote(), |
| std::move(searchbox_receiver)); |
| |
| // Set up mock session handle and controller. |
| auto session_handle = std::make_unique< |
| NiceMock<contextual_search::MockContextualSearchSessionHandle>>(); |
| mock_session_handle_ = session_handle.get(); |
| |
| mock_controller_ = std::make_unique< |
| NiceMock<contextual_search::MockContextualSearchContextController>>(); |
| |
| ON_CALL(*mock_session_handle_, GetController()) |
| .WillByDefault(Return(mock_controller_.get())); |
| |
| lens::ClientToAimMessage non_empty_message; |
| non_empty_message.mutable_submit_query(); |
| ON_CALL(*mock_controller_, CreateClientToAimRequest(_)) |
| .WillByDefault(Return(non_empty_message)); |
| |
| // Set up file info for the active tab context. |
| SessionID active_tab_id = |
| sessions::SessionTabHelper::IdForTab(web_contents_); |
| contextual_search::FileInfo file_info; |
| file_info.tab_session_id = active_tab_id; |
| lens::LensOverlayRequestId request_id; |
| request_id.set_context_id(12345); |
| file_info.request_id = request_id; |
| std::vector<contextual_search::FileInfo> file_infos = {file_info}; |
| |
| ON_CALL(*mock_session_handle_, GetUploadedContextFileInfos()) |
| .WillByDefault(Return(file_infos)); |
| |
| ContextualSearchWebContentsHelper::GetOrCreateForWebContents(web_contents_) |
| ->SetTaskSession(std::nullopt, std::move(session_handle), |
| /*input_state_model=*/nullptr); |
| } |
| |
| void TearDownOnMainThread() override { |
| handler_ = nullptr; |
| mock_session_handle_ = nullptr; |
| mock_controller_.reset(); |
| web_contents_ = nullptr; |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| protected: |
| base::test::ScopedFeatureList feature_list_; |
| raw_ptr<content::WebContents> web_contents_ = nullptr; |
| raw_ptr<ContextualTasksExtensionHandler> handler_ = nullptr; |
| NiceMock<MockContextualTasksExtensionPage> mock_page_; |
| NiceMock<MockSearchboxPage> mock_searchbox_page_; |
| raw_ptr<contextual_search::MockContextualSearchSessionHandle> |
| mock_session_handle_ = nullptr; |
| std::unique_ptr<contextual_search::MockContextualSearchContextController> |
| mock_controller_; |
| }; |
| |
| class ContextualTasksExtensionHandlerBrowserTest |
| : public ContextualTasksExtensionHandlerBrowserTestBase { |
| public: |
| ContextualTasksExtensionHandlerBrowserTest() |
| : ContextualTasksExtensionHandlerBrowserTestBase( |
| {kContextualTasks, kContextualTasksRearchitecture}, |
| {}) {} |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksExtensionHandlerBrowserTest, |
| SubmitQuery) { |
| base::RunLoop run_loop; |
| |
| // We expect PostAimMessage to be called on the mock page. |
| EXPECT_CALL(mock_page_, PostAimMessage(_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| // Call SubmitQuery on the handler (which implements |
| // searchbox::mojom::PageHandler). We cast it to make sure we are calling the |
| // interface method. |
| static_cast<searchbox::mojom::PageHandler*>(handler_)->SubmitQuery( |
| "test query", 0, false, false, false, false, /*is_voice_search=*/false); |
| |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksExtensionHandlerBrowserTest, |
| GetInputState) { |
| base::RunLoop run_loop; |
| |
| static_cast<searchbox::mojom::PageHandler*>(handler_)->GetInputState( |
| base::BindLambdaForTesting( |
| [&](const std::optional<omnibox::InputState>& state) { |
| // Since we are mocking the session, we expect a valid model to be |
| // created and a default InputState to be returned (not nullopt). |
| EXPECT_TRUE(state.has_value()); |
| run_loop.Quit(); |
| })); |
| |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksExtensionHandlerBrowserTest, |
| OnPermissionPromptChangedPropagated) { |
| test::PermissionRequestManagerTestApi test_api(browser()); |
| |
| base::RunLoop show_run_loop; |
| EXPECT_CALL(mock_searchbox_page_, |
| OnPermissionPromptChanged(true, gfx::Size(0, 0))) |
| .WillOnce(base::test::RunClosure(show_run_loop.QuitClosure())); |
| |
| test_api.AddSimpleRequest(web_contents_->GetPrimaryMainFrame(), |
| permissions::RequestType::kMultipleDownloads); |
| show_run_loop.Run(); |
| |
| base::RunLoop dismiss_run_loop; |
| EXPECT_CALL(mock_searchbox_page_, |
| OnPermissionPromptChanged(false, gfx::Size(0, 0))) |
| .WillOnce(base::test::RunClosure(dismiss_run_loop.QuitClosure())); |
| |
| test_api.manager()->Dismiss(/*prompt_options=*/std::monostate()); |
| dismiss_run_loop.Run(); |
| } |
| |
| } // namespace contextual_tasks |