diff --git a/chrome/browser/ui/views/autofill/popup/popup_view_views_unittest.cc b/chrome/browser/ui/views/autofill/popup/popup_view_views_unittest.cc index 33e6b3b016db6..25e53ba6a4118 100644 --- a/chrome/browser/ui/views/autofill/popup/popup_view_views_unittest.cc +++ b/chrome/browser/ui/views/autofill/popup/popup_view_views_unittest.cc @@ -474,6 +474,55 @@ TEST_F(PopupViewViewsTest, AccessibleNameAndRole) { node_data.GetString16Attribute(ax::mojom::StringAttribute::kName)); } +// Regression test: PopupViewViews::SetSelectedCell holds a bare PopupRowView& +// across PopupRowView::SetSelectedCell -> NotifyAXSelection. On Windows with an +// active UIA client, firing native a11y events synchronously pumps the COM STA +// loop (see popup_base_view.cc, crbug.com/514228954), which can run a posted +// ShowAutofillSuggestionsImpl task that calls OnSuggestionsChanged() and +// synchronously deletes every PopupRowView via RemoveAllChildViews(). After +// unwinding, both PopupRowView::SetSelectedCell (on freed *this) and +// PopupViewViews::SetSelectedCell (new_selected_row.ScrollViewToVisible()) +// dereference freed memory. This test simulates the documented Windows UIA +// re-entry by hooking the cross-platform AXUpdateNotifier path that fires +// during NotifyAXSelection. +TEST_F(PopupViewViewsTest, + SetSelectedCellSurvivesReentrantOnSuggestionsChanged) { + CreateAndShowView({SuggestionType::kAutocompleteEntry, + SuggestionType::kAutocompleteEntry}); + + // NotifyAXSelection fires kMenuStart/kMenuPopupStart on the PopupViewViews + // via FireNativeEvent. On Windows that lands in ::UiaRaiseAutomationEvent, + // which pumps the COM STA loop and can run a posted + // ShowAutofillSuggestionsImpl task. We simulate that re-entry by hooking the + // FireNativeEvent test callback at the same synchronous point. + bool fired = false; + view().GetViewAccessibility().set_accessibility_events_callback( + base::BindLambdaForTesting( + [&](const ui::AXPlatformNodeDelegate*, const ax::mojom::Event event) { + if (fired || event != ax::mojom::Event::kMenuStart) { + return; + } + fired = true; + // Simulates AutofillPopupControllerImpl::Show() with view_ + // non-null reached via the UIA pump: OnSuggestionsChanged() -> + // CreateSuggestionViews() -> rows_.clear() + + // RemoveAllChildViews() synchronously deletes every PopupRowView, + // including the one held by `PopupRowView& new_selected_row` in + // PopupViewViews::SetSelectedCell. + UpdateSuggestions({SuggestionType::kAutocompleteEntry, + SuggestionType::kAutocompleteEntry, + SuggestionType::kAutocompleteEntry}); + })); + + // VKEY_DOWN -> SelectNextRow -> SetSelectedCell({0, kContent}). + // Caches `PopupRowView& new_selected_row`; calls into NotifyAXSelection + // which fires kMenuStart -> callback frees the row. Unwind hits UAF in + // PopupRowView::SetSelectedCell on freed *this, then + // new_selected_row.ScrollViewToVisible() in PopupViewViews::SetSelectedCell. + SimulateKeyPress(ui::VKEY_DOWN); + EXPECT_TRUE(fired); +} + TEST_F(PopupViewViewsTest, CanShowDropdownInBoundsVertically) { CreateAndShowView({SuggestionType::kAutocompleteEntry, SuggestionType::kSeparator,