<!doctype html>
<html>
<head>
<script>
/**
* Main entry point. This page operates in two modes:
* 1. If ?session= is provided, restoreSession() parses URLs from the query
* parameter and uses History API to insert into the current session
* history an entry for each URL in order. Due to same origin policy,
* the entries link back to this page with the target URL encoded in
* the query parameter. The actual redirection takes place when user
* navigates to the restored session entries.
* 2. If ?targetUrl= is provided, redirect() immediately redirect the page
* to the URL encoded in the query parameter.
*/
window.onload = function() {
// Remember the base URL of this page before it is changed by the History
// API calls so it can be used to create redirect page URLs.
let baseUrl = [
window.location.protocol,
window.location.host,
window.location.pathname].join('');
if (window.location.hash == '') {
handleError("URL fragment is mandatory");
}
// 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) {
redirect(targetUrl);
} else {
handleError("Missing both sessionHistory and targetUrl");
}
};
/**
* Manipulates the current session history to mimic the provided serialized
* history.
* @param {string} sessionHistory An string serialization of a JSON object
* that represents the session history to recreate. It contains three
* fields:
* urls: A list of strings that represent the URLs visited in the session
* in order.
* titles: A list of strings that represent the page title of each URL in
* the URL list. It must have the same length as 'urls'.
* offset: An non-positive integer that represents the last visible entry
* relative to the end of the list. This is used to jump back to the
* last visible entry after restoring the entire list.
* @param {string} baseUrl The URL of this page without the query parameter.
* The restored history entry initially points to this page with the
* target URL encoded in the query parameter. A user is redirected to the
* target URL when they navigates to the restored entry.
*/
function restoreSession(baseUrl, sessionHistory) {
function getRestoreURL(targetUrl) {
return baseUrl + '#targetUrl=' + encodeURIComponent(targetUrl);
}
var sessionHistoryObject = {};
try {
sessionHistoryObject = JSON.parse(sessionHistory);
if (sessionHistoryObject.urls.length < 1) {
handleError("sessionHistory is empty");
return;
}
history.replaceState(
null, /* state */
sessionHistoryObject.titles[0] || "Untitled",
getRestoreURL(sessionHistoryObject.urls[0] || "about:blank"));
for (var i = 1; i < sessionHistoryObject.urls.length; i++) {
history.pushState(
null, /* state */
sessionHistoryObject.titles[i] || "Untitled",
getRestoreURL(sessionHistoryObject.urls[i] || "about:blank"));
}
// iOS12.2 added a throttling mechanism where the previous pushStates
// may not immediately be available. Instead of directly calling
// finishRestore(currentItemOffset) here, call it via a round trip
// native call to flush out the pushState IPC backlog.
var currentItemOffset = parseInt(sessionHistoryObject.offset);
var offset = {'offset': currentItemOffset}
window.webkit.messageHandlers['session_restore'].postMessage(offset);
} catch (e) {
handleError(e.name + ": " + e.message + " raw session history: " + sessionHistory);
}
}
/**
* Finishes restoration by history.go-ing to |offset| and reloading the
* page.
* @param {int} offset Either zero, or the number of pages (negative) to
* go backwards.
*/
function _crFinishSessionRestoration(offset) {
// Webkit indirectly forces a user gesture on the final restore
// navigations because this method is called from native code via
// ExecuteJavaScript. This has unintended consequences (such as triggering
// universal app links). Instead, discard the gesture by calling the same
// setInterval twice as WebKit won't propagate the gesture the second
// time.
var skipped_first_interval = false;
var finalizeInterval = setInterval(() => {
if (skipped_first_interval) {
try {
window.clearInterval(finalizeInterval);
history.go(offset);
// Queue a reload to redirect to the target URL after history.go
// is processed.
setTimeout(() => {
location.reload();
});
} catch (e) {
handleError(e.name + ": " + e.message + " offset: " + offset);
}
}
skipped_first_interval = true;
}, 0)
}
/**
* Redirects to a target URL.
* @param {string} targetUrl The target URL to redirect to.
*/
function redirect(targetUrl) {
window.location.replace(targetUrl);
}
/**
* print error and show blank page.
*/
function handleError(message) {
console.log("Error: " + message);
window.location.replace("about:blank");
}
</script>
</head>
<body>
</body>
</html>