Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Prevent links in standalone web apps opening Mobile Safari
<!DOCTYPE html>
<title>Stay Standalone</title>
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<script src="stay_standalone.js" type="text/javascript"></script>
<li><a href="">Remote Link (Google)</a></li>
<li><a href="javascript:alert('Awesome script is awesome')">JavaScript Link</a></li>
<li><a href="/">Local Link</a></li>
<li><a href="#amp">Local Anchor</a></li>
// Mobile Safari in standalone mode
if(("standalone" in window.navigator) && window.navigator.standalone){
// If you want to prevent remote links in standalone web apps opening Mobile Safari, change 'remotes' to true
var noddy, remotes = false;
document.addEventListener('click', function(event) {
noddy =;
// Bubble up until we hit link or top HTML element. Warning: BODY element is not compulsory so better to stop on HTML
while(noddy.nodeName !== "A" && noddy.nodeName !== "HTML") {
noddy = noddy.parentNode;
if('href' in noddy && noddy.href.indexOf('http') !== -1 && (noddy.href.indexOf( !== -1 || remotes))
document.location.href = noddy.href;

The variable links will be created in the global scope here. How about this?

// Mobile Safari in standalone mode
if (('standalone' in window.navigator) && window.navigator.standalone) {

    window.addEventListener('load', function() {

        var links = document.links,

        for (i = 0; i < links.length; i++) {
            // Don't do this for javascript: links
            if (~(link = links[i]).href.toLowerCase().indexOf('javascript')) {
                link.addEventListener('click', function(event) {
                    top.location.href = this.href;
                    event.returnValue = false;
                }, false);

    }, false);


Still, for documents with a lot of links in them it would probably be better to use event delegation.

altryne commented Jun 23, 2011

awesome! thanx

Warry commented Jun 23, 2011

And you should add this CSS property on body to disable the callout shown when you touch and hold a touch target :

And what about document.links?

irae commented Jun 23, 2011

I've forked the gist and did a complete rewrite with event delegation and a minimal test case html.

Waiting for the loadevent is a bad idea. On 3g networks it could mean your code will not run before the user taps the screen. I've tryied to pull request, but gists can't do that...

<!DOCTYPE html>
    <title>stay standalone</title>
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="viewport" content="width=device-width,initial-scale=1.5,user-scalable=no">
    <script type="text/javascript">
        (function(document,navigator,standalone) {
            // prevents links from apps from oppening in mobile safari
            // this javascript must be the first script in your <head>
            if ((standalone in navigator) && navigator[standalone]) {
                var curnode, location=document.location, stop=/^(a|html)$/i;
                document.addEventListener('click', function(e) {
                    while (!(stop).test(curnode.nodeName)) {
                    // Condidions to do this only on links to your own app
                    // if you want all links, use if('href' in curnode) instead.
                    if('href' in curnode && ( curnode.href.indexOf('http') || ~curnode.href.indexOf( ) ) {
                        location.href = curnode.href;
    <p><a href="">google</a></p>
    <script type="text/javascript" charset="utf-8">
        // NEVER user document.write, unless for test porposes.
        document.write('<p><a href="http://''/test/">Same domain</a></p>')
    <p><a href="/test/"><span>absolute path</span></a></p>
    <p><a href="test/">relative path</a></p>
    <p><a href="/test?">http not on beginning</a></p>
    <p><a href="#test">anchor</a></p>

Yeah I really dig this event delegation approach instead. Nice work irae


kylebarrow commented Jun 24, 2011

Original script is quick and dirty solution for a site with max of three links and louisremi and irae make good comments regarding performance. Will update with irae's event bubbling, m(_ _)m

irae commented Jun 24, 2011

Nice catch! It is indeed better to use html instead of body. I am so used to always declaring it that I forgot.
Also, the href check will indeed be faster with indexOf insted of regexp. I changed my version accordingly.

But I'll keep my script inline and my other regexp for the flowing reasons:

  • The <script> is inline on purpose. I feel this is essential functionality and the houndtrip too costly. Have you notice what happens to Facebook's WebApp on bad 3G or EDGE coverage?
  • The stop regexp is compiled once at load and is tested so little. test is the fastest RegExp method by far, since it does not check for backreferences. If you have a very messy DOM structure you would be talking of 10 checks against on precompiled regexp.
  • By not over optimizing and keeping the regexp I gain the little advantage of byte size and case insensitive comparison. (you never know when some other “maybe buggy” OS or browser will support “standalone mode”)

After minification, @irae’s script is only 345 bytes – so I definitely agreed that this script should be inlined unless there already is another script file it can be merged with. Some thoughts on inline vs. external files and the file size threshold:

irae commented Jun 24, 2011

Adjusted my script, 306 bytes after minification \o/

Any thought on how to handle a download when running this script? I have a site that has PDF downloads however when you click to download the iPad does not give you the option to do anything with the PDF, as in, it doesnt give me the pop up for (Open in Good Reader or iBooks) and without the navigation you can't return to the original html page.

P.S. I can't inline code to the download because I actually built my site in joomla.

Thoughts?? Need help on this one.


kylebarrow commented Aug 21, 2011

I'm kind of a newbie at this. Can you show me how and where to put that into the script? Sorry for the high maintenance... still learning.


kylebarrow commented Aug 22, 2011


if('href' in noddy && noddy.href.indexOf('http') !== -1 && (noddy.href.indexOf( !== -1 || remotes))


if('href' in noddy && noddy.href.indexOf('http') !== -1 && noddy.href.indexOf('.pdf') == -1 && (noddy.href.indexOf( !== -1 || remotes))

Better to use regex but this will work if your PDFs use lowercase .pdf extension.

stormiii commented Nov 2, 2011

I got a problem here.

The standalone mode is working perfectly when I click on links to other sides.
But in my case I got some lightbox effects for pictures and youtube videos. So when I click on the pictures it opens with the lightbox effect when I am not in the fullscreen/standalone mode. But as soon as I am in the standalone mode it opens up the picture without the lightbox effect. Is there any way around?

Thanks for your help

irae commented Nov 2, 2011

Yes, there is a way around that. Your lightbox script must be using the href to grab the picture or video url. In this case, both scripts fire, and since we set document.location here, it prevails.

To fix that, your lightbox script must call event.stopPropagation(); on the click handler. This way the stay_standalone.js script is never called for elements affected by the lightbox.

stormiii commented Nov 3, 2011

Thanks irae for the quick response.
I am using fancybox for the lightbox effects. But I am not sure if I understood you right.

I added event.stopPropagation(); into the script of fancybox. The only effect is then that the lightbox effect won't occur at all too. Could you tell me where I have to add this?

irae commented Nov 3, 2011

@stormiii, since you are not comfortable in finding where to place the stopPropagation(), it's better not to change the original script, this way you won't have problems updating fancybox in the future. There is a better approach in this case: you can bind a new click handler to stop the propagation. You should place it just before the fancybox initialization. For example:

var fancylinks = $("a.grouped_elements");{

stormiii commented Nov 3, 2011

onclick="if(event && event.stopPropagation) event.stopPropagation();"

This one helped me, even I dont feel that comfy with it. But thanks again for your help

Thank you!

cjanis commented Oct 17, 2012

All of these methods, including @irae's fork, were failing for me because of some async Facebook plugin code. I ended up with this:

It works just fine for me.


ghost commented Mar 14, 2013

Can anyone help me? I'm using this script for my mobile app. It is meant to be used as a standalone app on iPad, after users save the icon to their home screen. I'm having two issues with links, using this script.

  1. Using Twitter web intents to show mini user profiles, when somebody clicks on a twitter handle link, it goes within the standalone app to the Twitter profile. Mobile Safari doesn't open. But then there's no back button or any way to get back to the main app functionality.
  2. The app uses Facebook authentication through Javascript, which works perfectly on the web. When I click a normal link to an outside site with target="_blank", mobile Safari opens, which I would expect. But when I come back to the standalone app from Safari, I'm no longer signed in. It's as if the browser closed, and I have to click the sign in button again.

Can anybody point me in the right direction?


boomkap commented Mar 27, 2013

Anyone have any ideas on how does a user navigate back after opening a remote link. Seems like the user gets jailed at that point.

cawoodm commented Apr 3, 2013

Not clear to me what this script does. Is it only for packaged apps using webkit - i.e. a phonegap HTML5 app? Is the idea that all links remain inside the app instead of opening Safari on the iPhone?

All of these scripts are freezing my iphone and I have to hard reboot. Any ideas?

I think it was because I didn't have type="text/javascript" in the script tag. Now it works! Thanks!

Edit... Noep, it still freezes at times. Strange. Maybe it has nothing to to with the script. Dunno.

Thanks man, you saved my day :)

think it would be a good idea to add this event.defaultPrevented !== true on the following line:

if('href' in noddy && noddy.href.indexOf('http') !== -1 && (noddy.href.indexOf( !== -1 || remotes))

So it will end up something like:

if('href' in noddy && noddy.href.indexOf('http') !== -1 && (noddy.href.indexOf( !== -1 || remotes) && event.defaultPrevented !== true)

The reason why I'm proposing it is because if I have attached some other behavior to the same link and I prevent default on it, it won't work, because this tiny script will take over and redirect.

for the revised script, rolandjitsu's proposal is

  if('href' in curnode && ( curnode.href.indexOf('http') || ~curnode.href.indexOf( ) && e.defaultPrevented !== true) {

I have a link with onclick to confirm the action, with OK and CANCEL option. On any desktop browser (Chrome, Safari...) it works properly, but on iOS WebApp (using that script) the CANCEL button do the same function as OK.

Example Link:

 <a onclick="return confirm('Really want to proceed?')" href="/reservation/cancel/1">Cancel</a>

Any idea to solve it?

mvdklip commented Mar 7, 2014

Thanks for this gist. I ran into the same problem as lucianocn and I think I have solved it by checking for event.preventDefaulted inside the event handler:

if (noddy.nodeName === 'A' && !event.defaultPrevented) {
    if ('href' in noddy && noddy.href.indexOf('http') !== -1 && (noddy.href.indexOf( !== -1 || remotes)) {
        document.location.href = noddy.href;

I think this might be an important addition to prevent conflicts with existing event handlers. Please consider adding it to the gist.

Edit: just found out that rolandjitsu suggested a similar thing a while back.

mvdklip commented Mar 7, 2014

Another suggestion. Instead of checking for the host and remotes flag, I have modified the code to simply look at the target attribute like this:

if (noddy.nodeName === 'A' && !event.defaultPrevented) {
    if ('href' in noddy && !== '_blank') {
        document.location.href = noddy.href;

This allows the app to control which links exactly should be opened in a browser window.

aogaga commented Mar 11, 2014

awesome work dude

how about user login using ajax?. using location.href as a baseURL

This is what i was looking for, but has anyone had any success in iOS 7.1?

sturze commented Jun 4, 2014

yup! also works in IOS 7.1.1

anyone tested this with jomsocial? it isnt working in the dropdown and i cant tell why :(

I made a bower installable snippet of the solution created here and called it iosweblinks

bower install --save iosweblinks

hi i sue this script in an angular web app and i face a problem with the menu. it is not expanding. instead it refreshes the whole page.

when I use the script it kills the Nivo-Lightbox ( launch up and it just opens the image on its own :( Anyone have any ideas? help me please

First and foremost, this has been very helpful so thank you to everyone who contributed!!

This is working great for me on my site on all links except for one:

" "

I've tried several different things thus far to prevent this link from opening outside of my app but I've had no luck yet.

If anyone can shed light on this I would be very grateful!

Thanks in advance!

I see this is a good four years old now. It doesn't seem to work for me in iOS 8.1+ Can anyone confirm if it works or doesn't for them? I tried setting remotes = TRUE but all href links open in mobile Safari for me.

Can anyone confirm this works with modern IOS? I'm having no luck making this work.

Amazing, thank you so much! Just used it on an iPhone 6. Does work!

amazing thanks, confirmed working in iOS 9.3.4 : +1: using irae 's gist here:

This is excellent. Works on iOS 10.2.1. Thanks @irae !

eprompc commented Feb 24, 2017

I have a link with onclick to confirm the action, with OK and CANCEL option. On any desktop browser (Chrome, Safari...) it works properly, but on iOS WebApp (using that script) the CANCEL button do the same function as OK.

Example Link:

<a onclick="return confirm('Really want to proceed?')" href="/reservation/cancel/1">Cancel</a>

Any idea to solve it?

Did a simple test and it still work in IOS 11.2 Beta. Thanks!

Here is a modification for rails applications wanting their data-remote="true" links to work as well. In coffeescript:

$(document).ready ->
  # For iOS Web apps, so they do not open in new window
  if 'standalone' of window.navigator and window.navigator.standalone
    # If you want to prevent remote links in standalone web apps opening Mobile Safari, change 'remotes' to true
    noddy = undefined
    remotes = false
    document.addEventListener 'click', ((event) ->
      noddy =
      # Bubble up until we hit link or top HTML element. Warning: BODY element is not compulsory so better to stop on HTML
      while noddy.nodeName != 'A' and noddy.nodeName != 'HTML'
        noddy = noddy.parentNode
      if 'href' of noddy and noddy.href.indexOf('http') != -1 and (noddy.href.indexOf( != -1 or remotes)
        # do not redirect page on data-remote links
        (document.location.href = noddy.href) unless $(noddy).data('remote') == true
    ), false
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment