How can I reliably detect using javascript that a page is loaded inside a WKWebView? I'd like to be able to detect these scenarios:
- iOS & WKWebView
- iOS & Safari
- not iOS
There is a similar question about UIWebView here. But it's quite old and I'm not sure if same still applies to WKWebView.
You can check for the existence of window.webkit.messageHandlers
which WKWebKit uses to receive messages from JavaScript. If it exists, you're inside a WKWebView
.
That combined with a simple user agent check should do the trick:
var iOS = (navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false);
var isWKWebView = false;
if (window.webkit && window.webkit.messageHandlers) {
isWKWebView = true;
}
The accepted answer doesn't work as tested using the WKWebView vs UIWebView app
As the article mentions, the only HTML5 feature difference is IndexedDB support. So I'd go for a more reliable pattern with:
if (navigator.platform.substr(0,2) === 'iP'){
//iOS (iPhone, iPod or iPad)
var lte9 = /constructor/i.test(window.HTMLElement);
var nav = window.navigator, ua = nav.userAgent, idb = !!window.indexedDB;
if (ua.indexOf('Safari') !== -1 && ua.indexOf('Version') !== -1 && !nav.standalone){
//Safari (WKWebView/Nitro since 6+)
} else if ((!idb && lte9) || !window.statusbar.visible) {
//UIWebView
} else if ((window.webkit && window.webkit.messageHandlers) || !lte9 || idb){
//WKWebView
}
}
You may ask: Why not using the UserAgent? That's because Android browsers use it as settings! So, we should never trust any UAs. Only browser features and property checks as such.
Also I noticed that the QuickTime
plugin was always loaded as part of Older Safari and other Browsers in UIWebView. But the plugin is no longer present in WKWebView. So you can use the QuickTime
plugin presence as an extra check.
9/23/16 Edit: I adjusted the code for Safari 10 which no longer allowed the sole idb check to be reliable, as mentioned by @xmnboy. To discard Safari 10, it checks for the old webkit engine bug, which only applied until Safari 9.2; and i use a window.statusbar.visible
fallback which appears to be a reliable indicator signal after a few comparison tests between iOS 9 and 10. (please check though)
Given the change in behavior to the UIWebView that was introduced by Apple in iOS 10, here's a new answer that combines the original response by @Justin-Michael and the follow-up favorite by @hexalys.
var isWKWebView = false ;
if( navigator.platform.substr(0,2) === 'iP' ) { // iOS detected
if( window.webkit && window.webkit.messageHandlers ) {
isWKWebView = true ;
}
}
It turns out that Justin's answer was really the better feature detection mechanism, because it works for both iOS 9 and iOS 10.
No telling what happens when we get to iOS 11. :-)
Qualification: this test will work if you are using the official Cordova WKWebView plugin to build your webview app, because that plugin does initialize the addScriptMessageHandler
method, as noted by @hexalys in the comments to this post. That mechanism is being used by Cordova to define a new JS to native bridge when the WKWebView plugin is present.
Search for addScriptMessageHandler
in that plugin repo and see the very end of the ios-wkwebview-exec.js
file in that repo for some implementation details (or search for the string window.webkit.messageHandlers
in that file).
It seems that because of the latest iOS Chrome using WKWebView as rendering engine, Chrome is detected as WKWebView. ua.indexOf('CriOS') !== -1 will helps to distinguish Chrome from WKWebView in App.
In iOS, you could add this code to establish communication between javascript and objective-c:
WKWebViewConfiguration *configuration = [[WKWebViewConfiguration alloc] init];
WKUserContentController *controller = [[WKUserContentController alloc] init];
[controller addScriptMessageHandler:self name:@"javascript_observer"];
configuration.userContentController = controller;
...
webview = [[WKWebView alloc] initWithFrame:... configuration: configuration];
In javascript, you could test the connection like this:
if ( window.webkit != undefined ){
//javascript is running in webview
}