Avi Drissman | 4e1b7bc3 | 2022-09-15 14:03:50 | [diff] [blame] | 1 | // Copyright 2020 The Chromium Authors |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Sean Maher | e672a66 | 2023-01-09 21:42:28 | [diff] [blame] | 5 | #include "base/task/single_thread_task_runner.h" |
sebsg | 901c271 | 2021-10-18 21:12:52 | [diff] [blame] | 6 | #include "base/test/metrics/histogram_tester.h" |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 7 | #include "base/test/scoped_feature_list.h" |
| 8 | #include "base/test/test_timeouts.h" |
John Abd-El-Malek | ea3ba3c | 2021-06-14 12:05:48 | [diff] [blame] | 9 | #include "build/build_config.h" |
David Bokan | 1501b6f | 2021-04-21 00:28:24 | [diff] [blame] | 10 | #include "content/browser/renderer_host/frame_tree_node.h" |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 11 | #include "content/browser/renderer_host/render_widget_host_impl.h" |
David Bokan | 1501b6f | 2021-04-21 00:28:24 | [diff] [blame] | 12 | #include "content/browser/web_contents/web_contents_impl.h" |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 13 | #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 Amni | 348fe2b | 2021-07-09 05:28:52 | [diff] [blame] | 17 | #include "content/public/test/back_forward_cache_util.h" |
Peter Kasting | 919ce65 | 2020-05-07 10:22:36 | [diff] [blame] | 18 | #include "content/public/test/browser_test.h" |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 19 | #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 Chenney | 91a186b | 2021-03-23 10:42:01 | [diff] [blame] | 28 | #include "third_party/blink/public/common/switches.h" |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 29 | #include "url/gurl.h" |
| 30 | |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 31 | // 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 Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 44 | namespace content { |
| 45 | |
| 46 | class TextFragmentAnchorBrowserTest : public ContentBrowserTest { |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 47 | protected: |
| 48 | void SetUpOnMainThread() override { |
| 49 | host_resolver()->AddRule("*", "127.0.0.1"); |
| 50 | } |
| 51 | |
| 52 | void SetUpCommandLine(base::CommandLine* command_line) override { |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 53 | command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures, |
| 54 | "TextFragmentIdentifiers"); |
Stephen Chenney | 91a186b | 2021-03-23 10:42:01 | [diff] [blame] | 55 | // Slow bots are flaky due to slower loading interacting with |
| 56 | // deferred commits. |
| 57 | command_line->AppendSwitch(blink::switches::kAllowPreCommitInput); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 58 | } |
| 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 Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 80 | RunUntilInputProcessed(GetWidgetHost()); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 81 | } |
| 82 | |
| 83 | void WaitForPageLoad(WebContents* contents) { |
| 84 | EXPECT_TRUE(WaitForLoadStop(contents)); |
Dave Tapuska | 327c06c9 | 2022-06-13 20:31:51 | [diff] [blame] | 85 | EXPECT_TRUE(WaitForRenderFrameReady(contents->GetPrimaryMainFrame())); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 86 | } |
| 87 | |
| 88 | RenderWidgetHostImpl* GetWidgetHost() { |
Lucas Furukawa Gadani | e764942 | 2021-02-03 03:04:32 | [diff] [blame] | 89 | return RenderWidgetHostImpl::From(shell() |
| 90 | ->web_contents() |
Dave Tapuska | 327c06c9 | 2022-06-13 20:31:51 | [diff] [blame] | 91 | ->GetPrimaryMainFrame() |
Lucas Furukawa Gadani | e764942 | 2021-02-03 03:04:32 | [diff] [blame] | 92 | ->GetRenderViewHost() |
| 93 | ->GetWidget()); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 94 | } |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 95 | }; |
| 96 | |
| 97 | IN_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 Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 107 | |
| 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 Amni | d3af5db9 | 2020-08-06 06:51:39 | [diff] [blame] | 116 | // 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 Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 120 | WaitForPageLoad(main_contents); |
| 121 | frame_observer.WaitForScrollOffsetAtTop( |
| 122 | /*expected_scroll_offset_at_top=*/false); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 123 | |
| 124 | EXPECT_DID_SCROLL(true); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 125 | } |
| 126 | |
| 127 | IN_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 Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 140 | EXPECT_DID_SCROLL(true); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 141 | } |
| 142 | |
| 143 | IN_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 Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 154 | |
Avi Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 155 | EXPECT_TRUE( |
| 156 | ExecJs(main_contents, "location = '" + target_text_url.spec() + "';")); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 157 | observer.Wait(); |
| 158 | EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL()); |
Rakina Zata Amni | d3af5db9 | 2020-08-06 06:51:39 | [diff] [blame] | 159 | // 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 Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 163 | |
| 164 | WaitForPageLoad(main_contents); |
| 165 | frame_observer.WaitForScrollOffsetAtTop( |
| 166 | /*expected_scroll_offset_at_top=*/false); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 167 | EXPECT_DID_SCROLL(true); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 168 | } |
| 169 | |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 170 | // 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 Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 173 | IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 174 | UserGesturePassedThroughRedirect) { |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 175 | 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 Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 180 | // This navigtion is simulated as if it came from the omnibox, hence it is |
| 181 | // considered to be user initiated. |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 182 | EXPECT_TRUE(NavigateToURL(shell(), url)); |
| 183 | |
| 184 | WebContents* main_contents = shell()->web_contents(); |
| 185 | TestNavigationObserver observer(main_contents); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 186 | |
| 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 Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 190 | EXPECT_TRUE(ExecJs(main_contents, |
| 191 | "location = '" + target_text_url.spec() + "';", |
| 192 | EXECUTE_SCRIPT_NO_USER_GESTURE)); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 193 | observer.Wait(); |
| 194 | EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL()); |
| 195 | |
| 196 | WaitForPageLoad(main_contents); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 197 | RenderFrameSubmissionObserver frame_observer(main_contents); |
| 198 | frame_observer.WaitForScrollOffsetAtTop( |
| 199 | /*expected_scroll_offset_at_top=*/false); |
| 200 | EXPECT_DID_SCROLL(true); |
| 201 | } |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 202 | |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 203 | // 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. |
| 206 | IN_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 Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 236 | ASSERT_TRUE(ExecJs(main_contents, |
| 237 | "location = '" + empty_page_url.spec() + "';", |
| 238 | EXECUTE_SCRIPT_NO_USER_GESTURE)); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 239 | 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 Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 248 | ASSERT_TRUE(ExecJs(main_contents, |
| 249 | "location = '" + target_text_url.spec() + "';", |
| 250 | EXECUTE_SCRIPT_NO_USER_GESTURE)); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 251 | 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 Maher | 5b9af51f | 2022-11-21 15:32:47 | [diff] [blame] | 257 | base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 258 | FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| 259 | run_loop.Run(); |
| 260 | EXPECT_DID_SCROLL(false); |
| 261 | } |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 262 | } |
| 263 | |
| 264 | IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, |
Dominique Fauteux-Chapleau | 3e8d345 | 2021-07-14 17:20:02 | [diff] [blame] | 265 | DisabledOnScriptHistoryNavigation) { |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 266 | 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-Chapleau | 3e8d345 | 2021-07-14 17:20:02 | [diff] [blame] | 274 | // 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 Amni | 30af706 | 2022-01-19 23:46:36 | [diff] [blame] | 278 | main_contents, BackForwardCacheImpl::TEST_REQUIRES_NO_CACHING); |
Dominique Fauteux-Chapleau | 3e8d345 | 2021-07-14 17:20:02 | [diff] [blame] | 279 | |
Rakina Zata Amni | d3af5db9 | 2020-08-06 06:51:39 | [diff] [blame] | 280 | { |
| 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 Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 286 | |
Rakina Zata Amni | d3af5db9 | 2020-08-06 06:51:39 | [diff] [blame] | 287 | // Scroll the page back to top so scroll restoration does not scroll the |
| 288 | // target back into view. |
Avi Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 289 | EXPECT_TRUE(ExecJs(main_contents, "window.scrollTo(0, 0)")); |
Rakina Zata Amni | d3af5db9 | 2020-08-06 06:51:39 | [diff] [blame] | 290 | frame_observer.WaitForScrollOffsetAtTop(true); |
| 291 | } |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 292 | |
| 293 | EXPECT_TRUE(NavigateToURL(shell(), url)); |
| 294 | |
| 295 | TestNavigationObserver observer(main_contents); |
Avi Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 296 | EXPECT_TRUE( |
| 297 | ExecJs(main_contents, "history.back()", EXECUTE_SCRIPT_NO_USER_GESTURE)); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 298 | 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 Maher | 5b9af51f | 2022-11-21 15:32:47 | [diff] [blame] | 305 | base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 306 | FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| 307 | run_loop.Run(); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 308 | |
| 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 Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 314 | EXPECT_DID_SCROLL(false); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 315 | } |
| 316 | |
Rune Lillesveen | 09a17df4 | 2023-08-07 14:19:09 | [diff] [blame] | 317 | // 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 Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 324 | // Ensure a same-document navigation from browser UI scrolls to the text |
| 325 | // fragment. |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 326 | IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, |
Rune Lillesveen | 09a17df4 | 2023-08-07 14:19:09 | [diff] [blame] | 327 | MAYBE_SameDocumentBrowserNavigation) { |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 328 | 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 Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 342 | EXPECT_TRUE(ExecJs(main_contents, "window.scrollTo(0, 0)")); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 343 | frame_observer.WaitForScrollOffsetAtTop(true); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 344 | RunUntilInputProcessed(GetWidgetHost()); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 345 | 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 Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 355 | EXPECT_DID_SCROLL(true); |
| 356 | } |
| 357 | |
Rune Lillesveen | 09a17df4 | 2023-08-07 14:19:09 | [diff] [blame] | 358 | // 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 |
| 366 | IN_PROC_BROWSER_TEST_F( |
| 367 | TextFragmentAnchorBrowserTest, |
| 368 | MAYBE_SameDocumentBrowserNavigationOnScriptNavigatedDocument) { |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 369 | ASSERT_TRUE(embedded_test_server()->Start()); |
| 370 | WebContents* main_contents = shell()->web_contents(); |
Dominique Fauteux-Chapleau | 3e8d345 | 2021-07-14 17:20:02 | [diff] [blame] | 371 | // 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 Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 376 | |
| 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 Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 392 | EXPECT_TRUE(ExecJs(main_contents, "location = '" + target_url.spec() + "';", |
| 393 | EXECUTE_SCRIPT_NO_USER_GESTURE)); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 394 | observer.Wait(); |
Rakina Zata Amni | 364eb5d | 2023-03-22 13:19:00 | [diff] [blame] | 395 | |
| 396 | RenderFrameSubmissionObserver frame_observer(main_contents); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 397 | 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 Amni | 364eb5d | 2023-03-22 13:19:00 | [diff] [blame] | 406 | RenderFrameSubmissionObserver frame_observer(main_contents); |
Avi Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 407 | EXPECT_TRUE(ExecJs(main_contents, "window.scrollTo(0, 0)")); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 408 | 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 Amni | 364eb5d | 2023-03-22 13:19:00 | [diff] [blame] | 421 | RenderFrameSubmissionObserver frame_observer(main_contents); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 422 | 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". |
| 433 | IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, |
| 434 | HistoryDoesntGenerateToken) { |
| 435 | ASSERT_TRUE(embedded_test_server()->Start()); |
| 436 | WebContents* main_contents = shell()->web_contents(); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 437 | GURL url(embedded_test_server()->GetURL( |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 438 | "a.com", "/scrollable_page_with_content.html#:~:text=text")); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 439 | |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 440 | { |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 441 | // 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 Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 446 | EXPECT_TRUE(NavigateToURL(shell(), url)); |
| 447 | WaitForPageLoad(main_contents); |
| 448 | frame_observer.WaitForScrollOffsetAtTop(false); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 449 | |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 450 | // 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 Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 455 | frame_observer.WaitForScrollOffsetAtTop(true); |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 456 | EXPECT_TRUE(ExecJs(main_contents, "did_scroll = false;", |
Avi Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 457 | EXECUTE_SCRIPT_NO_USER_GESTURE)); |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 458 | |
| 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 Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 481 | ASSERT_EQ(EvalJs(main_contents, "window.scrollY;", |
| 482 | EXECUTE_SCRIPT_NO_USER_GESTURE) |
| 483 | .ExtractInt(), |
| 484 | 0); |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 485 | ASSERT_DID_SCROLL(false); |
| 486 | } |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 487 | } |
| 488 | |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 489 | // Now try to navigate to a new page with a text-fragment. This should be |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 490 | // blocked because the token was consumed in the initial load at the top and |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 491 | // a new one must not have been generated by the same document navigations |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 492 | // above. |
| 493 | { |
| 494 | GURL new_url(embedded_test_server()->GetURL( |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 495 | "b.com", "/scrollable_page_with_content.html#:~:text=Some")); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 496 | TestNavigationObserver observer(main_contents); |
Avi Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 497 | EXPECT_TRUE(ExecJs(main_contents, "location = '" + new_url.spec() + "';", |
| 498 | EXECUTE_SCRIPT_NO_USER_GESTURE)); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 499 | observer.Wait(); |
| 500 | EXPECT_EQ(new_url, main_contents->GetLastCommittedURL()); |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 501 | base::RunLoop run_loop; |
Sean Maher | 5b9af51f | 2022-11-21 15:32:47 | [diff] [blame] | 502 | base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 503 | FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| 504 | run_loop.Run(); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 505 | EXPECT_DID_SCROLL(false); |
| 506 | } |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 507 | } |
| 508 | |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 509 | // Ensure same-document navigation to a text-fragment works when initiated from |
| 510 | // the document itself. |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 511 | IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 512 | SameDocumentScriptNavigation) { |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 513 | 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 Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 523 | // User gesture not required since the script is running in the same origin |
| 524 | // as the page. |
Avi Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 525 | EXPECT_TRUE(ExecJs(main_contents, |
| 526 | "location = '" + target_text_url.spec() + "';", |
| 527 | EXECUTE_SCRIPT_NO_USER_GESTURE)); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 528 | observer.Wait(); |
| 529 | EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL()); |
| 530 | |
| 531 | WaitForPageLoad(main_contents); |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 532 | RenderFrameSubmissionObserver frame_observer(main_contents); |
| 533 | frame_observer.WaitForScrollOffsetAtTop( |
| 534 | /*expected_scroll_offset_at_top=*/false); |
| 535 | EXPECT_DID_SCROLL(true); |
| 536 | } |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 537 | |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 538 | // Ensure same-document navigation to a text-fragment works when initiated from |
| 539 | // the same origin. |
| 540 | IN_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 Caballero | 15caeeb | 2021-10-27 09:57:55 | [diff] [blame] | 554 | FrameTreeNode* root = main_contents->GetPrimaryFrameTree().root(); |
David Bokan | a71830a | 2021-04-23 18:22:13 | [diff] [blame] | 555 | |
| 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 Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 590 | } |
| 591 | |
David Bokan | 1501b6f | 2021-04-21 00:28:24 | [diff] [blame] | 592 | // Ensure same-document navigation to a text-fragment is blocked when initiated |
| 593 | // from a different origin. |
| 594 | IN_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 Caballero | 15caeeb | 2021-10-27 09:57:55 | [diff] [blame] | 608 | FrameTreeNode* root = main_contents->GetPrimaryFrameTree().root(); |
David Bokan | 1501b6f | 2021-04-21 00:28:24 | [diff] [blame] | 609 | |
| 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 Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 644 | // Test that when ForceLoadAtTop document policy is explicitly turned off, |
| 645 | // scrolling to a text fragment is allowed. |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 646 | IN_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 Bokan | 84c96536 | 2020-08-14 17:25:32 | [diff] [blame] | 668 | "Document-Policy: force-load-at-top=?0\r\n" |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 669 | "\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 Daly | 83bc3cd | 2023-01-18 00:22:54 | [diff] [blame] | 681 | ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 682 | |
| 683 | WaitForPageLoad(main_contents); |
| 684 | frame_observer.WaitForScrollOffsetAtTop( |
| 685 | /*expected_scroll_offset_at_top=*/false); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 686 | EXPECT_DID_SCROLL(true); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 687 | } |
| 688 | |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 689 | // Test that the ForceLoadAtTop document policy disables scrolling to a text |
| 690 | // fragment. |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 691 | IN_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 Daly | 83bc3cd | 2023-01-18 00:22:54 | [diff] [blame] | 726 | ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 727 | |
| 728 | WaitForPageLoad(main_contents); |
| 729 | // Wait a short amount of time to ensure the page does not scroll. |
| 730 | base::RunLoop run_loop; |
Sean Maher | 5b9af51f | 2022-11-21 15:32:47 | [diff] [blame] | 731 | base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 732 | FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| 733 | run_loop.Run(); |
David Bokan | 71e8151 | 2020-08-18 17:19:46 | [diff] [blame] | 734 | EXPECT_DID_SCROLL(false); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 735 | } |
| 736 | |
David Bokan | b2e47a1 | 2022-05-19 22:14:38 | [diff] [blame] | 737 | // Test that Tab key press puts focus from the start of the text directive that |
| 738 | // was scrolled into view. |
David Bokan | 2b84970 | 2021-01-14 00:22:28 | [diff] [blame] | 739 | IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, TabFocus) { |
| 740 | ASSERT_TRUE(embedded_test_server()->Start()); |
David Bokan | b2e47a1 | 2022-05-19 22:14:38 | [diff] [blame] | 741 | GURL url( |
| 742 | embedded_test_server()->GetURL("/scrollable_page_with_anchor.html#:~:" |
| 743 | "text=nonexistent&text=text&text=more")); |
David Bokan | 2b84970 | 2021-01-14 00:22:28 | [diff] [blame] | 744 | 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 Blundell | ecd384f | 2022-05-11 08:58:30 | [diff] [blame] | 751 | DOMMessageQueue msg_queue(main_contents); |
David Bokan | 2b84970 | 2021-01-14 00:22:28 | [diff] [blame] | 752 | 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 Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 761 | class ForceLoadAtTopBrowserTest : public TextFragmentAnchorBrowserTest { |
| 762 | protected: |
David Bokan | 2b84970 | 2021-01-14 00:22:28 | [diff] [blame] | 763 | // 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 Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 775 | ASSERT_TRUE(embedded_test_server()->Start()); |
David Bokan | 2b84970 | 2021-01-14 00:22:28 | [diff] [blame] | 776 | 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 Daly | 83bc3cd | 2023-01-18 00:22:54 | [diff] [blame] | 822 | ASSERT_TRUE(navigation_manager.WaitForNavigationFinished()); |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 823 | |
| 824 | WaitForPageLoad(shell()->web_contents()); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 825 | } |
| 826 | }; |
| 827 | |
| 828 | // Test that scroll restoration is disabled with ForceLoadAtTop |
| 829 | IN_PROC_BROWSER_TEST_F(ForceLoadAtTopBrowserTest, ScrollRestorationDisabled) { |
David Bokan | 2b84970 | 2021-01-14 00:22:28 | [diff] [blame] | 830 | ASSERT_NO_FATAL_FAILURE(LoadScrollablePageWithContent("/index.html")); |
| 831 | |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 832 | WebContents* main_contents = shell()->web_contents(); |
Rakina Zata Amni | 348fe2b | 2021-07-09 05:28:52 | [diff] [blame] | 833 | // 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 Amni | 30af706 | 2022-01-19 23:46:36 | [diff] [blame] | 837 | BackForwardCache::TEST_REQUIRES_NO_CACHING); |
Rakina Zata Amni | 348fe2b | 2021-07-09 05:28:52 | [diff] [blame] | 838 | |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 839 | // Scroll down the page a bit |
Avi Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 840 | EXPECT_TRUE(ExecJs(main_contents, "window.scrollTo(0, 1000)")); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 841 | |
| 842 | // Navigate away |
Avi Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 843 | EXPECT_TRUE(ExecJs(main_contents, "window.location = 'about:blank'")); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 844 | EXPECT_TRUE(WaitForLoadStop(main_contents)); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 845 | |
| 846 | // Navigate back |
Avi Drissman | c91bd8e | 2021-04-19 23:58:44 | [diff] [blame] | 847 | EXPECT_TRUE(ExecJs(main_contents, "history.back()")); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 848 | EXPECT_TRUE(WaitForLoadStop(main_contents)); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 849 | |
| 850 | // Wait a short amount of time to ensure the page does not scroll. |
| 851 | base::RunLoop run_loop; |
Sean Maher | 5b9af51f | 2022-11-21 15:32:47 | [diff] [blame] | 852 | base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 853 | FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| 854 | run_loop.Run(); |
Kevin McNee | 777eef4 | 2020-10-20 18:53:49 | [diff] [blame] | 855 | RunUntilInputProcessed(GetWidgetHost()); |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 856 | EXPECT_EQ(EvalJs(main_contents, "window.scrollY;").ExtractInt(), 0); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 857 | } |
| 858 | |
| 859 | // Test that element fragment anchor scrolling is disabled with ForceLoadAtTop |
| 860 | IN_PROC_BROWSER_TEST_F(ForceLoadAtTopBrowserTest, FragmentAnchorDisabled) { |
David Bokan | 2b84970 | 2021-01-14 00:22:28 | [diff] [blame] | 861 | ASSERT_NO_FATAL_FAILURE(LoadScrollablePageWithContent("/index.html#text")); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 862 | WebContents* main_contents = shell()->web_contents(); |
| 863 | |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 864 | // Wait a short amount of time to ensure the page does not scroll. |
| 865 | base::RunLoop run_loop; |
Sean Maher | 5b9af51f | 2022-11-21 15:32:47 | [diff] [blame] | 866 | base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 867 | FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| 868 | run_loop.Run(); |
Kevin McNee | 777eef4 | 2020-10-20 18:53:49 | [diff] [blame] | 869 | RunUntilInputProcessed(GetWidgetHost()); |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 870 | EXPECT_DID_SCROLL(false); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 871 | } |
| 872 | |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 873 | // Ensure the ForceLoadAtTop policy doesn't prevent same-document fragment |
| 874 | // navigations. |
| 875 | IN_PROC_BROWSER_TEST_F(ForceLoadAtTopBrowserTest, SameDocumentNavigation) { |
David Bokan | 2b84970 | 2021-01-14 00:22:28 | [diff] [blame] | 876 | ASSERT_NO_FATAL_FAILURE(LoadScrollablePageWithContent("/index.html")); |
David Bokan | dd1b8d0 | 2020-06-25 21:17:58 | [diff] [blame] | 877 | WebContents* main_contents = shell()->web_contents(); |
| 878 | |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 879 | ASSERT_DID_SCROLL(false); |
David Bokan | dd1b8d0 | 2020-06-25 21:17:58 | [diff] [blame] | 880 | |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 881 | // Click on a link with a fragment id. Ensure we scroll to the targeted |
| 882 | // element. |
David Bokan | dd1b8d0 | 2020-06-25 21:17:58 | [diff] [blame] | 883 | ClickElementWithId(main_contents, "link"); |
| 884 | |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 885 | EXPECT_DID_SCROLL(true); |
David Bokan | dd1b8d0 | 2020-06-25 21:17:58 | [diff] [blame] | 886 | } |
| 887 | |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 888 | // Ensure the ForceLoadAtTop policy prevents scrolling to a navigated text |
| 889 | // directive. |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 890 | IN_PROC_BROWSER_TEST_F(ForceLoadAtTopBrowserTest, TextFragmentAnchorDisabled) { |
David Bokan | 2b84970 | 2021-01-14 00:22:28 | [diff] [blame] | 891 | ASSERT_NO_FATAL_FAILURE( |
| 892 | LoadScrollablePageWithContent("/index.html#:~:text=text")); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 893 | WebContents* main_contents = shell()->web_contents(); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 894 | |
| 895 | // Wait a short amount of time to ensure the page does not scroll. |
| 896 | base::RunLoop run_loop; |
Sean Maher | 5b9af51f | 2022-11-21 15:32:47 | [diff] [blame] | 897 | base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 898 | FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout()); |
| 899 | run_loop.Run(); |
Kevin McNee | 777eef4 | 2020-10-20 18:53:49 | [diff] [blame] | 900 | RunUntilInputProcessed(GetWidgetHost()); |
David Bokan | 289abe0f | 2021-10-04 16:51:07 | [diff] [blame] | 901 | EXPECT_DID_SCROLL(false); |
David Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 902 | } |
| 903 | |
sebsg | 901c271 | 2021-10-18 21:12:52 | [diff] [blame] | 904 | // 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. |
| 907 | IN_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. |
| 948 | IN_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 Bokan | 3f72f10 | 2020-04-23 22:18:56 | [diff] [blame] | 992 | } // namespace content |