summaryrefslogtreecommitdiff
path: root/chromium/content/browser/text_fragment_browsertest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/content/browser/text_fragment_browsertest.cc')
-rw-r--r--chromium/content/browser/text_fragment_browsertest.cc453
1 files changed, 453 insertions, 0 deletions
diff --git a/chromium/content/browser/text_fragment_browsertest.cc b/chromium/content/browser/text_fragment_browsertest.cc
new file mode 100644
index 00000000000..2c6e0d5fdca
--- /dev/null
+++ b/chromium/content/browser/text_fragment_browsertest.cc
@@ -0,0 +1,453 @@
+// Copyright 2020 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/test/scoped_feature_list.h"
+#include "base/test/test_timeouts.h"
+#include "content/browser/renderer_host/render_widget_host_impl.h"
+#include "content/public/browser/render_view_host.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/content_switches.h"
+#include "content/public/test/browser_test.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/content_browser_test.h"
+#include "content/public/test/content_browser_test_utils.h"
+#include "content/public/test/hit_test_region_observer.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/shell/browser/shell.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/test/embedded_test_server/controllable_http_response.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "url/gurl.h"
+
+namespace content {
+
+class TextFragmentAnchorBrowserTest : public ContentBrowserTest {
+ public:
+ TextFragmentAnchorBrowserTest() {
+ feature_list_.InitAndEnableFeature(features::kDocumentPolicy);
+ }
+
+ protected:
+ void SetUpOnMainThread() override {
+ host_resolver()->AddRule("*", "127.0.0.1");
+ }
+
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ ContentBrowserTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
+ "TextFragmentIdentifiers");
+ }
+
+ // Simulates a click on the middle of the DOM element with the given |id|.
+ void ClickElementWithId(WebContents* web_contents, const std::string& id) {
+ // Get the center coordinates of the DOM element.
+ const int x = EvalJs(web_contents,
+ JsReplace("const bounds = "
+ "document.getElementById($1)."
+ "getBoundingClientRect();"
+ "Math.floor(bounds.left + bounds.width / 2)",
+ id))
+ .ExtractInt();
+ const int y = EvalJs(web_contents,
+ JsReplace("const bounds = "
+ "document.getElementById($1)."
+ "getBoundingClientRect();"
+ "Math.floor(bounds.top + bounds.height / 2)",
+ id))
+ .ExtractInt();
+
+ SimulateMouseClickAt(web_contents, 0, blink::WebMouseEvent::Button::kLeft,
+ gfx::Point(x, y));
+ }
+
+ void WaitForPageLoad(WebContents* contents) {
+ EXPECT_TRUE(WaitForLoadStop(contents));
+ EXPECT_TRUE(WaitForRenderFrameReady(contents->GetMainFrame()));
+ }
+
+ RenderWidgetHostImpl* GetWidgetHost() {
+ return RenderWidgetHostImpl::From(
+ shell()->web_contents()->GetRenderViewHost()->GetWidget());
+ }
+
+ base::test::ScopedFeatureList feature_list_;
+};
+
+IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledOnUserNavigation) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL("/target_text_link.html"));
+ GURL target_text_url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=text"));
+
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+
+ WebContents* main_contents = shell()->web_contents();
+ TestNavigationObserver observer(main_contents);
+ RenderFrameSubmissionObserver frame_observer(main_contents);
+
+ // We need to wait until hit test data is available.
+ HitTestRegionObserver hittest_observer(GetWidgetHost()->GetFrameSinkId());
+ hittest_observer.WaitForHitTestData();
+
+ ClickElementWithId(main_contents, "link");
+ observer.Wait();
+ EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
+
+ WaitForPageLoad(main_contents);
+ frame_observer.WaitForScrollOffsetAtTop(
+ /*expected_scroll_offset_at_top=*/false);
+ RunUntilInputProcessed(GetWidgetHost());
+ EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
+}
+
+IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
+ EnabledOnBrowserNavigation) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=text"));
+ WebContents* main_contents = shell()->web_contents();
+ RenderFrameSubmissionObserver frame_observer(main_contents);
+
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+
+ WaitForPageLoad(main_contents);
+ frame_observer.WaitForScrollOffsetAtTop(
+ /*expected_scroll_offset_at_top=*/false);
+ RunUntilInputProcessed(GetWidgetHost());
+ EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
+}
+
+IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
+ EnabledOnUserGestureScriptNavigation) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL("/empty.html"));
+ GURL target_text_url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=text"));
+
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+
+ WebContents* main_contents = shell()->web_contents();
+ TestNavigationObserver observer(main_contents);
+ RenderFrameSubmissionObserver frame_observer(main_contents);
+
+ // ExecuteScript executes with a user gesture
+ EXPECT_TRUE(ExecuteScript(main_contents,
+ "location = '" + target_text_url.spec() + "';"));
+ observer.Wait();
+ EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
+
+ WaitForPageLoad(main_contents);
+ frame_observer.WaitForScrollOffsetAtTop(
+ /*expected_scroll_offset_at_top=*/false);
+ RunUntilInputProcessed(GetWidgetHost());
+ EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
+}
+
+IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
+ DisabledOnScriptNavigation) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL("/empty.html"));
+ GURL target_text_url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=text"));
+
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+
+ WebContents* main_contents = shell()->web_contents();
+ TestNavigationObserver observer(main_contents);
+ EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
+ main_contents, "location = '" + target_text_url.spec() + "';"));
+ observer.Wait();
+ EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
+
+ WaitForPageLoad(main_contents);
+
+ // Wait a short amount of time to ensure the page does not scroll.
+ base::RunLoop run_loop;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+ run_loop.Run();
+ RunUntilInputProcessed(GetWidgetHost());
+ EXPECT_EQ(false, EvalJs(main_contents, "did_scroll;"));
+}
+
+IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
+ DisabledOnScriptHistoryNavigation) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL target_text_url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=text"));
+ GURL url(embedded_test_server()->GetURL("/empty.html"));
+
+ EXPECT_TRUE(NavigateToURL(shell(), target_text_url));
+
+ WebContents* main_contents = shell()->web_contents();
+ RenderFrameSubmissionObserver frame_observer(main_contents);
+ frame_observer.WaitForScrollOffsetAtTop(false);
+
+ // Scroll the page back to top so scroll restoration does not scroll the
+ // target back into view.
+ EXPECT_TRUE(ExecuteScript(main_contents, "window.scrollTo(0, 0)"));
+ frame_observer.WaitForScrollOffsetAtTop(true);
+
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+
+ TestNavigationObserver observer(main_contents);
+ EXPECT_TRUE(ExecuteScriptWithoutUserGesture(main_contents, "history.back()"));
+ observer.Wait();
+ EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
+
+ WaitForPageLoad(main_contents);
+
+ // Wait a short amount of time to ensure the page does not scroll.
+ base::RunLoop run_loop;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+ run_loop.Run();
+ RunUntilInputProcessed(GetWidgetHost());
+
+ // Note: we use a scroll handler in the page to check whether any scrolls
+ // happened at all, rather than checking the current scroll offset. This is
+ // to ensure that if the offset is reset back to the top for other reasons
+ // (e.g. history restoration) we still fail this test. See
+ // https://crbug.com/1042986 for why this matters.
+ EXPECT_EQ(false, EvalJs(main_contents, "did_scroll;"));
+}
+
+IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
+ EnabledOnSameDocumentBrowserNavigation) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=text"));
+ WebContents* main_contents = shell()->web_contents();
+ RenderFrameSubmissionObserver frame_observer(main_contents);
+
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+
+ WaitForPageLoad(main_contents);
+ frame_observer.WaitForScrollOffsetAtTop(false);
+
+ // Scroll the page back to top. Make sure we reset the |did_scroll| variable
+ // we'll use below to ensure the same-document navigation invokes the text
+ // fragment.
+ EXPECT_TRUE(ExecuteScript(main_contents, "window.scrollTo(0, 0)"));
+ frame_observer.WaitForScrollOffsetAtTop(true);
+ EXPECT_TRUE(ExecJs(main_contents, "did_scroll = false;"));
+
+ // Perform a same-document browser initiated navigation
+ GURL same_doc_url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=some"));
+ EXPECT_TRUE(NavigateToURL(shell(), same_doc_url));
+
+ WaitForPageLoad(main_contents);
+ frame_observer.WaitForScrollOffsetAtTop(
+ /*expected_scroll_offset_at_top=*/false);
+ RunUntilInputProcessed(GetWidgetHost());
+ EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
+}
+
+IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
+ DisabledOnSameDocumentScriptNavigation) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(
+ embedded_test_server()->GetURL("/scrollable_page_with_content.html"));
+ GURL target_text_url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=some"));
+
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+
+ WebContents* main_contents = shell()->web_contents();
+ TestNavigationObserver observer(main_contents);
+ EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
+ main_contents, "location = '" + target_text_url.spec() + "';"));
+ observer.Wait();
+ EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
+
+ WaitForPageLoad(main_contents);
+
+ // Wait a short amount of time to ensure the page does not scroll.
+ base::RunLoop run_loop;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+ run_loop.Run();
+ RunUntilInputProcessed(GetWidgetHost());
+ EXPECT_EQ(false, EvalJs(main_contents, "did_scroll;"));
+}
+
+IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledByDocumentPolicy) {
+ net::test_server::ControllableHttpResponse response(embedded_test_server(),
+ "/target.html");
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL("/target.html#:~:text=text"));
+ WebContents* main_contents = shell()->web_contents();
+ RenderFrameSubmissionObserver frame_observer(main_contents);
+
+ // Load the target document
+ TestNavigationManager navigation_manager(main_contents, url);
+ shell()->LoadURL(url);
+
+ // Start navigation
+ EXPECT_TRUE(navigation_manager.WaitForRequestStart());
+ navigation_manager.ResumeNavigation();
+
+ // Send Document-Policy header
+ response.WaitForRequest();
+ response.Send(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n"
+ "Document-Policy: no-force-load-at-top\r\n"
+ "\r\n"
+ "<script>"
+ " let did_scroll = false;"
+ " window.addEventListener('scroll', () => {"
+ " did_scroll = true;"
+ " });"
+ "</script>"
+ "<p style='position: absolute; top: 10000px;'>Some text</p>");
+ response.Done();
+
+ EXPECT_TRUE(navigation_manager.WaitForResponse());
+ navigation_manager.ResumeNavigation();
+ navigation_manager.WaitForNavigationFinished();
+
+ WaitForPageLoad(main_contents);
+ frame_observer.WaitForScrollOffsetAtTop(
+ /*expected_scroll_offset_at_top=*/false);
+ RunUntilInputProcessed(GetWidgetHost());
+ EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
+}
+
+IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
+ DisabledByDocumentPolicy) {
+ net::test_server::ControllableHttpResponse response(embedded_test_server(),
+ "/target.html");
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL url(embedded_test_server()->GetURL("/target.html#:~:text=text"));
+ WebContents* main_contents = shell()->web_contents();
+
+ // Load the target document
+ TestNavigationManager navigation_manager(main_contents, url);
+ shell()->LoadURL(url);
+
+ // Start navigation
+ EXPECT_TRUE(navigation_manager.WaitForRequestStart());
+ navigation_manager.ResumeNavigation();
+
+ // Send Document-Policy header
+ response.WaitForRequest();
+ response.Send(
+ "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/html; charset=utf-8\r\n"
+ "Document-Policy: force-load-at-top\r\n"
+ "\r\n"
+ "<script>"
+ " let did_scroll = false;"
+ " window.addEventListener('scroll', () => {"
+ " did_scroll = true;"
+ " });"
+ "</script>"
+ "<p style='position: absolute; top: 10000px;'>Some text</p>");
+ response.Done();
+
+ EXPECT_TRUE(navigation_manager.WaitForResponse());
+ navigation_manager.ResumeNavigation();
+ navigation_manager.WaitForNavigationFinished();
+
+ WaitForPageLoad(main_contents);
+ // Wait a short amount of time to ensure the page does not scroll.
+ base::RunLoop run_loop;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+ run_loop.Run();
+ RunUntilInputProcessed(GetWidgetHost());
+ EXPECT_EQ(false, EvalJs(main_contents, "did_scroll;"));
+}
+
+class ForceLoadAtTopBrowserTest : public TextFragmentAnchorBrowserTest {
+ protected:
+ void SetUpOnMainThread() override {
+ TextFragmentAnchorBrowserTest::SetUpOnMainThread();
+ ASSERT_TRUE(embedded_test_server()->Start());
+ }
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ TextFragmentAnchorBrowserTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitchASCII(switches::kEnableBlinkFeatures,
+ "ForceLoadAtTop");
+ }
+};
+
+// Test that scroll restoration is disabled with ForceLoadAtTop
+IN_PROC_BROWSER_TEST_F(ForceLoadAtTopBrowserTest, ScrollRestorationDisabled) {
+ GURL url(
+ embedded_test_server()->GetURL("/scrollable_page_with_content.html"));
+ WebContents* main_contents = shell()->web_contents();
+ RenderFrameSubmissionObserver frame_observer(main_contents);
+
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+ EXPECT_TRUE(WaitForRenderFrameReady(main_contents->GetMainFrame()));
+
+ // Scroll down the page a bit
+ EXPECT_TRUE(ExecuteScript(main_contents, "window.scrollTo(0, 1000)"));
+ frame_observer.WaitForScrollOffsetAtTop(false);
+
+ // Navigate away
+ EXPECT_TRUE(ExecuteScript(main_contents, "window.location = 'about:blank'"));
+ EXPECT_TRUE(WaitForLoadStop(main_contents));
+ EXPECT_TRUE(WaitForRenderFrameReady(main_contents->GetMainFrame()));
+
+ // Navigate back
+ EXPECT_TRUE(ExecuteScript(main_contents, "history.back()"));
+ EXPECT_TRUE(WaitForLoadStop(main_contents));
+ EXPECT_TRUE(WaitForRenderFrameReady(main_contents->GetMainFrame()));
+
+ // Wait a short amount of time to ensure the page does not scroll.
+ base::RunLoop run_loop;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+ run_loop.Run();
+ RunUntilInputProcessed(RenderWidgetHostImpl::From(
+ main_contents->GetRenderViewHost()->GetWidget()));
+ EXPECT_TRUE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
+}
+
+// Test that element fragment anchor scrolling is disabled with ForceLoadAtTop
+IN_PROC_BROWSER_TEST_F(ForceLoadAtTopBrowserTest, FragmentAnchorDisabled) {
+ GURL url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#text"));
+ WebContents* main_contents = shell()->web_contents();
+
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+ EXPECT_TRUE(WaitForRenderFrameReady(main_contents->GetMainFrame()));
+
+ // Wait a short amount of time to ensure the page does not scroll.
+ base::RunLoop run_loop;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+ run_loop.Run();
+ RunUntilInputProcessed(RenderWidgetHostImpl::From(
+ main_contents->GetRenderViewHost()->GetWidget()));
+ EXPECT_TRUE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
+}
+
+IN_PROC_BROWSER_TEST_F(ForceLoadAtTopBrowserTest, TextFragmentAnchorDisabled) {
+ GURL url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=text"));
+ WebContents* main_contents = shell()->web_contents();
+ RenderFrameSubmissionObserver frame_observer(main_contents);
+
+ EXPECT_TRUE(NavigateToURL(shell(), url));
+ EXPECT_TRUE(WaitForRenderFrameReady(main_contents->GetMainFrame()));
+
+ // Wait a short amount of time to ensure the page does not scroll.
+ base::RunLoop run_loop;
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), TestTimeouts::tiny_timeout());
+ run_loop.Run();
+ RunUntilInputProcessed(RenderWidgetHostImpl::From(
+ main_contents->GetRenderViewHost()->GetWidget()));
+ EXPECT_TRUE(main_contents->GetMainFrame()->GetView()->IsScrollOffsetAtTop());
+}
+
+} // namespace content