Skip to content

Commit 9fb98fa

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 9fb98fa

8 files changed

+198
-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,133 @@
1+
#if os(macOS)
2+
import Cocoa
3+
#endif
4+
#if os(iOS)
5+
import UIKit
6+
#endif
7+
8+
#if os(iOS) || os(macOS)
9+
import WebKit
10+
11+
final class ForwardingWKNavigationDelegate: NSObject {
12+
var didFinish: (() -> Void)?
13+
var original: WKNavigationDelegate?
14+
init(didFinish: @escaping () -> Void = {}, originalDelegate: WKNavigationDelegate?) {
15+
self.original = originalDelegate
16+
self.didFinish = didFinish
17+
}
18+
19+
override public func responds(to aSelector: Selector!) -> Bool {
20+
if decidePolicyPre13 == aSelector, original?.responds(to: aSelector) != true {
21+
return NSObject.responds(to: aSelector)
22+
}
23+
24+
if
25+
#available(iOSApplicationExtension 13.0, *, OSXApplicationExtension 10.15, *),
26+
decidePolicy13 == aSelector,
27+
original?.responds(to: aSelector) != true
28+
{
29+
return NSObject.responds(to: aSelector)
30+
}
31+
32+
return interceptedSelectors.contains(aSelector)
33+
|| original?.responds(to: aSelector) == true
34+
|| super.responds(to: aSelector)
35+
}
36+
37+
override public func forwardingTarget(for aSelector: Selector!) -> Any? {
38+
interceptedSelectors.contains(aSelector)
39+
? nil
40+
: original
41+
}
42+
43+
func finish() {
44+
didFinish?()
45+
didFinish = nil
46+
}
47+
}
48+
49+
extension ForwardingWKNavigationDelegate: WKNavigationDelegate {
50+
@available(iOSApplicationExtension 13.0, *, OSXApplicationExtension 10.15, *)
51+
func webView(
52+
_ webView: WKWebView,
53+
decidePolicyFor navigationAction: WKNavigationAction,
54+
preferences: WKWebpagePreferences,
55+
decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void
56+
) {
57+
original?.webView?(
58+
webView,
59+
decidePolicyFor: navigationAction,
60+
preferences: preferences,
61+
decisionHandler: { [weak self] policy, prefs in
62+
decisionHandler(policy, prefs)
63+
if policy == .cancel {
64+
self?.finish()
65+
}
66+
}
67+
)
68+
}
69+
70+
func webView(
71+
_ webView: WKWebView,
72+
decidePolicyFor navigationAction: WKNavigationAction,
73+
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void
74+
) {
75+
original?.webView?(webView, decidePolicyFor: navigationAction, decisionHandler: { [weak self] policy in
76+
decisionHandler(policy)
77+
if policy == .cancel {
78+
self?.finish()
79+
}
80+
})
81+
}
82+
83+
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
84+
original?.webView?(webView, didFinish: navigation)
85+
webView.evaluateJavaScript("document.readyState") { [weak self] _, _ in
86+
self?.finish()
87+
}
88+
}
89+
90+
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
91+
original?.webView?(webView, didFail: navigation, withError: error)
92+
finish()
93+
}
94+
95+
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
96+
original?.webView?(webView, didFailProvisionalNavigation: navigation, withError: error)
97+
finish()
98+
}
99+
100+
@available(OSXApplicationExtension 10.11, *)
101+
func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
102+
original?.webViewWebContentProcessDidTerminate?(webView)
103+
finish()
104+
}
105+
}
106+
107+
fileprivate let decidePolicyPre13 = #selector(WKNavigationDelegate.webView(_:decidePolicyFor:decisionHandler:) as (
108+
(WKNavigationDelegate) ->
109+
(WKWebView, WKNavigationAction, @escaping (WKNavigationActionPolicy) -> Void) -> Void
110+
)?)
111+
112+
@available(iOSApplicationExtension 13.0, *, OSXApplicationExtension 10.15, *)
113+
fileprivate let decidePolicy13 = #selector(WKNavigationDelegate.webView(_:decidePolicyFor:preferences:decisionHandler:))
114+
115+
fileprivate var interceptedSelectors: Set<Selector> = {
116+
var selectors: Set<Selector> = [
117+
#selector(WKNavigationDelegate.webView(_:didFinish:)),
118+
#selector(WKNavigationDelegate.webView(_:didFail:withError:)),
119+
#selector(WKNavigationDelegate.webView(_:didFailProvisionalNavigation:withError:)),
120+
decidePolicyPre13,
121+
]
122+
123+
if #available(iOSApplicationExtension 13.0, *, OSXApplicationExtension 10.15, *) {
124+
selectors.insert(decidePolicy13)
125+
}
126+
127+
if #available(iOSApplicationExtension 8.0, *, OSXApplicationExtension 10.11, *) {
128+
selectors.insert(#selector(WKNavigationDelegate.webViewWebContentProcessDidTerminate(_:)))
129+
}
130+
return selectors
131+
}()
132+
133+
#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

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

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

0 commit comments

Comments
 (0)