WKWebview getAllCookies crash in iOS 11.3

After some investigation we have come to the following working solution:

Backstory

We have a crash when the user updates to a newer version of our App.

Problem

We were using UIWebView and injecting cookies into it. The problem comes when:

  • User installs new updated App which uses WKWebview.
  • User opens a web view.
  • We try to retrieve all the previously UIWebView injected cookies by calling getAllCookies(_ completionHandler: @escaping ([HTTPCookie]) -> Void) on the component wkhttpcookiestore so we can loop through them and remove them one by one.

Verdict

UIWebView uses nshttpcookiestorage: https://developer.apple.com/documentation/foundation/nshttpcookiestorage

WKWebView uses wkhttpcookiestore: https://developer.apple.com/documentation/webkit/wkhttpcookiestore

Somewhere in the middle of the synchronisation from nshttpcookiestorage to the wkhttpcookiestore when we try to retrieve the cookies, it is passing one of the values as a NSURL and then someone is calling length() function on that object which crashes because NSURL does not have that function.

Resolution

Therefore, we should remove the cookies that are set on the nshttpcookiestorage with the right method which is by using: HTTPCookieStorage.shared.removeCookies(since: Date.distantPast) and then remove the cookies from the wkhttpcookiestore with the right method that is removeData(ofTypes:for:completionHandler:) and set the type as WKWebsiteDataTypeCookies rather than looping through all the cookies and removing them one by one.

Test Considerations

All tests MUST be done on real devices (iPhone/iPad) since this crash is NOT reproducible on iOS simulators.

Code Snippet

public func clearCookies(completion: @escaping (() -> Swift.Void)) {
    // First remove any previous cookies set in the NSHTTP cookie storage.
    HTTPCookieStorage.shared.removeCookies(since: Date.distantPast)
    // Second remove any previous cookies set in the WKHTTP cookie storage.
    let typeCookiesToBeRemoved: Set<String> = [WKWebsiteDataTypeCookies]
    // Only fetch the records in the storage with a cookie type.
    WKWebsiteDataStore.default().fetchDataRecords(ofTypes: typeCookiesToBeRemoved) { records in
        let dispatchGroup = DispatchGroup()
        records.forEach { record in
            dispatchGroup.enter()
            WKWebsiteDataStore.default().removeData(ofTypes: record.dataTypes, for: [record], completionHandler: {
                dispatchGroup.leave()
            })
        }
        dispatchGroup.notify(queue: DispatchQueue.main) {
            print("All cookies removed.")
            completion()
        }
    }
}

I was able to fix this crash by calling getAllCookies asynchronously in the main thread.

func cookiesDidChange(in cookieStore: WKHTTPCookieStore) {
     DispatchQueue.main.async {
         cookieStore.getAllCookies { (cookies) in
             //Code here...
         })
     }
}