chromium/content/browser/renderer_host/navigation_controller_history_intervention_browsertest.cc

// Copyright 2023 The Chromium Authors
// 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 "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/navigation_controller_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/features.h"
#include "content/public/test/back_forward_cache_util.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/test_navigation_observer.h"
#include "content/shell/browser/shell.h"
#include "content/shell/common/shell_switches.h"
#include "content/test/content_browser_test_utils_internal.h"
#include "content/test/render_document_feature.h"
#include "net/dns/mock_host_resolver.h"

namespace content {

namespace {

// Does a renderer-initiated location.replace navigation to |url|, replacing the
// current entry.
bool RendererLocationReplace(Shell* shell, const GURL& url) {}

}  // namespace

class NavigationControllerHistoryInterventionBrowserTest
    : public ContentBrowserTest,
      public ::testing::WithParamInterface<
          std::tuple<std::string /* render_document_level */,
                     bool /* enable_back_forward_cache*/>> {};

// Tests that the navigation entry is marked as skippable on back/forward button
// if it does a renderer initiated navigation without ever getting a user
// activation.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       NoUserActivationSetSkipOnBackForward) {}

// Same as the above test except the navigation is cross-site in this case.
// Tests that the navigation entry is marked as skippable on back/forward button
// if it does a renderer initiated cross-site navigation without ever getting a
// user activation.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       NoUserActivationSetSkipOnBackForwardCrossSite) {}

// Tests that when a navigation entry is not marked as skippable the first time
// it redirects because of user activation, that entry will be marked skippable
// if it does another redirect without user activation after the user has come
// back to that document again. This implies that a single user activation does
// not mean that the user can be infintely trapped.
// For a same-document version of this test, see
// OnUserGestureResetSameDocumentEntriesSkipFlag (https://crbug.com/1248529).
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       NoUserActivationAfterReturningSetsSkippable) {}

// Tests that the navigation entry is marked as skippable on back button if it
// does a renderer initiated navigation without ever getting a user activation.
// Also tests this for an entry added using history.pushState.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       NoUserActivationSetSkippableMultipleGoBack) {}

// Same as above but tests the metrics on going forward.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       NoUserActivationSetSkippableMultipleGoForward) {}

// Tests that if an entry is marked as skippable, it will not be reset if there
// is a navigation to this entry again (crbug.com/112129).
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       DoNotResetSkipOnBackForward) {}

// Tests that if an entry is marked as skippable, it will not be reset if there
// is a navigation to this entry again (crbug.com/1121293) using history.back/
// forward.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       DoNotResetSkipOnHistoryBackAPI) {}

namespace {

// WebContentsDelegate that keeps track of CanGoBack state during all
// NavigationStateChanged notifications.
class CanGoBackNavigationStateChangedDelegate : public WebContentsDelegate {};
}  // namespace

// Tests the value of honor_sticky_activation_for_history_intervention_ starts
// at true, becomes false after same-document history navigations and gets
// properly reset to true again.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       TestHonorStickyActivationForHistoryIntervention) {}

// Same as above but for forward button instead of back button to ensure the
// intervention works in both directions.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       TestHonorStickyActivationForHistoryInterventionForward) {}

// Same as above but expects honor_sticky_activation_for_history_intervention_
// to be true when a user activation is received.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       HonorStickyActivationForHistoryInterventionReset) {}

// Same as above but expects honor_sticky_activation_for_history_intervention_
// to not be reset with a replaceState call.
IN_PROC_BROWSER_TEST_P(
    NavigationControllerHistoryInterventionBrowserTest,
    HonorStickyActivationForHistoryInterventionNotResetOnReplaceState) {}

// Tests that both sticky user activation and
// `honor_sticky_activation_for_history_intervention_`are reset on a reload
// since reload is considered a cross-document navigation.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       TestStickyActivationOnReload) {}

// Tests that when `honor_sticky_activation_for_history_intervention_` is
// false, a cross-document navigation should mark the entry as skippable.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       TestHonorStickyActivationCrossDocument) {}

// Tests that `honor_sticky_activation_for_history_intervention_` should
// stay false and a cross-document navigation should mark the entry as
// skippable, even if the back navigation that set the
// `honor_sticky_activation_for_history_intervention_` to false was for a
// child frame that did a same-document navigation. The next test tests the
// same behavior after the child frame does a cross-document navigation.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       TestHonorStickyActivationWithChildFrame) {}

// Same as the above test except the child frame does a cross-document
// navigation in this test.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       TestHonorStickyActivationWithChildFrameCrossDocument) {}

// 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_P(NavigationControllerHistoryInterventionBrowserTest,
                       OnUserGestureResetSameDocumentEntriesSkipFlag) {}

// 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_P(NavigationControllerHistoryInterventionBrowserTest,
                       OnUserGestureDoNotResetDifferentDocumentEntrySkipFlag) {}

