Rigel Group

They shoot Yaks, don't they?

Opening Links in PhoneGap Apps in Mobile Safari

The Cordova (nee PhoneGap) project lets you wrap your HTML5 app up in a nice native wrapper for deployment to iOS or Android mobile devices. I am using this on a current project built with the Sencha Touch framework, and for the most part it has been a great experience.

However, there is this issue that was causing us some problems on iOS. The way PhoneGap works, it hosts your HTML5 app in a WebView, and you tell it which links should open in the WebView, and which should “shell out” of the app and open in Mobile Safari. You do this by “whitelisting” certain hosts via the ExternalHosts setting in Cordova.plist.

Our issue was that we had user-generated content as part of our app, and the users could enter links to arbitrary hosts on the web, both in anchor tags and image tags. If you tell PhoneGap to whitelist all hosts, “*”, then all links will open in the WebView. This is bad, since if a user clicks on a link to a website, that website will load in the PhoneGap WebView, with no browser chrome, and no way to get “back” to your application. The HTML way to tell the browser you want content to open in a new window, is to use the target=“_blank” attribute. Unfortunately, this does not work in PhoneGap.

A longer-term fix involves getting intimately familiar with the bowels of iOS events with names like UIWebViewNavigationTypeOther and UIWebViewNavigationTypeLinkClicked. A short-term hack is what we needed, and it looks like this:

First, in your PhoneGap/Cordova project, paste in the following code in your MainViewController.m file. This snippet will examine any request to load a specific URL, and look for a fragment of “phonegap=external”. (i.e. http://mysite.com?page=1#phonegap=external) If it finds this snippet, it loads the URL in Mobile Safari, which will cause your app to stop, and Mobile Safari to launch.

1
2
3
4
5
6
7
8
9
10
11
12
13
- (BOOL) webView:(UIWebView*)theWebView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSURL *url = [request URL];
    if ( ([url fragment] != NULL) && ([[url fragment] rangeOfString:@"phonegap=external"].location != NSNotFound))
    {
        if ([[UIApplication sharedApplication] canOpenURL:url]) {
            [[UIApplication sharedApplication] openURL:url];
            return NO;
        }
    }

  return [super webView:theWebView shouldStartLoadWithRequest:request navigationType:navigationType];
}

The next piece of the puzzle was to munge the URLs in our app to add the fragment, but only if we wanted them to open in an external browser — in keeping with HTML conventions we will signify this with a target=“_blank”. We placed this in the <head> of our index.html, and it adds a listener that gets fired before the link is acted on (thats the ‘true’ as the last parameter to addEventListener) and gives us a chance to munge it on its way to PhoneGap.

1
2
3
4
5
document.addEventListener('click', function(e) {
  if (e.srcElement.target === "_blank" && e.srcElement.href.indexOf("#phonegap=external") === -1) {
    e.srcElement.href = e.srcElement.href + "#phonegap=external";
  }
}, true);

That should do the trick!