1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
|
// 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.
#include "base/i18n/rtl.h"
#include "base/memory/weak_ptr.h"
#include "base/test/run_until.h"
#include "chrome/browser/ui/autofill/chrome_autofill_client.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/browser_window/public/browser_window_features.h"
#include "chrome/browser/ui/user_education/browser_user_education_interface.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/content/browser/test_autofill_client_injector.h"
#include "components/autofill/content/browser/test_autofill_driver_injector.h"
#include "components/autofill/core/browser/filling/filling_product.h"
#include "components/autofill/core/browser/foundations/autofill_client.h"
#include "components/autofill/core/browser/foundations/browser_autofill_manager.h"
#include "components/autofill/core/browser/foundations/browser_autofill_manager_test_api.h"
#include "components/autofill/core/browser/suggestions/suggestion.h"
#include "components/autofill/core/browser/suggestions/suggestion_type.h"
#include "components/autofill/core/browser/test_utils/autofill_test_utils.h"
#include "components/autofill/core/browser/ui/autofill_external_delegate.h"
#include "components/autofill/core/browser/ui/popup_open_enums.h"
#include "components/autofill/core/common/aliases.h"
#include "components/autofill/core/common/autofill_test_utils.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/form_data_test_api.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/feature_engagement/test/scoped_iph_feature_list.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/gfx/geometry/rect_f.h"
#include "url/gurl.h"
namespace autofill {
namespace {
using ::testing::Not;
using ::testing::Optional;
using ::testing::Return;
class TestAutofillExternalDelegate : public AutofillExternalDelegate {
public:
explicit TestAutofillExternalDelegate(
BrowserAutofillManager* autofill_manager,
AutofillClient* autofill_client)
: AutofillExternalDelegate(autofill_manager), client_(*autofill_client) {}
~TestAutofillExternalDelegate() override = default;
void OnSuggestionsShown(base::span<const Suggestion>) override {
++show_counter_;
ui_session_id_at_last_show_ =
client_->GetSessionIdForCurrentAutofillSuggestions();
CHECK(ui_session_id_at_last_show_)
<< "session id should be non-empty directly after show";
}
FillingProduct GetMainFillingProduct() const override {
return FillingProduct::kAutocomplete;
}
int show_counter() const { return show_counter_; }
std::optional<AutofillClient::SuggestionUiSessionId>
ui_session_id_at_last_show() const {
return ui_session_id_at_last_show_;
}
private:
const raw_ref<AutofillClient> client_;
int show_counter_ = 0;
std::optional<AutofillClient::SuggestionUiSessionId>
ui_session_id_at_last_show_;
};
// This test class is needed to make the constructor public.
class TestChromeAutofillClient : public ChromeAutofillClient {
public:
explicit TestChromeAutofillClient(content::WebContents* web_contents)
: ChromeAutofillClient(web_contents) {}
~TestChromeAutofillClient() override = default;
};
class ChromeAutofillClientBrowserTest : public InProcessBrowserTest {
public:
void SetUpInProcessBrowserTestFixture() override {
// `BrowserWindow::MaybeShowFeaturePromo()` doesn't work in tests unless the
// IPH feature is explicitly enabled.
iph_feature_list_.InitAndEnableFeatures(
{feature_engagement::kIPHAutofillAiOptInFeature});
}
void SetUpOnMainThread() override {
ASSERT_TRUE(
ui_test_utils::NavigateToURL(browser(), GURL("https://test.com")));
test_api(browser_autofill_manager())
.SetExternalDelegate(std::make_unique<TestAutofillExternalDelegate>(
&browser_autofill_manager(), client()));
}
TestAutofillExternalDelegate* aed() {
return static_cast<TestAutofillExternalDelegate*>(
test_api(browser_autofill_manager()).external_delegate());
}
TestChromeAutofillClient* client() {
return autofill_client_injector_[web_contents()];
}
ContentAutofillDriver* driver() {
return autofill_driver_injector_[web_contents()->GetPrimaryMainFrame()];
}
BrowserAutofillManager& browser_autofill_manager() {
return static_cast<BrowserAutofillManager&>(driver()->GetAutofillManager());
}
content::WebContents* web_contents() const {
return browser()->tab_strip_model()->GetActiveWebContents();
}
AutofillClient::SuggestionUiSessionId ShowSuggestions(
const gfx::RectF& bounds) {
return client()->ShowAutofillSuggestions(
ChromeAutofillClient::PopupOpenArgs(
bounds, base::i18n::TextDirection::LEFT_TO_RIGHT,
{Suggestion(u"test", SuggestionType::kAutocompleteEntry)},
AutofillSuggestionTriggerSource::kFormControlElementClicked,
/*form_control_ax_id=*/0, PopupAnchorType::kField),
test_api(browser_autofill_manager())
.external_delegate()
->GetWeakPtrForTest());
}
// Waits until the suggestions have been at least once more since calling this
// function.
void WaitUntilSuggestionsHaveBeenShown(
const base::Location& location = FROM_HERE) {
EXPECT_TRUE(base::test::RunUntil([this, initial_count =
suggestion_show_counter()]() {
return suggestion_show_counter() > initial_count;
})) << location.ToString()
<< ": Showing Autofill suggestions timed out.";
}
// Returns show many times the suggestions have been shown or updated.
int suggestion_show_counter() { return aed()->show_counter(); }
std::optional<AutofillClient::SuggestionUiSessionId>
ui_session_id_at_last_show() {
return aed()->ui_session_id_at_last_show();
}
private:
test::AutofillBrowserTestEnvironment autofill_test_environment_;
feature_engagement::test::ScopedIphFeatureList iph_feature_list_;
TestAutofillClientInjector<TestChromeAutofillClient>
autofill_client_injector_;
TestAutofillDriverInjector<ContentAutofillDriver> autofill_driver_injector_;
};
// This test displays an autofill field IPH, and then tries to show the Autofill
// Popup on top of it (they would overlap). The expected behaviour is
// that the IPH is hidden and the Autofill Popup is successfully shown.
IN_PROC_BROWSER_TEST_F(ChromeAutofillClientBrowserTest,
AutofillPopupIsShownIfOverlappingWithIph) {
FormData form = test::CreateTestAddressFormData();
test_api(form).field(0).set_bounds(gfx::RectF(10, 10));
client()->ShowAutofillFieldIphForFeature(
form.fields()[0], AutofillClient::IphFeature::kAutofillAi);
// Set the bounds such that the Autofill Popup would overlap with the IPH (the
// IPH is displayed right below `form.fields[0]`, whose bounds are set above).
ShowSuggestions(/*bounds=*/gfx::RectF(100, 100));
WaitUntilSuggestionsHaveBeenShown();
EXPECT_FALSE(
BrowserUserEducationInterface::From(browser())->IsFeaturePromoActive(
feature_engagement::kIPHAutofillAiOptInFeature));
}
IN_PROC_BROWSER_TEST_F(ChromeAutofillClientBrowserTest, SuggestionUiSessionId) {
// Before a popup is showing, no identifier is returned.
EXPECT_EQ(client()->GetSessionIdForCurrentAutofillSuggestions(),
std::nullopt);
// Showing suggestions leads (asynchronously) to showing a popup with the
// identifier returned by ShowAutofillSuggestions.
const AutofillClient::SuggestionUiSessionId first_id =
ShowSuggestions(gfx::RectF(50, 50));
WaitUntilSuggestionsHaveBeenShown();
EXPECT_THAT(ui_session_id_at_last_show(), std::make_optional(first_id));
const AutofillClient::SuggestionUiSessionId second_id =
ShowSuggestions(gfx::RectF(60, 60));
EXPECT_NE(first_id, second_id);
// Since showing suggestions is asynchronous, the identifier returned by
// ShowAutofillSuggestions can be different from the one currently showing.
EXPECT_THAT(client()->GetSessionIdForCurrentAutofillSuggestions(),
Not(Optional(second_id)));
// But once the new popup has been shown, they will be the same.
WaitUntilSuggestionsHaveBeenShown();
EXPECT_THAT(ui_session_id_at_last_show(), Optional(second_id));
// Updating the suggestions does not lead to a new identifier. Note that
// updating the suggestions is synchronous.
const int old_count = suggestion_show_counter();
const bool is_showing_suggestions =
client()->GetSessionIdForCurrentAutofillSuggestions().has_value();
client()->UpdateAutofillSuggestions(
{Suggestion(u"other text", SuggestionType::kAutocompleteEntry)},
FillingProduct::kAutocomplete,
AutofillSuggestionTriggerSource::kUnspecified);
EXPECT_GT(suggestion_show_counter(), old_count);
// It is possible that some external interaction with popup could have led
// to the suggestions hiding. In that case, updating the suggestions should
// be a no-op. Note that this is a "hack" to reduce flakiness for the test.
if (is_showing_suggestions) {
EXPECT_THAT(client()->GetSessionIdForCurrentAutofillSuggestions(),
Optional(second_id));
} else {
EXPECT_EQ(client()->GetSessionIdForCurrentAutofillSuggestions(),
std::nullopt);
}
}
} // namespace
} // namespace autofill
|