Detectar el bloqueo de los popups

Publicado el 20 febrero 2011 por Marcis

Hace poco tuve que implementar una API en PHP que pretendía integrar funcionalidades de terceros en un sitio web. Uno de los métodos que se necesitaban debía devolver la URL a la que se redirige inicialmente al usuario para que nos dé permisos de acceso a su cuenta, en principio mediante OAuth.

No hubo problema para los dos primeros casos (Gmail y Facebook), pero al empezar con Hotmail, que utiliza una librería en Javascript, era preferible abrir el popup estándar que provee Windows Live antes que mandar al usuario a una página en la que el 95% del contenido está en blanco.

Las complicaciones empezaron al intentar abrir una ventana nueva de forma “indirecta”, es decir, sin especificar el código directamente en el HTML original, sino a través de una función asignada de forma dinámica:

function wlConsent(connect_url) {
  window.open(connect_url, 'wl', 'width=...');
}

Dicha ventana se abría correctamente en la mayoría de los navegadores, pero algunos, los más modernos, no lo permitían debido a su bloqueador de popups.

La solución parecía sencilla, recoger el valor devuelto por el método “open” del objeto “window” y comprobar si estaba vacío:

var popup = window.open(...)
if (!popup || popup.closed || typeof popup == 'undefined' || typeof popup.closed=='undefined') {
  // popup bloqueado
}

Pero Chrome no se comporta como el resto de sus competidores. El navegador de Google requiere de una comprobación “extra” realizada desde un popup a través de la función:

function chrome_popups_permitted() {
  if(window.innerHeight != 0) {
    return true;
  } else return false;
}

Basta pues con detectar si el navegador del usuario es Chrome y, en tal caso, abrir una ventana con el código Javascript indicado y realizar la llamada a dicha función. Si los popups están bloqueados, se redirige al usuario a la URL solicitada en vez de abrirla en una nueva ventana/pestaña.

El resultado final, encapsulado en una clase sencilla, es el siguiente:

var PopupTester = {
  popup : Object,
  url : "",
  name : "popup_tester",
  options : "width=1,height=1,left=0,top=0",
  open : function(url, name, options) {
    this.url = url
    if (name) this.name = name
    if (options) this.options = options
    if(this.popups_are_disabled() == true) {
      this.redirect_to_instruction_page();
    }
  },

  redirect_to_instruction_page : function() {
    document.location.href = this.url;
  },

  popups_are_disabled : function() {
    this.popup = window.open("/chrome_popup_test.html", this.name, this.options);

    if (!this.popup || this.popup.closed || typeof this.popup == 'undefined' || typeof this.popup.closed=='undefined') {
      return true;
    }

    //
    // Chrome popup detection requires that the popup validates itself
    // - so we need to give
    // the popup time to load, then call js on the popup itself
    //
    if (navigator & (navigator.userAgent.toLowerCase()).indexOf("chrome") > -1) {
      var on_load_test = function(){PopupTester.test_chrome_popups();};
      var timer = setTimeout(on_load_test, 200);
      return;
    }

    this.popup.location.href = this.url;
    return false;
  },

  test_chrome_popups : function() {
    if (this.popup & this.popup.chrome_popups_permitted & this.popup.chrome_popups_permitted() == true) {
      this.popup.location.href = this.url;
      return true;
    }

    this.popup.close();
    //
    // If the popup js fails - popups are blocked
    //
    this.redirect_to_instruction_page();
  }
};

Y un ejemplo de uso, idéntico al método original:

PopupTester.open(url, window_name, options)

Esta solución se basa en la propuesta de The Code Abode con algunas modificaciones, que permiten abrir el popup “de prueba” bajo demanda, es decir sólo en caso de necesitarlo.