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.backForwardList
and stored on disk, along with the current position in the list. - On tab restore,
sessionrestore.html
is loaded, and arguments are passed to the page with the list of history URLs, and the current position -
sessionrestore.html
performswindow.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. Thehistory
param is parsed, and each item is pushed using an URL format ofinternal://local/sessionrestore?url=<actual URL>
to populate the browser history. - The
sessionrestore.html
is loaded usingWKWebView.load(request)
, and theload
API 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 validuuidkey
param 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.