24

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.

Marty
  • 5,926
  • 9
  • 53
  • 91
andr111
  • 2,982
  • 11
  • 35
  • 45

5 Answers5

36

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)

robocat
  • 5,293
  • 48
  • 65
hexalys
  • 5,177
  • 3
  • 31
  • 56
  • 2
    Unfortunately, this approach no longer works. It works well on an iOS 9 device, but on iOS 10 devices the check for window.indexedDB will come up true even when you are inside the UIWebView. – xmnboy Sep 23 '16 at 22:30
  • @xmnboy OK not surprised. I somewhat suspected this might happen. I am going to check this out and look for an alternative. – hexalys Sep 23 '16 at 22:56
  • @xmnboy I forgot that I actually had found another fallback already, left handy as a comment in my script, which seems to be still true and, a working fallback for iOS10. – hexalys Sep 24 '16 at 07:17
  • Thanks for that update to your detection mechanism. :-) – xmnboy Sep 26 '16 at 16:16
  • `window.statusbar.visible` didn't work for us - it was true within a WKWebkitView on iOS10.3 in our App. Maybe because our app is fullscreen? See [wantsFullScreenLayout versus modalPresentationStyle of UIModalPresentationFullScreen](http://stackoverflow.com/questions/13455472/ios-wantsfullscreenlayout-statusbar-visible). – robocat Feb 14 '17 at 06:39
  • @robocat FYI: window.statusbar.visible = true is correctly assumed to be WKWebView! I don't have 10.3 to test yet. It works for me as expected in the WebView app with navigation and top bar removed in 10.2.1. – hexalys Feb 14 '17 at 07:41
  • @hexalys `window.statusbar.visible` didn't work for me with our App (and I don't think it was due to using 10.3 beta). FYI: I noticed [this one wierd trick](https://github.com/eligrey/FileSaver.js/blob/master/FileSaver.js) used to detect Safari: `is_safari = /constructor/i.test(window.HTMLElement) || window.safari` – robocat Feb 20 '17 at 00:05
  • @robocat window.safari is not guarantee on mobile so I would not rely on that small snippet. Platform is more reliable. – hexalys Feb 20 '17 at 00:17
  • In my instance, the script fails because of invalid JS syntax if I write `constructor` with lowercase letters. It only works if I write it as `Constructor`. – Tamás Sengel Oct 05 '17 at 14:46
  • @the4kman It doesn't matter, `i` stands for case insensitive. And the syntax isn't invalid AFAIK. So I am not at all sure what you mean. Or please tell me what environment or browser are you seeing this in. – hexalys Oct 06 '17 at 07:09
  • It happens to me on Safari for Mac and iOS as well. It may be my temp webserver's fault (biz.nf). – Tamás Sengel Oct 06 '17 at 08:00
  • A nasty surprise with this detection is that Chrome for iOS renders using `WKWebView`. So if you think that "being inside a WKWebView" means "being embedded within an app" you're up for a surprise. – gustafc Apr 24 '19 at 14:24
  • Sidenote: although navigator.platform.substr(0,2) === 'iP' NO LONGER true on iOS 13 if Desktop Mode is used on Mobile Safari 13 (tested on iOS 13 Beta 3 and iPadOS 13 Beta 2). However in the WKWebView the platform is set correctly (because it is always Mobile Mode?!). – robocat Jul 15 '19 at 05:24
  • @robocat They spoof platform with MacIntel to enforce Desktop Mode on iPad and iPhone, in that mode. Both `MacIntel` and `navigator.maxTouchPoints` can probably be used safely to infer iPadOS/iOS in Desktop mode. I'll make edits after I test the iPadOS which I haven't gotten to yet. – hexalys Aug 23 '20 at 12:05
10

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).

xmnboy
  • 2,314
  • 2
  • 14
  • 31
  • 2
    Again though webkit isn't always a defined object on all iOS views by default. So not reliable on the JS side. As stated by [this answer](http://stackoverflow.com/a/32785085/1647538). "The window.webkit namespace only appears in webview with script message handlers. Make sure that you have called addScriptMessageHandler method of WKUserContentController." e.g. The test app surely doesn't set it. – hexalys Sep 24 '16 at 01:55
  • @hexalys -- thanks for that added bit of knowledge. I've updated my answer to include a qualification regarding my test environment. I was only concerned about the detection of WKWebVew in a Cordova (aka Adobe PhoneGap or Intel XDK) build environment. – xmnboy Sep 26 '16 at 16:14
0

It seems that because the latest iOS Chrome uses WKWebView as a rendering engine, Chrome is detected as WKWebView.

ua.indexOf('CriOS') !== -1 

will helps to distinguish Chrome from WKWebView in App.

Dupinder Singh
  • 7,175
  • 6
  • 37
  • 61
0

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
}
Jarir
  • 327
  • 3
  • 10
-2

Update: This no longer works, see the higher-rated answers above instead!

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;
}
Community
  • 1
  • 1
Justin Michael
  • 5,634
  • 1
  • 29
  • 32
  • This sounds like a good solution. Together with iOS check it should provide reliable results. – andr111 Mar 03 '15 at 13:52
  • The iOS test is wrong because it uses the user agent (also detects Android, WinPhone 8.1 Update, etc). Something like /^iPhone|^iPad|^iPod/.test(navigator.platform) is better. Note that "iPod touch" is used for iOS 8 so don't add a $ after iPod. – robocat Jun 25 '15 at 05:12
  • This is the only answer that actually works. Not the iOS check, but messageHandlers check. – juminoz May 28 '21 at 20:48