blob: 16db4797d3a81b8735b34ca58f92d072edb39522 [file] [log] [blame]
// Copyright 2017 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 "content/test/navigation_simulator_impl.h"
#include <utility>
#include "base/bind.h"
#include "base/debug/stack_trace.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "content/browser/renderer_host/debug_urls.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_entry_impl.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/common/content_navigation_policy.h"
#include "content/common/frame_messages.h"
#include "content/common/navigation_params.h"
#include "content/common/navigation_params_utils.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/navigation_policy.h"
#include "content/public/common/url_utils.h"
#include "content/test/test_navigation_url_loader.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_web_contents.h"
#include "ipc/ipc_message.h"
#include "mojo/public/cpp/bindings/pending_associated_remote.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "net/base/load_flags.h"
#include "net/url_request/redirect_info.h"
#include "services/metrics/public/cpp/ukm_recorder.h"
namespace content {
namespace {
class NavigationThrottleCallbackRunner : public NavigationThrottle {
public:
NavigationThrottleCallbackRunner(
NavigationHandle* handle,
base::OnceClosure on_will_start_request,
const base::RepeatingClosure& on_will_redirect_request,
base::OnceClosure on_will_fail_request,
base::OnceClosure on_will_process_response)
: NavigationThrottle(handle),
on_will_start_request_(std::move(on_will_start_request)),
on_will_redirect_request_(on_will_redirect_request),
on_will_fail_request_(std::move(on_will_fail_request)),
on_will_process_response_(std::move(on_will_process_response)) {}
NavigationThrottle::ThrottleCheckResult WillStartRequest() override {
std::move(on_will_start_request_).Run();
return NavigationThrottle::PROCEED;
}
NavigationThrottle::ThrottleCheckResult WillRedirectRequest() override {
on_will_redirect_request_.Run();
return NavigationThrottle::PROCEED;
}
NavigationThrottle::ThrottleCheckResult WillFailRequest() override {
std::move(on_will_fail_request_).Run();
return NavigationThrottle::PROCEED;
}
NavigationThrottle::ThrottleCheckResult WillProcessResponse() override {
std::move(on_will_process_response_).Run();
return NavigationThrottle::PROCEED;
}
const char* GetNameForLogging() override {
return "NavigationThrottleCallbackRunner";
}
private:
base::OnceClosure on_will_start_request_;
base::RepeatingClosure on_will_redirect_request_;
base::OnceClosure on_will_fail_request_;
base::OnceClosure on_will_process_response_;
};
int64_t g_unique_identifier = 0;
FrameTreeNode* GetFrameTreeNodeForPendingEntry(WebContentsImpl* contents) {
NavigationEntryImpl* pending_entry =
contents->GetController().GetPendingEntry();
int frame_tree_node_id = pending_entry->frame_tree_node_id();
FrameTree* frame_tree = contents->GetFrameTree();
if (frame_tree_node_id == FrameTreeNode::kFrameTreeNodeInvalidId)
return frame_tree->root();
return frame_tree->FindByID(frame_tree_node_id);
}
} // namespace
// static
RenderFrameHost* NavigationSimulator::NavigateAndCommitFromBrowser(
WebContents* web_contents,
const GURL& url) {
auto simulator =
NavigationSimulatorImpl::CreateBrowserInitiated(url, web_contents);
simulator->Commit();
return simulator->GetFinalRenderFrameHost();
}
// static
RenderFrameHost* NavigationSimulator::Reload(WebContents* web_contents) {
NavigationEntry* entry =
web_contents->GetController().GetLastCommittedEntry();
CHECK(entry);
auto simulator = NavigationSimulatorImpl::CreateBrowserInitiated(
entry->GetURL(), web_contents);
simulator->SetReloadType(ReloadType::NORMAL);
simulator->Commit();
return simulator->GetFinalRenderFrameHost();
}
// static
RenderFrameHost* NavigationSimulator::GoBack(WebContents* web_contents) {
return GoToOffset(web_contents, -1);
}
// static
RenderFrameHost* NavigationSimulator::GoForward(WebContents* web_contents) {
return GoToOffset(web_contents, 1);
}
// static
RenderFrameHost* NavigationSimulator::GoToOffset(WebContents* web_contents,
int offset) {
auto simulator =
NavigationSimulatorImpl::CreateHistoryNavigation(offset, web_contents);
simulator->Commit();
return simulator->GetFinalRenderFrameHost();
}
// static
RenderFrameHost* NavigationSimulator::NavigateAndCommitFromDocument(
const GURL& original_url,
RenderFrameHost* render_frame_host) {
auto simulator = NavigationSimulator::CreateRendererInitiated(
original_url, render_frame_host);
simulator->Commit();
return simulator->GetFinalRenderFrameHost();
}
// static
RenderFrameHost* NavigationSimulator::NavigateAndFailFromBrowser(
WebContents* web_contents,
const GURL& url,
int net_error_code) {
auto simulator =
NavigationSimulator::CreateBrowserInitiated(url, web_contents);
simulator->Fail(net_error_code);
if (net_error_code == net::ERR_ABORTED)
return nullptr;
simulator->CommitErrorPage();
return simulator->GetFinalRenderFrameHost();
}
// static
RenderFrameHost* NavigationSimulator::ReloadAndFail(WebContents* web_contents,
int net_error_code) {
NavigationEntry* entry =
web_contents->GetController().GetLastCommittedEntry();
CHECK(entry);
auto simulator = NavigationSimulator::CreateBrowserInitiated(entry->GetURL(),
web_contents);
simulator->SetReloadType(ReloadType::NORMAL);
simulator->Fail(net_error_code);
if (net_error_code == net::ERR_ABORTED)
return nullptr;
simulator->CommitErrorPage();
return simulator->GetFinalRenderFrameHost();
}
// static
RenderFrameHost* NavigationSimulator::GoBackAndFail(WebContents* web_contents,
int net_error_code) {
return GoToOffsetAndFail(web_contents, -1, net_error_code);
}
// static
RenderFrameHost* NavigationSimulator::GoToOffsetAndFail(
WebContents* web_contents,
int offset,
int net_error_code) {
auto simulator =
NavigationSimulator::CreateHistoryNavigation(offset, web_contents);
simulator->Fail(net_error_code);
if (net_error_code == net::ERR_ABORTED)
return nullptr;
simulator->CommitErrorPage();
return simulator->GetFinalRenderFrameHost();
}
// static
RenderFrameHost* NavigationSimulator::NavigateAndFailFromDocument(
const GURL& original_url,
int net_error_code,
RenderFrameHost* render_frame_host) {
auto simulator = NavigationSimulator::CreateRendererInitiated(
original_url, render_frame_host);
simulator->Fail(net_error_code);
if (net_error_code == net::ERR_ABORTED)
return nullptr;
simulator->CommitErrorPage();
return simulator->GetFinalRenderFrameHost();
}
// static
std::unique_ptr<NavigationSimulator>
NavigationSimulator::CreateBrowserInitiated(const GURL& original_url,
WebContents* web_contents) {
return NavigationSimulatorImpl::CreateBrowserInitiated(original_url,
web_contents);
}
// static
std::unique_ptr<NavigationSimulatorImpl>
NavigationSimulatorImpl::CreateBrowserInitiated(const GURL& original_url,
WebContents* web_contents) {
return std::unique_ptr<NavigationSimulatorImpl>(new NavigationSimulatorImpl(
original_url, true /* browser_initiated */,
static_cast<WebContentsImpl*>(web_contents), nullptr));
}
// static
std::unique_ptr<NavigationSimulator>
NavigationSimulator::CreateHistoryNavigation(int offset,
WebContents* web_contents) {
return NavigationSimulatorImpl::CreateHistoryNavigation(offset, web_contents);
}
// static
std::unique_ptr<NavigationSimulatorImpl>
NavigationSimulatorImpl::CreateHistoryNavigation(int offset,
WebContents* web_contents) {
auto simulator =
NavigationSimulatorImpl::CreateBrowserInitiated(GURL(), web_contents);
simulator->SetSessionHistoryOffset(offset);
return simulator;
}
// static
std::unique_ptr<NavigationSimulator>
NavigationSimulator::CreateRendererInitiated(
const GURL& original_url,
RenderFrameHost* render_frame_host) {
return NavigationSimulatorImpl::CreateRendererInitiated(original_url,
render_frame_host);
}
// static
std::unique_ptr<NavigationSimulatorImpl>
NavigationSimulatorImpl::CreateRendererInitiated(
const GURL& original_url,
RenderFrameHost* render_frame_host) {
return std::unique_ptr<NavigationSimulatorImpl>(new NavigationSimulatorImpl(
original_url, false /* browser_initiated */,
static_cast<WebContentsImpl*>(
WebContents::FromRenderFrameHost(render_frame_host)),
static_cast<TestRenderFrameHost*>(render_frame_host)));
}
// static
std::unique_ptr<NavigationSimulator> NavigationSimulator::CreateFromPending(
WebContents* contents) {
return NavigationSimulatorImpl::CreateFromPending(contents);
}
// static
std::unique_ptr<NavigationSimulatorImpl>
NavigationSimulatorImpl::CreateFromPending(WebContents* contents) {
WebContentsImpl* contents_impl = static_cast<WebContentsImpl*>(contents);
FrameTreeNode* frame_tree_node =
GetFrameTreeNodeForPendingEntry(contents_impl);
return NavigationSimulatorImpl::CreateFromPendingInFrame(frame_tree_node);
}
// static
std::unique_ptr<NavigationSimulatorImpl>
NavigationSimulatorImpl::CreateFromPendingInFrame(
FrameTreeNode* frame_tree_node) {
CHECK(frame_tree_node);
TestRenderFrameHost* test_frame_host =
static_cast<TestRenderFrameHost*>(frame_tree_node->current_frame_host());
CHECK(test_frame_host);
NavigationRequest* request = frame_tree_node->navigation_request();
// It is possible to not have a NavigationRequest in the frame tree node if
// it did not go to the network (such as about:blank). In that case it is
// already in the RenderFrameHost.
if (!request)
request = test_frame_host->navigation_requests().begin()->second.get();
CHECK(request);
// Simulate the BeforeUnload completion callback if needed.
if (request->state() == NavigationRequest::WAITING_FOR_RENDERER_RESPONSE)
test_frame_host->SimulateBeforeUnloadCompleted(true /* proceed */);
auto simulator = base::WrapUnique(new NavigationSimulatorImpl(
GURL(), request->browser_initiated(),
WebContentsImpl::FromFrameTreeNode(frame_tree_node), test_frame_host));
simulator->frame_tree_node_ = frame_tree_node;
simulator->InitializeFromStartedRequest(request);
return simulator;
}
NavigationSimulatorImpl::NavigationSimulatorImpl(
const GURL& original_url,
bool browser_initiated,
WebContentsImpl* web_contents,
TestRenderFrameHost* render_frame_host)
: WebContentsObserver(web_contents),
web_contents_(web_contents),
render_frame_host_(render_frame_host),
frame_tree_node_(render_frame_host
? render_frame_host->frame_tree_node()
: web_contents->GetMainFrame()->frame_tree_node()),
request_(nullptr),
original_url_(original_url),
navigation_url_(original_url),
initial_method_("GET"),
browser_initiated_(browser_initiated),
referrer_(blink::mojom::Referrer::New()),
transition_(browser_initiated ? ui::PAGE_TRANSITION_TYPED
: ui::PAGE_TRANSITION_LINK),
contents_mime_type_("text/html"),
load_url_params_(nullptr) {
net::IPAddress address;
CHECK(address.AssignFromIPLiteral("2001:db8::1"));
remote_endpoint_ = net::IPEndPoint(address, 80);
// For renderer-initiated navigation, the RenderFrame must be initialized. Do
// it if it hasn't happened yet.
if (!browser_initiated)
render_frame_host->InitializeRenderFrameIfNeeded();
if (render_frame_host && render_frame_host->GetParent()) {
if (!render_frame_host->frame_tree_node()->has_committed_real_load())
transition_ = ui::PAGE_TRANSITION_AUTO_SUBFRAME;
else
transition_ = ui::PAGE_TRANSITION_MANUAL_SUBFRAME;
}
browser_interface_broker_receiver_ =
mojo::PendingRemote<blink::mojom::BrowserInterfaceBroker>()
.InitWithNewPipeAndPassReceiver();
}
NavigationSimulatorImpl::~NavigationSimulatorImpl() {}
void NavigationSimulatorImpl::InitializeFromStartedRequest(
NavigationRequest* request) {
CHECK(request);
request_ = request;
CHECK(request_->IsNavigationStarted());
CHECK_EQ(web_contents_, request_->GetDelegate());
CHECK(render_frame_host_);
CHECK_EQ(frame_tree_node_, request_->frame_tree_node());
state_ = STARTED;
original_url_ = request->commit_params().original_url;
navigation_url_ = request_->GetURL();
// |remote_endpoint_| cannot be inferred from the request.
// |initial_method_| cannot be set after the request has started.
browser_initiated_ = request_->browser_initiated();
// |same_document_| should always be false here.
referrer_ = request_->common_params().referrer.Clone();
transition_ = request_->GetPageTransition();
// |reload_type_| cannot be set after the request has started.
// |session_history_offset_| cannot be set after the request has started.
has_user_gesture_ = request_->HasUserGesture();
// |contents_mime_type_| cannot be inferred from the request.
// Add a throttle to count NavigationThrottle calls count. Bump
// num_did_start_navigation to account for the fact that the navigation handle
// has already been created.
num_did_start_navigation_called_++;
RegisterTestThrottle(request);
PrepareCompleteCallbackOnRequest();
}
void NavigationSimulatorImpl::RegisterTestThrottle(NavigationRequest* request) {
request->RegisterThrottleForTesting(
std::make_unique<NavigationThrottleCallbackRunner>(
request,
base::BindOnce(&NavigationSimulatorImpl::OnWillStartRequest,
weak_factory_.GetWeakPtr()),
base::BindRepeating(&NavigationSimulatorImpl::OnWillRedirectRequest,
weak_factory_.GetWeakPtr()),
base::BindOnce(&NavigationSimulatorImpl::OnWillFailRequest,
weak_factory_.GetWeakPtr()),
base::BindOnce(&NavigationSimulatorImpl::OnWillProcessResponse,
weak_factory_.GetWeakPtr())));
}
void NavigationSimulatorImpl::Start() {
CHECK(state_ == INITIALIZATION || state_ == WAITING_BEFORE_UNLOAD)
<< "NavigationSimulatorImpl::Start should only be called once.";
if (browser_initiated_) {
if (!SimulateBrowserInitiatedStart())
return;
} else {
if (!SimulateRendererInitiatedStart())
return;
}
state_ = STARTED;
CHECK(request_);
if (IsRendererDebugURL(navigation_url_))
return;
if (same_document_ || !IsURLHandledByNetworkStack(navigation_url_) ||
navigation_url_.IsAboutBlank()) {
CHECK_EQ(1, num_did_start_navigation_called_);
return;
}
MaybeWaitForThrottleChecksComplete(base::BindOnce(
&NavigationSimulatorImpl::StartComplete, weak_factory_.GetWeakPtr()));
}
void NavigationSimulatorImpl::StartComplete() {
CHECK_EQ(1, num_did_start_navigation_called_);
if (GetLastThrottleCheckResult().action() == NavigationThrottle::PROCEED) {
CHECK_EQ(1, num_will_start_request_called_);
} else {
state_ = FAILED;
}
}
void NavigationSimulatorImpl::Redirect(const GURL& new_url) {
CHECK_LE(state_, STARTED) << "NavigationSimulatorImpl::Redirect should be "
"called before Fail or Commit";
CHECK_EQ(0, num_did_finish_navigation_called_)
<< "NavigationSimulatorImpl::Redirect cannot be called after the "
"navigation has finished";
if (state_ < STARTED) {
Start();
if (state_ == FAILED)
return;
}
navigation_url_ = new_url;
int previous_num_will_redirect_request_called =
num_will_redirect_request_called_;
int previous_did_redirect_navigation_called =
num_did_redirect_navigation_called_;
PrepareCompleteCallbackOnRequest();
NavigationRequest* request = frame_tree_node_->navigation_request();
CHECK(request) << "Trying to redirect a navigation that does not go to the "
"network stack.";
TestNavigationURLLoader* url_loader =
static_cast<TestNavigationURLLoader*>(request->loader_for_testing());
CHECK(url_loader);
net::RedirectInfo redirect_info;
redirect_info.status_code = 302;
redirect_info.new_method = "GET";
redirect_info.new_url = new_url;
redirect_info.new_site_for_cookies = net::SiteForCookies::FromUrl(new_url);
redirect_info.new_referrer = referrer_->url.spec();
redirect_info.new_referrer_policy =
Referrer::ReferrerPolicyForUrlRequest(referrer_->policy);
auto response = network::mojom::URLResponseHead::New();
response->connection_info = http_connection_info_;
response->ssl_info = ssl_info_;
url_loader->CallOnRequestRedirected(redirect_info, std::move(response));
MaybeWaitForThrottleChecksComplete(base::BindOnce(
&NavigationSimulatorImpl::RedirectComplete, weak_factory_.GetWeakPtr(),
previous_num_will_redirect_request_called,
previous_did_redirect_navigation_called));
}
void NavigationSimulatorImpl::RedirectComplete(
int previous_num_will_redirect_request_called,
int previous_did_redirect_navigation_called) {
if (GetLastThrottleCheckResult().action() == NavigationThrottle::PROCEED) {
CHECK_EQ(previous_num_will_redirect_request_called + 1,
num_will_redirect_request_called_);
CHECK_EQ(previous_did_redirect_navigation_called + 1,
num_did_redirect_navigation_called_);
} else {
state_ = FAILED;
}
}
void NavigationSimulatorImpl::ReadyToCommit() {
CHECK_LE(state_, STARTED)
<< "NavigationSimulatorImpl::ReadyToCommit can only "
"be called once, and cannot be called after "
"NavigationSimulatorImpl::Fail";
CHECK_EQ(0, num_did_finish_navigation_called_)
<< "NavigationSimulatorImpl::ReadyToCommit cannot be called after the "
"navigation has finished";
if (state_ < STARTED) {
if (block_invoking_before_unload_completed_callback_ &&
state_ == WAITING_BEFORE_UNLOAD) {
// The user should have simulated the BeforeUnloadCompleted by themselves.
// Finish the initialization and skip the Start simulation.
InitializeFromStartedRequest(request_);
} else {
Start();
if (state_ == FAILED)
return;
}
}
if (!response_headers_) {
response_headers_ =
base::MakeRefCounted<net::HttpResponseHeaders>(std::string());
}
response_headers_->SetHeader("Content-Type", contents_mime_type_);
PrepareCompleteCallbackOnRequest();
if (frame_tree_node_->navigation_request()) {
static_cast<TestRenderFrameHost*>(frame_tree_node_->current_frame_host())
->PrepareForCommitDeprecatedForNavigationSimulator(
remote_endpoint_, was_fetched_via_cache_,
is_signed_exchange_inner_response_, http_connection_info_,
ssl_info_, response_headers_);
}
// Synchronous failure can cause the navigation to finish here.
if (!request_) {
state_ = FAILED;
return;
}
bool needs_throttle_checks = !same_document_ &&
!navigation_url_.IsAboutBlank() &&
IsURLHandledByNetworkStack(navigation_url_);
auto complete_closure =
base::BindOnce(&NavigationSimulatorImpl::ReadyToCommitComplete,
weak_factory_.GetWeakPtr(), needs_throttle_checks);
if (needs_throttle_checks) {
MaybeWaitForThrottleChecksComplete(std::move(complete_closure));
return;
}
std::move(complete_closure).Run();
}
void NavigationSimulatorImpl::ReadyToCommitComplete(bool ran_throttles) {
if (ran_throttles) {
if (GetLastThrottleCheckResult().action() != NavigationThrottle::PROCEED) {
state_ = FAILED;
return;
}
CHECK_EQ(1, num_will_process_response_called_);
CHECK_EQ(1, num_ready_to_commit_called_);
}
request_id_ = request_->GetGlobalRequestID();
// Update the RenderFrameHost now that we know which RenderFrameHost will
// commit the navigation.
render_frame_host_ =
static_cast<TestRenderFrameHost*>(request_->GetRenderFrameHost());
state_ = READY_TO_COMMIT;
}
void NavigationSimulatorImpl::Commit() {
CHECK_LE(state_, READY_TO_COMMIT)
<< "NavigationSimulatorImpl::Commit can only "
"be called once, and cannot be called "
"after NavigationSimulatorImpl::Fail";
CHECK_EQ(0, num_did_finish_navigation_called_)
<< "NavigationSimulatorImpl::Commit cannot be called after the "
"navigation "
"has finished";
if (state_ < READY_TO_COMMIT) {
ReadyToCommit();
if (state_ == FAILED || state_ == FINISHED)
return;
}
// Keep a pointer to the current RenderFrameHost that may be pending deletion
// after commit.
RenderFrameHostImpl* previous_rfh =
render_frame_host_->frame_tree_node()->current_frame_host();
// RenderDocument: Do not dispatch UnloadACK if the navigation was committed
// in the same SiteInstance. This has already been dispatched during the
// navigation in the renderer process.
if (previous_rfh->GetSiteInstance() == render_frame_host_->GetSiteInstance())
drop_unload_ack_ = true;
// If the frame is not alive we do not displatch Unload ACK. CommitPending()
// may be called immediately and delete the old RenderFrameHost, so we need to
// record that now while we can still access the object.
if (!previous_rfh->IsRenderFrameLive())
drop_unload_ack_ = true;
if (same_document_) {
browser_interface_broker_receiver_.reset();
}
auto params = BuildDidCommitProvisionalLoadParams(
same_document_ /* same_document */, false /* failed_navigation */,
render_frame_host_->last_http_status_code());
render_frame_host_->SimulateCommitProcessed(
request_, std::move(params),
std::move(browser_interface_broker_receiver_), same_document_);
if (previous_rfh)
SimulateUnloadCompletionCallbackForPreviousFrameIfNeeded(previous_rfh);
loading_scenario_ =
TestRenderFrameHost::LoadingScenario::NewDocumentNavigation;
state_ = FINISHED;
if (!keep_loading_)
StopLoading();
if (!IsRendererDebugURL(navigation_url_))
CHECK_EQ(1, num_did_finish_navigation_called_);
}
void NavigationSimulatorImpl::AbortCommit() {
CHECK_LE(state_, FAILED)
<< "NavigationSimulatorImpl::AbortCommit cannot be called after "
"NavigationSimulatorImpl::Commit or "
"NavigationSimulatorImpl::CommitErrorPage.";
if (state_ < READY_TO_COMMIT) {
ReadyToCommit();
if (state_ == FINISHED)
return;
}
CHECK(render_frame_host_)
<< "NavigationSimulatorImpl::AbortCommit can only be "
"called for navigations that commit.";
render_frame_host_->AbortCommit(request_);
state_ = FINISHED;
StopLoading();
CHECK_EQ(1, num_did_finish_navigation_called_);
}
void NavigationSimulatorImpl::AbortFromRenderer() {
CHECK(!browser_initiated_)
<< "NavigationSimulatorImpl::AbortFromRenderer cannot be called for "
"browser-initiated navigation.";
CHECK_LE(state_, FAILED)
<< "NavigationSimulatorImpl::AbortFromRenderer cannot be called after "
"NavigationSimulatorImpl::Commit or "
"NavigationSimulatorImpl::CommitErrorPage.";
was_aborted_ = true;
request_->RendererAbortedNavigationForTesting();
state_ = FINISHED;
CHECK_EQ(1, num_did_finish_navigation_called_);
}
void NavigationSimulatorImpl::Fail(int error_code) {
CHECK_LE(state_, STARTED) << "NavigationSimulatorImpl::Fail can only be "
"called once, and cannot be called after "
"NavigationSimulatorImpl::ReadyToCommit";
CHECK_EQ(0, num_did_finish_navigation_called_)
<< "NavigationSimulatorImpl::Fail cannot be called after the "
"navigation has finished";
CHECK(!IsRendererDebugURL(navigation_url_));
if (state_ == INITIALIZATION)
Start();
state_ = FAILED;
PrepareCompleteCallbackOnRequest();
CHECK(request_);
TestNavigationURLLoader* url_loader =
static_cast<TestNavigationURLLoader*>(request_->loader_for_testing());
CHECK(url_loader);
network::URLLoaderCompletionStatus status(error_code);
status.resolve_error_info = resolve_error_info_;
status.ssl_info = ssl_info_;
url_loader->SimulateErrorWithStatus(status);
auto complete_closure =
base::BindOnce(&NavigationSimulatorImpl::FailComplete,
weak_factory_.GetWeakPtr(), error_code);
if (error_code != net::ERR_ABORTED) {
MaybeWaitForThrottleChecksComplete(std::move(complete_closure));
return;
}
std::move(complete_closure).Run();
}
void NavigationSimulatorImpl::FailComplete(int error_code) {
bool should_result_in_error_page = error_code != net::ERR_ABORTED;
if (error_code != net::ERR_ABORTED) {
NavigationThrottle::ThrottleCheckResult result =
GetLastThrottleCheckResult();
if (result.action() == NavigationThrottle::CANCEL ||
result.action() == NavigationThrottle::CANCEL_AND_IGNORE) {
should_result_in_error_page = false;
}
}
if (should_result_in_error_page) {
// TODO(clamy): Check that ReadyToCommit has been called once, once the test
// architecture of NavigationRequest vs NavigationHandle has been clarified.
// Currently, when auto-advance is off, this function will be called before
// NavigationRequest::CommitErrorPage which is the one that triggers the
// call to observers.
CHECK_EQ(0, num_did_finish_navigation_called_);
// Update the RenderFrameHost now that we know which RenderFrameHost will
// commit the error page.
render_frame_host_ =
static_cast<TestRenderFrameHost*>(request_->GetRenderFrameHost());
}
}
void NavigationSimulatorImpl::CommitErrorPage() {
CHECK_EQ(FAILED, state_)
<< "NavigationSimulatorImpl::CommitErrorPage can only be "
"called once, and should be called after Fail "
"has been called";
CHECK_EQ(0, num_did_finish_navigation_called_)
<< "NavigationSimulatorImpl::CommitErrorPage cannot be called after the "
"navigation has finished";
// Keep a pointer to the current RenderFrameHost that may be pending deletion
// after commit.
// RenderDocument: The |previous_rfh| might also be immediately deleted after
// commit, because it has already run its unload handler.
RenderFrameHostImpl* previous_rfh =
render_frame_host_->frame_tree_node()->current_frame_host();
// RenderDocument: Do not dispatch UnloadACK if the navigation was committed
// in the same SiteInstance. This has already been dispatched during the
// navigation in the renderer process.
if (previous_rfh->GetSiteInstance() == render_frame_host_->GetSiteInstance())
drop_unload_ack_ = true;
// If the frame is not alive we do not displatch Unload ACK. CommitPending()
// may be called immediately and delete the old RenderFrameHost, so we need to
// record that now while we can still access the object.
if (!previous_rfh->IsRenderFrameLive())
drop_unload_ack_ = true;
auto params = BuildDidCommitProvisionalLoadParams(
false /* same_document */, true /* failed_navigation */,
render_frame_host_->last_http_status_code());
render_frame_host_->SimulateCommitProcessed(
request_, std::move(params),
std::move(browser_interface_broker_receiver_), false /* same_document */);
SimulateUnloadCompletionCallbackForPreviousFrameIfNeeded(previous_rfh);
state_ = FINISHED;
if (!keep_loading_)
StopLoading();
CHECK_EQ(1, num_did_finish_navigation_called_);
}
void NavigationSimulatorImpl::CommitSameDocument() {
if (!browser_initiated_) {
CHECK_EQ(INITIALIZATION, state_)
<< "NavigationSimulatorImpl::CommitSameDocument should be the only "
"navigation event function called on the NavigationSimulatorImpl";
} else {
// This function is intended for same document navigations initiating from
// the renderer. For regular same document navigations simply use Commit().
Commit();
return;
}
auto params = BuildDidCommitProvisionalLoadParams(
true /* same_document */, false /* failed_navigation */,
render_frame_host_->last_http_status_code());
browser_interface_broker_receiver_.reset();
render_frame_host_->SimulateCommitProcessed(
request_, std::move(params),
mojo::NullReceiver() /* browser_interface_broker_receiver */,
true /* same_document */);
// Same-document commits should never hit network-related stages of committing
// a navigation.
CHECK_EQ(0, num_will_start_request_called_);
CHECK_EQ(0, num_will_process_response_called_);
CHECK_EQ(0, num_ready_to_commit_called_);
if (num_did_finish_navigation_called_ == 0) {
// Fail the navigation if it results in a process kill (e.g. see
// NavigatorTestWithBrowserSideNavigation.CrossSiteClaimWithinPage test).
state_ = FAILED;
return;
}
loading_scenario_ =
TestRenderFrameHost::LoadingScenario::kSameDocumentNavigation;
state_ = FINISHED;
if (!keep_loading_)
StopLoading();
CHECK_EQ(1, num_did_start_navigation_called_);
CHECK_EQ(1, num_did_finish_navigation_called_);
}
void NavigationSimulatorImpl::SetInitiatorFrame(
RenderFrameHost* initiator_frame_host) {
// Browser-initiated navigations are not associated with an initiator frame.
CHECK(!browser_initiated_);
CHECK(initiator_frame_host);
// TODO(https://crbug.com/1072790): Support cross-process initiators here by
// using NavigationRequest::CreateBrowserInitiated() (like
// RenderFrameProxyHost does) for the navigation.
CHECK_EQ(render_frame_host_->GetProcess(), initiator_frame_host->GetProcess())
<< "The initiator frame must belong to the same process as the frame you "
"are navigating";
initiator_frame_host_ = initiator_frame_host;
}
void NavigationSimulatorImpl::SetTransition(ui::PageTransition transition) {
if (frame_tree_node_ && !frame_tree_node_->IsMainFrame()) {
// Subframe case. The subframe page transition is only set at commit time in
// the navigation code, so it can be modified later in time.
CHECK(PageTransitionCoreTypeIs(transition,
ui::PAGE_TRANSITION_AUTO_SUBFRAME) ||
PageTransitionCoreTypeIs(transition,
ui::PAGE_TRANSITION_MANUAL_SUBFRAME))
<< "The transition type is not appropriate for a subframe";
} else {
CHECK_EQ(INITIALIZATION, state_)
<< "The transition cannot be set after the navigation has started";
CHECK_EQ(ReloadType::NONE, reload_type_)
<< "The transition cannot be specified for reloads";
CHECK_EQ(0, session_history_offset_)
<< "The transition cannot be specified for back/forward navigations";
}
transition_ = transition;
}
void NavigationSimulatorImpl::SetHasUserGesture(bool has_user_gesture) {
CHECK_EQ(INITIALIZATION, state_) << "The has_user_gesture parameter cannot "
"be set after the navigation has started";
has_user_gesture_ = has_user_gesture;
}
void NavigationSimulatorImpl::SetReloadType(ReloadType reload_type) {
CHECK_EQ(INITIALIZATION, state_) << "The reload_type parameter cannot "
"be set after the navigation has started";
CHECK(browser_initiated_) << "The reload_type parameter can only be set for "
"browser-intiated navigations";
CHECK_EQ(0, session_history_offset_)
<< "The reload_type parameter cannot be set for "
"session history navigations";
reload_type_ = reload_type;
if (reload_type_ != ReloadType::NONE)
transition_ = ui::PAGE_TRANSITION_RELOAD;
}
void NavigationSimulatorImpl::SetMethod(const std::string& method) {
CHECK_EQ(INITIALIZATION, state_) << "The method parameter cannot "
"be set after the navigation has started";
initial_method_ = method;
}
void NavigationSimulatorImpl::SetIsFormSubmission(bool is_form_submission) {
CHECK_EQ(INITIALIZATION, state_) << "The form submission parameter cannot "
"be set after the navigation has started";
is_form_submission_ = is_form_submission;
}
void NavigationSimulatorImpl::SetReferrer(blink::mojom::ReferrerPtr referrer) {
CHECK_LE(state_, STARTED) << "The referrer cannot be set after the "
"navigation has committed or has failed";
referrer_ = std::move(referrer);
}
void NavigationSimulatorImpl::SetSocketAddress(
const net::IPEndPoint& remote_endpoint) {
CHECK_LE(state_, STARTED) << "The socket address cannot be set after the "
"navigation has committed or failed";
remote_endpoint_ = remote_endpoint;
}
void NavigationSimulatorImpl::SetWasFetchedViaCache(
bool was_fetched_via_cache) {
CHECK_LE(state_, STARTED) << "The was_fetched_via_cache flag cannot be set "
"after the navigation has committed or failed";
was_fetched_via_cache_ = was_fetched_via_cache;
}
void NavigationSimulatorImpl::SetIsSignedExchangeInnerResponse(
bool is_signed_exchange_inner_response) {
CHECK_LE(state_, STARTED) << "The signed exchange flag cannot be set after "
"the navigation has committed or failed";
is_signed_exchange_inner_response_ = is_signed_exchange_inner_response;
}
void NavigationSimulatorImpl::SetFeaturePolicyHeader(
blink::ParsedFeaturePolicy feature_policy_header) {
CHECK_LE(state_, STARTED) << "The Feature-Policy headers cannot be set after "
"the navigation has committed or failed";
feature_policy_header_ = std::move(feature_policy_header);
}
void NavigationSimulatorImpl::SetContentsMimeType(
const std::string& contents_mime_type) {
CHECK_LE(state_, STARTED) << "The contents mime type cannot be set after the "
"navigation has committed or failed";
contents_mime_type_ = contents_mime_type;
}
void NavigationSimulatorImpl::SetResponseHeaders(
scoped_refptr<net::HttpResponseHeaders> response_headers) {
CHECK_LE(state_, STARTED) << "The response headers cannot be set after the "
"navigation has committed or failed";
response_headers_ = response_headers;
}
void NavigationSimulatorImpl::SetLoadURLParams(
NavigationController::LoadURLParams* load_url_params) {
load_url_params_ = load_url_params;
// Make sure the internal attributes of NavigationSimulatorImpl match the
// LoadURLParams that is going to be sent.
referrer_ = blink::mojom::Referrer::New(load_url_params->referrer.url,
load_url_params->referrer.policy);
transition_ = load_url_params->transition_type;
}
void NavigationSimulatorImpl::SetAutoAdvance(bool auto_advance) {
auto_advance_ = auto_advance;
}
void NavigationSimulatorImpl::SetResolveErrorInfo(
const net::ResolveErrorInfo& resolve_error_info) {
resolve_error_info_ = resolve_error_info;
}
void NavigationSimulatorImpl::SetSSLInfo(const net::SSLInfo& ssl_info) {
ssl_info_ = ssl_info;
}
NavigationThrottle::ThrottleCheckResult
NavigationSimulatorImpl::GetLastThrottleCheckResult() {
return last_throttle_check_result_.value();
}
NavigationRequest* NavigationSimulatorImpl::GetNavigationHandle() {
CHECK_GE(state_, STARTED);
return request_;
}
content::GlobalRequestID NavigationSimulatorImpl::GetGlobalRequestID() {
CHECK_GT(state_, STARTED) << "The GlobalRequestID is not available until "
"after the navigation has completed "
"WillProcessResponse";
return request_id_;
}
void NavigationSimulatorImpl::BrowserInitiatedStartAndWaitBeforeUnload() {
if (reload_type_ != ReloadType::NONE) {
web_contents_->GetController().Reload(reload_type_,
false /*check_for_repost */);
} else if (session_history_offset_) {
web_contents_->GetController().GoToOffset(session_history_offset_);
} else {
if (load_url_params_) {
web_contents_->GetController().LoadURLWithParams(*load_url_params_);
load_url_params_ = nullptr;
} else {
NavigationController::LoadURLParams load_url_params(navigation_url_);
load_url_params.referrer = Referrer(*referrer_);
load_url_params.transition_type = transition_;
if (initial_method_ == "POST")
load_url_params.load_type = NavigationController::LOAD_TYPE_HTTP_POST;
web_contents_->GetController().LoadURLWithParams(load_url_params);
}
}
frame_tree_node_ = GetFrameTreeNodeForPendingEntry(web_contents_);
CHECK(frame_tree_node_);
render_frame_host_ =
static_cast<TestRenderFrameHost*>(frame_tree_node_->current_frame_host());
// The navigation url might have been rewritten by the NavigationController.
// Update it.
NavigationController& controller = web_contents_->GetController();
NavigationEntryImpl* pending_entry =
static_cast<NavigationEntryImpl*>(controller.GetPendingEntry());
FrameNavigationEntry* pending_frame_entry =
pending_entry->GetFrameEntry(frame_tree_node_);
navigation_url_ = pending_frame_entry->url();
state_ = WAITING_BEFORE_UNLOAD;
}
void NavigationSimulatorImpl::DidStartNavigation(
NavigationHandle* navigation_handle) {
// Check if this navigation is the one we're simulating.
if (request_)
return;
NavigationRequest* request = NavigationRequest::From(navigation_handle);
if (request->frame_tree_node() != frame_tree_node_)
return;
request_ = request;
num_did_start_navigation_called_++;
// Add a throttle to count NavigationThrottle calls count.
RegisterTestThrottle(request);
PrepareCompleteCallbackOnRequest();
}
void NavigationSimulatorImpl::DidRedirectNavigation(
NavigationHandle* navigation_handle) {
if (request_ == navigation_handle)
num_did_redirect_navigation_called_++;
}
void NavigationSimulatorImpl::ReadyToCommitNavigation(
NavigationHandle* navigation_handle) {
if (request_ && navigation_handle == request_)
num_ready_to_commit_called_++;
}
void NavigationSimulatorImpl::DidFinishNavigation(
NavigationHandle* navigation_handle) {
NavigationRequest* request = NavigationRequest::From(navigation_handle);
if (request == request_) {
num_did_finish_navigation_called_++;
if (navigation_handle->IsServedFromBackForwardCache()) {
// Back-forward cache navigations commit and finish synchronously, unlike
// all other navigations, which wait for a reply from the renderer.
// The |state_| is normally updated to 'FINISHED' when we simulate a
// renderer reply at the end of the NavigationSimulatorImpl::Commit()
// function, but we have not reached this stage yet.
// Set |state_| to FINISHED to ensure that we would not try to simulate
// navigation commit for the second time.
RenderFrameHostImpl* previous_rfh = RenderFrameHostImpl::FromID(
navigation_handle->GetPreviousRenderFrameHostId());
CHECK(previous_rfh) << "Previous RenderFrameHost should not be destroyed "
"without a Unload_ACK";
// If the frame is not alive we do not displatch Unload ACK.
// CommitPending() may be called immediately and delete the old
// RenderFrameHost, so we need to record that now while we can still
// access the object.
if (!previous_rfh->IsRenderFrameLive())
drop_unload_ack_ = true;
SimulateUnloadCompletionCallbackForPreviousFrameIfNeeded(previous_rfh);
state_ = FINISHED;
}
request_ = nullptr;
if (was_aborted_)
CHECK_EQ(net::ERR_ABORTED, request->GetNetErrorCode());
}
}
void NavigationSimulatorImpl::OnWillStartRequest() {
num_will_start_request_called_++;
}
void NavigationSimulatorImpl::OnWillRedirectRequest() {
num_will_redirect_request_called_++;
}
void NavigationSimulatorImpl::OnWillFailRequest() {
num_will_fail_request_called_++;
}
void NavigationSimulatorImpl::OnWillProcessResponse() {
num_will_process_response_called_++;
}
bool NavigationSimulatorImpl::SimulateBrowserInitiatedStart() {
if (state_ == INITIALIZATION)
BrowserInitiatedStartAndWaitBeforeUnload();
// Simulate the BeforeUnload completion callback if needed.
NavigationRequest* request = frame_tree_node_->navigation_request();
if (request &&
request->state() == NavigationRequest::WAITING_FOR_RENDERER_RESPONSE) {
if (block_invoking_before_unload_completed_callback_) {
// Since we do not simulate the BeforeUnloadCompleted, DidStartNavigation
// will not have been called, and |request_| will not be properly set. Do
// it manually.
request_ = request;
return false;
}
render_frame_host_->SimulateBeforeUnloadCompleted(true /* proceed */);
}
// Note: WillStartRequest checks can destroy the request synchronously, or
// this can be a navigation that doesn't need a network request and that was
// passed directly to a RenderFrameHost for commit.
request =
web_contents_->GetMainFrame()->frame_tree_node()->navigation_request();
if (!request) {
if (IsRendererDebugURL(navigation_url_)) {
// We don't create NavigationRequests nor NavigationHandles for a
// navigation to a renderer-debug URL. Instead, the URL is passed to the
// current RenderFrameHost so that the renderer process can handle it.
CHECK(!request_);
CHECK(web_contents_->GetMainFrame()->is_loading());
// A navigation to a renderer-debug URL cannot commit. Simulate the
// renderer process aborting it.
render_frame_host_ =
static_cast<TestRenderFrameHost*>(web_contents_->GetMainFrame());
StopLoading();
state_ = FAILED;
return false;
} else if (request_ &&
web_contents_->GetMainFrame()->GetSameDocumentNavigationRequest(
request_->commit_params().navigation_token)) {
CHECK(request_->IsSameDocument());
same_document_ = true;
return true;
}
return false;
}
CHECK_EQ(request_, request);
return true;
}
bool NavigationSimulatorImpl::SimulateRendererInitiatedStart() {
mojom::BeginNavigationParamsPtr begin_params =
mojom::BeginNavigationParams::New(
initiator_frame_host_
? base::make_optional(initiator_frame_host_->GetFrameToken())
: base::nullopt,
std::string() /* headers */, net::LOAD_NORMAL,
false /* skip_service_worker */,
blink::mojom::RequestContextType::HYPERLINK,
network::mojom::RequestDestination::kDocument,
blink::WebMixedContentContextType::kBlockable, is_form_submission_,
false /* was_initiated_by_link_click */,
GURL() /* searchable_form_url */,
std::string() /* searchable_form_encoding */,
GURL() /* client_side_redirect_url */,
base::nullopt /* detools_initiator_info */,
nullptr /* trust_token_params */, impression_,
base::TimeTicks() /* renderer_before_unload_start */,
base::TimeTicks() /* renderer_before_unload_end */);
auto common_params = CreateCommonNavigationParams();
common_params->navigation_start = base::TimeTicks::Now();
common_params->url = navigation_url_;
common_params->initiator_origin = url::Origin();
common_params->method = initial_method_;
common_params->referrer = referrer_.Clone();
common_params->transition = transition_;
common_params->navigation_type =
PageTransitionCoreTypeIs(transition_, ui::PAGE_TRANSITION_RELOAD)
? mojom::NavigationType::RELOAD
: mojom::NavigationType::DIFFERENT_DOCUMENT;
common_params->has_user_gesture = has_user_gesture_;
common_params->initiator_csp_info = mojom::InitiatorCSPInfo::New(
should_check_main_world_csp_,
std::vector<network::mojom::ContentSecurityPolicyPtr>());
mojo::PendingAssociatedRemote<mojom::NavigationClient>
navigation_client_remote;
navigation_client_receiver_ =
navigation_client_remote.InitWithNewEndpointAndPassReceiver();
render_frame_host_->frame_host_receiver_for_testing().impl()->BeginNavigation(
std::move(common_params), std::move(begin_params), mojo::NullRemote(),
std::move(navigation_client_remote), mojo::NullRemote());
NavigationRequest* request =
render_frame_host_->frame_tree_node()->navigation_request();
// The request failed synchronously.
if (!request)
return false;
CHECK_EQ(request_, request);
return true;
}
void NavigationSimulatorImpl::MaybeWaitForThrottleChecksComplete(
base::OnceClosure complete_closure) {
// If last_throttle_check_result_ is set, then throttle checks completed
// synchronously.
if (last_throttle_check_result_) {
std::move(complete_closure).Run();
return;
}
throttle_checks_complete_closure_ = std::move(complete_closure);
if (auto_advance_)
Wait();
}
void NavigationSimulatorImpl::Wait() {
CHECK(!wait_closure_);
if (!IsDeferred())
return;
base::RunLoop run_loop;
wait_closure_ = run_loop.QuitClosure();
run_loop.Run();
}
bool NavigationSimulatorImpl::OnThrottleChecksComplete(
NavigationThrottle::ThrottleCheckResult result) {
CHECK(!last_throttle_check_result_);
last_throttle_check_result_ = result;
if (wait_closure_)
std::move(wait_closure_).Run();
if (throttle_checks_complete_closure_)
std::move(throttle_checks_complete_closure_).Run();
return false;
}
void NavigationSimulatorImpl::PrepareCompleteCallbackOnRequest() {
last_throttle_check_result_.reset();
request_->set_complete_callback_for_testing(
base::BindOnce(&NavigationSimulatorImpl::OnThrottleChecksComplete,
base::Unretained(this)));
}
RenderFrameHost* NavigationSimulatorImpl::GetFinalRenderFrameHost() {
CHECK_GE(state_, READY_TO_COMMIT);
return render_frame_host_;
}
bool NavigationSimulatorImpl::IsDeferred() {
return !throttle_checks_complete_closure_.is_null();
}
bool NavigationSimulatorImpl::DidCreateNewEntry() {
if (did_create_new_entry_.has_value())
return did_create_new_entry_.value();
if (ui::PageTransitionCoreTypeIs(transition_,
ui::PAGE_TRANSITION_AUTO_SUBFRAME))
return false;
if (reload_type_ != ReloadType::NONE ||
(request_ && NavigationTypeUtils::IsReload(
request_->common_params().navigation_type))) {
return false;
}
if (session_history_offset_ ||
(request_ && NavigationTypeUtils::IsHistory(
request_->common_params().navigation_type))) {
return false;
}
if (request_ && (request_->common_params().navigation_type ==
mojom::NavigationType::RESTORE ||
request_->common_params().navigation_type ==
mojom::NavigationType::RESTORE_WITH_POST)) {
return false;
}
return true;
}
void NavigationSimulatorImpl::SetSessionHistoryOffset(
int session_history_offset) {
CHECK(session_history_offset);
session_history_offset_ = session_history_offset;
transition_ =
ui::PageTransitionFromInt(transition_ | ui::PAGE_TRANSITION_FORWARD_BACK);
}
void NavigationSimulatorImpl::set_did_create_new_entry(
bool did_create_new_entry) {
did_create_new_entry_ = did_create_new_entry;
}
void NavigationSimulatorImpl::set_history_list_was_cleared(
bool history_cleared) {
history_list_was_cleared_ = history_cleared;
}
mojom::DidCommitProvisionalLoadParamsPtr
NavigationSimulatorImpl::BuildDidCommitProvisionalLoadParams(
bool same_document,
bool failed_navigation,
int last_http_status_code) {
auto params = mojom::DidCommitProvisionalLoadParams::New();
params->url = navigation_url_;
params->original_request_url = original_url_;
params->referrer = mojo::Clone(referrer_);
params->contents_mime_type = contents_mime_type_;
params->transition = transition_;
params->gesture =
has_user_gesture_ ? NavigationGestureUser : NavigationGestureAuto;
params->history_list_was_cleared = history_list_was_cleared_;
params->did_create_new_entry = DidCreateNewEntry();
params->should_replace_current_entry = should_replace_current_entry_;
params->navigation_token = request_
? request_->commit_params().navigation_token
: base::UnguessableToken::Create();
params->post_id = post_id_;
params->intended_as_new_entry =
request_ ? request_->commit_params().intended_as_new_entry : false;
params->method = request_ ? request_->common_params().method : "GET";
RenderFrameHostImpl* current_rfh = frame_tree_node_->current_frame_host();
if (failed_navigation) {
// Note: Error pages must commit in a unique origin. So it is left unset.
params->url_is_unreachable = true;
} else {
params->redirects.push_back(navigation_url_);
params->should_update_history = true;
if (same_document) {
params->sandbox_flags = current_rfh->active_sandbox_flags();
params->origin = current_rfh->GetLastCommittedOrigin();
} else {
params->sandbox_flags = request_->SandboxFlagsToCommit();
params->origin =
origin_.value_or(request_->GetOriginForURLLoaderFactory());
}
}
if (same_document) {
// Same document navigations always retain the last HTTP status code.
params->http_status_code = last_http_status_code;
} else if (request_ && request_->commit_params().http_response_code != -1) {
// If we have a valid HTTP response code in |request_|, use it.
params->http_status_code = request_->commit_params().http_response_code;
} else {
// Otherwise, unit tests will never issue real network requests and thus
// will never receive any HTTP response.
params->http_status_code = 0;
}
CHECK(same_document || request_);
params->feature_policy_header = std::move(feature_policy_header_);
// Simulate Blink assigning a item sequence number and document sequence
// number to the navigation.
params->item_sequence_number = ++g_unique_identifier;
if (same_document) {
FrameNavigationEntry* current_entry =
web_contents_->GetController().GetLastCommittedEntry()->GetFrameEntry(
frame_tree_node_);
params->document_sequence_number =
current_entry->document_sequence_number();
} else {
params->document_sequence_number = ++g_unique_identifier;
}
// Simulate embedding token creation.
if (!same_document)
params->embedding_token = base::UnguessableToken::Create();
params->page_state = page_state_.value_or(
blink::PageState::CreateForTestingWithSequenceNumbers(
navigation_url_, params->item_sequence_number,
params->document_sequence_number));
params->is_overriding_user_agent =
request_ ? (request_->commit_params().is_overriding_user_agent &&
frame_tree_node_->IsMainFrame())
: false;
return params;
}
void NavigationSimulatorImpl::SetKeepLoading(bool keep_loading) {
keep_loading_ = keep_loading;
}
void NavigationSimulatorImpl::StopLoading() {
CHECK(render_frame_host_);
render_frame_host_->SimulateLoadingCompleted(loading_scenario_);
}
void NavigationSimulatorImpl::
SimulateUnloadCompletionCallbackForPreviousFrameIfNeeded(
RenderFrameHostImpl* previous_rfh) {
// Do not dispatch mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame if the
// navigation was committed in the same RenderFrameHost.
if (previous_rfh == render_frame_host_)
return;
if (drop_unload_ack_)
return;
// The previous RenderFrameHost is not live, we will not attempt to unload
// it.
if (!previous_rfh->IsRenderFrameLive())
return;
// The previous RenderFrameHost entered the back-forward cache and hasn't been
// requested to unload. The browser process do not expect
// mojo::AgentSchedulingGroupHost::DidUnloadRenderFrame.
if (previous_rfh->IsInBackForwardCache())
return;
previous_rfh->OnUnloadACK();
}
} // namespace content