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.cc285
1 files changed, 251 insertions, 34 deletions
diff --git a/chromium/content/browser/text_fragment_browsertest.cc b/chromium/content/browser/text_fragment_browsertest.cc
index 86c392b8733..d69d5fc3f8a 100644
--- a/chromium/content/browser/text_fragment_browsertest.cc
+++ b/chromium/content/browser/text_fragment_browsertest.cc
@@ -21,6 +21,19 @@
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "url/gurl.h"
+// RunUntilInputProcessed will force a Blink lifecycle which is needed
+// because did_scroll is set in an onscroll handler which may be delayed from
+// the scroll by a frame.
+#define EXPECT_DID_SCROLL(scrolled) \
+ RunUntilInputProcessed(GetWidgetHost()); \
+ EXPECT_EQ(scrolled, EvalJs(main_contents, "did_scroll;", \
+ EXECUTE_SCRIPT_NO_USER_GESTURE));
+
+#define ASSERT_DID_SCROLL(scrolled) \
+ RunUntilInputProcessed(GetWidgetHost()); \
+ ASSERT_EQ(scrolled, EvalJs(main_contents, "did_scroll;", \
+ EXECUTE_SCRIPT_NO_USER_GESTURE));
+
namespace content {
class TextFragmentAnchorBrowserTest : public ContentBrowserTest {
@@ -85,7 +98,6 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledOnUserNavigation) {
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());
@@ -95,11 +107,15 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledOnUserNavigation) {
observer.Wait();
EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
+ // Observe the frame after page is loaded. Note that we need to initialize
+ // this after navigation because the main RenderFrameHost might have changed
+ // from before the navigation started.
+ RenderFrameSubmissionObserver frame_observer(main_contents);
WaitForPageLoad(main_contents);
frame_observer.WaitForScrollOffsetAtTop(
/*expected_scroll_offset_at_top=*/false);
- RunUntilInputProcessed(GetWidgetHost());
- EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
+
+ EXPECT_DID_SCROLL(true);
}
IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
@@ -115,8 +131,7 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
WaitForPageLoad(main_contents);
frame_observer.WaitForScrollOffsetAtTop(
/*expected_scroll_offset_at_top=*/false);
- RunUntilInputProcessed(GetWidgetHost());
- EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
+ EXPECT_DID_SCROLL(true);
}
IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
@@ -130,46 +145,112 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
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());
+ // Observe the frame after page is loaded. Note that we need to initialize
+ // this after navigation because the main RenderFrameHost might have changed
+ // from before the navigation started.
+ RenderFrameSubmissionObserver frame_observer(main_contents);
WaitForPageLoad(main_contents);
frame_observer.WaitForScrollOffsetAtTop(
/*expected_scroll_offset_at_top=*/false);
- RunUntilInputProcessed(GetWidgetHost());
- EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
+ EXPECT_DID_SCROLL(true);
}
+// Ensures that a simulated redirect service works correctly. That is, only the
+// initial NavigateToURL has a user gesture but this should be propagated
+// through the window.location navigation which doesn't have a user gesture.
IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
- DisabledOnScriptNavigation) {
+ UserGesturePassedThroughRedirect) {
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"));
+ // This navigtion is simulated as if it came from the omnibox, hence it is
+ // considered to be user initiated.
EXPECT_TRUE(NavigateToURL(shell(), url));
WebContents* main_contents = shell()->web_contents();
TestNavigationObserver observer(main_contents);
+
+ // This navigation occurs without a user gesture, simulating a client
+ // redirect. However, because the above navigation didn't activate a text
+ // fragment, permission should be propagated to this navigation.
EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
main_contents, "location = '" + target_text_url.spec() + "';"));
observer.Wait();
EXPECT_EQ(target_text_url, main_contents->GetLastCommittedURL());
WaitForPageLoad(main_contents);
+ RenderFrameSubmissionObserver frame_observer(main_contents);
+ frame_observer.WaitForScrollOffsetAtTop(
+ /*expected_scroll_offset_at_top=*/false);
+ EXPECT_DID_SCROLL(true);
+}
- // 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;"));
+// Ensures that a text fragment activation consumes a user gesture so that
+// future navigations cannot activate a text fragment without a new user
+// gesture.
+IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, UserGestureConsumed) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ GURL empty_page_url(embedded_test_server()->GetURL("/empty.html"));
+ GURL target_text_url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=text"));
+
+ WebContents* main_contents = shell()->web_contents();
+
+ // This navigtion is simulated as if it came from the omnibox, hence it is
+ // considered to be user initiated.
+ {
+ TestNavigationObserver observer(main_contents);
+ ASSERT_TRUE(NavigateToURL(shell(), target_text_url));
+ observer.Wait();
+ ASSERT_EQ(target_text_url, main_contents->GetLastCommittedURL());
+
+ // Ensure the page did scroll to the text fragment. Note, we can't use
+ // WaitForPageLoad since the WaitForRenderFrameReady executes javascript
+ // with a user gesture.
+ WaitForLoadStop(main_contents);
+ RenderFrameSubmissionObserver frame_observer(main_contents);
+ frame_observer.WaitForScrollOffsetAtTop(
+ /*expected_scroll_offset_at_top=*/false);
+ ASSERT_DID_SCROLL(true);
+ }
+
+ // We now want to try a second text fragment navigation. Same document
+ // navigations are blocked so we'll navigate away first.
+ {
+ TestNavigationObserver observer(main_contents);
+ ASSERT_TRUE(ExecuteScriptWithoutUserGesture(
+ main_contents, "location = '" + empty_page_url.spec() + "';"));
+ observer.Wait();
+ ASSERT_EQ(empty_page_url, main_contents->GetLastCommittedURL());
+ WaitForLoadStop(main_contents);
+ }
+
+ // Now try another text fragment navigation. Since we haven't had a user
+ // gesture since the last one, it should be blocked.
+ {
+ TestNavigationObserver observer(main_contents);
+ ASSERT_TRUE(ExecuteScriptWithoutUserGesture(
+ main_contents, "location = '" + target_text_url.spec() + "';"));
+ observer.Wait();
+ ASSERT_EQ(target_text_url, main_contents->GetLastCommittedURL());
+ WaitForLoadStop(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();
+ EXPECT_DID_SCROLL(false);
+ }
}
IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
@@ -182,13 +263,18 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
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);
+ {
+ // The RenderFrameSubmissionObserver destructor expects the RenderFrameHost
+ // stays the same until it gets destructed, so we need to scope this to make
+ // sure it gets destructed before the next navigation.
+ 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));
@@ -204,16 +290,18 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
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;"));
+ EXPECT_DID_SCROLL(false);
}
+// Normally, same document navigations don't allow invoking the text fragment.
+// We make an exception for browser-initiated (e.g. typing a URL into the
+// omnibox) navigations. This test ensures we allow the latter.
IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
EnabledOnSameDocumentBrowserNavigation) {
ASSERT_TRUE(embedded_test_server()->Start());
@@ -232,6 +320,7 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
// fragment.
EXPECT_TRUE(ExecuteScript(main_contents, "window.scrollTo(0, 0)"));
frame_observer.WaitForScrollOffsetAtTop(true);
+ RunUntilInputProcessed(GetWidgetHost());
EXPECT_TRUE(ExecJs(main_contents, "did_scroll = false;"));
// Perform a same-document browser initiated navigation
@@ -242,8 +331,139 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
WaitForPageLoad(main_contents);
frame_observer.WaitForScrollOffsetAtTop(
/*expected_scroll_offset_at_top=*/false);
- RunUntilInputProcessed(GetWidgetHost());
- EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
+ EXPECT_DID_SCROLL(true);
+}
+
+// Similar to the above test, we're checking that browser-initiated
+// same-document navigations invoke the text fragment. However, this time, the
+// initial landing on the page is via a non-user-activated script navigation.
+// This ensure we're not inappropriately blocking a text-fragment based on the
+// state of the initial document load.
+IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
+ SameDocumentBrowserNavigationOnScriptNavigatedDocument) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ WebContents* main_contents = shell()->web_contents();
+ RenderFrameSubmissionObserver frame_observer(main_contents);
+
+ // Load an initial page
+ {
+ GURL initial_url(embedded_test_server()->GetURL("/empty.html"));
+ EXPECT_TRUE(NavigateToURL(shell(), initial_url));
+ WaitForPageLoad(main_contents);
+ }
+
+ // Now navigate to the target document without a user gesture. We provide a
+ // text-fragment here and expect it to be invoked because the initial load
+ // was browser-initiated so its transferred to this load via the text fragment
+ // token. This navigation ensures the token is consumed.
+ {
+ GURL target_url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=text"));
+ TestNavigationObserver observer(main_contents);
+ EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
+ main_contents, "location = '" + target_url.spec() + "';"));
+ observer.Wait();
+ EXPECT_EQ(target_url, main_contents->GetLastCommittedURL());
+ frame_observer.WaitForScrollOffsetAtTop(false);
+ EXPECT_DID_SCROLL(true);
+ }
+
+ // 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);
+ RunUntilInputProcessed(GetWidgetHost());
+ EXPECT_TRUE(ExecJs(main_contents, "did_scroll = false;"));
+ }
+
+ // Perform a same-document browser initiated navigation. This should cause a
+ // scroll because the navigation is browser-initiated, despite the fact that
+ // the document was loaded without a user gesture.
+ {
+ 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);
+ EXPECT_DID_SCROLL(true);
+ }
+}
+
+// Ensure a text fragment token isn't generated via history.back() navigation.
+// This is a tricky case because all history navigations (including script
+// initiated) appear to the renderer as "browser-initiated".
+IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
+ HistoryDoesntGenerateToken) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ WebContents* main_contents = shell()->web_contents();
+ RenderFrameSubmissionObserver frame_observer(main_contents);
+ GURL url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=text"));
+
+ // Load a page with a text-fragment
+ {
+ 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);
+ RunUntilInputProcessed(GetWidgetHost());
+ EXPECT_TRUE(ExecJs(main_contents, "did_scroll = false;"));
+ }
+
+ // Perform a scripted same-document navigation to a non-existent fragment to
+ // generate a history entry.
+ {
+ GURL temp_url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#doesntexist"));
+ TestNavigationObserver observer(main_contents);
+ EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
+ main_contents, "location = '" + temp_url.spec() + "';"));
+ observer.Wait();
+ EXPECT_EQ(temp_url, main_contents->GetLastCommittedURL());
+ }
+
+ // Navigate back using history.back().
+ {
+ TestNavigationObserver observer(main_contents);
+ EXPECT_TRUE(
+ ExecuteScriptWithoutUserGesture(main_contents, "history.back();"));
+ observer.Wait();
+ EXPECT_EQ(url, main_contents->GetLastCommittedURL());
+
+ // The page should be restored to where we left off at the top.
+ RunUntilInputProcessed(GetWidgetHost());
+ ASSERT_TRUE(
+ frame_observer.LastRenderFrameMetadata().is_scroll_offset_at_top);
+ ASSERT_DID_SCROLL(false);
+ }
+
+ // Now try to navigate to a same-document text-fragment. This should be
+ // blocked because the token was consumed in the initial load at the top and
+ // a new one should not have been generated by the same document navigations
+ // above.
+ {
+ GURL new_url(embedded_test_server()->GetURL(
+ "/scrollable_page_with_content.html#:~:text=Some"));
+ TestNavigationObserver observer(main_contents);
+ EXPECT_TRUE(ExecuteScriptWithoutUserGesture(
+ main_contents, "location = '" + new_url.spec() + "';"));
+ observer.Wait();
+ EXPECT_EQ(new_url, main_contents->GetLastCommittedURL());
+ frame_observer.WaitForScrollOffsetAtTop(true);
+ EXPECT_DID_SCROLL(false);
+ }
}
IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
@@ -270,8 +490,7 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
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;"));
+ EXPECT_DID_SCROLL(false);
}
IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledByDocumentPolicy) {
@@ -296,7 +515,7 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledByDocumentPolicy) {
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"
+ "Document-Policy: force-load-at-top=?0\r\n"
"\r\n"
"<script>"
" let did_scroll = false;"
@@ -314,8 +533,7 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest, EnabledByDocumentPolicy) {
WaitForPageLoad(main_contents);
frame_observer.WaitForScrollOffsetAtTop(
/*expected_scroll_offset_at_top=*/false);
- RunUntilInputProcessed(GetWidgetHost());
- EXPECT_EQ(true, EvalJs(main_contents, "did_scroll;"));
+ EXPECT_DID_SCROLL(true);
}
IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
@@ -361,8 +579,7 @@ IN_PROC_BROWSER_TEST_F(TextFragmentAnchorBrowserTest,
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;"));
+ EXPECT_DID_SCROLL(false);
}
class ForceLoadAtTopBrowserTest : public TextFragmentAnchorBrowserTest {