miércoles, 26 de diciembre de 2012

La detección del navegador y sus problemas(browser detection)

Detectar el navegador vs detectar funcionalidades

Detectar el navegador que está utilizando el usuario mediante una búsqueda del string correspondiente ("MSIE", "Firefox", etc) en la cadena de texto guardada en navigator.userAgent ha sido una práctica común durante años. Se ha utilizado, por ejemplo, para ejecutar un código diferente según el navegador.

if ( navigator.userAgent.indexOf("MSIE") !== -1 )
  {
    // Run custom code for Internet Explorer.
  }
else {
    // Run code for other browsers
} 
Un ejemplo clásico es el objeto utilizado para AJAX. Durante muchos años el IE ha utilizado ActiveXObject mientras el resto de navegadores utilizaban XMLHttpRequest. Era necesario obtenerlo de forma diferente.

Hoy en día esta detección tan simple está descartada porque estamos asumiendo que cualquier versión futura de MSIE tendrá el mismo problema. En el momento que una versión de IE cambie y utilice el objeto estándar nuestro script fallará.

Podemos hacer un script más complejo y detectar también la versión del navegador, pero, como veremos más adelante, el uso de navigator.userAgent no es recomendable. Para saber si podemos usar una determinada funcionalidad o no, debemos probar si esa funcionalidad concreta está presente. Esto es lo que se llama detección de funcionalidades o detección de capacidades.

La forma correcta de saber si podemos utilizar XMLHttpRequest sería:

if ( window.XMLHttpRequest ) {

   // Use XMLHttpRequest
}
else {

    // Use ActiveXObject
} 
De esta forma nuestra prueba es independiente del fabricante/versión del navegador.

Los problemas de utilizar el user-agent


Esta cadena tiene valores del tipo:

Chrome: "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 (KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"

IE: "Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)"

Safari: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/534.55.3 (KHTML, like Gecko) Version/5.1.3 Safari/534.53.10"

Estos son sólo unos ejemplos puntuales, pero el texto será diferente según versión, sistema operativo, etc. Hay cientos de user agents diferentes, no solo por las versiones de navegadores, también están los robots de los buscadores ( crawlers ), los lectores de feeds, los clientes de correo, los validadores de código, librerías, etc. En la web http://www.useragentstring.com puede verse una lista completa.

Normalmente la detección, como vimos antes, consiste en localizar el nombre del navegador en este string. Un ejemplo simplificado en JavaScript:

if ( navigator.userAgent.indexOf("Chrome") !== -1 ) {
    alert ("El navegador es Chrome")
}

Igualmente podríamos buscar el string "Firefox" o "MSIE" para detectar otros navegadores. El problema es que el formato del texto no sigue un estándar unificado. Es necesario hacer una búsqueda del nombre del navegador en toda la cadena. Es necesario también saber con que nombre aparece cada navegador.

Además del problema de bloquear versiones futuras (y corregidas) del mismo navegador, la información del user-agent no es fiable. Hay muchos navegadores que utilizan el nombre de otros para que no se les bloquee o se les sirva una versión diferente del sitio web. Por ejemplo Opera puede contener el string "MSIE". Internet Explorer utilizó durante años el string "Mozilla" para indentificarse como Netscape, Chrome incluye el string "Safari" para 'disfrazarse' en los dispositivos de Apple.

Las razones y la historia detrás de los múltiples cambios, parches y añadidos del user-agent puede leerse en el divertido artículo History of the browser user-agent string.

Debido a este empeño de los navegadores en ofuscar su identidad, los scripts de detección se han hecho más complejos porque es necesario saber qué texto buscar y qué texto excluir para cada caso. Por ejemplo, para saber si un navegador es Safari no basta con buscar ese texto, tenemos también que comprobar que no aparezca "Chrome". Además es recomendable localizar también la versión del navegador, que puede aparecer en lugares distintos según el fabricante. Por todo esto es recomendable utilizar un script probado como el de [Quirksmode].

Además de todo esto un usuario puede manipular y cambiar muy facilmente el valor del user-agent en su navegador.

Esta técnica ha sido relegada a casos muy excepcionales y no debe usarse nunca cuando lo que queremos saber es si una funcionalidad o capacidad concreta está disponible. Para estos casos, la detección de funcionalidades es mucho más apropiada.

Detección de funcionalidades (feature detection)

Las diferencias entre navegadores aún nos obligan a realizar algún tipo de detección antes de utilizar algunas funcionalidades implementadas de forma diferente. Por ejemplo, si queremos capturar un evento del DOM ( para asignarle una función, por ejemplo ) tendremos que utilizar el método attachEvent() para IE<9 o el método addEventListener() para otros navegadores. Lo correcto es detectar si el método en cuestión está disponible:

var element = document.getElementById( “target” );
if ( element.addEventListener ) {
        element.addEventListener( "click", function, false);
} else if ( element.attachEvent ){
        element.attachEvent("onclick", function);
}

De forma parecida podemos saber si el navegador soporta algunas de las características más novedosas de la familia HTML5.

LocalStorage

if (!!window.localStorage) {
   /* you have localstorage */
}
nota: !! sirve sólo para forzar una conversión de tipo a booleano.

canvas

if ( !!document.createElement("canvas").getContext ) {
   /* you have canvas*/
}

atributo placeholder en los input de texto

var test = document.createElement('input');
if ( 'placeholder' in test ) {
    // you have placeholder attribute for inputs
}
Como vemos en estos ejemplos anteriores, la clave está en encontrar la forma de detectar la capacidad concreta que necesitamos. Debemos relegar la búsqueda en el user-agent a problemas muy puntuales, por ejemplo cuando una característica está soportada pero el soporte es incorrecto o muy ineficiente.

Fuentes:
Feature detection: state of the art browser scripting
Stack Overflow: What is the best way to do browser detection
Stack Overflow: Best way to detect browser
Browser detection is bad

No hay comentarios:

Publicar un comentario