| // 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/public/test/navigation_simulator.h" |
| |
| #include "base/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "content/browser/frame_host/debug_urls.h" |
| #include "content/browser/frame_host/navigation_handle_impl.h" |
| #include "content/browser/frame_host/navigation_request.h" |
| #include "content/common/frame_messages.h" |
| #include "content/public/browser/navigation_throttle.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/browser_side_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 "net/base/host_port_pair.h" |
| #include "net/base/load_flags.h" |
| #include "net/url_request/redirect_info.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| class NavigationThrottleCallbackRunner : public NavigationThrottle { |
| public: |
| NavigationThrottleCallbackRunner( |
| NavigationHandle* handle, |
| const base::Closure& on_will_start_request, |
| const base::Closure& on_will_redirect_request, |
| const base::Closure& on_will_fail_request, |
| const base::Closure& on_will_process_response) |
| : NavigationThrottle(handle), |
| on_will_start_request_(on_will_start_request), |
| on_will_redirect_request_(on_will_redirect_request), |
| on_will_fail_request_(on_will_fail_request), |
| on_will_process_response_(on_will_process_response) {} |
| |
| NavigationThrottle::ThrottleCheckResult WillStartRequest() override { |
| on_will_start_request_.Run(); |
| return NavigationThrottle::PROCEED; |
| } |
| |
| NavigationThrottle::ThrottleCheckResult WillRedirectRequest() override { |
| on_will_redirect_request_.Run(); |
| return NavigationThrottle::PROCEED; |
| } |
| |
| NavigationThrottle::ThrottleCheckResult WillFailRequest() override { |
| on_will_fail_request_.Run(); |
| return NavigationThrottle::PROCEED; |
| } |
| |
| NavigationThrottle::ThrottleCheckResult WillProcessResponse() override { |
| on_will_process_response_.Run(); |
| return NavigationThrottle::PROCEED; |
| } |
| |
| const char* GetNameForLogging() override { |
| return "NavigationThrottleCallbackRunner"; |
| } |
| |
| private: |
| base::Closure on_will_start_request_; |
| base::Closure on_will_redirect_request_; |
| base::Closure on_will_fail_request_; |
| base::Closure on_will_process_response_; |
| }; |
| |
| } // namespace |
| |
| // static |
| RenderFrameHost* NavigationSimulator::NavigateAndCommitFromBrowser( |
| WebContents* web_contents, |
| const GURL& url) { |
| auto simulator = |
| NavigationSimulator::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 = NavigationSimulator::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 = |
| NavigationSimulator::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 std::unique_ptr<NavigationSimulator>(new NavigationSimulator( |
| original_url, true /* browser_initiated */, |
| static_cast<WebContentsImpl*>(web_contents), nullptr)); |
| } |
| |
| // static |
| std::unique_ptr<NavigationSimulator> |
| NavigationSimulator::CreateHistoryNavigation(int offset, |
| WebContents* web_contents) { |
| auto simulator = std::unique_ptr<NavigationSimulator>(new NavigationSimulator( |
| GURL(), true /* browser_initiated */, |
| static_cast<WebContentsImpl*>(web_contents), nullptr)); |
| simulator->SetSessionHistoryOffset(offset); |
| return simulator; |
| } |
| |
| // static |
| std::unique_ptr<NavigationSimulator> |
| NavigationSimulator::CreateRendererInitiated( |
| const GURL& original_url, |
| RenderFrameHost* render_frame_host) { |
| return std::unique_ptr<NavigationSimulator>(new NavigationSimulator( |
| original_url, false /* browser_initiated */, |
| static_cast<WebContentsImpl*>( |
| WebContents::FromRenderFrameHost(render_frame_host)), |
| static_cast<TestRenderFrameHost*>(render_frame_host))); |
| } |
| |
| NavigationSimulator::NavigationSimulator(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()), |
| handle_(nullptr), |
| navigation_url_(original_url), |
| socket_address_("2001:db8::1", 80), |
| initial_method_("GET"), |
| browser_initiated_(browser_initiated), |
| transition_(browser_initiated ? ui::PAGE_TRANSITION_TYPED |
| : ui::PAGE_TRANSITION_LINK), |
| contents_mime_type_("text/html"), |
| weak_factory_(this) { |
| // 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; |
| } |
| |
| service_manager::mojom::InterfaceProviderPtr stub_interface_provider; |
| interface_provider_request_ = mojo::MakeRequest(&stub_interface_provider); |
| } |
| |
| NavigationSimulator::~NavigationSimulator() {} |
| |
| void NavigationSimulator::Start() { |
| CHECK(state_ == INITIALIZATION) |
| << "NavigationSimulator::Start should only be called once."; |
| state_ = STARTED; |
| |
| if (browser_initiated_) { |
| if (!SimulateBrowserInitiatedStart()) |
| return; |
| } else { |
| if (!SimulateRendererInitiatedStart()) |
| return; |
| } |
| |
| CHECK(handle_); |
| if (IsRendererDebugURL(navigation_url_)) |
| return; |
| |
| if (same_document_ || !IsURLHandledByNetworkStack(navigation_url_) || |
| navigation_url_.IsAboutBlank()) { |
| CHECK_EQ(1, num_did_start_navigation_called_); |
| return; |
| } |
| |
| WaitForThrottleChecksComplete(); |
| |
| 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 NavigationSimulator::Redirect(const GURL& new_url) { |
| CHECK_LE(state_, STARTED) << "NavigationSimulator::Redirect should be " |
| "called before Fail or Commit"; |
| CHECK_EQ(0, num_did_finish_navigation_called_) |
| << "NavigationSimulator::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_; |
| |
| PrepareCompleteCallbackOnHandle(); |
| NavigationRequest* request = |
| render_frame_host_->frame_tree_node()->navigation_request(); |
| 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 = new_url; |
| redirect_info.new_referrer = referrer_.url.spec(); |
| redirect_info.new_referrer_policy = |
| Referrer::ReferrerPolicyForUrlRequest(referrer_.policy); |
| |
| url_loader->CallOnRequestRedirected( |
| redirect_info, |
| scoped_refptr<network::ResourceResponse>(new network::ResourceResponse)); |
| |
| WaitForThrottleChecksComplete(); |
| |
| 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 NavigationSimulator::ReadyToCommit() { |
| CHECK_LE(state_, STARTED) << "NavigationSimulator::ReadyToCommit can only " |
| "be called once, and cannot be called after " |
| "NavigationSimulator::Fail"; |
| CHECK_EQ(0, num_did_finish_navigation_called_) |
| << "NavigationSimulator::ReadyToCommit cannot be called after the " |
| "navigation has finished"; |
| |
| if (state_ < STARTED) { |
| Start(); |
| if (state_ == FAILED) |
| return; |
| } |
| |
| PrepareCompleteCallbackOnHandle(); |
| if (frame_tree_node_->navigation_request()) { |
| static_cast<TestRenderFrameHost*>(frame_tree_node_->current_frame_host()) |
| ->PrepareForCommitWithSocketAddress(socket_address_); |
| } |
| |
| // Synchronous failure can cause the navigation to finish here. |
| if (!handle_) { |
| state_ = FAILED; |
| return; |
| } |
| |
| if (!same_document_ && !navigation_url_.IsAboutBlank() && |
| IsURLHandledByNetworkStack(navigation_url_)) { |
| WaitForThrottleChecksComplete(); |
| |
| 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_ = handle_->GetGlobalRequestID(); |
| |
| // Update the RenderFrameHost now that we know which RenderFrameHost will |
| // commit the navigation. |
| render_frame_host_ = |
| static_cast<TestRenderFrameHost*>(handle_->GetRenderFrameHost()); |
| state_ = READY_TO_COMMIT; |
| } |
| |
| void NavigationSimulator::Commit() { |
| CHECK_LE(state_, READY_TO_COMMIT) << "NavigationSimulator::Commit can only " |
| "be called once, and cannot be called " |
| "after NavigationSimulator::Fail"; |
| CHECK_EQ(0, num_did_finish_navigation_called_) |
| << "NavigationSimulator::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(); |
| |
| FrameHostMsg_DidCommitProvisionalLoad_Params params; |
| params.nav_entry_id = handle_->pending_nav_entry_id(); |
| params.url = navigation_url_; |
| params.origin = url::Origin::Create(navigation_url_); |
| params.referrer = referrer_; |
| params.transition = transition_; |
| params.should_update_history = true; |
| params.did_create_new_entry = DidCreateNewEntry(); |
| params.gesture = |
| has_user_gesture_ ? NavigationGestureUser : NavigationGestureAuto; |
| params.contents_mime_type = contents_mime_type_; |
| params.method = "GET"; |
| params.http_status_code = 200; |
| params.history_list_was_cleared = false; |
| params.original_request_url = navigation_url_; |
| params.was_within_same_document = same_document_; |
| |
| // Simulate Blink assigning an item and document sequence number to the |
| // navigation. |
| params.item_sequence_number = base::Time::Now().ToDoubleT() * 1000000; |
| params.document_sequence_number = params.item_sequence_number + 1; |
| |
| params.page_state = PageState::CreateForTestingWithSequenceNumbers( |
| navigation_url_, params.item_sequence_number, |
| params.document_sequence_number); |
| |
| if (params.was_within_same_document) |
| interface_provider_request_ = nullptr; |
| |
| render_frame_host_->SendNavigateWithParamsAndInterfaceProvider( |
| ¶ms, std::move(interface_provider_request_)); |
| |
| // Simulate the UnloadACK in the old RenderFrameHost if it was swapped out at |
| // commit time. |
| if (previous_rfh != render_frame_host_) { |
| previous_rfh->OnMessageReceived( |
| FrameHostMsg_SwapOut_ACK(previous_rfh->GetRoutingID())); |
| } |
| |
| state_ = FINISHED; |
| |
| if (!IsRendererDebugURL(navigation_url_)) |
| CHECK_EQ(1, num_did_finish_navigation_called_); |
| } |
| |
| void NavigationSimulator::Fail(int error_code) { |
| CHECK_LE(state_, STARTED) << "NavigationSimulator::Fail can only be " |
| "called once, and cannot be called after " |
| "NavigationSimulator::ReadyToCommit"; |
| CHECK_EQ(0, num_did_finish_navigation_called_) |
| << "NavigationSimulator::Fail cannot be called after the " |
| "navigation has finished"; |
| DCHECK(!IsRendererDebugURL(navigation_url_)); |
| |
| if (state_ == INITIALIZATION) |
| Start(); |
| |
| state_ = FAILED; |
| |
| bool should_result_in_error_page = error_code != net::ERR_ABORTED; |
| PrepareCompleteCallbackOnHandle(); |
| NavigationRequest* request = frame_tree_node_->navigation_request(); |
| CHECK(request); |
| TestNavigationURLLoader* url_loader = |
| static_cast<TestNavigationURLLoader*>(request->loader_for_testing()); |
| CHECK(url_loader); |
| url_loader->SimulateError(error_code); |
| if (error_code != net::ERR_ABORTED) { |
| WaitForThrottleChecksComplete(); |
| 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) { |
| CHECK_EQ(1, num_ready_to_commit_called_); |
| 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*>(handle_->GetRenderFrameHost()); |
| } else { |
| CHECK_EQ(1, num_did_finish_navigation_called_); |
| } |
| } |
| |
| void NavigationSimulator::CommitErrorPage() { |
| CHECK_EQ(FAILED, state_) |
| << "NavigationSimulator::CommitErrorPage can only be " |
| "called once, and should be called after Fail " |
| "has been called"; |
| CHECK_EQ(0, num_did_finish_navigation_called_) |
| << "NavigationSimulator::CommitErrorPage cannot be called after the " |
| "navigation has finished"; |
| |
| // 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(); |
| |
| GURL error_url = GURL(kUnreachableWebDataURL); |
| render_frame_host_->OnMessageReceived(FrameHostMsg_DidStartProvisionalLoad( |
| render_frame_host_->GetRoutingID(), error_url, std::vector<GURL>(), |
| base::TimeTicks::Now())); |
| FrameHostMsg_DidCommitProvisionalLoad_Params params; |
| params.nav_entry_id = handle_->pending_nav_entry_id(); |
| params.did_create_new_entry = DidCreateNewEntry(); |
| params.url = navigation_url_; |
| params.referrer = referrer_; |
| params.transition = transition_; |
| params.was_within_same_document = false; |
| params.url_is_unreachable = true; |
| |
| // Simulate Blink assigning an item and document sequence number to the |
| // navigation. |
| params.item_sequence_number = base::Time::Now().ToDoubleT() * 1000000; |
| params.document_sequence_number = params.item_sequence_number + 1; |
| |
| params.page_state = PageState::CreateForTestingWithSequenceNumbers( |
| navigation_url_, params.item_sequence_number, |
| params.document_sequence_number); |
| |
| render_frame_host_->SendNavigateWithParamsAndInterfaceProvider( |
| ¶ms, std::move(interface_provider_request_)); |
| |
| // Simulate the UnloadACK in the old RenderFrameHost if it was swapped out at |
| // commit time. |
| if (previous_rfh != render_frame_host_) { |
| previous_rfh->OnMessageReceived( |
| FrameHostMsg_SwapOut_ACK(previous_rfh->GetRoutingID())); |
| } |
| |
| state_ = FINISHED; |
| |
| CHECK_EQ(1, num_did_finish_navigation_called_); |
| } |
| |
| void NavigationSimulator::CommitSameDocument() { |
| if (!browser_initiated_) { |
| CHECK_EQ(INITIALIZATION, state_) |
| << "NavigationSimulator::CommitErrorPage should be the only " |
| "navigation event function called on the NavigationSimulator"; |
| } else { |
| CHECK(same_document_); |
| CHECK_EQ(STARTED, state_); |
| } |
| |
| render_frame_host_->OnMessageReceived( |
| FrameHostMsg_DidStartLoading(render_frame_host_->GetRoutingID(), false)); |
| |
| FrameHostMsg_DidCommitProvisionalLoad_Params params; |
| params.nav_entry_id = 0; |
| params.url = navigation_url_; |
| params.origin = url::Origin::Create(navigation_url_); |
| params.referrer = referrer_; |
| params.transition = transition_; |
| params.should_update_history = true; |
| params.did_create_new_entry = false; |
| params.gesture = |
| has_user_gesture_ ? NavigationGestureUser : NavigationGestureAuto; |
| params.contents_mime_type = contents_mime_type_; |
| params.method = "GET"; |
| params.http_status_code = 200; |
| params.history_list_was_cleared = false; |
| params.original_request_url = navigation_url_; |
| params.was_within_same_document = true; |
| params.page_state = |
| PageState::CreateForTesting(navigation_url_, false, nullptr, nullptr); |
| |
| interface_provider_request_ = nullptr; |
| render_frame_host_->SendNavigateWithParamsAndInterfaceProvider( |
| ¶ms, nullptr /* interface_provider_request */); |
| |
| state_ = FINISHED; |
| |
| CHECK_EQ(1, num_did_start_navigation_called_); |
| CHECK_EQ(0, num_will_start_request_called_); |
| CHECK_EQ(0, num_will_process_response_called_); |
| CHECK_EQ(0, num_ready_to_commit_called_); |
| CHECK_EQ(1, num_did_finish_navigation_called_); |
| } |
| |
| void NavigationSimulator::SetTransition(ui::PageTransition transition) { |
| 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 NavigationSimulator::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 NavigationSimulator::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 NavigationSimulator::SetMethod(const std::string& method) { |
| CHECK_EQ(INITIALIZATION, state_) << "The method parameter cannot " |
| "be set after the navigation has started"; |
| initial_method_ = method; |
| } |
| |
| void NavigationSimulator::SetReferrer(const Referrer& referrer) { |
| CHECK_LE(state_, STARTED) << "The referrer cannot be set after the " |
| "navigation has committed or has failed"; |
| referrer_ = referrer; |
| } |
| |
| void NavigationSimulator::SetSocketAddress( |
| const net::HostPortPair& socket_address) { |
| CHECK_LE(state_, STARTED) << "The socket address cannot be set after the " |
| "navigation has committed or failed"; |
| socket_address_ = socket_address; |
| } |
| |
| void NavigationSimulator::SetInterfaceProviderRequest( |
| service_manager::mojom::InterfaceProviderRequest request) { |
| CHECK_LE(state_, STARTED) << "The InterfaceProviderRequest cannot be set " |
| "after the navigation has committed or failed"; |
| CHECK(request.is_pending()); |
| interface_provider_request_ = std::move(request); |
| } |
| |
| void NavigationSimulator::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; |
| } |
| |
| NavigationThrottle::ThrottleCheckResult |
| NavigationSimulator::GetLastThrottleCheckResult() { |
| return last_throttle_check_result_.value(); |
| } |
| |
| NavigationHandle* NavigationSimulator::GetNavigationHandle() const { |
| CHECK_EQ(STARTED, state_); |
| return handle_; |
| } |
| |
| content::GlobalRequestID NavigationSimulator::GetGlobalRequestID() const { |
| CHECK_GT(state_, STARTED) << "The GlobalRequestID is not available until " |
| "after the navigation has completed " |
| "WillProcessResponse"; |
| return request_id_; |
| } |
| |
| void NavigationSimulator::SetOnDeferCallback( |
| const base::Closure& on_defer_callback) { |
| CHECK_LT(state_, FINISHED) |
| << "The callback should not be set after the navigation has finished"; |
| if (handle_) { |
| handle_->SetOnDeferCallbackForTesting(on_defer_callback); |
| return; |
| } |
| |
| // If there is no NavigationHandle for the navigation yet, store the callback |
| // until one has been created. |
| on_defer_callback_ = on_defer_callback; |
| } |
| |
| void NavigationSimulator::DidStartNavigation( |
| NavigationHandle* navigation_handle) { |
| // Check if this navigation is the one we're simulating. |
| if (handle_) |
| return; |
| |
| NavigationHandleImpl* handle = |
| static_cast<NavigationHandleImpl*>(navigation_handle); |
| |
| if (handle->frame_tree_node() != frame_tree_node_) |
| return; |
| |
| handle_ = handle; |
| |
| num_did_start_navigation_called_++; |
| |
| // Add a throttle to count NavigationThrottle calls count. |
| handle->RegisterThrottleForTesting( |
| std::make_unique<NavigationThrottleCallbackRunner>( |
| handle, |
| base::Bind(&NavigationSimulator::OnWillStartRequest, |
| weak_factory_.GetWeakPtr()), |
| base::Bind(&NavigationSimulator::OnWillRedirectRequest, |
| weak_factory_.GetWeakPtr()), |
| base::Bind(&NavigationSimulator::OnWillFailRequest, |
| weak_factory_.GetWeakPtr()), |
| base::Bind(&NavigationSimulator::OnWillProcessResponse, |
| weak_factory_.GetWeakPtr()))); |
| |
| // Pass the |on_defer_callback_| if it was registered. |
| if (!on_defer_callback_.is_null()) { |
| handle->SetOnDeferCallbackForTesting(on_defer_callback_); |
| on_defer_callback_.Reset(); |
| } |
| |
| PrepareCompleteCallbackOnHandle(); |
| } |
| |
| void NavigationSimulator::DidRedirectNavigation( |
| NavigationHandle* navigation_handle) { |
| if (navigation_handle == handle_) |
| num_did_redirect_navigation_called_++; |
| } |
| |
| void NavigationSimulator::ReadyToCommitNavigation( |
| NavigationHandle* navigation_handle) { |
| if (navigation_handle == handle_) |
| num_ready_to_commit_called_++; |
| } |
| |
| void NavigationSimulator::DidFinishNavigation( |
| NavigationHandle* navigation_handle) { |
| if (navigation_handle == handle_) { |
| num_did_finish_navigation_called_++; |
| handle_ = nullptr; |
| } |
| } |
| |
| void NavigationSimulator::OnWillStartRequest() { |
| num_will_start_request_called_++; |
| } |
| |
| void NavigationSimulator::OnWillRedirectRequest() { |
| num_will_redirect_request_called_++; |
| } |
| |
| void NavigationSimulator::OnWillFailRequest() { |
| num_will_fail_request_called_++; |
| } |
| |
| void NavigationSimulator::OnWillProcessResponse() { |
| num_will_process_response_called_++; |
| } |
| |
| bool NavigationSimulator::SimulateBrowserInitiatedStart() { |
| 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 { |
| web_contents_->GetController().LoadURL(navigation_url_, referrer_, |
| transition_, std::string()); |
| } |
| |
| // The navigation url might have been rewritten by the NavigationController. |
| // Update it. |
| navigation_url_ = web_contents_->GetController().GetPendingEntry()->GetURL(); |
| |
| // Simulate the BeforeUnload ACK if needed. |
| NavigationRequest* request = frame_tree_node_->navigation_request(); |
| if (request && |
| request->state() == NavigationRequest::WAITING_FOR_RENDERER_RESPONSE) { |
| static_cast<TestRenderFrameHost*>(web_contents_->GetMainFrame()) |
| ->SendBeforeUnloadACK(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. |
| DCHECK(!handle_); |
| DCHECK(web_contents_->GetMainFrame()->is_loading()); |
| |
| // A navigation to a renderer-debug URL cannot commit. Simulate the |
| // renderer process aborting it. |
| web_contents_->GetMainFrame()->OnMessageReceived( |
| FrameHostMsg_DidStopLoading( |
| web_contents_->GetMainFrame()->GetRoutingID())); |
| state_ = FAILED; |
| return false; |
| } else if (web_contents_->GetMainFrame()->GetNavigationHandle() == |
| handle_) { |
| DCHECK(handle_->IsSameDocument() || |
| !IsURLHandledByNetworkStack(handle_->GetURL())); |
| same_document_ = handle_->IsSameDocument(); |
| return true; |
| } |
| return false; |
| } |
| |
| DCHECK_EQ(handle_, request->navigation_handle()); |
| return true; |
| } |
| |
| bool NavigationSimulator::SimulateRendererInitiatedStart() { |
| mojom::BeginNavigationParamsPtr begin_params = |
| mojom::BeginNavigationParams::New( |
| std::string() /* headers */, net::LOAD_NORMAL, |
| false /* skip_service_worker */, REQUEST_CONTEXT_TYPE_HYPERLINK, |
| blink::WebMixedContentContextType::kBlockable, |
| false /* is_form_submission */, GURL() /* searchable_form_url */, |
| std::string() /* searchable_form_encoding */, url::Origin(), |
| GURL() /* client_side_redirect_url */); |
| CommonNavigationParams common_params; |
| common_params.url = navigation_url_; |
| common_params.method = initial_method_; |
| common_params.referrer = referrer_; |
| common_params.transition = transition_; |
| common_params.navigation_type = |
| PageTransitionCoreTypeIs(transition_, ui::PAGE_TRANSITION_RELOAD) |
| ? FrameMsg_Navigate_Type::RELOAD |
| : FrameMsg_Navigate_Type::DIFFERENT_DOCUMENT; |
| common_params.has_user_gesture = has_user_gesture_; |
| render_frame_host_->frame_host_binding_for_testing().impl()->BeginNavigation( |
| common_params, std::move(begin_params)); |
| NavigationRequest* request = |
| render_frame_host_->frame_tree_node()->navigation_request(); |
| |
| // The request failed synchronously. |
| if (!request) |
| return false; |
| |
| DCHECK_EQ(handle_, request->navigation_handle()); |
| return true; |
| } |
| |
| void NavigationSimulator::WaitForThrottleChecksComplete() { |
| // If last_throttle_check_result_ is set, then throttle checks completed |
| // synchronously. |
| if (!last_throttle_check_result_) { |
| base::RunLoop run_loop; |
| throttle_checks_wait_closure_ = run_loop.QuitClosure(); |
| run_loop.Run(); |
| throttle_checks_wait_closure_.Reset(); |
| } |
| |
| // Run message loop once since NavigationRequest::OnStartChecksComplete posted |
| // a task. |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void NavigationSimulator::OnThrottleChecksComplete( |
| NavigationThrottle::ThrottleCheckResult result) { |
| DCHECK(!last_throttle_check_result_); |
| last_throttle_check_result_ = result; |
| if (throttle_checks_wait_closure_) |
| throttle_checks_wait_closure_.Run(); |
| } |
| |
| void NavigationSimulator::PrepareCompleteCallbackOnHandle() { |
| last_throttle_check_result_.reset(); |
| handle_->set_complete_callback_for_testing( |
| base::Bind(&NavigationSimulator::OnThrottleChecksComplete, |
| weak_factory_.GetWeakPtr())); |
| } |
| |
| RenderFrameHost* NavigationSimulator::GetFinalRenderFrameHost() { |
| CHECK_GE(state_, READY_TO_COMMIT); |
| return render_frame_host_; |
| } |
| |
| bool NavigationSimulator::CheckIfSameDocument() { |
| // This approach to determining whether a navigation is to be treated as |
| // same document is not robust, as it will not handle pushState type |
| // navigation. Do not use elsewhere! |
| |
| // First we need a valid document that is not an error page. |
| if (!render_frame_host_->GetLastCommittedURL().is_valid() || |
| render_frame_host_->last_commit_was_error_page()) { |
| return false; |
| } |
| |
| // Exclude reloads. |
| if (ui::PageTransitionCoreTypeIs(transition_, ui::PAGE_TRANSITION_RELOAD)) { |
| return false; |
| } |
| |
| // A browser-initiated navigation to the exact same url in the address bar is |
| // not a same document navigation. |
| if (browser_initiated_ && |
| render_frame_host_->GetLastCommittedURL() == navigation_url_) { |
| return false; |
| } |
| |
| // Finally, the navigation url and the last committed url should match, |
| // except for the fragment. |
| GURL url_copy(navigation_url_); |
| url::Replacements<char> replacements; |
| replacements.ClearRef(); |
| return url_copy.ReplaceComponents(replacements) == |
| render_frame_host_->GetLastCommittedURL().ReplaceComponents( |
| replacements); |
| } |
| |
| bool NavigationSimulator::DidCreateNewEntry() { |
| if (ui::PageTransitionCoreTypeIs(transition_, |
| ui::PAGE_TRANSITION_AUTO_SUBFRAME)) |
| return false; |
| if (reload_type_ != ReloadType::NONE) |
| return false; |
| if (session_history_offset_) |
| return false; |
| |
| return true; |
| } |
| |
| void NavigationSimulator::SetSessionHistoryOffset(int session_history_offset) { |
| CHECK(session_history_offset); |
| session_history_offset_ = session_history_offset; |
| transition_ = |
| ui::PageTransitionFromInt(transition_ | ui::PAGE_TRANSITION_FORWARD_BACK); |
| } |
| |
| } // namespace content |