| // 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 "base/json/json_writer.h" |
| #include "base/mac/bundle_locations.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/values.h" |
| #import "ios/web/public/navigation_item.h" |
| #import "ios/web/public/web_client.h" |
| #include "net/base/escape.h" |
| #include "net/base/url_util.h" |
| #include "url/url_constants.h" |
| |
| #if !defined(__has_feature) || !__has_feature(objc_arc) |
| #error "This file requires ARC support." |
| #endif |
| |
| namespace web { |
| namespace wk_navigation_util { |
| |
| // Session restoration algorithms uses pushState calls to restore back forward |
| // navigation list. WKWebView does not allow pushing more than 100 items per |
| // 30 seconds. Limiting max session size to 75 will allow web pages to use push |
| // state calls. |
| const int kMaxSessionSize = 75; |
| |
| const char kRestoreSessionSessionHashPrefix[] = "session="; |
| const char kRestoreSessionTargetUrlHashPrefix[] = "targetUrl="; |
| const char kOriginalUrlKey[] = "for"; |
| |
| namespace { |
| // Returns begin and end iterators for the given navigation items. The length of |
| // these iterators range will not exceed kMaxSessionSize. If |items.size()| is |
| // greater than kMaxSessionSize, then this function will trim navigation items, |
| // which are the furthest to |last_committed_item_index|. |
| void GetSafeItemIterators( |
| int last_committed_item_index, |
| const std::vector<std::unique_ptr<NavigationItem>>& items, |
| std::vector<std::unique_ptr<NavigationItem>>::const_iterator* begin, |
| std::vector<std::unique_ptr<NavigationItem>>::const_iterator* end) { |
| if (items.size() <= kMaxSessionSize) { |
| // No need to trim anything. |
| *begin = items.begin(); |
| *end = items.end(); |
| return; |
| } |
| |
| if (last_committed_item_index < kMaxSessionSize / 2) { |
| // Items which are the furthest to |last_committed_item_index| are located |
| // on the right side of the vector. Trim those. |
| *begin = items.begin(); |
| *end = items.begin() + kMaxSessionSize; |
| return; |
| } |
| |
| if (items.size() - last_committed_item_index < kMaxSessionSize / 2) { |
| // Items which are the furthest to |last_committed_item_index| are located |
| // on the left side of the vector. Trim those. |
| *begin = items.end() - kMaxSessionSize; |
| *end = items.end(); |
| return; |
| } |
| |
| // Trim items from both sides of the vector. Keep the same number of items |
| // on the left and right side of |last_committed_item_index|. |
| *begin = items.begin() + last_committed_item_index - kMaxSessionSize / 2; |
| *end = items.begin() + last_committed_item_index + kMaxSessionSize / 2 + 1; |
| } |
| } |
| |
| bool IsWKInternalUrl(const GURL& url) { |
| return IsPlaceholderUrl(url) || IsRestoreSessionUrl(url); |
| } |
| |
| bool URLNeedsUserAgentType(const GURL& url) { |
| if (web::GetWebClient()->IsAppSpecificURL(url)) |
| return false; |
| |
| if (url.SchemeIs(url::kAboutScheme) && IsPlaceholderUrl(url)) { |
| return !web::GetWebClient()->IsAppSpecificURL( |
| ExtractUrlFromPlaceholderUrl(url)); |
| } |
| |
| if (url.SchemeIs(url::kAboutScheme)) |
| return false; |
| |
| if (url.SchemeIs(url::kFileScheme) && IsRestoreSessionUrl(url)) |
| return true; |
| |
| if (url.SchemeIs(url::kFileScheme)) |
| return false; |
| |
| return true; |
| } |
| |
| GURL GetRestoreSessionBaseUrl() { |
| std::string restore_session_resource_path = base::SysNSStringToUTF8( |
| [base::mac::FrameworkBundle() pathForResource:@"restore_session" |
| ofType:@"html"]); |
| GURL::Replacements replacements; |
| replacements.SetSchemeStr(url::kFileScheme); |
| replacements.SetPathStr(restore_session_resource_path); |
| return GURL(url::kAboutBlankURL).ReplaceComponents(replacements); |
| } |
| |
| GURL CreateRestoreSessionUrl( |
| int last_committed_item_index, |
| const std::vector<std::unique_ptr<NavigationItem>>& items) { |
| DCHECK(last_committed_item_index >= 0 && |
| last_committed_item_index < static_cast<int>(items.size())); |
| |
| std::vector<std::unique_ptr<NavigationItem>>::const_iterator begin; |
| std::vector<std::unique_ptr<NavigationItem>>::const_iterator end; |
| GetSafeItemIterators(last_committed_item_index, items, &begin, &end); |
| size_t new_size = end - begin; |
| |
| // The URLs and titles of the restored entries are stored in two separate |
| // lists instead of a single list of objects to reduce the size of the JSON |
| // string to be included in the query parameter. |
| base::Value restored_urls(base::Value::Type::LIST); |
| base::Value restored_titles(base::Value::Type::LIST); |
| restored_urls.GetList().reserve(new_size); |
| restored_titles.GetList().reserve(new_size); |
| for (auto it = begin; it != end; ++it) { |
| NavigationItem* item = (*it).get(); |
| GURL original_url = item->GetURL(); |
| GURL restored_url = original_url; |
| if (web::GetWebClient()->IsAppSpecificURL(original_url)) { |
| restored_url = CreatePlaceholderUrlForUrl(original_url); |
| } |
| restored_urls.GetList().push_back(base::Value(restored_url.spec())); |
| restored_titles.GetList().push_back(base::Value(item->GetTitle())); |
| } |
| base::Value session(base::Value::Type::DICTIONARY); |
| int offset = last_committed_item_index + 1 - new_size; |
| session.SetKey("offset", base::Value(offset)); |
| session.SetKey("urls", std::move(restored_urls)); |
| session.SetKey("titles", std::move(restored_titles)); |
| |
| std::string session_json; |
| base::JSONWriter::Write(session, &session_json); |
| std::string ref = |
| kRestoreSessionSessionHashPrefix + |
| net::EscapeQueryParamValue(session_json, false /* use_plus */); |
| GURL::Replacements replacements; |
| replacements.SetRefStr(ref); |
| return GetRestoreSessionBaseUrl().ReplaceComponents(replacements); |
| } |
| |
| bool IsRestoreSessionUrl(const GURL& url) { |
| return url.SchemeIsFile() && url.path() == GetRestoreSessionBaseUrl().path(); |
| } |
| |
| GURL CreateRedirectUrl(const GURL& target_url) { |
| GURL::Replacements replacements; |
| std::string ref = |
| kRestoreSessionTargetUrlHashPrefix + |
| net::EscapeQueryParamValue(target_url.spec(), false /* use_plus */); |
| replacements.SetRefStr(ref); |
| return GetRestoreSessionBaseUrl().ReplaceComponents(replacements); |
| } |
| |
| bool ExtractTargetURL(const GURL& restore_session_url, GURL* target_url) { |
| DCHECK(IsRestoreSessionUrl(restore_session_url)) |
| << restore_session_url.possibly_invalid_spec() |
| << " is not a restore session URL"; |
| std::string target_url_spec; |
| bool success = |
| restore_session_url.ref().find(kRestoreSessionTargetUrlHashPrefix) == 0; |
| if (success) { |
| std::string encoded_target_url = restore_session_url.ref().substr( |
| strlen(kRestoreSessionTargetUrlHashPrefix)); |
| net::UnescapeRule::Type unescape_rules = |
| net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS | |
| net::UnescapeRule::SPACES | net::UnescapeRule::PATH_SEPARATORS; |
| *target_url = |
| GURL(net::UnescapeURLComponent(encoded_target_url, unescape_rules)); |
| } |
| |
| return success; |
| } |
| |
| bool IsPlaceholderUrl(const GURL& url) { |
| return url.IsAboutBlank() && base::StartsWith(url.query(), kOriginalUrlKey, |
| base::CompareCase::SENSITIVE); |
| } |
| |
| GURL CreatePlaceholderUrlForUrl(const GURL& original_url) { |
| if (!original_url.is_valid()) |
| return GURL::EmptyGURL(); |
| |
| GURL placeholder_url = net::AppendQueryParameter( |
| GURL(url::kAboutBlankURL), kOriginalUrlKey, original_url.spec()); |
| DCHECK(placeholder_url.is_valid()); |
| return placeholder_url; |
| } |
| |
| GURL ExtractUrlFromPlaceholderUrl(const GURL& url) { |
| std::string value; |
| if (IsPlaceholderUrl(url) && |
| net::GetValueForKeyInQuery(url, kOriginalUrlKey, &value)) { |
| GURL decoded_url(value); |
| if (decoded_url.is_valid()) |
| return decoded_url; |
| } |
| return GURL::EmptyGURL(); |
| } |
| |
| } // namespace wk_navigation_util |
| } // namespace web |