Avoid browser popup blockers
Asked Answered
E

12

225

I'm developing an OAuth authentication flow purely in JavaScript and I want to show the user the "grant access" window in a popup, but it gets blocked.

How can I prevent pop up windows created by either window.open or window.showModalDialog from being blocked by the different browsers' pop-up blockers?

Enviable answered 6/4, 2010 at 19:29 Comment(6)
Even if it were possible (I don't know), if people use popup blockers you should respect it. Most browsers display a message when a site tried to open a popup so they can still see it if they want to. You can put a remark on your site that some content opens in a popup and the user should allow it in order to proceed.License
The best practice would look like: 1) Do this successfully 2) Shutter your windows and bar your door and cower in fear from the gathering crowd of upset web-patrons 3) Repent, remove the pop-up-busta-busta, and respect your audience.Coiffure
Alex and Felix, I've updated the question. I wont use the knowledge for evil :). Thanks!Enviable
I'd like to add that bypassing a popup blocker may be actually trying to make the user experience better. In an example I'm working on now, we are using a Javascript application (based on ExtJS) and we are trying to let users pay using paypal. We are giving them a button they can click to launch paypal in a new window, but certain versions of IE are blocking it as a popup (even though it's a button click). If they now enable the popup, the screen reloads and as a JavaScript app, we lose the window state and they have to start over. So really: the problem is IE is dumb.Competence
@PabloFernandez did you solve this issue? I notice you accepted dthorpe's answer, but just curious to know if this worked for you or if the OAuth pop-up still triggered the blocker?Elconin
@FelixKling Yes it is possible. Browser makers already thought about this. Opening a popup is OK as far as there is user intent (signaled by the user clicking a link or button). Popup blockers should respect the user intent. If IE's popup blocker does not, it's the popup blocker that is at fault. Users use popup blockers to prevent scripts from opening popups at will, not for blocking popups they themselves tried to open (by clicking a button or link).Mamey
D
344

The general rule is that popup blockers will engage if window.open or similar is invoked from javascript that is not invoked by direct user action. That is, you can call window.open in response to a button click without getting hit by the popup blocker, but if you put the same code in a timer event it will be blocked. Depth of call chain is also a factor - some older browsers only look at the immediate caller, newer browsers can backtrack a little to see if the caller's caller was a mouse click etc. Keep it as shallow as you can to avoid the popup blockers.

Deserve answered 6/4, 2010 at 19:32 Comment(4)
Interestingly, popups initiated through a change event bound to a select element will get blocked (in Chrome, not FF), even though that event is initiated by a direct user action, like a click. Although if bound to an input, they're allowed. Strange.Sarco
Nobody said the browser was consistent. :PDeserve
Through experiments I've got to understand that stack depth has nothing to do with popup blocker. It actually checks whether window.open is called within 1 second after user action or not. Tested in Chrome 46 and Firefox 42.Hammad
The 1 second timeout can be circumvented in both browsers allowing for an indefinite amount of time using the answer by @t-j-crowder on this page .. have the URL you're calling via ajax be some php (or whatever) that uses sleep() for an amount of time before it returns a response. The browser will hang while it 'loads' the page.. then trigger the popup when it receives a response. In Chrome, though, you must add an alert() right before the ajax() to make this trick work.Regress
B
234

Based on Jason Sebring's very useful tip, and on the stuff covered here and there, I found a perfect solution for my case:

Pseudo code with Javascript snippets:

  1. immediately create a blank popup on user action

     var importantStuff = window.open('', '_blank');
    

    (Enrich the call to window.open with whatever additional options you need.)

    Optional: add some "waiting" info message. Examples:

    a) An external HTML page: replace the above line with

     var importantStuff = window.open('http://example.com/waiting.html', '_blank');
    

    b) Text: add the following line below the above one:

     importantStuff.document.write('Loading preview...');
    
  2. fill it with content when ready (when the AJAX call is returned, for instance)

     importantStuff.location.href = 'https://example.com/finally.html';
    

    Alternatively, you could close the window here if you don't need it after all (if ajax request fails, for example - thanks to @Goose for the comment):

     importantStuff.close();
    

I actually use this solution for a mailto redirection, and it works on all my browsers (windows 7, Android). The _blank bit helps for the mailto redirection to work on mobile, btw.

