Commit 19487703 authored by Shivani Sharma's avatar Shivani Sharma Committed by Commit Bot

History intervention: Reset skippable bit if user gesture after pushState

If a navigation entry was marked as skippable because it added an entry
using pushState, then a user gesture on the page should reset the skippable
bit. If the pushState was done after the user gesture, then the entry would
not have been marked as skippable to begin with. This change will make the
behavior independent of the ordering between user gesture and pushState.

content_browsertests --gtest_filter=
NavigationControllerHistoryInterventionBrowserTest.
OnUserGestureResetSameDocumentEntriesSkipFlag
content_browsertests --gtest_filter=
NavigationControllerHistoryInterventionBrowserTest.
OnUserGestureDoNotResetDifferentDocumentEntrySkipFlag

Test: 
Bug: 937423
Change-Id: Ie1e55815163ca84950ed7e04fbfa55c06f86c2cf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1499834
Commit-Queue: Shivani Sharma <shivanisha@chromium.org>
Reviewed-by: default avatarCharlie Reis <creis@chromium.org>
Cr-Commit-Position: refs/heads/master@{#638641}
parent 8106c5b0
......@@ -18,6 +18,7 @@
#include "base/strings/string_util.h"
#include "content/browser/devtools/devtools_instrumentation.h"
#include "content/browser/frame_host/frame_tree.h"
#include "content/browser/frame_host/navigation_controller_impl.h"
#include "content/browser/frame_host/navigation_request.h"
#include "content/browser/frame_host/navigator.h"
#include "content/browser/frame_host/render_frame_host_impl.h"
......@@ -579,6 +580,9 @@ bool FrameTreeNode::NotifyUserActivation() {
}
}
static_cast<NavigationControllerImpl*>(navigator()->GetController())
->NotifyUserActivation();
return true;
}
......
......@@ -423,6 +423,23 @@ void ValidateRequestMatchesEntry(NavigationRequest* request,
}
#endif // DCHECK_IS_ON()
// Resets |should_skip_on_back_forward_ui| flag for |entry| if it has a frame
// entry for |root_frame| with the same document sequence number as
// |document_sequence_number|.
bool ResetSkippableForSameDocumentEntry(FrameTreeNode* root_frame,
int64_t& document_sequence_number,
NavigationEntryImpl* entry) {
if (entry && entry->should_skip_on_back_forward_ui()) {
auto* frame_entry = entry->GetFrameEntry(root_frame);
if (frame_entry &&
frame_entry->document_sequence_number() == document_sequence_number) {
entry->set_should_skip_on_back_forward_ui(false);
return true;
}
}
return false;
}
} // namespace
// NavigationControllerImpl ----------------------------------------------------
......@@ -2075,6 +2092,44 @@ bool NavigationControllerImpl::ValidateDataURLAsString(
}
#endif
void NavigationControllerImpl::NotifyUserActivation() {
// When a user activation occurs, ensure that all adjacent entries for the
// same document clear their skippable bit, so that the history manipulation
// intervention does not apply to them.
auto* last_committed_entry = GetLastCommittedEntry();
if (!last_committed_entry)
return;
int last_committed_entry_index = GetLastCommittedEntryIndex();
auto* root_frame = delegate_->GetFrameTree()->root();
auto* frame_entry = last_committed_entry->GetFrameEntry(root_frame);
if (!frame_entry)
return;
int64_t document_sequence_number = frame_entry->document_sequence_number();
// |last_committed_entry| should not be skippable because it is the current
// entry and in case the skippable bit was earlier set then on re-navigation
// it would have been reset.
DCHECK(!last_committed_entry->should_skip_on_back_forward_ui());
for (int index = last_committed_entry_index - 1; index >= 0; index--) {
auto* entry = GetEntryAtIndex(index);
if (!ResetSkippableForSameDocumentEntry(root_frame,
document_sequence_number, entry)) {
break;
}
}
for (int index = last_committed_entry_index + 1; index < GetEntryCount();
index++) {
auto* entry = GetEntryAtIndex(index);
if (!ResetSkippableForSameDocumentEntry(root_frame,
document_sequence_number, entry)) {
break;
}
}
}
bool NavigationControllerImpl::StartHistoryNavigationInNewSubframe(
RenderFrameHostImpl* render_frame_host,
const GURL& default_url) {
......
......@@ -238,6 +238,10 @@ class CONTENT_EXPORT NavigationControllerImpl : public NavigationController {
const scoped_refptr<const base::RefCountedString>& data_url_as_string);
#endif
// Invoked when a user activation occurs within the page, so that relevant
// entries can be updated as needed.
void NotifyUserActivation();
private:
friend class RestoreHelper;
......
......@@ -8734,6 +8734,166 @@ IN_PROC_BROWSER_TEST_F(NavigationControllerBrowserTest,
"Navigation.BackForward.SetShouldSkipOnBackForwardUI", false, 1);
}
// Tests that if a navigation entry is marked as skippable due to pushState then
// the flag should be reset if there is a user gesture on this document. All of
// the adjacent entries belonging to the same document will have their skippable
// bits reset.
IN_PROC_BROWSER_TEST_F(NavigationControllerHistoryInterventionBrowserTest,
OnUserGestureResetSameDocumentEntriesSkipFlag) {
GURL skippable_url(embedded_test_server()->GetURL("/frame_tree/top.html"));
EXPECT_TRUE(NavigateToURL(shell(), skippable_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_FALSE(root->HasBeenActivated());
EXPECT_FALSE(root->HasTransientUserActivation());
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
// Redirect to another page without a user gesture.
GURL redirected_url(embedded_test_server()->GetURL("/empty.html"));
EXPECT_TRUE(
NavigateToURLFromRendererWithoutUserGesture(shell(), redirected_url));
// Last entry should have been marked as skippable.
EXPECT_TRUE(controller.GetEntryAtIndex(0)->should_skip_on_back_forward_ui());
// Use the pushState API to add another entry without user gesture.
GURL push_state_url1(embedded_test_server()->GetURL("/title1.html"));
std::string script("history.pushState('', '','" + push_state_url1.spec() +
"');");
EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(shell()->web_contents(),
script));
// Use the pushState API to add another entry without user gesture.
GURL push_state_url2(embedded_test_server()->GetURL("/title2.html"));
script = "history.pushState('', '','" + push_state_url2.spec() + "');";
EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(shell()->web_contents(),
script));
EXPECT_EQ(3, controller.GetCurrentEntryIndex());
EXPECT_EQ(3, controller.GetLastCommittedEntryIndex());
// We now have
// [skippable_url(skip), redirected_url(skip), push_state_url1(skip),
// push_state_url2*]
// Last 2 entries should have been marked as skippable.
EXPECT_TRUE(controller.GetEntryAtIndex(1)->should_skip_on_back_forward_ui());
EXPECT_TRUE(controller.GetEntryAtIndex(2)->should_skip_on_back_forward_ui());
EXPECT_FALSE(
controller.GetLastCommittedEntry()->should_skip_on_back_forward_ui());
EXPECT_EQ(skippable_url, controller.GetEntryAtIndex(0)->GetURL());
EXPECT_EQ(redirected_url, controller.GetEntryAtIndex(1)->GetURL());
EXPECT_EQ(push_state_url1, controller.GetEntryAtIndex(2)->GetURL());
EXPECT_EQ(push_state_url2, controller.GetEntryAtIndex(3)->GetURL());
// Do another pushState so push_state_url2's entry also becomes skippable.
GURL push_state_url3(embedded_test_server()->GetURL("/title3.html"));
script = "history.pushState('', '','" + push_state_url3.spec() + "');";
EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(shell()->web_contents(),
script));
EXPECT_TRUE(controller.GetEntryAtIndex(3)->should_skip_on_back_forward_ui());
// We now have
// [skippable_url(skip), redirected_url(skip), push_state_url1(skip),
// push_state_url2(skip), push_state_url3*]
// Go to index 2.
TestNavigationObserver load_observer(shell()->web_contents());
controller.GoToIndex(2);
load_observer.Wait();
EXPECT_EQ(push_state_url1, controller.GetLastCommittedEntry()->GetURL());
// We now have (Before user gesture)
// [skippable_url(skip), redirected_url(skip), push_state_url1*,
// push_state_url2(skip), push_state_url3]
EXPECT_TRUE(controller.GetEntryAtIndex(0)->should_skip_on_back_forward_ui());
EXPECT_TRUE(controller.GetEntryAtIndex(1)->should_skip_on_back_forward_ui());
EXPECT_FALSE(controller.GetEntryAtIndex(2)->should_skip_on_back_forward_ui());
EXPECT_TRUE(controller.GetEntryAtIndex(3)->should_skip_on_back_forward_ui());
EXPECT_FALSE(controller.GetEntryAtIndex(4)->should_skip_on_back_forward_ui());
// Simulate a user gesture.
root->UpdateUserActivationState(
blink::UserActivationUpdateType::kNotifyActivation);
// We now have (After user gesture)
// [skippable_url(skip), redirected_url, push_state_url1*, push_state_url2,
// push_state_url3]
// All the navigations that refer to the same document should have their
// skippable bit reset.
EXPECT_FALSE(controller.GetEntryAtIndex(1)->should_skip_on_back_forward_ui());
EXPECT_FALSE(controller.GetEntryAtIndex(2)->should_skip_on_back_forward_ui());
EXPECT_FALSE(controller.GetEntryAtIndex(3)->should_skip_on_back_forward_ui());
EXPECT_FALSE(controller.GetEntryAtIndex(4)->should_skip_on_back_forward_ui());
// The first entry is not the same document and its bit should not be reset.
EXPECT_TRUE(controller.GetEntryAtIndex(0)->should_skip_on_back_forward_ui());
// goBack should now navigate to entry at index 1.
TestNavigationObserver back_load_observer(shell()->web_contents());
controller.GoBack();
back_load_observer.Wait();
EXPECT_EQ(redirected_url, controller.GetLastCommittedEntry()->GetURL());
// Do another pushState without user gesture.
GURL push_state_url4(embedded_test_server()->GetURL("/title3.html"));
script = "history.pushState('', '','" + push_state_url3.spec() + "');";
EXPECT_TRUE(content::ExecuteScriptWithoutUserGesture(shell()->web_contents(),
script));
// We now have
// [skippable_url(skip), redirected_url, push_state_url4*]
EXPECT_EQ(3, controller.GetEntryCount());
EXPECT_EQ(skippable_url, controller.GetEntryAtIndex(0)->GetURL());
EXPECT_EQ(redirected_url, controller.GetEntryAtIndex(1)->GetURL());
EXPECT_EQ(push_state_url4, controller.GetEntryAtIndex(2)->GetURL());
// The skippable flag will still be unset since this page has seen a user
// gesture once.
EXPECT_FALSE(controller.GetEntryAtIndex(1)->should_skip_on_back_forward_ui());
}
// Tests that if a navigation entry is marked as skippable due to redirect to a
// new document then the flag should not be reset if there is a user gesture on
// the new document.
IN_PROC_BROWSER_TEST_F(NavigationControllerHistoryInterventionBrowserTest,
OnUserGestureDoNotResetDifferentDocumentEntrySkipFlag) {
GURL skippable_url(embedded_test_server()->GetURL("/frame_tree/top.html"));
EXPECT_TRUE(NavigateToURL(shell(), skippable_url));
// It is safe to obtain the root frame tree node here, as it doesn't change.
FrameTreeNode* root = static_cast<WebContentsImpl*>(shell()->web_contents())
->GetFrameTree()
->root();
EXPECT_FALSE(root->HasBeenActivated());
EXPECT_FALSE(root->HasTransientUserActivation());
// Navigate to a new same-site document from the renderer without a user
// gesture.
GURL redirected_url(embedded_test_server()->GetURL("/title1.html"));
EXPECT_TRUE(
NavigateToURLFromRendererWithoutUserGesture(shell(), redirected_url));
NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
shell()->web_contents()->GetController());
EXPECT_EQ(1, controller.GetCurrentEntryIndex());
EXPECT_EQ(1, controller.GetLastCommittedEntryIndex());
EXPECT_TRUE(controller.GetEntryAtIndex(0)->should_skip_on_back_forward_ui());
EXPECT_FALSE(
controller.GetLastCommittedEntry()->should_skip_on_back_forward_ui());
// Simulate a user gesture.
root->UpdateUserActivationState(
blink::UserActivationUpdateType::kNotifyActivation);
// Since the last navigations refer to a different document, a user gesture
// here should not reset the skippable bit in the previous entries.
EXPECT_TRUE(controller.GetEntryAtIndex(0)->should_skip_on_back_forward_ui());
}
// Tests that the navigation entry is not marked as skippable on back/forward
// button if it does a renderer initiated navigation after getting a user
// activation.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment