Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

L'arte dello sniffing: come riconoscere i browser

Tecniche e metodi per riconoscere i browser con JavaScript
Tecniche e metodi per riconoscere i browser con JavaScript
Link copiato negli appunti

In questo articolo esaminiamo alcune tecniche di sniffing. Niente di illegale, sono quelle tecniche che consentono di rilevare le caratteristiche del browser e della piattaforma dell'utente che sta visitando le nostre pagine.

L'argomento, con il crescente supporto ai principali standard del W3C da parte di tutti i Browser potrebbe sembrare superfluo. Ma un buon codice crossbrowser può essere interessante l'uso dello sniffing. L'importanza di questa pratica si fonda su diversi punti cardine, più o meno ovvi:

  • Visibilità e Accessibilità, aumentare il numero di utenti che fruire senza problemi dei contenuti;
  • Potenzialità, sfruttare le caratteristiche specifiche di browser per miglioare al massimo l'esperienza utente (per questo è un ottima pratica anche la features-detection).

Fatta questa breve premessa, passiamo alle tecniche di sniffing sui maggiori browser. Esistono più strade, diverse tra loro, sia per rilevare le informazioni cercate, sia per utilizzarle.

Vediamo per prima cosa come recuperare i dati. Esistono due strade principali per recuperare le informazioni sul tipo di browser attraverso Javascript, una, per così dire "rigorosa" e l'altra più diretta.

Leggere lo userAgent (tecnica rigorosa)

La tecnica che abbiamo definito "rigorosa", consiste nella lettura della proprietà userAgent dell'oggetto navigator, che contiene informazioni circa le caratteristiche del browser.

navigator.userAgent

User-Agent è una stringa, definita dal protocollo HTTP come un product token, cioè in sostanza, una sorta di "etichetta del prodotto" navigator, che caratterizza e identifica il browser utilizzato.

User-Agent contiene l'informazioni circa il tipo di browser utilizzato, alla sua versione, alla piattaforma sul quale è istallato e può contenere anche una stringa di commento.

Insieme alla User-Agent, vengono utilizzate altre proprietà dell'oggetto navigator, che contengono stringhe che compongono ed esplicitano la User-Agent stessa, e che possono semplificare le operazioni di sniffing, cioè:

  • appCodeName restituisce il nome codificato del browser
  • appVersion restituisce la versione del browser
  • appName restituisce il nome completo del browser
  • platform restituisce la piattaforma su cui è istallato il browser

Il codice seguente serve per visualizzare ciò che le stringhe riportano nel vostro browser:

<script type="text/javascript">
var output = "<table>"+
             "<tr><td><strong>userAgent</strong></td><td>"+navigator.userAgent+"</td></tr>"+
			 "<tr><td><strong>appCodeName</strong></td><td>"+navigator.appCodeName+"</td></tr>"+
			 "<tr><td><strong>appVersion</strong></td><td>"+navigator.appVersion+"</td></tr>"+
			 "<tr><td><strong>appName</strong></td><td>"+navigator.appName+"</td></tr>"+
			 "<tr><td><strong>platform</strong></td><td>"+navigator.platform+"</td></tr>"+
             "</table>";
document.write(output);
</script>

Vediamo un esempio di cosa riporta questo breve snippet e come appare lo userAgent del browser che stiamo utilizzando (da provare con diversi browser per vedere le differenze):

Come si può notare non esiste un formato preciso di come debbano essere descritte le varie informazioni. Questo è un primo problema da superare per discriminare tra browser e versioni.

Non solo, esistono anche delle incongruenze. Per esempio il valore di appVersion non sembra essere semanticamente comparabile tra i diversi browser.

In Explorer, nelle versioni 5 e superiori, il valore iniziale della versione riportato dalla proprietà appVersion non corrisponde al valore effettivo della versione del browser.

La Microsoft spiegò che tale comportamento era giustificato dalla necessità di estendere l'usabilità di vecchi script creati precedentemente alle nuove versioni e che facevano solo questo tipo di controllo:

if(parseInt(navigator.appVersion)==4) {
	// lo script viene eseguito solo dai browser
	// in cui la proprietà appVersion inizia con "4.0..."
}

