From 429b6a699c8f076dd0ab0412ee2b41c5da7a8b4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fl=C3=A1vio=20Caetano?= Date: Thu, 21 Dec 2017 18:35:53 -0200 Subject: [PATCH] Fix: Better detection of resources loading end Fix #16 --- Example/Podfile.lock | 6 +- Example/ReCaptcha.xcodeproj/project.pbxproj | 21 +++++- .../Core/DispatchQueue__Tests.swift | 45 ++++++++++++ Example/ReCaptcha_Tests/mock.html | 32 +++++---- Gemfile | 2 +- Gemfile.lock | 18 ++--- ReCaptcha-Carthage.xcodeproj/project.pbxproj | 4 ++ ReCaptcha/Assets/recaptcha.html | 6 +- .../Classes/DispatchQueue+Throttle.swift | 32 +++++++++ ReCaptcha/Classes/ReCaptchaDecoder.swift | 16 ++++- .../Classes/ReCaptchaWebViewManager.swift | 70 ++++++++++++++++--- 11 files changed, 211 insertions(+), 41 deletions(-) create mode 100644 Example/ReCaptcha_Tests/Core/DispatchQueue__Tests.swift create mode 100644 ReCaptcha/Classes/DispatchQueue+Throttle.swift diff --git a/Example/Podfile.lock b/Example/Podfile.lock index ca6b955..30716b2 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,8 +1,8 @@ PODS: - AppSwizzle (1.2) - - ReCaptcha/Core (1.0.0): + - ReCaptcha/Core (1.0.1): - Result (~> 3.0) - - ReCaptcha/RxSwift (1.0.0): + - ReCaptcha/RxSwift (1.0.1): - ReCaptcha/Core - RxSwift (~> 4.0) - Result (3.2.4) @@ -31,7 +31,7 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: AppSwizzle: bbd3782652fc426ce59c045a92ec61d36f261984 - ReCaptcha: ea35d3bdbb2098eb3c89d1cf3b5cdbdd4d77f4b8 + ReCaptcha: f281cd074be9b282f528c6dda9337476f13bd776 Result: d2d07204ce72856f1fd9130bbe42c35a7b0fea10 RxCocoa: d62846ca96495d862fa4c59ea7d87e5031d7340e RxSwift: fd680d75283beb5e2559486f3c0ff852f0d35334 diff --git a/Example/ReCaptcha.xcodeproj/project.pbxproj b/Example/ReCaptcha.xcodeproj/project.pbxproj index 3fbcc13..c63b680 100644 --- a/Example/ReCaptcha.xcodeproj/project.pbxproj +++ b/Example/ReCaptcha.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ BD850CB2DF4C9C94FC51226C /* Pods_ReCaptcha_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 62BEEA62161F672468CCFD64 /* Pods_ReCaptcha_Example.framework */; }; D091B6E053FD250B4757E34C /* Pods_ReCaptcha_Tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9417A28DC340FF0BC1627B3F /* Pods_ReCaptcha_Tests.framework */; }; F206BAD51F8D3FEB00A25807 /* Cartfile in Resources */ = {isa = PBXBuildFile; fileRef = F206BAD41F8D3FEB00A25807 /* Cartfile */; }; + F231B3971FEC325A00F82943 /* DispatchQueue__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */; }; F288E9451F9537760018688D /* ReCaptchaError+Equatable.swift in Sources */ = {isa = PBXBuildFile; fileRef = F288E9441F9537760018688D /* ReCaptchaError+Equatable.swift */; }; F2E2685E1F7AEE3400CD876D /* ReCaptcha__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */; }; F2ECCF8A1E9FCEFE0097B199 /* ReCaptchaDecoder__Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2ECCF891E9FCEFE0097B199 /* ReCaptchaDecoder__Tests.swift */; }; @@ -54,6 +55,7 @@ C8537003ECC47117AF54DCA9 /* LICENSE */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; }; F206BAD41F8D3FEB00A25807 /* Cartfile */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Cartfile; path = ../Cartfile; sourceTree = ""; }; F21901D91F98D62F00D8E2C9 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; name = CHANGELOG.md; path = ../CHANGELOG.md; sourceTree = ""; }; + F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DispatchQueue__Tests.swift; sourceTree = ""; }; F288E9441F9537760018688D /* ReCaptchaError+Equatable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ReCaptchaError+Equatable.swift"; sourceTree = ""; }; F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReCaptcha__Tests.swift; sourceTree = ""; }; F2ECCF761E9FC47B0097B199 /* ReCaptcha_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReCaptcha_Tests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -170,6 +172,7 @@ F2ECCF891E9FCEFE0097B199 /* ReCaptchaDecoder__Tests.swift */, F2ECCF8D1E9FE68C0097B199 /* ReCaptchaWebViewManager__Tests.swift */, F2E2685D1F7AEE3400CD876D /* ReCaptcha__Tests.swift */, + F231B3961FEC325A00F82943 /* DispatchQueue__Tests.swift */, ); path = Core; sourceTree = ""; @@ -214,6 +217,7 @@ 607FACCE1AFB9204008FA782 /* Resources */, 8F03FFB3F5C55E873C23C682 /* [CP] Embed Pods Frameworks */, ED1C0E07490C9C4B4A401061 /* [CP] Copy Pods Resources */, + F231B3981FEC3B7F00F82943 /* SwiftLint */, ); buildRules = ( ); @@ -418,6 +422,20 @@ shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-ReCaptcha_Example/Pods-ReCaptcha_Example-resources.sh\"\n"; showEnvVarsInLog = 0; }; + F231B3981FEC3B7F00F82943 /* SwiftLint */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = SwiftLint; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/SwiftLint/swiftlint\" --path \"${PROJECT_DIR}/..\""; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -437,6 +455,7 @@ F2ECCF961EA00A5B0097B199 /* ReCaptchaWebViewManager+Helpers.swift in Sources */, F2ECCF8E1E9FE68C0097B199 /* ReCaptchaWebViewManager__Tests.swift in Sources */, F2ECCF981EA011370097B199 /* Result+Helpers.swift in Sources */, + F231B3971FEC325A00F82943 /* DispatchQueue__Tests.swift in Sources */, F2E2685E1F7AEE3400CD876D /* ReCaptcha__Tests.swift in Sources */, F2ECCF931EA009360097B199 /* ReCaptcha+Rx__Tests.swift in Sources */, F288E9451F9537760018688D /* ReCaptchaError+Equatable.swift in Sources */, @@ -620,7 +639,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.flaviocaetano.ReCaptcha-Tests"; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG UNIT_TESTS"; SWIFT_SWIFT3_OBJC_INFERENCE = Default; SWIFT_VERSION = 4.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReCaptcha_Example.app/ReCaptcha_Example"; diff --git a/Example/ReCaptcha_Tests/Core/DispatchQueue__Tests.swift b/Example/ReCaptcha_Tests/Core/DispatchQueue__Tests.swift new file mode 100644 index 0000000..3a26b8c --- /dev/null +++ b/Example/ReCaptcha_Tests/Core/DispatchQueue__Tests.swift @@ -0,0 +1,45 @@ +// +// DispatchQueue__Tests.swift +// ReCaptcha +// +// Created by Flávio Caetano on 21/12/17. +// Copyright © 2017 ReCaptcha. All rights reserved. +// + +@testable import ReCaptcha +import XCTest + +class DispatchQueue__Tests: XCTestCase { + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func test__Throttle() { + // Execute closure called once + let exp0 = expectation(description: "did call single closure") + + DispatchQueue.main.throttle(deadline: .now() + 0.1) { + exp0.fulfill() + } + + waitForExpectations(timeout: 1) + + // Does not execute first closure + let exp1 = expectation(description: "") + DispatchQueue.main.throttle(deadline: .now() + 0.1) { + XCTFail("Shouldn't be called") + } + + DispatchQueue.main.throttle(deadline: .now() + 0.1) { + exp1.fulfill() + } + + waitForExpectations(timeout: 1) + } +} diff --git a/Example/ReCaptcha_Tests/mock.html b/Example/ReCaptcha_Tests/mock.html index 7893c28..025ac88 100644 --- a/Example/ReCaptcha_Tests/mock.html +++ b/Example/ReCaptcha_Tests/mock.html @@ -1,16 +1,20 @@ - - - - - - - + + + + + + + -cartha diff --git a/Gemfile b/Gemfile index c1d608d..598db7e 100644 --- a/Gemfile +++ b/Gemfile @@ -1,4 +1,4 @@ source 'https://rubygems.org' -gem 'fastlane', '~> 2.62.1' +gem 'fastlane', '~> 2.69.2' gem 'cocoapods', '~> 1.3.1' diff --git a/Gemfile.lock b/Gemfile.lock index 220a292..4950783 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -64,7 +64,7 @@ GEM faraday_middleware (0.12.2) faraday (>= 0.7.4, < 1.0) fastimage (2.1.0) - fastlane (2.62.1) + fastlane (2.69.2) CFPropertyList (>= 2.3, < 3.0.0) addressable (>= 2.3, < 3.0.0) babosa (>= 1.0.2, < 2.0.0) @@ -92,9 +92,9 @@ GEM slack-notifier (>= 1.3, < 2.0.0) terminal-notifier (>= 1.6.2, < 2.0.0) terminal-table (>= 1.4.5, < 2.0.0) - tty-screen (~> 0.5.0) + tty-screen (~> 0.6.3) word_wrap (~> 1.0.0) - xcodeproj (>= 1.5.0, < 2.0.0) + xcodeproj (>= 1.5.2, < 2.0.0) xcpretty (>= 0.2.4, < 1.0.0) xcpretty-travis-formatter (>= 0.0.3) fourflusher (2.0.1) @@ -107,7 +107,7 @@ GEM mime-types (~> 3.0) representable (~> 3.0) retriable (>= 2.0, < 4.0) - googleauth (0.6.1) + googleauth (0.6.2) faraday (~> 0.12) jwt (>= 1.4, < 3.0) logging (~> 2.0) @@ -115,7 +115,7 @@ GEM multi_json (~> 1.11) os (~> 0.9) signet (~> 0.7) - highline (1.7.8) + highline (1.7.10) http-cookie (1.0.3) domain_name (~> 0.5) httpclient (2.8.3) @@ -141,7 +141,7 @@ GEM nap (1.1.0) netrc (0.11.0) os (0.9.6) - plist (3.3.0) + plist (3.4.0) public_suffix (2.0.5) representable (3.0.4) declarative (< 0.1.0) @@ -162,7 +162,7 @@ GEM terminal-table (1.8.0) unicode-display_width (~> 1.1, >= 1.1.1) thread_safe (0.3.6) - tty-screen (0.5.1) + tty-screen (0.6.3) tzinfo (1.2.4) thread_safe (~> 0.1) uber (0.1.0) @@ -186,7 +186,7 @@ PLATFORMS DEPENDENCIES cocoapods (~> 1.3.1) - fastlane (~> 2.62.1) + fastlane (~> 2.69.2) BUNDLED WITH - 1.15.4 + 1.16.0 diff --git a/ReCaptcha-Carthage.xcodeproj/project.pbxproj b/ReCaptcha-Carthage.xcodeproj/project.pbxproj index 44b4377..72520df 100644 --- a/ReCaptcha-Carthage.xcodeproj/project.pbxproj +++ b/ReCaptcha-Carthage.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ F206BAD31F8D3E7600A25807 /* Result.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F206BAD21F8D3E7600A25807 /* Result.framework */; }; F206BB1D1F8D4DBC00A25807 /* ReCaptcha_RxSwift.h in Headers */ = {isa = PBXBuildFile; fileRef = F206BB1B1F8D4DBC00A25807 /* ReCaptcha_RxSwift.h */; settings = {ATTRIBUTES = (Public, ); }; }; F206BB221F8D4DDF00A25807 /* RxSwift.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F206BB121F8D4D1400A25807 /* RxSwift.framework */; }; + F231B39A1FEC51C800F82943 /* DispatchQueue+Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = F231B3991FEC51C800F82943 /* DispatchQueue+Throttle.swift */; }; F24EA1E11F9683FB001DEC17 /* ReCaptcha+Rx.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24EA1DD1F9683F5001DEC17 /* ReCaptcha+Rx.swift */; }; F24EA1E21F968403001DEC17 /* ReCaptchaDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24EA1D81F9683F5001DEC17 /* ReCaptchaDecoder.swift */; }; F24EA1E31F968403001DEC17 /* ReCaptchaWebViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F24EA1D91F9683F5001DEC17 /* ReCaptchaWebViewManager.swift */; }; @@ -41,6 +42,7 @@ F206BB191F8D4DBC00A25807 /* ReCaptcha_RxSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ReCaptcha_RxSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; F206BB1B1F8D4DBC00A25807 /* ReCaptcha_RxSwift.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReCaptcha_RxSwift.h; sourceTree = ""; }; F206BB1C1F8D4DBC00A25807 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + F231B3991FEC51C800F82943 /* DispatchQueue+Throttle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+Throttle.swift"; sourceTree = ""; }; F24EA1D81F9683F5001DEC17 /* ReCaptchaDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReCaptchaDecoder.swift; sourceTree = ""; }; F24EA1D91F9683F5001DEC17 /* ReCaptchaWebViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReCaptchaWebViewManager.swift; sourceTree = ""; }; F24EA1DA1F9683F5001DEC17 /* String+Dict.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Dict.swift"; sourceTree = ""; }; @@ -145,6 +147,7 @@ F24EA1D91F9683F5001DEC17 /* ReCaptchaWebViewManager.swift */, F24EA1DA1F9683F5001DEC17 /* String+Dict.swift */, F24EA1DB1F9683F5001DEC17 /* ReCaptchaError.swift */, + F231B3991FEC51C800F82943 /* DispatchQueue+Throttle.swift */, F24EA1DC1F9683F5001DEC17 /* Rx */, F24EA1DE1F9683F5001DEC17 /* ReCaptcha.swift */, ); @@ -291,6 +294,7 @@ F24EA1E61F968403001DEC17 /* ReCaptcha.swift in Sources */, F24EA1E31F968403001DEC17 /* ReCaptchaWebViewManager.swift in Sources */, F24EA1E41F968403001DEC17 /* String+Dict.swift in Sources */, + F231B39A1FEC51C800F82943 /* DispatchQueue+Throttle.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ReCaptcha/Assets/recaptcha.html b/ReCaptcha/Assets/recaptcha.html index b49e00b..e8223d1 100644 --- a/ReCaptcha/Assets/recaptcha.html +++ b/ReCaptcha/Assets/recaptcha.html @@ -13,7 +13,11 @@ var execute = function() { // Removes ReCaptcha dismissal when clicking outside div area - document.getElementsByTagName("div")[4].outerHTML = "" + try { + document.getElementsByTagName("div")[4].outerHTML = "" + } + catch(e) { + } // Listens to changes on the div element that presents the ReCaptcha challenge observeDOM(document.getElementsByTagName("div")[3], function() { diff --git a/ReCaptcha/Classes/DispatchQueue+Throttle.swift b/ReCaptcha/Classes/DispatchQueue+Throttle.swift new file mode 100644 index 0000000..e26d3c7 --- /dev/null +++ b/ReCaptcha/Classes/DispatchQueue+Throttle.swift @@ -0,0 +1,32 @@ +// +// DispatchQueue+Throttle.swift +// ReCaptcha +// +// Created by Flávio Caetano on 21/12/17. +// Copyright © 2017 ReCaptcha. All rights reserved. +// + +import Foundation + +private var workItem: DispatchWorkItem? + +extension DispatchQueue { + /** + - parameters: + - deadline: The timespan to delay a closure execution + - action: The closure to be executed + + Delays a closure execution and ensures no other executions are made during deadline + */ + func throttle(deadline: DispatchTime, action: @escaping () -> Void) { + let worker = DispatchWorkItem { + defer { workItem = nil } + action() + } + + asyncAfter(deadline: deadline, execute: worker) + + workItem?.cancel() + workItem = worker + } +} diff --git a/ReCaptcha/Classes/ReCaptchaDecoder.swift b/ReCaptcha/Classes/ReCaptchaDecoder.swift index 12c6611..2c813df 100644 --- a/ReCaptcha/Classes/ReCaptchaDecoder.swift +++ b/ReCaptcha/Classes/ReCaptchaDecoder.swift @@ -24,6 +24,9 @@ internal class ReCaptchaDecoder: NSObject { /// Any errors case error(ReCaptchaError) + + /// Did finish loading resources + case didLoad } /// The closure that receives messages @@ -84,8 +87,17 @@ fileprivate extension ReCaptchaDecoder.Result { return .token(token) } - if let action = response["action"] as? String, action == "showReCaptcha" { - return .showReCaptcha + if let action = response["action"] as? String { + switch action { + case "showReCaptcha": + return .showReCaptcha + + case "didLoad": + return .didLoad + + default: + break + } } return .error(.wrongMessageFormat) diff --git a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift index c964822..3b1d35e 100644 --- a/ReCaptcha/Classes/ReCaptchaWebViewManager.swift +++ b/ReCaptcha/Classes/ReCaptchaWebViewManager.swift @@ -22,6 +22,9 @@ open class ReCaptchaWebViewManager { /// The parent manager private weak var manager: ReCaptchaWebViewManager? + /// The active requests' urls + private var activeRequests = Set(minimumCapacity: 0) + /// - parameter manager: The parent manager init(manager: ReCaptchaWebViewManager) { self.manager = manager @@ -30,16 +33,55 @@ open class ReCaptchaWebViewManager { /** - parameters: - webView: The web view invoking the delegate method. - - navigation: The navigation object that finished. - - Called when the navigation is complete. - */ - func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { - manager?.didFinishLoading = true + - navigationAction: Descriptive information about the action triggering the navigation request. + - decisionHandler: The decision handler to call to allow or cancel the navigation. The argument is one of + the constants of the enumerated type WKNavigationActionPolicy. - if manager?.completion != nil { - // User has requested for validation - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in + Decides whether to allow or cancel a navigation. + */ + func webView( + _ webView: WKWebView, + decidePolicyFor navigationAction: WKNavigationAction, + decisionHandler: @escaping (WKNavigationActionPolicy + ) -> Void) { + defer { decisionHandler(.allow) } + + if let url = navigationAction.request.url, let host = url.host, let endpoint = manager?.endpoint, + endpoint.range(of: host) != nil { + activeRequests.insert(url.absoluteString) + } + } + + /** + - parameters: + - webView: The web view invoking the delegate method. + - navigationResponse: Descriptive information about the navigation response. + - decisionHandler: A block to be called when your app has decided whether to allow or cancel the navigation + + Decides whether to allow or cancel a navigation after its response is known. + */ + func webView( + _ webView: WKWebView, + decidePolicyFor navigationResponse: WKNavigationResponse, + decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void + ) { + defer { decisionHandler(.allow) } + guard let url = navigationResponse.response.url?.absoluteString, + activeRequests.remove(url) != nil, activeRequests.isEmpty else { + return + } + + execute() + } + + /// Flag the requests as finished and call ReCaptcha execution if necessary + func execute() { + DispatchQueue.main.throttle(deadline: .now() + 1) { [weak self] in + // Did finish loading the ReCaptcha JS source + self?.manager?.didFinishLoading = true + + if self?.manager?.completion != nil { + // User has requested for validation self?.manager?.execute() } } @@ -65,6 +107,9 @@ open class ReCaptchaWebViewManager { /// The observer for `.UIWindowDidBecomeVisible` fileprivate var observer: NSObjectProtocol? + /// The endpoint url being used + fileprivate var endpoint: String + /// The `webView` delegate implementation fileprivate lazy var webviewDelegate: WebViewDelegate = { WebViewDelegate(manager: self) @@ -90,7 +135,8 @@ open class ReCaptchaWebViewManager { - endpoint: The JS API endpoint to be loaded onto the HTML file. */ init(html: String, apiKey: String, baseURL: URL, endpoint: String) { - decoder = ReCaptchaDecoder { [weak self] result in + self.endpoint = endpoint + self.decoder = ReCaptchaDecoder { [weak self] result in self?.handle(result: result) } @@ -201,6 +247,10 @@ fileprivate extension ReCaptchaWebViewManager { case .showReCaptcha: configureWebView?(webView) + + case .didLoad: + // For testing purposes + webviewDelegate.execute() } }