blob: b92d6bbbcce3c96d240110e81ecff8cf97fc4d48 [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.
#import "ios/web/navigation/wk_navigation_util.h"
#include <memory>
#include <vector>
#include "base/json/json_reader.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "ios/web/common/features.h"
#import "ios/web/navigation/navigation_item_impl.h"
#include "ios/web/test/test_url_constants.h"
#include "net/base/escape.h"
#import "net/base/mac/url_conversions.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#include "url/scheme_host_port.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
namespace web {
namespace wk_navigation_util {
namespace {
// Creates a vector with given number of navigation items. All items will
// have distinct titles and URLs.
void CreateTestNavigationItems(
size_t count,
std::vector<std::unique_ptr<NavigationItem>>& items) {
for (size_t i = 0; i < count; i++) {
auto item = std::make_unique<NavigationItemImpl>();
item->SetURL(GURL(base::StringPrintf("http://www.%zu.com", i)));
item->SetTitle(base::ASCIIToUTF16(base::StringPrintf("Test%zu", i)));
items.push_back(std::move(item));
}
}
// Extracts session dictionary from |restore_session_url|.
base::JSONReader::ValueWithError ExtractSessionDict(GURL restore_session_url) {
NSString* fragment = net::NSURLWithGURL(restore_session_url).fragment;
NSString* encoded_session =
[fragment substringFromIndex:strlen(kRestoreSessionSessionHashPrefix)];
std::string session_json =
net::UnescapeBinaryURLComponent(base::SysNSStringToUTF8(encoded_session));
return base::JSONReader::ReadAndReturnValueWithError(session_json,
base::JSON_PARSE_RFC);
}
} // namespace
typedef PlatformTest WKNavigationUtilTest;
TEST_F(WKNavigationUtilTest, CreateRestoreSessionUrl) {
auto item0 = std::make_unique<NavigationItemImpl>();
item0->SetURL(GURL("http://www.0.com"));
item0->SetTitle(base::ASCIIToUTF16("Test Website 0"));
auto item1 = std::make_unique<NavigationItemImpl>();
item1->SetURL(GURL("http://www.1.com"));
// Create an App-specific URL
auto item2 = std::make_unique<NavigationItemImpl>();
GURL url2("http://webui");
GURL::Replacements scheme_replacements;
scheme_replacements.SetSchemeStr(kTestWebUIScheme);
item2->SetURL(url2.ReplaceComponents(scheme_replacements));
std::vector<std::unique_ptr<NavigationItem>> items;
items.push_back(std::move(item0));
items.push_back(std::move(item1));
items.push_back(std::move(item2));
int first_index = 0;
GURL restore_session_url;
CreateRestoreSessionUrl(0 /* last_committed_item_index */, items,
&restore_session_url, &first_index);
ASSERT_EQ(0, first_index);
ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url));
std::string session_json =
net::UnescapeBinaryURLComponent(restore_session_url.ref());
EXPECT_EQ("session={\"offset\":-2,\"titles\":[\"Test Website 0\",\"\",\"\"],"
"\"urls\":[\"http://www.0.com/\",\"http://www.1.com/\","
"\"testwebui://webui/\"]}",
session_json);
}
// Verifies that large session can be stored in NSURL. GURL is converted to
// NSURL, because NSURL is passed to WKWebView during the session restoration.
TEST_F(WKNavigationUtilTest, CreateRestoreSessionUrlForLargeSession) {
// Create restore session URL with large number of items.
const size_t kItemCount = kMaxSessionSize;
std::vector<std::unique_ptr<NavigationItem>> items;
CreateTestNavigationItems(kItemCount, items);
int first_index = 0;
GURL restore_session_url;
CreateRestoreSessionUrl(
/*last_committed_item_index=*/0, items, &restore_session_url,
&first_index);
ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url));
// Extract session JSON from restoration URL.
base::JSONReader::ValueWithError value_with_error =
ExtractSessionDict(restore_session_url);
ASSERT_EQ(base::JSONReader::JSON_NO_ERROR, value_with_error.error_code);
ASSERT_TRUE(value_with_error.value.has_value());
// Verify that all titles and URLs are present.
base::Value* titles_value = value_with_error.value->FindKey("titles");
ASSERT_TRUE(titles_value);
ASSERT_TRUE(titles_value->is_list());
ASSERT_EQ(kItemCount, titles_value->GetList().size());
base::Value* urls_value = value_with_error.value->FindKey("urls");
ASSERT_TRUE(urls_value);
ASSERT_TRUE(urls_value->is_list());
ASSERT_EQ(kItemCount, urls_value->GetList().size());
}
// Verifies that large session can be stored in NSURL and that extra items
// are trimmed from the right side of |last_committed_item_index|.
TEST_F(WKNavigationUtilTest, CreateRestoreSessionUrlForExtraLargeForwardList) {
// Create restore session URL with large number of items that exceeds
// kMaxSessionSize.
const size_t kItemCount = kMaxSessionSize * 3;
std::vector<std::unique_ptr<NavigationItem>> items;
CreateTestNavigationItems(kItemCount, items);
int first_index = 0;
GURL restore_session_url;
CreateRestoreSessionUrl(
/*last_committed_item_index=*/0, items, &restore_session_url,
&first_index);
ASSERT_EQ(0, first_index);
ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url));
// Extract session JSON from restoration URL.
base::JSONReader::ValueWithError value_with_error =
ExtractSessionDict(restore_session_url);
ASSERT_EQ(base::JSONReader::JSON_NO_ERROR, value_with_error.error_code);
ASSERT_TRUE(value_with_error.value.has_value());
// Verify that first kMaxSessionSize titles and URLs are present.
base::Value* titles_value = value_with_error.value->FindKey("titles");
ASSERT_TRUE(titles_value);
ASSERT_TRUE(titles_value->is_list());
ASSERT_EQ(static_cast<size_t>(kMaxSessionSize),
titles_value->GetList().size());
ASSERT_EQ("Test0", titles_value->GetList()[0].GetString());
ASSERT_EQ("Test74", titles_value->GetList()[kMaxSessionSize - 1].GetString());
base::Value* urls_value = value_with_error.value->FindKey("urls");
ASSERT_TRUE(urls_value);
ASSERT_TRUE(urls_value->is_list());
ASSERT_EQ(static_cast<size_t>(kMaxSessionSize), urls_value->GetList().size());
ASSERT_EQ("http://www.0.com/", urls_value->GetList()[0].GetString());
ASSERT_EQ("http://www.74.com/",
urls_value->GetList()[kMaxSessionSize - 1].GetString());
// Verify the offset is correct.
ASSERT_EQ(1 - kMaxSessionSize,
value_with_error.value->FindKey("offset")->GetInt());
}
// Verifies that large session can be stored in NSURL and that extra items
// are trimmed from the left side of |last_committed_item_index|.
TEST_F(WKNavigationUtilTest, CreateRestoreSessionUrlForExtraLargeBackList) {
// Create restore session URL with large number of items that exceeds
// kMaxSessionSize.
const size_t kItemCount = kMaxSessionSize * 3;
std::vector<std::unique_ptr<NavigationItem>> items;
CreateTestNavigationItems(kItemCount, items);
int first_index = 0;
GURL restore_session_url;
CreateRestoreSessionUrl(
/*last_committed_item_index=*/kItemCount - 1, items, &restore_session_url,
&first_index);
ASSERT_EQ(150, first_index);
ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url));
// Extract session JSON from restoration URL.
base::JSONReader::ValueWithError value_with_error =
ExtractSessionDict(restore_session_url);
ASSERT_EQ(base::JSONReader::JSON_NO_ERROR, value_with_error.error_code);
ASSERT_TRUE(value_with_error.value.has_value());
// Verify that last kMaxSessionSize titles and URLs are present.
base::Value* titles_value = value_with_error.value->FindKey("titles");
ASSERT_TRUE(titles_value);
ASSERT_TRUE(titles_value->is_list());
ASSERT_EQ(static_cast<size_t>(kMaxSessionSize),
titles_value->GetList().size());
ASSERT_EQ("Test150", titles_value->GetList()[0].GetString());
ASSERT_EQ("Test224",
titles_value->GetList()[kMaxSessionSize - 1].GetString());
base::Value* urls_value = value_with_error.value->FindKey("urls");
ASSERT_TRUE(urls_value);
ASSERT_TRUE(urls_value->is_list());
ASSERT_EQ(static_cast<size_t>(kMaxSessionSize), urls_value->GetList().size());
ASSERT_EQ("http://www.150.com/", urls_value->GetList()[0].GetString());
ASSERT_EQ("http://www.224.com/",
urls_value->GetList()[kMaxSessionSize - 1].GetString());
// Verify the offset is correct.
ASSERT_EQ(0, value_with_error.value->FindKey("offset")->GetInt());
}
// Verifies that large session can be stored in NSURL and that extra items
// are trimmed from the left and right sides of |last_committed_item_index|.
TEST_F(WKNavigationUtilTest,
CreateRestoreSessionUrlForExtraLargeBackAndForwardList) {
// Create restore session URL with large number of items that exceeds
// kMaxSessionSize.
const size_t kItemCount = kMaxSessionSize * 2;
std::vector<std::unique_ptr<NavigationItem>> items;
CreateTestNavigationItems(kItemCount, items);
int first_index = 0;
GURL restore_session_url;
CreateRestoreSessionUrl(
/*last_committed_item_index=*/kMaxSessionSize, items,
&restore_session_url, &first_index);
ASSERT_EQ(38, first_index);
ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url));
// Extract session JSON from restoration URL.
base::JSONReader::ValueWithError value_with_error =
ExtractSessionDict(restore_session_url);
ASSERT_EQ(base::JSONReader::JSON_NO_ERROR, value_with_error.error_code);
ASSERT_TRUE(value_with_error.value.has_value());
// Verify that last kMaxSessionSize titles and URLs are present.
base::Value* titles_value = value_with_error.value->FindKey("titles");
ASSERT_TRUE(titles_value);
ASSERT_TRUE(titles_value->is_list());
ASSERT_EQ(static_cast<size_t>(kMaxSessionSize),
titles_value->GetList().size());
ASSERT_EQ("Test38", titles_value->GetList()[0].GetString());
ASSERT_EQ("Test112",
titles_value->GetList()[kMaxSessionSize - 1].GetString());
base::Value* urls_value = value_with_error.value->FindKey("urls");
ASSERT_TRUE(urls_value);
ASSERT_TRUE(urls_value->is_list());
ASSERT_EQ(static_cast<size_t>(kMaxSessionSize), urls_value->GetList().size());
ASSERT_EQ("http://www.38.com/", urls_value->GetList()[0].GetString());
ASSERT_EQ("http://www.112.com/",
urls_value->GetList()[kMaxSessionSize - 1].GetString());
// Verify the offset is correct.
ASSERT_EQ((1 - kMaxSessionSize) / 2,
value_with_error.value->FindKey("offset")->GetInt());
}
TEST_F(WKNavigationUtilTest, IsNotRestoreSessionUrl) {
EXPECT_FALSE(IsRestoreSessionUrl(GURL()));
EXPECT_FALSE(IsRestoreSessionUrl(GURL("file://somefile")));
EXPECT_FALSE(IsRestoreSessionUrl(GURL("http://www.1.com")));
}
// Tests that CreateRedirectUrl and ExtractTargetURL used back-to-back is an
// identity transformation.
TEST_F(WKNavigationUtilTest, CreateAndExtractTargetURL) {
GURL target_url = GURL("http://www.1.com?query=special%26chars");
GURL url = CreateRedirectUrl(target_url);
ASSERT_TRUE(url.SchemeIsFile());
GURL extracted_url;
ASSERT_TRUE(ExtractTargetURL(url, &extracted_url));
EXPECT_EQ(target_url, extracted_url);
}
TEST_F(WKNavigationUtilTest, IsPlaceholderUrl) {
// Valid placeholder URLs.
EXPECT_TRUE(IsPlaceholderUrl(GURL("about:blank?for=")));
EXPECT_TRUE(IsPlaceholderUrl(GURL("about:blank?for=chrome%3A%2F%2Fnewtab")));
// Not an about:blank URL.
EXPECT_FALSE(IsPlaceholderUrl(GURL::EmptyGURL()));
// Missing ?for= query parameter.
EXPECT_FALSE(IsPlaceholderUrl(GURL("about:blank")));
EXPECT_FALSE(IsPlaceholderUrl(GURL("about:blank?chrome:%3A%2F%2Fnewtab")));
}
TEST_F(WKNavigationUtilTest, EncodReturnsEmptyOnInvalidUrls) {
EXPECT_EQ(GURL::EmptyGURL(), CreatePlaceholderUrlForUrl(GURL::EmptyGURL()));
EXPECT_EQ(GURL::EmptyGURL(), CreatePlaceholderUrlForUrl(GURL("notaurl")));
}
TEST_F(WKNavigationUtilTest, EncodeDecodeValidUrls) {
{
GURL original("chrome://chrome-urls");
GURL encoded("about:blank?for=chrome%3A%2F%2Fchrome-urls");
EXPECT_EQ(encoded, CreatePlaceholderUrlForUrl(original));
EXPECT_EQ(original, ExtractUrlFromPlaceholderUrl(encoded));
}
{
GURL original("about:blank");
GURL encoded("about:blank?for=about%3Ablank");
EXPECT_EQ(encoded, CreatePlaceholderUrlForUrl(original));
EXPECT_EQ(original, ExtractUrlFromPlaceholderUrl(encoded));
}
}
// Tests that invalid URLs will be rejected in decoding.
TEST_F(WKNavigationUtilTest, DecodeRejectInvalidUrls) {
GURL encoded("about:blank?for=thisisnotanurl");
EXPECT_EQ(GURL::EmptyGURL(), ExtractUrlFromPlaceholderUrl(encoded));
}
// Tests that app specific urls and non-placeholder about: urls do not need a
// user agent type, but normal urls and placeholders do.
TEST_F(WKNavigationUtilTest, URLNeedsUserAgentType) {
// Not app specific or non-placeholder about urls.
GURL non_user_agent_urls("http://newtab");
GURL::Replacements scheme_replacements;
scheme_replacements.SetSchemeStr(kTestNativeContentScheme);
EXPECT_FALSE(URLNeedsUserAgentType(
non_user_agent_urls.ReplaceComponents(scheme_replacements)));
scheme_replacements.SetSchemeStr(url::kAboutScheme);
EXPECT_FALSE(URLNeedsUserAgentType(
non_user_agent_urls.ReplaceComponents(scheme_replacements)));
// Not a placeholder or normal URL.
EXPECT_TRUE(URLNeedsUserAgentType(GURL("about:blank?for=")));
EXPECT_TRUE(URLNeedsUserAgentType(GURL("http://www.0.com")));
// file:// URL.
EXPECT_FALSE(URLNeedsUserAgentType(GURL("file://foo.pdf")));
// App specific URL or a placeholder for an app specific URL.
GURL app_specific(
url::SchemeHostPort(kTestAppSpecificScheme, "foo", 0).Serialize());
EXPECT_FALSE(URLNeedsUserAgentType(app_specific));
EXPECT_FALSE(URLNeedsUserAgentType(CreatePlaceholderUrlForUrl(app_specific)));
}
} // namespace wk_navigation_util
} // namespace web