| // Copyright 2025 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_composebox_handler.h" |
| |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/span.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/observer_list.h" |
| #include "base/run_loop.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/metrics/user_action_tester.h" |
| #include "base/test/mock_callback.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/time/time.h" |
| #include "chrome/browser/contextual_search/contextual_search_service_factory.h" |
| #include "chrome/browser/contextual_search/contextual_search_web_contents_helper.h" |
| #include "chrome/browser/contextual_tasks/contextual_tasks_service_factory.h" |
| #include "chrome/browser/contextual_tasks/contextual_tasks_ui.h" |
| #include "chrome/browser/contextual_tasks/contextual_tasks_ui_service.h" |
| #include "chrome/browser/contextual_tasks/contextual_tasks_ui_service_factory.h" |
| #include "chrome/browser/search_engines/template_url_service_factory.h" |
| #include "chrome/browser/tab_list/tab_list_interface.h" |
| #include "chrome/browser/ui/browser_tabstrip.h" |
| #include "chrome/browser/ui/contextual_search/desktop_query_contextualizer_delegate.h" |
| #include "chrome/browser/ui/contextual_search/tab_contextualization_controller.h" |
| #include "chrome/browser/ui/lens/lens_query_flow_router.h" |
| #include "chrome/browser/ui/lens/lens_search_controller.h" |
| #include "chrome/browser/ui/tabs/public/tab_features.h" |
| #include "components/omnibox/common/omnibox_features.h" |
| #include "chrome/browser/ui/webui/searchbox/searchbox_test_utils.h" |
| #include "chrome/browser/ui/webui/webui_embedding_context.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "components/contextual_search/contextual_search_metrics_recorder.h" |
| #include "components/contextual_search/contextual_search_service.h" |
| #include "components/contextual_search/contextual_search_session_handle.h" |
| #include "components/contextual_search/contextual_search_types.h" |
| #include "components/contextual_search/fake_variations_client.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/contextual_task.h" |
| #include "components/contextual_tasks/public/contextual_tasks_service.h" |
| #include "components/contextual_tasks/public/features.h" |
| #include "components/contextual_tasks/public/mock_contextual_tasks_service.h" |
| #include "components/keyed_service/content/browser_context_dependency_manager.h" |
| #include "components/keyed_service/core/keyed_service.h" |
| #include "components/omnibox/common/composebox_features.h" |
| #include "components/sessions/content/session_tab_helper.h" |
| #include "components/tabs/public/mock_tab_interface.h" |
| #include "components/variations/scoped_variations_ids_provider.h" |
| #include "components/variations/variations_switches.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "content/public/test/test_web_ui.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h" |
| #include "services/network/test/test_url_loader_factory.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "third_party/lens_server_proto/aim_communication.pb.h" |
| #include "third_party/omnibox_proto/chrome_aim_entry_point.pb.h" |
| #include "url/gurl.h" |
| #include "url/url_constants.h" |
| |
| class BrowserWindowInterface; |
| class TemplateURLService; |
| |
| class LocalContextualSearchboxHandlerTestHarness : public InProcessBrowserTest { |
| public: |
| LocalContextualSearchboxHandlerTestHarness() = default; |
| ~LocalContextualSearchboxHandlerTestHarness() override = default; |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| |
| tabs::TabInterface* active_tab = AddTab(GURL(url::kAboutBlankURL)); |
| web_contents_ = active_tab->GetContents(); |
| |
| template_url_service_ = TemplateURLServiceFactory::GetForProfile(profile()); |
| } |
| |
| void TearDownOnMainThread() override { |
| // Safely reset pointers inside controllers to avoid dangling references |
| for (int i = 0; i < browser()->tab_strip_model()->count(); ++i) { |
| tabs::TabInterface* tab = |
| tabs::TabLookupFromWebContents::FromWebContents( |
| browser()->tab_strip_model()->GetWebContentsAt(i)) |
| ->model(); |
| if (tab && tab->GetTabFeatures()) { |
| tab->GetTabFeatures()->SetTabContextualizationControllerForTesting( |
| nullptr); |
| } |
| } |
| mock_tab_controller_ = nullptr; |
| web_contents_ = nullptr; |
| template_url_service_ = nullptr; |
| tab_features_override_ = nullptr; |
| InProcessBrowserTest::TearDownOnMainThread(); |
| } |
| |
| protected: |
| raw_ptr<content::WebContents> web_contents_ = nullptr; |
| raw_ptr<TemplateURLService> template_url_service_ = nullptr; |
| contextual_search::FakeVariationsClient fake_variations_client_; |
| raw_ptr<tabs::TabFeatures> tab_features_override_ = nullptr; |
| |
| // Mock controller kept by Setup/AddTab to set expectations |
| raw_ptr<MockTabContextualizationController> mock_tab_controller_ = nullptr; |
| |
| // Helper methods to access protected members |
| content::WebContents* web_contents() { return web_contents_; } |
| Profile* profile() { return browser()->GetProfile(); } |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory() { |
| return profile() |
| ->GetDefaultStoragePartition() |
| ->GetURLLoaderFactoryForBrowserProcess(); |
| } |
| TemplateURLService* template_url_service() { return template_url_service_; } |
| contextual_search::FakeVariationsClient* fake_variations_client() { |
| return &fake_variations_client_; |
| } |
| |
| tabs::TabInterface* AddTab(const GURL& url) { |
| chrome::AddSelectedTabWithURL(browser(), url, ui::PAGE_TRANSITION_LINK); |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| content::TestNavigationObserver navigation_observer(contents); |
| navigation_observer.Wait(); |
| |
| tabs::TabInterface* tab = |
| tabs::TabLookupFromWebContents::FromWebContents(contents)->model(); |
| tab->GetTabFeatures()->SetTabContextualizationControllerForTesting(nullptr); |
| auto mock_tab_controller = |
| std::make_unique<MockTabContextualizationController>(tab); |
| mock_tab_controller_ = mock_tab_controller.get(); |
| tab->GetTabFeatures()->SetTabContextualizationControllerForTesting( |
| std::move(mock_tab_controller)); |
| |
| return tab; |
| } |
| }; |
| |
| class MockContextualTasksUI : public ContextualTasksUI { |
| public: |
| explicit MockContextualTasksUI(content::WebUI* web_ui) |
| : ContextualTasksUI(web_ui) {} |
| ~MockContextualTasksUI() override = default; |
| |
| contextual_search::ContextualSearchSessionHandle* |
| GetOrCreateContextualSessionHandle() override { |
| return session_handle_ptr_; |
| } |
| void SetSessionHandle( |
| contextual_search::ContextualSearchSessionHandle* handle) { |
| session_handle_ptr_ = handle; |
| } |
| MOCK_METHOD(void, |
| PostAimMessage, |
| (const lens::ClientToAimMessage& message), |
| (override)); |
| MOCK_METHOD(content::WebContents*, GetWebUIWebContents, (), (override)); |
| MOCK_METHOD(const std::optional<base::Uuid>&, GetTaskId, (), (override)); |
| MOCK_METHOD(BrowserWindowInterface*, GetBrowser, (), (override)); |
| MOCK_METHOD(bool, IsLensOverlayShowing, (), (const, override)); |
| MOCK_METHOD(const GURL&, GetInnerFrameUrl, (), (const, override)); |
| MOCK_METHOD(std::unique_ptr<contextual_search::InputStateModel>, |
| TakeInputStateModel, |
| (), |
| (override)); |
| MOCK_METHOD(std::vector<int32_t>, GetRestoredTabIds, (), (override)); |
| MOCK_METHOD(bool, IsActiveTabContextSuggestionShowing, (), (const, override)); |
| MOCK_METHOD(bool, IsContextualTasksEligibleOnInit, (), (const, override)); |
| MOCK_METHOD(contextual_tasks::ContextualTasksAutoSuggestionManager*, |
| GetAutoSuggestionManager, |
| (), |
| (override)); |
| |
| private: |
| raw_ptr<contextual_search::ContextualSearchSessionHandle> |
| session_handle_ptr_ = nullptr; |
| }; |
| |
| class TestContextualTasksComposeboxHandler |
| : public ContextualTasksComposeboxHandler { |
| public: |
| using ContextualTasksComposeboxHandler::ContextualTasksComposeboxHandler; |
| |
| MOCK_METHOD(std::optional<base::UnguessableToken>, |
| GetLensOverlayToken, |
| (), |
| (override)); |
| MOCK_METHOD(LensSearchController*, |
| GetLensSearchController, |
| (), |
| (const, override)); |
| MOCK_METHOD(void, |
| OnContextUploadStatusChanged, |
| (const base::UnguessableToken& context_token, |
| lens::MimeType mime_type, |
| contextual_search::ContextUploadStatus context_upload_status, |
| const std::optional<contextual_search::ContextUploadErrorType>& |
| error_type), |
| (override)); |
| |
| protected: |
| contextual_tasks::ContextualTasksService* GetContextualTasksService() |
| override { |
| return mock_contextual_tasks_service_; |
| } |
| |
| public: |
| void SetMockContextualTasksService( |
| contextual_tasks::ContextualTasksService* contextual_tasks_service) { |
| mock_contextual_tasks_service_ = contextual_tasks_service; |
| } |
| |
| contextual_search::InputStateModel* TakeInputStateModelForTesting() { |
| return input_state_model_.get(); |
| } |
| |
| private: |
| raw_ptr<contextual_tasks::ContextualTasksService> |
| mock_contextual_tasks_service_ = nullptr; |
| }; |
| |
| class MockLensQueryFlowRouter : public lens::LensQueryFlowRouter { |
| public: |
| explicit MockLensQueryFlowRouter(LensSearchController* controller) |
| : lens::LensQueryFlowRouter(controller) {} |
| MOCK_METHOD(std::optional<base::UnguessableToken>, |
| overlay_tab_context_file_token, |
| (), |
| (const, override)); |
| }; |
| |
| class MockLensSearchController : public LensSearchController { |
| public: |
| explicit MockLensSearchController(tabs::TabInterface* tab) |
| : LensSearchController(tab) { |
| mock_router_ = |
| std::make_unique<testing::NiceMock<MockLensQueryFlowRouter>>(this); |
| } |
| ~MockLensSearchController() override = default; |
| |
| MOCK_METHOD(void, |
| OpenLensOverlay, |
| (lens::LensOverlayInvocationSource invocation_source, |
| bool should_show_csb), |
| (override)); |
| MOCK_METHOD(void, |
| CloseLensSync, |
| (lens::LensOverlayDismissalSource dismissal_source), |
| (override)); |
| MOCK_METHOD(void, |
| CloseLensAsync, |
| (lens::LensOverlayDismissalSource dismissal_source), |
| (override)); |
| MOCK_METHOD(void, |
| CloseLensAsync, |
| (lens::LensOverlayDismissalSource dismissal_source, |
| bool side_panel_already_closing), |
| (override)); |
| |
| lens::LensQueryFlowRouter* query_router() override { |
| return mock_router_.get(); |
| } |
| |
| MockLensQueryFlowRouter* mock_router() { return mock_router_.get(); } |
| |
| private: |
| std::unique_ptr<MockLensQueryFlowRouter> mock_router_; |
| }; |
| |
| class ContextualTasksComposeboxHandlerTest |
| : public LocalContextualSearchboxHandlerTestHarness { |
| public: |
| ContextualTasksComposeboxHandlerTest() |
| : ContextualTasksComposeboxHandlerTest( |
| std::map<std::string, std::string>()) {} |
| |
| explicit ContextualTasksComposeboxHandlerTest( |
| const std::map<std::string, std::string>& parameters) { |
| feature_list_.InitWithFeaturesAndParameters( |
| /*enabled_features=*/ |
| {{contextual_tasks::kContextualTasks, parameters}, |
| {contextual_tasks::kContextualTasksForceEntryPointEligibility, {}}}, |
| /*disabled_features=*/ |
| // TODO(crbug.com/452061489): Fix tests that fail when the WebUI Omnibox |
| // is enabled and then remove these two Features. |
| {omnibox::internal::kWebUIOmniboxPopup, |
| omnibox::internal::kWebUIOmniboxAimPopup}); |
| } |
| ~ContextualTasksComposeboxHandlerTest() override = default; |
| |
| void SimulateUploadStatusChanged( |
| const base::UnguessableToken& context_token, |
| lens::MimeType mime_type, |
| contextual_search::ContextUploadStatus context_upload_status, |
| const std::optional<contextual_search::ContextUploadErrorType>& |
| error_type = std::nullopt) { |
| for (auto& obs : upload_observers_) { |
| obs.OnContextUploadStatusChanged(context_token, mime_type, |
| context_upload_status, error_type); |
| } |
| } |
| |
| void PostUploadStatusChanged( |
| const base::UnguessableToken& context_token, |
| lens::MimeType mime_type, |
| contextual_search::ContextUploadStatus context_upload_status, |
| const std::optional<contextual_search::ContextUploadErrorType>& |
| error_type = std::nullopt) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce( |
| &ContextualTasksComposeboxHandlerTest::SimulateUploadStatusChanged, |
| base::Unretained(this), context_token, mime_type, |
| context_upload_status, error_type)); |
| } |
| |
| std::unique_ptr<contextual_search::InputStateModel> |
| CreateMockInputStateModel() { |
| omnibox::SearchboxConfig config; |
| auto model = std::make_unique<contextual_search::InputStateModel>( |
| *session_handle_, config, GURL(), /*is_off_the_record=*/false, |
| /*is_signed_in=*/false, |
| /*browser_identity_matches_aim_identity=*/false); |
| model->setActiveModel(omnibox::ModelMode::MODEL_MODE_GEMINI_PRO); |
| return model; |
| } |
| |
| void SetUpMockUI() { |
| web_ui_.set_web_contents(web_contents()); |
| webui::SetTabInterface(web_contents(), nullptr); |
| webui::SetBrowserWindowInterface(web_contents(), browser()); |
| |
| auto mock_controller = std::make_unique<testing::NiceMock< |
| contextual_search::MockContextualSearchContextController>>(); |
| mock_controller_ = mock_controller.get(); |
| |
| controller_weak_factory_ = std::make_unique<base::WeakPtrFactory< |
| contextual_search::ContextualSearchContextController>>( |
| mock_controller_); |
| ON_CALL(*mock_controller_, AsWeakPtr()).WillByDefault([this]() { |
| return controller_weak_factory_->GetWeakPtr(); |
| }); |
| |
| ON_CALL(*mock_controller_, AddObserver(testing::_)) |
| .WillByDefault( |
| [this](contextual_search::ContextualSearchContextController:: |
| ContextUploadStatusObserver* obs) { |
| if (!upload_observers_.HasObserver(obs)) { |
| upload_observers_.AddObserver(obs); |
| } |
| }); |
| ON_CALL(*mock_controller_, RemoveObserver(testing::_)) |
| .WillByDefault( |
| [this](contextual_search::ContextualSearchContextController:: |
| ContextUploadStatusObserver* obs) { |
| upload_observers_.RemoveObserver(obs); |
| }); |
| service_ = std::make_unique<contextual_search::ContextualSearchService>( |
| /*identity_manager=*/nullptr, url_loader_factory(), |
| template_url_service(), fake_variations_client(), |
| version_info::Channel::UNKNOWN, "en-US", |
| /*tab_validator=*/nullptr, |
| base::BindRepeating( |
| [](std::optional<size_t>, |
| base::OnceCallback<void(std::vector<std::string>)>) {})); |
| auto contextual_session_handle = service_->CreateSessionForTesting( |
| std::move(mock_controller), |
| std::make_unique<contextual_search::ContextualSearchMetricsRecorder>( |
| contextual_search::ContextualSearchSource::kContextualTasks)); |
| // Check the search content sharing settings to notify the session handle |
| // that the client is properly checking the pref value. |
| contextual_session_handle->CheckSearchContentSharingSettings( |
| profile()->GetPrefs()); |
| session_handle_ = |
| service_->GetSession(contextual_session_handle->session_id(), |
| /*invocation_source=*/std::nullopt); |
| session_handle_->CheckSearchContentSharingSettings(profile()->GetPrefs()); |
| ContextualSearchWebContentsHelper::GetOrCreateForWebContents(web_contents()) |
| ->SetTaskSession(std::nullopt, std::move(contextual_session_handle), |
| /*input_state_model=*/nullptr); |
| |
| mock_ui_ = |
| std::make_unique<testing::NiceMock<MockContextualTasksUI>>(&web_ui_); |
| mock_ui_->SetSessionHandle(session_handle_.get()); |
| ON_CALL(*mock_ui_, GetWebUIWebContents()) |
| .WillByDefault(testing::Return(web_contents())); |
| ON_CALL(*mock_ui_, GetTaskId()) |
| .WillByDefault(testing::ReturnRefOfCopy(std::optional<base::Uuid>())); |
| ON_CALL(*mock_ui_, GetBrowser()).WillByDefault(testing::Return(browser())); |
| ON_CALL(*mock_ui_, GetInnerFrameUrl()) |
| .WillByDefault(testing::ReturnRefOfCopy(GURL())); |
| ON_CALL(*mock_ui_, GetAutoSuggestionManager()) |
| .WillByDefault(testing::Return(&auto_suggestion_manager_)); |
| ON_CALL(*mock_ui_, IsActiveTabContextSuggestionShowing()) |
| .WillByDefault([this]() { |
| return auto_suggestion_manager_.GetCurrentSuggestion() != nullptr; |
| }); |
| ON_CALL(*mock_ui_, IsContextualTasksEligibleOnInit()) |
| .WillByDefault(testing::Return(true)); |
| |
| // Create mock controller directly. |
| mock_contextual_tasks_service_owner_ = std::make_unique< |
| testing::NiceMock<contextual_tasks::MockContextualTasksService>>(); |
| mock_contextual_tasks_service_ptr_ = |
| mock_contextual_tasks_service_owner_.get(); |
| } |
| |
| void SetUpHandler() { |
| handler_ = std::make_unique<TestContextualTasksComposeboxHandler>( |
| mock_ui_.get(), profile(), web_contents(), |
| mojo::PendingReceiver<composebox::mojom::PageHandler>(), |
| mojo::PendingReceiver<searchbox::mojom::PageHandler>(), |
| searchbox_page_receiver_.BindNewPipeAndPassRemote(), |
| base::BindRepeating( |
| &ContextualTasksUI::GetOrCreateContextualSessionHandle, |
| base::Unretained(mock_ui_.get())), |
| base::BindRepeating(&ContextualTasksUI::ClearContextualSessionHandle, |
| base::Unretained(mock_ui_.get())), |
| base::BindRepeating(&ContextualTasksUI::TakeInputStateModel, |
| base::Unretained(mock_ui_.get()))); |
| ON_CALL(*handler_, GetLensSearchController()) |
| .WillByDefault(testing::Return(mock_lens_controller_.get())); |
| handler_->SetMockContextualTasksService(mock_contextual_tasks_service_ptr_); |
| handler_->recontextualizer_ = |
| std::make_unique<contextual_tasks::QueryContextualizer>( |
| mock_contextual_tasks_service_ptr_, |
| handler_->desktop_delegate_.get()); |
| |
| // Default to calling the real implementation for |
| // OnContextUploadStatusChanged. |
| ON_CALL(*handler_, OnContextUploadStatusChanged(testing::_, testing::_, |
| testing::_, testing::_)) |
| .WillByDefault( |
| [handler = handler_.get()]( |
| const base::UnguessableToken& context_token, |
| lens::MimeType mime_type, |
| contextual_search::ContextUploadStatus context_upload_status, |
| const std::optional<contextual_search::ContextUploadErrorType>& |
| error_type) { |
| handler->ContextualTasksComposeboxHandler:: |
| OnContextUploadStatusChanged(context_token, mime_type, |
| context_upload_status, |
| error_type); |
| }); |
| |
| ASSERT_TRUE(mock_lens_controller_); |
| } |
| |
| void SetUpOnMainThread() override { |
| // Install override before AddTab is called in base SetUpOnMainThread. |
| lens_controller_override_ = |
| tabs::TabFeatures::GetUserDataFactoryForTesting().AddOverrideForTesting( |
| base::BindLambdaForTesting( |
| [this](tabs::TabInterface& tab) |
| -> std::unique_ptr<LensSearchController> { |
| auto mock = std::make_unique< |
| testing::NiceMock<MockLensSearchController>>(&tab); |
| this->mock_lens_controller_ = mock.get(); |
| return mock; |
| })); |
| |
| LocalContextualSearchboxHandlerTestHarness::SetUpOnMainThread(); |
| SetUpMockUI(); |
| SetUpHandler(); |
| } |
| |
| std::unique_ptr<contextual_tasks::MockContextualTasksService> |
| mock_contextual_tasks_service_owner_; |
| |
| void TearDownOnMainThread() override { |
| // Manually verify and clear expectations to avoid issues during teardown |
| // when the tab is closed and CloseLensSync is called again with kTabClosed. |
| testing::Mock::VerifyAndClearExpectations(mock_lens_controller_); |
| |
| // Reset handler first to destroy the omnibox client which observes the |
| // lens controller. |
| handler_.reset(); |
| mock_controller_ = nullptr; |
| mock_contextual_tasks_service_ptr_ = nullptr; |
| mock_tab_controller_ = nullptr; |
| mock_lens_controller_ = nullptr; |
| mock_ui_->SetSessionHandle(nullptr); |
| session_handle_.reset(); |
| service_.reset(); |
| mock_ui_.reset(); |
| LocalContextualSearchboxHandlerTestHarness::TearDownOnMainThread(); |
| } |
| |
| protected: |
| content::TestWebUI web_ui_; |
| std::unique_ptr<testing::NiceMock<MockContextualTasksUI>> mock_ui_; |
| contextual_tasks::ContextualTasksAutoSuggestionManager |
| auto_suggestion_manager_; |
| std::unique_ptr<TestContextualTasksComposeboxHandler> handler_; |
| |
| // For session management. |
| std::unique_ptr<contextual_search::ContextualSearchService> service_; |
| std::unique_ptr<contextual_search::ContextualSearchSessionHandle> |
| session_handle_; |
| raw_ptr<contextual_search::MockContextualSearchContextController> |
| mock_controller_; |
| raw_ptr<contextual_tasks::MockContextualTasksService> |
| mock_contextual_tasks_service_ptr_ = nullptr; |
| |
| raw_ptr<MockLensSearchController> mock_lens_controller_ = nullptr; |
| |
| testing::NiceMock<MockSearchboxPage> mock_searchbox_page_; |
| mojo::Receiver<searchbox::mojom::Page> searchbox_page_receiver_{ |
| &mock_searchbox_page_}; |
| |
| base::ObserverList<contextual_search::ContextualSearchContextController:: |
| ContextUploadStatusObserver> |
| upload_observers_; |
| std::unique_ptr<base::WeakPtrFactory< |
| contextual_search::ContextualSearchContextController>> |
| controller_weak_factory_; |
| |
| base::test::ScopedFeatureList feature_list_; |
| ui::UserDataFactory::ScopedOverride lens_controller_override_; |
| }; |
| |
| class ContextualTasksComposeboxHandlerTestWithAutoSuggestionDisabled |
| : public ContextualTasksComposeboxHandlerTest { |
| public: |
| ContextualTasksComposeboxHandlerTestWithAutoSuggestionDisabled() |
| : ContextualTasksComposeboxHandlerTest( |
| {{"ContextualTasksTabAutoSuggestionChipEnabled", "false"}}) {} |
| ~ContextualTasksComposeboxHandlerTestWithAutoSuggestionDisabled() override = |
| default; |
| }; |
| |
| class ContextualTasksComposeboxHandlerTestWithContextManagementEnabled |
| : public ContextualTasksComposeboxHandlerTest { |
| public: |
| ContextualTasksComposeboxHandlerTestWithContextManagementEnabled() { |
| feature_list_context_management_.InitWithFeatures( |
| {omnibox::kContextManagementInComposebox, |
| omnibox::kContextManagementInOmnibox}, |
| {}); |
| } |
| ~ContextualTasksComposeboxHandlerTestWithContextManagementEnabled() override = |
| default; |
| |
| void SetUpOnMainThread() override { |
| lens_controller_override_ = |
| tabs::TabFeatures::GetUserDataFactoryForTesting().AddOverrideForTesting( |
| base::BindLambdaForTesting( |
| [this](tabs::TabInterface& tab) |
| -> std::unique_ptr<LensSearchController> { |
| auto mock = std::make_unique< |
| testing::NiceMock<MockLensSearchController>>(&tab); |
| this->mock_lens_controller_ = mock.get(); |
| return mock; |
| })); |
| |
| LocalContextualSearchboxHandlerTestHarness::SetUpOnMainThread(); |
| SetUpMockUI(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_context_management_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, SubmitQuery) { |
| base::UserActionTester user_action_tester; |
| |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL in SubmitQuery!"; |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)); |
| EXPECT_CALL( |
| *mock_lens_controller_, |
| CloseLensSync( |
| lens::LensOverlayDismissalSource::kContextualTasksQuerySubmitted)); |
| |
| handler_->SubmitQuery("test query", 0, false, false, false, false, |
| /*is_voice_search=*/false); |
| EXPECT_EQ(session_handle_->previous_turns().back().query, "test query"); |
| EXPECT_EQ(1, user_action_tester.GetActionCount( |
| "ContextualTasks.Composebox.UserAction.QuerySubmitted")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| CloseLensOverlayFromWebUI) { |
| EXPECT_CALL(*mock_lens_controller_, |
| CloseLensAsync(lens::LensOverlayDismissalSource:: |
| kContextualTasksImageUploadsDisabled)); |
| |
| handler_->CloseLensOverlayFromWebUI( |
| composebox::mojom::LensOverlayDismissalSource:: |
| kContextualTasksImageUploadsDisabled); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage) { |
| std::string kQuery = "direct query"; |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly(testing::ReturnRefOfCopy(std::optional<base::Uuid>())); |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([&kQuery](std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_EQ(info->query_text, kQuery); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_PipesAdditionalCgiParams) { |
| std::string kQuery = "direct query"; |
| std::map<std::string, std::string> kCgiParams = { |
| {"gs_lcrp", "EgZjaHJvbWWwAgE"}, {"source", "chrome.crn.rb"}}; |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly(testing::ReturnRefOfCopy(std::optional<base::Uuid>())); |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([&kQuery, &kCgiParams]( |
| std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_EQ(info->query_text, kQuery); |
| EXPECT_EQ(info->additional_cgi_params, kCgiParams); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false, |
| kCgiParams); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_UpdatesMetricsRecorderSource) { |
| // Set the initial source of the session metrics recorder to kLens. |
| session_handle_->GetMetricsRecorder()->UpdateContextualSearchSource( |
| contextual_search::ContextualSearchSource::kLens); |
| EXPECT_EQ(session_handle_->GetMetricsRecorder()->source(), |
| contextual_search::ContextualSearchSource::kLens); |
| |
| std::string kQuery = "direct query"; |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly(testing::ReturnRefOfCopy(std::optional<base::Uuid>())); |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| |
| // The source of the metrics recorder should now be updated to |
| // kContextualTasks. |
| EXPECT_EQ(session_handle_->GetMetricsRecorder()->source(), |
| contextual_search::ContextualSearchSource::kContextualTasks); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_WithOverlayToken) { |
| std::string kQuery = "direct query"; |
| base::UnguessableToken overlay_token = base::UnguessableToken::Create(); |
| |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly(testing::ReturnRefOfCopy(std::optional<base::Uuid>())); |
| EXPECT_CALL(*handler_, GetLensOverlayToken()) |
| .WillOnce(testing::Return(overlay_token)); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([&kQuery, &overlay_token]( |
| std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_EQ(info->query_text, kQuery); |
| EXPECT_THAT(info->file_tokens, testing::Contains(overlay_token)); |
| EXPECT_TRUE(info->force_include_latest_interaction_request_data); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_NoOverlayToken) { |
| std::string kQuery = "direct query"; |
| |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly(testing::ReturnRefOfCopy(std::optional<base::Uuid>())); |
| EXPECT_CALL(*handler_, GetLensOverlayToken()) |
| .WillOnce(testing::Return(std::nullopt)); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([&kQuery](std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_EQ(info->query_text, kQuery); |
| EXPECT_FALSE(info->force_include_latest_interaction_request_data); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_RecontextualizeExpiredTab) { |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL!"; |
| std::string kQuery = "recontextualize query"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context with expired tab. |
| contextual_tasks::ContextualTask task(task_id); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| GURL kUrl("about:blank"); |
| // The default title for about:blank is "about:blank". |
| std::string kTitle = "about:blank"; |
| |
| contextual_tasks::UrlResource resource( |
| kUrl, contextual_tasks::ResourceType::kWebpage); |
| resource.title = kTitle; |
| resource.tab_id = session_id; |
| task.AddUrlResource(resource); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Setup FileInfo with expired status. |
| std::vector<raw_ptr<const contextual_search::FileInfo>> file_info_list; |
| contextual_search::FileInfo file_info; |
| file_info.tab_session_id = session_id; |
| file_info.upload_status = |
| contextual_search::ContextUploadStatus::kUploadExpired; |
| file_info.request_id.emplace(); |
| file_info.request_id->set_context_id(12345); |
| file_info_list.push_back(&file_info); |
| |
| EXPECT_CALL(*mock_controller_, GetFileInfoList()) |
| .WillRepeatedly(testing::Return(file_info_list)); |
| |
| // Expect GetPageContext call. |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillOnce([session_id]( |
| MockTabContextualizationController::GetPageContextCallback |
| callback) { |
| // Return some dummy content. |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| data->tab_session_id = session_id; |
| data->page_url = GURL("about:blank"); |
| data->page_title = "about:blank"; |
| data->context_id = 12345; |
| data->is_page_context_eligible = true; |
| std::move(callback).Run(std::move(data)); |
| }); |
| |
| // Expect StartFileUploadFlow call. |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [this](const base::UnguessableToken& file_token, |
| std::unique_ptr<lens::ContextualInputData> data, |
| std::optional<lens::ImageEncodingOptions> image_options) { |
| EXPECT_TRUE(data->is_implicit_upload); |
| PostUploadStatusChanged( |
| file_token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful); |
| }); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_RecontextualizeContentChanged) { |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL!"; |
| std::string kQuery = "recontextualize query"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context with uploaded tab (not expired). |
| contextual_tasks::ContextualTask task(task_id); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| GURL kUrl("about:blank"); |
| std::string kTitle = "about:blank"; |
| |
| contextual_tasks::UrlResource resource( |
| kUrl, contextual_tasks::ResourceType::kWebpage); |
| resource.title = kTitle; |
| resource.tab_id = session_id; |
| task.AddUrlResource(resource); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Setup context with uploaded status and some previous content. |
| std::vector<raw_ptr<const contextual_search::FileInfo>> file_info_list; |
| contextual_search::FileInfo file_info; |
| file_info.tab_session_id = session_id; |
| file_info.upload_status = |
| contextual_search::ContextUploadStatus::kUploadSuccessful; |
| file_info.request_id.emplace(); |
| file_info.request_id->set_context_id(12345); |
| |
| file_info_list.push_back(&file_info); |
| |
| EXPECT_CALL(*mock_controller_, GetFileInfoList()) |
| .WillRepeatedly(testing::Return(file_info_list)); |
| |
| // Expect GetPageContext call with NEW content. |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillOnce([session_id]( |
| MockTabContextualizationController::GetPageContextCallback |
| callback) { |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| std::string new_content = "new content"; |
| auto new_content_span = base::as_bytes(base::span(new_content)); |
| std::vector<uint8_t> new_bytes(new_content_span.begin(), |
| new_content_span.end()); |
| lens::ContextualInput new_input(std::move(new_bytes), |
| lens::MimeType::kPlainText); |
| data->context_input.emplace().push_back(std::move(new_input)); |
| data->tab_session_id = session_id; |
| data->page_url = GURL("about:blank"); |
| data->page_title = "about:blank"; |
| data->context_id = 12345; |
| data->is_page_context_eligible = true; |
| std::move(callback).Run(std::move(data)); |
| }); |
| |
| // Expect StartFileUploadFlow call because content changed. |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [this](const base::UnguessableToken& file_token, |
| std::unique_ptr<lens::ContextualInputData> data, |
| std::optional<lens::ImageEncodingOptions> image_options) { |
| EXPECT_TRUE(data->is_implicit_upload); |
| PostUploadStatusChanged( |
| file_token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful); |
| }); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_NoRecontextualizationIfUnchanged) { |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL!"; |
| std::string kQuery = "valid tab query"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context with uploaded tab |
| contextual_tasks::ContextualTask task(task_id); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| contextual_tasks::UrlResource resource( |
| GURL("about:blank"), contextual_tasks::ResourceType::kWebpage); |
| resource.title = "about:blank"; |
| resource.tab_id = session_id; |
| task.AddUrlResource(resource); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Setup FileInfo with uploaded status and SAME content. |
| std::vector<raw_ptr<const contextual_search::FileInfo>> file_info_list; |
| contextual_search::FileInfo file_info; |
| file_info.tab_session_id = session_id; |
| file_info.upload_status = |
| contextual_search::ContextUploadStatus::kUploadSuccessful; |
| file_info.request_id.emplace(); |
| file_info.request_id->set_context_id(12345); |
| |
| // Populate input_data for comparison |
| auto input_data = std::make_unique<lens::ContextualInputData>(); |
| std::string content = "same content"; |
| auto content_span = base::as_bytes(base::span(content)); |
| std::vector<uint8_t> bytes(content_span.begin(), content_span.end()); |
| lens::ContextualInput input(std::move(bytes), lens::MimeType::kPlainText); |
| input_data->context_input.emplace().push_back(std::move(input)); |
| input_data->primary_content_type = lens::MimeType::kPlainText; |
| file_info.input_data = std::move(input_data); |
| |
| file_info_list.push_back(&file_info); |
| |
| EXPECT_CALL(*mock_controller_, GetFileInfoList()) |
| .WillRepeatedly(testing::Return(file_info_list)); |
| |
| // Expect GetPageContext call with SAME content. |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillOnce([session_id]( |
| MockTabContextualizationController::GetPageContextCallback |
| callback) { |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| // Reconstruct same input. |
| std::string content = "same content"; |
| auto content_span = base::as_bytes(base::span(content)); |
| std::vector<uint8_t> bytes(content_span.begin(), content_span.end()); |
| lens::ContextualInput new_input(std::move(bytes), |
| lens::MimeType::kPlainText); |
| data->context_input.emplace().push_back(std::move(new_input)); |
| data->primary_content_type = lens::MimeType::kPlainText; |
| // Set session ID to match |
| data->tab_session_id = session_id; |
| data->page_url = GURL("about:blank"); |
| data->page_title = "about:blank"; |
| data->context_id = 12345; |
| std::move(callback).Run(std::move(data)); |
| }); |
| |
| // Expect StartFileUploadFlow call to NOT be called. |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .Times(0); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_ActiveTabNotInContext) { |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL!"; |
| std::string kQuery = "query with unrelated active tab"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context with NO tabs (or just not the active one). |
| contextual_tasks::ContextualTask task(task_id); |
| // Add a resource that is NOT the active tab. |
| contextual_tasks::UrlResource resource( |
| GURL("http://example.com"), contextual_tasks::ResourceType::kWebpage); |
| resource.tab_id = SessionID::NewUnique(); // Random ID |
| task.AddUrlResource(resource); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Expect StartFileUploadFlow to NOT be called. |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .Times(0); |
| |
| // Expect CreateClientToAimRequest IS called (query submission continues). |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_ActiveTabUrlMismatch) { |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL!"; |
| std::string kQuery = "query with url mismatch"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context with matching SessionID but mismatching URL. |
| // Active tab is at about:blank. Resource is at http://example.com. |
| contextual_tasks::ContextualTask task(task_id); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| GURL kUrl("http://example.com"); |
| std::u16string kTitle = u"Example Title"; |
| |
| contextual_tasks::UrlResource resource( |
| kUrl, contextual_tasks::ResourceType::kWebpage); |
| resource.title = base::UTF16ToUTF8(kTitle); |
| resource.tab_id = session_id; |
| task.AddUrlResource(resource); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Expect StartFileUploadFlow to NOT be called. |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .Times(0); |
| |
| // Expect CreateClientToAimRequest IS called. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| run_loop.Run(); |
| } |
| |
| // crbug.com/488112121: This test covers the temporary behavior of disabling |
| // tools when the aegc=1 URL parameter is present. Remove this test when the |
| // temporary workaround in ContextualTasksComposeboxHandler is removed. |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| AegcParameterDisablesTools) { |
| omnibox::SearchboxConfig config; |
| config.add_input_type_configs()->set_input_type( |
| omnibox::InputType::INPUT_TYPE_LENS_IMAGE); |
| config.add_input_type_configs()->set_input_type( |
| omnibox::InputType::INPUT_TYPE_LENS_FILE); |
| config.add_tool_configs()->set_tool(omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH); |
| |
| auto session_handle = |
| std::make_unique<contextual_search::MockContextualSearchSessionHandle>(); |
| auto input_state_model = std::make_unique<contextual_search::InputStateModel>( |
| *session_handle, config, GURL(), /*is_off_the_record=*/false, |
| /*is_signed_in=*/false, |
| /*browser_identity_matches_aim_identity=*/false); |
| |
| EXPECT_CALL(*mock_ui_, TakeInputStateModel()) |
| .WillOnce(testing::Return(testing::ByMove(std::move(input_state_model)))); |
| |
| GURL aegc_url("https://gemini.google.com/app?aegc=1"); |
| EXPECT_CALL(*mock_ui_, GetInnerFrameUrl()) |
| .WillRepeatedly(testing::ReturnRef(aegc_url)); |
| |
| // Re-initialize the model to pick up the URL change. |
| handler_->OnTaskChanged(); |
| |
| auto* model = handler_->TakeInputStateModelForTesting(); |
| ASSERT_TRUE(model); |
| |
| const auto& state = model->get_state_for_testing(); |
| |
| EXPECT_THAT(state.disabled_tools, |
| testing::Contains(omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH)); |
| |
| EXPECT_THAT( |
| state.disabled_input_types, |
| testing::UnorderedElementsAre(omnibox::InputType::INPUT_TYPE_LENS_FILE)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_RecontextualizeScreenshotChanged_SkBitmap) { |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL!"; |
| std::string kQuery = "recontextualize query"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context with uploaded tab. |
| contextual_tasks::ContextualTask task(task_id); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| GURL kUrl("about:blank"); |
| std::string kTitle = "about:blank"; |
| |
| contextual_tasks::UrlResource resource( |
| kUrl, contextual_tasks::ResourceType::kWebpage); |
| resource.title = kTitle; |
| resource.tab_id = session_id; |
| task.AddUrlResource(resource); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Setup FileInfo with uploaded status and OLD bitmap. |
| std::vector<raw_ptr<const contextual_search::FileInfo>> file_info_list; |
| contextual_search::FileInfo file_info; |
| file_info.tab_session_id = session_id; |
| file_info.upload_status = |
| contextual_search::ContextUploadStatus::kUploadSuccessful; |
| file_info.request_id.emplace(); |
| file_info.request_id->set_context_id(12345); |
| |
| auto input_data = std::make_unique<lens::ContextualInputData>(); |
| SkBitmap old_bitmap; |
| old_bitmap.allocN32Pixels(10, 10); |
| old_bitmap.eraseColor(SK_ColorRED); |
| input_data->viewport_screenshot = old_bitmap; |
| file_info.input_data = std::move(input_data); |
| |
| file_info_list.push_back(&file_info); |
| |
| EXPECT_CALL(*mock_controller_, GetFileInfoList()) |
| .WillRepeatedly(testing::Return(file_info_list)); |
| |
| // Expect GetPageContext call with NEW bitmap (different color). |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillOnce([session_id]( |
| MockTabContextualizationController::GetPageContextCallback |
| callback) { |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| SkBitmap new_bitmap; |
| new_bitmap.allocN32Pixels(10, 10); |
| new_bitmap.eraseColor(SK_ColorBLUE); |
| data->viewport_screenshot = new_bitmap; |
| data->tab_session_id = session_id; |
| data->page_url = GURL("about:blank"); |
| data->page_title = "about:blank"; |
| data->context_id = 12345; |
| data->is_page_context_eligible = true; |
| std::move(callback).Run(std::move(data)); |
| }); |
| |
| // Expect StartFileUploadFlow call because bitmap changed. |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [this](const base::UnguessableToken& context_token, |
| std::unique_ptr<lens::ContextualInputData> data, |
| std::optional<lens::ImageEncodingOptions> image_options) { |
| EXPECT_TRUE(data->is_implicit_upload); |
| PostUploadStatusChanged( |
| context_token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful); |
| }); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_NoRecontextualizationIfScreenshotUnchanged_SkBitmap) { |
| // Test case for no recontextualization. |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL!"; |
| std::string kQuery = "valid tab query"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context with uploaded tab |
| contextual_tasks::ContextualTask task(task_id); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| contextual_tasks::UrlResource resource( |
| GURL("about:blank"), contextual_tasks::ResourceType::kWebpage); |
| resource.title = "about:blank"; |
| resource.tab_id = session_id; |
| task.AddUrlResource(resource); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Setup FileInfo with uploaded status and SAME bitmap. |
| std::vector<raw_ptr<const contextual_search::FileInfo>> file_info_list; |
| contextual_search::FileInfo file_info; |
| file_info.tab_session_id = session_id; |
| file_info.upload_status = |
| contextual_search::ContextUploadStatus::kUploadSuccessful; |
| file_info.request_id.emplace(); |
| file_info.request_id->set_context_id(12345); |
| |
| auto input_data = std::make_unique<lens::ContextualInputData>(); |
| SkBitmap old_bitmap; |
| old_bitmap.allocN32Pixels(10, 10); |
| old_bitmap.eraseColor(SK_ColorRED); |
| input_data->viewport_screenshot = old_bitmap; |
| file_info.input_data = std::move(input_data); |
| |
| file_info_list.push_back(&file_info); |
| |
| EXPECT_CALL(*mock_controller_, GetFileInfoList()) |
| .WillRepeatedly(testing::Return(file_info_list)); |
| |
| // Expect GetPageContext call with SAME bitmap. |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillOnce([session_id]( |
| MockTabContextualizationController::GetPageContextCallback |
| callback) { |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| SkBitmap new_bitmap; |
| new_bitmap.allocN32Pixels(10, 10); |
| new_bitmap.eraseColor(SK_ColorRED); |
| data->viewport_screenshot = new_bitmap; |
| data->tab_session_id = session_id; |
| data->page_url = GURL("about:blank"); |
| data->page_title = "about:blank"; |
| data->context_id = 12345; |
| std::move(callback).Run(std::move(data)); |
| }); |
| |
| // Expect StartFileUploadFlow call to NOT be called. |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .Times(0); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| OnAutocompleteAccept) { |
| base::UserActionTester user_action_tester; |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)); |
| |
| AutocompleteMatch match; |
| handler_->GetOmniboxClientForTesting()->OnAutocompleteAccept( |
| GURL("https://www.google.com/search?q=test query"), nullptr, |
| WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, |
| AutocompleteMatchType::SEARCH_SUGGEST, base::TimeTicks::Now(), false, |
| false, u"test query", match, match); |
| |
| EXPECT_EQ(1, user_action_tester.GetActionCount( |
| "ContextualTasks.Composebox.UserAction.QuerySubmitted")); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| HandleLensButtonClick) { |
| base::UserActionTester user_action_tester; |
| base::HistogramTester histogram_tester; |
| |
| EXPECT_CALL( |
| *mock_lens_controller_, |
| OpenLensOverlay( |
| lens::LensOverlayInvocationSource::kContextualTasksComposebox, true)); |
| handler_->HandleLensButtonClick(); |
| |
| EXPECT_EQ(1, user_action_tester.GetActionCount( |
| "ContextualTasks.Composebox.UserAction.LensButtonClicked")); |
| histogram_tester.ExpectUniqueSample( |
| "ContextualTasks.Composebox.UserAction.LensButtonClicked", true, 1); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| OnAutocompleteAccept_ExtractsQuery) { |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([](std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_EQ(info->query_text, "extracted query"); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)); |
| |
| AutocompleteMatch match; |
| handler_->GetOmniboxClientForTesting()->OnAutocompleteAccept( |
| GURL("https://www.google.com/search?q=extracted%20query"), nullptr, |
| WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, |
| AutocompleteMatchType::SEARCH_SUGGEST, base::TimeTicks::Now(), false, |
| false, u"extracted query", match, match); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| OnAutocompleteAccept_NoQueryParam) { |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([](std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_EQ(info->query_text, ""); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)); |
| |
| AutocompleteMatch match; |
| handler_->GetOmniboxClientForTesting()->OnAutocompleteAccept( |
| GURL("https://www.google.com/search?other=param"), nullptr, |
| WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, |
| AutocompleteMatchType::SEARCH_SUGGEST, base::TimeTicks::Now(), false, |
| false, u"other param", match, match); |
| } |
| |
| struct ToolModeTestParam { |
| omnibox::ToolMode tool_mode; |
| }; |
| |
| class ContextualTasksComposeboxHandlerToolModeTest |
| : public ContextualTasksComposeboxHandlerTest, |
| public ::testing::WithParamInterface<ToolModeTestParam> {}; |
| |
| IN_PROC_BROWSER_TEST_P(ContextualTasksComposeboxHandlerToolModeTest, |
| SetsToolModeFlags) { |
| const auto& param = GetParam(); |
| // Setting active tool should send `exit_tool_info` to AIM webpage (on client |
| // side). |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([&](std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_TRUE(info->exit_tool_info.has_value()); |
| EXPECT_EQ(info->exit_tool_info->tool_mode, |
| omnibox::ToolMode::TOOL_MODE_UNSPECIFIED); |
| EXPECT_EQ(info->exit_tool_info->new_tool_mode, param.tool_mode); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| |
| // No server-added tool means `exitTool` is sent. |
| handler_->SetActiveToolMode(param.tool_mode, /*is_set_by_server=*/false); |
| handler_->RecordToolSelectionAction(param.tool_mode); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([&](std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_EQ(info->active_tool, param.tool_mode); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| |
| handler_->CreateAndSendQueryMessage("test query", /*is_voice_search=*/false); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| All, |
| ContextualTasksComposeboxHandlerToolModeTest, |
| ::testing::Values( |
| ToolModeTestParam{omnibox::ToolMode::TOOL_MODE_UNSPECIFIED}, |
| ToolModeTestParam{omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH}, |
| ToolModeTestParam{omnibox::ToolMode::TOOL_MODE_IMAGE_GEN}, |
| ToolModeTestParam{omnibox::ToolMode::TOOL_MODE_IMAGE_GEN_UPLOAD})); |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| SetActiveToolMode_SendsExitToolMessage) { |
| // Setting active tool should send `exit_tool_info` to AIM webpage (on client |
| // side). |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([&](std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_TRUE(info->exit_tool_info.has_value()); |
| EXPECT_EQ(info->exit_tool_info->tool_mode, |
| omnibox::ToolMode::TOOL_MODE_UNSPECIFIED); |
| EXPECT_EQ(info->exit_tool_info->new_tool_mode, |
| omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| |
| handler_->SetActiveToolMode(omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH, |
| /*is_set_by_server=*/false); |
| |
| // Clearing active tool should also send `exit_tool_info`. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([&](std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_TRUE(info->exit_tool_info.has_value()); |
| EXPECT_EQ(info->exit_tool_info->tool_mode, |
| omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH); |
| EXPECT_EQ(info->exit_tool_info->new_tool_mode, |
| omnibox::ToolMode::TOOL_MODE_UNSPECIFIED); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| |
| handler_->SetActiveToolMode(omnibox::ToolMode::TOOL_MODE_UNSPECIFIED, |
| /*is_set_by_server=*/false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTest, |
| SetActiveToolMode_SetByServer_DoesNotSendExitToolMessage) { |
| // When `is_set_by_server` is true, `exit_tool_info` message should NOT be |
| // sent. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)).Times(0); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| |
| handler_->SetActiveToolMode(omnibox::ToolMode::TOOL_MODE_DEEP_SEARCH, |
| /*is_set_by_server=*/true); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| AddTabContext_Delayed) { |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL!"; |
| std::string kQuery = "delayed tab query"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context. |
| contextual_tasks::ContextualTask task(task_id); |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| |
| // 1. Add delayed tab context. |
| int32_t tab_id = 100; |
| std::optional<base::UnguessableToken> token_opt; |
| base::MockCallback<ContextualSearchboxHandler::AddTabContextCallback> |
| callback; |
| EXPECT_CALL(callback, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| token_opt = result.value(); |
| }); |
| |
| handler_->AddTabContext(tab_id, /*delay_upload=*/true, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| callback.Get()); |
| std::vector<int32_t> selected_tab_ids = handler_->GetSelectedTabIds(); |
| EXPECT_THAT(selected_tab_ids, testing::Contains(tab_id)); |
| ASSERT_TRUE(token_opt.has_value()); |
| base::UnguessableToken token = token_opt.value(); |
| ASSERT_FALSE(token.is_empty()); |
| |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 1); |
| |
| // 2. Verify tab is added to GetTabsToUpdate (via CreateAndSendQueryMessage). |
| // We need to mock the tab handle resolution. Since we can't easily mock |
| // TabHandle::Get() for arbitrary IDs in this test harness without more setup, |
| // we will use the active tab's ID which IS set up. |
| tabs::TabInterface* active_tab = |
| TabListInterface::From(browser())->GetActiveTab(); |
| int32_t active_tab_id = active_tab->GetHandle().raw_value(); |
| |
| // Reset and try again with active tab ID. |
| std::optional<base::UnguessableToken> active_token_opt; |
| EXPECT_CALL(callback, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| active_token_opt = result.value(); |
| }); |
| handler_->AddTabContext(active_tab_id, /*delay_upload=*/true, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| callback.Get()); |
| ASSERT_TRUE(active_token_opt.has_value()); |
| base::UnguessableToken active_token = active_token_opt.value(); |
| ASSERT_FALSE(active_token.is_empty()); |
| |
| // Expect GetPageContext call for the active tab. |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillOnce([](MockTabContextualizationController::GetPageContextCallback |
| callback) { |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| data->is_page_context_eligible = true; |
| std::move(callback).Run(std::move(data)); |
| }); |
| |
| // Expect StartFileUploadFlow call. |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [this](const base::UnguessableToken& file_token, |
| std::unique_ptr<lens::ContextualInputData> data, |
| std::optional<lens::ImageEncodingOptions> image_options) { |
| // The delay-upload tab is an implicit upload because it was |
| // auto-suggested. |
| EXPECT_TRUE(data->is_implicit_upload); |
| PostUploadStatusChanged( |
| file_token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful); |
| }); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| run_loop.Run(); |
| |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| DeleteContext_Delayed) { |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL!"; |
| std::string kQuery = "delete context query"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context. |
| contextual_tasks::ContextualTask task(task_id); |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // 1. Add delayed tab context. |
| tabs::TabInterface* active_tab = |
| TabListInterface::From(browser())->GetActiveTab(); |
| int32_t active_tab_id = active_tab->GetHandle().raw_value(); |
| std::optional<base::UnguessableToken> token_opt; |
| base::MockCallback<ContextualSearchboxHandler::AddTabContextCallback> |
| callback; |
| EXPECT_CALL(callback, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| token_opt = result.value(); |
| }); |
| |
| handler_->AddTabContext(active_tab_id, /*delay_upload=*/true, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| callback.Get()); |
| ASSERT_TRUE(token_opt.has_value()); |
| base::UnguessableToken token = token_opt.value(); |
| ASSERT_FALSE(token.is_empty()); |
| |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 1); |
| |
| // 2. Delete the context. |
| handler_->DeleteContext(token, /*from_automatic_chip=*/true); |
| |
| // No stashed message since we have not submitted a query yet, |
| // nor uploaded the delayed tab yet. |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| |
| // 3. Verify StartFileUploadFlow is NOT called when submitting. |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .Times(0); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTestWithContextManagementEnabled, |
| RestoreTabIds) { |
| std::vector<int32_t> restored_tab_ids = {1, 2}; |
| EXPECT_CALL(*mock_ui_, GetRestoredTabIds()) |
| .WillOnce(testing::Return(restored_tab_ids)); |
| EXPECT_CALL(mock_searchbox_page_, SetRestoredTabIds(restored_tab_ids)) |
| .Times(1); |
| |
| SetUpHandler(); |
| searchbox_page_receiver_.FlushForTesting(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| GetSelectedTabIds) { |
| int32_t tab_id = 15; |
| base::MockCallback<ContextualSearchboxHandler::AddTabContextCallback> |
| callback; |
| handler_->AddTabContext(tab_id, /*delay_upload=*/true, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| callback.Get()); |
| |
| std::vector<int32_t> selected_tab_ids = handler_->GetSelectedTabIds(); |
| EXPECT_THAT(selected_tab_ids, testing::Contains(tab_id)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| SubmitQuery_WaitsForUpload) { |
| tabs::TabInterface* active_tab = browser()->tab_strip_model()->GetActiveTab(); |
| ASSERT_NE(active_tab, nullptr) << "No active tab found."; |
| |
| int32_t tab_handle_id = active_tab->GetHandle().raw_value(); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| |
| contextual_tasks::ContextualTask task(task_id); |
| |
| // Set mock taskID for when submit query/upload file. |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Mock getting tab's content by mocking the 2 functions |
| // that start tab uploads until barrier closure. |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillRepeatedly([session_id](auto callback) { |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| data->context_id = 123; |
| data->tab_session_id = session_id; |
| data->is_page_context_eligible = true; |
| data->page_url = GURL("https://example.com"); |
| std::move(callback).Run(std::move(data)); |
| }); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| EXPECT_CALL(*mock_contextual_tasks_service_ptr_, |
| GetContextForTask(testing::_, testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid&, |
| const std::set<contextual_tasks::ContextualTaskContextSource>&, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams>, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Expect client request is formulated. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillRepeatedly(testing::Return(lens::ClientToAimMessage())); |
| |
| // Run add tab context's callback via mock so can store token in test. |
| base::MockCallback<ContextualTasksComposeboxHandler::AddTabContextCallback> |
| callback; |
| std::optional<base::UnguessableToken> token_opt; |
| base::RunLoop run_loop; |
| EXPECT_CALL(callback, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| token_opt = result.value(); |
| run_loop.Quit(); |
| }); |
| |
| handler_->AddTabContext(tab_handle_id, /*delay_upload=*/false, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| callback.Get()); |
| run_loop.Run(); |
| |
| ASSERT_TRUE(token_opt.has_value()) << "AddTabContext failed."; |
| base::UnguessableToken token = *token_opt; |
| |
| // Mock file upload status: |
| contextual_search::FileInfo uploading_info{}; |
| uploading_info.upload_status = |
| contextual_search::ContextUploadStatus::kProcessing; |
| uploading_info.mime_type = lens::MimeType::kPdf; |
| uploading_info.tab_session_id = |
| sessions::SessionTabHelper::IdForTab(active_tab->GetContents()); |
| uploading_info.request_id = lens::LensOverlayRequestId(); |
| EXPECT_CALL(*mock_controller_, GetFileInfo(token)) |
| .WillRepeatedly(testing::Return(&uploading_info)); |
| // Do not submit request to server yet. |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| |
| handler_->SubmitQuery("Summarize the tab", 0, false, false, false, false, |
| /*is_voice_search=*/false); |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_TRUE(handler_->HasPendingQueryForTesting()); |
| |
| // Now, once file is successfully uploaded, should send request to server. |
| uploading_info.upload_status = |
| contextual_search::ContextUploadStatus::kUploadSuccessful; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| SimulateUploadStatusChanged( |
| token, lens::MimeType::kPdf, |
| contextual_search::ContextUploadStatus::kUploadSuccessful, std::nullopt); |
| |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| SubmitQuery_ImageReplacedThenOtherTerminalStates) { |
| tabs::TabInterface* active_tab = browser()->tab_strip_model()->GetActiveTab(); |
| ASSERT_NE(active_tab, nullptr) << "No active tab found."; |
| |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| contextual_tasks::ContextualTask task(task_id); |
| |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| auto file_info = searchbox::mojom::SelectedFileInfo::New(); |
| file_info->file_name = "test.pdf"; |
| file_info->mime_type = "application/pdf"; |
| file_info->is_deletable = true; |
| std::vector<uint8_t> data = {0xDE, 0xAD, 0xBE, 0xEF}; |
| mojo_base::BigBuffer file_bytes(data); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillRepeatedly(testing::Return(lens::ClientToAimMessage())); |
| |
| // Only execute part of context upload compared to past tests |
| // to verify early return's in upload callback workflow. |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL(*mock_contextual_tasks_service_ptr_, |
| GetContextForTask(testing::_, testing::_, testing::_, testing::_)) |
| .WillRepeatedly( |
| [&context]( |
| const base::Uuid&, |
| const std::set<contextual_tasks::ContextualTaskContextSource>&, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams>, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Run add file context's callback via mock so can store token in test. |
| base::MockCallback<ContextualTasksComposeboxHandler::AddFileContextCallback> |
| callback; |
| std::optional<base::UnguessableToken> current_token; |
| base::RunLoop run_loop; |
| EXPECT_CALL(callback, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| current_token = result.value(); |
| run_loop.Quit(); |
| }); |
| |
| handler_->AddFileContext(std::move(file_info), std::move(file_bytes), |
| callback.Get()); |
| run_loop.Run(); |
| |
| ASSERT_TRUE(current_token.has_value()) << "AddFileContext failed."; |
| |
| SimulateUploadStatusChanged( |
| *current_token, lens::MimeType::kImage, |
| contextual_search::ContextUploadStatus::kProcessing, std::nullopt); |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| |
| SimulateUploadStatusChanged( |
| *current_token, lens::MimeType::kImage, |
| contextual_search::ContextUploadStatus::kUploadReplaced, std::nullopt); |
| |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| |
| auto file_info_2 = searchbox::mojom::SelectedFileInfo::New(); |
| file_info_2->file_name = "test2.pdf"; |
| file_info_2->mime_type = "application/pdf"; |
| file_info_2->is_deletable = true; |
| std::vector<uint8_t> data_2 = {0xDE, 0xAD, 0xBE, 0xEF}; |
| mojo_base::BigBuffer file_bytes_2(data_2); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillRepeatedly(testing::Return(lens::ClientToAimMessage())); |
| |
| // Run add file context's callback via mock so can store token in test. |
| base::MockCallback<ContextualTasksComposeboxHandler::AddFileContextCallback> |
| callback_2; |
| std::optional<base::UnguessableToken> current_token_2; |
| base::RunLoop run_loop_2; |
| EXPECT_CALL(callback_2, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| current_token_2 = result.value(); |
| run_loop_2.Quit(); |
| }); |
| |
| handler_->AddFileContext(std::move(file_info_2), std::move(file_bytes_2), |
| callback_2.Get()); |
| run_loop_2.Run(); |
| |
| ASSERT_TRUE(current_token_2.has_value()) << "AddFileContext failed."; |
| |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| SimulateUploadStatusChanged( |
| *current_token_2, lens::MimeType::kImage, |
| contextual_search::ContextUploadStatus::kProcessing, std::nullopt); |
| |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| SimulateUploadStatusChanged( |
| *current_token_2, lens::MimeType::kImage, |
| contextual_search::ContextUploadStatus::kNotUploaded, std::nullopt); |
| |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| SimulateUploadStatusChanged( |
| *current_token_2, lens::MimeType::kImage, |
| contextual_search::ContextUploadStatus::kUploadStarted, std::nullopt); |
| |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| SimulateUploadStatusChanged( |
| *current_token_2, lens::MimeType::kImage, |
| contextual_search::ContextUploadStatus::kProcessingSuggestSignalsReady, |
| std::nullopt); |
| |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| handler_->SubmitQuery("What is this?", 0, false, false, false, false, |
| /*is_voice_search=*/false); |
| |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_TRUE(handler_->HasPendingQueryForTesting()); |
| |
| testing::Mock::VerifyAndClearExpectations(mock_ui_.get()); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| SimulateUploadStatusChanged( |
| *current_token_2, lens::MimeType::kImage, |
| contextual_search::ContextUploadStatus::kUploadExpired, std::nullopt); |
| |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| SubmitQuery_ThenDeleteToTriggerFullSubmit) { |
| tabs::TabInterface* active_tab = browser()->tab_strip_model()->GetActiveTab(); |
| ASSERT_NE(active_tab, nullptr) << "No active tab found."; |
| |
| int32_t tab_handle_id = active_tab->GetHandle().raw_value(); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| |
| contextual_tasks::ContextualTask task(task_id); |
| |
| // Set mock taskID for when submit query/upload file. |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Mock getting tab's content by mocking the 2 functions |
| // that start tab uploads until barrier closure. |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillRepeatedly([session_id](auto callback) { |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| data->context_id = 123; |
| data->tab_session_id = session_id; |
| data->is_page_context_eligible = true; |
| data->page_url = GURL("https://example.com"); |
| std::move(callback).Run(std::move(data)); |
| }); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| EXPECT_CALL(*mock_contextual_tasks_service_ptr_, |
| GetContextForTask(testing::_, testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid&, |
| const std::set<contextual_tasks::ContextualTaskContextSource>&, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams>, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Expect client request is formulated. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillRepeatedly(testing::Return(lens::ClientToAimMessage())); |
| |
| // Run add tab context's callback via mock so can store token in test. |
| base::MockCallback<ContextualTasksComposeboxHandler::AddTabContextCallback> |
| callback; |
| std::optional<base::UnguessableToken> token_opt; |
| base::RunLoop run_loop; |
| EXPECT_CALL(callback, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| token_opt = result.value(); |
| run_loop.Quit(); |
| }); |
| |
| handler_->AddTabContext(tab_handle_id, /*delay_upload=*/false, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| callback.Get()); |
| run_loop.Run(); |
| |
| ASSERT_TRUE(token_opt.has_value()) << "AddTabContext failed."; |
| base::UnguessableToken token = *token_opt; |
| |
| // Mock file upload status: |
| contextual_search::FileInfo uploading_info{}; |
| uploading_info.upload_status = |
| contextual_search::ContextUploadStatus::kProcessing; |
| uploading_info.mime_type = lens::MimeType::kPdf; |
| uploading_info.tab_session_id = |
| sessions::SessionTabHelper::IdForTab(active_tab->GetContents()); |
| uploading_info.request_id = lens::LensOverlayRequestId(); |
| EXPECT_CALL(*mock_controller_, GetFileInfo(token)) |
| .WillRepeatedly(testing::Return(&uploading_info)); |
| // Do not submit request to server yet. |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| |
| // Should stash message instead of submit. |
| handler_->SubmitQuery("Summarize the tab", 0, false, false, false, false, |
| /*is_voice_search=*/false); |
| |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_TRUE(handler_->HasPendingQueryForTesting()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 1); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| |
| // Deleting last file uploading should trigger full submit. |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| |
| handler_->DeleteContext(token, /*from_automatic_chip=*/true); |
| |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| SubmitQuery_AfterDeleteLastUploadingFile) { |
| tabs::TabInterface* active_tab = browser()->tab_strip_model()->GetActiveTab(); |
| ASSERT_NE(active_tab, nullptr) << "No active tab found."; |
| |
| int32_t tab_handle_id = active_tab->GetHandle().raw_value(); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| |
| contextual_tasks::ContextualTask task(task_id); |
| |
| // Set mock taskID for when submit query/upload file. |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Mock getting tab's content by mocking the 2 functions |
| // that start tab uploads until barrier closure. |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillRepeatedly([session_id](auto callback) { |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| data->context_id = 123; |
| data->tab_session_id = session_id; |
| data->is_page_context_eligible = true; |
| data->page_url = GURL("https://example.com"); |
| std::move(callback).Run(std::move(data)); |
| }); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| EXPECT_CALL(*mock_contextual_tasks_service_ptr_, |
| GetContextForTask(testing::_, testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid&, |
| const std::set<contextual_tasks::ContextualTaskContextSource>&, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams>, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Expect client request is formulated. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillRepeatedly(testing::Return(lens::ClientToAimMessage())); |
| |
| // Run add tab context's callback via mock so can store token in test. |
| base::MockCallback<ContextualTasksComposeboxHandler::AddTabContextCallback> |
| callback; |
| std::optional<base::UnguessableToken> token_opt; |
| base::RunLoop run_loop; |
| EXPECT_CALL(callback, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| token_opt = result.value(); |
| run_loop.Quit(); |
| }); |
| |
| handler_->AddTabContext(tab_handle_id, /*delay_upload=*/false, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| callback.Get()); |
| run_loop.Run(); |
| |
| ASSERT_TRUE(token_opt.has_value()) << "AddTabContext failed."; |
| base::UnguessableToken token = *token_opt; |
| |
| // Mock file upload status: |
| contextual_search::FileInfo uploading_info{}; |
| uploading_info.upload_status = |
| contextual_search::ContextUploadStatus::kProcessing; |
| uploading_info.mime_type = lens::MimeType::kPdf; |
| uploading_info.tab_session_id = |
| sessions::SessionTabHelper::IdForTab(active_tab->GetContents()); |
| uploading_info.request_id = lens::LensOverlayRequestId(); |
| EXPECT_CALL(*mock_controller_, GetFileInfo(token)) |
| .WillRepeatedly(testing::Return(&uploading_info)); |
| // Do not submit request to server yet. |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| |
| handler_->SubmitQuery("Summarize the tab", 0, false, false, false, false, |
| /*is_voice_search=*/false); |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_TRUE(handler_->HasPendingQueryForTesting()); |
| |
| // Now, once file is deleted, should send request to server. |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| handler_->DeleteContext(token, /*from_automatic_chip=*/true); |
| |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| SubmitQuery_WaitsForDelayedUpload) { |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is null."; |
| std::string kQuery = "recontextualize query"; |
| |
| // Setup context with uploaded tab (not expired). |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| GURL kUrl("about:blank"); |
| std::string kTitle = "about:blank"; |
| |
| contextual_tasks::UrlResource resource( |
| kUrl, contextual_tasks::ResourceType::kUnknown); |
| resource.title = kTitle; |
| resource.tab_id = session_id; |
| |
| tabs::TabInterface* active_tab = browser()->tab_strip_model()->GetActiveTab(); |
| ASSERT_NE(active_tab, nullptr) << "No active tab found!."; |
| int32_t tab_handle_id = active_tab->GetHandle().raw_value(); |
| |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| contextual_tasks::ContextualTask task(task_id); |
| task.AddUrlResource(resource); |
| |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| // Execute full context upload compared to past tests |
| // to verify full upload callback workflow. |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillRepeatedly([session_id](auto callback) { |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| data->context_id = 123; |
| data->tab_session_id = session_id; |
| data->is_page_context_eligible = true; |
| data->page_url = GURL("https://example.com"); |
| std::move(callback).Run(std::move(data)); |
| }); |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| EXPECT_CALL( |
| *mock_controller_, |
| StartFileUploadFlow( |
| testing::_, testing::A<std::unique_ptr<lens::ContextualInputData>>(), |
| testing::_)) |
| .WillOnce( |
| [this](const base::UnguessableToken& file_token, |
| std::unique_ptr<lens::ContextualInputData> data, |
| std::optional<lens::ImageEncodingOptions> image_options) { |
| PostUploadStatusChanged( |
| file_token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful); |
| }); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillRepeatedly(testing::Return(lens::ClientToAimMessage())); |
| |
| // Run add tab context's callback via mock so can store token in test. |
| base::MockCallback<ContextualTasksComposeboxHandler::AddTabContextCallback> |
| callback; |
| std::optional<base::UnguessableToken> token_opt; |
| base::RunLoop run_loop; |
| |
| EXPECT_CALL(callback, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| token_opt = result.value(); |
| run_loop.Quit(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| |
| handler_->AddTabContext(tab_handle_id, /*delay_upload=*/true, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| callback.Get()); |
| run_loop.Run(); |
| |
| ASSERT_TRUE(token_opt.has_value()) |
| << "AddTabContext failed. URL setup might be wrong."; |
| contextual_search::FileInfo uploading_info{}; |
| uploading_info.mime_type = lens::MimeType::kPdf; |
| uploading_info.upload_status = |
| contextual_search::ContextUploadStatus::kProcessing; |
| uploading_info.tab_session_id = |
| sessions::SessionTabHelper::IdForTab(active_tab->GetContents()); |
| uploading_info.request_id = lens::LensOverlayRequestId(); |
| |
| EXPECT_CALL(*mock_controller_, GetFileInfo(testing::_)) |
| .WillRepeatedly(testing::Return(&uploading_info)); |
| // Should submit when SubmitQuery run + delayed tabs finish uploading. |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 1); |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| // No pending query yet since have not submitted yet. |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| handler_->SubmitQuery("What is this?", 0, false, false, false, false, |
| /*is_voice_search=*/false); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Now the delayed tabs should have uploaded. |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| SubmitQuery_Immediately) { |
| tabs::TabInterface* active_tab = browser()->tab_strip_model()->GetActiveTab(); |
| ASSERT_NE(active_tab, nullptr) << "No active tab found."; |
| |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| contextual_tasks::ContextualTask task(task_id); |
| |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| auto file_info = searchbox::mojom::SelectedFileInfo::New(); |
| file_info->file_name = "test.pdf"; |
| file_info->mime_type = "application/pdf"; |
| file_info->is_deletable = true; |
| std::vector<uint8_t> data = {0xDE, 0xAD, 0xBE, 0xEF}; |
| mojo_base::BigBuffer file_bytes(data); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillRepeatedly(testing::Return(lens::ClientToAimMessage())); |
| |
| // Only execute part of context upload compared to past tests |
| // to verify early return's in upload callback workflow. |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| EXPECT_CALL(*mock_contextual_tasks_service_ptr_, |
| GetContextForTask(testing::_, testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid&, |
| const std::set<contextual_tasks::ContextualTaskContextSource>&, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams>, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Run add file context's callback via mock so can store token in test. |
| base::MockCallback<ContextualTasksComposeboxHandler::AddFileContextCallback> |
| callback; |
| std::optional<base::UnguessableToken> current_token; |
| base::RunLoop run_loop; |
| EXPECT_CALL(callback, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| current_token = result.value(); |
| run_loop.Quit(); |
| }); |
| |
| handler_->AddFileContext(std::move(file_info), std::move(file_bytes), |
| callback.Get()); |
| run_loop.Run(); |
| |
| ASSERT_TRUE(current_token.has_value()) << "AddFileContext failed."; |
| |
| // File is not finished uploading. |
| contextual_search::FileInfo uploading_info{}; |
| uploading_info.mime_type = lens::MimeType::kPdf; |
| uploading_info.upload_status = |
| contextual_search::ContextUploadStatus::kProcessing; |
| uploading_info.tab_session_id = |
| sessions::SessionTabHelper::IdForTab(active_tab->GetContents()); |
| uploading_info.request_id = lens::LensOverlayRequestId(); |
| EXPECT_CALL(*mock_controller_, GetFileInfo(*current_token)) |
| .WillRepeatedly(testing::Return(&uploading_info)); |
| |
| handler_->SubmitQuery("What is this?", 0, false, false, false, false, |
| /*is_voice_search=*/false); |
| |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| ASSERT_TRUE(handler_->HasPendingQueryForTesting()); |
| |
| // File is finished uploading. |
| uploading_info.upload_status = |
| contextual_search::ContextUploadStatus::kUploadSuccessful; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| SimulateUploadStatusChanged( |
| *current_token, lens::MimeType::kPdf, |
| contextual_search::ContextUploadStatus::kUploadSuccessful, std::nullopt); |
| |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| SubmitQuery_WaitsForFilesAndDelayedTabs) { |
| // Set up tabs and functions that return them. |
| tabs::TabInterface* active_tab = browser()->tab_strip_model()->GetActiveTab(); |
| ASSERT_NE(active_tab, nullptr) << "No active tab found."; |
| int32_t tab_handle_id = active_tab->GetHandle().raw_value(); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| contextual_tasks::ContextualTask task(task_id); |
| GURL kUrl("about:blank"); |
| contextual_tasks::UrlResource resource( |
| kUrl, contextual_tasks::ResourceType::kUnknown); |
| resource.title = "about:blank"; |
| resource.tab_id = session_id; |
| task.AddUrlResource(resource); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| // Execute full context upload compared to past tests |
| // to verify full upload callback workflow. |
| EXPECT_CALL(*mock_contextual_tasks_service_ptr_, |
| GetContextForTask(testing::_, testing::_, testing::_, testing::_)) |
| .WillOnce([&context](auto, auto, auto, auto callback) { |
| std::move(callback).Run(std::move(context)); |
| }); |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillRepeatedly([session_id](auto callback) { |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| data->context_id = 123; |
| data->tab_session_id = session_id; |
| data->is_page_context_eligible = true; |
| std::move(callback).Run(std::move(data)); |
| }); |
| |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .WillRepeatedly( |
| [](const base::UnguessableToken& file_token, |
| std::unique_ptr<lens::ContextualInputData> data, |
| std::optional<lens::ImageEncodingOptions> image_options) { |
| EXPECT_FALSE(data->is_implicit_upload); |
| }); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillRepeatedly(testing::Return(lens::ClientToAimMessage())); |
| |
| // Run add file context's callback via mock so can store token in test. |
| base::MockCallback<ContextualTasksComposeboxHandler::AddFileContextCallback> |
| file_cb; |
| std::optional<base::UnguessableToken> file_token_opt; |
| |
| auto file_info = searchbox::mojom::SelectedFileInfo::New(); |
| file_info->file_name = "test_file.pdf"; |
| file_info->mime_type = "application/pdf"; |
| std::vector<uint8_t> data = {0x1, 0x2}; |
| |
| base::RunLoop run_loop; |
| EXPECT_CALL(file_cb, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| file_token_opt = result.value(); |
| run_loop.Quit(); |
| }); |
| |
| handler_->AddFileContext(std::move(file_info), mojo_base::BigBuffer(data), |
| file_cb.Get()); |
| |
| run_loop.Run(); |
| |
| // Run add tab context's callback via mock so can store token in test. |
| base::MockCallback<ContextualTasksComposeboxHandler::AddTabContextCallback> |
| normal_tab_cb; |
| std::optional<base::UnguessableToken> normal_tab_token_opt; |
| |
| base::RunLoop run_loop_2; |
| EXPECT_CALL(normal_tab_cb, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| normal_tab_token_opt = result.value(); |
| run_loop_2.Quit(); |
| }); |
| |
| handler_->AddTabContext(tab_handle_id, /*delay_upload=*/false, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| normal_tab_cb.Get()); |
| |
| run_loop_2.Run(); |
| |
| // Run add tab context's callback via mock so can store token in test. |
| base::MockCallback<ContextualTasksComposeboxHandler::AddTabContextCallback> |
| delayed_tab_cb; |
| EXPECT_CALL(delayed_tab_cb, Run(testing::_)) |
| .WillOnce([&](const auto& result) { |
| // We don't store the token or quit a loop for delayed_tab_cb as it's |
| // not critical for the logic tested here, but we acknowledge the call. |
| }); |
| handler_->AddTabContext(tab_handle_id, /*delay_upload=*/true, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| delayed_tab_cb.Get()); |
| |
| ASSERT_TRUE(normal_tab_token_opt.has_value()); |
| ASSERT_TRUE(file_token_opt.has_value()); |
| |
| // Verify added context: |
| ASSERT_EQ(handler_->GetNumContextUploading(), 2); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 1); |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| |
| // Configure Mock to say Files/Normal Tabs are still "Processing". |
| contextual_search::FileInfo info_processing; |
| info_processing.upload_status = |
| contextual_search::ContextUploadStatus::kProcessing; |
| info_processing.tab_session_id = session_id; |
| info_processing.request_id = lens::LensOverlayRequestId(); |
| EXPECT_CALL(*mock_controller_, GetFileInfo(testing::_)) |
| .WillRepeatedly(testing::Return(&info_processing)); |
| |
| // Do not submit to server yet. |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| |
| testing::Mock::VerifyAndClearExpectations(mock_controller_.get()); |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillRepeatedly(testing::Return(lens::ClientToAimMessage())); |
| EXPECT_CALL(*mock_controller_, GetFileInfo(testing::_)) |
| .WillRepeatedly(testing::Return(&info_processing)); |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [this](const base::UnguessableToken& file_token, |
| std::unique_ptr<lens::ContextualInputData> data, |
| std::optional<lens::ImageEncodingOptions> image_options) { |
| PostUploadStatusChanged( |
| file_token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful); |
| }); |
| |
| handler_->SubmitQuery("Combined Test", 0, false, false, false, false, |
| /*is_voice_search=*/false); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Delayed tabs should be uploaded once submit is run. |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| |
| // Expect that message is stashed instead while normal tab/files are |
| // uploading. |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_TRUE(handler_->HasPendingQueryForTesting()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 2); |
| |
| // Explicit files and non-delayed tabs need to be manually completed, |
| // since they started uploading before the auto-completing mock was set up. |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| |
| SimulateUploadStatusChanged( |
| *normal_tab_token_opt, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful, std::nullopt); |
| SimulateUploadStatusChanged( |
| *file_token_opt, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful, std::nullopt); |
| |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| AddDeleteAdd_DelayedAndRegular_Submit) { |
| // Set up task and tabs, and mock related functions. |
| tabs::TabInterface* active_tab = browser()->tab_strip_model()->GetActiveTab(); |
| int32_t tab_handle_id = active_tab->GetHandle().raw_value(); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| contextual_tasks::ContextualTask task(task_id); |
| contextual_tasks::UrlResource resource( |
| GURL("about:blank"), contextual_tasks::ResourceType::kUnknown); |
| resource.title = "about:blank"; |
| resource.tab_id = session_id; |
| task.AddUrlResource(resource); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| // Execute full context upload compared to past tests |
| // to verify full upload callback workflow. |
| EXPECT_CALL(*mock_contextual_tasks_service_ptr_, |
| GetContextForTask(testing::_, testing::_, testing::_, testing::_)) |
| .WillRepeatedly([&context](auto, auto, auto, auto callback) { |
| std::move(callback).Run( |
| std::make_unique<contextual_tasks::ContextualTaskContext>( |
| *context)); |
| }); |
| // Capture upload tab callback to simulate delayed tab pause. |
| lens::TabContextualizationController::GetPageContextCallback |
| delayed_tab_callback; |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillRepeatedly( |
| [&](auto callback) { delayed_tab_callback = std::move(callback); }); |
| |
| std::vector<base::UnguessableToken> query_tokens; |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .WillRepeatedly( |
| [&query_tokens]( |
| const base::UnguessableToken& file_token, |
| std::unique_ptr<lens::ContextualInputData> data, |
| std::optional<lens::ImageEncodingOptions> image_options) { |
| query_tokens.push_back(file_token); |
| }); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillRepeatedly(testing::Return(lens::ClientToAimMessage())); |
| |
| base::MockCallback<ContextualTasksComposeboxHandler::AddTabContextCallback> |
| cb_d1; |
| std::optional<base::UnguessableToken> token_d1_opt; |
| base::RunLoop run_loop_d1; |
| |
| // Run add tab context's callback via mock so can store token in test. |
| EXPECT_CALL(cb_d1, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| token_d1_opt = result.value(); |
| run_loop_d1.Quit(); |
| }); |
| |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| |
| handler_->AddTabContext(tab_handle_id, /*delay_upload=*/true, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| cb_d1.Get()); |
| run_loop_d1.Run(); |
| |
| ASSERT_TRUE(token_d1_opt.has_value()); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 1); |
| |
| // Delete Delayed Tab #1. |
| handler_->DeleteContext(*token_d1_opt, /*from_automatic_chip=*/true); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| |
| // Add Delayed Tab #2 (which we will not delete). |
| base::MockCallback<ContextualTasksComposeboxHandler::AddTabContextCallback> |
| cb_d2; |
| std::optional<base::UnguessableToken> token_d2_opt; |
| base::RunLoop run_loop_d2; |
| |
| // Run add tab context's callback via mock so can store token in test. |
| EXPECT_CALL(cb_d2, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| token_d2_opt = result.value(); |
| run_loop_d2.Quit(); |
| }); |
| |
| handler_->AddTabContext(tab_handle_id, /*delay_upload=*/true, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| cb_d2.Get()); |
| run_loop_d2.Run(); |
| |
| ASSERT_TRUE(token_d2_opt.has_value()); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 1); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| |
| // Add regular tab A (which we will delete). |
| base::MockCallback<ContextualTasksComposeboxHandler::AddTabContextCallback> |
| cb_rA; |
| std::optional<base::UnguessableToken> token_rA_opt; |
| base::RunLoop run_loop_rA; |
| |
| // Run add tab context's callback via mock so can store token in test. |
| EXPECT_CALL(cb_rA, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| token_rA_opt = result.value(); |
| run_loop_rA.Quit(); |
| }); |
| |
| handler_->AddTabContext(tab_handle_id, /*delay_upload=*/false, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| cb_rA.Get()); |
| run_loop_rA.Run(); |
| |
| ASSERT_TRUE(token_rA_opt.has_value()); |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 1); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 1); |
| |
| handler_->DeleteContext(*token_rA_opt, /*from_automatic_chip=*/false); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 1); |
| |
| // Add regular tab B (which we will keep). |
| base::MockCallback<ContextualTasksComposeboxHandler::AddTabContextCallback> |
| cb_rB; |
| std::optional<base::UnguessableToken> token_rB_opt; |
| base::RunLoop run_loop_rB; |
| |
| // Run add tab context's callback via mock so can store token in test. |
| EXPECT_CALL(cb_rB, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| token_rB_opt = result.value(); |
| run_loop_rB.Quit(); |
| }); |
| |
| handler_->AddTabContext(tab_handle_id, /*delay_upload=*/false, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| cb_rB.Get()); |
| run_loop_rB.Run(); |
| |
| ASSERT_TRUE(token_rB_opt.has_value()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 1); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 1); |
| |
| contextual_search::FileInfo file_info_rB; |
| file_info_rB.upload_status = |
| contextual_search::ContextUploadStatus::kProcessing; |
| file_info_rB.tab_session_id = session_id; |
| file_info_rB.request_id = lens::LensOverlayRequestId(); |
| EXPECT_CALL(*mock_controller_, GetFileInfo(testing::_)) |
| .WillRepeatedly(testing::Return(&file_info_rB)); |
| |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| handler_->SubmitQuery("Stress Test", 0, false, false, false, false, |
| /*is_voice_search=*/false); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Delayed tab #2 finishes uploading. |
| ASSERT_TRUE(!delayed_tab_callback.is_null()); |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| data->is_page_context_eligible = true; |
| std::move(delayed_tab_callback).Run(std::move(data)); |
| base::RunLoop().RunUntilIdle(); |
| |
| // Now manually complete the UploadTracker's pending items. |
| for (const auto& token : query_tokens) { |
| SimulateUploadStatusChanged( |
| token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful); |
| } |
| base::RunLoop().RunUntilIdle(); |
| |
| // Verify that still uploading. |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 1); |
| |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_TRUE(handler_->HasPendingQueryForTesting()); |
| |
| // Finish uploading file B. |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| SimulateUploadStatusChanged( |
| *token_rB_opt, lens::MimeType::kHtml, |
| contextual_search::ContextUploadStatus::kUploadSuccessful, std::nullopt); |
| |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_WithVisualSelection) { |
| std::string kQuery = "overlay query"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| base::UnguessableToken overlay_token = base::UnguessableToken::Create(); |
| |
| // Set task ID so we enter the relevant if block. |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Mock GetLensOverlayToken to return a token. |
| EXPECT_CALL(*handler_, GetLensOverlayToken()) |
| .WillOnce(testing::Return(overlay_token)); |
| |
| // Expect CloseLensSync to be called. |
| EXPECT_CALL( |
| *mock_lens_controller_, |
| CloseLensSync( |
| lens::LensOverlayDismissalSource::kContextualTasksQuerySubmitted)); |
| |
| // Expect GetContextForTask NOT to be called (recontextualization skipped). |
| EXPECT_CALL(*mock_contextual_tasks_service_ptr_, |
| GetContextForTask(testing::_, testing::_, testing::_, testing::_)) |
| .Times(0); |
| |
| // Expect CreateClientToAimRequest IS called (immediate submission). |
| // Verify overlay token is included. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([&](std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_EQ(info->query_text, kQuery); |
| EXPECT_THAT(info->file_tokens, testing::Contains(overlay_token)); |
| EXPECT_TRUE(info->force_include_latest_interaction_request_data); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_WithVisualSelection_AndUpload) { |
| std::string kQuery = "overlay query with upload"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| base::UnguessableToken overlay_token = base::UnguessableToken::Create(); |
| |
| // Setup an upload to make IsAnyContextUploading() true. |
| auto file_info = searchbox::mojom::SelectedFileInfo::New(); |
| file_info->file_name = "test.pdf"; |
| file_info->mime_type = "application/pdf"; |
| std::vector<uint8_t> data = {0x1}; |
| |
| // Create a pending upload |
| base::MockCallback<ContextualTasksComposeboxHandler::AddFileContextCallback> |
| callback; |
| base::RunLoop run_loop; |
| base::UnguessableToken file_token; |
| EXPECT_CALL(callback, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| file_token = result.value(); |
| run_loop.Quit(); |
| }); |
| |
| handler_->AddFileContext(std::move(file_info), mojo_base::BigBuffer(data), |
| callback.Get()); |
| run_loop.Run(); |
| |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| EXPECT_CALL(*handler_, GetLensOverlayToken()) |
| .WillOnce(testing::Return(overlay_token)); |
| |
| EXPECT_CALL( |
| *mock_lens_controller_, |
| CloseLensSync( |
| lens::LensOverlayDismissalSource::kContextualTasksQuerySubmitted)); |
| |
| // Expect GetContextForTask TO be called because an upload is in progress. |
| contextual_tasks::ContextualTask task(task_id); |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL(*mock_contextual_tasks_service_ptr_, |
| GetContextForTask(task_id, testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // CreateClientToAimRequest should NOT be called during |
| // CreateAndSendQueryMessage because context is uploading. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)).Times(0); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| |
| EXPECT_TRUE(handler_->HasPendingQueryForTesting()); |
| |
| // Now complete the upload and verify that CreateClientToAimRequest is called. |
| testing::Mock::VerifyAndClearExpectations(mock_controller_.get()); |
| testing::Mock::VerifyAndClearExpectations(mock_ui_.get()); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([&](std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_EQ(info->query_text, kQuery); |
| EXPECT_THAT(info->file_tokens, testing::Contains(overlay_token)); |
| EXPECT_TRUE(info->force_include_latest_interaction_request_data); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| |
| SimulateUploadStatusChanged( |
| file_token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful, std::nullopt); |
| |
| EXPECT_FALSE(handler_->HasPendingQueryForTesting()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_WithVisualSelection_AndUploadedTokens) { |
| std::string kQuery = "overlay query with tokens"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| base::UnguessableToken overlay_token = base::UnguessableToken::Create(); |
| |
| // Set task ID. |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Set up uploaded tokens in the session handle used by the handler. |
| session_handle_->CreateContextToken(); |
| |
| // Mock GetLensOverlayToken. |
| EXPECT_CALL(*handler_, GetLensOverlayToken()) |
| .WillOnce(testing::Return(overlay_token)); |
| |
| // Expect CloseLensSync. |
| EXPECT_CALL( |
| *mock_lens_controller_, |
| CloseLensSync( |
| lens::LensOverlayDismissalSource::kContextualTasksQuerySubmitted)); |
| |
| // Expect GetContextForTask TO BE CALLED. |
| contextual_tasks::ContextualTask task(task_id); |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL(*mock_contextual_tasks_service_ptr_, |
| GetContextForTask(task_id, testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Expect CreateClientToAimRequest to be called eventually. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([&](std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_EQ(info->query_text, kQuery); |
| EXPECT_EQ(info->file_tokens.size(), 2ul); |
| EXPECT_THAT(info->file_tokens, testing::Contains(overlay_token)); |
| EXPECT_TRUE(info->force_include_latest_interaction_request_data); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| CreateAndSendQueryMessage_NoVisualSelection) { |
| std::string kQuery = "normal query"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| // Token that exists but should not be used. |
| base::UnguessableToken overlay_token = base::UnguessableToken::Create(); |
| |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Mock GetLensOverlayToken to return nullopt. |
| EXPECT_CALL(*handler_, GetLensOverlayToken()) |
| .WillOnce(testing::Return(std::nullopt)); |
| |
| // Expect CloseLensSync to be called (it's always called). |
| EXPECT_CALL( |
| *mock_lens_controller_, |
| CloseLensSync( |
| lens::LensOverlayDismissalSource::kContextualTasksQuerySubmitted)); |
| |
| // Expect GetContextForTask TO be called. |
| contextual_tasks::ContextualTask task(task_id); |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| EXPECT_CALL(*mock_contextual_tasks_service_ptr_, |
| GetContextForTask(task_id, testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // The test returns a context with no matching attachments to the active tab, |
| // so it will proceed to submission immediately. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce([&](std::unique_ptr< |
| contextual_search::ContextualSearchContextController:: |
| CreateClientToAimRequestInfo> info) { |
| EXPECT_EQ(info->query_text, kQuery); |
| // Verify overlay token is NOT included. |
| EXPECT_THAT(info->file_tokens, |
| testing::Not(testing::Contains(overlay_token))); |
| EXPECT_FALSE(info->force_include_latest_interaction_request_data); |
| return lens::ClientToAimMessage(); |
| }); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| ClearFiles_Delayed) { |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL!"; |
| std::string kQuery = "clear files query"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context. |
| contextual_tasks::ContextualTask task(task_id); |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // 1. Add delayed tab context. |
| tabs::TabInterface* active_tab = |
| TabListInterface::From(browser())->GetActiveTab(); |
| int32_t active_tab_id = active_tab->GetHandle().raw_value(); |
| std::optional<base::UnguessableToken> token_opt; |
| base::MockCallback<ContextualSearchboxHandler::AddTabContextCallback> |
| callback; |
| EXPECT_CALL(callback, Run(testing::_)).WillOnce([&](const auto& result) { |
| ASSERT_TRUE(result.has_value()); |
| token_opt = result.value(); |
| }); |
| |
| handler_->AddTabContext(active_tab_id, /*delay_upload=*/true, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| callback.Get()); |
| ASSERT_TRUE(token_opt.has_value()); |
| base::UnguessableToken token = token_opt.value(); |
| ASSERT_FALSE(token.is_empty()); |
| |
| // 2. Clear files. |
| handler_->ClearFiles(/*should_block_auto_suggested_tabs=*/false); |
| |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| ASSERT_EQ(handler_->GetNumTabsDelayed(), 0); |
| |
| // 3. Verify StartFileUploadFlow is NOT called. |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .Times(0); |
| |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| run_loop.Run(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| ClearFiles_BlockAutoSuggestedTabs) { |
| GURL url("https://example.com"); |
| auto create_tab_info = [&]() { |
| auto info = std::make_unique<contextual_tasks::SuggestedTabInfo>(); |
| info->url = url; |
| info->title = u"Example"; |
| return info; |
| }; |
| |
| // 1. Initially, the suggestion should be allowed. |
| EXPECT_CALL(mock_searchbox_page_, |
| UpdateAutoSuggestedTabContext(testing::_, testing::_)) |
| .WillOnce([&](const searchbox::mojom::TabInfoPtr& received_info, |
| const std::optional<std::string>& invocation_source) { |
| EXPECT_TRUE(!received_info.is_null()) |
| << "Expected a non-null pointer for received_info."; |
| }); |
| |
| auto_suggestion_manager_.SetCurrentSuggestion(create_tab_info()); |
| handler_->UpdateSuggestedTabContext( |
| auto_suggestion_manager_.GetCurrentSuggestion()); |
| |
| searchbox_page_receiver_.FlushForTesting(); |
| EXPECT_TRUE(mock_ui_->IsActiveTabContextSuggestionShowing()); |
| |
| // 2. Blocklist the URL by clearing the files. |
| handler_->ClearFiles(/*should_block_auto_suggested_tabs=*/true); |
| |
| searchbox_page_receiver_.FlushForTesting(); |
| EXPECT_FALSE(mock_ui_->IsActiveTabContextSuggestionShowing()); |
| |
| // 3. Simulate a title change - tab context should still be filtered out. |
| EXPECT_CALL(mock_searchbox_page_, |
| UpdateAutoSuggestedTabContext(testing::_, testing::_)) |
| .WillOnce([&](const searchbox::mojom::TabInfoPtr& received_info, |
| const std::optional<std::string>& invocation_source) { |
| EXPECT_TRUE(received_info.is_null()) |
| << "Expected a null pointer for received_info."; |
| }); |
| auto_suggestion_manager_.SetCurrentSuggestion(create_tab_info()); |
| handler_->UpdateSuggestedTabContext( |
| auto_suggestion_manager_.GetCurrentSuggestion()); |
| |
| searchbox_page_receiver_.FlushForTesting(); |
| EXPECT_FALSE(mock_ui_->IsActiveTabContextSuggestionShowing()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTestWithAutoSuggestionDisabled, |
| UpdateSuggestedTabContext_ForceAllowWhenUploadedViaLens) { |
| GURL url("https://example.com"); |
| AddTab(url); |
| auto create_tab_info = [&]() { |
| auto info = std::make_unique<contextual_tasks::SuggestedTabInfo>(); |
| info->url = url; |
| info->title = u"Example"; |
| info->tab_id = TabListInterface::From(browser()) |
| ->GetActiveTab() |
| ->GetHandle() |
| .raw_value(); |
| return info; |
| }; |
| |
| // 2. Set the bool flag on the session handle! |
| session_handle_->set_is_contextual_lens_session(true); |
| |
| // 3. Expect the suggestion IS ALLOWED despite the feature flag being |
| // disabled, because dynamic enabling sees the session bool! |
| EXPECT_CALL(mock_searchbox_page_, |
| UpdateAutoSuggestedTabContext(testing::_, testing::_)) |
| .WillOnce([&](const searchbox::mojom::TabInfoPtr& received_info, |
| const std::optional<std::string>& invocation_source) { |
| EXPECT_TRUE(!received_info.is_null()) |
| << "Expected a non-null pointer because session was contextual."; |
| }); |
| |
| auto_suggestion_manager_.SetCurrentSuggestion(create_tab_info()); |
| handler_->UpdateSuggestedTabContext( |
| auto_suggestion_manager_.GetCurrentSuggestion()); |
| |
| searchbox_page_receiver_.FlushForTesting(); |
| EXPECT_TRUE(mock_ui_->IsActiveTabContextSuggestionShowing()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTestWithAutoSuggestionDisabled, |
| UpdateSuggestedTabContext_NoForceAllowWhenVisualQuery) { |
| GURL url("https://example.com"); |
| AddTab(url); |
| auto create_tab_info = [&]() { |
| auto info = std::make_unique<contextual_tasks::SuggestedTabInfo>(); |
| info->url = url; |
| info->title = u"Example"; |
| info->tab_id = TabListInterface::From(browser()) |
| ->GetActiveTab() |
| ->GetHandle() |
| .raw_value(); |
| return info; |
| }; |
| |
| // 2. Keep the bool flag on the session handle as false! |
| session_handle_->set_is_contextual_lens_session(false); |
| |
| // 3. Expect the suggestion IS NOT ALLOWED because it wasn't a text query. |
| EXPECT_CALL(mock_searchbox_page_, |
| UpdateAutoSuggestedTabContext(testing::_, testing::_)) |
| .WillOnce([&](const searchbox::mojom::TabInfoPtr& received_info, |
| const std::optional<std::string>& invocation_source) { |
| EXPECT_TRUE(received_info.is_null()) |
| << "Expected a null pointer because it was a visual query."; |
| }); |
| |
| auto_suggestion_manager_.SetCurrentSuggestion(create_tab_info()); |
| handler_->UpdateSuggestedTabContext( |
| auto_suggestion_manager_.GetCurrentSuggestion()); |
| |
| searchbox_page_receiver_.FlushForTesting(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| UpdateSuggestedTabContext) { |
| GURL url("https://example.com"); |
| auto create_tab_info = [&]() { |
| auto info = std::make_unique<contextual_tasks::SuggestedTabInfo>(); |
| info->url = url; |
| info->title = u"Example"; |
| return info; |
| }; |
| |
| // 1. Initially, the suggestion should be allowed. |
| EXPECT_CALL(mock_searchbox_page_, |
| UpdateAutoSuggestedTabContext(testing::_, testing::_)) |
| .WillOnce([&](const searchbox::mojom::TabInfoPtr& received_info, |
| const std::optional<std::string>& invocation_source) { |
| EXPECT_TRUE(!received_info.is_null()) |
| << "Expected a non-null pointer for received_info."; |
| }); |
| |
| auto_suggestion_manager_.SetCurrentSuggestion(create_tab_info()); |
| handler_->UpdateSuggestedTabContext( |
| auto_suggestion_manager_.GetCurrentSuggestion()); |
| |
| searchbox_page_receiver_.FlushForTesting(); |
| EXPECT_TRUE(mock_ui_->IsActiveTabContextSuggestionShowing()); |
| // 2. Blocklist the URL by dismissing an automatic chip. |
| // We need to navigate the active tab to the URL being blocklisted. |
| AddTab(url); |
| |
| handler_->DeleteContext(base::UnguessableToken::Create(), |
| /*from_automatic_chip=*/true); |
| // 3. Now the suggestion should be filtered out. |
| EXPECT_CALL(mock_searchbox_page_, |
| UpdateAutoSuggestedTabContext(testing::_, testing::_)) |
| .WillOnce([&](const searchbox::mojom::TabInfoPtr& received_info, |
| const std::optional<std::string>& invocation_source) { |
| EXPECT_TRUE(received_info.is_null()) |
| << "Expected a null pointer for received_info."; |
| }); |
| |
| auto_suggestion_manager_.SetCurrentSuggestion(create_tab_info()); |
| handler_->UpdateSuggestedTabContext( |
| auto_suggestion_manager_.GetCurrentSuggestion()); |
| |
| searchbox_page_receiver_.FlushForTesting(); |
| EXPECT_FALSE(mock_ui_->IsActiveTabContextSuggestionShowing()); |
| // 4. Explicitly adding the tab should remove it from the blocklist. |
| { |
| base::HistogramTester histogram_tester; |
| base::UserActionTester user_action_tester; |
| tabs::TabInterface* active_tab = |
| TabListInterface::From(browser())->GetActiveTab(); |
| int32_t active_tab_id = active_tab->GetHandle().raw_value(); |
| handler_->AddTabContext(active_tab_id, /*delay_upload=*/false, |
| searchbox::mojom::TabAttachmentSource::kContextMenu, |
| base::DoNothing()); |
| histogram_tester.ExpectTotalCount( |
| "ContextualTasks.Composebox.UserAction." |
| "AddedActiveTabAfterDeletingAutoSuggestion", |
| 1); |
| EXPECT_EQ(user_action_tester.GetActionCount( |
| "ContextualTasks.Composebox.UserAction." |
| "AddedActiveTabAfterDeletingAutoSuggestion"), |
| 1); |
| } |
| |
| // 5. The suggestion should be allowed again. |
| EXPECT_CALL(mock_searchbox_page_, |
| UpdateAutoSuggestedTabContext(testing::_, testing::_)) |
| .WillOnce([&](const searchbox::mojom::TabInfoPtr& received_info, |
| const std::optional<std::string>& invocation_source) { |
| EXPECT_TRUE(!received_info.is_null()) |
| << "Expected a non-null pointer for received_info."; |
| EXPECT_EQ(received_info->url, url); |
| }); |
| auto_suggestion_manager_.SetCurrentSuggestion(create_tab_info()); |
| handler_->UpdateSuggestedTabContext( |
| auto_suggestion_manager_.GetCurrentSuggestion()); |
| |
| searchbox_page_receiver_.FlushForTesting(); |
| EXPECT_TRUE(mock_ui_->IsActiveTabContextSuggestionShowing()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| AddFileContext_NullSessionHandle) { |
| // Create a handler with a callback that returns nullptr for session handle. |
| mojo::PendingRemote<searchbox::mojom::Page> searchbox_page_remote; |
| mojo::PendingReceiver<searchbox::mojom::Page> searchbox_page_receiver = |
| searchbox_page_remote.InitWithNewPipeAndPassReceiver(); |
| |
| auto handler = std::make_unique<TestContextualTasksComposeboxHandler>( |
| mock_ui_.get(), profile(), web_contents(), |
| mojo::PendingReceiver<composebox::mojom::PageHandler>(), |
| mojo::PendingReceiver<searchbox::mojom::PageHandler>(), |
| std::move(searchbox_page_remote), |
| base::BindRepeating( |
| []() -> contextual_search::ContextualSearchSessionHandle* { |
| return nullptr; |
| }), |
| base::DoNothing(), |
| base::BindRepeating(&ContextualTasksUI::TakeInputStateModel, |
| base::Unretained(mock_ui_.get()))); |
| auto file_info = searchbox::mojom::SelectedFileInfo::New(); |
| std::vector<uint8_t> data = {0x1}; |
| mojo_base::BigBuffer file_bytes(data); |
| |
| base::MockCallback<ContextualTasksComposeboxHandler::AddFileContextCallback> |
| callback; |
| base::expected<base::UnguessableToken, |
| contextual_search::ContextUploadErrorType> |
| result; |
| |
| EXPECT_CALL(callback, Run(testing::_)).WillOnce(testing::SaveArg<0>(&result)); |
| |
| handler->AddFileContext(std::move(file_info), std::move(file_bytes), |
| callback.Get()); |
| |
| EXPECT_FALSE(result.has_value()); |
| EXPECT_EQ(result.error(), |
| contextual_search::ContextUploadErrorType::kBrowserProcessingError); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| OnContextUploadStatusChanged_LensOverlayToken_Ignored) { |
| base::UnguessableToken lens_token = base::UnguessableToken::Create(); |
| |
| EXPECT_CALL(*mock_lens_controller_->mock_router(), |
| overlay_tab_context_file_token()) |
| .WillRepeatedly(testing::Return(lens_token)); |
| EXPECT_CALL(mock_searchbox_page_, OnContextualInputStatusChanged( |
| testing::_, testing::_, testing::_)) |
| .Times(0); |
| |
| handler_->OnContextUploadStatusChanged( |
| lens_token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful, std::nullopt); |
| |
| // Verify that for a different token, it IS called. |
| base::UnguessableToken other_token = base::UnguessableToken::Create(); |
| EXPECT_CALL(mock_searchbox_page_, OnContextualInputStatusChanged( |
| testing::_, testing::_, testing::_)) |
| .Times(1); |
| |
| handler_->OnContextUploadStatusChanged( |
| other_token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful, std::nullopt); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| ActiveModelIsPassed) { |
| // 1. Arrange: Setup a mock callback to simulate ContextualTasksUI returning a |
| // model. We explicitly set a distinct state (MODEL_MODE_GEMINI_PRO) to verify |
| // it gets passed correctly. |
| auto mock_callback = base::BindRepeating( |
| &ContextualTasksComposeboxHandlerTest::CreateMockInputStateModel, |
| base::Unretained(this)); |
| mojo::PendingRemote<searchbox::mojom::Page> searchbox_page_remote; |
| mojo::PendingReceiver<searchbox::mojom::Page> searchbox_page_receiver = |
| searchbox_page_remote.InitWithNewPipeAndPassReceiver(); |
| auto custom_handler = std::make_unique<TestContextualTasksComposeboxHandler>( |
| mock_ui_.get(), profile(), web_contents(), |
| mojo::PendingReceiver<composebox::mojom::PageHandler>(), |
| mojo::PendingReceiver<searchbox::mojom::PageHandler>(), |
| std::move(searchbox_page_remote), |
| base::BindRepeating( |
| &ContextualTasksUI::GetOrCreateContextualSessionHandle, |
| base::Unretained(mock_ui_.get())), |
| base::BindRepeating(&ContextualTasksUI::ClearContextualSessionHandle, |
| base::Unretained(mock_ui_.get())), |
| std::move(mock_callback)); |
| |
| // 2. Act: Trigger the handler to fetch the model via the callback. |
| custom_handler->InitializeInputStateModel(); |
| |
| // 3. Assert: Verify the handler successfully took ownership of the model |
| // and the internal state matches exactly what the callback provided. |
| contextual_search::InputStateModel* handler_model = |
| custom_handler->TakeInputStateModelForTesting(); |
| |
| ASSERT_NE(handler_model, nullptr); |
| EXPECT_EQ(handler_model->get_state_for_testing().active_model, |
| omnibox::ModelMode::MODEL_MODE_GEMINI_PRO); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| SuggestInputsCallbackWorks) { |
| auto mock_session = |
| std::make_unique<contextual_search::MockContextualSearchSessionHandle>(); |
| |
| lens::proto::LensOverlaySuggestInputs suggest_inputs; |
| |
| EXPECT_CALL(*mock_session, GetSuggestInputs()) |
| .WillRepeatedly(testing::Return(suggest_inputs)); |
| |
| auto mock_session_ptr = mock_session.get(); |
| |
| auto mock_get_session_callback = base::BindRepeating( |
| [](contextual_search::MockContextualSearchSessionHandle* ptr) |
| -> contextual_search::ContextualSearchSessionHandle* { return ptr; }, |
| mock_session_ptr); |
| mojo::PendingRemote<searchbox::mojom::Page> searchbox_page_remote; |
| mojo::PendingReceiver<searchbox::mojom::Page> searchbox_page_receiver = |
| searchbox_page_remote.InitWithNewPipeAndPassReceiver(); |
| |
| auto custom_handler = std::make_unique<TestContextualTasksComposeboxHandler>( |
| mock_ui_.get(), profile(), web_contents(), |
| mojo::PendingReceiver<composebox::mojom::PageHandler>(), |
| mojo::PendingReceiver<searchbox::mojom::PageHandler>(), |
| std::move(searchbox_page_remote), mock_get_session_callback, |
| base::BindRepeating(&ContextualTasksUI::ClearContextualSessionHandle, |
| base::Unretained(mock_ui_.get())), |
| base::BindRepeating(&ContextualTasksUI::TakeInputStateModel, |
| base::Unretained(mock_ui_.get()))); |
| |
| auto* client = static_cast<ContextualOmniboxClient*>( |
| custom_handler->GetOmniboxClientForTesting()); |
| |
| auto result = client->GetLensOverlaySuggestInputsForTesting(); |
| |
| ASSERT_TRUE(result.has_value()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| OnLensThumbnailCreated_TriggersUploadStatusChanges) { |
| // Setup: mock the overlay token. |
| base::UnguessableToken overlay_token = base::UnguessableToken::Create(); |
| EXPECT_CALL(*mock_lens_controller_->mock_router(), |
| overlay_tab_context_file_token()) |
| .WillRepeatedly(testing::Return(overlay_token)); |
| |
| // Mock OnContextUploadStatusChanged to verify calls and forward to real |
| // implementation. |
| // We capture the uploaded tokens to verify them. |
| std::vector<base::UnguessableToken> successful_uploads; |
| std::vector<base::UnguessableToken> replaced_uploads; |
| |
| EXPECT_CALL(*handler_, OnContextUploadStatusChanged(testing::_, testing::_, |
| testing::_, testing::_)) |
| .WillRepeatedly([&](const base::UnguessableToken& context_token, |
| lens::MimeType mime_type, |
| contextual_search::ContextUploadStatus |
| context_upload_status, |
| const std::optional< |
| contextual_search::ContextUploadErrorType>& |
| error_type) { |
| if (context_upload_status == |
| contextual_search::ContextUploadStatus::kUploadSuccessful) { |
| successful_uploads.push_back(context_token); |
| } else if (context_upload_status == |
| contextual_search::ContextUploadStatus::kUploadReplaced) { |
| replaced_uploads.push_back(context_token); |
| } |
| handler_ |
| ->ContextualTasksComposeboxHandler::OnContextUploadStatusChanged( |
| context_token, mime_type, context_upload_status, error_type); |
| }); |
| |
| // 1. First selection. |
| std::string thumbnail_data = "data:image/png;base64,DATA"; |
| handler_->OnLensThumbnailCreated(thumbnail_data); |
| |
| // Verify: Should have one successful upload and no replacements. |
| ASSERT_EQ(successful_uploads.size(), 1u); |
| ASSERT_EQ(replaced_uploads.size(), 0u); |
| base::UnguessableToken first_token = successful_uploads[0]; |
| |
| // 2. Second selection (replace). |
| std::string thumbnail_data_2 = "data:image/png;base64,DATA2"; |
| handler_->OnLensThumbnailCreated(thumbnail_data_2); |
| |
| // Verify: Should have one replacement (the first token) and one new |
| // successful upload. |
| ASSERT_EQ(successful_uploads.size(), 2u); |
| ASSERT_EQ(replaced_uploads.size(), 1u); |
| EXPECT_EQ(replaced_uploads[0], first_token); |
| EXPECT_NE(successful_uploads[1], first_token); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| SubmitQuery_WaitsForRecontextualization) { |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL!"; |
| std::string kQuery = "stashed query"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context with uploaded tab. |
| contextual_tasks::ContextualTask task(task_id); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| GURL kUrl("about:blank"); |
| std::string kTitle = "about:blank"; |
| |
| contextual_tasks::UrlResource resource( |
| kUrl, contextual_tasks::ResourceType::kWebpage); |
| resource.title = kTitle; |
| resource.tab_id = session_id; |
| task.AddUrlResource(resource); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Setup FileInfo with expired status. |
| std::vector<raw_ptr<const contextual_search::FileInfo>> file_info_list; |
| contextual_search::FileInfo file_info; |
| file_info.tab_session_id = session_id; |
| file_info.upload_status = |
| contextual_search::ContextUploadStatus::kUploadExpired; |
| file_info.request_id.emplace(); |
| file_info.request_id->set_context_id(12345); |
| file_info_list.push_back(&file_info); |
| |
| EXPECT_CALL(*mock_controller_, GetFileInfoList()) |
| .WillRepeatedly(testing::Return(file_info_list)); |
| |
| // 1. Capture the GetPageContext callback. |
| MockTabContextualizationController::GetPageContextCallback pending_callback; |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillOnce([&](MockTabContextualizationController::GetPageContextCallback |
| callback) { pending_callback = std::move(callback); }); |
| |
| // 2. Call CreateAndSendQueryMessage. |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| |
| // Verify: recontextualization is pending, so the query is blocked. |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| ASSERT_FALSE(pending_callback.is_null()); |
| |
| // 3. Start file upload when recontextualizer completes context fetch. |
| base::UnguessableToken uploaded_token; |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .WillOnce([&](const base::UnguessableToken& file_token, |
| std::unique_ptr<lens::ContextualInputData> data, |
| std::optional<lens::ImageEncodingOptions> image_options) { |
| EXPECT_TRUE(data->is_implicit_upload); |
| uploaded_token = file_token; |
| }); |
| |
| auto data = std::make_unique<lens::ContextualInputData>(); |
| data->tab_session_id = session_id; |
| data->page_url = GURL("about:blank"); |
| data->page_title = "about:blank"; |
| data->context_id = 12345; |
| data->is_page_context_eligible = true; |
| std::move(pending_callback).Run(std::move(data)); |
| |
| // Verify: Still uploading and query not stashed in handler (as |
| // recontextualization waits in UploadTracker before |
| // ContinueCreateAndSendQueryMessage is triggered). |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| |
| // 4. Complete the upload status change. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| PostUploadStatusChanged( |
| uploaded_token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful); |
| run_loop.Run(); |
| |
| // Verify: Upload finished, stashed query is successfully sent. |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| Recontextualization_TabInvalidatedGracefullyCompletes) { |
| ASSERT_NE(mock_contextual_tasks_service_ptr_, nullptr) |
| << "Mock controller is NULL!"; |
| std::string kQuery = "invalid tab query"; |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context. |
| contextual_tasks::ContextualTask task(task_id); |
| SessionID session_id = sessions::SessionTabHelper::IdForTab(web_contents()); |
| GURL kUrl("about:blank"); |
| std::string kTitle = "about:blank"; |
| |
| contextual_tasks::UrlResource resource( |
| kUrl, contextual_tasks::ResourceType::kWebpage); |
| resource.title = kTitle; |
| resource.tab_id = session_id; |
| task.AddUrlResource(resource); |
| |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| |
| EXPECT_CALL( |
| *mock_contextual_tasks_service_ptr_, |
| GetContextForTask( |
| task_id, |
| testing::Contains(contextual_tasks::ContextualTaskContextSource:: |
| kSubmittedContextDecorator), |
| testing::NotNull(), testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid& task_id, |
| const std::set<contextual_tasks::ContextualTaskContextSource>& |
| sources, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams> params, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| // Setup FileInfo with expired status. |
| std::vector<raw_ptr<const contextual_search::FileInfo>> file_info_list; |
| contextual_search::FileInfo file_info; |
| file_info.tab_session_id = session_id; |
| file_info.upload_status = |
| contextual_search::ContextUploadStatus::kUploadExpired; |
| file_info.request_id.emplace(); |
| file_info.request_id->set_context_id(12345); |
| file_info_list.push_back(&file_info); |
| |
| EXPECT_CALL(*mock_controller_, GetFileInfoList()) |
| .WillRepeatedly(testing::Return(file_info_list)); |
| |
| // 1. GetPageContext returns nullptr, representing tab was invalidated. |
| EXPECT_CALL(*mock_tab_controller_, GetPageContext(testing::_)) |
| .WillOnce([](MockTabContextualizationController::GetPageContextCallback |
| callback) { std::move(callback).Run(nullptr); }); |
| |
| // 2. Upload is NOT triggered. |
| EXPECT_CALL(*mock_controller_, |
| StartFileUploadFlow(testing::_, testing::_, testing::_)) |
| .Times(0); |
| |
| // 3. Complete direct AIM query submission without context upload. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)) |
| .WillOnce(base::test::RunClosure(run_loop.QuitClosure())); |
| |
| handler_->CreateAndSendQueryMessage(kQuery, /*is_voice_search=*/false); |
| run_loop.Run(); |
| |
| // Verify: No context was uploaded, pending uploads are back to 0. |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| SubmitQuery_WaitsForModalityChipUpload) { |
| base::Uuid task_id = base::Uuid::GenerateRandomV4(); |
| EXPECT_CALL(*mock_ui_, GetTaskId()) |
| .WillRepeatedly( |
| testing::ReturnRefOfCopy(std::optional<base::Uuid>(task_id))); |
| |
| // Setup context. |
| contextual_tasks::ContextualTask task(task_id); |
| auto context = |
| std::make_unique<contextual_tasks::ContextualTaskContext>(task); |
| EXPECT_CALL(*mock_contextual_tasks_service_ptr_, |
| GetContextForTask(testing::_, testing::_, testing::_, testing::_)) |
| .WillOnce( |
| [&context]( |
| const base::Uuid&, |
| const std::set<contextual_tasks::ContextualTaskContextSource>&, |
| std::unique_ptr<contextual_tasks::ContextDecorationParams>, |
| base::OnceCallback<void( |
| std::unique_ptr<contextual_tasks::ContextualTaskContext>)> |
| callback) { std::move(callback).Run(std::move(context)); }); |
| |
| base::UnguessableToken token = base::UnguessableToken::Create(); |
| |
| // Setup FileInfo representing a server-injected modality chip in kProcessing |
| // status. |
| contextual_search::FileInfo uploading_info{}; |
| uploading_info.upload_status = |
| contextual_search::ContextUploadStatus::kProcessing; |
| uploading_info.mime_type = lens::MimeType::kUnknown; |
| auto input_data = std::make_unique<lens::ContextualInputData>(); |
| input_data->modality_chip_props.emplace(); |
| input_data->modality_chip_props->set_id("test_chip_id"); |
| uploading_info.input_data = std::move(input_data); |
| |
| EXPECT_CALL(*mock_controller_, GetFileInfo(token)) |
| .WillRepeatedly(testing::Return(&uploading_info)); |
| |
| // Expect no queries are sent immediately because the chip is still uploading. |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(0); |
| |
| // Simulate the status transition to kProcessing, which should register the |
| // modality chip in the handler's pending uploads set. |
| SimulateUploadStatusChanged( |
| token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kProcessing, std::nullopt); |
| |
| // Verify the chip is tracked as uploading. |
| ASSERT_TRUE(handler_->IsAnyContextUploading()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 1); |
| |
| // Submit query manually. It should be stashed. |
| handler_->SubmitQuery("Test query", 0, false, false, false, false, |
| /*is_voice_search=*/false); |
| ASSERT_TRUE(handler_->HasPendingQueryForTesting()); |
| |
| // Now expect the stashed query to be sent when the chip completes |
| // successfully. |
| EXPECT_CALL(*mock_controller_, CreateClientToAimRequest(testing::_)) |
| .WillOnce(testing::Return(lens::ClientToAimMessage())); |
| EXPECT_CALL(*mock_ui_, PostAimMessage(testing::_)).Times(1); |
| |
| // Simulate transition to kUploadSuccessful. |
| SimulateUploadStatusChanged( |
| token, lens::MimeType::kUnknown, |
| contextual_search::ContextUploadStatus::kUploadSuccessful, std::nullopt); |
| |
| // Verify pending uploads are cleared and the query has been sent. |
| ASSERT_FALSE(handler_->IsAnyContextUploading()); |
| ASSERT_FALSE(handler_->HasPendingQueryForTesting()); |
| ASSERT_EQ(handler_->GetNumContextUploading(), 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| MultiFilesSelected) { |
| base::ScopedAllowBlockingForTesting allow_blocking; |
| base::ScopedTempDir temp_dir; |
| ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); |
| |
| base::FilePath file1_path = temp_dir.GetPath().AppendASCII("file1.pdf"); |
| base::FilePath file2_path = temp_dir.GetPath().AppendASCII("file2.png"); |
| |
| std::string file1_content = "dummy pdf content"; |
| std::string file2_content = "dummy image content"; |
| |
| ASSERT_TRUE(base::WriteFile(file1_path, file1_content)); |
| ASSERT_TRUE(base::WriteFile(file2_path, file2_content)); |
| |
| std::vector<ui::SelectedFileInfo> files; |
| files.emplace_back(file1_path, file1_path); |
| files.emplace_back(file2_path, file2_path); |
| |
| base::RunLoop run_loop; |
| int file_contexts_added = 0; |
| EXPECT_CALL(mock_searchbox_page_, AddFileContext(testing::_, testing::_)) |
| .Times(2) |
| .WillRepeatedly([&](const base::UnguessableToken& token, |
| searchbox::mojom::SelectedFileInfoPtr file_info) { |
| file_contexts_added++; |
| if (file_info->file_name == "file1.pdf") { |
| EXPECT_EQ(file_info->mime_type, "application/pdf"); |
| } else if (file_info->file_name == "file2.png") { |
| EXPECT_EQ(file_info->mime_type, "image/png"); |
| } |
| if (file_contexts_added == 2) { |
| run_loop.Quit(); |
| } |
| }); |
| |
| handler_->MultiFilesSelected(files); |
| |
| // Wait for ThreadPool tasks to finish processing files. |
| run_loop.Run(); |
| |
| EXPECT_EQ(handler_->GetNumContextUploading(), 2); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerTest, |
| UpdateStateFromUrl_SoftNavigation) { |
| // Arrange: Setup local config with Canvas tool and its url params. |
| omnibox::SearchboxConfig config; |
| auto* canvas_config = config.add_tool_configs(); |
| canvas_config->set_tool(omnibox::ToolMode::TOOL_MODE_CANVAS); |
| auto* canvas_param = canvas_config->add_aim_url_params(); |
| canvas_param->set_param_key("rc"); |
| canvas_param->set_param_value("1"); |
| |
| // Create composebox handler using a custom callback that binds the local |
| // config. |
| auto mock_callback = base::BindRepeating( |
| [](contextual_search::ContextualSearchSessionHandle* session_handle, |
| const omnibox::SearchboxConfig config) { |
| return std::make_unique<contextual_search::InputStateModel>( |
| *session_handle, config, GURL(), /*is_off_the_record=*/false, |
| /*is_signed_in=*/false, |
| /*browser_identity_matches_aim_identity=*/false); |
| }, |
| session_handle_.get(), config); |
| mojo::PendingRemote<searchbox::mojom::Page> searchbox_page_remote; |
| mojo::PendingReceiver<searchbox::mojom::Page> searchbox_page_receiver = |
| searchbox_page_remote.InitWithNewPipeAndPassReceiver(); |
| auto custom_handler = std::make_unique<TestContextualTasksComposeboxHandler>( |
| mock_ui_.get(), profile(), web_contents(), |
| mojo::PendingReceiver<composebox::mojom::PageHandler>(), |
| mojo::PendingReceiver<searchbox::mojom::PageHandler>(), |
| std::move(searchbox_page_remote), |
| base::BindRepeating( |
| &ContextualTasksUI::GetOrCreateContextualSessionHandle, |
| base::Unretained(mock_ui_.get())), |
| base::BindRepeating(&ContextualTasksUI::ClearContextualSessionHandle, |
| base::Unretained(mock_ui_.get())), |
| std::move(mock_callback)); |
| |
| custom_handler->InitializeInputStateModel(); |
| |
| contextual_search::InputStateModel* model = |
| custom_handler->TakeInputStateModelForTesting(); |
| ASSERT_NE(model, nullptr); |
| |
| // Default tool is unspecified. |
| EXPECT_EQ(model->get_state_for_testing().active_tool, |
| omnibox::ToolMode::TOOL_MODE_UNSPECIFIED); |
| |
| // Act: Simulate soft navigation by calling UpdateStateFromUrl with Canvas |
| // GURL. |
| GURL canvas_url("https://example.com/?rc=1"); |
| custom_handler->UpdateStateFromUrl(canvas_url); |
| |
| // Assert: Verify the tool successfully restored/persisted to Canvas. |
| EXPECT_EQ(model->get_state_for_testing().active_tool, |
| omnibox::ToolMode::TOOL_MODE_CANVAS); |
| EXPECT_TRUE(model->get_state_for_testing().is_canvas_query_submitted); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTestWithContextManagementEnabled, |
| SetAimThreadRestoredTabs) { |
| SetUpHandler(); |
| ASSERT_NE(handler_, nullptr); |
| |
| std::vector<searchbox::mojom::TabInfoPtr> restored_tabs; |
| auto tab_info = searchbox::mojom::TabInfo::New(); |
| tab_info->url = GURL("https://example.com"); |
| tab_info->title = "Example Site"; |
| restored_tabs.push_back(std::move(tab_info)); |
| |
| EXPECT_CALL(mock_searchbox_page_, SetAimThreadRestoredTabs(testing::_)) |
| .WillOnce([&](const std::vector<searchbox::mojom::TabInfoPtr>& tabs) { |
| EXPECT_EQ(tabs.size(), 1u); |
| EXPECT_EQ(tabs[0]->url, GURL("https://example.com")); |
| EXPECT_EQ(tabs[0]->title, "Example Site"); |
| }); |
| |
| handler_->SetAimThreadRestoredTabs(std::move(restored_tabs)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTestWithContextManagementEnabled, |
| SetAimThreadRestoredTabs_ClearsAutoSuggestedTabWhenRestored) { |
| SetUpHandler(); |
| ASSERT_NE(handler_, nullptr); |
| |
| const GURL url("https://example.com"); |
| auto suggestion = std::make_unique<contextual_tasks::SuggestedTabInfo>(); |
| suggestion->url = url; |
| suggestion->title = u"Example Site"; |
| suggestion->tab_id = 42; |
| auto_suggestion_manager_.SetCurrentSuggestion(std::move(suggestion)); |
| |
| std::vector<searchbox::mojom::TabInfoPtr> restored_tabs; |
| auto tab_info = searchbox::mojom::TabInfo::New(); |
| tab_info->url = url; |
| tab_info->title = "Example Site"; |
| tab_info->tab_id = 42; |
| restored_tabs.push_back(std::move(tab_info)); |
| |
| // Verify that UpdateAutoSuggestedTabContext(nullptr) is dispatched to WebUI |
| // to clear the uncommitted suggestion chip. |
| EXPECT_CALL(mock_searchbox_page_, |
| UpdateAutoSuggestedTabContext(testing::_, testing::_)) |
| .WillOnce([&](const searchbox::mojom::TabInfoPtr& received_info, |
| const std::optional<std::string>& invocation_source) { |
| EXPECT_TRUE(received_info.is_null()); |
| EXPECT_EQ(invocation_source, std::nullopt); |
| }); |
| |
| EXPECT_CALL(mock_searchbox_page_, SetAimThreadRestoredTabs(testing::_)) |
| .WillOnce([url](const std::vector<searchbox::mojom::TabInfoPtr>& tabs) { |
| ASSERT_EQ(tabs.size(), 1u); |
| EXPECT_EQ(tabs[0]->tab_id, 42); |
| EXPECT_EQ(tabs[0]->url, url); |
| }); |
| |
| handler_->SetAimThreadRestoredTabs(std::move(restored_tabs)); |
| searchbox_page_receiver_.FlushForTesting(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTestWithContextManagementEnabled, |
| UpdateSuggestedTabContext_SuppressedIfTabAlreadyRestored) { |
| SetUpHandler(); |
| ASSERT_NE(handler_, nullptr); |
| |
| const GURL url("https://example.com"); |
| auto suggestion = std::make_unique<contextual_tasks::SuggestedTabInfo>(); |
| suggestion->url = url; |
| suggestion->title = u"Example Site"; |
| suggestion->tab_id = 42; |
| auto_suggestion_manager_.SetCurrentSuggestion(std::move(suggestion)); |
| |
| // Mock UI indicates tab_id 42 is already restored in the task. |
| EXPECT_CALL(*mock_ui_, GetRestoredTabIds()) |
| .WillRepeatedly(testing::Return(std::vector<int32_t>{42})); |
| |
| // Expect that auto-suggestion is suppressed (sent as nullptr) because the tab |
| // is already restored. |
| EXPECT_CALL(mock_searchbox_page_, |
| UpdateAutoSuggestedTabContext(testing::_, testing::_)) |
| .WillOnce([&](const searchbox::mojom::TabInfoPtr& received_info, |
| const std::optional<std::string>& invocation_source) { |
| EXPECT_TRUE(received_info.is_null()); |
| }); |
| |
| handler_->UpdateSuggestedTabContext( |
| auto_suggestion_manager_.GetCurrentSuggestion()); |
| searchbox_page_receiver_.FlushForTesting(); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTestWithContextManagementEnabled, |
| CacheSubmittedTabsOnInit) { |
| class TestSupportsTabHandles : public tabs::SupportsTabHandles { |
| public: |
| void SetSessionId(int32_t session_id) { |
| tabs::SupportsTabHandles::SetSessionId(session_id); |
| } |
| }; |
| |
| // Use the default tab created in SetUpOnMainThread. |
| tabs::TabInterface* tab1 = |
| tabs::TabLookupFromWebContents::FromWebContents(web_contents_)->model(); |
| ASSERT_NE(tab1, nullptr); |
| SessionID session_id1 = |
| sessions::SessionTabHelper::FromWebContents(tab1->GetContents()) |
| ->session_id(); |
| |
| // Add a second tab. |
| tabs::TabInterface* tab2 = AddTab(GURL("about:blank#2")); |
| ASSERT_NE(tab2, nullptr); |
| SessionID session_id2 = |
| sessions::SessionTabHelper::FromWebContents(tab2->GetContents()) |
| ->session_id(); |
| |
| auto mock_session = std::make_unique<testing::NiceMock< |
| contextual_search::MockContextualSearchSessionHandle>>(); |
| |
| std::vector<contextual_search::FileInfo> submitted_file_infos; |
| base::Time now = base::Time::Now(); |
| contextual_search::FileInfo tab_info1; |
| tab_info1.tab_url = GURL("about:blank#1"); |
| tab_info1.tab_title = "About Blank 1"; |
| tab_info1.tab_session_id = session_id1; |
| tab_info1.mime_type = lens::MimeType::kHtml; |
| tab_info1.selection_time = now; |
| submitted_file_infos.push_back(tab_info1); |
| |
| contextual_search::FileInfo tab_info2; |
| tab_info2.tab_url = GURL("about:blank#2"); |
| tab_info2.tab_title = "About Blank 2"; |
| tab_info2.tab_session_id = session_id2; |
| tab_info2.mime_type = lens::MimeType::kHtml; |
| tab_info2.selection_time = now + base::Seconds(1); |
| submitted_file_infos.push_back(tab_info2); |
| |
| // We should also include a raw file to ensure it's filtered out. |
| contextual_search::FileInfo file_info; |
| file_info.file_name = "test.pdf"; |
| submitted_file_infos.push_back(file_info); |
| |
| EXPECT_CALL(*mock_session, GetSubmittedContextFileInfos()) |
| .WillRepeatedly(testing::Return(submitted_file_infos)); |
| |
| mock_ui_->SetSessionHandle(mock_session.get()); |
| |
| EXPECT_CALL(mock_searchbox_page_, SetAimThreadRestoredTabs(testing::_)) |
| .WillOnce([&](const std::vector<searchbox::mojom::TabInfoPtr>& tabs) { |
| EXPECT_EQ(tabs.size(), 2u); |
| EXPECT_EQ(tabs[0]->url, GURL("about:blank#1")); |
| EXPECT_EQ(tabs[0]->title, "About Blank 1"); |
| EXPECT_EQ(tabs[0]->tab_id, 2); |
| EXPECT_EQ(tabs[1]->url, GURL("about:blank#2")); |
| EXPECT_EQ(tabs[1]->title, "About Blank 2"); |
| EXPECT_EQ(tabs[1]->tab_id, 3); |
| }); |
| |
| SetUpHandler(); |
| ASSERT_NE(handler_, nullptr); |
| |
| // Clean up session handle from mock UI. |
| mock_ui_->SetSessionHandle(nullptr); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerTestWithContextManagementEnabled, |
| CacheSubmittedTabsOnInit_UnmappedClosedTab) { |
| auto mock_session = std::make_unique<testing::NiceMock< |
| contextual_search::MockContextualSearchSessionHandle>>(); |
| |
| std::vector<contextual_search::FileInfo> submitted_file_infos; |
| const SessionID closed_tab_session_id = SessionID::FromSerializedValue(9999); |
| |
| contextual_search::FileInfo closed_tab_info; |
| closed_tab_info.tab_url = GURL("https://example.com/closed"); |
| closed_tab_info.tab_title = "Closed Tab"; |
| closed_tab_info.tab_session_id = closed_tab_session_id; |
| closed_tab_info.mime_type = lens::MimeType::kHtml; |
| closed_tab_info.selection_time = base::Time::Now(); |
| submitted_file_infos.push_back(closed_tab_info); |
| |
| EXPECT_CALL(*mock_session, GetSubmittedContextFileInfos()) |
| .WillRepeatedly(testing::Return(submitted_file_infos)); |
| |
| mock_ui_->SetSessionHandle(mock_session.get()); |
| |
| EXPECT_CALL(mock_searchbox_page_, SetAimThreadRestoredTabs(testing::_)) |
| .WillOnce([&](const std::vector<searchbox::mojom::TabInfoPtr>& tabs) { |
| ASSERT_EQ(tabs.size(), 1u); |
| EXPECT_EQ(tabs[0]->url, GURL("https://example.com/closed")); |
| EXPECT_EQ(tabs[0]->title, "Closed Tab"); |
| // Verify fallback to raw SessionID integer when tab is unmapped. |
| EXPECT_EQ(tabs[0]->tab_id, 9999); |
| }); |
| |
| SetUpHandler(); |
| ASSERT_NE(handler_, nullptr); |
| |
| mock_ui_->SetSessionHandle(nullptr); |
| } |
| |
| class ContextualTasksComposeboxHandlerAutoTriggerTest |
| : public ContextualTasksComposeboxHandlerTest { |
| public: |
| ContextualTasksComposeboxHandlerAutoTriggerTest() { |
| local_feature_list_.InitAndEnableFeatureWithParameters( |
| omnibox::kWebUIOmniboxAskGAboutThisPage, |
| {{"Omnibox_AskGCoBrowseWithVisualSelection", "true"}}); |
| } |
| private: |
| base::test::ScopedFeatureList local_feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerAutoTriggerTest, AutoTriggerLens) { |
| // Destroy the default handler and reset the receiver to allow rebinding. |
| handler_.reset(); |
| searchbox_page_receiver_.reset(); |
| |
| // Set the invocation source on the mock LensSearchController. |
| mock_lens_controller_->SetInvocationSource( |
| lens::LensOverlayInvocationSource::kOmniboxPageAction); |
| |
| // We expect OpenLensOverlay to be called when OnTaskChanged is called. |
| EXPECT_CALL( |
| *mock_lens_controller_, |
| OpenLensOverlay( |
| lens::LensOverlayInvocationSource::kOmniboxPageAction, |
| testing::_)) |
| .Times(1); |
| |
| // Manually create the handler to use our mock page. |
| auto custom_handler = std::make_unique<TestContextualTasksComposeboxHandler>( |
| mock_ui_.get(), profile(), web_contents(), |
| mojo::PendingReceiver<composebox::mojom::PageHandler>(), |
| mojo::PendingReceiver<searchbox::mojom::PageHandler>(), |
| searchbox_page_receiver_.BindNewPipeAndPassRemote(), |
| base::BindRepeating( |
| &ContextualTasksUI::GetOrCreateContextualSessionHandle, |
| base::Unretained(mock_ui_.get())), |
| base::BindRepeating(&ContextualTasksUI::ClearContextualSessionHandle, |
| base::Unretained(mock_ui_.get())), |
| base::BindRepeating(&ContextualTasksUI::TakeInputStateModel, |
| base::Unretained(mock_ui_.get()))); |
| |
| ON_CALL(*custom_handler, GetLensSearchController()) |
| .WillByDefault(testing::Return(mock_lens_controller_.get())); |
| |
| custom_handler->OnTaskChanged(); |
| } |
| |
| class ContextualTasksComposeboxHandlerSmartTabSharingTest |
| : public ContextualTasksComposeboxHandlerTest { |
| public: |
| ContextualTasksComposeboxHandlerSmartTabSharingTest() { |
| feature_list_sts_.InitWithFeaturesAndParameters( |
| {{contextual_tasks::kContextualTasksContext, |
| {{"ContextualTasksContextSmartTabSharing", "true"}}}, |
| {contextual_tasks::kContextualTasksForceEntryPointEligibility, {}}}, |
| {}); |
| } |
| ~ContextualTasksComposeboxHandlerSmartTabSharingTest() override = default; |
| |
| private: |
| base::test::ScopedFeatureList feature_list_sts_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(ContextualTasksComposeboxHandlerSmartTabSharingTest, |
| OnTaskChangedResetsSmartTabSharing) { |
| // Enable STS. |
| handler_->SetSmartTabSharingActive(true); |
| EXPECT_TRUE(handler_->IsSmartTabSharingActive()); |
| |
| // Call OnTaskChanged. |
| handler_->OnTaskChanged(); |
| |
| // Verify STS is disabled. |
| EXPECT_FALSE(handler_->IsSmartTabSharingActive()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F( |
| ContextualTasksComposeboxHandlerSmartTabSharingTest, |
| InitializeInputStateModelPreservesSmartTabSharingFromInputStateModel) { |
| auto mock_session = std::make_unique< |
| testing::NiceMock<contextual_search::MockContextualSearchSessionHandle>>(); |
| auto input_state_model = std::make_unique<contextual_search::InputStateModel>( |
| *mock_session, omnibox::SearchboxConfig(), GURL(), false, false, false); |
| input_state_model->SetSmartTabSharingActive(true); |
| |
| searchbox_page_receiver_.reset(); |
| auto custom_handler = std::make_unique<TestContextualTasksComposeboxHandler>( |
| mock_ui_.get(), profile(), web_contents(), |
| mojo::PendingReceiver<composebox::mojom::PageHandler>(), |
| mojo::PendingReceiver<searchbox::mojom::PageHandler>(), |
| searchbox_page_receiver_.BindNewPipeAndPassRemote(), |
| base::BindRepeating( |
| &ContextualTasksUI::GetOrCreateContextualSessionHandle, |
| base::Unretained(mock_ui_.get())), |
| base::BindRepeating(&ContextualTasksUI::ClearContextualSessionHandle, |
| base::Unretained(mock_ui_.get())), |
| base::BindRepeating( |
| [](std::unique_ptr<contextual_search::InputStateModel>* model) { |
| return std::move(*model); |
| }, |
| base::Unretained(&input_state_model))); |
| |
| EXPECT_TRUE(custom_handler->IsSmartTabSharingActive()); |
| } |