A journey from the past to the future of WebViews in Apache Cordova for iOS
Once upon a time a junior developer was tasked with building an app using Cordova. He took over the codebase of an app and started working on it. The app worked great but sometimes he encountered strange rendering issues. After long investigations he found out these issues only happening on iOS because of this webview implementation called UIWebView.
I am this junior developer and this was the start of my journey with webviews on iOS. This work with webviews and Cordova on iOS kept me busy a few times in my job and got me to engage with Cordova and WebKit developers. To this day we still have strange problems and solutions for webview related problems on Cordova and I think having a look at the history may help finding a better solution for the future.
Ignore the memes if you want to take this post seriously. I just tried to spice it up a little since probably nobody would want to read this wall of text with just ramblings about webviews. To be honest working on this shaped my career in some way and I try to make app development better for many people out there using Cordova and similar technologies.
UIWebview was the first webview option available to iOS developers since iOS 2.0. Cordovas iOS platform was built around using UIWebView for most of it’s history. UIWebView has one big advantage for us Cordova developers but many disadvantages. It’s is considered deprecated and should not be used in new apps.
UIWebView generally worked perfectly for many years. It doesn’t have cross-origin resource sharing (CORS) policies for local files like its successor. Because Cordova works by loading local files from the apps bundle into the WebView it was a very convenient to use by app developers. App developers did not have to care about CORS policies on servers they are interacting with. To me this totally makes sense because apps using a webview to load local web pages as part of the app are just like native apps. Native code does not have the CORS restrictions web pages have and Cordova apps in this context are just native apps.
CORS totally makes sense in a browser context where web pages from different origins need to be separated from each other, but the local pages in Cordova are something different.
Rendering issues and performance problems
Additionally to these problems Apple has been suggesting developers to move their apps to WKWebView. This meant I had to figure out how to migrate this app. Cordova had support for WKWebView for a while but it required some changes to app and servers.
A while after my work on this started Apple finally announced the deprecation of UIWebView in iOS 13 and that uploads to the App Store with UIWebView will be rejected. On the Cordova side this meant all UIWebView code in the iOS platform and plugins had to be removed. This happened in two stages. At first a
WKWebViewOnly preference was introduced to exclude UIWebView code at compile time. This was released in a minor version to address the upload warnings developers now got for uploading to the App Store. After a while a new major version removed the UIWebView code and added the WKWebView implementation to the platform.
This was after I became Project Management Committee (PMC) member for Cordova and I was happy to help with releasing these versions and working on the InAppBrowser plugin. This post explains how the preference worked and the update post explains the changes in cordova-ios 6.x.
Early days of WKWebView
Cordova app developers had the option to use WKWebView for a long time with the official Apache WKWebView-Engine plugin. This plugin uses WKWebView instead of the built in UIWebView to load the local files of the Cordova app. Because WKWebView enforces CORS on the
file: protocol it was not a drop-in replacement for many developers. Another problem of the plugin is that many developers use a web framework like Angular or React that use URL based routing which does not work properly for sites running on the file protocol.
Ionic the company behind the Ionic framework that traditionally uses Angular addressed this problem with their fork of the WKWebView plugin. Their version uses an iOS API called WKURLSchemeHandler to load the local files via a custom protocol like
ionic://localhost. This solves the routing problems and this plugin is way more popular than the Apache one (according to NPM download stats).
These WKWebView options are just fine for many app developers and their environments. With cordova-ios 6.0.0 WKWebView came to the Cordova iOS platform with the schemehandler and UIWebView was removed. In most cases you should be happy now with everything Cordova now offers.
Intelligent Tracking Prevention (ITP) in iOS 13
Nevertheless iOS 13 got me panic again when iOS 13.3 was released and cookies stopped working again. Again I opened a bug for WebKit and it got fixed really quickly in iOS 13.3 beta 4 and was resolved even before it got released.
I learned a lesson in this iOS 13 release that third party cookies are coming to an end. iOS 13 shipped some big privacy features for Safari which protect users from tracking with cookies. I personally really like the push for more privacy in browsers and making the live hard for “evil” tracking cookies. Unfortunately an app setup with the Cordova app running on one origin and the server running on a different one is pretty similar to most (ad) tracking technology.
From the comments in the WebKit bugs I learned that those bugs that troubled my app were regressions from changes for privacy features that just accidentally applied to WKWebView. WKWebView on iOS 13 was not supposed to enable these features, yet but I could feel they would become enabled in the future.
ITP rolling out in iOS 14
About a year later exactly this happened. The iOS 14 developer beta came out and I immediately noticed that cookies are blocked again. Changes to WKWebView where officially announced at the WWDC 2020 in this video. Basically WKWebView now enforces the tracking prevention by default and developers have to take some measures to adapt their apps.
Luckily I already found ways to make cookies and CORS work together in the mean time. More on that later in this post.
The preferred way to properly setup WKWebView for secure communication with servers in iOS 14 is to configure app-bound domains. I really like the idea behind that and this is probably the best solution for the most apps out there. Cordova is already investigating to integrate that. Unfortunately for my personal use case this is not a solution since my app allows the user to configure it’s own server which means I don’t have a list of known domains to set at build time.
Hacks to make WKWebView work
For the migration to WKWebView I had to figure out solutions to the CORS and cookie problems mentioned before. With Cordova I had several plugins available that use native code to work around the limitations the webview now enforces.
Native XHR Plugin
My first idea was using plugins like cordova-plugin-wkwebview-file-xhr or cordova-plugin-advanced-http. These plugins basically allow you to do web requests through native code where no CORS restrictions apply.
I quickly came to the conclusion that these plugins were no ideal solution because they had the big drawback that the apps webview and InAppBrowser don’t sync the cookies and therefore are not authenticated with the server. This means loading some content from the server in InAppBrowser won’t work without the user logging in again.
Origin and hostname switching
We still needed to get rid of UIWebView completely. I thought how can we solve all CORS and third party cookie issues? Just make requests first party!
ionic://localhost. If the user configures the app to use
theirserver.com the app changes the hostname with this
WebviewSwitch.setHostname('theirserver.com') and it will reload and run with the origin
ionic://theirserver.com. This will treat HTTP requests to
theirserver.com as same-origin with no CORS and cookie restrictions.
These solutions worked in production for a while but were not perfect. In particular the user experience was bad because the app had to reload in some cases which slowed it down significantly at start and bothered the user with white pages. While discussing this issue with some people in the Cordova community a fellow PMC member had an idea I just needed to try out.
As mentioned a few times the Ionic webview plugin uses
WKURLSchemeHandler introduced in iOS 11 to load content from the app with a custom URL scheme. Why not use this scheme to load stuff from the internet, too? This means we could rewrite all URLs to the custom scheme and use native code to make the request. CORS and tracking prevention would not apply because to the webview this request is same origin. This sounds great right?
After a bit trial and error I found a way to expands Ionics WKURLSchemeHandler to do internet requests as well. After rewriting all URLs in the app with a helper function I tell XHR, fetch and even image tags to go through my now dubbed “proxy” WKURLSchemeHandler. This proved to be a good solution.
Future in Cordova
Since cordova-ios 6.0 WKWebView is the default and it uses WKURLSchemeHandler, too. This means I could get rid of the Ionic webview for iOS. I thought about adding my “proxy” to the iOS platform and started the discussion with other Cordova maintainers. After a year of using this in a fork I finally did the pull request to the iOS platform. This is still in an early stage (for example the Cordova whitelist is not considered, yet) but I hope to bring this feature to a future release. Time will tell if this solution will be part of Cordova and still work in the future.
Some ideas for WebKit
As far as I know a large portion of iOS apps out there are hybrid apps built with Cordova or similar technology. Small changes or new features to WebKit could potentially improve a large numbers of apps. WKURLSchemeHandler has been a blessing for WKWebView in Cordova. The “proxy” currently is a good solution. but does not really feel right. I personally still feel the uncertainty with every iOS update that this may break in another strange way. Let’s work together between app/framework developers and WebKit to make lives easier for developers and apps more reliable. I got some ideas but finding the right balance between privacy, security and features for developers is really hard.