chromium/ios/web/navigation/resources/restore_session.html

<!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>