| <!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 previous pushStates may |
| // not immediately be available. Set a 10ms interval delay until |
| // history.length reaches sessionHistory.length. |
| var currentItemOffset = parseInt(sessionHistoryObject.offset); |
| var goWhenReady = setInterval(() => { |
| if (history.length == sessionHistoryObject.urls.length) { |
| history.go(currentItemOffset); |
| window.clearInterval(goWhenReady); |
| |
| // Queue a reload to redirect to the target URL after history.go |
| // is processed. |
| setTimeout(() => { |
| location.reload(); |
| }); |
| } |
| }, 10); |
| } catch (e) { |
| handleError(e.name + ": " + e.message + " raw session history: " + sessionHistory); |
| } |
| } |
| |
| /** |
| * 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> |