CVE-2026-91739
Overview
Background
- `UpdateAutofillDataListValues()`
- An
AutofillClientmethod that pushes updated<datalist>options into the currently visible Autofill suggestion popup. - `LocalFrameToken`
- An unguessable token that uniquely identifies a single renderer frame, used browser-side to authorize per-frame actions.
- `suggestion_controller_`
- The single per-
WebContentscontroller that owns the active Autofill popup and is associated with exactly one frame at a time viacontroller_common_.frame_token. - `AutofillExternalDelegate`
- The per-frame, browser-side object that handles Autofill queries and relays renderer-supplied field data (including datalist options) to the
AutofillClient.
Root Cause Analysis
Pre-patch, ChromeAutofillClient::UpdateAutofillDataListValues gated the update solely on suggestion_controller_ being non-null, and then unconditionally called suggestion_controller_->UpdateDataListValues(options). Because one ChromeAutofillClient and its suggestion_controller_ are shared across every frame in a WebContents, a datalist update that originated in one frame’s AutofillExternalDelegate would be applied to a popup that a different frame had opened. The violated invariant is that only the frame that owns the currently-shown popup may mutate its contents; nothing tied the caller’s frame identity to the controller’s owning frame.
The fix threads the querying field’s frame token (field.global_id().frame_token) down through the AutofillClient::UpdateAutofillDataListValues interface and compares it against the new accessor suggestion_controller_->GetFrameToken(), applying the update only when the tokens match. This adds the missing per-frame authorization check, so the condition is TIGHTENED from “a popup exists” to “a popup exists and the caller owns it.”
LocalFrameToken equality check so a frame can update datalist values only in its own popup.Attack Path
- Victim popup is open
The user focuses a field in frame B, and Autofill shows a suggestion/datalist popup owned by the shared
suggestion_controller_, which is associated with frame B’s token. - Attacker frame issues a query
A malicious or compromised renderer frame A (e.g. a cross-origin subframe in the same tab) sends an Autofill query carrying attacker-chosen
<datalist>options through frame A’sAutofillExternalDelegate. - Unauthorized cross-frame update
Pre-patch
ChromeAutofillClient::UpdateAutofillDataListValuesseessuggestion_controller_is non-null and applies frame A’s datalist options to frame B’s popup without any frame-token check. - Injected content in the victim popup The popup shown for frame B now displays attacker-controlled datalist entries, enabling cross-frame content injection/spoofing (inferred: could be leveraged to mislead the user or desynchronize popup state such as the selected line index).
Impact Assessment
WebContents, acting in the browser-process UI, with the sole precondition that a popup is currently showing for the victim frame. This is a missing-authorization logic bug (no memory corruption is demonstrated by the patch) that permits cross-frame UI content injection/spoofing. The vendor advisory rates this medium severity and does not report in-the-wild exploitation; NVD scores it CVSS 4.2. Any escalation to credential or data disclosure is (inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
WebContentschrome/browser/ui/autofill/autofill_suggestion_controller.h |
modified | |
ifchrome/browser/ui/autofill/chrome_autofill_client.cc |
modified | |
TEST_Fchrome/browser/ui/autofill/chrome_autofill_client_unittest.cc |
modified | |
TEST_Fcomponents/autofill/core/browser/ui/autofill_external_delegate_unittest.cc |
modified |
Files Changed
chrome/browser/ui/autofill/autofill_popup_controller_impl.ccchrome/browser/ui/autofill/chrome_autofill_client_unittest.cccomponents/android_autofill/browser/android_autofill_client.hios/chrome/browser/autofill/ui_bundled/chrome_autofill_client_ios.hchrome/browser/ui/autofill/mock_autofill_popup_controller.hchrome/browser/ui/autofill/chrome_autofill_client.ccios/chrome/browser/autofill/ui_bundled/chrome_autofill_client_ios.mmios/web_view/internal/autofill/web_view_autofill_client_ios.mmcomponents/autofill/core/browser/ui/autofill_external_delegate_unittest.cccomponents/android_autofill/browser/android_autofill_client.ccchrome/browser/ui/autofill/at_memory_suggestion_controller.hcomponents/autofill/core/browser/foundations/test_autofill_client.hchrome/browser/ui/autofill/autofill_suggestion_controller.hchrome/browser/ui/autofill/autofill_popup_controller_impl.hios/web_view/internal/autofill/web_view_autofill_client_ios.hchrome/browser/ui/autofill/autofill_popup_controller_interactive_uitest.ccchrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.hchrome/browser/ui/autofill/at_memory_suggestion_controller.ccchrome/browser/ui/autofill/chrome_autofill_client.hchrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cccomponents/autofill/core/browser/ui/autofill_external_delegate.cccomponents/autofill/core/browser/foundations/autofill_client.h
Audit Directions
- Shared per-`WebContents` singletons driven by per-frame IPCAny browser-side object keyed to a
WebContentsbut mutated by messages from individual frames should verify the caller’sLocalFrameTokenbefore acting. - UI mutators gated on existence, not ownershipMethods that modify the currently-shown popup/UI element based only on “does one exist” (e.g. a non-null controller) rather than “does the caller own it” are prime candidates for cross-frame injection.
- Frame-token plumbing gaps in Autofill entry pointsAudit
AutofillClient/AutofillManagermethods that apply renderer-supplied field data without threadingfield.global_id().frame_token, since datalist was one of several such paths.
Patch
From 343058f4edcefa3434a16466ce8eb1e1664bbb42 Mon Sep 17 00:00:00 2001
From: Jihad Hanna <jihadghanna@google.com>
Date: Fri, 11 Sep 2026 03:11:18 -0700
Subject: [PATCH] Validate calls to UpdateAutofillDataListValues()
Details in the bug description. The CL compares frame tokens instead of
delegate pointers as it seems like the more direct comparison to make.
Fixed: 521951328
Change-Id: Ib0537a3e941c0a744248e3f931253ee378a35fd1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8367287
Reviewed-by: Jan Keitel <jkeitel@google.com>
Reviewed-by: Christoph Schwering <schwering@google.com>
Commit-Queue: Jihad Hanna <jihadghanna@google.com>
Cr-Commit-Position: refs/heads/main@{#1696085}
---
diff --git a/chrome/browser/ui/autofill/at_memory_suggestion_controller.cc b/chrome/browser/ui/autofill/at_memory_suggestion_controller.cc
index 358176d2..b383ef9e 100644
--- a/chrome/browser/ui/autofill/at_memory_suggestion_controller.cc
+++ b/chrome/browser/ui/autofill/at_memory_suggestion_controller.cc
@@ -169,6 +169,10 @@
NOTREACHED();
}
+const LocalFrameToken& AtMemorySuggestionController::GetFrameToken() const {
+ return controller_common_.frame_token;
+}
+
void AtMemorySuggestionController::HideViewAndDie() {
ui_session_id_ = std::nullopt;
diff --git a/chrome/browser/ui/autofill/at_memory_suggestion_controller.h b/chrome/browser/ui/autofill/at_memory_suggestion_controller.h
index f417fdd..21fa9a94 100644
--- a/chrome/browser/ui/autofill/at_memory_suggestion_controller.h
+++ b/chrome/browser/ui/autofill/at_memory_suggestion_controller.h
@@ -69,6 +69,7 @@
std::optional<UiSessionId> GetUiSessionId() const override;
void SetKeepPopupOpenForTesting(bool keep_popup_open_for_testing) override;
void UpdateDataListValues(base::span<const SelectOption> options) override;
+ const LocalFrameToken& GetFrameToken() const override;
bool MayRecycle(
base::WeakPtr<AutofillSuggestionDelegate> delegate,
content::WebContents* web_contents,
diff --git a/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cc b/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cc
index eae42c1c..56de103 100644
--- a/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cc
+++ b/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.cc
@@ -755,6 +755,11 @@
}
}
+const LocalFrameToken& AutofillKeyboardAccessoryControllerImpl::GetFrameToken()
+ const {
+ return controller_common_.frame_token;
+}
+
bool AutofillKeyboardAccessoryControllerImpl::HasSuggestions() const {
return std::ranges::any_of(suggestions_, &IsStandaloneSuggestionType,
&Suggestion::type);
diff --git a/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.h b/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.h
index 3d2ff87b..ebccecb 100644
--- a/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.h
+++ b/chrome/browser/ui/autofill/autofill_keyboard_accessory_controller_impl.h
@@ -94,6 +94,7 @@
std::optional<UiSessionId> GetUiSessionId() const override;
void SetKeepPopupOpenForTesting(bool keep_popup_open_for_testing) override;
void UpdateDataListValues(base::span<const SelectOption> options) override;
+ const LocalFrameToken& GetFrameToken() const override;
bool MayRecycle(
base::WeakPtr<AutofillSuggestionDelegate> delegate,
content::WebContents* web_contents,
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
index 8c417aa..6e15018f 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl.cc
@@ -483,6 +483,10 @@
}
}
+const LocalFrameToken& AutofillPopupControllerImpl::GetFrameToken() const {
+ return controller_common_.frame_token;
+}
+
bool AutofillPopupControllerImpl::IsViewVisibilityAcceptingThresholdEnabled()
const {
return !disable_threshold_for_testing_ &&
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_impl.h b/chrome/browser/ui/autofill/autofill_popup_controller_impl.h
index e463460..e60b4d1e 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_impl.h
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_impl.h
@@ -74,6 +74,7 @@
std::optional<UiSessionId> GetUiSessionId() const override;
void SetKeepPopupOpenForTesting(bool keep_popup_open_for_testing) override;
void UpdateDataListValues(base::span<const SelectOption> options) override;
+ const LocalFrameToken& GetFrameToken() const override;
bool MayRecycle(
base::WeakPtr<AutofillSuggestionDelegate> delegate,
content::WebContents* web_contents,
diff --git a/chrome/browser/ui/autofill/autofill_suggestion_controller.h b/chrome/browser/ui/autofill/autofill_suggestion_controller.h
index 336b6ac..998a370 100644
--- a/chrome/browser/ui/autofill/autofill_suggestion_controller.h
+++ b/chrome/browser/ui/autofill/autofill_suggestion_controller.h
@@ -17,6 +17,7 @@
#include "components/autofill/core/browser/metrics/autofill_metrics.h"
#include "components/autofill/core/browser/suggestions/suggestion.h"
#include "components/autofill/core/common/aliases.h"
+#include "components/autofill/core/common/unique_ids.h"
namespace content {
class WebContents;
@@ -106,6 +107,9 @@
// Updates the data list values currently shown.
virtual void UpdateDataListValues(base::span<const SelectOption> options) = 0;
+ // Returns the token of the frame that this controller is associated with.
+ virtual const LocalFrameToken& GetFrameToken() const = 0;
+
// Returns true if the controller can be reused for the given parameters.
virtual bool MayRecycle(
base::WeakPtr<AutofillSuggestionDelegate> delegate,
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.cc b/chrome/browser/ui/autofill/chrome_autofill_client.cc
index 7cc2d3d..ff84ec64 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.cc
@@ -908,8 +908,10 @@
}
void ChromeAutofillClient::UpdateAutofillDataListValues(
+ const LocalFrameToken& frame_token,
base::span<const SelectOption> options) {
- if (suggestion_controller_) {
+ if (suggestion_controller_ &&
+ suggestion_controller_->GetFrameToken() == frame_token) {
suggestion_controller_->UpdateDataListValues(options);
}
}
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client.h b/chrome/browser/ui/autofill/chrome_autofill_client.h
index ea61deb..b7001aa 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client.h
+++ b/chrome/browser/ui/autofill/chrome_autofill_client.h
@@ -211,6 +211,7 @@
const PopupOpenArgs& open_args,
base::WeakPtr<AutofillSuggestionDelegate> delegate) override;
void UpdateAutofillDataListValues(
+ const LocalFrameToken& frame_token,
base::span<const SelectOption> datalist) final;
base::span<const Suggestion> GetAutofillSuggestions() const final;
std::optional<SuggestionUiSessionId>
diff --git a/chrome/browser/ui/autofill/mock_autofill_popup_controller.h b/chrome/browser/ui/autofill/mock_autofill_popup_controller.h
index 89aac74..7de6c73 100644
--- a/chrome/browser/ui/autofill/mock_autofill_popup_controller.h
+++ b/chrome/browser/ui/autofill/mock_autofill_popup_controller.h
@@ -52,6 +52,10 @@
void set_element_bounds(const gfx::RectF& bounds) {
element_bounds_ = bounds;
}
+ const LocalFrameToken& GetFrameToken() const override { return frame_token_; }
+ void set_frame_token(const LocalFrameToken& frame_token) {
+ frame_token_ = frame_token;
+ }
MOCK_METHOD(base::i18n::TextDirection,
GetElementTextDirection,
(),
@@ -166,6 +170,7 @@
"Arial, Times New Roman, 15px"};
gfx::RectF element_bounds_ = {100, 100, 250, 50};
PopupAnchorType anchor_type_ = PopupAnchorType::kField;
+ LocalFrameToken frame_token_;
base::WeakPtrFactory<MockAutofillPopupController> weak_ptr_factory_{this};
};
diff --git a/components/android_autofill/browser/android_autofill_client.cc b/components/android_autofill/browser/android_autofill_client.cc
index c3b4fa0a..feba6818 100644
--- a/components/android_autofill/browser/android_autofill_client.cc
+++ b/components/android_autofill/browser/android_autofill_client.cc
@@ -233,6 +233,7 @@
}
void AndroidAutofillClient::UpdateAutofillDataListValues(
+ const autofill::LocalFrameToken& frame_token,
base::span<const autofill::SelectOption> datalist) {
// Leaving as an empty method since updating autofill popup window
// dynamically does not seem to be a useful feature when delegating to Android
diff --git a/components/android_autofill/browser/android_autofill_client.h b/components/android_autofill/browser/android_autofill_client.h
index 4e5187bf..4c324a0e 100644
--- a/components/android_autofill/browser/android_autofill_client.h
+++ b/components/android_autofill/browser/android_autofill_client.h
@@ -127,6 +127,7 @@
const autofill::AutofillClient::PopupOpenArgs& open_args,
base::WeakPtr<autofill::AutofillSuggestionDelegate> delegate) final;
void UpdateAutofillDataListValues(
+ const autofill::LocalFrameToken& frame_token,
base::span<const autofill::SelectOption> datalist) final;
void HideSuggestions(autofill::SuggestionHidingReason reason,
std::optional<autofill::FillingProduct> product) final;
diff --git a/components/autofill/core/browser/foundations/autofill_client.h b/components/autofill/core/browser/foundations/autofill_client.h
index 37965a54..69e8ff9 100644
--- a/components/autofill/core/browser/foundations/autofill_client.h
+++ b/components/autofill/core/browser/foundations/autofill_client.h
@@ -665,6 +665,7 @@
// Update the data list values shown by the Autofill suggestions, if visible.
virtual void UpdateAutofillDataListValues(
+ const LocalFrameToken& frame_token,
base::span<const SelectOption> datalist) = 0;
// Returns the identifier of the suggestion UI that is currently showing or
diff --git a/components/autofill/core/browser/foundations/test_autofill_client.h b/components/autofill/core/browser/foundations/test_autofill_client.h
index 91b442d..f1e3bc23 100644
--- a/components/autofill/core/browser/foundations/test_autofill_client.h
+++ b/components/autofill/core/browser/foundations/test_autofill_client.h
@@ -410,6 +410,7 @@
}
void UpdateAutofillDataListValues(
+ const LocalFrameToken& frame_token,
base::span<const SelectOption> options) override {}
void SetAutofillSuggestions(std::vector<Suggestion> suggestions) {
diff --git a/components/autofill/core/browser/ui/autofill_external_delegate.cc b/components/autofill/core/browser/ui/autofill_external_delegate.cc
index 3a840a9d1..12756da 100644
--- a/components/autofill/core/browser/ui/autofill_external_delegate.cc
+++ b/components/autofill/core/browser/ui/autofill_external_delegate.cc
@@ -438,7 +438,8 @@
.field_datalist_options = field.datalist_options()};
caret_bounds_ = caret_bounds;
trigger_source_ = trigger_source;
- manager_->client().UpdateAutofillDataListValues(field.datalist_options());
+ manager_->client().UpdateAutofillDataListValues(field.global_id().frame_token,
+ field.datalist_options());
}
const AutofillField* AutofillExternalDelegate::GetQueriedField() const {
diff --git a/ios/chrome/browser/autofill/ui_bundled/chrome_autofill_client_ios.h b/ios/chrome/browser/autofill/ui_bundled/chrome_autofill_client_ios.h
index 9a7b19d..d911716 100644
--- a/ios/chrome/browser/autofill/ui_bundled/chrome_autofill_client_ios.h
+++ b/ios/chrome/browser/autofill/ui_bundled/chrome_autofill_client_ios.h
@@ -171,6 +171,7 @@
const PopupOpenArgs& open_args,
base::WeakPtr<AutofillSuggestionDelegate> delegate) override;
void UpdateAutofillDataListValues(
+ const LocalFrameToken& frame_token,
base::span<const SelectOption> datalist) override;
void HideSuggestions(SuggestionHidingReason reason,
std::optional<FillingProduct> product) override;
diff --git a/ios/chrome/browser/autofill/ui_bundled/chrome_autofill_client_ios.mm b/ios/chrome/browser/autofill/ui_bundled/chrome_autofill_client_ios.mm
index ee508dc..7cd3365ad 100644
--- a/ios/chrome/browser/autofill/ui_bundled/chrome_autofill_client_ios.mm
+++ b/ios/chrome/browser/autofill/ui_bundled/chrome_autofill_client_ios.mm
@@ -547,6 +547,7 @@
}
void ChromeAutofillClientIOS::UpdateAutofillDataListValues(
+ const LocalFrameToken& frame_token,
base::span<const SelectOption> datalist) {
// No op. ios/web_view does not support display datalist.
}
diff --git a/ios/web_view/internal/autofill/web_view_autofill_client_ios.h b/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
index a8ce175..e49b3c3 100644
--- a/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
+++ b/ios/web_view/internal/autofill/web_view_autofill_client_ios.h
@@ -133,6 +133,7 @@
const AutofillClient::PopupOpenArgs& open_args,
base::WeakPtr<AutofillSuggestionDelegate> delegate) override;
void UpdateAutofillDataListValues(
+ const LocalFrameToken& frame_token,
base::span<const SelectOption> datalist) override;
void HideSuggestions(SuggestionHidingReason reason,
std::optional<FillingProduct> product) override;
diff --git a/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm b/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
index af6dfaf..5c030bb 100644
--- a/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
+++ b/ios/web_view/internal/autofill/web_view_autofill_client_ios.mm
@@ -251,6 +251,7 @@
}
void WebViewAutofillClientIOS::UpdateAutofillDataListValues(
+ const LocalFrameToken& frame_token,
base::span<const SelectOption> datalist) {
// No op. ios/web_view does not support display datalist.
}
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_interactive_uitest.cc b/chrome/browser/ui/autofill/autofill_popup_controller_interactive_uitest.cc
index baac8ab8a..71171c81 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_interactive_uitest.cc
@@ -220,13 +220,13 @@
{u"suggestion2", u"suggestion2"},
{u"suggestion3", u"suggestion3"},
{u"suggestion4", u"suggestion4"}};
- client->UpdateAutofillDataListValues(rows);
+ client->UpdateAutofillDataListValues(controller->GetFrameToken(), rows);
int original_suggestions_count = controller->GetLineCount();
static_cast<AutofillPopupController&>(*controller).SelectSuggestion(3);
// Replace the list with the smaller one.
rows = {{u"suggestion1", u"suggestion1"}};
- client->UpdateAutofillDataListValues(rows);
+ client->UpdateAutofillDataListValues(controller->GetFrameToken(), rows);
// Make sure that previously selected line #3 doesn't exist.
ASSERT_LT(controller->GetLineCount(), original_suggestions_count);
// Selecting a new line should not crash.
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc b/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc
index c933d3d..427c8ba 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc
@@ -57,6 +57,7 @@
#include "components/autofill/core/common/autofill_prefs.h"
#include "components/autofill/core/common/autofill_test_util.h"
#include "components/autofill/core/common/form_field_data.h"
+#include "components/autofill/core/common/unique_ids.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/personal_context/core/personal_context_eligibility_service.h"
#include "components/personal_context/core/personal_context_prefs.h"
@@ -1275,6 +1276,30 @@
client()->HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
FillingProduct::kAddress);
}
+
+TEST_F(ChromeAutofillClientTest,
+ UpdateAutofillDataListValues_FrameTokenFilter) {
+ LocalFrameToken token1(base::UnguessableToken::Create());
+ LocalFrameToken token2(base::UnguessableToken::Create());
+
+ testing::NiceMock<MockAutofillPopupController> mock_controller;
+ mock_controller.set_frame_token(token1);
+
+ client()->set_suggestion_controller_for_testing(mock_controller.GetWeakPtr());
+
+ std::vector<SelectOption> options = {{.value = u"val", .text = u"txt"}};
+
+ // Attempt to update with a non-matching frame token should be ignored.
+ EXPECT_CALL(mock_controller, UpdateDataListValues).Times(0);
+ client()->UpdateAutofillDataListValues(token2, options);
+ testing::Mock::VerifyAndClearExpectations(&mock_controller);
+
+ // Attempt to update with a matching frame token should succeed.
+ EXPECT_CALL(
+ mock_controller,
+ UpdateDataListValues(ElementsAre(Field(&SelectOption::value, u"val"))));
+ client()->UpdateAutofillDataListValues(token1, options);
+}
#endif // !BUILDFLAG(IS_ANDROID)
TEST_F(ChromeAutofillClientTest, IsAutofillProfileEnabled_BlockedByPolicy) {
diff --git a/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc b/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
index 68c6b82..653998c 100644
--- a/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
+++ b/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
@@ -280,7 +280,7 @@
(const override));
MOCK_METHOD(void,
UpdateAutofillDataListValues,
- (base::span<const SelectOption> options),
+ (const LocalFrameToken&, base::span<const SelectOption> options),
(override));
MOCK_METHOD(void,
HideSuggestions,
@@ -1580,7 +1580,7 @@
std::vector<SelectOption> data_list_items;
data_list_items.emplace_back();
- EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(SizeIs(1)));
+ EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(_, SizeIs(1)));
IssueOnQuery(data_list_items);
// This should call ShowAutofillSuggestions.
@@ -1616,7 +1616,7 @@
std::vector<SelectOption> data_list_items;
data_list_items.emplace_back();
- EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(SizeIs(1)));
+ EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(_, SizeIs(1)));
IssueOnQuery(data_list_items);
// Ensure the popup is displayed.
@@ -1640,7 +1640,7 @@
// Update the current data list and ensure the popup is updated.
data_list_items.emplace_back();
- EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(SizeIs(2)));
+ EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(_, SizeIs(2)));
IssueOnQuery(data_list_items);
}
@@ -1649,12 +1649,13 @@
TEST_F(AutofillExternalDelegateTest, DuplicateAutofillDatalistValues) {
std::vector<SelectOption> datalist{{.value = u"Rick", .text = u"Deckard"},
{.value = u"Beyonce", .text = u"Knowles"}};
- EXPECT_CALL(autofill_client(),
- UpdateAutofillDataListValues(
- ElementsAre(AllOf(Field(&SelectOption::value, u"Rick"),
- Field(&SelectOption::text, u"Deckard")),
- AllOf(Field(&SelectOption::value, u"Beyonce"),
- Field(&SelectOption::text, u"Knowles")))));
+ EXPECT_CALL(
+ autofill_client(),
+ UpdateAutofillDataListValues(
+ _, ElementsAre(AllOf(Field(&SelectOption::value, u"Rick"),
+ Field(&SelectOption::text, u"Deckard")),
+ AllOf(Field(&SelectOption::value, u"Beyonce"),
... (file diff truncated)
Regression Test / PoC
diff --git a/chrome/browser/ui/autofill/autofill_popup_controller_interactive_uitest.cc b/chrome/browser/ui/autofill/autofill_popup_controller_interactive_uitest.cc
index baac8ab8a..71171c81 100644
--- a/chrome/browser/ui/autofill/autofill_popup_controller_interactive_uitest.cc
+++ b/chrome/browser/ui/autofill/autofill_popup_controller_interactive_uitest.cc
@@ -220,13 +220,13 @@
{u"suggestion2", u"suggestion2"},
{u"suggestion3", u"suggestion3"},
{u"suggestion4", u"suggestion4"}};
- client->UpdateAutofillDataListValues(rows);
+ client->UpdateAutofillDataListValues(controller->GetFrameToken(), rows);
int original_suggestions_count = controller->GetLineCount();
static_cast<AutofillPopupController&>(*controller).SelectSuggestion(3);
// Replace the list with the smaller one.
rows = {{u"suggestion1", u"suggestion1"}};
- client->UpdateAutofillDataListValues(rows);
+ client->UpdateAutofillDataListValues(controller->GetFrameToken(), rows);
// Make sure that previously selected line #3 doesn't exist.
ASSERT_LT(controller->GetLineCount(), original_suggestions_count);
// Selecting a new line should not crash.
diff --git a/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc b/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc
index c933d3d..427c8ba 100644
--- a/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc
+++ b/chrome/browser/ui/autofill/chrome_autofill_client_unittest.cc
@@ -57,6 +57,7 @@
#include "components/autofill/core/common/autofill_prefs.h"
#include "components/autofill/core/common/autofill_test_util.h"
#include "components/autofill/core/common/form_field_data.h"
+#include "components/autofill/core/common/unique_ids.h"
#include "components/feature_engagement/public/feature_constants.h"
#include "components/personal_context/core/personal_context_eligibility_service.h"
#include "components/personal_context/core/personal_context_prefs.h"
@@ -1275,6 +1276,30 @@
client()->HideSuggestions(SuggestionHidingReason::kAcceptSuggestion,
FillingProduct::kAddress);
}
+
+TEST_F(ChromeAutofillClientTest,
+ UpdateAutofillDataListValues_FrameTokenFilter) {
+ LocalFrameToken token1(base::UnguessableToken::Create());
+ LocalFrameToken token2(base::UnguessableToken::Create());
+
+ testing::NiceMock<MockAutofillPopupController> mock_controller;
+ mock_controller.set_frame_token(token1);
+
+ client()->set_suggestion_controller_for_testing(mock_controller.GetWeakPtr());
+
+ std::vector<SelectOption> options = {{.value = u"val", .text = u"txt"}};
+
+ // Attempt to update with a non-matching frame token should be ignored.
+ EXPECT_CALL(mock_controller, UpdateDataListValues).Times(0);
+ client()->UpdateAutofillDataListValues(token2, options);
+ testing::Mock::VerifyAndClearExpectations(&mock_controller);
+
+ // Attempt to update with a matching frame token should succeed.
+ EXPECT_CALL(
+ mock_controller,
+ UpdateDataListValues(ElementsAre(Field(&SelectOption::value, u"val"))));
+ client()->UpdateAutofillDataListValues(token1, options);
+}
#endif // !BUILDFLAG(IS_ANDROID)
TEST_F(ChromeAutofillClientTest, IsAutofillProfileEnabled_BlockedByPolicy) {
diff --git a/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc b/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
index 68c6b82..653998c 100644
--- a/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
+++ b/components/autofill/core/browser/ui/autofill_external_delegate_unittest.cc
@@ -280,7 +280,7 @@
(const override));
MOCK_METHOD(void,
UpdateAutofillDataListValues,
- (base::span<const SelectOption> options),
+ (const LocalFrameToken&, base::span<const SelectOption> options),
(override));
MOCK_METHOD(void,
HideSuggestions,
@@ -1580,7 +1580,7 @@
std::vector<SelectOption> data_list_items;
data_list_items.emplace_back();
- EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(SizeIs(1)));
+ EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(_, SizeIs(1)));
IssueOnQuery(data_list_items);
// This should call ShowAutofillSuggestions.
@@ -1616,7 +1616,7 @@
std::vector<SelectOption> data_list_items;
data_list_items.emplace_back();
- EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(SizeIs(1)));
+ EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(_, SizeIs(1)));
IssueOnQuery(data_list_items);
// Ensure the popup is displayed.
@@ -1640,7 +1640,7 @@
// Update the current data list and ensure the popup is updated.
data_list_items.emplace_back();
- EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(SizeIs(2)));
+ EXPECT_CALL(autofill_client(), UpdateAutofillDataListValues(_, SizeIs(2)));
IssueOnQuery(data_list_items);
}
@@ -1649,12 +1649,13 @@
TEST_F(AutofillExternalDelegateTest, DuplicateAutofillDatalistValues) {
std::vector<SelectOption> datalist{{.value = u"Rick", .text = u"Deckard"},
{.value = u"Beyonce", .text = u"Knowles"}};
- EXPECT_CALL(autofill_client(),
- UpdateAutofillDataListValues(
- ElementsAre(AllOf(Field(&SelectOption::value, u"Rick"),
- Field(&SelectOption::text, u"Deckard")),
- AllOf(Field(&SelectOption::value, u"Beyonce"),
- Field(&SelectOption::text, u"Knowles")))));
+ EXPECT_CALL(
+ autofill_client(),
+ UpdateAutofillDataListValues(
+ _, ElementsAre(AllOf(Field(&SelectOption::value, u"Rick"),
+ Field(&SelectOption::text, u"Deckard")),
+ AllOf(Field(&SelectOption::value, u"Beyonce"),
+ Field(&SelectOption::text, u"Knowles")))));
IssueOnQuery(datalist);
const auto kExpectedSuggestions = SuggestionVectorIdsAre(
@@ -1681,12 +1682,13 @@
TEST_F(AutofillExternalDelegateTest, DuplicateAutocompleteDatalistValues) {
std::vector<SelectOption> datalist{{.value = u"Rick", .text = u"Deckard"},
{.value = u"Beyonce", .text = u"Knowles"}};
- EXPECT_CALL(autofill_client(),
- UpdateAutofillDataListValues(
- ElementsAre(AllOf(Field(&SelectOption::value, u"Rick"),
- Field(&SelectOption::text, u"Deckard")),
- AllOf(Field(&SelectOption::value, u"Beyonce"),
- Field(&SelectOption::text, u"Knowles")))));
+ EXPECT_CALL(
+ autofill_client(),
+ UpdateAutofillDataListValues(
+ _, ElementsAre(AllOf(Field(&SelectOption::value, u"Rick"),
+ Field(&SelectOption::text, u"Deckard")),
+ AllOf(Field(&SelectOption::value, u"Beyonce"),
+ Field(&SelectOption::text, u"Knowles")))));
IssueOnQuery(datalist);
const auto kExpectedSuggestions = SuggestionVectorIdsAre(