Gli stessi problemi si presentano anche nelle versioni 6.0 e superiori di Netscape.

Queste incongruenze e questa mancanza di un formato definito, possono comunque essere colmate andando a scandire le stringhe sopracitate grazie ai metodi che JavaScript mette a disposizione per controllare le stringhe.

In particolare è possibile riuscire a recuperare le informazioni volute, cercando "parole chiave" relative a browser e versioni con il metodo indexOf, la cui sintassi è:

stringa.indexOf(sottostringa,[indice_iniziale])

Argomento Descrizione
sottostringa è la stringa che si vuole cercare all'interno della stringa a cui viene applicato il metodo
Se la sottostringa è presente viene restituito l'indice (intero) della stringa al quale la sottostringa comincia.
In caso contrario il metodo restituisce -1.
indice_iniziale è l'indice del carattere nella stringa dal quale si vuole cominciare la ricerca della sottostringa

Non solo, insieme a questo metodo possono essere usati gli altri metodi per estrapolare la parte delle stringhe che interessa.

Cominciamo a descrivere questi metodi proprio per eseguire lo sniffing delle versioni del browser di casa Microsoft.

In particolare poniamo l'attenzione sul fatto che lo userAgent, e in particolare appVersion di Explorer è contraddistinto in tutte le sue versioni dalla parola chiave "MSIE", seguita poi dall'effettiva versione del browser.

Ricercare la sola presenza della sottostringa "MSIE" non è però una soluzione efficace, per il fatto che spesso anche Opera inserisce la stessa stringa nella propria userAgent. Occorre quindi incrociare queste informazioni per effettuare la rilevazione.

Una possibilità è:

if((ind = navigator.appVersion.indexOf("MSIE")) > -1 &&
          navigator.userAgent.indexOf("Opera") == -1)
{
	/*
	 * il browser è Explorer
	 * e la variabile ind contiene l'indice di "MSIE" nella stringa appVersion
	 * a questo punto possiamo recuperare la versione in vari modi:
	 *
	 * 1) recuperando il valore intero della versione ed eliminando la parte
	 *    iniziale della stringa, incluso "MSIE " (5 caratteri)
     *
     * 2) recuperando la versione completa, sotto forma di valore decimale
     */
	/* 1 */
	versione1 = parseInt(navigator.appVersion.substr(ind+5));
	/* 2 */
	versione2 = parseFloat(navigator.appVersion.substr(ind+5));
}

Come vedete, la filosofia per riuscire a carpire ciò che ci interessa è piuttosto semplice.

La stessa filosofia, con gli accorgimenti del caso, può essere applicata anche agli altri browser. In particolare per Netscape occorre notare che per essere sicuri che il browser sia effettivamente netscape, conviene recuperare l'appName; nel caso di Opera, ciò che non è sempre rispettato nella stringa userAgent è la presenza dello spazio tra la sottostringa "Opera" e la versione del browser, ma con un po' di ingegno si può far fronte anche a questa difficoltà:

if(navigator.appName.indexOf("Netscape") > -1)
{
	/* il browser è Netscape
     * anche in questo caso abbiamo le due possibilità.
	 */
	/* 1 */
	versione1 = parseInt(navigator.appVersion);
	/* 2 */
	versione2 = parseFloat(navigator.appVersion);
} 
if((ind = navigator.userAgent.indexOf("Opera")) > -1)
{
	/* il browser è Opera
	 * a questo punto ind contiene l'indice della stringa "Opera"
	 * che prendiamo come nuovo punto di partenza per la ricerca
	 * della versione del browser:
	 */ 
	punto = navigator.userAgent.indexOf(".",ind); 
	/* 1 */
	versione1 = parseInt(navigator.userAgent.substr(punto-1));
	/* 2 */
	versione2 = parseFloat(navigator.userAgent.substr(punto-1));
}

A questo punto, occorre stabilire il modo più semplice per usare queste informazioni.

La strada più conveniente da seguire, in particolare per applicazioni DHTML, è quella di crearsi variabili globali il cui nome sia descrittivo (il cui significato sia quindi facilmente riconoscibile) e il cui valore permetta di stabilire quale sia il browser (e la sua versione) e su quale piattaforma.

Una prima soluzione potrebbe essere:

// rileviamo il Sistema Operativo il Browser e la sua versione
// e settiamo variabili globali che contengano i risultati dello sniffing 
SOWIN = (navigator.userAgent.toLowerCase().indexOf("win") > -1) ? 1 : 0;
SOMAC = (navigator.userAgent.toLowerCase().indexOf("mac") > -1) ? 1 : 0;
SOLIN = (navigator.userAgent.toLowerCase().indexOf("linux") > -1) ? 1 : 0;
SOALT = (!SOWIN && !SOMAC && !SOLIN) ? 1 : 0; 
OP = ((ind1 = navigator.userAgent.indexOf("Opera")) > -1) ? 1 : 0;
punto = (OP) ? navigator.userAgent.indexOf(".",ind1):0;
OP5 = (OP && parseInt(navigator.userAgent.substr(punto-1)) == 5) ? 1 : 0;
OP6 = (OP && parseInt(navigator.userAgent.substr(punto-1)) == 6) ? 1 : 0; 
IE = ((ind2 = navigator.appVersion.indexOf("MSIE")) > -1 && !OP) ? 1 : 0;
IE4 = (IE && parseInt(navigator.appVersion.substr(ind2+5)) == 4) ? 1 : 0;
IE5 = (IE && parseInt(navigator.appVersion.substr(ind2+5)) == 5) ? 1 : 0;
IE6 = (IE && parseInt(navigator.appVersion.substr(ind2+5)) == 6) ? 1 : 0; 
NN = (navigator.appName.indexOf("Netscape")>-1) ? 1 : 0;
NN4 = (NN && parseInt(navigator.appVersion)==4) ? 1 : 0;
NN6 = (NN && parseInt(navigator.appVersion)>4) ? 1 : 0; 
OT = (!IE && !NN && !OP) ? 1 : 0;

Verifichiamolo:

<form>
	<input type="button" value="sniffa" onclick="sniffa()">
</form>

A questo punto è possibile far uso delle variabili globali per eseguire script a seconda delle varie discriminanti.

Per esempio, per eseguire una funzione solo nel caso in cui il browser sia Explorer su Windows o Mac la funzione avrebbe la seguente struttura:

function ie_win_mac() { 
	if(IE && (SOWIN || SOMAC)) {
		// corpo della funzione
	}
}

Questo stesso principio può essere applicato ad ogni funzione con scopi crossbrowser, crossversion e crossplatform.

Quanto detto finora farebbe pensare che esista un numero limitato di possibili stringhe per la User-Agent, al massimo pari al numero di tutte le possibili combinazioni di browser, versione e piattaforma.

In realtà non è così!

È possibile, attraverso diverse strade (Perl, byte editor, ecc..), per lo più battute da programmatori esperti, riuscire ad aggiungere stringhe che possano contenere le più disparate informazioni. In alcuni siti, anche di modesto traffico, sono state rilevate migliaia di User-Agent differenti, alcune contenenti anche i propri gusti musicali!

È possibile riuscire a cambiare o mascherare anche le informazioni che in teoria non dovrebbero essere modificate.

Un'esempio di cosa si può trovare? in un sito che ne riportava alcuni c'era: "Nintendo64/1.0 (SuperMarioOS with Cray-II Y-MP Emulation)"

Perciò, benchè questa sia la tecnica più rigorosa, in realtà non si è sicuri di poter rilevare correttamente tutti i browser.

Tecnica Diretta a Rilevazione d'Oggetto

Questa è la tecnica che più preferiscono i programmatori DHTML. In sostanza, il fondamento di questo metodo sta nel fatto che non tutti gli oggetti, le collezioni, le proprietà accessibili attraverso il Javascript sono supportati da ogni browser.

Ergo, questi stessi oggetti, collezioni e proprietà vengono utilizzati per discriminare i vari browser.
Anche in questo caso ci sono centinaia di scripts che eseguono lo sniffing dei browser e delle versioni rilevando gli oggetti. La filosofia è la stessa della tecnica precedente, ovvero si creano variabili globali identificative del browser e della versione che poi sono recuperate nelle varie funzioni.

Un esempio che comprende i tre browser principali è il seguente:

OP = (window.opera) ? 1:0;

IE4 = (document.all && !OP) ? 1:0;

IE5 = (IE4 && document.getElementById) ? 1:0;

NN4 = (document.layers) ? 1:0;

NN6 = (!IE4 && !NN4 && document.getElementById) ? 1:0;

NOT = (!OP && !IE4 && !IE5 && !NN4 && !NN6) ? 1:0;

OK! funziona. I pragmatici sono accontentati!Occorre però spendere due parole sul giusto utilizzo di questa tecnica.

Troppo spesso si utilizzano "formule oramai comuni" per individuare le varie versioni e i vari browser, indipendentemente dallo scopo che si vuole raggiungere. Beh, questo è il modo sbagliato di utilizzare questa tecnica.

Infatti chi fa uso di uno script come quello precedente, assume implicitamente che se, ad esempio, è presente la collezione document.all, allora il browser è Explorer e perciò si sente autorizzato, una volta superata questa condizione a fare script che facciano uso di tutte le capacità di Explorer, utilizzando il suo DOM e le sue caratteristiche. Allo stesso modo, se è supportata la collezione document.layers, si è certi di trovarsi in Netscape 4!

Purtroppo questo non è garantito! O meglio, siamo sì sicuri che, ad esempio, Explorer riconosca document.all, ma non abbiamo la certezza che altri browser non supportino anch'essi la stessa collezione

.

Ci sono decine di browser diversi, e nuovi browser escono piuttosto rapidamente,e purtroppo nessuno ci assicura che questi non abbiano un DOM ibrido tra Netscape, Explorer e il DOM del W3C, per cui potrebbero oltrepassare le varie condizioni ed incepparsi poi nel proseguimento delle funzioni.

È vero, la stragrande maggioranza degli utenti (per questione di percentuali) ricadrà in modo corretto nei nostri "canali", ma questo evidenzia la distorsione subita dal metodo a rilevazione d'oggetto, nato, in realtà, con altri presupposti.

La tecnica nasce proprio dal fatto che esistono molti browser e probabilmente molti di più ne esisteranno in futuro. Perciò cercare di realizzare uno sniffing completo potrebbe risultare un'operazione piuttosto laboriosa. Quello che si fa, è un discorso semplice quanto efficace: voglio realizzare uno script che faccia uso di particolari oggetti? Allora, prima, ne verifico l'esistenza e poi li utilizzo! semplice no?!

Facciamo qualche esempio. Supponiamo di voler realizzare un qualsivoglia script che esegua una manipolazione delle immagini attraverso l'uso della collezione images. Il giusto uso della tecnica è il seguente:

if(document.images) {

// esegue uno script che fa uso

// della collezione images

}

Passiamo ad un esempio pratico di una funzione con la quale cambiamo il colore di fondo della pagina, controllando la presenza degli oggetti e delle proprietà che andremo ad usare:

function backColor(){

if(document.bgColor)

document.bgColor="#33FFFF";

if(document.backgroundColor)

document.backgroundColor = "#33FFFF";

if(document.styleSheets){

stile = document.styleSheets[0]

if(stile.rules)

stile.rules[0].style.backgroundColor = "#33FFFF";

if(stile.cssRules)

stile.cssRules[0].style.backgroundColor = "#33FFFF";

}

}

<form>
<input type="button" value="backColor()" onclick="backColor()">
</form>

