| // Copyright 2017 The Chromium Authors |
| // 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" |
| |
| #import <algorithm> |
| |
| #import "base/apple/bundle_locations.h" |
| #import "base/json/json_writer.h" |
| #import "base/metrics/field_trial_params.h" |
| #import "base/strings/escape.h" |
| #import "base/strings/string_util.h" |
| #import "base/strings/sys_string_conversions.h" |
| #import "base/values.h" |
| #import "ios/web/common/features.h" |
| #import "ios/web/navigation/crw_error_page_helper.h" |
| #import "ios/web/public/navigation/navigation_item.h" |
| #import "ios/web/public/web_client.h" |
| #import "net/base/url_util.h" |
| #import "url/url_constants.h" |
| |
| 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="; |
| NSString* const kReferrerHeaderName = @"Referer"; |
| |
| int GetSafeItemRange(int last_committed_item_index, |
| int item_count, |
| int* offset, |
| int* size) { |
| *size = std::min(kMaxSessionSize, item_count); |
| *offset = std::min(last_committed_item_index - kMaxSessionSize / 2, |
| item_count - kMaxSessionSize); |
| *offset = std::max(*offset, 0); |
| return last_committed_item_index - *offset; |
| } |
| |
| bool IsWKInternalUrl(const GURL& url) { |
| return IsRestoreSessionUrl(url); |
| } |
| |
| bool IsWKInternalUrl(NSURL* url) { |
| return IsRestoreSessionUrl(url); |
| } |
| |
| bool URLNeedsUserAgentType(const GURL& url) { |
| if (web::GetWebClient()->IsAppSpecificURL(url)) |
| return false; |
| |
| if (url.SchemeIs(url::kAboutScheme)) |
| return false; |
| |
| if (url.SchemeIs(url::kFileScheme) && IsRestoreSessionUrl(url)) |
| return true; |
| |
| if (url.SchemeIs(url::kFileScheme) && |
| [CRWErrorPageHelper isErrorPageFileURL:url]) { |
| return true; |
| } |
| |
| if (url.SchemeIs(url::kFileScheme)) |
| return false; |
| |
| return true; |
| } |
| |
| GURL GetRestoreSessionBaseUrl() { |
| std::string restore_session_resource_path = base::SysNSStringToUTF8( |
| [base::apple::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); |
| } |
| |
| void CreateRestoreSessionUrl( |
| int last_committed_item_index, |
| const std::vector<std::unique_ptr<NavigationItem>>& items, |
| GURL* url, |
| int* first_index) { |
| DCHECK(last_committed_item_index >= 0 && |
| last_committed_item_index < static_cast<int>(items.size())); |
| |
| int first_restored_item_offset = 0; |
| int new_size = 0; |
| int new_last_committed_item_index = |
| GetSafeItemRange(last_committed_item_index, items.size(), |
| &first_restored_item_offset, &new_size); |
| |
| // 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::List restored_urls; |
| base::Value::List restored_titles; |
| for (int i = first_restored_item_offset; |
| i < new_size + first_restored_item_offset; i++) { |
| NavigationItem* item = items[i].get(); |
| restored_urls.Append(item->GetURL().spec()); |
| restored_titles.Append(item->GetTitle()); |
| } |
| base::Value::Dict session; |
| int committed_item_offset = new_last_committed_item_index + 1 - new_size; |
| session.Set("offset", committed_item_offset); |
| session.Set("urls", std::move(restored_urls)); |
| session.Set("titles", std::move(restored_titles)); |
| |
| std::string session_json; |
| base::JSONWriter::Write(session, &session_json); |
| std::string ref = |
| kRestoreSessionSessionHashPrefix + |
| base::EscapeQueryParamValue(session_json, false /* use_plus */); |
| GURL::Replacements replacements; |
| replacements.SetRefStr(ref); |
| *first_index = first_restored_item_offset; |
| *url = GetRestoreSessionBaseUrl().ReplaceComponents(replacements); |
| } |
| |
| bool IsRestoreSessionUrl(const GURL& url) { |
| return url.SchemeIsFile() && url.path() == GetRestoreSessionBaseUrl().path(); |
| } |
| |
| bool IsRestoreSessionUrl(NSURL* url) { |
| return [url.scheme isEqualToString:@"file"] && |
| [url.path isEqualToString:base::SysUTF8ToNSString( |
| GetRestoreSessionBaseUrl().path())]; |
| } |
| |
| 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 = base::StartsWith(restore_session_url.ref(), |
| kRestoreSessionTargetUrlHashPrefix); |
| if (success) { |
| std::string encoded_target_url = restore_session_url.ref().substr( |
| strlen(kRestoreSessionTargetUrlHashPrefix)); |
| *target_url = GURL(base::UnescapeBinaryURLComponent(encoded_target_url)); |
| } |
| |
| return success; |
| } |
| |
| } // namespace wk_navigation_util |
| } // namespace web |