// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <functional>
#import "base/functional/bind.h"
#import "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#import "base/test/scoped_feature_list.h"
#import "ios/net/protocol_handler_util.h"
#import "ios/testing/embedded_test_server_handlers.h"
#import "ios/web/common/features.h"
#import "ios/web/navigation/web_kit_constants.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#import "ios/web/public/navigation/reload_type.h"
#import "ios/web/public/navigation/web_state_policy_decider.h"
#import "ios/web/public/security/security_style.h"
#import "ios/web/public/security/ssl_status.h"
#import "ios/web/public/test/element_selector.h"
#import "ios/web/public/test/error_test_util.h"
#import "ios/web/public/test/fakes/fake_browser_state.h"
#import "ios/web/public/test/fakes/fake_web_client.h"
#import "ios/web/public/test/fakes/fake_web_state_observer.h"
#import "ios/web/public/test/navigation_test_util.h"
#import "ios/web/public/test/web_test_with_web_state.h"
#import "ios/web/public/test/web_view_content_test_util.h"
#import "ios/web/public/web_client.h"
#import "ios/web/public/web_state.h"
#import "ios/web/test/test_url_constants.h"
#import "net/base/apple/url_conversions.h"
#import "net/base/net_errors.h"
#import "net/test/embedded_test_server/default_handlers.h"
#import "net/test/embedded_test_server/request_handler_util.h"
#import "url/gurl.h"
using base::test::ios::kWaitForPageLoadTimeout;
using base::test::ios::WaitUntilConditionOrTimeout;
namespace web {
namespace {
// Waits for text for and error in NSURLErrorDomain and
// kCFURLErrorNetworkConnectionLost error code.
[[nodiscard]] bool WaitForErrorText(WebState* web_state, const GURL& url) {
return test::WaitForWebViewContainingText(
web_state, testing::GetErrorText(
web_state, url, web::testing::CreateConnectionLostError(),
/*is_post=*/false, /*is_otr=*/false,
/*cert_status=*/0));
}
// The error domain and code presented by `TestWebStatePolicyDecider` for
// cancelled navigations.
NSString* const kCancelledNavigationErrorDomain = @"Error domain";
const int kCancelledNavigationErrorCode = 123;
// Creates an error using kCancelledNavigationErrorDomain and
// kCancelledNavigationErrorCode.
NSError* CreateEmbedderError() {
return [NSError errorWithDomain:kCancelledNavigationErrorDomain
code:kCancelledNavigationErrorCode
userInfo:nil];
}
// A WebStatePolicyDecider which cancels requests to URLs of the form
// "/echo-query?blocked" and displays an error.
class TestWebStatePolicyDecider : public WebStatePolicyDecider {
public:
explicit TestWebStatePolicyDecider(WebState* web_state)
: WebStatePolicyDecider(web_state),
path_("/echo-query"),
allowed_query_("allowed"),
blocked_request_query_("blocked-request"),
blocked_response_query_("blocked-response") {}
~TestWebStatePolicyDecider() override = default;
std::string allowed_url_spec() const { return path_ + "?" + allowed_query_; }
std::string blocked_request_url_spec() const {
return path_ + "?" + blocked_request_query_;
}
std::string blocked_response_url_spec() const {
return path_ + "?" + blocked_response_query_;
}
const std::string& allowed_page_text() const { return allowed_query_; }
// WebStatePolicyDecider overrides
void ShouldAllowRequest(NSURLRequest* request,
RequestInfo request_info,
PolicyDecisionCallback callback) override {
PolicyDecision decision = PolicyDecision::Allow();
GURL URL = net::GURLWithNSURL(request.URL);
if (URL.path() != path_ || URL.query() == blocked_request_query_)
decision = PolicyDecision::CancelAndDisplayError(CreateEmbedderError());
std::move(callback).Run(decision);
}
void ShouldAllowResponse(NSURLResponse* response,
ResponseInfo response_info,
PolicyDecisionCallback callback) override {
PolicyDecision decision = PolicyDecision::Allow();
GURL URL = net::GURLWithNSURL(response.URL);
if (URL.path() != path_ || URL.query() != allowed_query_)
decision = PolicyDecision::CancelAndDisplayError(CreateEmbedderError());
std::move(callback).Run(decision);
}
const std::string path_;
const std::string allowed_query_;
const std::string blocked_request_query_;
const std::string blocked_response_query_;
};
} // namespace
// Test fixture for error page testing. Error page simply renders the arguments
// passed to WebClient::PrepareErrorPage, so the test also acts as integration
// test for PrepareErrorPage WebClient method.
class ErrorPageTest : public WebTestWithWebState {
public:
ErrorPageTest(const ErrorPageTest&) = delete;
ErrorPageTest& operator=(const ErrorPageTest&) = delete;
protected:
ErrorPageTest() : WebTestWithWebState(std::make_unique<FakeWebClient>()) {
RegisterDefaultHandlers(&server_);
server_.RegisterRequestHandler(base::BindRepeating(
&net::test_server::HandlePrefixedRequest, "/echo-query",
base::BindRepeating(::testing::HandleEchoQueryOrCloseSocket,
std::cref(server_responds_with_content_))));
server_.RegisterRequestHandler(
base::BindRepeating(&net::test_server::HandlePrefixedRequest, "/iframe",
base::BindRepeating(::testing::HandleIFrame)));
server_.RegisterRequestHandler(
base::BindRepeating(&net::test_server::HandlePrefixedRequest, "/form",
base::BindRepeating(::testing::HandleForm)));
}
void SetUp() override {
WebTestWithWebState::SetUp();
web_state_observer_ = std::make_unique<FakeWebStateObserver>(web_state());
ASSERT_TRUE(server_.Start());
}
TestDidChangeVisibleSecurityStateInfo* security_state_info() {
return web_state_observer_->did_change_visible_security_state_info();
}
net::EmbeddedTestServer server_;
bool server_responds_with_content_ = false;
private:
std::unique_ptr<FakeWebStateObserver> web_state_observer_;
};
// Tests that the error page is correctly displayed after navigating back to it
// multiple times. See http://crbug.com/944037 .
TEST_F(ErrorPageTest, BackForwardErrorPage) {
test::LoadUrl(web_state(), server_.GetURL("/close-socket"));
ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/close-socket")));
test::LoadUrl(web_state(), server_.GetURL("/echo"));
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "Echo"));
web_state()->GetNavigationManager()->GoBack();
ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/close-socket")));
web_state()->GetNavigationManager()->GoForward();
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "Echo"));
web_state()->GetNavigationManager()->GoBack();
ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/close-socket")));
// Make sure that the forward history isn't destroyed.
web_state()->GetNavigationManager()->GoForward();
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "Echo"));
}
// Tests that reloading a page that is no longer accessible doesn't destroy
// forward history.
TEST_F(ErrorPageTest, ReloadOfflinePage) {
server_responds_with_content_ = true;
test::LoadUrl(web_state(), server_.GetURL("/echo-query?foo"));
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "foo"));
test::LoadUrl(web_state(), server_.GetURL("/echoall?bar"));
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "bar"));
web_state()->GetNavigationManager()->GoBack();
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "foo"));
server_responds_with_content_ = false;
web_state()->GetNavigationManager()->Reload(ReloadType::NORMAL,
/*check_for_repost=*/false);
ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/echo-query?foo")));
server_responds_with_content_ = true;
// Make sure that forward history hasn't been destroyed.
ASSERT_TRUE(web_state()->GetNavigationManager()->CanGoForward());
web_state()->GetNavigationManager()->GoForward();
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "bar"));
}
// Loads the URL which fails to load, then sucessfully reloads the page.
TEST_F(ErrorPageTest, ReloadErrorPage) {
// No response leads to -1005 error code (NSURLErrorNetworkConnectionLost).
server_responds_with_content_ = false;
test::LoadUrl(web_state(), server_.GetURL("/echo-query?foo"));
ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/echo-query?foo")));
ASSERT_TRUE(security_state_info());
ASSERT_TRUE(security_state_info()->visible_ssl_status);
EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED,
security_state_info()->visible_ssl_status->security_style);
// Reload the page, which should load without errors.
server_responds_with_content_ = true;
web_state()->GetNavigationManager()->Reload(ReloadType::NORMAL,
/*check_for_repost=*/false);
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "foo"));
}
// Sucessfully loads the page, stops the server and reloads the page.
TEST_F(ErrorPageTest, ReloadPageAfterServerIsDown) {
// Sucessfully load the page.
server_responds_with_content_ = true;
test::LoadUrl(web_state(), server_.GetURL("/echo-query?foo"));
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "foo"));
// Reload the page, no response leads to -1005 error code
// (NSURLErrorNetworkConnectionLost).
server_responds_with_content_ = false;
web_state()->GetNavigationManager()->Reload(ReloadType::NORMAL,
/*check_for_repost=*/false);
ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/echo-query?foo")));
ASSERT_TRUE(security_state_info());
ASSERT_TRUE(security_state_info()->visible_ssl_status);
EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED,
security_state_info()->visible_ssl_status->security_style);
}
// Sucessfully loads the page, goes back, stops the server, goes forward and
// reloads.
TEST_F(ErrorPageTest, GoForwardAfterServerIsDownAndReload) {
// First page loads sucessfully.
test::LoadUrl(web_state(), server_.GetURL("/echo"));
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "Echo"));
// Second page loads sucessfully.
server_responds_with_content_ = true;
test::LoadUrl(web_state(), server_.GetURL("/echo-query?foo"));
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "foo"));
// Go back to the first page.
web_state()->GetNavigationManager()->GoBack();
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "Echo"));
#if TARGET_IPHONE_SIMULATOR
// Go forward. The response will be retrieved from the page cache and will not
// present the error page. Page cache may not always exist on device (which is
// more memory constrained), so this part of the test is simulator-only.
server_responds_with_content_ = false;
web_state()->GetNavigationManager()->GoForward();
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "foo"));
// Reload bypasses the cache.
web_state()->GetNavigationManager()->Reload(ReloadType::NORMAL,
/*check_for_repost=*/false);
ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/echo-query?foo")));
ASSERT_TRUE(security_state_info());
ASSERT_TRUE(security_state_info()->visible_ssl_status);
EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED,
security_state_info()->visible_ssl_status->security_style);
#endif // TARGET_IPHONE_SIMULATOR
}
// Sucessfully loads the page, then loads the URL which fails to load, then
// sucessfully goes back to the first page and goes forward to error page.
// Back-forward navigations are browser-initiated.
TEST_F(ErrorPageTest, GoBackFromErrorPageAndForwardToErrorPage) {
// First page loads sucessfully.
test::LoadUrl(web_state(), server_.GetURL("/echo"));
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "Echo"));
// Second page fails to load.
test::LoadUrl(web_state(), server_.GetURL("/close-socket"));
ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/close-socket")));
// Going back should sucessfully load the first page.
web_state()->GetNavigationManager()->GoBack();
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "Echo"));
// Going forward fails the load.
web_state()->GetNavigationManager()->GoForward();
ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/close-socket")));
ASSERT_TRUE(security_state_info());
ASSERT_TRUE(security_state_info()->visible_ssl_status);
EXPECT_EQ(SECURITY_STYLE_UNAUTHENTICATED,
security_state_info()->visible_ssl_status->security_style);
}
// Sucessfully loads the page, then loads the URL which fails to load, then
// sucessfully goes back to the first page and goes forward to error page.
// Back-forward navigations are renderer-initiated.
// TODO(crbug.com/41404136): Re-enable this test.
TEST_F(ErrorPageTest,
DISABLED_RendererInitiatedGoBackFromErrorPageAndForwardToErrorPage) {
// First page loads sucessfully.
test::LoadUrl(web_state(), server_.GetURL("/echo"));
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "Echo"));
// Second page fails to load.
test::LoadUrl(web_state(), server_.GetURL("/close-socket"));
ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/close-socket")));
// Going back should sucessfully load the first page.
ExecuteJavaScript(@"window.history.back();");
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "Echo"));
// Going forward fails the load.
ExecuteJavaScript(@"window.history.forward();");
ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/close-socket")));
ASSERT_TRUE(security_state_info());
ASSERT_TRUE(security_state_info()->visible_ssl_status);
EXPECT_EQ(SECURITY_STYLE_UNKNOWN,
security_state_info()->visible_ssl_status->security_style);
}
// Loads the URL which redirects to unresponsive server.
TEST_F(ErrorPageTest, RedirectToFailingURL) {
// No response leads to -1005 error code (NSURLErrorNetworkConnectionLost).
server_responds_with_content_ = false;
test::LoadUrl(web_state(), server_.GetURL("/server-redirect?echo-query"));
// Error is displayed after the resdirection to /echo-query.
ASSERT_TRUE(WaitForErrorText(web_state(), server_.GetURL("/echo-query")));
}
// Loads the page with iframe, and that iframe fails to load. There should be no
// error page if the main frame has sucessfully loaded.
TEST_F(ErrorPageTest, ErrorPageInIFrame) {
test::LoadUrl(web_state(), server_.GetURL("/iframe?echo-query"));
EXPECT_TRUE(WaitUntilConditionOrTimeout(kWaitForPageLoadTimeout, ^{
return test::IsWebViewContainingElement(
web_state(),
[ElementSelector selectorWithCSSSelector:"iframe[src*='echo-query']"]);
}));
}
// Loads the URL with off the record browser state.
TEST_F(ErrorPageTest, OtrError) {
FakeBrowserState browser_state;
browser_state.SetOffTheRecord(true);
WebState::CreateParams params(&browser_state);
auto web_state = WebState::Create(params);
// No response leads to -1005 error code (NSURLErrorNetworkConnectionLost).
server_responds_with_content_ = false;
test::LoadUrl(web_state.get(), server_.GetURL("/echo-query?foo"));
// LoadIfNecessary is needed because the view is not created (but needed) when
// loading the page. TODO(crbug.com/41309809): Remove this call.
web_state->GetNavigationManager()->LoadIfNecessary();
ASSERT_TRUE(test::WaitForWebViewContainingText(
web_state.get(),
testing::GetErrorText(web_state.get(), server_.GetURL("/echo-query?foo"),
web::testing::CreateConnectionLostError(),
/*is_post=*/false, /*is_otr=*/true,
/*cert_status=*/0)));
}
// Loads the URL with form which fails to submit.
TEST_F(ErrorPageTest, FormSubmissionError) {
test::LoadUrl(web_state(), server_.GetURL("/form?close-socket"));
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(),
::testing::kTestFormPage));
// Submit the form using JavaScript.
ExecuteJavaScript(@"document.getElementById('form').submit();");
// Error is displayed after the form submission navigation.
ASSERT_TRUE(test::WaitForWebViewContainingText(
web_state(), testing::GetErrorText(
web_state(), server_.GetURL("/close-socket"),
web::testing::CreateConnectionLostError(),
/*is_post=*/true, /*is_otr=*/false, /*cert_status=*/0)));
}
// Loads an item and checks that virtualURL and URL after displaying the error
// are correct.
TEST_F(ErrorPageTest, URLAndVirtualURLAfterError) {
GURL url(server_.GetURL("/close-socket"));
GURL virtual_url("http://virual_url.test");
web::NavigationManager::WebLoadParams params(url);
params.virtual_url = virtual_url;
web::NavigationManager* manager = web_state()->GetNavigationManager();
manager->LoadURLWithParams(params);
manager->LoadIfNecessary();
ASSERT_TRUE(WaitForErrorText(web_state(), url));
EXPECT_EQ(url, manager->GetLastCommittedItem()->GetURL());
EXPECT_EQ(virtual_url, manager->GetLastCommittedItem()->GetVirtualURL());
}
// Tests that an error page is displayed when a WebStatePolicyDecider returns a
// PolicyDecision created with PolicyDecision::CancelAndDisplayError() from
// WebStatePolicyDecider::ShouldAllowRequest() and that the error page loads
// correctly when navigating forward to the error page.
TEST_F(ErrorPageTest, ShouldAllowRequestCancelAndDisplayErrorForwardNav) {
server_responds_with_content_ = true;
TestWebStatePolicyDecider policy_decider(web_state());
// Load successful page.
GURL allowed_url = server_.GetURL(policy_decider.allowed_url_spec());
test::LoadUrl(web_state(), allowed_url);
ASSERT_TRUE(test::WaitForWebViewContainingText(
web_state(), policy_decider.allowed_page_text()));
// Load page which is blocked.
GURL blocked_url = server_.GetURL(policy_decider.blocked_request_url_spec());
test::LoadUrl(web_state(), blocked_url);
NSError* error = testing::CreateErrorWithUnderlyingErrorChain(
{{kCancelledNavigationErrorDomain, kCancelledNavigationErrorCode}});
std::string error_text =
testing::GetErrorText(web_state(), blocked_url, error,
/*is_post=*/false, /*is_otr=*/false,
/*cert_status=*/0);
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), error_text));
// Go back/forward to validate going forward to error page.
web_state()->GetNavigationManager()->GoBack();
ASSERT_TRUE(test::WaitForWebViewContainingText(
web_state(), policy_decider.allowed_page_text()));
web_state()->GetNavigationManager()->GoForward();
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), error_text));
}
// Tests that an error page is displayed when a WebStatePolicyDecider returns a
// PolicyDecision created with PolicyDecision::CancelAndDisplayError() from
// WebStatePolicyDecider::ShouldAllowRequest() and that the error page loads
// correctly when navigating back to the error page.
TEST_F(ErrorPageTest, ShouldAllowRequestCancelAndDisplayErrorBackNav) {
server_responds_with_content_ = true;
TestWebStatePolicyDecider policy_decider(web_state());
// Load page which is blocked.
GURL blocked_url = server_.GetURL(policy_decider.blocked_request_url_spec());
test::LoadUrl(web_state(), blocked_url);
NSError* error = testing::CreateErrorWithUnderlyingErrorChain(
{{kCancelledNavigationErrorDomain, kCancelledNavigationErrorCode}});
std::string error_text =
testing::GetErrorText(web_state(), blocked_url, error,
/*is_post=*/false, /*is_otr=*/false,
/*cert_status=*/0);
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), error_text));
// Load successful page.
GURL allowed_url = server_.GetURL(policy_decider.allowed_url_spec());
test::LoadUrl(web_state(), allowed_url);
ASSERT_TRUE(test::WaitForWebViewContainingText(
web_state(), policy_decider.allowed_page_text()));
// Go back to validate going back to error page.
web_state()->GetNavigationManager()->GoBack();
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), error_text));
}
// Tests that an error page is displayed when a WebStatePolicyDecider executes
// the PolicyDecisionCallback with PolicyDecision::CancelAndDisplayError() from
// WebStatePolicyDecider::ShouldAllowResponse() and that the error page loads
// correctly when navigating forward to the error page.
TEST_F(ErrorPageTest, ShouldAllowResponseCancelAndDisplayErrorForwardNav) {
server_responds_with_content_ = true;
TestWebStatePolicyDecider policy_decider(web_state());
// Load successful page.
GURL allowed_url = server_.GetURL(policy_decider.allowed_url_spec());
test::LoadUrl(web_state(), allowed_url);
ASSERT_TRUE(test::WaitForWebViewContainingText(
web_state(), policy_decider.allowed_page_text()));
// Load page which is blocked.
GURL blocked_url = server_.GetURL(policy_decider.blocked_response_url_spec());
test::LoadUrl(web_state(), blocked_url);
NSError* error = testing::CreateErrorWithUnderlyingErrorChain(
{{base::SysUTF8ToNSString(kWebKitErrorDomain),
kWebKitErrorFrameLoadInterruptedByPolicyChange},
{net::kNSErrorDomain, net::ERR_FAILED},
{kCancelledNavigationErrorDomain, kCancelledNavigationErrorCode}});
std::string error_text =
testing::GetErrorText(web_state(), blocked_url, error,
/*is_post=*/false, /*is_otr=*/false,
/*cert_status=*/0);
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), error_text));
// Go back/forward to validate going forward to error page.
web_state()->GetNavigationManager()->GoBack();
ASSERT_TRUE(test::WaitForWebViewContainingText(
web_state(), policy_decider.allowed_page_text()));
web_state()->GetNavigationManager()->GoForward();
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), error_text));
}
// Tests that an error page is displayed when a WebStatePolicyDecider executes
// the PolicyDecisionCallback with PolicyDecision::CancelAndDisplayError() from
// WebStatePolicyDecider::ShouldAllowResponse() and that the error page loads
// correctly when navigating back to the error page.
TEST_F(ErrorPageTest, ShouldAllowResponseCancelAndDisplayErrorBackNav) {
server_responds_with_content_ = true;
TestWebStatePolicyDecider policy_decider(web_state());
// Load page which is blocked.
GURL blocked_url = server_.GetURL(policy_decider.blocked_response_url_spec());
test::LoadUrl(web_state(), blocked_url);
NSError* error = testing::CreateErrorWithUnderlyingErrorChain(
{{base::SysUTF8ToNSString(kWebKitErrorDomain),
kWebKitErrorFrameLoadInterruptedByPolicyChange},
{net::kNSErrorDomain, net::ERR_FAILED},
{kCancelledNavigationErrorDomain, kCancelledNavigationErrorCode}});
std::string error_text =
testing::GetErrorText(web_state(), blocked_url, error,
/*is_post=*/false, /*is_otr=*/false,
/*cert_status=*/0);
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), error_text));
// Load successful page.
GURL allowed_url = server_.GetURL(policy_decider.allowed_url_spec());
test::LoadUrl(web_state(), allowed_url);
ASSERT_TRUE(test::WaitForWebViewContainingText(
web_state(), policy_decider.allowed_page_text()));
// Go back to validate going back to error page.
web_state()->GetNavigationManager()->GoBack();
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), error_text));
}
// Tests that restoring an invalid WebUI URL doesn't create a new navigation.
TEST_F(ErrorPageTest, RestorationFromInvalidURL) {
server_responds_with_content_ = true;
std::string scheme = kTestWebUIScheme;
GURL invalid_webui = GURL(scheme + "://invalid");
NSError* error = testing::CreateErrorWithUnderlyingErrorChain(
{{@"NSURLErrorDomain", NSURLErrorUnsupportedURL},
{net::kNSErrorDomain, net::ERR_INVALID_URL}});
test::LoadUrl(web_state(), server_.GetURL("/echo-query?foo"));
ASSERT_TRUE(test::WaitForWebViewContainingText(web_state(), "foo"));
test::LoadUrl(web_state(), invalid_webui);
ASSERT_TRUE(test::WaitForWebViewContainingText(
web_state(), testing::GetErrorText(web_state(), invalid_webui, error,
/*is_post=*/false, /*is_otr=*/false,
/*cert_status=*/0)));
// Use Clone() to serialize and then load the session.
auto cloned_web_state = web_state()->Clone();
cloned_web_state->GetNavigationManager()->LoadIfNecessary();
ASSERT_TRUE(test::WaitForWebViewContainingText(
cloned_web_state.get(),
testing::GetErrorText(cloned_web_state.get(), invalid_webui, error,
/*is_post=*/false, /*is_otr=*/false,
/*cert_status=*/0)));
// Check that there is one item in the back list and no forward item.
EXPECT_EQ(
1UL, cloned_web_state->GetNavigationManager()->GetBackwardItems().size());
EXPECT_EQ(0UL,
cloned_web_state->GetNavigationManager()->GetForwardItems().size());
cloned_web_state->GetNavigationManager()->GoBack();
ASSERT_TRUE(
test::WaitForWebViewContainingText(cloned_web_state.get(), "foo"));
// Check that there is one item in the forward list and no back item.
EXPECT_EQ(
0UL, cloned_web_state->GetNavigationManager()->GetBackwardItems().size());
EXPECT_EQ(1UL,
cloned_web_state->GetNavigationManager()->GetForwardItems().size());
}
} // namespace web