-
Notifications
You must be signed in to change notification settings - Fork 32
/
Copy pathColdBootVisit.swift
134 lines (109 loc) · 4.48 KB
/
ColdBootVisit.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import Foundation
import WebKit
/// A "Cold Boot" visit is the initial visit to load the page, including all resources.
/// Subsequent visits go through Turbo and use `JavaScriptVisit`.
final class ColdBootVisit: Visit {
private(set) var navigation: WKNavigation?
override func startVisit() {
log("startVisit")
webView.navigationDelegate = self
bridge.pageLoadDelegate = self
if let response = options.response, response.isSuccessful, let body = response.responseHTML {
navigation = webView.loadHTMLString(body, baseURL: location)
} else {
navigation = webView.load(URLRequest(url: location))
}
delegate?.visitDidStart(self)
startRequest()
}
override func cancelVisit() {
log("cancelVisit")
removeNavigationDelegate()
webView.stopLoading()
finishRequest()
}
override func completeVisit() {
log("completeVisit")
removeNavigationDelegate()
delegate?.visitDidInitializeWebView(self)
}
override func failVisit() {
log("failVisit")
removeNavigationDelegate()
finishRequest()
}
private func removeNavigationDelegate() {
guard webView.navigationDelegate === self else { return }
webView.navigationDelegate = nil
}
private func log(_ name: String) {
logger.debug("[ColdBootVisit] \(name) \(self.location.absoluteString)")
}
}
extension ColdBootVisit: WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
guard navigation == self.navigation else { return }
finishRequest()
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
// Ignore any clicked links before the cold boot finishes navigation
if navigationAction.navigationType == .linkActivated {
decisionHandler(.cancel)
if let url = navigationAction.request.url {
UIApplication.shared.open(url)
}
return
}
guard let url = navigationAction.request.url else {
decisionHandler(.cancel)
return
}
let isRedirect = location != url
let redirectIsCrossOrigin = isRedirect && location.host != url.host
if redirectIsCrossOrigin {
log("Cross-origin redirect detected: \(location) -> \(url).")
decisionHandler(.cancel)
UIApplication.shared.open(url)
return
}
if isRedirect {
log("Same-origin redirect detected: \(location) -> \(url).")
}
decisionHandler(.allow)
}
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
if let httpResponse = navigationResponse.response as? HTTPURLResponse {
if httpResponse.statusCode >= 200 && httpResponse.statusCode < 300 {
decisionHandler(.allow)
} else {
decisionHandler(.cancel)
fail(with: TurboError.http(statusCode: httpResponse.statusCode))
}
} else {
if navigationResponse.response.url?.scheme == "blob" {
decisionHandler(.allow)
} else {
decisionHandler(.cancel)
fail(with: TurboError.http(statusCode: 0))
}
}
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
guard navigation == self.navigation else { return }
fail(with: error)
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
guard navigation == self.navigation else { return }
fail(with: error)
}
func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
delegate?.visit(self, didReceiveAuthenticationChallenge: challenge, completionHandler: completionHandler)
}
}
extension ColdBootVisit: WebViewPageLoadDelegate {
func webView(_ bridge: WebViewBridge, didLoadPageWithRestorationIdentifier restorationIdentifier: String) {
self.restorationIdentifier = restorationIdentifier
delegate?.visitDidRender(self)
complete()
}
}