1 // Copyright 2020 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "base/command_line.h"
6 #include "base/run_loop.h"
7 #include "base/strings/stringprintf.h"
8 #include "base/test/test_timeouts.h"
9 #include "build/build_config.h"
10 #include "chrome/browser/ui/browser.h"
11 #include "chrome/browser/ui/browser_window.h"
12 #include "chrome/browser/ui/location_bar/location_bar.h"
13 #include "chrome/browser/ui/tabs/tab_strip_model.h"
14 #include "chrome/test/base/in_process_browser_test.h"
15 #include "chrome/test/base/interactive_test_utils.h"
16 #include "chrome/test/base/ui_test_utils.h"
17 #include "components/omnibox/browser/omnibox_view.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/focused_node_details.h"
20 #include "content/public/browser/render_widget_host_view.h"
21 #include "content/public/browser/web_contents.h"
22 #include "content/public/test/browser_test.h"
23 #include "content/public/test/browser_test_utils.h"
24 #include "content/public/test/content_browser_test.h"
25 #include "content/public/test/content_browser_test_utils.h"
26 #include "content/public/test/test_utils.h"
27 #include "net/dns/mock_host_resolver.h"
28 #include "net/test/embedded_test_server/embedded_test_server.h"
31 class ActiveRenderWidgetHostBrowserTest : public InProcessBrowserTest {
33 ActiveRenderWidgetHostBrowserTest() = default;
35 ActiveRenderWidgetHostBrowserTest(const ActiveRenderWidgetHostBrowserTest&) =
37 ActiveRenderWidgetHostBrowserTest& operator=(
38 const ActiveRenderWidgetHostBrowserTest&) = delete;
40 ~ActiveRenderWidgetHostBrowserTest() override = default;
42 void SetUpCommandLine(base::CommandLine* command_line) override {
43 content::IsolateAllSitesForTesting(command_line);
46 void SetUpOnMainThread() override {
47 host_resolver()->AddRule("*", "127.0.0.1");
49 // Add content/test/data for cross_site_iframe_factory.html
50 embedded_test_server()->ServeFilesFromSourceDirectory("content/test/data");
52 ASSERT_TRUE(embedded_test_server()->Start());
56 IN_PROC_BROWSER_TEST_F(ActiveRenderWidgetHostBrowserTest,
57 DocumentIsActiveAndFocused) {
58 GURL main_url(embedded_test_server()->GetURL(
59 "a.com", "/cross_site_iframe_factory.html?a(b(c),d)"));
61 // Site A ------------ proxies for B C D
62 // |--Site B ------- proxies for A C D
63 // | +--Site C -- proxies for A B D
64 // +--Site D ------- proxies for A B C
65 // Where A = http://a.com/
69 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
71 content::WebContents* web_contents =
72 browser()->tab_strip_model()->GetActiveWebContents();
73 content::RenderFrameHost* main_frame_a = web_contents->GetPrimaryMainFrame();
74 content::RenderFrameHost* child_frame_b = ChildFrameAt(main_frame_a, 0);
75 ASSERT_NE(nullptr, child_frame_b);
76 content::RenderFrameHost* child_frame_d = ChildFrameAt(main_frame_a, 1);
77 ASSERT_NE(nullptr, child_frame_d);
78 content::RenderFrameHost* child_frame_c = ChildFrameAt(child_frame_b, 0);
79 ASSERT_NE(nullptr, child_frame_c);
81 EXPECT_NE(main_frame_a->GetSiteInstance(), child_frame_b->GetSiteInstance());
82 EXPECT_NE(main_frame_a->GetSiteInstance(), child_frame_d->GetSiteInstance());
83 EXPECT_NE(child_frame_b->GetSiteInstance(), child_frame_c->GetSiteInstance());
85 // Helper function to check document.hasFocus() for a given frame.
86 // hasFocus internally calls FocusController::IsDocumentFocused which
87 // return true only iff document is active and focused.
88 auto document_is_active_and_focused =
89 [](content::RenderFrameHost* rfh) -> bool {
90 return EvalJs(rfh, "document.hasFocus()").ExtractBool();
93 // Helper function to check a property of document.activeElement in the
95 auto verify_active_element_property = [](content::RenderFrameHost* rfh,
96 const std::string& property,
97 const std::string& expected_value) {
98 std::string script = base::StringPrintf(
99 "document.activeElement.%s.toLowerCase();", property.c_str());
100 EXPECT_EQ(expected_value, EvalJs(rfh, script));
103 // The main_frame_a should have a focus to start with.
104 EXPECT_EQ(main_frame_a, web_contents->GetFocusedFrame());
105 EXPECT_TRUE(document_is_active_and_focused(main_frame_a));
106 EXPECT_FALSE(document_is_active_and_focused(child_frame_b));
107 EXPECT_FALSE(document_is_active_and_focused(child_frame_c));
108 EXPECT_FALSE(document_is_active_and_focused(child_frame_d));
109 verify_active_element_property(main_frame_a, "tagName", "body");
111 // After focusing child_frame_b, document.hasFocus() should return
112 // true for child_frame_b and all its ancestor frames.
113 EXPECT_TRUE(ExecJs(child_frame_b, "window.focus();"));
114 EXPECT_EQ(child_frame_b, web_contents->GetFocusedFrame());
115 EXPECT_TRUE(document_is_active_and_focused(main_frame_a));
116 EXPECT_TRUE(document_is_active_and_focused(child_frame_b));
117 EXPECT_FALSE(document_is_active_and_focused(child_frame_c));
118 EXPECT_FALSE(document_is_active_and_focused(child_frame_d));
119 verify_active_element_property(main_frame_a, "tagName", "iframe");
120 verify_active_element_property(main_frame_a, "src",
121 child_frame_b->GetLastCommittedURL().spec());
123 // After focusing child_frame_c, document.hasFocus() should return
124 // true for child_frame_c and all its ancestor frames.
125 EXPECT_TRUE(ExecJs(child_frame_c, "window.focus();"));
126 EXPECT_EQ(child_frame_c, web_contents->GetFocusedFrame());
127 EXPECT_TRUE(document_is_active_and_focused(main_frame_a));
128 EXPECT_TRUE(document_is_active_and_focused(child_frame_b));
129 EXPECT_TRUE(document_is_active_and_focused(child_frame_c));
130 EXPECT_FALSE(document_is_active_and_focused(child_frame_d));
131 verify_active_element_property(main_frame_a, "tagName", "iframe");
132 // Check document.activeElement in main_frame_a. It should still
133 // point to <iframe> for the b.com frame, since Blink computes the
134 // focused iframe element by walking the parent chain of the focused
135 // frame until it hits the current frame. This logic should still
136 // work with remote frames.
137 verify_active_element_property(main_frame_a, "src",
138 child_frame_b->GetLastCommittedURL().spec());
140 // After focusing child_frame_d, document.hasFocus() should return
141 // true for child_frame_d and all its ancestor frames.
142 EXPECT_TRUE(ExecJs(child_frame_d, "window.focus();"));
143 EXPECT_EQ(child_frame_d, web_contents->GetFocusedFrame());
144 EXPECT_TRUE(document_is_active_and_focused(main_frame_a));
145 EXPECT_FALSE(document_is_active_and_focused(child_frame_b));
146 EXPECT_FALSE(document_is_active_and_focused(child_frame_c));
147 EXPECT_TRUE(document_is_active_and_focused(child_frame_d));
148 verify_active_element_property(main_frame_a, "tagName", "iframe");
149 verify_active_element_property(main_frame_a, "src",
150 child_frame_d->GetLastCommittedURL().spec());
152 // After focusing main_frame_a, document.hasFocus() should return
153 // true for main_frame_a and since it's a root of tree, all its
154 // descendants should return false. On the renderer side, both the
155 // 'active' and 'focus' states for blink::FocusController will be
157 EXPECT_TRUE(ExecJs(main_frame_a, "window.focus();"));
158 EXPECT_EQ(main_frame_a, web_contents->GetFocusedFrame());
159 EXPECT_TRUE(document_is_active_and_focused(main_frame_a));
160 EXPECT_FALSE(document_is_active_and_focused(child_frame_b));
161 EXPECT_FALSE(document_is_active_and_focused(child_frame_c));
162 EXPECT_FALSE(document_is_active_and_focused(child_frame_d));
163 verify_active_element_property(main_frame_a, "tagName", "body");
165 // Focus the URL bar.
166 OmniboxView* omnibox =
167 browser()->window()->GetLocationBar()->GetOmniboxView();
168 // Give the omnibox focus.
169 omnibox->SetFocus(/*is_user_initiated=*/true);
170 base::RunLoop().RunUntilIdle();
171 EXPECT_EQ(main_frame_a, web_contents->GetFocusedFrame());
173 // `omnibox->SetFocus()` should call blur event on main_frame_a and
174 // deactivate the active render widget, but on Mac calling
175 // `omnibox->SetFocus()` function doesn't invoke
176 // RWHI::SetActive(false). As a result, `blink::FocusController`'s
177 // 'active' state maintains the previous value of false.
179 // This table sums up `blink::FocusController`'s 'active' and 'focus'
180 // states on different platforms after focusing the omnibox:
182 // | | Linux | Mac | Windows |
183 // | active | false | true | false |
184 // | focus | false | false | false |
186 // Since `document.hasFocus()` only returns true iff the document is
187 // both active and focus, the test still expects
188 // `document.hasFocus()` to be false on all platforms.
190 // Note that there is no separate API to test active state of the
191 // document. Instead, Mac's active behavior is separately tested in
192 // `ActiveRenderWidgetHostBrowserTest.FocusOmniBox`.
193 EXPECT_FALSE(document_is_active_and_focused(main_frame_a));
194 EXPECT_FALSE(document_is_active_and_focused(child_frame_b));
195 EXPECT_FALSE(document_is_active_and_focused(child_frame_c));
196 EXPECT_FALSE(document_is_active_and_focused(child_frame_d));
197 // body tag is active by default.
198 verify_active_element_property(main_frame_a, "tagName", "body");
199 verify_active_element_property(child_frame_b, "tagName", "body");
200 verify_active_element_property(child_frame_c, "tagName", "body");
201 verify_active_element_property(child_frame_d, "tagName", "body");
204 // This test verifies that on Mac, moving the focus from webcontents to Omnibox
205 // doesn't change the 'active' state and old value of the active state is
208 // FakeFrameWidget has Optional<bool> 'active' state which is
209 // uninitialised at the beginning. omnibox->SetFocus() invokes
210 // RWHI::SetActive(false) for webcontents and there is a IPC call to
211 // renderer which changes 'active' state to false.
213 // On Mac, calling omnibox->SetFocus function doesn't invoke
214 // RWHI::SetActive(false). Hence there is no IPC call to renderer and
215 // 'active' state maintains old value.
216 IN_PROC_BROWSER_TEST_F(ActiveRenderWidgetHostBrowserTest, FocusOmniBox) {
217 GURL main_url(embedded_test_server()->GetURL("a.com", "/title1.html"));
218 ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), main_url));
220 content::WebContents* web_contents =
221 browser()->tab_strip_model()->GetActiveWebContents();
223 content::RenderFrameHost* main_frame = web_contents->GetPrimaryMainFrame();
224 EXPECT_EQ(main_frame, web_contents->GetFocusedFrame());
226 mojo::PendingAssociatedReceiver<blink::mojom::FrameWidget>
227 blink_frame_widget_receiver =
228 content::BindFakeFrameWidgetInterfaces(main_frame);
229 content::FakeFrameWidget fake_frame_widget(
230 std::move(blink_frame_widget_receiver));
232 // Main frame is already focused at this point and now focus URL bar.
233 OmniboxView* omnibox =
234 browser()->window()->GetLocationBar()->GetOmniboxView();
235 // Give the omnibox focus.
236 omnibox->SetFocus(/*is_user_initiated=*/true);
238 base::RunLoop().RunUntilIdle();
239 #if BUILDFLAG(IS_MAC)
240 // On MacOS, calling omnibox->SetFocus function doesn't invoke
241 // RWHI::SetActive. Hence there is no IPC call to renderer and
242 // FakeFrameWidget's 'active' state remains uninitialised.
243 EXPECT_EQ(fake_frame_widget.GetActive(), absl::nullopt);
245 EXPECT_EQ(fake_frame_widget.GetActive(), false);