3
votes

I try to catch the url about to load in WKWebView before it loads. Based on documents decidePolicyFor navigationAction (WKNavigationDelegate) should do the job but my problem is this delegate gets called after new url get loaded not before that.

here is the extension I wrote.

extension MyWebViewController: WKNavigationDelegate {

    public func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {

        guard let navigationURL = navigationAction.request.url else {
            decisionHandler(.allow)
            return
        }

        let forbiddenUrlPattern = Configuration.current.links.forbiddenUrlPattern
        if forbiddenUrlPattern.matches(url: navigationURL) {
            decisionHandler(.cancel)
            showFullScreenError(error: .forbidden)
            return
        }

        // Default policy is to allow navigation to any links the subclass doesn't know about
        decisionHandler(.allow)
    }
}

*PS the matches extension checks the pattern and it works fine. now the problem is that the content of forbiddenUrl showed for a time before this delegate func gets called and then the error page comes to screen, and if I close it the last visible webPage is from forbidden link pattern.

is there any way to understand about the link before actually loading it in webView?

I am using Xcode 11.2.1 & Swift 5.0

1
Some comment, because it might help you in some way: I suspect there might be something else going on, possibly with the web page you're using. We use the same pattern (as far as I can see) in our apps, and for us, if the .cancel condition is met the redirect it not executed, and no new content is shown. So I can confirm that this setup, in principle, is working as you originally expected it to work. Also (to test the basic setup) if you just put decisionHandler(.cancel); return in the delegate function I would be very surprised if you could still see any content loading at all. Good luck!Gamma
Have you tried the webView didCommitNavigation function instead since this isn't working properly? You could also try setting webView.isOpaque = false and webView.backgroundColor = UIColor.clear until you have verified the URL is not forbidden.elliott-io
Provide a bit more details. Which URL do you load? What is the forbidden URL pattern? Which link do you click within the loaded URL to experience the bug?Andriy Gordiychuk
Works fine here. Xcode 11.4 / iOS 13.4. So I assume this is either in other code, or the filter matches no all urls. Try to log urls right before returning .allow case. If you provide URL with which you work and URLs which should be filtered out, it would be possible to test more deeply.Asperi
@Gamma Thanks for your comment, you were right, there were something wrong with the webPage as all urls were relative and not absolute, please see my answer below.nfarshchi

1 Answers

4
votes

I wrote the answer I found here that it may help someone else as well. After lots of struggle I found out decisionHandler wont get called if the url is relative (not absolute urls). So why decisionHandler got called after loading that page? the answer I found is: when we have urls like href:"/foo/ba" then after calling that url, it will load and resolve as www.domain.com/foo/ba and only then desicionHandler got called.
Also didCommit only called once when I wanted to load the url for the first time in webView.

so the solution helped me was to add an observer to webView

webView.addObserver(self, forKeyPath: "URL", options: [.new,.old], context: nil)

and

override open func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
        /// This observer is in addition to the navigationAction delegate to block relative urls and intrrupt them and do native
        /// action if possible.
        if let newValue = change?[.newKey] as? URL, let oldValue = change?[.oldKey] as? URL, newValue != oldValue {
            let forbiddenUrlPattern = Configuration.current.links.forbiddenUrlPattern
        if forbiddenUrlPattern.matches(url: newValue) {
            showFullScreenError(error: .forbidden)
            return
        }

                /// These two action needed to cancel the webView loading the offerDetail page.
                /// otherwise as we stop loading the about to start process webView will show blank page.
                webView.stopLoading()
                ///This is small extension for make webView go one step back
                webView.handleBackAction(sender: newValue)
                return
            }
        }
    }

So this observer in addition to decisionHandler would cover both absolute and relative urls any one wants to listen and take action if needed.