| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // This class runs functional tests for lens overlay. These tests spin up a full |
| // web browser, but allow for inspection and modification of internal state of |
| // LensOverlayController and other business-logic classes. |
| |
| #include "chrome/browser/ui/lens/lens_overlay_controller.h" |
| |
| #include "base/strings/string_util.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/run_until.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/lens/core/mojom/geometry.mojom.h" |
| #include "chrome/browser/lens/core/mojom/lens.mojom.h" |
| #include "chrome/browser/lens/core/mojom/overlay_object.mojom.h" |
| #include "chrome/browser/lens/core/mojom/polygon.mojom.h" |
| #include "chrome/browser/lens/core/mojom/text.mojom.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/signin/identity_manager_factory.h" |
| #include "chrome/browser/sync/sync_service_factory.h" |
| #include "chrome/browser/themes/theme_service.h" |
| #include "chrome/browser/themes/theme_service_factory.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_element_identifiers.h" |
| #include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h" |
| #include "chrome/browser/ui/exclusive_access/fullscreen_controller.h" |
| #include "chrome/browser/ui/lens/lens_overlay_dismissal_source.h" |
| #include "chrome/browser/ui/lens/lens_overlay_invocation_source.h" |
| #include "chrome/browser/ui/lens/lens_overlay_permission_utils.h" |
| #include "chrome/browser/ui/lens/lens_overlay_side_panel_coordinator.h" |
| #include "chrome/browser/ui/lens/lens_overlay_url_builder.h" |
| #include "chrome/browser/ui/lens/lens_permission_bubble_controller.h" |
| #include "chrome/browser/ui/lens/lens_search_bubble_controller.h" |
| #include "chrome/browser/ui/side_panel/side_panel_entry_id.h" |
| #include "chrome/browser/ui/tabs/tab_features.h" |
| #include "chrome/browser/ui/tabs/tab_model.h" |
| #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| #include "chrome/browser/ui/views/frame/browser_view.h" |
| #include "chrome/browser/ui/views/side_panel/side_panel.h" |
| #include "chrome/browser/ui/views/side_panel/side_panel_coordinator.h" |
| #include "chrome/browser/ui/views/side_panel/side_panel_util.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/lens/lens_features.h" |
| #include "components/lens/proto/server/lens_overlay_response.pb.h" |
| #include "components/permissions/test/permission_request_observer.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/page_navigator.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_widget_host_view.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/referrer.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/hit_test_region_observer.h" |
| #include "content/public/test/test_navigation_observer.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "net/base/url_util.h" |
| #include "ui/base/page_transition_types.h" |
| #include "ui/base/window_open_disposition.h" |
| #include "ui/events/base_event_utils.h" |
| #include "ui/views/controls/webview/webview.h" |
| #include "ui/views/test/widget_test.h" |
| #include "ui/views/view_utils.h" |
| #include "ui/views/widget/any_widget_observer.h" |
| #include "url/origin.h" |
| |
| namespace { |
| |
| constexpr char kDocumentWithNamedElement[] = "/select.html"; |
| |
| using State = LensOverlayController::State; |
| using LensOverlayInvocationSource = lens::LensOverlayInvocationSource; |
| using LensOverlayDismissalSource = lens::LensOverlayDismissalSource; |
| |
| constexpr char kNewTabLinkClickScript[] = |
| "(function() {const anchor = document.createElement('a');anchor.href = " |
| "$1;anchor.target = " |
| "'_blank';document.body.appendChild(anchor);anchor.click();})();"; |
| |
| constexpr char kSameTabLinkClickScript[] = |
| "(function() {const anchor = document.createElement('a');anchor.href = " |
| "$1;document.body.appendChild(anchor);anchor.click();})();"; |
| |
| constexpr char kCheckSearchboxInput[] = |
| "(function() {const root = " |
| "document.getElementsByTagName('lens-side-panel-app')[0].shadowRoot;" |
| "const searchboxInputLoaded = " |
| " root.getElementById('realbox').shadowRoot.getElementById('input').value " |
| " === $1; return searchboxInputLoaded;})();"; |
| |
| constexpr char kRequestNotificationsScript[] = R"( |
| new Promise(resolve => { |
| Notification.requestPermission().then(function (permission) { |
| resolve(permission); |
| }); |
| }) |
| )"; |
| |
| constexpr char kCheckSidePanelResultsLoadedScript[] = |
| "(function() {const root = " |
| "document.getElementsByTagName('lens-side-panel-app')[0].shadowRoot; " |
| "const iframeSrcLoaded = " |
| " root.getElementById('results').src.includes('q=' + $1);" |
| "const searchboxInputLoaded = " |
| " root.getElementById('realbox').shadowRoot.getElementById('input').value " |
| " === $1; return iframeSrcLoaded && searchboxInputLoaded;})();"; |
| |
| constexpr char kCheckSidePanelTranslateResultsLoadedScript[] = |
| "(function() {const root = " |
| "document.getElementsByTagName('lens-side-panel-app')[0].shadowRoot; " |
| "const iframeSrcLoaded = " |
| " root.getElementById('results').src.includes('q=' + $1);" |
| "const tlitetxtPresent = " |
| " root.getElementById('results').src.includes('tlitetxt=' + $1);" |
| "const ctxslTransPresent = " |
| " root.getElementById('results').src.includes('ctxsl_trans=1');" |
| "const tliteslPresent = " |
| " root.getElementById('results').src.includes('tlitesl=' + $2);" |
| "const tlitetlPresent = " |
| " root.getElementById('results').src.includes('tlitetl=en-US');" |
| "const searchboxInputLoaded = " |
| " root.getElementById('realbox').shadowRoot.getElementById('input').value " |
| " === $1; return iframeSrcLoaded && tlitetxtPresent && ctxslTransPresent " |
| "&& tliteslPresent & tlitetlPresent && searchboxInputLoaded;})();"; |
| |
| constexpr char kCheckSidePanelThumbnailShownScript[] = |
| "(function() {const appRoot = " |
| "document.getElementsByTagName('lens-side-panel-app')[0].shadowRoot;" |
| "const realboxRoot = appRoot.getElementById('realbox').shadowRoot;" |
| "const thumbContainer = realboxRoot.getElementById('thumbnailContainer');" |
| "const thumbnailRoot = realboxRoot.getElementById('thumbnail').shadowRoot;" |
| "const imageSrc = thumbnailRoot.getElementById('image').src;" |
| "return window.getComputedStyle(thumbContainer).display !== 'none' && " |
| " imageSrc.startsWith('data:image/jpeg');})();"; |
| |
| constexpr char kTestSuggestSignals[] = "suggest_signals"; |
| |
| constexpr char kStartTimeQueryParamKey[] = "qsubts"; |
| constexpr char kViewportWidthQueryParamKey[] = "biw"; |
| constexpr char kViewportHeightQueryParamKey[] = "bih"; |
| constexpr char kTextQueryParamKey[] = "q"; |
| |
| constexpr char kResultsSearchBaseUrl[] = "https://www.google.com/search"; |
| |
| void ClickBubbleDialogButton( |
| views::BubbleDialogDelegate* bubble_widget_delegate, |
| views::View* button) { |
| // Reset the timer so that the test click isn't discarded as unintended. |
| bubble_widget_delegate->ResetViewShownTimeStampForTesting(); |
| gfx::Point center(button->width() / 2, button->height() / 2); |
| const ui::MouseEvent event(ui::ET_MOUSE_PRESSED, center, center, |
| ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, |
| ui::EF_LEFT_MOUSE_BUTTON); |
| button->OnMousePressed(event); |
| button->OnMouseReleased(event); |
| } |
| |
| const lens::mojom::GeometryPtr kTestGeometry = |
| lens::mojom::Geometry::New(lens::mojom::CenterRotatedBox::New( |
| gfx::RectF(0.1, 0.1, 0.8, 0.8), |
| 0.1, |
| lens::mojom::CenterRotatedBox_CoordinateType::kNormalized), |
| std::vector<lens::mojom::PolygonPtr>()); |
| const lens::mojom::OverlayObjectPtr kTestOverlayObject = |
| lens::mojom::OverlayObject::New("unique_id", kTestGeometry->Clone()); |
| const lens::mojom::TextPtr kTestText = |
| lens::mojom::Text::New(lens::mojom::TextLayout::New(), "es"); |
| const lens::mojom::CenterRotatedBoxPtr kTestRegion = |
| lens::mojom::CenterRotatedBox::New( |
| gfx::RectF(0.1, 0.1, 0.8, 0.8), |
| 0.1, |
| lens::mojom::CenterRotatedBox_CoordinateType::kNormalized); |
| |
| class LensOverlayPageFake : public lens::mojom::LensPage { |
| public: |
| void ScreenshotDataUriReceived(const std::string& data_uri) override { |
| last_received_screenshot_data_uri_ = data_uri; |
| // Do the real call on the open WebUI we intercepted. |
| overlay_page_->ScreenshotDataUriReceived(data_uri); |
| } |
| |
| void ObjectsReceived( |
| std::vector<lens::mojom::OverlayObjectPtr> objects) override { |
| last_received_objects_ = std::move(objects); |
| } |
| |
| void TextReceived(lens::mojom::TextPtr text) override { |
| last_received_text_ = std::move(text); |
| } |
| |
| void NotifyResultsPanelOpened() override { |
| did_notify_results_opened_ = true; |
| } |
| |
| void NotifyOverlayClosing() override { did_notify_overlay_closing_ = true; } |
| |
| void SetPostRegionSelection( |
| lens::mojom::CenterRotatedBoxPtr region) override { |
| post_region_selection_ = std::move(region); |
| } |
| |
| void SetTextSelection(int selection_start_index, |
| int selection_end_index) override { |
| text_selection_indexes_ = |
| std::make_pair(selection_start_index, selection_end_index); |
| } |
| |
| void ClearRegionSelection() override { did_clear_region_selection_ = true; } |
| |
| void ClearTextSelection() override { did_clear_text_selection_ = true; } |
| |
| void ClearAllSelections() override { |
| did_clear_region_selection_ = true; |
| did_clear_text_selection_ = true; |
| } |
| |
| // The real side panel page that was opened by the lens overlay. Needed to |
| // call real functions on the WebUI. |
| mojo::Remote<lens::mojom::LensPage> overlay_page_; |
| |
| std::string last_received_screenshot_data_uri_; |
| std::vector<lens::mojom::OverlayObjectPtr> last_received_objects_; |
| lens::mojom::TextPtr last_received_text_; |
| bool did_notify_results_opened_ = false; |
| bool did_notify_overlay_closing_ = false; |
| lens::mojom::CenterRotatedBoxPtr post_region_selection_; |
| std::pair<int, int> text_selection_indexes_; |
| bool did_clear_region_selection_ = false; |
| bool did_clear_text_selection_ = false; |
| }; |
| |
| // TODO(b/334147680): Since both our interactive UI tests and our browser tests |
| // both mock out network calls via this method, we should factor this out so it |
| // can be used across files. |
| class LensOverlayQueryControllerFake : public lens::LensOverlayQueryController { |
| public: |
| explicit LensOverlayQueryControllerFake( |
| lens::LensOverlayFullImageResponseCallback full_image_callback, |
| lens::LensOverlayUrlResponseCallback url_callback, |
| lens::LensOverlayInteractionResponseCallback interaction_data_callback, |
| lens::LensOverlayThumbnailCreatedCallback thumbnail_created_callback, |
| variations::VariationsClient* variations_client, |
| signin::IdentityManager* identity_manager, |
| LensOverlayInvocationSource invocation_source, |
| bool use_dark_mode) |
| : LensOverlayQueryController(full_image_callback, |
| url_callback, |
| interaction_data_callback, |
| thumbnail_created_callback, |
| variations_client, |
| identity_manager, |
| invocation_source, |
| use_dark_mode) {} |
| |
| void StartQueryFlow(const SkBitmap& screenshot, |
| std::optional<GURL> page_url, |
| std::optional<std::string> page_title) override { |
| // Send response for full image callback / HandleStartQueryResponse. |
| std::vector<lens::mojom::OverlayObjectPtr> test_objects; |
| test_objects.push_back(kTestOverlayObject->Clone()); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(full_image_callback_, std::move(test_objects), |
| kTestText->Clone())); |
| |
| // Send response for interaction data callback / |
| // HandleInteractionDataResponse. |
| lens::proto::LensOverlayInteractionResponse interaction_response; |
| interaction_response.set_suggest_signals(kTestSuggestSignals); |
| base::SequencedTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(interaction_data_callback_, interaction_response)); |
| } |
| }; |
| |
| // Stubs out network requests and mojo calls. |
| class LensOverlayControllerFake : public LensOverlayController { |
| public: |
| LensOverlayControllerFake(tabs::TabInterface* tab, |
| variations::VariationsClient* variations_client, |
| signin::IdentityManager* identity_manager, |
| PrefService* pref_service, |
| syncer::SyncService* sync_service, |
| ThemeService* theme_service) |
| : LensOverlayController(tab, |
| variations_client, |
| identity_manager, |
| pref_service, |
| sync_service, |
| theme_service) {} |
| |
| std::unique_ptr<lens::LensOverlayQueryController> CreateLensQueryController( |
| lens::LensOverlayFullImageResponseCallback full_image_callback, |
| lens::LensOverlayUrlResponseCallback url_callback, |
| lens::LensOverlayInteractionResponseCallback interaction_data_callback, |
| lens::LensOverlayThumbnailCreatedCallback thumbnail_created_callback, |
| variations::VariationsClient* variations_client, |
| signin::IdentityManager* identity_manager, |
| lens::LensOverlayInvocationSource invocation_source, |
| bool use_dark_mode) override { |
| return std::make_unique<LensOverlayQueryControllerFake>( |
| full_image_callback, url_callback, interaction_data_callback, |
| thumbnail_created_callback, variations_client, identity_manager, |
| invocation_source, use_dark_mode); |
| } |
| |
| void BindOverlay(mojo::PendingReceiver<lens::mojom::LensPageHandler> receiver, |
| mojo::PendingRemote<lens::mojom::LensPage> page) override { |
| fake_overlay_page_.overlay_page_.Bind(std::move(page)); |
| // Set up the fake overlay page to intercept the mojo call. |
| LensOverlayController::BindOverlay( |
| std::move(receiver), |
| fake_overlay_page_receiver_.BindNewPipeAndPassRemote()); |
| } |
| |
| void SetSidePanelIsLoadingResults(bool is_loading) override { |
| if (is_loading) { |
| is_side_panel_loading_set_to_true_++; |
| return; |
| } |
| |
| is_side_panel_loading_set_to_false_++; |
| } |
| |
| void ResetLoadingTracking() { |
| is_side_panel_loading_set_to_true_ = 0; |
| is_side_panel_loading_set_to_false_ = 0; |
| } |
| |
| void FlushForTesting() { fake_overlay_page_receiver_.FlushForTesting(); } |
| |
| int is_side_panel_loading_set_to_true_ = 0; |
| int is_side_panel_loading_set_to_false_ = 0; |
| LensOverlayPageFake fake_overlay_page_; |
| mojo::Receiver<lens::mojom::LensPage> fake_overlay_page_receiver_{ |
| &fake_overlay_page_}; |
| }; |
| |
| class TabFeaturesFake : public tabs::TabFeatures { |
| public: |
| TabFeaturesFake() = default; |
| |
| protected: |
| std::unique_ptr<LensOverlayController> CreateLensController( |
| tabs::TabInterface* tab, |
| Profile* profile) override { |
| auto* theme_service = ThemeServiceFactory::GetForProfile(profile); |
| // Set browser color scheme to light mode for consistency. |
| theme_service->SetBrowserColorScheme( |
| ThemeService::BrowserColorScheme::kLight); |
| return std::make_unique<LensOverlayControllerFake>( |
| tab, profile->GetVariationsClient(), |
| IdentityManagerFactory::GetForProfile(profile), profile->GetPrefs(), |
| SyncServiceFactory::GetForProfile(profile), theme_service); |
| } |
| }; |
| |
| class LensOverlayControllerBrowserTest : public InProcessBrowserTest { |
| protected: |
| LensOverlayControllerBrowserTest() { |
| tabs::TabFeatures::ReplaceTabFeaturesForTesting(base::BindRepeating( |
| &LensOverlayControllerBrowserTest::CreateTabFeatures, |
| base::Unretained(this))); |
| feature_list_.InitAndEnableFeatureWithParameters( |
| lens::features::kLensOverlay, |
| { |
| {"search-bubble", "true"}, |
| {"results-search-url", kResultsSearchBaseUrl}, |
| }); |
| } |
| |
| void SetUp() override { |
| ASSERT_TRUE(embedded_test_server()->InitializeAndListen()); |
| InProcessBrowserTest::SetUp(); |
| } |
| |
| void SetUpOnMainThread() override { |
| InProcessBrowserTest::SetUpOnMainThread(); |
| embedded_test_server()->StartAcceptingConnections(); |
| |
| // Permits sharing the page screenshot by default. |
| PrefService* prefs = browser()->profile()->GetPrefs(); |
| prefs->SetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled, true); |
| } |
| |
| void TearDownOnMainThread() override { |
| EXPECT_TRUE(embedded_test_server()->ShutdownAndWaitUntilComplete()); |
| InProcessBrowserTest::TearDownOnMainThread(); |
| |
| // Disallow sharing the page screenshot by default. |
| PrefService* prefs = browser()->profile()->GetPrefs(); |
| prefs->SetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled, false); |
| } |
| |
| ~LensOverlayControllerBrowserTest() override { |
| tabs::TabFeatures::ReplaceTabFeaturesForTesting(base::NullCallback()); |
| } |
| |
| std::unique_ptr<tabs::TabFeatures> CreateTabFeatures() { |
| return std::make_unique<TabFeaturesFake>(); |
| } |
| |
| content::WebContents* GetOverlayWebContents() { |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| raw_ptr<views::WebView> overlay_web_view = |
| views::AsViewClass<views::WebView>( |
| controller->GetOverlayWidgetForTesting() |
| ->GetContentsView() |
| ->children()[0]); |
| return overlay_web_view->GetWebContents(); |
| } |
| |
| void SimulateLeftClickDrag(gfx::Point from, gfx::Point to) { |
| auto* overlay_web_contents = GetOverlayWebContents(); |
| // We should wait for the main frame's hit-test data to be ready before |
| // sending the click event below to avoid flakiness. |
| content::WaitForHitTestData(overlay_web_contents->GetPrimaryMainFrame()); |
| content::SimulateMouseEvent(overlay_web_contents, |
| blink::WebInputEvent::Type::kMouseDown, |
| blink::WebMouseEvent::Button::kLeft, from); |
| content::SimulateMouseEvent(overlay_web_contents, |
| blink::WebInputEvent::Type::kMouseMove, |
| blink::WebMouseEvent::Button::kLeft, to); |
| content::SimulateMouseEvent(overlay_web_contents, |
| blink::WebInputEvent::Type::kMouseUp, |
| blink::WebMouseEvent::Button::kLeft, to); |
| content::RunUntilInputProcessed( |
| overlay_web_contents->GetRenderWidgetHostView()->GetRenderWidgetHost()); |
| } |
| |
| // Lens overlay takes a screenshot of the tab. In order to take a screenshot |
| // the tab must not be about:blank and must be painted. By default opens in |
| // the current tab. |
| void WaitForPaint( |
| WindowOpenDisposition disposition = WindowOpenDisposition::CURRENT_TAB, |
| int browser_test_flags = ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP) { |
| const GURL url = embedded_test_server()->GetURL(kDocumentWithNamedElement); |
| ASSERT_TRUE(ui_test_utils::NavigateToURLWithDisposition( |
| browser(), url, disposition, browser_test_flags)); |
| ASSERT_TRUE(base::test::RunUntil([&]() { |
| return browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->contents() |
| ->CompletedFirstVisuallyNonEmptyPaint(); |
| })); |
| } |
| |
| // Helper to remove the start time and viewport size query params from the |
| // url. |
| GURL RemoveStartTimeAndSizeParams(const GURL& url_to_process) { |
| GURL processed_url = url_to_process; |
| std::string actual_start_time; |
| bool has_start_time = net::GetValueForKeyInQuery( |
| GURL(url_to_process), kStartTimeQueryParamKey, &actual_start_time); |
| EXPECT_TRUE(has_start_time); |
| processed_url = net::AppendOrReplaceQueryParameter( |
| processed_url, kStartTimeQueryParamKey, std::nullopt); |
| std::string actual_viewport_width; |
| bool has_viewport_width = net::GetValueForKeyInQuery( |
| GURL(url_to_process), kViewportWidthQueryParamKey, |
| &actual_viewport_width); |
| std::string actual_viewport_height; |
| bool has_viewport_height = net::GetValueForKeyInQuery( |
| GURL(url_to_process), kViewportHeightQueryParamKey, |
| &actual_viewport_height); |
| EXPECT_TRUE(has_viewport_width); |
| EXPECT_TRUE(has_viewport_height); |
| EXPECT_NE(actual_viewport_width, "0"); |
| EXPECT_NE(actual_viewport_height, "0"); |
| processed_url = net::AppendOrReplaceQueryParameter( |
| processed_url, kViewportWidthQueryParamKey, std::nullopt); |
| processed_url = net::AppendOrReplaceQueryParameter( |
| processed_url, kViewportHeightQueryParamKey, std::nullopt); |
| return processed_url; |
| } |
| |
| void VerifyTextQueriesAreEqual(const GURL& url, const GURL& url_to_compare) { |
| std::string text_query; |
| bool has_text_query = |
| net::GetValueForKeyInQuery(GURL(url), kTextQueryParamKey, &text_query); |
| EXPECT_TRUE(has_text_query); |
| |
| std::string query_to_compare; |
| bool has_query_to_compare = net::GetValueForKeyInQuery( |
| GURL(url_to_compare), kTextQueryParamKey, &query_to_compare); |
| EXPECT_TRUE(has_query_to_compare); |
| |
| EXPECT_EQ(query_to_compare, text_query); |
| } |
| |
| void VerifySearchQueryParameters(const GURL& url_to_process) { |
| EXPECT_THAT(url_to_process.spec(), |
| testing::MatchesRegex(std::string(kResultsSearchBaseUrl) + |
| ".*q=.*&gsc=1&hl=.*&biw=\\d+&bih=\\d+")); |
| } |
| |
| void CloseOverlayAndWaitForOff(LensOverlayController* controller, |
| LensOverlayDismissalSource dismissal_source) { |
| controller->CloseUIAsync(dismissal_source); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOff; })); |
| } |
| |
| private: |
| base::test::ScopedFeatureList feature_list_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| PermissionBubble_Accept) { |
| WaitForPaint(); |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Disallow sharing the page screenshot. |
| PrefService* prefs = browser()->profile()->GetPrefs(); |
| prefs->SetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled, false); |
| ASSERT_FALSE(lens::CanSharePageScreenshotWithLensOverlay(prefs)); |
| |
| // Verify attempting to show the UI will show the permission bubble. |
| views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{}, |
| lens::kLensPermissionDialogName); |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| // State should remain off. |
| ASSERT_EQ(controller->state(), State::kOff); |
| auto* bubble_widget = waiter.WaitIfNeededAndGet(); |
| // Wait for the bubble to become visible. |
| views::test::WidgetVisibleWaiter(bubble_widget).Wait(); |
| ASSERT_TRUE(bubble_widget->IsVisible()); |
| ASSERT_TRUE(controller->GetLensPermissionBubbleControllerForTesting() |
| ->HasOpenDialogWidget()); |
| |
| // Verify attempting to show the UI again does not close the bubble widget. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| // State should remain off. |
| ASSERT_EQ(controller->state(), State::kOff); |
| ASSERT_TRUE(bubble_widget->IsVisible()); |
| ASSERT_TRUE(controller->GetLensPermissionBubbleControllerForTesting() |
| ->HasOpenDialogWidget()); |
| |
| // Simulate click on the accept button. |
| auto* bubble_widget_delegate = |
| bubble_widget->widget_delegate()->AsBubbleDialogDelegate(); |
| ClickBubbleDialogButton(bubble_widget_delegate, |
| bubble_widget_delegate->GetOkButton()); |
| // Wait for the bubble to be destroyed. |
| views::test::WidgetDestroyedWaiter(bubble_widget).Wait(); |
| ASSERT_FALSE(controller->GetLensPermissionBubbleControllerForTesting() |
| ->HasOpenDialogWidget()); |
| |
| // Verify sharing the page screenshot is now permitted. |
| ASSERT_TRUE(lens::CanSharePageScreenshotWithLensOverlay(prefs)); |
| |
| // Verify accepting the permission bubble will eventually result in the |
| // overlay state. |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| |
| // Verify screenshot was captured and stored. |
| auto screenshot_bitmap = controller->current_screenshot(); |
| EXPECT_FALSE(screenshot_bitmap.empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| PermissionBubble_Reject) { |
| WaitForPaint(); |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Disallow sharing the page screenshot. |
| PrefService* prefs = browser()->profile()->GetPrefs(); |
| prefs->SetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled, false); |
| ASSERT_FALSE(lens::CanSharePageScreenshotWithLensOverlay(prefs)); |
| |
| // Verify attempting to show the UI will show the permission bubble. |
| views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{}, |
| lens::kLensPermissionDialogName); |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| // State should remain off. |
| ASSERT_EQ(controller->state(), State::kOff); |
| auto* bubble_widget = waiter.WaitIfNeededAndGet(); |
| // Wait for the bubble to become visible. |
| views::test::WidgetVisibleWaiter(bubble_widget).Wait(); |
| ASSERT_TRUE(bubble_widget->IsVisible()); |
| ASSERT_TRUE(controller->GetLensPermissionBubbleControllerForTesting() |
| ->HasOpenDialogWidget()); |
| |
| // Verify attempting to show the UI again does not close the bubble widget. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| // State should remain off. |
| ASSERT_EQ(controller->state(), State::kOff); |
| ASSERT_TRUE(bubble_widget->IsVisible()); |
| ASSERT_TRUE(controller->GetLensPermissionBubbleControllerForTesting() |
| ->HasOpenDialogWidget()); |
| |
| // Simulate click on the reject button. |
| auto* bubble_widget_delegate = |
| bubble_widget->widget_delegate()->AsBubbleDialogDelegate(); |
| ClickBubbleDialogButton(bubble_widget_delegate, |
| bubble_widget_delegate->GetCancelButton()); |
| // Wait for the bubble to be destroyed. |
| views::test::WidgetDestroyedWaiter(bubble_widget).Wait(); |
| ASSERT_FALSE(controller->GetLensPermissionBubbleControllerForTesting() |
| ->HasOpenDialogWidget()); |
| |
| // Verify sharing the page screenshot is still not permitted. |
| ASSERT_FALSE(lens::CanSharePageScreenshotWithLensOverlay(prefs)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| PermissionBubble_PrefChange) { |
| WaitForPaint(); |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Disallow sharing the page screenshot. |
| PrefService* prefs = browser()->profile()->GetPrefs(); |
| prefs->SetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled, false); |
| ASSERT_FALSE(lens::CanSharePageScreenshotWithLensOverlay(prefs)); |
| |
| // Verify attempting to show the UI will show the permission bubble. |
| views::NamedWidgetShownWaiter waiter(views::test::AnyWidgetTestPasskey{}, |
| lens::kLensPermissionDialogName); |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| // State should remain off. |
| ASSERT_EQ(controller->state(), State::kOff); |
| auto* bubble_widget = waiter.WaitIfNeededAndGet(); |
| // Wait for the bubble to become visible. |
| views::test::WidgetVisibleWaiter(bubble_widget).Wait(); |
| ASSERT_TRUE(bubble_widget->IsVisible()); |
| ASSERT_TRUE(controller->GetLensPermissionBubbleControllerForTesting() |
| ->HasOpenDialogWidget()); |
| |
| // Verify attempting to show the UI again does not close the bubble widget. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| // State should remain off. |
| ASSERT_EQ(controller->state(), State::kOff); |
| ASSERT_TRUE(bubble_widget->IsVisible()); |
| ASSERT_TRUE(controller->GetLensPermissionBubbleControllerForTesting() |
| ->HasOpenDialogWidget()); |
| |
| // Simulate pref being enabled elsewhere. |
| prefs->SetBoolean(lens::prefs::kLensSharingPageScreenshotEnabled, true); |
| // Wait for the bubble to be destroyed. |
| views::test::WidgetDestroyedWaiter(bubble_widget).Wait(); |
| ASSERT_FALSE(controller->GetLensPermissionBubbleControllerForTesting() |
| ->HasOpenDialogWidget()); |
| |
| // Verify sharing the page screenshot is now permitted. |
| ASSERT_TRUE(lens::CanSharePageScreenshotWithLensOverlay(prefs)); |
| |
| // Verify accepting the permission bubble will eventually result in the |
| // overlay state. |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| |
| // Verify screenshot was captured and stored. |
| auto screenshot_bitmap = controller->current_screenshot(); |
| EXPECT_FALSE(screenshot_bitmap.empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, CaptureScreenshot) { |
| WaitForPaint(); |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| |
| // Verify screenshot was captured and stored. |
| auto screenshot_bitmap = controller->current_screenshot(); |
| EXPECT_FALSE(screenshot_bitmap.empty()); |
| |
| // Verify screenshot was encoded and passed to WebUI. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| EXPECT_FALSE(fake_controller->fake_overlay_page_ |
| .last_received_screenshot_data_uri_.empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, CreateAndLoadWebUI) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| |
| // Assert that the web view was created and loaded WebUI. |
| GURL webui_url(chrome::kChromeUILensUntrustedURL); |
| ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| ASSERT_EQ(GetOverlayWebContents()->GetLastCommittedURL(), webui_url); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| FullscreenClosesOverlay) { |
| WaitForPaint(); |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| |
| // Enter into fullscreen mode. |
| FullscreenController* fullscreen_controller = |
| browser()->exclusive_access_manager()->fullscreen_controller(); |
| content::WebContents* tab_web_contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| fullscreen_controller->EnterFullscreenModeForTab( |
| tab_web_contents->GetPrimaryMainFrame()); |
| |
| // Verify the overlay turns off. |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOff; })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, ShowSidePanel) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| |
| // Before showing the results panel, there should be no notification sent to |
| // WebUI. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| EXPECT_FALSE(fake_controller->fake_overlay_page_.did_notify_results_opened_); |
| |
| // Now show the side panel. |
| controller->results_side_panel_coordinator()->RegisterEntryAndShow(); |
| |
| // Prevent flakiness by flushing the tasks. |
| fake_controller->FlushForTesting(); |
| |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| EXPECT_TRUE(coordinator->IsSidePanelShowing()); |
| EXPECT_EQ(coordinator->GetCurrentEntryId(), |
| SidePanelEntry::Id::kLensOverlayResults); |
| EXPECT_TRUE(fake_controller->fake_overlay_page_.did_notify_results_opened_); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| ShowSidePanelWithPendingRegion) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUIWithPendingRegion(LensOverlayInvocationSource::kAppMenu, |
| kTestRegion->Clone()); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlayAndResults; })); |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| // Expect the Lens Overlay results panel to open. |
| ASSERT_TRUE(coordinator->IsSidePanelShowing()); |
| EXPECT_EQ(coordinator->GetCurrentEntryId(), |
| SidePanelEntry::Id::kLensOverlayResults); |
| |
| // Verify region was passed to WebUI. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| EXPECT_EQ(kTestRegion, |
| fake_controller->fake_overlay_page_.post_region_selection_); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, CloseSidePanel) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| |
| // Grab fake controller to test if notify the overlay of being closed. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| EXPECT_FALSE(fake_controller->fake_overlay_page_.did_notify_overlay_closing_); |
| |
| // Now show the side panel. |
| controller->results_side_panel_coordinator()->RegisterEntryAndShow(); |
| |
| // Ensure the side panel is showing. |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| EXPECT_TRUE(coordinator->IsSidePanelShowing()); |
| |
| // Close the side panel. |
| coordinator->Close(); |
| |
| // Ensure the overlay closes too. |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOff; })); |
| EXPECT_TRUE(fake_controller->fake_overlay_page_.did_notify_overlay_closing_); |
| } |
| |
| #if BUILDFLAG(IS_CHROMEOS_LACROS) && !BUILDFLAG(IS_CHROMEOS_DEVICE) && \ |
| defined(MEMORY_SANTIZER) |
| #define MAYBE_DelayPermissionsPrompt DISABLED_DelayPermissionsPrompt |
| #else |
| #define MAYBE_DelayPermissionsPrompt DelayPermissionsPrompt |
| #endif |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| MAYBE_DelayPermissionsPrompt) { |
| // Navigate to a page so we can request permissions |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| |
| content::WebContents* contents = |
| browser()->tab_strip_model()->GetActiveWebContents(); |
| ASSERT_TRUE(contents); |
| |
| permissions::PermissionRequestObserver observer(contents); |
| |
| // Request permission in tab under overlay. |
| EXPECT_TRUE(content::ExecJs( |
| contents->GetPrimaryMainFrame(), kRequestNotificationsScript, |
| content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); |
| |
| // Verify no prompt was shown |
| observer.Wait(); |
| EXPECT_FALSE(observer.request_shown()); |
| |
| // Close overlay |
| CloseOverlayAndWaitForOff(controller, |
| LensOverlayDismissalSource::kOverlayCloseButton); |
| |
| // Verify a prompt was shown |
| ASSERT_TRUE(base::test::RunUntil([&]() { return observer.request_shown(); })); |
| } |
| |
| // TODO(b/335801964): Test flaky on Mac. |
| #if BUILDFLAG(IS_MAC) |
| #define MAYBE_SidePanelInteractionsAfterRegionSelection \ |
| DISABLED_SidePanelInteractionsAfterRegionSelection |
| #else |
| #define MAYBE_SidePanelInteractionsAfterRegionSelection \ |
| SidePanelInteractionsAfterRegionSelection |
| #endif |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| MAYBE_SidePanelInteractionsAfterRegionSelection) { |
| WaitForPaint(); |
| |
| std::string text_query = "Apples"; |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| EXPECT_TRUE(controller->GetThumbnailForTesting().empty()); |
| EXPECT_EQ(controller->GetPageClassificationForTesting(), |
| metrics::OmniboxEventProto::SEARCH_SIDE_PANEL_SEARCHBOX); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| // We need to flush the mojo receiver calls to make sure the screenshot was |
| // passed back to the WebUI or else the region selection UI will not render. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| fake_controller->FlushForTesting(); |
| ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| |
| // Simulate mouse events on the overlay for drawing a manual region. |
| gfx::Point center = |
| GetOverlayWebContents()->GetContainerBounds().CenterPoint(); |
| gfx::Point off_center = gfx::Point(center); |
| off_center.Offset(100, 100); |
| SimulateLeftClickDrag(center, off_center); |
| |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlayAndResults; })); |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| // Expect the Lens Overlay results panel to open. |
| ASSERT_TRUE(coordinator->IsSidePanelShowing()); |
| EXPECT_EQ(coordinator->GetCurrentEntryId(), |
| SidePanelEntry::Id::kLensOverlayResults); |
| |
| // Verify that the side panel searchbox displays a thumbnail and that the |
| // controller has a copy. |
| ASSERT_TRUE(base::test::RunUntil([&]() { |
| return true == |
| content::EvalJs( |
| controller->GetSidePanelWebContentsForTesting(), |
| content::JsReplace(kCheckSidePanelThumbnailShownScript)); |
| })); |
| EXPECT_FALSE(controller->GetSelectedTextForTesting().has_value()); |
| EXPECT_FALSE(controller->GetSelectedRegionForTesting().is_null()); |
| EXPECT_TRUE(base::StartsWith(controller->GetThumbnailForTesting(), "data:")); |
| EXPECT_EQ(controller->GetPageClassificationForTesting(), |
| metrics::OmniboxEventProto::LENS_SIDE_PANEL_SEARCHBOX); |
| |
| // Verify that after text selection, the controller has a copy of the text, |
| // the thumbnail is no longer shown and the controller's copy of the |
| // thumbnail is empty. |
| controller->IssueTextSelectionRequestForTesting(text_query, |
| /*selection_start_index=*/10, |
| /*selection_end_index=*/16); |
| EXPECT_TRUE(content::EvalJs( |
| controller->GetSidePanelWebContentsForTesting() |
| ->GetPrimaryMainFrame(), |
| content::JsReplace(kCheckSearchboxInput, text_query), |
| content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES) |
| .ExtractBool()); |
| EXPECT_TRUE(controller->GetSelectedTextForTesting().has_value()); |
| EXPECT_TRUE(controller->GetSelectedRegionForTesting().is_null()); |
| EXPECT_TRUE(controller->GetThumbnailForTesting().empty()); |
| EXPECT_EQ(controller->GetPageClassificationForTesting(), |
| metrics::OmniboxEventProto::SEARCH_SIDE_PANEL_SEARCHBOX); |
| |
| // Verify that after a signal from the searchbox that the text was modified, |
| // no text selection is present. |
| EXPECT_FALSE(fake_controller->fake_overlay_page_.did_clear_text_selection_); |
| controller->OnTextModifiedForTesting(); |
| EXPECT_FALSE(controller->GetSelectedTextForTesting().has_value()); |
| fake_controller->FlushForTesting(); |
| EXPECT_TRUE(fake_controller->fake_overlay_page_.did_clear_text_selection_); |
| } |
| |
| // TODO(b/335028577): Test flaky on Mac. |
| #if BUILDFLAG(IS_MAC) |
| #define MAYBE_ShowSidePanelAfterTextSelectionRequest \ |
| DISABLED_ShowSidePanelAfterTextSelectionRequest |
| #else |
| #define MAYBE_ShowSidePanelAfterTextSelectionRequest \ |
| ShowSidePanelAfterTextSelectionRequest |
| #endif |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| MAYBE_ShowSidePanelAfterTextSelectionRequest) { |
| WaitForPaint(); |
| |
| std::string text_query = "Apples"; |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| |
| controller->IssueTextSelectionRequestForTesting(text_query, |
| /*selection_start_index=*/0, |
| /*selection_end_index=*/0); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlayAndResults; })); |
| |
| // Expect the Lens Overlay results panel to open. |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| EXPECT_TRUE(coordinator->IsSidePanelEntryShowing( |
| SidePanelEntryKey(SidePanelEntry::Id::kLensOverlayResults))); |
| |
| // Verify that the side panel displays our query. |
| ASSERT_TRUE(base::test::RunUntil([&]() { |
| return true == |
| content::EvalJs(controller->GetSidePanelWebContentsForTesting(), |
| content::JsReplace( |
| kCheckSidePanelResultsLoadedScript, text_query)); |
| })); |
| } |
| |
| // TODO(b/335028577): Test flaky on Mac. |
| #if BUILDFLAG(IS_MAC) |
| #define MAYBE_ShowSidePanelAfterTranslateSelectionRequest \ |
| DISABLED_ShowSidePanelAfterTranslateSelectionRequest |
| #else |
| #define MAYBE_ShowSidePanelAfterTranslateSelectionRequest \ |
| ShowSidePanelAfterTranslateSelectionRequest |
| #endif |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| MAYBE_ShowSidePanelAfterTranslateSelectionRequest) { |
| WaitForPaint(); |
| |
| std::string text_query = "Manzanas"; |
| std::string content_language = "es"; |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| |
| controller->IssueTranslateSelectionRequestForTesting( |
| text_query, content_language, |
| /*selection_start_index=*/0, |
| /*selection_end_index=*/0); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlayAndResults; })); |
| |
| // Expect the Lens Overlay results panel to open. |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| EXPECT_TRUE(coordinator->IsSidePanelEntryShowing( |
| SidePanelEntryKey(SidePanelEntry::Id::kLensOverlayResults))); |
| |
| // Verify that the side panel displays our query. |
| ASSERT_TRUE(base::test::RunUntil([&]() { |
| return true == |
| content::EvalJs( |
| controller->GetSidePanelWebContentsForTesting(), |
| content::JsReplace(kCheckSidePanelTranslateResultsLoadedScript, |
| text_query, content_language)); |
| })); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| HandleStartQueryResponse) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Before showing the UI, there should be no set objects or text as |
| // no query flow has started. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| EXPECT_TRUE( |
| fake_controller->fake_overlay_page_.last_received_objects_.empty()); |
| EXPECT_FALSE(fake_controller->fake_overlay_page_.last_received_text_); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| // When the overlay is bound, it should start the query flow which returns a |
| // response for the full image callback. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| |
| // Prevent flakiness by flushing the tasks. |
| fake_controller->FlushForTesting(); |
| |
| // After flushing the mojo calls, the data should be present. |
| EXPECT_FALSE( |
| fake_controller->fake_overlay_page_.last_received_objects_.empty()); |
| |
| auto* object = |
| fake_controller->fake_overlay_page_.last_received_objects_[0].get(); |
| auto* text = fake_controller->fake_overlay_page_.last_received_text_.get(); |
| EXPECT_TRUE(object); |
| EXPECT_TRUE(text); |
| EXPECT_TRUE(kTestOverlayObject->Equals(*object)); |
| EXPECT_TRUE(kTestText->Equals(*text)); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| HandleInteractionDataResponse) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Before showing the UI, there should be no suggest signals as no query flow |
| // has started. |
| EXPECT_FALSE(controller->GetLensResponseForTesting().has_suggest_signals()); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| // When the overlay is bound, it should start the query flow which returns a |
| // response for the full image callback. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| |
| // The lens response should have been correctly set for use by the searchbox. |
| EXPECT_TRUE(controller->GetLensResponseForTesting().has_suggest_signals()); |
| EXPECT_EQ(controller->GetLensResponseForTesting().suggest_signals(), |
| kTestSuggestSignals); |
| |
| // TODO(b/335234545): The current page URL should be made available for use by |
| // the searchbox. |
| EXPECT_TRUE(controller->GetPageURLForTesting().is_empty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| BackgroundAndForegroundUI) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Grab the index of the currently active tab so we can return to it later. |
| int active_controller_tab_index = |
| browser()->tab_strip_model()->active_index(); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| EXPECT_TRUE(controller->GetOverlayWidgetForTesting()->IsVisible()); |
| |
| // Open a side panel to test that the side panel persists between tab |
| // switches. |
| controller->results_side_panel_coordinator()->RegisterEntryAndShow(); |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| EXPECT_TRUE(coordinator->IsSidePanelShowing()); |
| EXPECT_EQ(coordinator->GetCurrentEntryId(), |
| SidePanelEntry::Id::kLensOverlayResults); |
| |
| // Opening a new tab should background the overlay UI. |
| WaitForPaint(WindowOpenDisposition::NEW_FOREGROUND_TAB, |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB | |
| ui_test_utils::BROWSER_TEST_WAIT_FOR_LOAD_STOP); |
| EXPECT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kBackground; })); |
| EXPECT_FALSE(controller->GetOverlayWidgetForTesting()->IsVisible()); |
| EXPECT_TRUE(base::test::RunUntil( |
| [&]() { return !coordinator->IsSidePanelShowing(); })); |
| |
| // Returning back to the previous tab should show the overlay UI again. |
| browser()->tab_strip_model()->ActivateTabAt(active_controller_tab_index); |
| EXPECT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| EXPECT_TRUE(controller->GetOverlayWidgetForTesting()->IsVisible()); |
| // Side panel should come back when returning to previous tab. |
| EXPECT_TRUE(base::test::RunUntil( |
| [&]() { return coordinator->IsSidePanelShowing(); })); |
| EXPECT_EQ(coordinator->GetCurrentEntryId(), |
| SidePanelEntry::Id::kLensOverlayResults); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| LoadURLInResultsFrame) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| EXPECT_TRUE(controller->GetOverlayWidgetForTesting()->IsVisible()); |
| |
| // Side panel is not showing at first. |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| EXPECT_FALSE(coordinator->IsSidePanelShowing()); |
| |
| // Loading a url in the side panel should show the results page. |
| const GURL search_url("https://www.google.com/search"); |
| controller->LoadURLInResultsFrame(search_url); |
| |
| // Expect the Lens Overlay results panel to open. |
| ASSERT_TRUE(coordinator->IsSidePanelShowing()); |
| EXPECT_EQ(coordinator->GetCurrentEntryId(), |
| SidePanelEntry::Id::kLensOverlayResults); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| LoadURLInResultsFrameOverlayNotShowing) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| const GURL search_url("https://www.google.com/search"); |
| controller->LoadURLInResultsFrame(search_url); |
| |
| // Controller should not open and load URLs when overlay is not showing. |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| EXPECT_FALSE(coordinator->IsSidePanelShowing()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| SidePanel_SameTabSameOriginLinkClick) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| EXPECT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| EXPECT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| EXPECT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| EXPECT_TRUE(controller->GetOverlayWidgetForTesting()->IsVisible()); |
| |
| // Loading a url in the side panel should show the results page. This needs to |
| // be done to set up the WebContentsObserver. |
| const GURL search_url("https://www.google.com/search"); |
| controller->LoadURLInResultsFrame(search_url); |
| |
| // Expect the Lens Overlay results panel to open. |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| EXPECT_TRUE(coordinator->IsSidePanelShowing()); |
| EXPECT_EQ(coordinator->GetCurrentEntryId(), |
| SidePanelEntry::Id::kLensOverlayResults); |
| EXPECT_TRUE(content::WaitForLoadStop( |
| controller->GetSidePanelWebContentsForTesting())); |
| int tabs = browser()->tab_strip_model()->count(); |
| |
| // Verify the fake controller exists and reset any loading that was done |
| // before as part of setup. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| fake_controller->ResetLoadingTracking(); |
| |
| // The results frame should be the only child frame of the side panel web |
| // contents. |
| content::RenderFrameHost* results_frame = content::ChildFrameAt( |
| controller->GetSidePanelWebContentsForTesting()->GetPrimaryMainFrame(), |
| 0); |
| EXPECT_TRUE(results_frame); |
| |
| // Simulate a same-origin navigation on the results frame. |
| const GURL nav_url("https://www.google.com/search?q=apples"); |
| content::TestNavigationObserver observer( |
| controller->GetSidePanelWebContentsForTesting()); |
| EXPECT_TRUE(content::ExecJs( |
| results_frame, content::JsReplace(kSameTabLinkClickScript, nav_url), |
| content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); |
| |
| // Wait for the navigation to finish and the page to finish loading. |
| observer.WaitForNavigationFinished(); |
| EXPECT_TRUE(content::WaitForLoadStop( |
| controller->GetSidePanelWebContentsForTesting())); |
| |
| // It should not open a new tab as this is a same-origin navigation. |
| EXPECT_EQ(tabs, browser()->tab_strip_model()->count()); |
| |
| VerifySearchQueryParameters(observer.last_navigation_url()); |
| VerifyTextQueriesAreEqual(observer.last_navigation_url(), nav_url); |
| |
| // Verify the loading state was set correctly. |
| // Loading is set to true twice because the URL is originally malformed. |
| EXPECT_EQ(fake_controller->is_side_panel_loading_set_to_true_, 2); |
| EXPECT_EQ(fake_controller->is_side_panel_loading_set_to_false_, 1); |
| |
| // We should find that the input text on the searchbox is the same as the text |
| // query of the nav_url. |
| EXPECT_TRUE(content::EvalJs( |
| controller->GetSidePanelWebContentsForTesting() |
| ->GetPrimaryMainFrame(), |
| content::JsReplace(kCheckSearchboxInput, "apples"), |
| content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES) |
| .ExtractBool()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| SidePanel_SameTabCrossOriginLinkClick) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| EXPECT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| EXPECT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| EXPECT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| EXPECT_TRUE(controller->GetOverlayWidgetForTesting()->IsVisible()); |
| |
| // Loading a url in the side panel should show the results page. This needs to |
| // be done to set up the WebContentsObserver. |
| const GURL search_url("https://www.google.com/search"); |
| controller->LoadURLInResultsFrame(search_url); |
| |
| // Expect the Lens Overlay results panel to open. |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| EXPECT_TRUE(coordinator->IsSidePanelShowing()); |
| EXPECT_EQ(coordinator->GetCurrentEntryId(), |
| SidePanelEntry::Id::kLensOverlayResults); |
| EXPECT_TRUE(content::WaitForLoadStop( |
| controller->GetSidePanelWebContentsForTesting())); |
| |
| // The results frame should be the only child frame of the side panel web |
| // contents. |
| content::RenderFrameHost* results_frame = content::ChildFrameAt( |
| controller->GetSidePanelWebContentsForTesting()->GetPrimaryMainFrame(), |
| 0); |
| EXPECT_TRUE(results_frame); |
| |
| // Verify the fake controller exists and reset any loading that was done |
| // before as part of setup. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| fake_controller->ResetLoadingTracking(); |
| |
| ui_test_utils::AllBrowserTabAddedWaiter add_tab; |
| const GURL nav_url("http://new.domain.com/"); |
| // Simulate a cross-origin navigation on the results frame. |
| EXPECT_TRUE(content::ExecJs( |
| results_frame, content::JsReplace(kSameTabLinkClickScript, nav_url), |
| content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); |
| |
| // Verify the new tab has the URL. |
| content::WebContents* new_tab = add_tab.Wait(); |
| content::WaitForLoadStop(new_tab); |
| EXPECT_EQ(new_tab->GetLastCommittedURL(), nav_url); |
| |
| // Verify the loading state was never set. |
| EXPECT_EQ(fake_controller->is_side_panel_loading_set_to_true_, 0); |
| EXPECT_EQ(fake_controller->is_side_panel_loading_set_to_false_, 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| SidePanel_NewTabCrossOriginLinkClick) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| EXPECT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should change the state to screenshot and eventually to overlay. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_EQ(controller->state(), State::kScreenshot); |
| EXPECT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| EXPECT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| EXPECT_TRUE(controller->GetOverlayWidgetForTesting()->IsVisible()); |
| |
| // Loading a url in the side panel should show the results page. This needs to |
| // be done to set up the WebContentsObserver. |
| const GURL search_url("https://www.google.com/search"); |
| controller->LoadURLInResultsFrame(search_url); |
| |
| // Expect the Lens Overlay results panel to open. |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| EXPECT_TRUE(coordinator->IsSidePanelShowing()); |
| EXPECT_EQ(coordinator->GetCurrentEntryId(), |
| SidePanelEntry::Id::kLensOverlayResults); |
| EXPECT_TRUE(content::WaitForLoadStop( |
| controller->GetSidePanelWebContentsForTesting())); |
| |
| // The results frame should be the only child frame of the side panel web |
| // contents. |
| content::RenderFrameHost* results_frame = content::ChildFrameAt( |
| controller->GetSidePanelWebContentsForTesting()->GetPrimaryMainFrame(), |
| 0); |
| const GURL nav_url("http://new.domain.com/"); |
| content::OverrideLastCommittedOrigin(results_frame, |
| url::Origin::Create(search_url)); |
| EXPECT_TRUE(results_frame); |
| |
| // Verify the fake controller exists and reset any loading that was done |
| // before as part of setup. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| fake_controller->ResetLoadingTracking(); |
| |
| // Simulate a cross-origin navigation on the results frame. |
| ui_test_utils::AllBrowserTabAddedWaiter add_tab; |
| EXPECT_TRUE(content::ExecJs( |
| results_frame, content::JsReplace(kNewTabLinkClickScript, nav_url), |
| content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); |
| |
| // Verify the new tab has the URL. |
| content::WebContents* new_tab = add_tab.Wait(); |
| content::WaitForLoadStop(new_tab); |
| EXPECT_EQ(new_tab->GetLastCommittedURL(), nav_url); |
| // Verify the loading state was never set. |
| EXPECT_EQ(fake_controller->is_side_panel_loading_set_to_true_, 0); |
| EXPECT_EQ(fake_controller->is_side_panel_loading_set_to_false_, 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| SidePanel_NewTabCrossOriginLinkClickFromUntrustedSite) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| EXPECT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should eventually result in overlay state. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| EXPECT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| EXPECT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| EXPECT_TRUE(controller->GetOverlayWidgetForTesting()->IsVisible()); |
| |
| // Loading a url in the side panel should show the results page. This needs to |
| // be done to set up the WebContentsObserver. |
| const GURL search_url("https://www.google.com/search"); |
| controller->LoadURLInResultsFrame(search_url); |
| |
| // Expect the Lens Overlay results panel to open. |
| auto* coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| EXPECT_TRUE(coordinator->IsSidePanelShowing()); |
| EXPECT_EQ(coordinator->GetCurrentEntryId(), |
| SidePanelEntry::Id::kLensOverlayResults); |
| EXPECT_TRUE(content::WaitForLoadStop( |
| controller->GetSidePanelWebContentsForTesting())); |
| int tabs = browser()->tab_strip_model()->count(); |
| |
| // The results frame should be the only child frame of the side panel web |
| // contents. |
| content::RenderFrameHost* results_frame = content::ChildFrameAt( |
| controller->GetSidePanelWebContentsForTesting()->GetPrimaryMainFrame(), |
| 0); |
| const GURL nav_url("http://new.domain.com/"); |
| content::OverrideLastCommittedOrigin(results_frame, |
| url::Origin::Create(nav_url)); |
| EXPECT_TRUE(results_frame); |
| |
| // Verify the fake controller exists and reset any loading that was done |
| // before as part of setup. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| fake_controller->ResetLoadingTracking(); |
| |
| // Simulate a cross-origin navigation on the results frame. |
| EXPECT_TRUE(content::ExecJs( |
| results_frame, content::JsReplace(kNewTabLinkClickScript, nav_url), |
| content::EvalJsOptions::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES)); |
| |
| // It should not open a new tab as the initatior origin should not be |
| // considered "trusted". |
| EXPECT_EQ(tabs, browser()->tab_strip_model()->count()); |
| // Verify the loading state was never set. |
| EXPECT_EQ(fake_controller->is_side_panel_loading_set_to_true_, 0); |
| EXPECT_EQ(fake_controller->is_side_panel_loading_set_to_false_, 0); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| CloseSearchBubbleOnOverlayInteraction) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should eventually result in overlay state. When the overlay is |
| // bound, it should start the query flow which returns a response for the |
| // interaction data callback. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| |
| auto* bubble_controller = |
| lens::LensSearchBubbleController::FromBrowser(browser()); |
| EXPECT_TRUE(!!bubble_controller->bubble_view_for_testing()); |
| |
| // We need to flush the mojo receiver calls to make sure the screenshot was |
| // passed back to the WebUI or else the region selection UI will not render. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| fake_controller->FlushForTesting(); |
| ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| |
| // Simulate mouse events on the overlay for drawing a manual region. |
| gfx::Point center = |
| GetOverlayWebContents()->GetContainerBounds().CenterPoint(); |
| gfx::Point off_center = gfx::Point(center); |
| off_center.Offset(100, 100); |
| SimulateLeftClickDrag(center, off_center); |
| |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlayAndResults; })); |
| EXPECT_FALSE(!!bubble_controller->bubble_view_for_testing()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| PopAndLoadQueryFromHistory) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should eventually result in overlay state. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| EXPECT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| EXPECT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| EXPECT_TRUE(controller->GetOverlayWidgetForTesting()->IsVisible()); |
| |
| // Loading a url in the side panel should show the results page. |
| const GURL first_search_url( |
| "https://www.google.com/" |
| "search?cs=0&source=chrome.cr.menu&q=oranges&lns_mode=text&gsc=1&" |
| "hl=en-US"); |
| controller->LoadURLInResultsFrame(first_search_url); |
| EXPECT_TRUE(content::WaitForLoadStop( |
| controller->GetSidePanelWebContentsForTesting())); |
| |
| // The search query history stack should be empty and the currently loaded |
| // query should be set. |
| EXPECT_TRUE(controller->GetSearchQueryHistoryForTesting().empty()); |
| auto loaded_search_query = controller->GetLoadedSearchQueryForTesting(); |
| EXPECT_TRUE(loaded_search_query); |
| EXPECT_EQ(loaded_search_query->search_query_text_, "oranges"); |
| VerifySearchQueryParameters(loaded_search_query->search_query_url_); |
| VerifyTextQueriesAreEqual(loaded_search_query->search_query_url_, |
| first_search_url); |
| EXPECT_TRUE(loaded_search_query->selected_region_thumbnail_uri_.empty()); |
| EXPECT_FALSE(loaded_search_query->selected_region_); |
| EXPECT_FALSE(loaded_search_query->selected_text_); |
| |
| // Loading a second url in the side panel should show the results page. |
| const GURL second_search_url( |
| "https://www.google.com/" |
| "search?cs=0&source=chrome.cr.menu&q=kiwi&lns_mode=text&gsc=1&hl=" |
| "en-US"); |
| // We can't use content::WaitForLoadStop here since the last navigation is |
| // successful. |
| content::TestNavigationObserver observer( |
| controller->GetSidePanelWebContentsForTesting()); |
| controller->LoadURLInResultsFrame(second_search_url); |
| observer.WaitForNavigationFinished(); |
| |
| // The search query history stack should have 1 entry and the currently loaded |
| // query should be set to the new query |
| EXPECT_EQ(controller->GetSearchQueryHistoryForTesting().size(), 1UL); |
| loaded_search_query = controller->GetLoadedSearchQueryForTesting(); |
| EXPECT_TRUE(loaded_search_query); |
| EXPECT_EQ(loaded_search_query->search_query_text_, "kiwi"); |
| VerifySearchQueryParameters(loaded_search_query->search_query_url_); |
| VerifyTextQueriesAreEqual(loaded_search_query->search_query_url_, |
| second_search_url); |
| EXPECT_TRUE(loaded_search_query->selected_region_thumbnail_uri_.empty()); |
| EXPECT_FALSE(loaded_search_query->selected_region_); |
| EXPECT_FALSE(loaded_search_query->selected_text_); |
| VerifySearchQueryParameters(observer.last_navigation_url()); |
| VerifyTextQueriesAreEqual(observer.last_navigation_url(), second_search_url); |
| // Popping the query should load the previous query into the results frame. |
| content::TestNavigationObserver pop_observer( |
| controller->GetSidePanelWebContentsForTesting()); |
| controller->PopAndLoadQueryFromHistory(); |
| pop_observer.WaitForNavigationFinished(); |
| |
| // The search query history stack should be empty and the currently loaded |
| // query should be set to the previous query. |
| EXPECT_TRUE(controller->GetSearchQueryHistoryForTesting().empty()); |
| loaded_search_query = controller->GetLoadedSearchQueryForTesting(); |
| EXPECT_TRUE(loaded_search_query); |
| EXPECT_EQ(loaded_search_query->search_query_text_, "oranges"); |
| VerifySearchQueryParameters(loaded_search_query->search_query_url_); |
| VerifyTextQueriesAreEqual(loaded_search_query->search_query_url_, |
| first_search_url); |
| EXPECT_TRUE(loaded_search_query->selected_region_thumbnail_uri_.empty()); |
| EXPECT_FALSE(loaded_search_query->selected_region_); |
| EXPECT_FALSE(loaded_search_query->selected_text_); |
| VerifySearchQueryParameters(pop_observer.last_navigation_url()); |
| VerifyTextQueriesAreEqual(pop_observer.last_navigation_url(), |
| first_search_url); |
| } |
| |
| // TODO(crbug.com/342390515): Test flaky on Windows. |
| #if BUILDFLAG(IS_WIN) |
| #define MAYBE_PopAndLoadQueryFromHistoryWithRegionAndTextSelection \ |
| DISABLED_PopAndLoadQueryFromHistoryWithRegionAndTextSelection |
| #else |
| #define MAYBE_PopAndLoadQueryFromHistoryWithRegionAndTextSelection \ |
| PopAndLoadQueryFromHistoryWithRegionAndTextSelection |
| #endif |
| IN_PROC_BROWSER_TEST_F( |
| LensOverlayControllerBrowserTest, |
| MAYBE_PopAndLoadQueryFromHistoryWithRegionAndTextSelection) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should eventually result in overlay state. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| EXPECT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| EXPECT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| EXPECT_TRUE(controller->GetOverlayWidgetForTesting()->IsVisible()); |
| |
| // Issuing a text selection request should show the results page. |
| const GURL first_search_url( |
| "https://www.google.com/" |
| "search?cs=0&source=chrome.cr.menu&q=oranges&lns_mode=text&gsc=1&" |
| "hl=en-US"); |
| controller->IssueTextSelectionRequestForTesting("oranges", 20, 200); |
| EXPECT_TRUE(content::WaitForLoadStop( |
| controller->GetSidePanelWebContentsForTesting())); |
| |
| // The search query history stack should be empty and the currently loaded |
| // query should be set. |
| EXPECT_TRUE(controller->GetSearchQueryHistoryForTesting().empty()); |
| auto loaded_search_query = controller->GetLoadedSearchQueryForTesting(); |
| EXPECT_TRUE(loaded_search_query); |
| EXPECT_EQ(loaded_search_query->search_query_text_, "oranges"); |
| GURL url_without_start_time_or_size = |
| RemoveStartTimeAndSizeParams(loaded_search_query->search_query_url_); |
| EXPECT_EQ(url_without_start_time_or_size, first_search_url); |
| EXPECT_TRUE(loaded_search_query->selected_text_); |
| EXPECT_EQ(loaded_search_query->selected_text_->first, 20); |
| EXPECT_EQ(loaded_search_query->selected_text_->second, 200); |
| EXPECT_TRUE(loaded_search_query->selected_region_thumbnail_uri_.empty()); |
| EXPECT_FALSE(loaded_search_query->selected_region_); |
| |
| // Issuing a region selection request should update the results page. |
| const GURL second_search_url( |
| "https://www.google.com/" |
| "search?cs=0&source=chrome.cr.menu&q=&lns_mode=un&gsc=1&" |
| "hl=en-US"); |
| // We can't use content::WaitForLoadStop here and below since the last |
| // navigation was already successful. |
| content::TestNavigationObserver second_search_observer( |
| controller->GetSidePanelWebContentsForTesting()); |
| controller->IssueLensRequestForTesting(kTestRegion->Clone()); |
| // The full sequnce of events necessary to load Lens search results is not |
| // currently testable, so load the expected URL manually. |
| controller->LoadURLInResultsFrame(second_search_url); |
| second_search_observer.Wait(); |
| |
| // The search query history stack should have 1 entry and the currently loaded |
| // region should be set. |
| EXPECT_EQ(controller->GetSearchQueryHistoryForTesting().size(), 1UL); |
| loaded_search_query.reset(); |
| loaded_search_query = controller->GetLoadedSearchQueryForTesting(); |
| EXPECT_TRUE(loaded_search_query); |
| EXPECT_EQ(loaded_search_query->search_query_text_, std::string()); |
| EXPECT_FALSE(loaded_search_query->selected_text_); |
| EXPECT_FALSE(loaded_search_query->selected_region_thumbnail_uri_.empty()); |
| EXPECT_TRUE(loaded_search_query->selected_region_); |
| |
| // Loading another url in the side panel should update the results page. |
| const GURL third_search_url( |
| "https://www.google.com/" |
| "search?cs=0&source=chrome.cr.menu&q=kiwi&lns_mode=text&gsc=1&hl=" |
| "en-US"); |
| content::TestNavigationObserver third_search_observer( |
| controller->GetSidePanelWebContentsForTesting()); |
| controller->IssueTextSelectionRequestForTesting("kiwi", 1, 100); |
| third_search_observer.Wait(); |
| |
| // The search query history stack should have 2 entries and the currently |
| // loaded query should be set to the new query |
| EXPECT_EQ(controller->GetSearchQueryHistoryForTesting().size(), 2UL); |
| loaded_search_query.reset(); |
| loaded_search_query = controller->GetLoadedSearchQueryForTesting(); |
| EXPECT_TRUE(loaded_search_query); |
| EXPECT_EQ(loaded_search_query->search_query_text_, "kiwi"); |
| url_without_start_time_or_size = |
| RemoveStartTimeAndSizeParams(loaded_search_query->search_query_url_); |
| EXPECT_EQ(url_without_start_time_or_size, third_search_url); |
| EXPECT_TRUE(loaded_search_query->selected_text_); |
| EXPECT_EQ(loaded_search_query->selected_text_->first, 1); |
| EXPECT_EQ(loaded_search_query->selected_text_->second, 100); |
| EXPECT_TRUE(loaded_search_query->selected_region_thumbnail_uri_.empty()); |
| EXPECT_FALSE(loaded_search_query->selected_region_); |
| url_without_start_time_or_size = |
| RemoveStartTimeAndSizeParams(third_search_observer.last_navigation_url()); |
| EXPECT_EQ(url_without_start_time_or_size, third_search_url); |
| |
| // Popping a query should load the previous query into the results frame. |
| content::TestNavigationObserver first_pop_observer( |
| controller->GetSidePanelWebContentsForTesting()); |
| controller->PopAndLoadQueryFromHistory(); |
| first_pop_observer.Wait(); |
| |
| // The search query history stack should have 1 entry and the previously |
| // loaded region should be present. |
| EXPECT_EQ(controller->GetSearchQueryHistoryForTesting().size(), 1UL); |
| loaded_search_query.reset(); |
| loaded_search_query = controller->GetLoadedSearchQueryForTesting(); |
| EXPECT_TRUE(loaded_search_query); |
| EXPECT_EQ(loaded_search_query->search_query_text_, std::string()); |
| EXPECT_FALSE(loaded_search_query->selected_text_); |
| EXPECT_FALSE(loaded_search_query->selected_region_thumbnail_uri_.empty()); |
| EXPECT_TRUE(loaded_search_query->selected_region_); |
| |
| // Popping another query should load the original query into the results |
| // frame. |
| content::TestNavigationObserver second_pop_observer( |
| controller->GetSidePanelWebContentsForTesting()); |
| controller->PopAndLoadQueryFromHistory(); |
| second_pop_observer.Wait(); |
| |
| // The search query history stack should be empty and the currently loaded |
| // query should be set to the original query. |
| EXPECT_TRUE(controller->GetSearchQueryHistoryForTesting().empty()); |
| loaded_search_query.reset(); |
| loaded_search_query = controller->GetLoadedSearchQueryForTesting(); |
| EXPECT_TRUE(loaded_search_query); |
| EXPECT_EQ(loaded_search_query->search_query_text_, "oranges"); |
| url_without_start_time_or_size = |
| RemoveStartTimeAndSizeParams(loaded_search_query->search_query_url_); |
| EXPECT_EQ(url_without_start_time_or_size, first_search_url); |
| EXPECT_TRUE(loaded_search_query->selected_region_thumbnail_uri_.empty()); |
| EXPECT_FALSE(loaded_search_query->selected_region_); |
| EXPECT_TRUE(loaded_search_query->selected_text_); |
| EXPECT_EQ(loaded_search_query->selected_text_->first, 20); |
| EXPECT_EQ(loaded_search_query->selected_text_->second, 200); |
| url_without_start_time_or_size = |
| RemoveStartTimeAndSizeParams(second_pop_observer.last_navigation_url()); |
| EXPECT_EQ(url_without_start_time_or_size, first_search_url); |
| |
| // Verify the text selection was sent back to mojo and any old selections were |
| // cleared. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| EXPECT_TRUE(fake_controller->fake_overlay_page_.did_clear_text_selection_); |
| EXPECT_TRUE(fake_controller->fake_overlay_page_.did_clear_region_selection_); |
| EXPECT_EQ(fake_controller->fake_overlay_page_.text_selection_indexes_, |
| loaded_search_query->selected_text_); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| AddQueryToHistoryAfterResize) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should eventually result in overlay state. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| EXPECT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| EXPECT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| EXPECT_TRUE(controller->GetOverlayWidgetForTesting()->IsVisible()); |
| |
| // Loading a url in the side panel should show the results page. |
| const GURL first_search_url( |
| "https://www.google.com/search?q=oranges&gsc=1&hl=en-US"); |
| controller->LoadURLInResultsFrame(first_search_url); |
| EXPECT_TRUE(content::WaitForLoadStop( |
| controller->GetSidePanelWebContentsForTesting())); |
| |
| // Loading a second url in the side panel should show the results page. |
| const GURL second_search_url( |
| "https://www.google.com/search?q=kiwi&gsc=1&hl=en-US"); |
| // We can't use content::WaitForLoadStop here since the last navigation is |
| // successful. |
| content::TestNavigationObserver observer( |
| controller->GetSidePanelWebContentsForTesting()); |
| controller->LoadURLInResultsFrame(second_search_url); |
| observer.WaitForNavigationFinished(); |
| |
| // Make the side panel larger. |
| const int increment = -50; |
| BrowserView::GetBrowserViewForBrowser(browser()) |
| ->unified_side_panel() |
| ->OnResize(increment, true); |
| // Popping the query should load the previous query into the results frame. |
| content::TestNavigationObserver pop_observer( |
| controller->GetSidePanelWebContentsForTesting()); |
| controller->PopAndLoadQueryFromHistory(); |
| pop_observer.WaitForNavigationFinished(); |
| // The search query history stack should be empty and the currently loaded |
| // query should be set to the previous query. |
| EXPECT_TRUE(controller->GetSearchQueryHistoryForTesting().empty()); |
| } |
| |
| #if BUILDFLAG(IS_MAC) |
| #define MAYBE_RecordInvocationAndDismissalHistograms \ |
| DISABLED_RecordInvocationAndDismissalHistograms |
| #else |
| #define MAYBE_RecordInvocationAndDismissalHistograms \ |
| RecordInvocationAndDismissalHistograms |
| #endif |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| MAYBE_RecordInvocationAndDismissalHistograms) { |
| base::HistogramTester histogram_tester; |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing the UI and then closing it should record an entry in the |
| // appropriate buckets and the total count of invocations and dismissals |
| // should be 1. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| ASSERT_TRUE(content::WaitForLoadStop(GetOverlayWebContents())); |
| histogram_tester.ExpectBucketCount("Lens.Overlay.Invoked", |
| LensOverlayInvocationSource::kAppMenu, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked", |
| /*expected_count=*/1); |
| CloseOverlayAndWaitForOff(controller, |
| LensOverlayDismissalSource::kOverlayCloseButton); |
| histogram_tester.ExpectBucketCount( |
| "Lens.Overlay.Dismissed", LensOverlayDismissalSource::kOverlayCloseButton, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed", |
| /*expected_count=*/1); |
| |
| // Attempting to invoke the overlay twice without closing it in between |
| // should record only a single new entry. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| histogram_tester.ExpectBucketCount("Lens.Overlay.Invoked", |
| LensOverlayInvocationSource::kAppMenu, |
| /*expected_count=*/2); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked", |
| /*expected_count=*/2); |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| histogram_tester.ExpectBucketCount("Lens.Overlay.Invoked", |
| LensOverlayInvocationSource::kAppMenu, |
| /*expected_count=*/2); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked", |
| /*expected_count=*/2); |
| |
| // Attempting to close the overlay twice without opening it in between should |
| // only record a single entry. |
| CloseOverlayAndWaitForOff(controller, |
| LensOverlayDismissalSource::kOverlayCloseButton); |
| histogram_tester.ExpectBucketCount( |
| "Lens.Overlay.Dismissed", LensOverlayDismissalSource::kOverlayCloseButton, |
| /*expected_count=*/2); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed", |
| /*expected_count=*/2); |
| CloseOverlayAndWaitForOff(controller, |
| LensOverlayDismissalSource::kOverlayCloseButton); |
| histogram_tester.ExpectBucketCount( |
| "Lens.Overlay.Dismissed", LensOverlayDismissalSource::kOverlayCloseButton, |
| /*expected_count=*/2); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed", |
| /*expected_count=*/2); |
| |
| // Each type of invocation and dismissal should record entries in the |
| // appropriate buckets. |
| controller->ShowUI(LensOverlayInvocationSource::kContentAreaContextMenuPage); |
| histogram_tester.ExpectBucketCount( |
| "Lens.Overlay.Invoked", |
| LensOverlayInvocationSource::kContentAreaContextMenuPage, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked", |
| /*expected_count=*/3); |
| CloseOverlayAndWaitForOff( |
| controller, LensOverlayDismissalSource::kOverlayBackgroundClick); |
| histogram_tester.ExpectBucketCount( |
| "Lens.Overlay.Dismissed", |
| LensOverlayDismissalSource::kOverlayBackgroundClick, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed", |
| /*expected_count=*/3); |
| |
| controller->ShowUI(LensOverlayInvocationSource::kContentAreaContextMenuImage); |
| histogram_tester.ExpectBucketCount( |
| "Lens.Overlay.Invoked", |
| LensOverlayInvocationSource::kContentAreaContextMenuImage, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked", |
| /*expected_count=*/4); |
| CloseOverlayAndWaitForOff(controller, |
| LensOverlayDismissalSource::kSidePanelCloseButton); |
| histogram_tester.ExpectBucketCount( |
| "Lens.Overlay.Dismissed", |
| LensOverlayDismissalSource::kSidePanelCloseButton, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed", |
| /*expected_count=*/4); |
| |
| controller->ShowUI(LensOverlayInvocationSource::kToolbar); |
| histogram_tester.ExpectBucketCount("Lens.Overlay.Invoked", |
| LensOverlayInvocationSource::kToolbar, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked", |
| /*expected_count=*/5); |
| CloseOverlayAndWaitForOff(controller, LensOverlayDismissalSource::kToolbar); |
| histogram_tester.ExpectBucketCount("Lens.Overlay.Dismissed", |
| LensOverlayDismissalSource::kToolbar, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed", |
| /*expected_count=*/5); |
| |
| controller->ShowUI(LensOverlayInvocationSource::kFindInPage); |
| histogram_tester.ExpectBucketCount("Lens.Overlay.Invoked", |
| LensOverlayInvocationSource::kFindInPage, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked", |
| /*expected_count=*/6); |
| CloseOverlayAndWaitForOff(controller, |
| LensOverlayDismissalSource::kPageChanged); |
| histogram_tester.ExpectBucketCount("Lens.Overlay.Dismissed", |
| LensOverlayDismissalSource::kPageChanged, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed", |
| /*expected_count=*/6); |
| |
| controller->ShowUI(LensOverlayInvocationSource::kOmnibox); |
| histogram_tester.ExpectBucketCount("Lens.Overlay.Invoked", |
| LensOverlayInvocationSource::kOmnibox, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Invoked", |
| /*expected_count=*/7); |
| CloseOverlayAndWaitForOff(controller, |
| LensOverlayDismissalSource::kTabContentsDiscarded); |
| histogram_tester.ExpectBucketCount( |
| "Lens.Overlay.Dismissed", |
| LensOverlayDismissalSource::kTabContentsDiscarded, |
| /*expected_count=*/1); |
| histogram_tester.ExpectTotalCount("Lens.Overlay.Dismissed", |
| /*expected_count=*/7); |
| } |
| |
| // TODO(b/340886492): Fix and reenable test on MSAN. |
| #if defined(MEMORY_SANITIZER) |
| #define MAYBE_OverlayClosesSidePanelBeforeOpening \ |
| DISABLED_OverlayClosesSidePanelBeforeOpening |
| #else |
| #define MAYBE_OverlayClosesSidePanelBeforeOpening \ |
| OverlayClosesSidePanelBeforeOpening |
| #endif |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| MAYBE_OverlayClosesSidePanelBeforeOpening) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Open the side panel |
| auto* side_panel_coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| side_panel_coordinator->Show(SidePanelEntry::Id::kBookmarks); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return side_panel_coordinator->IsSidePanelShowing(); })); |
| |
| // Showing UI should eventually result in overlay state. When the overlay is |
| // bound, it should start the query flow which returns a response for the |
| // interaction data callback. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| |
| // Side panel should now be closed. |
| EXPECT_FALSE(side_panel_coordinator->IsSidePanelShowing()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| OverlayClosesIfSidePanelIsOpened) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should eventually result in overlay state. When the overlay is |
| // bound, it should start the query flow which returns a response for the |
| // interaction data callback. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| |
| // Grab fake controller to test if notify the overlay of being closed. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| EXPECT_FALSE(fake_controller->fake_overlay_page_.did_notify_overlay_closing_); |
| |
| // Open the side panel |
| auto* side_panel_coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| side_panel_coordinator->Show(SidePanelEntry::Id::kBookmarks); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return side_panel_coordinator->IsSidePanelShowing(); })); |
| |
| // Overlay should close. |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOff; })); |
| EXPECT_TRUE(fake_controller->fake_overlay_page_.did_notify_overlay_closing_); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| OverlayClosesIfNewSidePanelEntryAppears) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // Showing UI should eventually result in overlay state. When the overlay is |
| // bound, it should start the query flow which returns a response for the |
| // interaction data callback. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| |
| // Grab fake controller to test if notify the overlay of being closed. |
| auto* fake_controller = static_cast<LensOverlayControllerFake*>(controller); |
| ASSERT_TRUE(fake_controller); |
| EXPECT_FALSE(fake_controller->fake_overlay_page_.did_notify_overlay_closing_); |
| |
| // Open our results panel |
| controller->IssueTextSelectionRequestForTesting("test query", |
| /*selection_start_index=*/0, |
| /*selection_end_index=*/0); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlayAndResults; })); |
| |
| // Open a different side panel |
| auto* side_panel_coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| |
| // Verify the side panel is open |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return side_panel_coordinator->IsSidePanelShowing(); })); |
| |
| side_panel_coordinator->Show(SidePanelEntry::Id::kBookmarks); |
| |
| // Overlay should close. |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOff; })); |
| EXPECT_TRUE(fake_controller->fake_overlay_page_.did_notify_overlay_closing_); |
| } |
| |
| // TODO(b/340886492): Fix and reenable test on MSAN. |
| #if defined(MEMORY_SANITIZER) |
| #define MAYBE_OverlaySidePanelEdgeCaseInteractions \ |
| DISABLED_OverlaySidePanelEdgeCaseInteractions |
| #else |
| #define MAYBE_OverlaySidePanelEdgeCaseInteractions \ |
| OverlaySidePanelEdgeCaseInteractions |
| #endif |
| IN_PROC_BROWSER_TEST_F(LensOverlayControllerBrowserTest, |
| MAYBE_OverlaySidePanelEdgeCaseInteractions) { |
| WaitForPaint(); |
| |
| // State should start in off. |
| auto* controller = browser() |
| ->tab_strip_model() |
| ->GetActiveTab() |
| ->tab_features() |
| ->lens_overlay_controller(); |
| ASSERT_EQ(controller->state(), State::kOff); |
| |
| // First, test the flow if the user has a side panel open, we request to close |
| // it, and then a new side panel opens before the close is finished. |
| // Open the side panel |
| auto* side_panel_coordinator = |
| SidePanelUtil::GetSidePanelCoordinatorForBrowser(browser()); |
| side_panel_coordinator->Show(SidePanelEntry::Id::kBookmarks); |
| ASSERT_TRUE(base::test::RunUntil([&]() { |
| return side_panel_coordinator->IsSidePanelShowing(); |
| })); // issue here |
| |
| // Wait for the side panel to open fully. |
| SidePanel* side_panel = |
| BrowserView::GetBrowserViewForBrowser(browser())->unified_side_panel(); |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return side_panel->GetAnimationValue() >= 1; })); |
| |
| // Showing UI should eventually result in overlay state. When the overlay is |
| // bound, it should start the query flow which returns a response for the |
| // interaction data callback. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| ASSERT_TRUE(controller->state() == State::kClosingOpenedSidePanel); |
| |
| // Wait for the side panel to start closing. |
| ASSERT_TRUE(base::test::RunUntil([&]() { return side_panel->IsClosing(); })); |
| |
| // Reshow the side panel to prevent the side panel from closing. |
| side_panel_coordinator->Show(SidePanelEntry::Id::kBookmarks); |
| |
| // Verify the overlay returns to kOff state. |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOff; })); |
| |
| // Secondly, test the flow if the side panel is open with our results, if we |
| // close our UI and request the close the side panel, we gracefully handle a |
| // new side panel opening which prevents our requested close. |
| controller->ShowUI(LensOverlayInvocationSource::kAppMenu); |
| |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlay; })); |
| |
| // Open our results panel |
| controller->IssueTextSelectionRequestForTesting("test query", |
| /*selection_start_index=*/0, |
| /*selection_end_index=*/0); |
| |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOverlayAndResults; })); |
| |
| // Request a close which will start to close the side panel. |
| controller->CloseUIAsync(LensOverlayDismissalSource::kOverlayBackgroundClick); |
| ASSERT_TRUE(controller->state() == State::kClosingSidePanel); |
| |
| // Reshow the side panel to prevent a the side panel from closing. |
| side_panel_coordinator->Show(SidePanelEntry::Id::kBookmarks); |
| |
| // The overlay eventually returns to kOff state. |
| ASSERT_TRUE(base::test::RunUntil( |
| [&]() { return controller->state() == State::kOff; })); |
| } |
| |
| } // namespace |