Bernina answered 31/7, 2014 at 4:32 Comment(10)
? so what do you do if it's not from user action in the browser? For my example, after user authenticates against server, it must open a new tabPurine
Whatever it is you intend to do, there is a better way than a popup window lol? Odd generalization. Client would like the page they authenticated at to stay open, and the new site/landing page after authentication to open in a new tab. Yes, not my idea of excellent user experience... but it seems reasonablePurine
@mmcrae. Sit down and think, seriously. I promise you you will find a "user action" in the scenario you describe. The whole point of my answer is that you create the popup window when you have the user action - and then fill it with content later. Hint for your case: user hits "authenticate" -> create the popup (empty); timer is up or server response is back or whatever -> fill the content into the popup.Bernina
not sure what you're describing create the popup ... -> fill the content into the popup. It's a URL which is opened in a new tab, but we must go to server first to authenticate (to determine if they're allowed to go to this new URL), and then reload page. It's after they clicked the button, after the server-side authentication, after the page reload that we must send to URL in newtab. So I guess I'm stuck with the issue of it being too divorced from user action. It's webforms btw which adds a wrinkle...Purine
Just do step 2 of the answer with whatever "authenticated" page the user will go to. Why would that not work?Bernina
@SwissMister I guess if we could do AJAX authentication it would work, but at this point it would be a lot of work to translate what we're doing server-side into the AJAX authentication required to do this. Thanks for your suggestionsPurine
My experience is this work perfectly. The problem is not that my window.open call was not prompted by the user, but that my call was embedded in a return of a subsequent API call. By instantiating the window immediately after the user "click", the problem is solved. Thanks!Ironclad
I really appreciated that solution. It just works fine. One way to improve this solution is to add a script on the loading page that closes automatically after some seconds. For my use case, it was the better behavior. Thx!Putter
Useful tip, if ajax request fails, call importantStuff.close to close the new tab and provide and alert in the original page.Ingram
I am doing this to open a link which downloads a file. Is there a way to close the window after the download has finished (or begun). If I just set the window's location and then close it, the download doesn't even begin and the window is closed.Sherronsherry
P
32

As a good practice I think it is a good idea to test if a popup was blocked and take action in case. You need to know that window.open has a return value, and that value may be null if the action failed. For example, in the following code:

function pop(url,w,h) {
    n=window.open(url,'_blank','toolbar=0,location=0,directories=0,status=1,menubar=0,titlebar=0,scrollbars=1,resizable=1,width='+w+',height='+h);
    if(n==null) {
        return true;
    }
    return false;
}

if the popup is blocked, window.open will return null. So the function will return false.

As an example, imagine calling this function directly from any link with target="_blank": if the popup is successfully opened, returning false will block the link action, else if the popup is blocked, returning true will let the default behavior (open new _blank window) and go on.

<a href="http://whatever.com" target="_blank" onclick='return pop("http://whatever.com",300,200);' >

This way you will have a popup if it works, and a _blank window if not.

If the popup does not open, you can:

  • open a blank window like in the example and go on
  • open a fake popup (an iframe inside the page)
  • inform the user ("please allow popups for this site")
  • open a blank window and then inform the user etc..
Predictory answered 25/9, 2015 at 11:20 Comment(2)
Thank you. Worked!Punk
In addition to the alternative actions mentioned above I was thinking of doing a window.confirm() to achieve user consent again to be able to open popup upon direct user click. but surprisingly didn't work :(Fullerton
A
24

In addition Swiss Mister post, in my case the window.open was launched inside a promise, which turned the popup blocker on, my solution was: in angular:

$scope.gotClick = function(){

  var myNewTab = browserService.openNewTab();
  someService.getUrl().then(
    function(res){
        browserService.updateTabLocation(res.url, myNewTab);

    }
  );
};

browserService:

this.openNewTab = function(){
     var newTabWindow = $window.open();
     return newTabWindow;
}

this.updateTabLocation = function(tabLocation, tab) {
     if(!tabLocation){
       tab.close();
     }
     tab.location.href = tabLocation;
}

this is how you can open a new tab using the promise response and not invoking the popup blocker.

Armure answered 27/10, 2016 at 11:44 Comment(5)
This led to my solution! Where I created a variable containing the opened tab, and then filled the url after data had loaded. Thanks. const tab = window.open(); observable.subscribe(dataUrl => tab.location.href = dataUrl);Supersession
If you have the popup blocker enabled this won't fix the issue with the popup blocker...Eventide
@Alejandro Vales jonas solution will work only if the code will run from an onClick event, or any user interaction. When you try to open a new tab via windows.open inside a promise, timeout, subscribe... the browser think it is a manipulation on the user, so the solution is to create an instance before entering the promise, inside you just change the href like jonas didArmure
@Armure Thank you very much!!! I didn't happen to see the .then on then answer and that is why I got so confused :/ SRY...Eventide
@Armure This is great! Works like a charm. If I could bother you with just one more question – do you know how to make this work in Edge, too? A nudge to a right resource would greatly help...Whitehurst
M
8

from Google's oauth JavaScript API:

http://code.google.com/p/google-api-javascript-client/wiki/Authentication

See the area where it reads:

Setting up Authentication

The client's implementation of OAuth 2.0 uses a popup window to prompt the user to sign-in and approve the application. The first call to gapi.auth.authorize can trigger popup blockers, as it opens the popup window indirectly. To prevent the popup blocker from triggering on auth calls, call gapi.auth.init(callback) when the client loads. The supplied callback will be executed when the library is ready to make auth calls.

I would guess its relating to the real answer above in how it explains if there is an immediate response, it won't trip the popup alarm. The "gapi.auth.init" is making it so the api happens immediately.

Practical Application

I made an open source authentication microservice using node passport on npm and the various passport packages for each provider. I used a standard redirect approach to the 3rd party giving it a redirect URL to come back to. This was programmatic so I could have different places to redirect back to if login/signup and on particular pages.

github.com/sebringj/athu

passportjs.org

Motorcar answered 5/6, 2012 at 19:14 Comment(0)
K
8

I tried multiple solutions, but his is the only one that actually worked for me in all the browsers

let newTab = window.open(); newTab.location.href = url;

Kerrykersey answered 2/6, 2018 at 22:2 Comment(5)
This just doesn't work for people that have the popup blocker enabledEventide
what is url? it is not assigned.Yingling
@Yingling url is the link to whatever page you want to access, for example http://example.comKerrykersey
@AlejandroVales i have popup blocker enabled and it works. The problem is that window.open() cannot open new window programmatically and if we need to, that answer is one of the options. With the newTab variable we make reference to window.open() and later we can call it, insert the url we need, in my case url of some blob, and the new tab will be opened with on the entered url.Gorblimey
yes its not working for people that has adblocker enabled. that is the problem that we have to avoidPasha
T
7

My use case: In my react app, Upon user click there is an API call performed to the backend. Based on the response, new tab is opened with the api response added as params to the new tab URL (in same domain).

The only caveat in my use case is that it takes more for 1 second for the API response to be received. Hence pop-up blocker shows up (if it is active) when opening up URL in a new tab.

To circumvent the above described issue, here is the sample code,

var new_tab=window.open()
axios.get('http://backend-api').then(response=>{
    const url="http://someurl"+"?response"
    new_tab.location.href=url;
}).catch(error=>{
    //catch error
})

Summary: Create an empty tab (as above line 1) and when the API call is completed, you can fill up the tab with the url and skip the popup blocker.

Tubercular answered 6/10, 2020 at 3:52 Comment(0)
S
4

@here I found it's work fine in all browsers

window.open(URL) || window.location.assign(URL)
Saliferous answered 22/11, 2021 at 15:22 Comment(0)
P
4

Just use window.location.href = yourUrl or a url with target='_blank'

window.open() has too many issues, if it's more than one second from a user action a lot of browsers treat it as a popup and block it

Passionless answered 17/12, 2021 at 8:51 Comment(1)
window.location.href = yourUrl worked hereMetasomatism
S
0

I didn't want to make the new page unless the callback returned successfully, so I did this to simulate the user click:

function submitAndRedirect {
  apiCall.then(({ redirect }) => {
      const a = document.createElement('a');
      a.href = redirect;
      a.target = '_blank';
      document.body.appendChild(a);
      a.click();
      document.body.removeChild(a);
  });
}
Setose answered 12/9, 2017 at 19:28 Comment(2)
doesn't work for me in Chrome 62. Popup (page) gets blocked.Godrich
Yes you're right - I have also found this to be inconsistent. I ended up making my api call synchronousSetose
S
0

I have managed this by using setTimeOut function.

setTimeOut(function(){
  window.location.replace("https://www.google.com/");
}, 1000)

✅✅✅✅✅

Straightaway answered 27/1, 2023 at 8:12 Comment(0)
G
-12

The easiest way to get rid of this is to:

  1. Dont use document.open().
  2. Instead use this.document.location.href = location; where location is the url to be loaded

Ex :

<script>
function loadUrl(location)
{
this.document.location.href = location;
}</script>

<div onclick="loadUrl('company_page.jsp')">Abc</div>

This worked very well for me. Cheers

Grogan answered 31/1, 2016 at 0:0 Comment(1)
What about to open url in new tab?Sizing

© 2022 - 2024 — McMap. All rights reserved.