Skip to content

Commit 19e9626

Browse files
Tieme van VeenTieme van Veen
Tieme van Veen
authored and
Tieme van Veen
committed
Fix pointfreeco#440 - Improve WKWebView navigation delegate
1 parent ce10f46 commit 19e9626

8 files changed

+206
-17
lines changed

SnapshotTesting.xcodeproj/project.pbxproj

+8
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,9 @@
148148
BC1C30E837022BC96B8707B7 /* testUpdateSeveralSnapshotsSwapingLines1.1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 86AF3E4E28A1292D02E080C7 /* testUpdateSeveralSnapshotsSwapingLines1.1.swift */; };
149149
BC673F41050B566E6553814F /* testUpdateSnapshotWithLongerExtendedDelimiter1.1.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1E18803C6CA9CC80FF55033 /* testUpdateSnapshotWithLongerExtendedDelimiter1.1.swift */; };
150150
BD42403C6DDE4A5B453DFA9F /* SnapshotTestingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA789F513A8B1E8EF5BF81E2 /* SnapshotTestingTests.swift */; };
151+
BDB6003F26028DCC00DECEE8 /* ForwardingWKNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6003E26028DCC00DECEE8 /* ForwardingWKNavigationDelegate.swift */; };
152+
BDB6004026028DCC00DECEE8 /* ForwardingWKNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6003E26028DCC00DECEE8 /* ForwardingWKNavigationDelegate.swift */; };
153+
BDB6004126028DCC00DECEE8 /* ForwardingWKNavigationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDB6003E26028DCC00DECEE8 /* ForwardingWKNavigationDelegate.swift */; };
151154
BF239214620668C79B314F6F /* testUpdateSnapshotWithExtendedDelimiter2.1.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9CAC8CA2C073A9D03BC22880 /* testUpdateSnapshotWithExtendedDelimiter2.1.swift */; };
152155
BF4FD7664E50DA3FC34E91CF /* testUpdateSnapshotWithExtendedDelimiter1.1.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D7D0D81B7E35C6284A3E65 /* testUpdateSnapshotWithExtendedDelimiter1.1.swift */; };
153156
C0F19173230428932EDD1A78 /* testUpdateSeveralSnapshotsSwapingLines2.1.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77BE996AE7868E9B58D6C74 /* testUpdateSeveralSnapshotsSwapingLines2.1.swift */; };
@@ -318,6 +321,7 @@
318321
ADACB597E6A3CAC5BFEEFB05 /* testCreateSnapshotWithShorterExtendedDelimiter2.1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = testCreateSnapshotWithShorterExtendedDelimiter2.1.swift; sourceTree = "<group>"; };
319322
B843712AB555B46BA6E4EC2A /* CaseIterable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CaseIterable.swift; sourceTree = "<group>"; };
320323
B8F1FEF91276E0D2BEFD9BC6 /* testCreateSnapshotEscapedNewlineLastLine.1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = testCreateSnapshotEscapedNewlineLastLine.1.swift; sourceTree = "<group>"; };
324+
BDB6003E26028DCC00DECEE8 /* ForwardingWKNavigationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardingWKNavigationDelegate.swift; sourceTree = "<group>"; };
321325
C13AB875AB58FF3BCAC1AA66 /* testCreateSnapshotWithExtendedDelimiterSingleLine1.1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = testCreateSnapshotWithExtendedDelimiterSingleLine1.1.swift; sourceTree = "<group>"; };
322326
CB124D18325606D35B7027A6 /* NSViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSViewController.swift; sourceTree = "<group>"; };
323327
D1FE81FC04984B93B0C27FA6 /* testUpdateSnapshot.1.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = testUpdateSnapshot.1.swift; sourceTree = "<group>"; };
@@ -494,6 +498,7 @@
494498
D49092873A7A5D6DA623E352 /* String+SpecialCharacters.swift */,
495499
0F6B5B3AB021FA3CD7519733 /* View.swift */,
496500
4BE8DE5E8A102E1012CD0C95 /* XCTAttachment.swift */,
501+
BDB6003E26028DCC00DECEE8 /* ForwardingWKNavigationDelegate.swift */,
497502
);
498503
path = Common;
499504
sourceTree = "<group>";
@@ -730,6 +735,7 @@
730735
42EEB5E183FA787232DCF756 /* UIBezierPath.swift in Sources */,
731736
2D9BA837D802B7347244178B /* UIImage.swift in Sources */,
732737
3CB93CF15AA49A2CB461D45E /* UIView.swift in Sources */,
738+
BDB6004026028DCC00DECEE8 /* ForwardingWKNavigationDelegate.swift in Sources */,
733739
1ABB68DCE39CE794B07296A7 /* UIViewController.swift in Sources */,
734740
30413A9E862B77B868A57B65 /* URLRequest.swift in Sources */,
735741
E66A02C861A316EDB4377C2E /* View.swift in Sources */,
@@ -770,6 +776,7 @@
770776
4CF683A5FA0EDF2C6ABC8B10 /* UIBezierPath.swift in Sources */,
771777
0C80A6C3FD6C62FDAC4123B3 /* UIImage.swift in Sources */,
772778
6630A021629D13CD012A62FD /* UIView.swift in Sources */,
779+
BDB6003F26028DCC00DECEE8 /* ForwardingWKNavigationDelegate.swift in Sources */,
773780
65ED4CF70D09F51DAD4C692A /* UIViewController.swift in Sources */,
774781
7B9F0DFC589C66887A37F0FB /* URLRequest.swift in Sources */,
775782
9727BFCBEE9C51BEFB754140 /* View.swift in Sources */,
@@ -882,6 +889,7 @@
882889
5E7F4795AFED0D8D4E1D75A6 /* UIBezierPath.swift in Sources */,
883890
B7C6F1A74C6D9DF70ACC52DB /* UIImage.swift in Sources */,
884891
436C10BC8313288D84733DC5 /* UIView.swift in Sources */,
892+
BDB6004126028DCC00DECEE8 /* ForwardingWKNavigationDelegate.swift in Sources */,
885893
969985906E267A3F203613F1 /* UIViewController.swift in Sources */,
886894
573488A1374FE9CD280A0BEE /* URLRequest.swift in Sources */,
887895
393181237190376E8A9D300E /* View.swift in Sources */,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#if os(iOS) || os(macOS) || os(tvOS)
2+
#if os(macOS)
3+
import Cocoa
4+
#endif
5+
import SceneKit
6+
import SpriteKit
7+
#if os(iOS) || os(tvOS)
8+
import UIKit
9+
#endif
10+
#if os(iOS) || os(macOS)
11+
import WebKit
12+
#endif
13+
14+
#if os(iOS) || os(macOS)
15+
16+
final class ForwardingWKNavigationDelegate: NSObject {
17+
var didFinish: (() -> Void)?
18+
var original: WKNavigationDelegate?
19+
init(didFinish: @escaping () -> Void = {}, originalDelegate: WKNavigationDelegate?) {
20+
self.original = originalDelegate
21+
self.didFinish = didFinish
22+
}
23+
24+
override public func responds(to aSelector: Selector!) -> Bool {
25+
if decidePolicyPre13 == aSelector, original?.responds(to: aSelector) != true {
26+
return NSObject.responds(to: aSelector)
27+
}
28+
29+
if
30+
#available(iOSApplicationExtension 13.0, *, OSXApplicationExtension 10.15, *),
31+
decidePolicy13 == aSelector,
32+
original?.responds(to: aSelector) != true
33+
{
34+
return NSObject.responds(to: aSelector)
35+
}
36+
37+
return interceptedSelectors.contains(aSelector)
38+
|| original?.responds(to: aSelector) == true
39+
|| super.responds(to: aSelector)
40+
}
41+
42+
override public func forwardingTarget(for aSelector: Selector!) -> Any? {
43+
interceptedSelectors.contains(aSelector)
44+
? nil
45+
: original
46+
}
47+
48+
func finish() {
49+
didFinish?()
50+
didFinish = nil
51+
}
52+
}
53+
54+
extension ForwardingWKNavigationDelegate: WKNavigationDelegate {
55+
@available(iOSApplicationExtension 13.0, *, OSXApplicationExtension 10.15, *)
56+
func webView(
57+
_ webView: WKWebView,
58+
decidePolicyFor navigationAction: WKNavigationAction,
59+
preferences: WKWebpagePreferences,
60+
decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void
61+
) {
62+
original?.webView?(
63+
webView,
64+
decidePolicyFor: navigationAction,
65+
preferences: preferences,
66+
decisionHandler: { [weak self] policy, prefs in
67+
decisionHandler(policy, prefs)
68+
if policy == .cancel {
69+
self?.finish()
70+
}
71+
}
72+
)
73+
}
74+
75+
func webView(
76+
_ webView: WKWebView,
77+
decidePolicyFor navigationAction: WKNavigationAction,
78+
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
79+
) {
80+
original?.webView?(webView, decidePolicyFor: navigationAction, decisionHandler: { [weak self] policy in
81+
decisionHandler(policy)
82+
if policy == .cancel {
83+
self?.finish()
84+
}
85+
})
86+
}
87+
88+
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
89+
original?.webView?(webView, didFinish: navigation)
90+
webView.evaluateJavaScript("document.readyState") { [weak self] _, _ in
91+
self?.finish()
92+
}
93+
}
94+
95+
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
96+
original?.webView?(webView, didFail: navigation, withError: error)
97+
finish()
98+
}
99+
100+
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
101+
original?.webView?(webView, didFailProvisionalNavigation: navigation, withError: error)
102+
finish()
103+
}
104+
105+
@available(OSXApplicationExtension 10.11, *)
106+
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
107+
original?.webViewWebContentProcessDidTerminate?(webView)
108+
finish()
109+
}
110+
}
111+
112+
fileprivate let decidePolicyPre13 = #selector(WKNavigationDelegate.webView(_:decidePolicyFor:decisionHandler:) as (
113+
(WKNavigationDelegate) ->
114+
(WKWebView, WKNavigationAction, @escaping (WKNavigationActionPolicy) -> Void) -> Void
115+
)?)
116+
117+
@available(iOSApplicationExtension 13.0, *, OSXApplicationExtension 10.15, *)
118+
fileprivate let decidePolicy13 = #selector(WKNavigationDelegate.webView(_:decidePolicyFor:preferences:decisionHandler:))
119+
120+
fileprivate var interceptedSelectors: Set<Selector> = {
121+
var selectors: Set<Selector> = [
122+
#selector(WKNavigationDelegate.webView(_:didFinish:)),
123+
#selector(WKNavigationDelegate.webView(_:didFail:withError:)),
124+
#selector(WKNavigationDelegate.webView(_:didFailProvisionalNavigation:withError:)),
125+
decidePolicyPre13,
126+
]
127+
128+
if #available(iOSApplicationExtension 13.0, *, OSXApplicationExtension 10.15, *) {
129+
selectors.insert(decidePolicy13)
130+
}
131+
132+
if #available(iOSApplicationExtension 8.0, *, OSXApplicationExtension 10.11, *) {
133+
selectors.insert(#selector(WKNavigationDelegate.webViewWebContentProcessDidTerminate(_:)))
134+
}
135+
return selectors
136+
}()
137+
138+
#endif
139+
#endif

