History Restoration in WKWebView (and Error Pages) - my-swift-lab/firefox-ios GitHub Wiki
WKWebView has no native API to restore history in the web view.
History is restored by using injected JS, the sessionrestore.html file in the bundle.
Overview:
- Tab history is queried from the
WKWebView.backForwardListand stored on disk, along with the current position in the list. - On tab restore,
sessionrestore.htmlis loaded, and arguments are passed to the page with the list of history URLs, and the current position -
sessionrestore.htmlperformswindow.history.pushState()for each URL in the history, then sets the current position in the history usingwindow.history.go().
- WKURLScheme is used to provide single-origin URLs for manipulating
window.history. The scheme and domain used isinternal://local/; the same-origin-restricted JS APIs we use are satisfied by that root URL. -
internal://local/sessionrestore?history=<list of URLs and current index>is the first URL loaded in a tab. Thehistoryparam is parsed, and each item is pushed using an URL format ofinternal://local/sessionrestore?url=<actual URL>to populate the browser history. - The
sessionrestore.htmlis loaded usingWKWebView.load(request), and theloadAPI creates an entry on the history stack for the webview. Thus, the current page has to be replaced in-place to avoid further modifying the stack. This is done withlocation.replace(<actual URL>)in JS. - Location replace also has to happen when going back/forward in the history, these urls will appear as
internal://local/sessionrestore?url=<actual URL>. The WKURLScheme handler will load a placeholder page in the web view first, then replace the location to the actual URL. This appears to be the only method of redirection using WKURLScheme (and related) API.
- Not mentioned above is that internal:// URLs have a
uuidkey=<a uuid>URL parameter. The UUID is generated once at app start, and internal history URLs must have a validuuidkeyparam to be loaded. This ensures only the native history restoration can push these URLs.
WKWebView delegate didFailProvisionalNavigation will be called when a page fails to load.
Note that the webview history will be unchanged at this point.
To provide a history entry for showing the error page in-content, WKWebView.load(request) is used to load a HTML page which will show the internal error page; different HTML is shown depending on the type of error.
The error page urls are of the format internal://local/errorpage?<many params> with URL parameters providing the original URL and details of the error.
The error page (i.e. the page with the URL internal://local/errorpage?...) acts a placeholder in the history for back/forward navigation, and for history restoration. If the original page needs to be loaded, the original URL is extracted from the error page and is loaded in-place with location.replace (any equivalent JS is acceptable that avoids doing a WKWebView.load and blowing away the forward history).
To ensure only the app can request these, they are secured with the uuidkey URL param as above.