blob: 199715a9b66de183cc8bef20747e25726fb6912e [file] [log] [blame]
// Copyright 2016 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.
#import <XCTest/XCTest.h>
#include <map>
#include <memory>
#include <string>
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "ios/chrome/browser/ui/util/ui_util.h"
#include "ios/chrome/grit/ios_strings.h"
#import "ios/chrome/test/app/chrome_test_util.h"
#import "ios/chrome/test/app/web_view_interaction_test_util.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey.h"
#import "ios/chrome/test/earl_grey/chrome_earl_grey_ui.h"
#import "ios/chrome/test/earl_grey/chrome_matchers.h"
#import "ios/chrome/test/earl_grey/chrome_test_case.h"
#include "ios/chrome/test/scoped_block_popups_pref.h"
#import "ios/web/public/test/earl_grey/web_view_actions.h"
#import "ios/web/public/test/earl_grey/web_view_matchers.h"
#include "ios/web/public/test/http_server/data_response_provider.h"
#import "ios/web/public/test/http_server/http_server.h"
#include "ios/web/public/test/http_server/http_server_util.h"
#import "ios/web/public/test/url_test_util.h"
#import "ios/web/public/web_client.h"
#include "net/http/http_response_headers.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
using chrome_test_util::GetOriginalBrowserState;
using chrome_test_util::OmniboxText;
using chrome_test_util::TapWebViewElementWithId;
namespace {
// URL used for the reload test.
const char kReloadTestUrl[] = "http://mock/reloadTest";
// Returns the number of serviced requests in HTTP body.
class ReloadResponseProvider : public web::DataResponseProvider {
public:
ReloadResponseProvider() : request_number_(0) {}
// URL used for the reload test.
static GURL GetReloadTestUrl() {
return web::test::HttpServer::MakeUrl(kReloadTestUrl);
}
bool CanHandleRequest(const Request& request) override {
return request.url == ReloadResponseProvider::GetReloadTestUrl();
}
void GetResponseHeadersAndBody(
const Request& request,
scoped_refptr<net::HttpResponseHeaders>* headers,
std::string* response_body) override {
DCHECK_EQ(ReloadResponseProvider::GetReloadTestUrl(), request.url);
*headers = GetDefaultResponseHeaders();
*response_body = GetResponseBody(request_number_++);
}
// static
static std::string GetResponseBody(int request_number) {
return base::StringPrintf("Load request %d", request_number);
}
private:
int request_number_; // Count of requests received by the response provider.
};
} // namespace
// Tests web browsing scenarios.
@interface BrowsingTestCase : ChromeTestCase
@end
@implementation BrowsingTestCase
// Matcher for the title of the current tab (on tablet only).
id<GREYMatcher> TabWithTitle(const std::string& tab_title) {
id<GREYMatcher> notPartOfOmnibox =
grey_not(grey_ancestor(chrome_test_util::Omnibox()));
return grey_allOf(grey_accessibilityLabel(base::SysUTF8ToNSString(tab_title)),
notPartOfOmnibox, nil);
}
// Tests that page successfully reloads.
- (void)testReload {
// Set up test HTTP server responses.
std::unique_ptr<web::DataResponseProvider> provider(
new ReloadResponseProvider());
web::test::SetUpHttpServer(std::move(provider));
GURL URL = ReloadResponseProvider::GetReloadTestUrl();
[ChromeEarlGrey loadURL:URL];
std::string expectedBodyBeforeReload(
ReloadResponseProvider::GetResponseBody(0 /* request number */));
[ChromeEarlGrey waitForWebViewContainingText:expectedBodyBeforeReload];
[ChromeEarlGreyUI reload];
std::string expectedBodyAfterReload(
ReloadResponseProvider::GetResponseBody(1 /* request_number */));
[ChromeEarlGrey waitForWebViewContainingText:expectedBodyAfterReload];
}
// Tests that a tab's title is based on the URL when no other information is
// available.
- (void)testBrowsingTabTitleSetFromURL {
if (!IsIPadIdiom()) {
EARL_GREY_TEST_SKIPPED(@"Tab Title not displayed on handset.");
}
web::test::SetUpFileBasedHttpServer();
const GURL destinationURL = web::test::HttpServer::MakeUrl(
"http://ios/testing/data/http_server_files/destination.html");
[ChromeEarlGrey loadURL:destinationURL];
// Add 3 for the "://" which is not considered part of the scheme
std::string URLWithoutScheme =
destinationURL.spec().substr(destinationURL.scheme().length() + 3);
[[EarlGrey selectElementWithMatcher:TabWithTitle(URLWithoutScheme)]
assertWithMatcher:grey_sufficientlyVisible()];
}
// Tests that after a PDF is loaded, the title appears in the tab bar on iPad.
- (void)testPDFLoadTitle {
if (!IsIPadIdiom()) {
EARL_GREY_TEST_SKIPPED(@"Tab Title not displayed on handset.");
}
web::test::SetUpFileBasedHttpServer();
const GURL destinationURL = web::test::HttpServer::MakeUrl(
"http://ios/testing/data/http_server_files/testpage.pdf");
[ChromeEarlGrey loadURL:destinationURL];
// Add 3 for the "://" which is not considered part of the scheme
std::string URLWithoutScheme =
destinationURL.spec().substr(destinationURL.scheme().length() + 3);
[[EarlGrey selectElementWithMatcher:TabWithTitle(URLWithoutScheme)]
assertWithMatcher:grey_sufficientlyVisible()];
}
// Tests that tab title is set to the specified title from a JavaScript.
- (void)testBrowsingTabTitleSetFromScript {
if (!IsIPadIdiom()) {
EARL_GREY_TEST_SKIPPED(@"Tab Title not displayed on handset.");
}
const char* kPageTitle = "Some title";
const GURL URL = GURL(base::StringPrintf(
"data:text/html;charset=utf-8,<script>document.title = "
"\"%s\"</script>",
kPageTitle));
[ChromeEarlGrey loadURL:URL];
[[EarlGrey selectElementWithMatcher:TabWithTitle(kPageTitle)]
assertWithMatcher:grey_sufficientlyVisible()];
}
// Tests clicking a link with target="_blank" and "event.stopPropagation()"
// opens a new tab.
- (void)testBrowsingStopPropagation {
// Create map of canned responses and set up the test HTML server.
std::map<GURL, std::string> responses;
const GURL URL = web::test::HttpServer::MakeUrl("http://stopPropagation");
const GURL destinationURL =
web::test::HttpServer::MakeUrl("http://destination");
// This is a page with a link to |kDestination|.
responses[URL] = base::StringPrintf(
"<a id='link' href='%s' target='_blank' "
"onclick='event.stopPropagation()'>link</a>",
destinationURL.spec().c_str());
// This is the destination page; it just contains some text.
responses[destinationURL] = "You've arrived!";
web::test::SetUpSimpleHttpServer(responses);
ScopedBlockPopupsPref prefSetter(CONTENT_SETTING_ALLOW,
GetOriginalBrowserState());
[ChromeEarlGrey loadURL:URL];
[ChromeEarlGrey waitForMainTabCount:1];
GREYAssert(TapWebViewElementWithId("link"), @"Failed to tap \"link\"");
[ChromeEarlGrey waitForMainTabCount:2];
// Verify the new tab was opened with the expected URL.
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
// Tests clicking a relative link with target="_blank" and
// "event.stopPropagation()" opens a new tab.
- (void)testBrowsingStopPropagationRelativePath {
// Create map of canned responses and set up the test HTML server.
std::map<GURL, std::string> responses;
const GURL URL = web::test::HttpServer::MakeUrl("http://stopPropRel");
const GURL destinationURL =
web::test::HttpServer::MakeUrl("http://stopPropRel/#test");
// This is page with a relative link to "#test".
responses[URL] =
"<a id='link' href='#test' target='_blank' "
"onclick='event.stopPropagation()'>link</a>";
// This is the page that should be showing at the end of the test.
responses[destinationURL] = "You've arrived!";
web::test::SetUpSimpleHttpServer(responses);
ScopedBlockPopupsPref prefSetter(CONTENT_SETTING_ALLOW,
GetOriginalBrowserState());
[ChromeEarlGrey loadURL:URL];
[ChromeEarlGrey waitForMainTabCount:1];
GREYAssert(TapWebViewElementWithId("link"), @"Failed to tap \"link\"");
[ChromeEarlGrey waitForMainTabCount:2];
// Verify the new tab was opened with the expected URL.
const std::string omniboxText =
web::GetContentAndFragmentForUrl(destinationURL);
[[EarlGrey selectElementWithMatcher:OmniboxText(omniboxText)]
assertWithMatcher:grey_notNil()];
}
// Tests that clicking a link with URL changed by onclick uses the href of the
// anchor tag instead of the one specified in JavaScript. Also verifies a new
// tab is opened by target '_blank'.
// TODO(crbug.com/688223): WKWebView does not open a new window as expected by
// this test.
- (void)DISABLED_testBrowsingPreventDefaultWithLinkOpenedByJavascript {
// Create map of canned responses and set up the test HTML server.
std::map<GURL, std::string> responses;
const GURL URL = web::test::HttpServer::MakeUrl(
"http://preventDefaultWithLinkOpenedByJavascript");
const GURL anchorURL =
web::test::HttpServer::MakeUrl("http://anchorDestination");
const GURL destinationURL =
web::test::HttpServer::MakeUrl("http://javaScriptDestination");
// This is a page with a link where the href and JavaScript are setting the
// destination to two different URLs so the test can verify which one the
// browser uses.
responses[URL] = base::StringPrintf(
"<a id='link' href='%s' target='_blank' "
"onclick='window.location.href=\"%s\"; "
"event.stopPropagation()' id='link'>link</a>",
anchorURL.spec().c_str(), destinationURL.spec().c_str());
responses[anchorURL] = "anchor destination";
web::test::SetUpSimpleHttpServer(responses);
ScopedBlockPopupsPref prefSetter(CONTENT_SETTING_ALLOW,
GetOriginalBrowserState());
[ChromeEarlGrey loadURL:URL];
[ChromeEarlGrey waitForMainTabCount:1];
GREYAssert(TapWebViewElementWithId("link"), @"Failed to tap \"link\"");
[ChromeEarlGrey waitForMainTabCount:2];
// Verify the new tab was opened with the expected URL.
[[EarlGrey selectElementWithMatcher:OmniboxText(anchorURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
// Tests tapping a link that navigates to a page that immediately navigates
// again via document.location.href.
- (void)testBrowsingWindowDataLinkScriptRedirect {
// Create map of canned responses and set up the test HTML server.
std::map<GURL, std::string> responses;
const GURL URL =
web::test::HttpServer::MakeUrl("http://windowDataLinkScriptRedirect");
const GURL intermediateURL =
web::test::HttpServer::MakeUrl("http://intermediate");
const GURL destinationURL =
web::test::HttpServer::MakeUrl("http://destination");
// This is a page with a link to the intermediate page.
responses[URL] =
base::StringPrintf("<a id='link' href='%s' target='_blank'>link</a>",
intermediateURL.spec().c_str());
// This intermediate page uses JavaScript to immediately navigate to the
// destination page.
responses[intermediateURL] =
base::StringPrintf("<script>document.location.href=\"%s\"</script>",
destinationURL.spec().c_str());
// This is the page that should be showing at the end of the test.
responses[destinationURL] = "You've arrived!";
web::test::SetUpSimpleHttpServer(responses);
ScopedBlockPopupsPref prefSetter(CONTENT_SETTING_ALLOW,
GetOriginalBrowserState());
[ChromeEarlGrey loadURL:URL];
[ChromeEarlGrey waitForMainTabCount:1];
GREYAssert(TapWebViewElementWithId("link"), @"Failed to tap \"link\"");
[ChromeEarlGrey waitForMainTabCount:2];
// Verify the new tab was opened with the expected URL.
[[EarlGrey selectElementWithMatcher:OmniboxText(destinationURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
// Tests that a link with a JavaScript-based navigation changes the page and
// that the back button works as expected afterwards.
- (void)testBrowsingJavaScriptBasedNavigation {
std::map<GURL, std::string> responses;
const GURL URL = web::test::HttpServer::MakeUrl("http://origin");
const GURL destURL = web::test::HttpServer::MakeUrl("http://destination");
// Page containing a link with onclick attribute that sets window.location
// to the destination URL.
responses[URL] = base::StringPrintf(
"<a href='#' onclick=\"window.location='%s';\" id='link'>Link</a>",
destURL.spec().c_str());
// Page with some text.
responses[destURL] = "You've arrived!";
web::test::SetUpSimpleHttpServer(responses);
[ChromeEarlGrey loadURL:URL];
GREYAssert(TapWebViewElementWithId("link"), @"Failed to tap \"link\"");
[[EarlGrey selectElementWithMatcher:OmniboxText(destURL.GetContent())]
assertWithMatcher:grey_notNil()];
[ChromeEarlGrey goBack];
[ChromeEarlGrey waitForWebViewContainingText:"Link"];
if (web::GetWebClient()->IsSlimNavigationManagerEnabled()) {
// Due to the link click, URL of the first page now has an extra '#'. This
// is consistent with all other browsers.
const GURL newURL = web::test::HttpServer::MakeUrl("http://origin#");
[[EarlGrey selectElementWithMatcher:OmniboxText(newURL.GetContent())]
assertWithMatcher:grey_notNil()];
} else {
[[EarlGrey selectElementWithMatcher:OmniboxText(URL.GetContent())]
assertWithMatcher:grey_notNil()];
}
}
// Tests that a link with WebUI URL does not trigger a load. WebUI pages may
// have increased power and using the same web process (which may potentially
// be controlled by an attacker) is dangerous.
- (void)testTapLinkWithWebUIURL {
// Create map of canned responses and set up the test HTML server.
std::map<GURL, std::string> responses;
const GURL URL(web::test::HttpServer::MakeUrl("http://pageWithWebUILink"));
const char kPageHTML[] =
"<script>"
" function printMsg() {"
" document.body.appendChild(document.createTextNode('Hello world!'));"
" }"
"</script>"
"<a href='chrome://version' id='link' onclick='printMsg()'>Version</a>";
responses[URL] = kPageHTML;
web::test::SetUpSimpleHttpServer(responses);
// Assert that test is starting with one tab.
[ChromeEarlGrey waitForMainTabCount:1];
[ChromeEarlGrey waitForIncognitoTabCount:0];
[ChromeEarlGrey loadURL:URL];
// Tap on chrome://version link.
[ChromeEarlGrey tapWebViewElementWithID:@"link"];
// Verify that page did not change by checking its URL and message printed by
// onclick event.
[[EarlGrey selectElementWithMatcher:OmniboxText("chrome://version")]
assertWithMatcher:grey_nil()];
[ChromeEarlGrey waitForWebViewContainingText:"Hello world!"];
// Verify that no new tabs were open which could load chrome://version.
[ChromeEarlGrey waitForMainTabCount:1];
}
// Tests that evaluating user JavaScript that causes navigation correctly
// modifies history.
- (void)testBrowsingUserJavaScriptNavigation {
// TODO(crbug.com/703855): Keyboard entry inside the omnibox fails only on
// iPad.
if (IsIPadIdiom())
return;
// Create map of canned responses and set up the test HTML server.
std::map<GURL, std::string> responses;
const GURL startURL = web::test::HttpServer::MakeUrl("http://startpage");
responses[startURL] = "<html><body><p>Ready to begin.</p></body></html>";
const GURL targetURL = web::test::HttpServer::MakeUrl("http://targetpage");
responses[targetURL] = "<html><body><p>You've arrived!</p></body></html>";
web::test::SetUpSimpleHttpServer(responses);
// Load the first page and run JS (using the codepath that user-entered JS in
// the omnibox would take, not page-triggered) that should navigate.
[ChromeEarlGrey loadURL:startURL];
NSString* script =
[NSString stringWithFormat:@"javascript:window.location='%s'",
targetURL.spec().c_str()];
[ChromeEarlGreyUI focusOmniboxAndType:script];
[[EarlGrey selectElementWithMatcher:grey_accessibilityID(@"Go")]
performAction:grey_tap()];
[ChromeEarlGrey waitForPageToFinishLoading];
[[EarlGrey selectElementWithMatcher:OmniboxText(targetURL.GetContent())]
assertWithMatcher:grey_notNil()];
[ChromeEarlGrey goBack];
[[EarlGrey selectElementWithMatcher:OmniboxText(startURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
// Tests that evaluating non-navigation user JavaScript doesn't affect history.
- (void)testBrowsingUserJavaScriptWithoutNavigation {
// TODO(crbug.com/703855): Keyboard entry inside the omnibox fails only on
// iPad.
if (IsIPadIdiom())
return;
// Create map of canned responses and set up the test HTML server.
std::map<GURL, std::string> responses;
const GURL firstURL = web::test::HttpServer::MakeUrl("http://firstURL");
const std::string firstResponse = "Test Page 1";
const GURL secondURL = web::test::HttpServer::MakeUrl("http://secondURL");
const std::string secondResponse = "Test Page 2";
responses[firstURL] = firstResponse;
responses[secondURL] = secondResponse;
web::test::SetUpSimpleHttpServer(responses);
[ChromeEarlGrey loadURL:firstURL];
[ChromeEarlGrey loadURL:secondURL];
// Execute some JavaScript in the omnibox.
[ChromeEarlGreyUI focusOmniboxAndType:@"javascript:document.write('foo')\n"];
[ChromeEarlGrey waitForWebViewContainingText:"foo"];
// Verify that the JavaScript did not affect history by going back and then
// forward again.
[ChromeEarlGrey goBack];
[[EarlGrey selectElementWithMatcher:OmniboxText(firstURL.GetContent())]
assertWithMatcher:grey_notNil()];
[ChromeEarlGrey goForward];
[[EarlGrey selectElementWithMatcher:OmniboxText(secondURL.GetContent())]
assertWithMatcher:grey_notNil()];
}
@end