Sources/SnapshotTesting/Common/View.swift

+1-17
Original file line numberDiff line numberDiff line change
@@ -604,7 +604,7 @@ extension View {
604604
#if os(iOS) || os(macOS)
605605
if let wkWebView = self as? WKWebView {
606606
return Async<Image> { callback in
607-
let delegate = NavigationDelegate()
607+
let delegate = ForwardingWKNavigationDelegate(originalDelegate: wkWebView.navigationDelegate)
608608
let work = {
609609
if #available(iOS 11.0, macOS 10.13, *) {
610610
inWindow {
@@ -647,22 +647,6 @@ extension View {
647647
#endif
648648
}
649649

650-
#if os(iOS) || os(macOS)
651-
private final class NavigationDelegate: NSObject, WKNavigationDelegate {
652-
var didFinish: () -> Void
653-
654-
init(didFinish: @escaping () -> Void = {}) {
655-
self.didFinish = didFinish
656-
}
657-
658-
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
659-
webView.evaluateJavaScript("document.readyState") { _, _ in
660-
self.didFinish()
661-
}
662-
}
663-
}
664-
#endif
665-
666650
#if os(iOS) || os(tvOS)
667651
extension UIApplication {
668652
static var sharedIfAvailable: UIApplication? {

Tests/SnapshotTestingTests/SnapshotTestingTests.swift

+58
Original file line numberDiff line numberDiff line change
@@ -1024,6 +1024,64 @@ final class SnapshotTestingTests: XCTestCase {
10241024
#endif
10251025
}
10261026

1027+
final class ManipulatingWKWebViewNavigationDelegate: NSObject, WKNavigationDelegate {
1028+
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
1029+
webView.evaluateJavaScript("document.body.children[0].classList.remove(\"hero\")") // Change layout
1030+
}
1031+
}
1032+
func testWebViewWithManipulatingNavigationDelegate() throws {
1033+
#if os(iOS) || os(macOS)
1034+
let manipulatingWKWebViewNavigationDelegate = ManipulatingWKWebViewNavigationDelegate()
1035+
let webView = WKWebView()
1036+
webView.navigationDelegate = manipulatingWKWebViewNavigationDelegate
1037+
1038+
let fixtureUrl = URL(fileURLWithPath: String(#file), isDirectory: false)
1039+
.deletingLastPathComponent()
1040+
.appendingPathComponent("__Fixtures__/pointfree.html")
1041+
let html = try String(contentsOf: fixtureUrl)
1042+
webView.loadHTMLString(html, baseURL: nil)
1043+
if !ProcessInfo.processInfo.environment.keys.contains("GITHUB_WORKFLOW") {
1044+
assertSnapshot(
1045+
matching: webView,
1046+
as: .image(size: .init(width: 800, height: 600)),
1047+
named: platform
1048+
)
1049+
}
1050+
_ = manipulatingWKWebViewNavigationDelegate
1051+
#endif
1052+
}
1053+
1054+
final class CancellingWKWebViewNavigationDelegate: NSObject, WKNavigationDelegate {
1055+
func webView(
1056+
_ webView: WKWebView,
1057+
decidePolicyFor navigationAction: WKNavigationAction,
1058+
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
1059+
) {
1060+
decisionHandler(.cancel)
1061+
}
1062+
}
1063+
func testWebViewWithCancellingNavigationDelegate() throws {
1064+
#if os(iOS) || os(macOS)
1065+
let cancellingWKWebViewNavigationDelegate = CancellingWKWebViewNavigationDelegate()
1066+
let webView = WKWebView()
1067+
webView.navigationDelegate = cancellingWKWebViewNavigationDelegate
1068+
1069+
let fixtureUrl = URL(fileURLWithPath: String(#file), isDirectory: false)
1070+
.deletingLastPathComponent()
1071+
.appendingPathComponent("__Fixtures__/pointfree.html")
1072+
let html = try String(contentsOf: fixtureUrl)
1073+
webView.loadHTMLString(html, baseURL: nil)
1074+
if !ProcessInfo.processInfo.environment.keys.contains("GITHUB_WORKFLOW") {
1075+
assertSnapshot(
1076+
matching: webView,
1077+
as: .image(size: .init(width: 800, height: 600)),
1078+
named: platform
1079+
)
1080+
}
1081+
_ = cancellingWKWebViewNavigationDelegate
1082+
#endif
1083+
}
1084+
10271085
@available(iOS 13.0, *)
10281086
func testSwiftUIView_iOS() {
10291087
#if os(iOS)

0 commit comments

Comments
 (0)