blob: 0249744f62f45777c74c1b724fc3b371b3d8f9bd [file] [log] [blame]
// Copyright 2018 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 <functional>
#include "base/bind.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#import "base/test/ios/wait_util.h"
#include "base/test/scoped_feature_list.h"
#include "ios/testing/embedded_test_server_handlers.h"
#include "ios/web/common/features.h"
#import "ios/web/public/navigation/navigation_item.h"
#import "ios/web/public/navigation/navigation_manager.h"
#include "ios/web/public/navigation/reload_type.h"
#include "ios/web/public/security/security_style.h"
#include "ios/web/public/security/ssl_status.h"
#include "ios/web/public/test/element_selector.h"
#import "ios/web/public/test/error_test_util.h"
#include "ios/web/public/test/fakes/test_browser_state.h"
#import "ios/web/public/test/fakes/test_web_client.h"
#include "ios/web/public/test/fakes/test_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"
#include "net/test/embedded_test_server/default_handlers.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
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.
bool WaitForErrorText(WebState* web_state, const GURL& url) WARN_UNUSED_RESULT;
bool WaitForErrorText(WebState* web_state, const GURL& url) {
return test::WaitForWebViewContainingText(
web_state,
testing::GetErrorText(web_state, url, "NSURLErrorDomain",
/*error_code=*/NSURLErrorNetworkConnectionLost,
/*is_post=*/false, /*is_otr=*/false,
/*has_ssl_info=*/false));
}
} // namespace
// ErrorPageTest is parameterized on this enum to test both
// LegacyNavigationManagerImpl and WKBasedNavigationManagerImpl.
enum class NavigationManagerChoice {
LEGACY,
WK_BASED,
};
// 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 ::testing::WithParamInterface<NavigationManagerChoice> {
protected:
ErrorPageTest() : WebTestWithWebState(std::make_unique<TestWebClient>()) {
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)));
if (GetParam() == NavigationManagerChoice::LEGACY) {
scoped_feature_list_.InitAndDisableFeature(
web::features::kSlimNavigationManager);
} else {
scoped_feature_list_.InitAndEnableFeature(
web::features::kSlimNavigationManager);
}
}
void SetUp() override {
WebTestWithWebState::SetUp();
web_state_observer_ = std::make_unique<TestWebStateObserver>(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<TestWebStateObserver> web_state_observer_;
base::test::ScopedFeatureList scoped_feature_list_;
DISALLOW_COPY_AND_ASSIGN(ErrorPageTest);
};
// Tests that the error page is correctly displayed after navigating back to it
// multiple times. See http://crbug.com/944037 .
// TODO(crbug.com/954231): this test is flaky on device.
#if TARGET_IPHONE_SIMULATOR
#define MAYBE_BackForwardErrorPage BackForwardErrorPage
#else
#define MAYBE_BackForwardErrorPage FLAKY_BackForwardErrorPage
#endif
TEST_P(ErrorPageTest, MAYBE_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_P(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_P(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_FALSE(security_state_info());
// 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_P(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_UNKNOWN,
security_state_info()->visible_ssl_status->security_style);
}
// Sucessfully loads the page, goes back, stops the server, goes forward and
// reloads.
TEST_P(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_UNKNOWN,
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_P(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_UNKNOWN,
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.
TEST_P(ErrorPageTest,
RendererInitiatedGoBackFromErrorPageAndForwardToErrorPage) {
if (GetParam() == NavigationManagerChoice::WK_BASED) {
// TODO(crbug.com/867927): Re-enable this test.
return;
}
// 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_P(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_P(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_P(ErrorPageTest, OtrError) {
TestBrowserState 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/705819): Remove this call.
web_state->GetNavigationManager()->LoadIfNecessary();
ASSERT_TRUE(test::WaitForWebViewContainingText(
web_state.get(),
testing::GetErrorText(web_state.get(), server_.GetURL("/echo-query?foo"),
"NSURLErrorDomain",
/*error_code=*/NSURLErrorNetworkConnectionLost,
/*is_post=*/false, /*is_otr=*/true,
/*has_ssl_info=*/false)));
}
// Loads the URL with form which fails to submit.
TEST_P(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"), "NSURLErrorDomain",
/*error_code=*/NSURLErrorNetworkConnectionLost,
/*is_post=*/true, /*is_otr=*/false, /*has_ssl_info=*/false)));
}
// Loads an item and checks that virtualURL and URL after displaying the error
// are correct.
TEST_P(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());
}
INSTANTIATE_TEST_SUITE_P(ProgrammaticErrorPageTest,
ErrorPageTest,
::testing::Values(NavigationManagerChoice::LEGACY,
NavigationManagerChoice::WK_BASED));
} // namespace web