0

Reland "Don't run NavigationThrottles on page activations"

This is a reland of 7d90f2699d which was
speculatively reverted but was not the root cause of the issue.

Original change's description:
> Don't run NavigationThrottles on page activations
>
> Background reading:
> https://groups.google.com/u/1/a/chromium.org/g/navigation-dev/c/RJVDOjG8sQ4
>
> https://docs.google.com/document/d/1aNCGTjfRg9KwNYIz6oA8VKZ8EjD2nLA9OfwcJ9dTUxc/edit#heading=h.w2ddswjnb45
>
> For an activating navigation like activating a prerendered page, or
> restoring a page from the BackForward cache, NavigationThrottles have
> already run when the page was navigated and loaded the first time. We
> don't want to run throttles a second time when activating the page to
> become primary.
>
> This CL avoids registering throttles when the NavigationRequest is a
> page activating request. We still proceed through the navigation using
> calls on NavigationThrottleRunner since that's pretty tightly coupled to
> the phases of a NavigationRequest.
>
> Summary:
>
>  * Remove two BFCache tests (added in https://crrev.com/c/1760220) that
>    were checking behavior of throttles on restoration, these are no
>    longer relevant.
>  * TestNavigationManager is used to pause at certain points in a
>    navigation. It uses a throttle to do this today. This CL adds a
>    CommitDeferringCondition instead where the request is PageActivating
>    to allow callers to pause at the equivalent stage. (this means such
>    callers cannot use WaitForRequestStart)
>  * Fixes NavigationSimulatorImpl to work for activating navigations
>    without throttles.
>
> Bug: 1199724
> Change-Id: I61e58912acf865cb4b82fc97ccf26d8808b61a9e
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2911320
> Commit-Queue: David Bokan <bokan@chromium.org>
> Reviewed-by: Alexander Timin <altimin@chromium.org>
> Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
> Reviewed-by: Tommy Li <tommycli@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#889886}

Bug: 1199724
Change-Id: Ie9fd0e3ab6f90296fe14521d6ab8f4d232f86336
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2945384
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Owners-Override: Dave Tapuska <dtapuska@chromium.org>
Commit-Queue: David Bokan <bokan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#890798}
This commit is contained in:
David Bokan
2021-06-09 16:19:57 +00:00
committed by Chromium LUCI CQ
parent 82309186bc
commit 377fb3e653
9 changed files with 267 additions and 114 deletions

@@ -112,7 +112,7 @@ IN_PROC_BROWSER_TEST_F(PDFIFrameNavigationThrottleBrowserTest,
// frame, we expect the navigation to be deferred during WillStartRequest // frame, we expect the navigation to be deferred during WillStartRequest
// until the prerender is activated. // until the prerender is activated.
{ {
pdf_navigation.WaitForDidStartNavigation(); pdf_navigation.WaitForFirstYieldAfterDidStartNavigation();
EXPECT_FALSE(pdf_navigation.GetNavigationHandle()->HasCommitted()); EXPECT_FALSE(pdf_navigation.GetNavigationHandle()->HasCommitted());
EXPECT_TRUE(pdf_navigation.GetNavigationHandle()->IsDeferredForTesting()); EXPECT_TRUE(pdf_navigation.GetNavigationHandle()->IsDeferredForTesting());
} }

@@ -71,6 +71,7 @@
#include "content/public/test/text_input_test_utils.h" #include "content/public/test/text_input_test_utils.h"
#include "content/public/test/url_loader_interceptor.h" #include "content/public/test/url_loader_interceptor.h"
#include "content/shell/browser/shell.h" #include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "content/shell/browser/shell_javascript_dialog_manager.h" #include "content/shell/browser/shell_javascript_dialog_manager.h"
#include "content/test/content_browser_test_utils_internal.h" #include "content/test/content_browser_test_utils_internal.h"
#include "content/test/echo.test-mojom.h" #include "content/test/echo.test-mojom.h"
@@ -3844,80 +3845,6 @@ IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, EvictPageWithInfiniteLoop) {
{}, {}, FROM_HERE); {}, {}, FROM_HERE);
} }
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
NavigationCancelledAtWillStartRequest) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
// 1) Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
// 2) Navigate to B.
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* rfh_b = current_frame_host();
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
// Cancel all navigation attempts.
content::TestNavigationThrottleInserter throttle_inserter(
shell()->web_contents(),
base::BindLambdaForTesting(
[&](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> {
auto throttle = std::make_unique<TestNavigationThrottle>(handle);
throttle->SetResponse(TestNavigationThrottle::WILL_START_REQUEST,
TestNavigationThrottle::SYNCHRONOUS,
NavigationThrottle::CANCEL_AND_IGNORE);
return throttle;
}));
// 3) Go back to A, which will be cancelled by the NavigationThrottle.
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// We should still be showing page B.
EXPECT_EQ(rfh_b, current_frame_host());
}
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
NavigationCancelledAtWillProcessResponse) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
// 1) Navigate to A.
EXPECT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
// 2) Navigate to B.
EXPECT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* rfh_b = current_frame_host();
EXPECT_FALSE(delete_observer_rfh_a.deleted());
EXPECT_TRUE(rfh_a->IsInBackForwardCache());
// Cancel all navigation attempts.
content::TestNavigationThrottleInserter throttle_inserter(
shell()->web_contents(),
base::BindLambdaForTesting(
[&](NavigationHandle* handle) -> std::unique_ptr<NavigationThrottle> {
auto throttle = std::make_unique<TestNavigationThrottle>(handle);
throttle->SetResponse(TestNavigationThrottle::WILL_PROCESS_RESPONSE,
TestNavigationThrottle::SYNCHRONOUS,
NavigationThrottle::CANCEL_AND_IGNORE);
return throttle;
}));
// 3) Go back to A, which will be cancelled by the NavigationThrottle.
web_contents()->GetController().GoBack();
EXPECT_TRUE(WaitForLoadStop(shell()->web_contents()));
// We should still be showing page B.
EXPECT_EQ(rfh_b, current_frame_host());
}
// Test the race condition where a document is evicted from the BackForwardCache // Test the race condition where a document is evicted from the BackForwardCache
// while it is in the middle of being restored and before URL loader starts a // while it is in the middle of being restored and before URL loader starts a
// response. // response.
@@ -4006,7 +3933,7 @@ IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
TestNavigationManager navigation_manager(shell()->web_contents(), url_a); TestNavigationManager navigation_manager(shell()->web_contents(), url_a);
web_contents()->GetController().GoBack(); web_contents()->GetController().GoBack();
EXPECT_TRUE(navigation_manager.WaitForRequestStart()); EXPECT_TRUE(navigation_manager.WaitForResponse());
// Flush the cache, which contains the document being navigated to. // Flush the cache, which contains the document being navigated to.
web_contents()->GetController().GetBackForwardCache().Flush(); web_contents()->GetController().GetBackForwardCache().Flush();
@@ -4578,7 +4505,6 @@ IN_PROC_BROWSER_TEST_F(
// 3) Go back to the first page using TestNavigationManager so that we split // 3) Go back to the first page using TestNavigationManager so that we split
// the navigation into stages. // the navigation into stages.
// web_contents()->GetController().GoBack();
TestNavigationManager navigation_manager_back(shell()->web_contents(), url); TestNavigationManager navigation_manager_back(shell()->web_contents(), url);
web_contents()->GetController().GoBack(); web_contents()->GetController().GoBack();
EXPECT_TRUE(navigation_manager_back.WaitForResponse()); EXPECT_TRUE(navigation_manager_back.WaitForResponse());
@@ -4587,6 +4513,9 @@ IN_PROC_BROWSER_TEST_F(
// asynchronously for renderers to reply that they've unfrozen. Finish the // asynchronously for renderers to reply that they've unfrozen. Finish the
// image response in that time. // image response in that time.
navigation_manager_back.ResumeNavigation(); navigation_manager_back.ResumeNavigation();
ASSERT_TRUE(
NavigationRequest::From(navigation_manager_back.GetNavigationHandle())
->IsCommitDeferringConditionDeferredForTesting());
ASSERT_FALSE(navigation_manager_back.GetNavigationHandle()->HasCommitted()); ASSERT_FALSE(navigation_manager_back.GetNavigationHandle()->HasCommitted());
image_response.Send(net::HTTP_OK, "image/png"); image_response.Send(net::HTTP_OK, "image/png");
@@ -11479,7 +11408,7 @@ IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest,
// 3) Start navigating back. // 3) Start navigating back.
TestNavigationManager nav_manager(shell->web_contents(), url_a); TestNavigationManager nav_manager(shell->web_contents(), url_a);
shell->web_contents()->GetController().GoBack(); shell->web_contents()->GetController().GoBack();
EXPECT_TRUE(nav_manager.WaitForRequestStart()); nav_manager.WaitForFirstYieldAfterDidStartNavigation();
testing::NiceMock<MockWebContentsObserver> observer(shell->web_contents()); testing::NiceMock<MockWebContentsObserver> observer(shell->web_contents());
EXPECT_CALL(observer, DidFinishNavigation(_)) EXPECT_CALL(observer, DidFinishNavigation(_))
@@ -11834,6 +11763,48 @@ IN_PROC_BROWSER_TEST_P(BackForwardCacheUnloadStrategyBrowserTest,
ExpectRestored(FROM_HERE); ExpectRestored(FROM_HERE);
} }
IN_PROC_BROWSER_TEST_F(BackForwardCacheBrowserTest, NoThrottlesOnCacheRestore) {
ASSERT_TRUE(embedded_test_server()->Start());
GURL url_a(embedded_test_server()->GetURL("a.com", "/title1.html"));
GURL url_b(embedded_test_server()->GetURL("b.com", "/title1.html"));
// 1) Navigate to A.
ASSERT_TRUE(NavigateToURL(shell(), url_a));
RenderFrameHostImpl* rfh_a = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_a(rfh_a);
bool did_register_throttles = false;
// This will track for each navigation whether we attempted to register
// NavigationThrottles.
content::ShellContentBrowserClient::Get()
->set_create_throttles_for_navigation_callback(base::BindLambdaForTesting(
[&did_register_throttles](content::NavigationHandle* handle)
-> std::vector<std::unique_ptr<content::NavigationThrottle>> {
did_register_throttles = true;
return std::vector<std::unique_ptr<content::NavigationThrottle>>();
}));
// 2) Navigate to B.
ASSERT_TRUE(NavigateToURL(shell(), url_b));
RenderFrameHostImpl* rfh_b = current_frame_host();
RenderFrameDeletedObserver delete_observer_rfh_b(rfh_b);
ASSERT_FALSE(delete_observer_rfh_a.deleted());
ASSERT_TRUE(rfh_a->IsInBackForwardCache());
EXPECT_TRUE(did_register_throttles);
did_register_throttles = false;
// 3) Go back to A which is in the BackForward cache and will be restored via
// an IsPageActivation navigation. Ensure that we did not register
// NavigationThrottles for this navigation since we already ran their checks
// when we navigated to A in step 1.
web_contents()->GetController().GoBack();
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
EXPECT_FALSE(did_register_throttles);
ExpectRestored(FROM_HERE);
}
namespace { namespace {
enum class SubframeType { SameSite, CrossSite }; enum class SubframeType { SameSite, CrossSite };
} }

@@ -50,8 +50,10 @@
#include "content/public/test/navigation_handle_observer.h" #include "content/public/test/navigation_handle_observer.h"
#include "content/public/test/prerender_test_util.h" #include "content/public/test/prerender_test_util.h"
#include "content/public/test/test_navigation_observer.h" #include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_navigation_throttle.h"
#include "content/public/test/test_utils.h" #include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h" #include "content/shell/browser/shell.h"
#include "content/shell/browser/shell_content_browser_client.h"
#include "content/test/content_browser_test_utils_internal.h" #include "content/test/content_browser_test_utils_internal.h"
#include "content/test/mock_commit_deferring_condition.h" #include "content/test/mock_commit_deferring_condition.h"
#include "content/test/test_content_browser_client.h" #include "content/test/test_content_browser_client.h"
@@ -868,6 +870,75 @@ IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
EXPECT_TRUE(is_activation_observer.was_activation()); EXPECT_TRUE(is_activation_observer.was_activation());
} }
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, ActivationDoesntRunThrottles) {
const GURL kInitialUrl = GetUrl("/empty.html");
const GURL kPrerenderingUrl = GetUrl("/empty.html?prerender");
test::PrerenderHostObserver prerender_observer(*shell()->web_contents(),
kPrerenderingUrl);
// Navigate to the initial page.
ASSERT_TRUE(NavigateToURL(shell(), kInitialUrl));
ASSERT_EQ(web_contents()->GetURL(), kInitialUrl);
ASSERT_TRUE(WaitForLoadStop(shell()->web_contents()));
NavigationThrottle* throttle = nullptr;
// This will attempt to insert a throttle that DEFERs the navigation at
// WillStartRequest into all new navigations.
content::ShellContentBrowserClient::Get()
->set_create_throttles_for_navigation_callback(base::BindLambdaForTesting(
[&throttle](content::NavigationHandle* handle)
-> std::vector<std::unique_ptr<content::NavigationThrottle>> {
std::vector<std::unique_ptr<content::NavigationThrottle>> throttles;
auto throttle_ptr =
std::make_unique<TestNavigationThrottle>(handle);
DCHECK(!throttle);
throttle = throttle_ptr.get();
throttle_ptr->SetResponse(
TestNavigationThrottle::WILL_START_REQUEST,
TestNavigationThrottle::SYNCHRONOUS, NavigationThrottle::DEFER);
throttles.push_back(std::move(throttle_ptr));
return throttles;
}));
// Start a prerender and ensure that a NavigationThrottle can defer the
// prerendering navigation. Then resume the navigation so the prerender
// navigation and load completes.
{
TestNavigationManager prerender_manager(shell()->web_contents(),
kPrerenderingUrl);
AddPrerenderAsync(kPrerenderingUrl);
prerender_manager.WaitForFirstYieldAfterDidStartNavigation();
ASSERT_NE(throttle, nullptr);
auto* request =
NavigationRequest::From(prerender_manager.GetNavigationHandle());
ASSERT_TRUE(request->IsDeferredForTesting());
EXPECT_EQ(request->GetDeferringThrottleForTesting(), throttle);
throttle = nullptr;
request->GetNavigationThrottleRunnerForTesting()->CallResumeForTesting();
prerender_manager.WaitForNavigationFinished();
auto host_id = GetHostForUrl(kPrerenderingUrl);
ASSERT_NE(host_id, RenderFrameHost::kNoFrameTreeNodeId);
EXPECT_EQ(GetPrerenderedMainFrameHost(host_id)->GetLastCommittedURL(),
kPrerenderingUrl);
}
// Now navigate the primary page to the prerendered URL so that we activate
// the prerender. The throttle should not have been registered for the
// activating navigation.
{
NavigatePrimaryPage(kPrerenderingUrl);
prerender_observer.WaitForActivation();
EXPECT_EQ(web_contents()->GetURL(), kPrerenderingUrl);
EXPECT_EQ(throttle, nullptr);
}
}
// Ensures that if we attempt to open a URL while prerendering with a window // Ensures that if we attempt to open a URL while prerendering with a window
// disposition other than CURRENT_TAB, we fail. // disposition other than CURRENT_TAB, we fail.
IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SuppressOpenURL) { IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest, SuppressOpenURL) {
@@ -2027,7 +2098,7 @@ IN_PROC_BROWSER_TEST_F(PrerenderBrowserTest,
kNewIframeUrl, Referrer(), child_frame->GetFrameTreeNodeId(), kNewIframeUrl, Referrer(), child_frame->GetFrameTreeNodeId(),
WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_AUTO_SUBFRAME, WindowOpenDisposition::CURRENT_TAB, ui::PAGE_TRANSITION_AUTO_SUBFRAME,
/*is_renderer_initiated=*/false)); /*is_renderer_initiated=*/false));
iframe_observer.WaitForDidStartNavigation(); iframe_observer.WaitForFirstYieldAfterDidStartNavigation();
NavigationRequest* request = NavigationRequest* request =
static_cast<NavigationRequest*>(iframe_observer.GetNavigationHandle()); static_cast<NavigationRequest*>(iframe_observer.GetNavigationHandle());
EXPECT_EQ(request->state(), NavigationRequest::WILL_START_REQUEST); EXPECT_EQ(request->state(), NavigationRequest::WILL_START_REQUEST);

