blob: b9588cbcc436c419f32f3082a1fd35875b55bea3 [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2020 The Chromium Authors
David Bokan3f72f102020-04-23 22:18:562// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Sean Mahere672a662023-01-09 21:42:285#include "base/task/single_thread_task_runner.h"
sebsg901c2712021-10-18 21:12:526#include "base/test/metrics/histogram_tester.h"
David Bokan3f72f102020-04-23 22:18:567#include "base/test/scoped_feature_list.h"
8#include "base/test/test_timeouts.h"
John Abd-El-Malekea3ba3c2021-06-14 12:05:489#include "build/build_config.h"
David Bokan1501b6f2021-04-21 00:28:2410#include "content/browser/renderer_host/frame_tree_node.h"
David Bokan3f72f102020-04-23 22:18:5611#include "content/browser/renderer_host/render_widget_host_impl.h"
David Bokan1501b6f2021-04-21 00:28:2412#include "content/browser/web_contents/web_contents_impl.h"
David Bokan3f72f102020-04-23 22:18:5613#include "content/public/browser/render_view_host.h"
14#include "content/public/browser/web_contents.h"
15#include "content/public/common/content_features.h"
16#include "content/public/common/content_switches.h"
Rakina Zata Amni348fe2b2021-07-09 05:28:5217#include "content/public/test/back_forward_cache_util.h"
Peter Kasting919ce652020-05-07 10:22:3618#include "content/public/test/browser_test.h"
David Bokan3f72f102020-04-23 22:18:5619#include "content/public/test/browser_test_utils.h"
20#include "content/public/test/content_browser_test.h"
21#include "content/public/test/content_browser_test_utils.h"
22#include "content/public/test/hit_test_region_observer.h"
23#include "content/public/test/test_navigation_observer.h"
24#include "content/shell/browser/shell.h"
25#include "net/dns/mock_host_resolver.h"
26#include "net/test/embedded_test_server/controllable_http_response.h"
27#include "net/test/embedded_test_server/embedded_test_server.h"
Stephen Chenney91a186b2021-03-23 10:42:0128#include "third_party/blink/public/common/switches.h"
David Bokan3f72f102020-04-23 22:18:5629#include "url/gurl.h"
30
David Bokan71e81512020-08-18 17:19:4631// RunUntilInputProcessed will force a Blink lifecycle which is needed
32// because did_scroll is set in an onscroll handler which may be delayed from
33// the scroll by a frame.
34#define EXPECT_DID_SCROLL(scrolled) \
35 RunUntilInputProcessed(GetWidgetHost()); \
36 EXPECT_EQ(scrolled, EvalJs(main_contents, "did_scroll;", \
37 EXECUTE_SCRIPT_NO_USER_GESTURE));
38
39#define ASSERT_DID_SCROLL(scrolled) \
40 RunUntilInputProcessed(GetWidgetHost()); \
41 ASSERT_EQ(scrolled, EvalJs(main_contents, "did_scroll;", \
42 EXECUTE_SCRIPT_NO_USER_GESTURE));
43
David Bokan3f72f102020-04-23 22:18:5644namespace content {
45
46class TextFragmentAnchorBrowserTest : public ContentBrowserTest {
David Bokan3f72f102020-04-23 22:18:5647 protected:
48 void SetUpOnMainThread() override {
49 host_resolver()->AddRule("*", "127.0.0.1");
50 }
51
52 void SetUpCommandLine(base::CommandLine* command_line) override {
David Bokan3f72f102020-04-23 22:18:5653 command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
54 "TextFragmentIdentifiers");
Stephen Chenney91a186b2021-03-23 10:42:0155 // Slow bots are flaky due to slower loading interacting with
56 // deferred commits.
57 command_line->AppendSwitch(blink::switches::kAllowPreCommitInput);
David Bokan3f72f102020-04-23 22:18:5658 }
59
60 // Simulates a click on the middle of the DOM element with the given |id|.
61 void ClickElementWithId(WebContents* web_contents, const std::string& id) {
62 // Get the center coordinates of the DOM element.
63 const int x = EvalJs(web_contents,
64 JsReplace("const bounds = "
65 "document.getElementById($1)."
66 "getBoundingClientRect();"
67 "Math.floor(bounds.left + bounds.width / 2)",
68 id))
69 .ExtractInt();
70 const int y = EvalJs(web_contents,
71 JsReplace("const bounds = "
72 "document.getElementById($1)."
73 "getBoundingClientRect();"
74 "Math.floor(bounds.top + bounds.height / 2)",
75 id))
76 .ExtractInt();
77
78 SimulateMouseClickAt(web_contents, 0, blink::WebMouseEvent::Button::kLeft,
79 gfx::Point(x, y));
David Bokan289abe0f2021-10-04 16:51:0780 RunUntilInputProcessed(GetWidgetHost());
David Bokan3f72f102020-04-23 22:18:5681 }
82
83 void WaitForPageLoad(WebContents* contents) {
84 EXPECT_TRUE(WaitForLoadStop(contents));
Dave Tapuska327c06c92022-06-13 20:31:5185 EXPECT_TRUE(WaitForRenderFrameReady(contents->GetPrimaryMainFrame()));
David Bokan3f72f102020-04-23 22:18:5686 }
87
88 RenderWidgetHostImpl* GetWidgetHost() {
Lucas Furukawa Gadanie7649422021-02-03 03:04:3289 return RenderWidgetHostImpl::From(shell()
90 ->web_contents()
Dave Tapuska327c06c92022-06-13 20:31:5191 ->GetPrimaryMainFrame()
Lucas Furukawa Gadanie7649422021-02-03 03:04:3292 ->GetRenderViewHost()
93 ->GetWidget());
David Bokan3f72f102020-04-23 22:18:5694 }
David Bokan3f72f102020-04-23 22:18:5695};
96
97IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledOnUserNavigation) {
98 ASSERT_TRUE(embedded_test_server()->Start());
99 GURL url(embedded_test_server()->GetURL("/target_text_link.html"));
100 GURL target_text_url(embedded_test_server()->GetURL(
101 "/scrollable_page_with_content.html#:~:text=text"));
102
103 EXPECT_TRUE(NavigateToURL(shell(), url));
104
105 WebContents* main_contents = shell()->web_contents();
106 TestNavigationObserver observer(main_contents);
David Bokan3f72f102020-04-23 22:18:56107
108 // We need to wait until hit test data is available.
109 HitTestRegionObserver hittest_observer(GetWidgetHost()->GetFrameSinkId());
110 hittest_observer.WaitForHitTestData();
111
112 ClickElementWithId(main_contents, "link");
113 observer.Wait();
114 EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
115
Rakina Zata Amnid3af5db92020-08-06 06:51:39116 // Observe the frame after page is loaded. Note that we need to initialize
117 // this after navigation because the main RenderFrameHost might have changed
118 // from before the navigation started.
119 RenderFrameSubmissionObserver frame_observer(main_contents);
David Bokan3f72f102020-04-23 22:18:56120 WaitForPageLoad(main_contents);
121 frame_observer.WaitForScrollOffsetAtTop(
122 /*expected_scroll_offset_at_top=*/false);
David Bokan71e81512020-08-18 17:19:46123
124 EXPECT_DID_SCROLL(true);
David Bokan3f72f102020-04-23 22:18:56125}
126
127IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
128 EnabledOnBrowserNavigation) {
129 ASSERT_TRUE(embedded_test_server()->Start());
130 GURL url(embedded_test_server()->GetURL(
131 "/scrollable_page_with_content.html#:~:text=text"));
132 WebContents* main_contents = shell()->web_contents();
133 RenderFrameSubmissionObserver frame_observer(main_contents);
134
135 EXPECT_TRUE(NavigateToURL(shell(), url));
136
137 WaitForPageLoad(main_contents);
138 frame_observer.WaitForScrollOffsetAtTop(
139 /*expected_scroll_offset_at_top=*/false);
David Bokan71e81512020-08-18 17:19:46140 EXPECT_DID_SCROLL(true);
David Bokan3f72f102020-04-23 22:18:56141}
142
143IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
144 EnabledOnUserGestureScriptNavigation) {
145 ASSERT_TRUE(embedded_test_server()->Start());
146 GURL url(embedded_test_server()->GetURL("/empty.html"));
147 GURL target_text_url(embedded_test_server()->GetURL(
148 "/scrollable_page_with_content.html#:~:text=text"));
149
150 EXPECT_TRUE(NavigateToURL(shell(), url));
151
152 WebContents* main_contents = shell()->web_contents();
153 TestNavigationObserver observer(main_contents);
David Bokan3f72f102020-04-23 22:18:56154
Avi Drissmanc91bd8e2021-04-19 23:58:44155 EXPECT_TRUE(
156 ExecJs(main_contents, "location = '" + target_text_url.spec() + "';"));
David Bokan3f72f102020-04-23 22:18:56157 observer.Wait();
158 EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
Rakina Zata Amnid3af5db92020-08-06 06:51:39159 // Observe the frame after page is loaded. Note that we need to initialize
160 // this after navigation because the main RenderFrameHost might have changed
161 // from before the navigation started.
162 RenderFrameSubmissionObserver frame_observer(main_contents);
David Bokan3f72f102020-04-23 22:18:56163
164 WaitForPageLoad(main_contents);
165 frame_observer.WaitForScrollOffsetAtTop(
166 /*expected_scroll_offset_at_top=*/false);
David Bokan71e81512020-08-18 17:19:46167 EXPECT_DID_SCROLL(true);
David Bokan3f72f102020-04-23 22:18:56168}
169
David Bokan71e81512020-08-18 17:19:46170// Ensures that a simulated redirect service works correctly. That is, only the
171// initial NavigateToURL has a user gesture but this should be propagated
172// through the window.location navigation which doesn't have a user gesture.
David Bokan3f72f102020-04-23 22:18:56173IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
David Bokan71e81512020-08-18 17:19:46174 UserGesturePassedThroughRedirect) {
David Bokan3f72f102020-04-23 22:18:56175 ASSERT_TRUE(embedded_test_server()->Start());
176 GURL url(embedded_test_server()->GetURL("/empty.html"));
177 GURL target_text_url(embedded_test_server()->GetURL(
178 "/scrollable_page_with_content.html#:~:text=text"));
179
David Bokan71e81512020-08-18 17:19:46180 // This navigtion is simulated as if it came from the omnibox, hence it is
181 // considered to be user initiated.
David Bokan3f72f102020-04-23 22:18:56182 EXPECT_TRUE(NavigateToURL(shell(), url));
183
184 WebContents* main_contents = shell()->web_contents();
185 TestNavigationObserver observer(main_contents);
David Bokan71e81512020-08-18 17:19:46186
187 // This navigation occurs without a user gesture, simulating a client
188 // redirect. However, because the above navigation didn't activate a text
189 // fragment, permission should be propagated to this navigation.
Avi Drissmanc91bd8e2021-04-19 23:58:44190 EXPECT_TRUE(ExecJs(main_contents,
191 "location = '" + target_text_url.spec() + "';",
192 EXECUTE_SCRIPT_NO_USER_GESTURE));
David Bokan3f72f102020-04-23 22:18:56193 observer.Wait();
194 EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
195
196 WaitForPageLoad(main_contents);
David Bokan71e81512020-08-18 17:19:46197 RenderFrameSubmissionObserver frame_observer(main_contents);
198 frame_observer.WaitForScrollOffsetAtTop(
199 /*expected_scroll_offset_at_top=*/false);
200 EXPECT_DID_SCROLL(true);
201}
David Bokan3f72f102020-04-23 22:18:56202
David Bokan71e81512020-08-18 17:19:46203// Ensures that a text fragment activation consumes a user gesture so that
204// future navigations cannot activate a text fragment without a new user
205// gesture.
206IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, UserGestureConsumed) {
207 ASSERT_TRUE(embedded_test_server()->Start());
208 GURL empty_page_url(embedded_test_server()->GetURL("/empty.html"));
209 GURL target_text_url(embedded_test_server()->GetURL(
210 "/scrollable_page_with_content.html#:~:text=text"));
211
212 WebContents* main_contents = shell()->web_contents();
213
214 // This navigtion is simulated as if it came from the omnibox, hence it is
215 // considered to be user initiated.
216 {
217 TestNavigationObserver observer(main_contents);
218 ASSERT_TRUE(NavigateToURL(shell(), target_text_url));
219 observer.Wait();
220 ASSERT_EQ(target_text_url, main_contents->GetLastCommittedURL());
221
222 // Ensure the page did scroll to the text fragment. Note, we can't use
223 // WaitForPageLoad since the WaitForRenderFrameReady executes javascript
224 // with a user gesture.
225 WaitForLoadStop(main_contents);
226 RenderFrameSubmissionObserver frame_observer(main_contents);
227 frame_observer.WaitForScrollOffsetAtTop(
228 /*expected_scroll_offset_at_top=*/false);
229 ASSERT_DID_SCROLL(true);
230 }
231
232 // We now want to try a second text fragment navigation. Same document
233 // navigations are blocked so we'll navigate away first.
234 {
235 TestNavigationObserver observer(main_contents);
Avi Drissmanc91bd8e2021-04-19 23:58:44236 ASSERT_TRUE(ExecJs(main_contents,
237 "location = '" + empty_page_url.spec() + "';",
238 EXECUTE_SCRIPT_NO_USER_GESTURE));
David Bokan71e81512020-08-18 17:19:46239 observer.Wait();
240 ASSERT_EQ(empty_page_url, main_contents->GetLastCommittedURL());
241 WaitForLoadStop(main_contents);
242 }
243
244 // Now try another text fragment navigation. Since we haven't had a user
245 // gesture since the last one, it should be blocked.
246 {
247 TestNavigationObserver observer(main_contents);
Avi Drissmanc91bd8e2021-04-19 23:58:44248 ASSERT_TRUE(ExecJs(main_contents,
249 "location = '" + target_text_url.spec() + "';",
250 EXECUTE_SCRIPT_NO_USER_GESTURE));
David Bokan71e81512020-08-18 17:19:46251 observer.Wait();
252 ASSERT_EQ(target_text_url, main_contents->GetLastCommittedURL());
253 WaitForLoadStop(main_contents);
254
255 // Wait a short amount of time to ensure the page does not scroll.
256 base::RunLoop run_loop;
Sean Maher5b9af51f2022-11-21 15:32:47257 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
David Bokan71e81512020-08-18 17:19:46258 FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
259 run_loop.Run();
260 EXPECT_DID_SCROLL(false);
261 }
David Bokan3f72f102020-04-23 22:18:56262}
263
264IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
Dominique Fauteux-Chapleau3e8d3452021-07-14 17:20:02265 DisabledOnScriptHistoryNavigation) {
David Bokan3f72f102020-04-23 22:18:56266 ASSERT_TRUE(embedded_test_server()->Start());
267 GURL target_text_url(embedded_test_server()->GetURL(
268 "/scrollable_page_with_content.html#:~:text=text"));
269 GURL url(embedded_test_server()->GetURL("/empty.html"));
270
271 EXPECT_TRUE(NavigateToURL(shell(), target_text_url));
272
273 WebContents* main_contents = shell()->web_contents();
Dominique Fauteux-Chapleau3e8d3452021-07-14 17:20:02274 // The test assumes the previous page gets deleted after navigation and will
275 // be recreated with did_scroll == false. Disable back/forward cache to ensure
276 // that it doesn't get preserved in the cache.
277 DisableBackForwardCacheForTesting(
Rakina Zata Amni30af7062022-01-19 23:46:36278 main_contents, BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING);
Dominique Fauteux-Chapleau3e8d3452021-07-14 17:20:02279
Rakina Zata Amnid3af5db92020-08-06 06:51:39280 {
281 // The RenderFrameSubmissionObserver destructor expects the RenderFrameHost
282 // stays the same until it gets destructed, so we need to scope this to make
283 // sure it gets destructed before the next navigation.
284 RenderFrameSubmissionObserver frame_observer(main_contents);
285 frame_observer.WaitForScrollOffsetAtTop(false);
David Bokan3f72f102020-04-23 22:18:56286
Rakina Zata Amnid3af5db92020-08-06 06:51:39287 // Scroll the page back to top so scroll restoration does not scroll the
288 // target back into view.
Avi Drissmanc91bd8e2021-04-19 23:58:44289 EXPECT_TRUE(ExecJs(main_contents, "window.scrollTo(0, 0)"));
Rakina Zata Amnid3af5db92020-08-06 06:51:39290 frame_observer.WaitForScrollOffsetAtTop(true);
291 }
David Bokan3f72f102020-04-23 22:18:56292
293 EXPECT_TRUE(NavigateToURL(shell(), url));
294
295 TestNavigationObserver observer(main_contents);
Avi Drissmanc91bd8e2021-04-19 23:58:44296 EXPECT_TRUE(
297 ExecJs(main_contents, "history.back()", EXECUTE_SCRIPT_NO_USER_GESTURE));
David Bokan3f72f102020-04-23 22:18:56298 observer.Wait();
299 EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
300
301 WaitForPageLoad(main_contents);
302
303 // Wait a short amount of time to ensure the page does not scroll.
304 base::RunLoop run_loop;
Sean Maher5b9af51f2022-11-21 15:32:47305 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
David Bokan3f72f102020-04-23 22:18:56306 FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
307 run_loop.Run();
David Bokan3f72f102020-04-23 22:18:56308
309 // Note: we use a scroll handler in the page to check whether any scrolls
310 // happened at all, rather than checking the current scroll offset. This is
311 // to ensure that if the offset is reset back to the top for other reasons
312 // (e.g. history restoration) we still fail this test. See
313 // https://crbug.com/1042986 for why this matters.
David Bokan71e81512020-08-18 17:19:46314 EXPECT_DID_SCROLL(false);
David Bokan3f72f102020-04-23 22:18:56315}
316
Rune Lillesveen09a17df42023-08-07 14:19:09317// crbug.com/1470712: Flaky on CrOS Debug
318#if BUILDFLAG(IS_CHROMEOS) && !defined(NDEBUG)
319#define MAYBE_SameDocumentBrowserNavigation \
320 DISABLED_SameDocumentBrowserNavigation
321#else
322#define MAYBE_SameDocumentBrowserNavigation SameDocumentBrowserNavigation
323#endif
David Bokana71830a2021-04-23 18:22:13324// Ensure a same-document navigation from browser UI scrolls to the text
325// fragment.
David Bokan3f72f102020-04-23 22:18:56326IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
Rune Lillesveen09a17df42023-08-07 14:19:09327 MAYBE_SameDocumentBrowserNavigation) {
David Bokan3f72f102020-04-23 22:18:56328 ASSERT_TRUE(embedded_test_server()->Start());
329 GURL url(embedded_test_server()->GetURL(
330 "/scrollable_page_with_content.html#:~:text=text"));
331 WebContents* main_contents = shell()->web_contents();
332 RenderFrameSubmissionObserver frame_observer(main_contents);
333
334 EXPECT_TRUE(NavigateToURL(shell(), url));
335
336 WaitForPageLoad(main_contents);
337 frame_observer.WaitForScrollOffsetAtTop(false);
338
339 // Scroll the page back to top. Make sure we reset the |did_scroll| variable
340 // we'll use below to ensure the same-document navigation invokes the text
341 // fragment.
Avi Drissmanc91bd8e2021-04-19 23:58:44342 EXPECT_TRUE(ExecJs(main_contents, "window.scrollTo(0, 0)"));
David Bokan3f72f102020-04-23 22:18:56343 frame_observer.WaitForScrollOffsetAtTop(true);
David Bokan71e81512020-08-18 17:19:46344 RunUntilInputProcessed(GetWidgetHost());
David Bokan3f72f102020-04-23 22:18:56345 EXPECT_TRUE(ExecJs(main_contents, "did_scroll = false;"));
346
347 // Perform a same-document browser initiated navigation
348 GURL same_doc_url(embedded_test_server()->GetURL(
349 "/scrollable_page_with_content.html#:~:text=some"));
350 EXPECT_TRUE(NavigateToURL(shell(), same_doc_url));
351
352 WaitForPageLoad(main_contents);
353 frame_observer.WaitForScrollOffsetAtTop(
354 /*expected_scroll_offset_at_top=*/false);
David Bokan71e81512020-08-18 17:19:46355 EXPECT_DID_SCROLL(true);
356}
357
Rune Lillesveen09a17df42023-08-07 14:19:09358// crbug.com/1470712: Flaky on CrOS Debug
359#if BUILDFLAG(IS_CHROMEOS) && !defined(NDEBUG)
360#define MAYBE_SameDocumentBrowserNavigationOnScriptNavigatedDocument \
361 DISABLED_SameDocumentBrowserNavigationOnScriptNavigatedDocument
362#else
363#define MAYBE_SameDocumentBrowserNavigationOnScriptNavigatedDocument \
364 SameDocumentBrowserNavigationOnScriptNavigatedDocument
365#endif
366IN_PROC_BROWSER_TEST_F(
367 TextFragmentAnchorBrowserTest,
368 MAYBE_SameDocumentBrowserNavigationOnScriptNavigatedDocument) {
David Bokan71e81512020-08-18 17:19:46369 ASSERT_TRUE(embedded_test_server()->Start());
370 WebContents* main_contents = shell()->web_contents();
Dominique Fauteux-Chapleau3e8d3452021-07-14 17:20:02371 // The test assumes the RenderWidgetHost stays the same after navigation,
372 // which won't happen if same-site back/forward-cache is enabled. Disable it
373 // so that we will keep RenderWidgetHost even after navigation.
374 DisableBackForwardCacheForTesting(
375 main_contents, BackForwardCacheImpl::TEST_ASSUMES_NO_RENDER_FRAME_CHANGE);
David Bokan71e81512020-08-18 17:19:46376
377 // Load an initial page
378 {
379 GURL initial_url(embedded_test_server()->GetURL("/empty.html"));
380 EXPECT_TRUE(NavigateToURL(shell(), initial_url));
381 WaitForPageLoad(main_contents);
382 }
383
384 // Now navigate to the target document without a user gesture. We provide a
385 // text-fragment here and expect it to be invoked because the initial load
386 // was browser-initiated so its transferred to this load via the text fragment
387 // token. This navigation ensures the token is consumed.
388 {
389 GURL target_url(embedded_test_server()->GetURL(
390 "/scrollable_page_with_content.html#:~:text=text"));
391 TestNavigationObserver observer(main_contents);
Avi Drissmanc91bd8e2021-04-19 23:58:44392 EXPECT_TRUE(ExecJs(main_contents, "location = '" + target_url.spec() + "';",
393 EXECUTE_SCRIPT_NO_USER_GESTURE));
David Bokan71e81512020-08-18 17:19:46394 observer.Wait();
Rakina Zata Amni364eb5d2023-03-22 13:19:00395
396 RenderFrameSubmissionObserver frame_observer(main_contents);
David Bokan71e81512020-08-18 17:19:46397 EXPECT_EQ(target_url, main_contents->GetLastCommittedURL());
398 frame_observer.WaitForScrollOffsetAtTop(false);
399 EXPECT_DID_SCROLL(true);
400 }
401
402 // Scroll the page back to top. Make sure we reset the |did_scroll| variable
403 // we'll use below to ensure the same-document navigation invokes the text
404 // fragment.
405 {
Rakina Zata Amni364eb5d2023-03-22 13:19:00406 RenderFrameSubmissionObserver frame_observer(main_contents);
Avi Drissmanc91bd8e2021-04-19 23:58:44407 EXPECT_TRUE(ExecJs(main_contents, "window.scrollTo(0, 0)"));
David Bokan71e81512020-08-18 17:19:46408 frame_observer.WaitForScrollOffsetAtTop(true);
409 RunUntilInputProcessed(GetWidgetHost());
410 EXPECT_TRUE(ExecJs(main_contents, "did_scroll = false;"));
411 }
412
413 // Perform a same-document browser initiated navigation. This should cause a
414 // scroll because the navigation is browser-initiated, despite the fact that
415 // the document was loaded without a user gesture.
416 {
417 GURL same_doc_url(embedded_test_server()->GetURL(
418 "/scrollable_page_with_content.html#:~:text=some"));
419 EXPECT_TRUE(NavigateToURL(shell(), same_doc_url));
420
Rakina Zata Amni364eb5d2023-03-22 13:19:00421 RenderFrameSubmissionObserver frame_observer(main_contents);
David Bokan71e81512020-08-18 17:19:46422 WaitForPageLoad(main_contents);
423
424 frame_observer.WaitForScrollOffsetAtTop(
425 /*expected_scroll_offset_at_top=*/false);
426 EXPECT_DID_SCROLL(true);
427 }
428}
429
430// Ensure a text fragment token isn't generated via history.back() navigation.
431// This is a tricky case because all history navigations (including script
432// initiated) appear to the renderer as "browser-initiated".
433IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
434 HistoryDoesntGenerateToken) {
435 ASSERT_TRUE(embedded_test_server()->Start());
436 WebContents* main_contents = shell()->web_contents();
David Bokan71e81512020-08-18 17:19:46437 GURL url(embedded_test_server()->GetURL(
David Bokana71830a2021-04-23 18:22:13438 "a.com", "/scrollable_page_with_content.html#:~:text=text"));
David Bokan71e81512020-08-18 17:19:46439
David Bokan71e81512020-08-18 17:19:46440 {
David Bokana71830a2021-04-23 18:22:13441 // RenderFrameSubmissionObserver must not outlive the RenderWidgetHostImpl
442 // so ensure it's destructed before we navigate to a new page.
443 RenderFrameSubmissionObserver frame_observer(main_contents);
444
445 // Load a page with a text-fragment
David Bokan71e81512020-08-18 17:19:46446 EXPECT_TRUE(NavigateToURL(shell(), url));
447 WaitForPageLoad(main_contents);
448 frame_observer.WaitForScrollOffsetAtTop(false);
David Bokan71e81512020-08-18 17:19:46449
David Bokana71830a2021-04-23 18:22:13450 // Scroll the page back to top. Make sure we reset the |did_scroll| variable
451 // we'll use below to ensure the same-document navigation invokes the text
452 // fragment.
453 EXPECT_TRUE(ExecJs(main_contents, "window.scrollTo(0, 0)",
454 EXECUTE_SCRIPT_NO_USER_GESTURE));
David Bokan71e81512020-08-18 17:19:46455 frame_observer.WaitForScrollOffsetAtTop(true);
David Bokana71830a2021-04-23 18:22:13456 EXPECT_TRUE(ExecJs(main_contents, "did_scroll = false;",
Avi Drissmanc91bd8e2021-04-19 23:58:44457 EXECUTE_SCRIPT_NO_USER_GESTURE));
David Bokana71830a2021-04-23 18:22:13458
459 // Perform a scripted same-document navigation to a non-existent fragment to
460 // generate a history entry.
461 {
462 GURL temp_url(embedded_test_server()->GetURL(
463 "a.com", "/scrollable_page_with_content.html#doesntexist"));
464 TestNavigationObserver observer(main_contents);
465 EXPECT_TRUE(ExecJs(main_contents, JsReplace("location = $1;", temp_url),
466 EXECUTE_SCRIPT_NO_USER_GESTURE));
467 observer.Wait();
468 EXPECT_EQ(temp_url, main_contents->GetLastCommittedURL());
469 }
470
471 // Navigate back using history.back().
472 {
473 TestNavigationObserver observer(main_contents);
474 EXPECT_TRUE(ExecJs(main_contents, "history.back();",
475 EXECUTE_SCRIPT_NO_USER_GESTURE));
476 observer.Wait();
477 EXPECT_EQ(url, main_contents->GetLastCommittedURL());
478
479 // The page should be restored to where we left off at the top.
480 RunUntilInputProcessed(GetWidgetHost());
David Bokan289abe0f2021-10-04 16:51:07481 ASSERT_EQ(EvalJs(main_contents, "window.scrollY;",
482 EXECUTE_SCRIPT_NO_USER_GESTURE)
483 .ExtractInt(),
484 0);
David Bokana71830a2021-04-23 18:22:13485 ASSERT_DID_SCROLL(false);
486 }
David Bokan71e81512020-08-18 17:19:46487 }
488
David Bokana71830a2021-04-23 18:22:13489 // Now try to navigate to a new page with a text-fragment. This should be
David Bokan71e81512020-08-18 17:19:46490 // blocked because the token was consumed in the initial load at the top and
David Bokan289abe0f2021-10-04 16:51:07491 // a new one must not have been generated by the same document navigations
David Bokan71e81512020-08-18 17:19:46492 // above.
493 {
494 GURL new_url(embedded_test_server()->GetURL(
David Bokana71830a2021-04-23 18:22:13495 "b.com", "/scrollable_page_with_content.html#:~:text=Some"));
David Bokan71e81512020-08-18 17:19:46496 TestNavigationObserver observer(main_contents);
Avi Drissmanc91bd8e2021-04-19 23:58:44497 EXPECT_TRUE(ExecJs(main_contents, "location = '" + new_url.spec() + "';",
498 EXECUTE_SCRIPT_NO_USER_GESTURE));
David Bokan71e81512020-08-18 17:19:46499 observer.Wait();
500 EXPECT_EQ(new_url, main_contents->GetLastCommittedURL());
David Bokan289abe0f2021-10-04 16:51:07501 base::RunLoop run_loop;
Sean Maher5b9af51f2022-11-21 15:32:47502 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
David Bokan289abe0f2021-10-04 16:51:07503 FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
504 run_loop.Run();
David Bokan71e81512020-08-18 17:19:46505 EXPECT_DID_SCROLL(false);
506 }
David Bokan3f72f102020-04-23 22:18:56507}
508
David Bokana71830a2021-04-23 18:22:13509// Ensure same-document navigation to a text-fragment works when initiated from
510// the document itself.
David Bokan3f72f102020-04-23 22:18:56511IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
David Bokana71830a2021-04-23 18:22:13512 SameDocumentScriptNavigation) {
David Bokan3f72f102020-04-23 22:18:56513 ASSERT_TRUE(embedded_test_server()->Start());
514 GURL url(
515 embedded_test_server()->GetURL("/scrollable_page_with_content.html"));
516 GURL target_text_url(embedded_test_server()->GetURL(
517 "/scrollable_page_with_content.html#:~:text=some"));
518
519 EXPECT_TRUE(NavigateToURL(shell(), url));
520
521 WebContents* main_contents = shell()->web_contents();
522 TestNavigationObserver observer(main_contents);
David Bokana71830a2021-04-23 18:22:13523 // User gesture not required since the script is running in the same origin
524 // as the page.
Avi Drissmanc91bd8e2021-04-19 23:58:44525 EXPECT_TRUE(ExecJs(main_contents,
526 "location = '" + target_text_url.spec() + "';",
527 EXECUTE_SCRIPT_NO_USER_GESTURE));
David Bokan3f72f102020-04-23 22:18:56528 observer.Wait();
529 EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
530
531 WaitForPageLoad(main_contents);
David Bokana71830a2021-04-23 18:22:13532 RenderFrameSubmissionObserver frame_observer(main_contents);
533 frame_observer.WaitForScrollOffsetAtTop(
534 /*expected_scroll_offset_at_top=*/false);
535 EXPECT_DID_SCROLL(true);
536}
David Bokan3f72f102020-04-23 22:18:56537
David Bokana71830a2021-04-23 18:22:13538// Ensure same-document navigation to a text-fragment works when initiated from
539// the same origin.
540IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
541 SameDocumentScriptNavigationSameOrigin) {
542 ASSERT_TRUE(embedded_test_server()->Start());
543 GURL url(embedded_test_server()->GetURL(
544 "a.com", "/scrollable_page_with_content.html"));
545 GURL target_text_url(embedded_test_server()->GetURL(
546 "a.com", "/scrollable_page_with_content.html#:~:text=some"));
547 GURL cross_origin_inner_url(
548 embedded_test_server()->GetURL("a.com", "/hello.html"));
549
550 EXPECT_TRUE(NavigateToURL(shell(), url));
551
552 WebContentsImpl* main_contents =
553 static_cast<WebContentsImpl*>(shell()->web_contents());
Carlos Caballero15caeeb2021-10-27 09:57:55554 FrameTreeNode* root = main_contents->GetPrimaryFrameTree().root();
David Bokana71830a2021-04-23 18:22:13555
556 // Insert a same-origin iframe from which we'll execute script.
557 {
558 const auto script = JsReplace(
559 R"JS(
560 let f = document.createElement("iframe");
561 f.src=$1;
562 document.body.appendChild(f);
563 )JS",
564 cross_origin_inner_url);
565
566 TestNavigationObserver observer(main_contents);
567 EXPECT_TRUE(ExecJs(main_contents, script, EXECUTE_SCRIPT_NO_USER_GESTURE));
568 observer.Wait();
569 ASSERT_EQ(1u, root->child_count());
570 }
571
572 // Try navigating the top frame to a same-document text fragment from inside
573 // the iframe. This should be allowed, even without user-gesture, since it's
574 // same-origin; script is able to see all its content anyway.
575 {
576 TestNavigationObserver observer(main_contents);
577 RenderFrameHostImpl* child_rfh = root->child_at(0)->current_frame_host();
578 EXPECT_TRUE(ExecJs(child_rfh,
579 JsReplace("window.top.location = $1;", target_text_url),
580 EXECUTE_SCRIPT_NO_USER_GESTURE));
581 observer.Wait();
582 EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
583
584 RenderFrameSubmissionObserver frame_observer(main_contents);
585 WaitForPageLoad(main_contents);
586 frame_observer.WaitForScrollOffsetAtTop(
587 /*expected_scroll_offset_at_top=*/false);
588 EXPECT_DID_SCROLL(true);
589 }
David Bokan3f72f102020-04-23 22:18:56590}
591
David Bokan1501b6f2021-04-21 00:28:24592// Ensure same-document navigation to a text-fragment is blocked when initiated
593// from a different origin.
594IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
595 SameDocumentScriptNavigationCrossOrigin) {
596 ASSERT_TRUE(embedded_test_server()->Start());
597 GURL url(embedded_test_server()->GetURL(
598 "a.com", "/scrollable_page_with_content.html"));
599 GURL target_text_url(embedded_test_server()->GetURL(
600 "a.com", "/scrollable_page_with_content.html#:~:text=some"));
601 GURL cross_origin_inner_url(
602 embedded_test_server()->GetURL("b.com", "/hello.html"));
603
604 EXPECT_TRUE(NavigateToURL(shell(), url));
605
606 WebContentsImpl* main_contents =
607 static_cast<WebContentsImpl*>(shell()->web_contents());
Carlos Caballero15caeeb2021-10-27 09:57:55608 FrameTreeNode* root = main_contents->GetPrimaryFrameTree().root();
David Bokan1501b6f2021-04-21 00:28:24609
610 // Insert a cross-origin iframe from which we'll execute script.
611 {
612 const auto script = JsReplace(
613 R"JS(
614 let f = document.createElement("iframe");
615 f.src=$1;
616 document.body.appendChild(f);
617 )JS",
618 cross_origin_inner_url);
619
620 TestNavigationObserver observer(main_contents);
621 EXPECT_TRUE(ExecJs(main_contents, script, EXECUTE_SCRIPT_NO_USER_GESTURE));
622 observer.Wait();
623 ASSERT_EQ(1u, root->child_count());
624 }
625
626 // Try navigating the top frame to a same-document text fragment from inside
627 // the iframe. This should be blocked as its cross-origin. Note, the script
628 // executes with a user gesture but this is still blocked. Same-document
629 // navigations are allowed only when initiated from same-origin or
630 // browser-UI.
631 {
632 TestNavigationObserver observer(main_contents);
633 RenderFrameHostImpl* child_rfh = root->child_at(0)->current_frame_host();
634 EXPECT_TRUE(ExecJs(
635 child_rfh, JsReplace("window.top.location = $1;", target_text_url)));
636 observer.Wait();
637 EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
638
639 WaitForPageLoad(main_contents);
640 EXPECT_DID_SCROLL(false);
641 }
642}
643
David Bokan289abe0f2021-10-04 16:51:07644// Test that when ForceLoadAtTop document policy is explicitly turned off,
645// scrolling to a text fragment is allowed.
David Bokan3f72f102020-04-23 22:18:56646IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledByDocumentPolicy) {
647 net::test_server::ControllableHttpResponse response(embedded_test_server(),
648 "/target.html");
649
650 ASSERT_TRUE(embedded_test_server()->Start());
651 GURL url(embedded_test_server()->GetURL("/target.html#:~:text=text"));
652 WebContents* main_contents = shell()->web_contents();
653 RenderFrameSubmissionObserver frame_observer(main_contents);
654
655 // Load the target document
656 TestNavigationManager navigation_manager(main_contents, url);
657 shell()->LoadURL(url);
658
659 // Start navigation
660 EXPECT_TRUE(navigation_manager.WaitForRequestStart());
661 navigation_manager.ResumeNavigation();
662
663 // Send Document-Policy header
664 response.WaitForRequest();
665 response.Send(
666 "HTTP/1.1 200 OK\r\n"
667 "Content-Type: text/html; charset=utf-8\r\n"
David Bokan84c965362020-08-14 17:25:32668 "Document-Policy: force-load-at-top=?0\r\n"
David Bokan3f72f102020-04-23 22:18:56669 "\r\n"
670 "<script>"
671 " let did_scroll = false;"
672 " window.addEventListener('scroll', () => {"
673 " did_scroll = true;"
674 " });"
675 "</script>"
676 "<p style='position: absolute; top: 10000px;'>Some text</p>");
677 response.Done();
678
679 EXPECT_TRUE(navigation_manager.WaitForResponse());
680 navigation_manager.ResumeNavigation();
Fergal Daly83bc3cd2023-01-18 00:22:54681 ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
David Bokan3f72f102020-04-23 22:18:56682
683 WaitForPageLoad(main_contents);
684 frame_observer.WaitForScrollOffsetAtTop(
685 /*expected_scroll_offset_at_top=*/false);
David Bokan71e81512020-08-18 17:19:46686 EXPECT_DID_SCROLL(true);
David Bokan3f72f102020-04-23 22:18:56687}
688
David Bokan289abe0f2021-10-04 16:51:07689// Test that the ForceLoadAtTop document policy disables scrolling to a text
690// fragment.
David Bokan3f72f102020-04-23 22:18:56691IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
692 DisabledByDocumentPolicy) {
693 net::test_server::ControllableHttpResponse response(embedded_test_server(),
694 "/target.html");
695
696 ASSERT_TRUE(embedded_test_server()->Start());
697 GURL url(embedded_test_server()->GetURL("/target.html#:~:text=text"));
698 WebContents* main_contents = shell()->web_contents();
699
700 // Load the target document
701 TestNavigationManager navigation_manager(main_contents, url);
702 shell()->LoadURL(url);
703
704 // Start navigation
705 EXPECT_TRUE(navigation_manager.WaitForRequestStart());
706 navigation_manager.ResumeNavigation();
707
708 // Send Document-Policy header
709 response.WaitForRequest();
710 response.Send(
711 "HTTP/1.1 200 OK\r\n"
712 "Content-Type: text/html; charset=utf-8\r\n"
713 "Document-Policy: force-load-at-top\r\n"
714 "\r\n"
715 "<script>"
716 " let did_scroll = false;"
717 " window.addEventListener('scroll', () => {"
718 " did_scroll = true;"
719 " });"
720 "</script>"
721 "<p style='position: absolute; top: 10000px;'>Some text</p>");
722 response.Done();
723
724 EXPECT_TRUE(navigation_manager.WaitForResponse());
725 navigation_manager.ResumeNavigation();
Fergal Daly83bc3cd2023-01-18 00:22:54726 ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
David Bokan3f72f102020-04-23 22:18:56727
728 WaitForPageLoad(main_contents);
729 // Wait a short amount of time to ensure the page does not scroll.
730 base::RunLoop run_loop;
Sean Maher5b9af51f2022-11-21 15:32:47731 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
David Bokan3f72f102020-04-23 22:18:56732 FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
733 run_loop.Run();
David Bokan71e81512020-08-18 17:19:46734 EXPECT_DID_SCROLL(false);
David Bokan3f72f102020-04-23 22:18:56735}
736
David Bokanb2e47a12022-05-19 22:14:38737// Test that Tab key press puts focus from the start of the text directive that
738// was scrolled into view.
David Bokan2b849702021-01-14 00:22:28739IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, TabFocus) {
740 ASSERT_TRUE(embedded_test_server()->Start());
David Bokanb2e47a12022-05-19 22:14:38741 GURL url(
742 embedded_test_server()->GetURL("/scrollable_page_with_anchor.html#:~:"
743 "text=nonexistent&text=text&text=more"));
David Bokan2b849702021-01-14 00:22:28744 WebContents* main_contents = shell()->web_contents();
745 RenderFrameSubmissionObserver frame_observer(main_contents);
746 EXPECT_TRUE(NavigateToURL(shell(), url));
747 WaitForPageLoad(main_contents);
748 frame_observer.WaitForScrollOffsetAtTop(
749 /*expected_scroll_offset_at_top=*/false);
750
Colin Blundellecd384f2022-05-11 08:58:30751 DOMMessageQueue msg_queue(main_contents);
David Bokan2b849702021-01-14 00:22:28752 SimulateKeyPress(main_contents, ui::DomKey::TAB, ui::DomCode::TAB,
753 ui::VKEY_TAB, false, false, false, false);
754
755 // Wait for focus to happen.
756 std::string message;
757 EXPECT_TRUE(msg_queue.WaitForMessage(&message));
758 EXPECT_EQ("\"FocusDone2\"", message);
759}
760
David Bokan3f72f102020-04-23 22:18:56761class ForceLoadAtTopBrowserTest : public TextFragmentAnchorBrowserTest {
762 protected:
David Bokan2b849702021-01-14 00:22:28763 // Loads the given path as predetermined HTML response with a
764 // |Document-Policy: force-load-at-top| header and waits for the navigation
765 // to finish.
766 void LoadScrollablePageWithContent(const std::string& path) {
767 std::size_t hash_pos = path.find("#");
768 std::string path_without_fragment = path;
769 if (hash_pos != std::string::npos) {
770 path_without_fragment = path.substr(0, hash_pos);
771 }
772 net::test_server::ControllableHttpResponse response(embedded_test_server(),
773 path_without_fragment);
774
David Bokan3f72f102020-04-23 22:18:56775 ASSERT_TRUE(embedded_test_server()->Start());
David Bokan2b849702021-01-14 00:22:28776 GURL url(embedded_test_server()->GetURL(path));
777 RenderFrameSubmissionObserver frame_observer(shell()->web_contents());
778
779 // Load the target document.
780 TestNavigationManager navigation_manager(shell()->web_contents(), url);
781 shell()->LoadURL(url);
782
783 // Start navigation
784 ASSERT_TRUE(navigation_manager.WaitForRequestStart());
785 navigation_manager.ResumeNavigation();
786
787 // Send Document-Policy header
788 response.WaitForRequest();
789 std::string response_string =
790 "HTTP/1.1 200 OK\r\n"
791 "Content-Type: text/html; charset=utf-8\r\n"
792 "Document-Policy: force-load-at-top\r\n"
793 "\r\n"
794 R"HTML(
795 <html>
796 <head>
797 <meta name="viewport" content="width=device-width">
798 <script>
799 let did_scroll = false;
800 window.addEventListener('scroll', () => {
801 did_scroll = true;
802 });
803 </script>
804 <style>
805 p {
806 position: absolute;
807 top: 10000px;
808 }
809 </style>
810 </head>
811 <body>
812 <a id="link" href="#text">Go Down</a>
813 <p id="text">Some text</p>
814 </body>
815 </html>
816 )HTML";
817 response.Send(response_string);
818 response.Done();
819
820 ASSERT_TRUE(navigation_manager.WaitForResponse());
821 navigation_manager.ResumeNavigation();
Fergal Daly83bc3cd2023-01-18 00:22:54822 ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
David Bokan289abe0f2021-10-04 16:51:07823
824 WaitForPageLoad(shell()->web_contents());
David Bokan3f72f102020-04-23 22:18:56825 }
826};
827
828// Test that scroll restoration is disabled with ForceLoadAtTop
829IN_PROC_BROWSER_TEST_F(ForceLoadAtTopBrowserTest, ScrollRestorationDisabled) {
David Bokan2b849702021-01-14 00:22:28830 ASSERT_NO_FATAL_FAILURE(LoadScrollablePageWithContent("/index.html"));
831
David Bokan3f72f102020-04-23 22:18:56832 WebContents* main_contents = shell()->web_contents();
Rakina Zata Amni348fe2b2021-07-09 05:28:52833 // This test expects the document is freshly loaded on the back navigation
834 // so that the document policy to force-load-at-top will run. This will not
835 // happen if the document is back-forward cached, so we need to disable it.
836 DisableBackForwardCacheForTesting(main_contents,
Rakina Zata Amni30af7062022-01-19 23:46:36837 BackForwardCache::TEST_REQUIRES_NO_CACHING);
Rakina Zata Amni348fe2b2021-07-09 05:28:52838
David Bokan3f72f102020-04-23 22:18:56839 // Scroll down the page a bit
Avi Drissmanc91bd8e2021-04-19 23:58:44840 EXPECT_TRUE(ExecJs(main_contents, "window.scrollTo(0, 1000)"));
David Bokan3f72f102020-04-23 22:18:56841
842 // Navigate away
Avi Drissmanc91bd8e2021-04-19 23:58:44843 EXPECT_TRUE(ExecJs(main_contents, "window.location = 'about:blank'"));
David Bokan3f72f102020-04-23 22:18:56844 EXPECT_TRUE(WaitForLoadStop(main_contents));
David Bokan3f72f102020-04-23 22:18:56845
846 // Navigate back
Avi Drissmanc91bd8e2021-04-19 23:58:44847 EXPECT_TRUE(ExecJs(main_contents, "history.back()"));
David Bokan3f72f102020-04-23 22:18:56848 EXPECT_TRUE(WaitForLoadStop(main_contents));
David Bokan3f72f102020-04-23 22:18:56849
850 // Wait a short amount of time to ensure the page does not scroll.
851 base::RunLoop run_loop;
Sean Maher5b9af51f2022-11-21 15:32:47852 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
David Bokan3f72f102020-04-23 22:18:56853 FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
854 run_loop.Run();
Kevin McNee777eef42020-10-20 18:53:49855 RunUntilInputProcessed(GetWidgetHost());
David Bokan289abe0f2021-10-04 16:51:07856 EXPECT_EQ(EvalJs(main_contents, "window.scrollY;").ExtractInt(), 0);
David Bokan3f72f102020-04-23 22:18:56857}
858
859// Test that element fragment anchor scrolling is disabled with ForceLoadAtTop
860IN_PROC_BROWSER_TEST_F(ForceLoadAtTopBrowserTest, FragmentAnchorDisabled) {
David Bokan2b849702021-01-14 00:22:28861 ASSERT_NO_FATAL_FAILURE(LoadScrollablePageWithContent("/index.html#text"));
David Bokan3f72f102020-04-23 22:18:56862 WebContents* main_contents = shell()->web_contents();
863
David Bokan3f72f102020-04-23 22:18:56864 // Wait a short amount of time to ensure the page does not scroll.
865 base::RunLoop run_loop;
Sean Maher5b9af51f2022-11-21 15:32:47866 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
David Bokan3f72f102020-04-23 22:18:56867 FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
868 run_loop.Run();
Kevin McNee777eef42020-10-20 18:53:49869 RunUntilInputProcessed(GetWidgetHost());
David Bokan289abe0f2021-10-04 16:51:07870 EXPECT_DID_SCROLL(false);
David Bokan3f72f102020-04-23 22:18:56871}
872
David Bokan289abe0f2021-10-04 16:51:07873// Ensure the ForceLoadAtTop policy doesn't prevent same-document fragment
874// navigations.
875IN_PROC_BROWSER_TEST_F(ForceLoadAtTopBrowserTest, SameDocumentNavigation) {
David Bokan2b849702021-01-14 00:22:28876 ASSERT_NO_FATAL_FAILURE(LoadScrollablePageWithContent("/index.html"));
David Bokandd1b8d02020-06-25 21:17:58877 WebContents* main_contents = shell()->web_contents();
878
David Bokan289abe0f2021-10-04 16:51:07879 ASSERT_DID_SCROLL(false);
David Bokandd1b8d02020-06-25 21:17:58880
David Bokan289abe0f2021-10-04 16:51:07881 // Click on a link with a fragment id. Ensure we scroll to the targeted
882 // element.
David Bokandd1b8d02020-06-25 21:17:58883 ClickElementWithId(main_contents, "link");
884
David Bokan289abe0f2021-10-04 16:51:07885 EXPECT_DID_SCROLL(true);
David Bokandd1b8d02020-06-25 21:17:58886}
887
David Bokan289abe0f2021-10-04 16:51:07888// Ensure the ForceLoadAtTop policy prevents scrolling to a navigated text
889// directive.
David Bokan3f72f102020-04-23 22:18:56890IN_PROC_BROWSER_TEST_F(ForceLoadAtTopBrowserTest, TextFragmentAnchorDisabled) {
David Bokan2b849702021-01-14 00:22:28891 ASSERT_NO_FATAL_FAILURE(
892 LoadScrollablePageWithContent("/index.html#:~:text=text"));
David Bokan3f72f102020-04-23 22:18:56893 WebContents* main_contents = shell()->web_contents();
David Bokan3f72f102020-04-23 22:18:56894
895 // Wait a short amount of time to ensure the page does not scroll.
896 base::RunLoop run_loop;
Sean Maher5b9af51f2022-11-21 15:32:47897 base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask(
David Bokan3f72f102020-04-23 22:18:56898 FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
899 run_loop.Run();
Kevin McNee777eef42020-10-20 18:53:49900 RunUntilInputProcessed(GetWidgetHost());
David Bokan289abe0f2021-10-04 16:51:07901 EXPECT_DID_SCROLL(false);
David Bokan3f72f102020-04-23 22:18:56902}
903
sebsg901c2712021-10-18 21:12:52904// Tests that text fragments opened after a client redirect are considered as
905// coming from an unknown source, even if the redirect is through a known
906// search engine URL.
907IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
908 LinkOpenSourceMetrics_GoogleClientRedirect) {
909 base::HistogramTester histogram_tester;
910
911 ASSERT_TRUE(embedded_test_server()->Start());
912 GURL first_url(embedded_test_server()->GetURL("google.com", "/empty.html"));
913 GURL final_url(embedded_test_server()->GetURL(
914 "/scrollable_page_with_content.html#:~:text=text"));
915
916 // This navigtion is simulated as if it came from the omnibox, hence it is
917 // considered to be user initiated.
918 EXPECT_TRUE(NavigateToURL(shell(), first_url));
919
920 WebContents* main_contents = shell()->web_contents();
921 TestNavigationObserver observer(main_contents);
922 EXPECT_EQ(first_url, main_contents->GetLastCommittedURL());
923
924 // This navigation occurs without a user gesture, simulating a client
925 // redirect. However, because the above navigation didn't activate a text
926 // fragment, permission should be propagated to this navigation.
927 EXPECT_TRUE(ExecJs(main_contents,
928 "location.replace('" + final_url.spec() + "');",
929 EXECUTE_SCRIPT_NO_USER_GESTURE));
930 observer.Wait();
931 EXPECT_EQ(final_url, main_contents->GetLastCommittedURL());
932
933 WaitForPageLoad(main_contents);
934 RenderFrameSubmissionObserver frame_observer(main_contents);
935 frame_observer.WaitForScrollOffsetAtTop(
936 /*expected_scroll_offset_at_top=*/false);
937 EXPECT_DID_SCROLL(true);
938
939 // Bucket 0 is unknown source.
940 content::FetchHistogramsFromChildProcesses();
941 histogram_tester.ExpectUniqueSample("TextFragmentAnchor.LinkOpenSource", 0,
942 1);
943}
944
945// Tests that text fragments opened after a server redirect are considered as
946// coming from an unknown source, even if the redirect is through a known
947// search engine URL.
948IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
949 LinkOpenSourceMetrics_GoogleServerRedirect) {
950 base::HistogramTester histogram_tester;
951
952 ASSERT_TRUE(embedded_test_server()->Start());
953 GURL initial_url(embedded_test_server()->GetURL("/simple_page.html"));
954 GURL redirected_url(embedded_test_server()->GetURL(
955 "/scrollable_page_with_content.html#:~:text=text"));
956 GURL redirector_url(embedded_test_server()->GetURL(
957 "google.com", "/server-redirect?" + redirected_url.spec()));
958
959 // This navigtion is simulated as if it came from the omnibox, hence it is
960 // considered to be user initiated.
961 EXPECT_TRUE(NavigateToURL(shell(), initial_url));
962
963 WebContents* main_contents = shell()->web_contents();
964 TestNavigationObserver observer(main_contents);
965 EXPECT_EQ(initial_url, main_contents->GetLastCommittedURL());
966
967 // Simulate a user clicking on a link to the redirector url.
968 EXPECT_TRUE(ExecJs(main_contents,
969 "var hyperLinkTag = document.createElement('a'); "
970 "hyperLinkTag.setAttribute('id','fragmentLink'); "
971 "hyperLinkTag.setAttribute('href','" +
972 redirector_url.spec() +
973 "'); document.body.appendChild(hyperLinkTag); "
974 "hyperLinkTag.appendChild(document.createTextNode('"
975 "Text Fragment Link.'));"
976 "document.getElementById('fragmentLink').click();"));
977
978 observer.Wait();
979 EXPECT_EQ(redirected_url, main_contents->GetLastCommittedURL());
980
981 RenderFrameSubmissionObserver frame_observer(main_contents);
982 frame_observer.WaitForScrollOffsetAtTop(
983 /*expected_scroll_offset_at_top=*/false);
984 EXPECT_DID_SCROLL(true);
985
986 // Bucket 0 is unknown source.
987 content::FetchHistogramsFromChildProcesses();
988 histogram_tester.ExpectUniqueSample("TextFragmentAnchor.LinkOpenSource", 0,
989 1);
990}
991
David Bokan3f72f102020-04-23 22:18:56992} // namespace content