Commit ef382103 authored by Danyao Wang's avatar Danyao Wang Committed by Commit Bot

[Nav Experiment] Reimplement restore_session.html using URL fragment.

The WebKit History API bug only affects push/replaceState calls that
change the URL path and query from file:// scheme. It turns out that
changing URL fragment is still allowed. This CL reimplements restore
session behavior by encoding all data in URL fragment and removes the
iOS11.3 guard for WebClient::IsSlimNavigationManagerEnabled().

Bug: 814803
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: Ib26734b2d62cbcfc1d8f8844e317fdb5842b6b16
Reviewed-on: https://chromium-review.googlesource.com/1024929
Commit-Queue: Danyao Wang <danyao@chromium.org>
Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Cr-Commit-Position: refs/heads/master@{#553119}
parent 2004f4bc
......@@ -21,13 +21,22 @@
window.location.host,
window.location.pathname].join('');
if (window.location.search == '') {
handleError("Search query is mandatory");
if (window.location.hash == '') {
handleError("URL fragment is mandatory");
}
let params = new URLSearchParams(document.location.search.substring(1));
let sessionHistory = params.get("session");
let targetUrl = params.get("targetUrl");
// Helper function to interpret the URL fragment as a key/value pair. If
// the URL fragment starts with 'prefix', interprets the remainder of the
// fragment as a URI-encoded value and returns the decoded value.
// Otherwise, returns null.
function ExtractHashValueForPrefix(prefix) {
if (!location.hash.startsWith(prefix))
return null;
return decodeURIComponent(location.hash.substring(prefix.length));
}
let sessionHistory = ExtractHashValueForPrefix("#session=");
let targetUrl = ExtractHashValueForPrefix("#targetUrl=");
if (sessionHistory) {
restoreSession(baseUrl, sessionHistory);
} else if (targetUrl) {
......@@ -66,7 +75,7 @@
*/
function restoreSession(baseUrl, sessionHistory) {
function getRestoreURL(targetUrl) {
return baseUrl + '?targetUrl=' + encodeURIComponent(targetUrl);
return baseUrl + '#targetUrl=' + encodeURIComponent(targetUrl);
}
var sessionHistoryObject = {};
......@@ -99,11 +108,10 @@
/**
* Redirects to a target URL.
* @param {string} encodedTargetUrl An escaped version of the target URL.
* @param {string} targetUrl The target URL to redirect to.
*/
function redirect(encodedTargetUrl) {
var target = decodeURIComponent(encodedTargetUrl);
window.location.replace(target);
function redirect(targetUrl) {
window.location.replace(targetUrl);
}
/**
......
......@@ -681,8 +681,14 @@ WKBasedNavigationManagerImpl::WKWebViewCache::GetNavigationItemImplAtIndex(
GURL virtual_url;
bool success = wk_navigation_util::ExtractTargetURL(url, &virtual_url);
DCHECK(success);
if (success)
new_item->SetVirtualURL(virtual_url);
if (success) {
if (wk_navigation_util::IsPlaceholderUrl(virtual_url)) {
new_item->SetVirtualURL(
wk_navigation_util::ExtractUrlFromPlaceholderUrl(virtual_url));
} else {
new_item->SetVirtualURL(virtual_url);
}
}
}
SetNavigationItemInWKItem(wk_item, std::move(new_item));
......
......@@ -20,15 +20,14 @@
#import "ios/web/public/web_client.h"
#import "ios/web/test/fakes/crw_fake_back_forward_list.h"
#include "ios/web/test/test_url_constants.h"
#include "net/base/escape.h"
#import "net/base/mac/url_conversions.h"
#include "net/base/url_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
#include "third_party/ocmock/OCMock/OCMock.h"
#include "ui/base/page_transition_types.h"
#include "url/scheme_host_port.h"
#include "url/url_util.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
......@@ -107,6 +106,16 @@ class WKBasedNavigationManagerTest : public PlatformTest {
url::AddStandardScheme(kSchemeToRewrite, url::SCHEME_WITH_HOST);
}
// Returns the value of the "#session=" URL hash component from |url|.
static std::string ExtractRestoredSession(const GURL& url) {
std::string decoded = net::UnescapeURLComponent(
url.ref(), net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS |
net::UnescapeRule::SPACES |
net::UnescapeRule::PATH_SEPARATORS);
return decoded.substr(
strlen(wk_navigation_util::kRestoreSessionSessionHashPrefix));
}
std::unique_ptr<NavigationManagerImpl> manager_;
CRWFakeBackForwardList* mock_wk_list_;
id mock_web_view_;
......@@ -607,14 +616,10 @@ TEST_F(WKBasedNavigationManagerTest, RestoreSessionWithHistory) {
EXPECT_EQ("restore_session.html", pending_url.ExtractFileName());
EXPECT_EQ("http://www.0.com/", pending_item->GetVirtualURL());
std::string session_json;
net::GetValueForKeyInQuery(pending_url,
wk_navigation_util::kRestoreSessionSessionQueryKey,
&session_json);
EXPECT_EQ(
"{\"offset\":0,\"titles\":[\"Test Website 0\",\"\"],"
"\"urls\":[\"http://www.0.com/\",\"http://www.1.com/\"]}",
session_json);
ExtractRestoredSession(pending_url));
}
// Tests that restoring session replaces existing history in navigation manager.
......@@ -689,9 +694,7 @@ TEST_F(WKBasedNavigationManagerTest, RestoreSessionWithEmptyHistory) {
// the target URL.
TEST_F(WKBasedNavigationManagerTest, HideInternalRedirectUrl) {
GURL target_url = GURL("http://www.1.com?query=special%26chars");
GURL url = net::AppendQueryParameter(
wk_navigation_util::GetRestoreSessionBaseUrl(),
wk_navigation_util::kRestoreSessionTargetUrlQueryKey, target_url.spec());
GURL url = wk_navigation_util::CreateRedirectUrl(target_url);
NSString* url_spec = base::SysUTF8ToNSString(url.spec());
[mock_wk_list_ setCurrentURL:url_spec];
NavigationItem* item = manager_->GetItemAtIndex(0);
......@@ -809,14 +812,6 @@ class WKBasedNavigationManagerDetachedModeTest
ASSERT_EQ(url2_, manager_->GetItemAtIndex(2)->GetURL());
}
// Returns the value of the "?session" URL param from |url|.
static std::string ExtractRestoredSession(const GURL& url) {
std::string session_json;
net::GetValueForKeyInQuery(
url, wk_navigation_util::kRestoreSessionSessionQueryKey, &session_json);
return session_json;
}
GURL url0_;
GURL url1_;
GURL url2_;
......
......@@ -29,12 +29,12 @@ class NavigationItem;
namespace wk_navigation_util {
// Query parameter key used to encode the session history to inject in a
// URL fragment prefix used to encode the session history to inject in a
// restore_session.html URL.
extern const char kRestoreSessionSessionQueryKey[];
extern const char kRestoreSessionSessionHashPrefix[];
// Query parameter key used to encode target URL in a restore_session.html URL.
extern const char kRestoreSessionTargetUrlQueryKey[];
// URL fragment prefix used to encode target URL in a restore_session.html URL.
extern const char kRestoreSessionTargetUrlHashPrefix[];
// Returns true if |url| is a placeholder URL or restore_session.html URL.
bool IsWKInternalUrl(const GURL& url);
......@@ -44,7 +44,7 @@ bool IsWKInternalUrl(const GURL& url);
GURL GetRestoreSessionBaseUrl();
// Creates a restore_session.html URL with the provided session history encoded
// in the query argument, such that when this URL is loaded in the web view,
// in the URL fragment, such that when this URL is loaded in the web view,
// recreates all the history entries in |items| and the current loaded item is
// the entry at |last_committed_item_index|.
GURL CreateRestoreSessionUrl(
......@@ -55,16 +55,17 @@ GURL CreateRestoreSessionUrl(
bool IsRestoreSessionUrl(const GURL& url);
// Creates a restore_session.html URL that encodes the specified |target_url| in
// its 'targetUrl' query component. When this URL is loaded in the web view, it
// executes a client-side redirect to |target_url|. This results in a new
// navigation entry and prunes forward navigation history. This URL is used by
// WKBasedNavigationManagerImpl to reload a page with user agent override, as
// reloading |target_url| directly doesn't create a new navigation entry.
// the URL fragment with a "targetUrl=" prefix. When this URL is loaded in the
// web view, it executes a client-side redirect to |target_url|. This results in
// a new navigation entry and prunes forward navigation history. This URL is
// used by WKBasedNavigationManagerImpl to reload a page with user agent
// override, as reloading |target_url| directly doesn't create a new navigation
// entry.
GURL CreateRedirectUrl(const GURL& target_url);
// Extracts the URL encoded in the 'targetUrl' query component of
// |restore_session_url| to |target_url| and returns true. If no such query
// component exists, returns false.
// Extracts the URL encoded in the URL fragment of |restore_session_url| to
// |target_url| and returns true. If the URL fragment does not have a
// "targetUrl=" prefix, returns false.
bool ExtractTargetURL(const GURL& restore_session_url, GURL* target_url);
// Returns true if |URL| is a placeholder navigation URL.
......
......@@ -22,8 +22,8 @@
namespace web {
namespace wk_navigation_util {
const char kRestoreSessionSessionQueryKey[] = "session";
const char kRestoreSessionTargetUrlQueryKey[] = "targetUrl";
const char kRestoreSessionSessionHashPrefix[] = "session=";
const char kRestoreSessionTargetUrlHashPrefix[] = "targetUrl=";
const char kOriginalUrlKey[] = "for";
bool IsWKInternalUrl(const GURL& url) {
......@@ -70,8 +70,12 @@ GURL CreateRestoreSessionUrl(
std::string session_json;
base::JSONWriter::Write(session, &session_json);
return net::AppendQueryParameter(
GetRestoreSessionBaseUrl(), kRestoreSessionSessionQueryKey, 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) {
......@@ -79,9 +83,12 @@ bool IsRestoreSessionUrl(const GURL& url) {
}
GURL CreateRedirectUrl(const GURL& target_url) {
return net::AppendQueryParameter(GetRestoreSessionBaseUrl(),
kRestoreSessionTargetUrlQueryKey,
target_url.spec());
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) {
......@@ -89,10 +96,17 @@ bool ExtractTargetURL(const GURL& restore_session_url, GURL* target_url) {
<< restore_session_url.possibly_invalid_spec()
<< " is not a restore session URL";
std::string target_url_spec;
bool success = net::GetValueForKeyInQuery(
restore_session_url, kRestoreSessionTargetUrlQueryKey, &target_url_spec);
if (success)
*target_url = GURL(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;
}
......
......@@ -10,7 +10,7 @@
#include "base/strings/utf_string_conversions.h"
#import "ios/web/navigation/navigation_item_impl.h"
#include "ios/web/test/test_url_constants.h"
#include "net/base/url_util.h"
#include "net/base/escape.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/platform_test.h"
......@@ -45,11 +45,13 @@ TEST_F(WKNavigationUtilTest, CreateRestoreSessionUrl) {
CreateRestoreSessionUrl(0 /* last_committed_item_index */, items);
ASSERT_TRUE(IsRestoreSessionUrl(restore_session_url));
std::string session_json;
net::GetValueForKeyInQuery(restore_session_url,
kRestoreSessionSessionQueryKey, &session_json);
net::UnescapeRule::Type unescape_rules =
net::UnescapeRule::URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS |
net::UnescapeRule::SPACES | net::UnescapeRule::PATH_SEPARATORS;
std::string session_json =
net::UnescapeURLComponent(restore_session_url.ref(), unescape_rules);
EXPECT_EQ(
"{\"offset\":-2,\"titles\":[\"Test Website 0\",\"\",\"\"],"
"session={\"offset\":-2,\"titles\":[\"Test Website 0\",\"\",\"\"],"
"\"urls\":[\"http://www.0.com/\",\"http://www.1.com/\","
"\"about:blank?for=testwebui%3A%2F%2Fwebui%2F\"]}",
session_json);
......
......@@ -96,15 +96,7 @@ void WebClient::AllowCertificateError(
}
bool WebClient::IsSlimNavigationManagerEnabled() const {
if (@available(iOS 11.3, *)) {
// Starting iOS 11.3, pushState and replaceState are not allowed in file://
// scheme which the new navigation manager relies on. So this excludes the
// newer iOS versions until this bug is fixed.
// TODO(crbug.com/814803): Remove this workaround.
return false;
} else {
return base::FeatureList::IsEnabled(web::features::kSlimNavigationManager);
}
return base::FeatureList::IsEnabled(web::features::kSlimNavigationManager);
}
void WebClient::PrepareErrorPage(NSError* error,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment