diff --git a/content/browser/android/text_suggestion_host_android_browsertest.cc b/content/browser/android/text_suggestion_host_android_browsertest.cc new file mode 100644 index 0000000000000..8fd4125e5561c --- /dev/null +++ b/content/browser/android/text_suggestion_host_android_browsertest.cc @@ -0,0 +1,88 @@ +// Copyright 2026 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/android/text_suggestion_host_android.h" + +#include "base/command_line.h" +#include "content/browser/renderer_host/frame_tree_node.h" +#include "content/browser/renderer_host/render_frame_host_impl.h" +#include "content/browser/site_instance_impl.h" +#include "content/browser/web_contents/web_contents_impl.h" +#include "content/public/test/browser_test.h" +#include "content/public/test/browser_test_utils.h" +#include "content/public/test/content_browser_test.h" +#include "content/public/test/content_browser_test_utils.h" +#include "content/public/test/test_utils.h" +#include "content/shell/browser/shell.h" +#include "net/dns/mock_host_resolver.h" +#include "net/test/embedded_test_server/embedded_test_server.h" +#include "url/gurl.h" + +namespace content { + +class TextSuggestionHostAndroidBrowserTest : public ContentBrowserTest { + public: + TextSuggestionHostAndroidBrowserTest() = default; + ~TextSuggestionHostAndroidBrowserTest() override = default; + + protected: + void SetUpCommandLine(base::CommandLine* command_line) override { + IsolateAllSitesForTesting(command_line); + } + + void SetUpOnMainThread() override { + host_resolver()->AddRule("*", "127.0.0.1"); + SetupCrossSiteRedirector(embedded_test_server()); + ASSERT_TRUE(embedded_test_server()->Start()); + } + + WebContentsImpl* web_contents() const { + return static_cast(shell()->web_contents()); + } +}; + +// The text-suggestion popup is displayed at WebContents scope but the +// associated state must be tracked per frame so that suggestions chosen by the +// user are routed back to the frame that requested the popup, even when a page +// embeds out-of-process iframes and focus moves between them. +IN_PROC_BROWSER_TEST_F(TextSuggestionHostAndroidBrowserTest, + PerFrameStateWithCrossProcessIframe) { + GURL main_url(embedded_test_server()->GetURL( + "a.com", "/cross_site_iframe_factory.html?a(b)")); + ASSERT_TRUE(NavigateToURL(shell(), main_url)); + + FrameTreeNode* root = web_contents()->GetPrimaryFrameTree().root(); + ASSERT_EQ(1u, root->child_count()); + FrameTreeNode* child = root->child_at(0); + + RenderFrameHostImpl* main_frame = root->current_frame_host(); + RenderFrameHostImpl* child_frame = child->current_frame_host(); + ASSERT_NE(main_frame->GetProcess(), child_frame->GetProcess()); + + auto* main_host = + TextSuggestionHostAndroid::GetOrCreateForCurrentDocument(main_frame); + auto* child_host = + TextSuggestionHostAndroid::GetOrCreateForCurrentDocument(child_frame); + + // Each frame must get its own host so that callbacks from the popup are + // delivered to the originating frame. + EXPECT_NE(main_host, child_host); + EXPECT_EQ(static_cast(main_frame), + &main_host->render_frame_host()); + EXPECT_EQ(static_cast(child_frame), + &child_host->render_frame_host()); + + // Moving focus to the cross-process child must not change which frame the + // main frame's host is associated with. + web_contents()->SetFocusedFrame(child, + child_frame->GetSiteInstance()->group()); + EXPECT_EQ(child_frame, web_contents()->GetFocusedFrame()); + + EXPECT_EQ(main_host, + TextSuggestionHostAndroid::GetForCurrentDocument(main_frame)); + EXPECT_EQ(static_cast(main_frame), + &main_host->render_frame_host()); +} + +} // namespace content diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn index 6715f3509d441..23c6d53e44b8d 100644 --- a/content/test/BUILD.gn +++ b/content/test/BUILD.gn @@ -2289,6 +2289,7 @@ test("content_browsertests") { "../browser/android/render_widget_host_connector_browsertest.cc", "../browser/android/render_widget_host_connector_browsertest.h", "../browser/android/synchronous_compositor_browsertest.cc", + "../browser/android/text_suggestion_host_android_browsertest.cc", "../browser/date_time_chooser/android/date_time_chooser_browsertest.cc", "../browser/media/session/audio_focus_delegate_android_browsertest.cc", "../browser/renderer_host/compositor_impl_android_browsertest.cc",