(Usate l'icona aggiorna del browser oppure il tasto F5 per ristabilire il colore originale)

Come vedete l'uso corretto della tecnica sposta l'attenzione dal browser all'oggetto da usare.
Entrambe le tecniche presentate quindi, se usate con accortezza, offrono i risultati desiderati, ognuna con i propri vantaggi e svantaggi.

Quello che consiglio ai fini dello sniffing, per cercare di unire il rigore della prima tecnica con la maneggevolezza della seconda, è usare una tecnica mista.

Ad esempio, per rilevare se il browser sia Netscape e la versione sia la 4 o la 6, si potrebbe fare:

NN = (navigator.appName.indexOf("Netscape")>-1) ? 1:0;

if(NN) {

NN4 = (document.layers) ? 1:0;

NN6 = (document.getElementById) ? 1:0;

}

<form>
<input type="button" value="Netscape ?" onclick="Net()">
</form>

Passiamo ora a qualcosa di nuovo e più interessante.

Una Tecnica Alternativa: i Commenti Condizionali

Questa è una tecnica che attualmente solo Explorer dalla versione 5 mette a disposizione. Vorrei trattarla soprattutto per il fatto che spesso (proprio per la sua diffusione) si creano pagine quasi pensando esclusivamente al browser della Microsoft, non solo in ambito casalingo.

Considerato ciò, allora perché non usare una tecnica che, come vedrete, è semplicissima e al tempo stesso ci libera da possibili fastidi crossbrowser? Il nome della tecnica, Commenti Condizionali (Conditional Comments), è assolutamente descrittivo.

Si tratta infatti di veri e propri commenti di pagine HTML (e come tali sono percepiti da tutti gli altri browser), che Explorer riesce a leggere ed interpretare.

Esistono due tipi di commenti condizionali:

  • Downlevel-hidden

    sintassi:

    <!--[if espressione]> CONTENUTO <![endif]-->

    Come vedete sono presenti i tag di commento (<!--  -->), questo fa si che il contenuto all'interno del commento condizionale sia nascosto a quelli che Microsoft chiama browsers di basso livello, ovvero tutti quei browser diversi da Explorer 5 e superiori (carini no?!).

    L'espressione che Explorer poi valuta nel blocco condizionale è volta solo ad individuare la versione del browser che deve elaborare il CONTENUTO del commento condizionale.

    La sintassi dell'espressione è:

    [operatore] IE versione

    • operatore (opzionale) = !(NOT)|lt(<)|lte(<=)|gt(>)|gte(>=) tra parentesi ho messo il significato logico degli operatori.
    • versione (intero|reale) la versione del browser. es: 5 o 5.5 ecc

    Facciamo subito un esempio pratico.

    Consideriamo di voler usare gli stili per colorare le scrollbar. Questi attualmente sono supportati solo dalla versione 5.5 e superiori di Explorer.

    Utilizzando i commenti condizionali, ci liberemo dalla paura che altri browser interpretino erroneamente gli stili creando effetti sgradevoli. Gli stili per le scrollbars di una pagina potrebbero essere stati inseriti così:

    <!--[if gte IE 5.5]>

    <style type="text/css">

    body {SCROLLBAR-FACE-COLOR:#DDDDDD;

    SCROLLBAR-HIGHLIGHT-COLOR:#DDDDDD;

    SCROLLBAR-SHADOW-COLOR:#DDDDDD;

    SCROLLBAR-3DLIGHT-COLOR:#666666;

    SCROLLBAR-ARROW-COLOR:#333333;

    SCROLLBAR-TRACK-COLOR:#EEEEEE;

    SCROLLBAR-DARKSHADOW-COLOR:#333333}

    </style>

    <![endif]-->

  • Downlevel-revealed

    sintassi:

    <![if espressione]> CONTENUTO <![endif]>

    In pratica è il duale del precedente, ovvero il CONTENUTO viene eseguito da tutti i browser di basso livello ad eccezione delle versioni di Explorer che oltrepassano la condizione.

    Infatti non sono più presenti i tag di commento e le altre espressioni sono ignorate dagli altri browser.
    <![if !IE 5]> un esempio? <![endif]>
    detto fatto:

    <![if !IE 5]> un esempio? <![endif]>

Sicuramente quest'approccio è molto efficace, si pensi infatti all'esempio degli stili, è stato realizzato senza bisogno di un motore di scripting, necessario invece nel caso si volesse realizzare la stessa cosa in altro modo.

Nota:Nel sito della Microsoft, in realtà, viene fornita una sintassi diversa con l'operatore interposto tra IE e la versione, ma come altri, anch'io ho riscontrato quest'errore. Probabilmente l'errore è solo nell'articolo della Microsoft.

Risorse

Ti consigliamo anche