// 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.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       UserActivationDoNotSkipOnBackForward) {}

// Tests that the navigation entry should not be marked as skippable on
// back/forward button if it is navigated away using a browser initiated
// navigation.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       BrowserInitiatedNavigationDoNotSkipOnBackForward) {}

// Tests that the navigation entry that is marked as skippable on back/forward
// button does not get skipped for history.back API calls.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       SetSkipOnBackDoNotSkipForHistoryBackAPI) {}

#if BUILDFLAG(IS_ANDROID)
// Test GoToOffset with enable history intervention.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       GoToOffsetWithSkippingEnableHistoryIntervention) {
  base::HistogramTester histograms;
  GURL non_skippable_url(
      embedded_test_server()->GetURL("/frame_tree/top.html"));
  EXPECT_TRUE(NavigateToURL(shell(), non_skippable_url));

  GURL skippable_url(embedded_test_server()->GetURL("/title1.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())
                            ->GetPrimaryFrameTree()
                            .root();

  EXPECT_FALSE(root->HasStickyUserActivation());
  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("/title2.html"));
  EXPECT_TRUE(
      NavigateToURLFromRendererWithoutUserGesture(shell(), redirected_url));

  GURL skippable_url2(embedded_test_server()->GetURL("/title3.html"));
  EXPECT_TRUE(NavigateToURL(shell(), skippable_url2));

  EXPECT_FALSE(root->HasStickyUserActivation());
  EXPECT_FALSE(root->HasTransientUserActivation());

  // Navigate to a new same-site document from the renderer without a user
  // gesture.
  GURL redirected_url2(embedded_test_server()->GetURL("/title4.html"));
  EXPECT_TRUE(
      NavigateToURLFromRendererWithoutUserGesture(shell(), redirected_url2));

  // CanGoToOffset should visit the skippable entries while
  // CanGoToOffsetWithSKipping will skip the skippable entries.
  NavigationControllerImpl& controller = static_cast<NavigationControllerImpl&>(
      shell()->web_contents()->GetController());
  EXPECT_TRUE(controller.CanGoToOffset(-3));
  EXPECT_TRUE(controller.CanGoToOffset(-4));
  EXPECT_FALSE(controller.CanGoToOffsetWithSkipping(-3));

  TestNavigationObserver nav_observer(shell()->web_contents());
  controller.GoToOffset(-4);
  nav_observer.Wait();
  EXPECT_EQ(0, controller.GetCurrentEntryIndex());
  EXPECT_EQ(0, controller.GetLastCommittedEntryIndex());
  EXPECT_EQ(non_skippable_url, controller.GetLastCommittedEntry()->GetURL());
}
#endif  // BUILDFLAG(IS_ANDROID)

// Tests that the navigation entry that is marked as skippable on back/forward
// button does not get skipped for GoToOffset calls.
// This covers actions in the following scenario:
// [non_skippable_url, skippable_url, redirected_url, skippable_url2,
// redirected_url2]
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       SetSkipOnBackForwardDoNotSkipForGoToOffset) {}

// Tests that the navigation entry that is marked as skippable on back/forward
// button is skipped for GoToOffset calls.
// This covers actions in the following scenario:
// [non_skippable_url, skippable_url, redirected_url, skippable_url2,
// redirected_url2]
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       SetSkipOnBackForwardDoSkipForGoToOffsetWithSkipping) {}

// Tests that the navigation entry that is marked as skippable on back/forward
// button does not get skipped for history.forward API calls.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       SetSkipOnBackDoNotSkipForHistoryForwardAPI) {}

// Tests that the oldest navigation entry that is marked as skippable is the one
// that is pruned if max entry count is reached.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       PruneOldestSkippableEntry) {}

// Tests that we fallback to pruning the oldest entry if the last committed
// entry is the oldest skippable navigation entry.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       PruneOldestWhenLastCommittedIsSkippable) {}

// Tests that the navigation entry is marked as skippable on back/forward
// button if a subframe does a push state without ever getting a user
// activation.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       NoUserActivationSetSkipOnBackForwardSubframe) {}

// Tests that the navigation entry is not marked as skippable on back/forward
// button if a subframe does a push state without ever getting a user
// activation on itself but there was a user gesture on the main frame.
IN_PROC_BROWSER_TEST_P(
    NavigationControllerHistoryInterventionBrowserTest,
    UserActivationMainFrameDoNotSetSkipOnBackForwardSubframe) {}

// Tests that all same document entries are marked as skippable together.
IN_PROC_BROWSER_TEST_P(NavigationControllerHistoryInterventionBrowserTest,
                       SetSkipOnBackForwardSameDocumentEntries) {}

INSTANTIATE_TEST_SUITE_P();

}  // namespace content