@@ -4791,6 +4791,11 @@ void NavigationRequest::CancelDeferredNavigation(
void NavigationRequest::RegisterThrottleForTesting( void NavigationRequest::RegisterThrottleForTesting(
std::unique_ptr<NavigationThrottle> navigation_throttle) { std::unique_ptr<NavigationThrottle> navigation_throttle) {
// Throttles will already have run the first time the page was navigated, we
// won't run them again on activation. See instead CommitDeferringCondition.
DCHECK(!IsPageActivation())
<< "Attempted to register a NavigationThrottle for an activating "
"navigation which will not work.";
throttle_runner_->AddThrottle(std::move(navigation_throttle)); throttle_runner_->AddThrottle(std::move(navigation_throttle));
} }
bool NavigationRequest::IsDeferredForTesting() { bool NavigationRequest::IsDeferredForTesting() {
@@ -4868,7 +4873,11 @@ void NavigationRequest::WillStartRequest() {
return; return;
} }
throttle_runner_->RegisterNavigationThrottles(); // Throttles will already have run the first time the page was navigated, we
// won't run them again on activation.
if (!IsPageActivation())
throttle_runner_->RegisterNavigationThrottles();
commit_deferrer_->RegisterDeferringConditions(*this); commit_deferrer_->RegisterDeferringConditions(*this);
// If the content/ embedder did not pass the NavigationUIData at the beginning // If the content/ embedder did not pass the NavigationUIData at the beginning

@@ -84,6 +84,7 @@
#include "content/public/test/test_navigation_observer.h" #include "content/public/test/test_navigation_observer.h"
#include "content/public/test/test_utils.h" #include "content/public/test/test_utils.h"
#include "content/test/did_commit_navigation_interceptor.h" #include "content/test/did_commit_navigation_interceptor.h"
#include "content/test/mock_commit_deferring_condition.h"
#include "ipc/ipc_security_test_util.h" #include "ipc/ipc_security_test_util.h"
#include "mojo/public/cpp/bindings/pending_remote.h" #include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h" #include "mojo/public/cpp/bindings/receiver.h"
@@ -2911,11 +2912,10 @@ TestNavigationManager::TestNavigationManager(WebContents* web_contents,
: WebContentsObserver(web_contents), url_(url) {} : WebContentsObserver(web_contents), url_(url) {}
TestNavigationManager::~TestNavigationManager() { TestNavigationManager::~TestNavigationManager() {
if (navigation_paused_) ResumeIfPaused();
request_->GetNavigationThrottleRunnerForTesting()->CallResumeForTesting();
} }
void TestNavigationManager::WaitForDidStartNavigation() { void TestNavigationManager::WaitForFirstYieldAfterDidStartNavigation() {
if (current_state_ >= NavigationState::WILL_START) if (current_state_ >= NavigationState::WILL_START)
return; return;
@@ -2933,8 +2933,7 @@ void TestNavigationManager::ResumeNavigation() {
current_state_ == NavigationState::RESPONSE); current_state_ == NavigationState::RESPONSE);
DCHECK_EQ(current_state_, desired_state_); DCHECK_EQ(current_state_, desired_state_);
DCHECK(navigation_paused_); DCHECK(navigation_paused_);
navigation_paused_ = false; ResumeIfPaused();
request_->GetNavigationThrottleRunnerForTesting()->CallResumeForTesting();
} }
NavigationHandle* TestNavigationManager::GetNavigationHandle() { NavigationHandle* TestNavigationManager::GetNavigationHandle() {
@@ -2956,13 +2955,27 @@ void TestNavigationManager::DidStartNavigation(NavigationHandle* handle) {
return; return;
request_ = NavigationRequest::From(handle); request_ = NavigationRequest::From(handle);
auto throttle = std::make_unique<TestNavigationManagerThrottle>( if (request_->IsPageActivation()) {
request_, // For activating navigations, we have no way of stopping at
base::BindOnce(&TestNavigationManager::OnWillStartRequest, // WillStartRequest since we don't run throttles. Callers should use
weak_factory_.GetWeakPtr()), // WaitForResponse() or WaitForFirstYieldAfterDidStartNavigation().
base::BindOnce(&TestNavigationManager::OnWillProcessResponse, DCHECK_NE(desired_state_, NavigationState::STARTED);
weak_factory_.GetWeakPtr()));
request_->RegisterThrottleForTesting(std::move(throttle)); auto condition = std::make_unique<MockCommitDeferringCondition>(
/*is_ready_to_commit=*/false,
base::BindOnce(
&TestNavigationManager::OnRunningCommitDeferringConditions,
weak_factory_.GetWeakPtr()));
request_->RegisterCommitDeferringConditionForTesting(std::move(condition));
} else {
auto throttle = std::make_unique<TestNavigationManagerThrottle>(
request_,
base::BindOnce(&TestNavigationManager::OnWillStartRequest,
weak_factory_.GetWeakPtr()),
base::BindOnce(&TestNavigationManager::OnWillProcessResponse,
weak_factory_.GetWeakPtr()));
request_->RegisterThrottleForTesting(std::move(throttle));
}
current_state_ = NavigationState::WILL_START; current_state_ = NavigationState::WILL_START;
@@ -2973,8 +2986,10 @@ void TestNavigationManager::DidStartNavigation(NavigationHandle* handle) {
// is set to always pause navigations at WillStartRequest. This ensures the // is set to always pause navigations at WillStartRequest. This ensures the
// navigation will defer and the user can always call // navigation will defer and the user can always call
// WaitForRequestStart. // WaitForRequestStart.
if (desired_state_ == NavigationState::WILL_START) if (!request_->IsPageActivation() &&
desired_state_ == NavigationState::WILL_START) {
desired_state_ = NavigationState::STARTED; desired_state_ = NavigationState::STARTED;
}
} }
void TestNavigationManager::DidFinishNavigation(NavigationHandle* handle) { void TestNavigationManager::DidFinishNavigation(NavigationHandle* handle) {
@@ -3006,6 +3021,14 @@ void TestNavigationManager::OnWillProcessResponse() {
OnNavigationStateChanged(); OnNavigationStateChanged();
} }
void TestNavigationManager::OnRunningCommitDeferringConditions(
base::OnceClosure resume_closure) {
current_state_ = NavigationState::RESPONSE;
commit_deferring_condition_resume_closure_ = std::move(resume_closure);
navigation_paused_ = true;
OnNavigationStateChanged();
}
// TODO(csharrison): Remove CallResumeForTesting method calls in favor of doing // TODO(csharrison): Remove CallResumeForTesting method calls in favor of doing
// it through the throttle. // it through the throttle.
bool TestNavigationManager::WaitForDesiredState() { bool TestNavigationManager::WaitForDesiredState() {
@@ -3014,8 +3037,7 @@ bool TestNavigationManager::WaitForDesiredState() {
return true; return true;
// Resume the navigation if it was paused. // Resume the navigation if it was paused.
if (navigation_paused_) ResumeIfPaused();
request_->GetNavigationThrottleRunnerForTesting()->CallResumeForTesting();
// Wait for the desired state if needed. // Wait for the desired state if needed.
if (current_state_ < desired_state_) { if (current_state_ < desired_state_) {
@@ -3031,6 +3053,12 @@ bool TestNavigationManager::WaitForDesiredState() {
} }
void TestNavigationManager::OnNavigationStateChanged() { void TestNavigationManager::OnNavigationStateChanged() {
if (request_ && request_->IsPageActivation()) {
DCHECK_NE(desired_state_, NavigationState::STARTED)
<< "Cannot use WaitForRequestStart() when managing an activating "
"navigation. Use either WaitForFirstYieldAfterDidStartNavigation() "
"or WaitForResponse()";
}
// If the state the user was waiting for has been reached, exit the message // If the state the user was waiting for has been reached, exit the message
// loop. // loop.
if (current_state_ >= desired_state_) { if (current_state_ >= desired_state_) {
@@ -3040,8 +3068,19 @@ void TestNavigationManager::OnNavigationStateChanged() {
} }
// Otherwise, the navigation should be resumed if it was previously paused. // Otherwise, the navigation should be resumed if it was previously paused.
if (navigation_paused_) ResumeIfPaused();
}
void TestNavigationManager::ResumeIfPaused() {
if (!navigation_paused_)
return;
navigation_paused_ = false;
if (!request_->IsPageActivation())
request_->GetNavigationThrottleRunnerForTesting()->CallResumeForTesting(); request_->GetNavigationThrottleRunnerForTesting()->CallResumeForTesting();
else if (commit_deferring_condition_resume_closure_)
std::move(commit_deferring_condition_resume_closure_).Run();
} }
bool TestNavigationManager::ShouldMonitorNavigation(NavigationHandle* handle) { bool TestNavigationManager::ShouldMonitorNavigation(NavigationHandle* handle) {

@@ -1507,6 +1507,12 @@ class FrameDeletedObserver {
// Note: This class is one time use only! After it successfully tracks a // Note: This class is one time use only! After it successfully tracks a
// navigation it will ignore all subsequent navigations. Explicitly create // navigation it will ignore all subsequent navigations. Explicitly create
// multiple instances of this class if you want to pause multiple navigations. // multiple instances of this class if you want to pause multiple navigations.
// Note2: For page activating navigations (like a prerender activation, or a
// BFCache restore navigation), the navigation will not run throttles. The
// manager in this case uses a CommitDeferringCondition for pausing the
// navigation at the equivalent of WillProcessResponse. However, in these kinds
// of navigations you cannot use WaitForRequestStart; if you want to yield
// before WillProcessResponse, use WaitForFirstYieldAfterDidStartNavigation.
class TestNavigationManager : public WebContentsObserver { class TestNavigationManager : public WebContentsObserver {
public: public:
// Monitors any frame in WebContents. // Monitors any frame in WebContents.
@@ -1514,15 +1520,23 @@ class TestNavigationManager : public WebContentsObserver {
~TestNavigationManager() override; ~TestNavigationManager() override;
// Waits until the navigation starts. This is called from // Waits until the first yield point after DidStartNavigation. Unlike
// WebContentsObserver::DidStartNavigation and is run before any navigation // WaitForRequestStart, this can be used to wait for a pause in cases where a
// throttles are registered. // test expects a NavigationThrottle to defer in WillStartRequest. In cases
void WaitForDidStartNavigation(); // where throttles run and none defer, this will break at the same time as
// WaitForRequestStart. Also unlike WaitForRequestStart, this can be used to
// wait on a page activating navigation to start. Note: since we won't know
// which throttle deferred, don't use ResumeNavigation() after this call since
// it assumes we paused from the TestNavigationManagerThrottle.
void WaitForFirstYieldAfterDidStartNavigation();
// Waits until the navigation request is ready to be sent to the network // Waits until the navigation request is ready to be sent to the network
// stack. This will wait until all NavigationThrottles have proceeded through // stack. This will wait until all NavigationThrottles have proceeded through
// WillStartRequest. Returns false if the request was aborted before // WillStartRequest. Returns false if the request was aborted before starting.
// starting. // Note: RequestStart is never reached for page activating navigations (e.g.
// prerender activation, BFCache restore). In those cases you should either
// use WaitForFirstYieldAfterDidStartNavigation or WaitForResponse. See
// TestNavigationManager class comment for more detail.
WARN_UNUSED_RESULT bool WaitForRequestStart(); WARN_UNUSED_RESULT bool WaitForRequestStart();
// Waits until the navigation response's headers have been received. This // Waits until the navigation response's headers have been received. This
@@ -1580,6 +1594,10 @@ class TestNavigationManager : public WebContentsObserver {
// WillProcessResponse. // WillProcessResponse.
void OnWillProcessResponse(); void OnWillProcessResponse();
// Called when the navigation pauses in the MockCommitDeferringCondition. This
// happens only for page activating navigations like a prerender activation.
void OnRunningCommitDeferringConditions(base::OnceClosure resume_closure);
// Waits for the desired state. Returns false if the desired state cannot be // Waits for the desired state. Returns false if the desired state cannot be
// reached (eg the navigation finishes before reaching this state). // reached (eg the navigation finishes before reaching this state).
bool WaitForDesiredState(); bool WaitForDesiredState();
@@ -1589,6 +1607,8 @@ class TestNavigationManager : public WebContentsObserver {
// resume the navigation if it hasn't been reached yet. // resume the navigation if it hasn't been reached yet.
void OnNavigationStateChanged(); void OnNavigationStateChanged();
void ResumeIfPaused();
const GURL url_; const GURL url_;
NavigationRequest* request_ = nullptr; NavigationRequest* request_ = nullptr;
bool navigation_paused_ = false; bool navigation_paused_ = false;
@@ -1599,6 +1619,12 @@ class TestNavigationManager : public WebContentsObserver {
base::OnceClosure quit_closure_; base::OnceClosure quit_closure_;
base::RunLoop::Type message_loop_type_ = base::RunLoop::Type::kDefault; base::RunLoop::Type message_loop_type_ = base::RunLoop::Type::kDefault;
// In a page activating navigation (prerender activation, back-forward cache
// activation), the navigation will be stopped in a commit deferring condition
// (since NavigationThrottles aren't run in a page activation). When that
// happens, the navigation can be resumed using this closure.
base::OnceClosure commit_deferring_condition_resume_closure_;
base::WeakPtrFactory<TestNavigationManager> weak_factory_{this}; base::WeakPtrFactory<TestNavigationManager> weak_factory_{this};
DISALLOW_COPY_AND_ASSIGN(TestNavigationManager); DISALLOW_COPY_AND_ASSIGN(TestNavigationManager);

@@ -385,6 +385,11 @@ void NavigationSimulatorImpl::InitializeFromStartedRequest(
} }
void NavigationSimulatorImpl::RegisterTestThrottle(NavigationRequest* request) { void NavigationSimulatorImpl::RegisterTestThrottle(NavigationRequest* request) {
// Page activating navigations don't run throttles so we don't need to
// register it in that case.
if (request_->IsPageActivation())
return;
request->RegisterThrottleForTesting( request->RegisterThrottleForTesting(
std::make_unique<NavigationThrottleCallbackRunner>( std::make_unique<NavigationThrottleCallbackRunner>(
request, request,
@@ -552,7 +557,7 @@ void NavigationSimulatorImpl::ReadyToCommit() {
auto complete_closure = auto complete_closure =
base::BindOnce(&NavigationSimulatorImpl::WillProcessResponseComplete, base::BindOnce(&NavigationSimulatorImpl::WillProcessResponseComplete,
weak_factory_.GetWeakPtr()); weak_factory_.GetWeakPtr());
if (NeedsThrottleChecks()) { if (NeedsPreCommitChecks()) {
MaybeWaitForThrottleChecksComplete(std::move(complete_closure)); MaybeWaitForThrottleChecksComplete(std::move(complete_closure));
MaybeWaitForReadyToCommitCheckComplete(); MaybeWaitForReadyToCommitCheckComplete();
if (state_ == READY_TO_COMMIT) { if (state_ == READY_TO_COMMIT) {
@@ -565,6 +570,7 @@ void NavigationSimulatorImpl::ReadyToCommit() {
} }
return; return;
} }
std::move(complete_closure).Run(); std::move(complete_closure).Run();
ReadyToCommitComplete(); ReadyToCommitComplete();
} }
@@ -1236,7 +1242,7 @@ bool NavigationSimulatorImpl::SimulateRendererInitiatedStart() {
void NavigationSimulatorImpl::MaybeWaitForThrottleChecksComplete( void NavigationSimulatorImpl::MaybeWaitForThrottleChecksComplete(
base::OnceClosure complete_closure) { base::OnceClosure complete_closure) {
// If last_throttle_check_result_ is set, then throttle checks completed // If last_throttle_check_result_ is set, then the navigation phase completed
// synchronously. // synchronously.
if (last_throttle_check_result_) { if (last_throttle_check_result_) {
std::move(complete_closure).Run(); std::move(complete_closure).Run();
@@ -1269,6 +1275,11 @@ void NavigationSimulatorImpl::Wait() {
bool NavigationSimulatorImpl::OnThrottleChecksComplete( bool NavigationSimulatorImpl::OnThrottleChecksComplete(
NavigationThrottle::ThrottleCheckResult result) { NavigationThrottle::ThrottleCheckResult result) {
if (request_->IsPageActivation()) {
// Throttles don't run in page activations so we shouldn't ever get back
// anything other than PROCEED.
CHECK_EQ(result.action(), NavigationThrottle::PROCEED);
}
CHECK(!last_throttle_check_result_); CHECK(!last_throttle_check_result_);
last_throttle_check_result_ = result; last_throttle_check_result_ = result;
if (wait_closure_) if (wait_closure_)
@@ -1482,7 +1493,19 @@ bool NavigationSimulatorImpl::NeedsThrottleChecks() const {
return false; return false;
} }
// Back/forward cache restores and prerendering page activations do not run
// NavigationThrottles since they were already run when the page was first
// loaded.
DCHECK(request_);
if (request_->IsPageActivation())
return false;
return IsURLHandledByNetworkStack(navigation_url_); return IsURLHandledByNetworkStack(navigation_url_);
} }
bool NavigationSimulatorImpl::NeedsPreCommitChecks() const {
DCHECK(request_);
return NeedsThrottleChecks() || request_->IsPageActivation();
}
} // namespace content } // namespace content

@@ -210,18 +210,20 @@ class NavigationSimulatorImpl : public NavigationSimulator,
// navigation failed synchronously. // navigation failed synchronously.
bool SimulateRendererInitiatedStart(); bool SimulateRendererInitiatedStart();
// This method will block waiting for throttle checks to complete if // This method will block waiting for the navigation to reach the next
// |auto_advance_|. Otherwise will just set up state for checking the result // NavigationThrottle phase of the navigation to complete
// when the throttles end up finishing. // (StartRequest|Redirect|Failed|ProcessResponse) if |auto_advance_|. This
// waits until *after* throttle checks are run (if the navigation requires
// throttle checks). If |!auto_advance_| this will just set up state for
// checking the result when the throttles end up finishing.
void MaybeWaitForThrottleChecksComplete(base::OnceClosure complete_closure); void MaybeWaitForThrottleChecksComplete(base::OnceClosure complete_closure);
// Like above but blocks waiting for the ReadyToCommit checks to complete. // Like above but blocks waiting for the ReadyToCommit checks to complete.
// This check calls ReadyToCommitComplete() when finished. // This check calls ReadyToCommitComplete() when finished.
void MaybeWaitForReadyToCommitCheckComplete(); void MaybeWaitForReadyToCommitCheckComplete();
// Sets |last_throttle_check_result_| and calls both the // Sets |last_throttle_check_result_| and calls both the |wait_closure_| and
// |wait_closure_| and the |throttle_checks_complete_closure_|, if they are // the |throttle_checks_complete_closure_|, if they are set.
// set.
bool OnThrottleChecksComplete(NavigationThrottle::ThrottleCheckResult result); bool OnThrottleChecksComplete(NavigationThrottle::ThrottleCheckResult result);
// Helper method to set the OnThrottleChecksComplete callback on the // Helper method to set the OnThrottleChecksComplete callback on the
@@ -252,8 +254,16 @@ class NavigationSimulatorImpl : public NavigationSimulator,
// - same-document navigations // - same-document navigations
// - about:blank navigations // - about:blank navigations
// - navigations not handled by the network stack // - navigations not handled by the network stack
// - page activations like prerendering and back-forward cache.
bool NeedsThrottleChecks() const; bool NeedsThrottleChecks() const;
// Whether the navigation performs CommitDeferringCondition checks before
// committing. i.e. if it goes through the full
// WillStartRequest->WillProcessResponse->etc.->Commit phases. This includes
// all navigations that require throttle checks plus page activations like
// prerendering/BFCache.
bool NeedsPreCommitChecks() const;
enum State { enum State {
INITIALIZATION, INITIALIZATION,
WAITING_BEFORE_UNLOAD, WAITING_BEFORE_UNLOAD,
@@ -366,10 +376,9 @@ class NavigationSimulatorImpl : public NavigationSimulator,
// result. Calling this will quit the nested run loop. // result. Calling this will quit the nested run loop.
base::OnceClosure wait_closure_; base::OnceClosure wait_closure_;
// This member simply ensures that we do not disconnect // This member simply ensures that we do not disconnect the NavigationClient
// the NavigationClient interface, as it would be interpreted as a // interface, as it would be interpreted as a cancellation coming from the
// cancellation coming from the renderer process side. This member interface // renderer process side. This member interface will never be bound.
// will never be bound.
mojo::PendingAssociatedReceiver<mojom::NavigationClient> mojo::PendingAssociatedReceiver<mojom::NavigationClient>
navigation_client_receiver_; navigation_client_receiver_;

@@ -160,5 +160,10 @@ They are typically registered in
`NavigationThrottleRunner::RegisterNavigationThrottles` or `NavigationThrottleRunner::RegisterNavigationThrottles` or
`ContentBrowserClient::CreateThrottlesForNavigation`. `ContentBrowserClient::CreateThrottlesForNavigation`.
NavigationThrottles are only invoked on navigations that require a URLLoader
(see NavigationRequest::NeedsUrlLoader). This means they don't typically run in
cases like same-document navigations, about:blank, etc. They are also not run in
page-activation navigations, such as activating a prerendered page or restoring
a page from the back-forward cache.
[WebContentsObserver]: https://source.chromium.org/chromium/chromium/src/+/main:content/public/browser/web_contents_observer.h [WebContentsObserver]: https://source.chromium.org/chromium/chromium/src/+/main:content/public/browser/web_contents_observer.h