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 <[email protected]> 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 <[email protected]> Reviewed-by: Christoph Schwering <[email protected]> Commit-Queue: Jihad Hanna <[email protected]> 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(