HTML 5 Web Messaging: window.postMessage

Web Messaging, Cross-origin Messaging o Cross-document messaging son los nombres por los que se reconocen a esta Api de HTML 5, ya en su versión Candidate Recommendation a Mayo del 2012. El objetivo de la misma es proporcionar al desarrollador un mecanismo para la comunicación entre dos contextos del navegador, ya sea entre un documento y un iframe o una nueva ventana. Los navegadores web, por razones de seguridad, previenen la interacción a través de script, con el objetivo de no afectar a un sitio web con el contenido, malicioso o no, de nuestro sitio o viceversa.

¿Cómo funciona?

El mecanismo de mensajes entre dos contextos es bastante sencillo, donde sólo debemos tener en cuenta dos escenarios: el envío y la recepción de mensajes. Para el primero de los casos actuaremos de la siguiente manera:

En primer lugar, debemos comenzar la comunicación en cualquiera de los dos extremos. Para ello, he creado una página HTML que tiene como único contenido la referencia a mi script source.js:

<!DOCTYPE html>
<html>
<head>
    <title>Cross-origin Communication - Sender</title>
</head>
<body>
    <script src="scripts/source.js"></script>
</body>
</html>
var domain = 'http://localhost:23907';
var myPopup = window.open(domain + '/listener.html', 'Listener Window');
var counter = 1;

setInterval(function () {
    var message = 'Hello guest number ' + counter + '!';
    console.log('Sending message: ' + message);

    myPopup.postMessage(message, domain);

    counter++;

}, 5000);

window.addEventListener('message', function (event) {
    if (event.origin !== domain) return;
    console.log('Received response: ', event.data);

}, false);

El código JavaScript que se presenta es bastante sencillo: se ha almacenado la url de la aplicación en una variable, se crea una nueva ventana donde se muestra la página listener.html (nuestro receptor) y por último se inicializa un contador. Se crea una función anónima dentro de un intervalo de 5 segundos que nos permite mandar a la ventana recientemente creada un mensaje con el número actual del contador. En este punto estaríamos mandando mensajes a nuestra segunda ventana a través de [myPopUp].postMessage.

Como podemos ver en el ejemplo, postMessage recibe dos parámetros: el mensaje (un string, objeto, array, etcétera), el dominio del receptor (podría ser un * pero no es recomendable por seguridad) y podría recibir un tercer parámetro opcional reconocido en la especificación como transfer, el cual se utiliza para Channel Messaging, no abarcado en este post.

¿Puedo esperar contestación? Por supuesto :D para ello debemos manejar el evento message de window y recibiremos como parámetro una serie de valores:

event.data
Devuelve la información que se envió a través de postMessage desde el emisor del mensaje.
event.origin
Se trata de la información relativa al remitente: schema, hostname y puerto. Por ejemplo: http://localhost:23907
event.source
Nos da acceso a la propiedad window de la fuente del evento.
event.lastEventId
Cadena con un identificador único del mensaje.
event.ports
Se devuelve un array con los puertos para channel messaging.

El resultado que podemos esperar como parte del registro en consola es el siguiente:

window.postmessage source

Una vez que ya tenemos el emisor estamos listos para implementar al receptor :) Para ello basta con escuchar también el evento message en el otro extremo:

//respond to events
window.addEventListener('message', function (event) {
    if (event.origin != 'http://localhost:23907') return;

    console.log('message received: ' + event.data, event);

    event.source.postMessage('hello back!!', event.origin);
}, false);

Lo que hacemos en primer lugar es comprobar el origen/responsable de la invocación de este evento. Es muy importante validar esta información antes de procesar el contenido por motivos de seguridad. Una vez verificado, mostramos por consola el contenido del mensaje, además de adjuntar el objeto event para poder visualizar un contenido similar al siguiente:

window.postmessage target

Compatibilidad

A día de hoy podemos disfrutar de esta Api en Internet Explorer 8+, Chrome, Opera, Safari y Maxthon.

Espero que sea de utilidad :)

¡Saludos!

La etiqueta base de HTML

Una buena práctica que raramente es aplicada al crear nuestras páginas en HTML es el uso de la etiqueta base, existente desde la versión 4.01 de HTML (y Candidate Recommendation en HTML 5). Este elemento nos va a permitir definir cuál es la URL absoluta para todos aquellos enlaces que sean relativos en la página, pero no solo eso sino que además nos va a permitir establecer cuál es el comportamiento de forma generalizada para todos los enlaces que se declaren. Sólo es posible tener una etiqueta base por página, por lo que el resto de etiquetas de este tipo a partir de la primera serán ignoradas.

Atributos

Los atributos que podemos asignar a la etiqueta base son los mismos que a un anchor: href y target. Si se especifica href es necesario que esté definido antes que el resto de los enlaces. Se pueden especificar tanto direcciones relativas como absolutas. En cuanto al target podemos asociar los mismos valores que en los propios enlaces:

_self
Carga el contenido en el mismo frame/browsing context* donde se encuentra el enlace.
_blank
Se lanza el contenido en una nueva ventana/browsing context*
_parent
Se cargaría en la ventana padre del frame que contiene el enlace si la hubiera. De no ser así funcionaría exactamente igual que _self.
_top
Carga el contenido en la ventana original, anulando todos los demás frames que hubiera contenidos en la misma.
*Respecto a HTML 5. entra en juego el concepto de browsing context, lo cual significa que por cada tab, window o iframe hay un contexto asociado, o lo que es lo mismo un nodo Document por el que navegar.

Para verlo con un ejemplo podemos hacer pruebas con el siguiente código:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <base href="http://www.google.com" target="_blank">
</head>
<body>
    <a href="/calendar">Calendar</a>
    <a href="/imghp">Images</a>
    <a href="/ig">iGoogle</a>
    <a href="/mobile">Mobile</a>
</body>
</html>

En este caso, independientemente de dónde esté alojada nuestra página se tomará como referencia la url indicada en la etiqueta base (http://www.google.com) para montar el resto de los enlaces relativos mostrados más abajo. Además, únicamente indicando el target en la etiqueta base de nuestra página definiremos el comportamiento de navegación de todos los enlaces que creemos (en este caso se abrirán en una nueva ventana).

Espero que sea de utilidad.

¡Saludos!

Hangout disponible: “Apis que molan un montón”

Hace justamente una semana tuve la suerte de poder colaborar con HTML 5 Spain en un Hangout, donde pude hablar de algunas de las Apis que nos esperan en el futuro :) El video ya quedó disponible desde Youtube:

Según el orden en el que aparecen las Apis, me gustaría hacer referencia a los posts donde traté cada uno de los temas:

Las slides quedaron disponibles a través de este enlace.

¡Saludos!

HTML 5 Fullscreen API

Fullscreen API no trata de mostrar una página web a pantalla completa, sino de mostrar un elemento/s de una página en pantalla completa :) . El efecto que se consigue con esta API es dar mayor énfasis a los elementos que solicitaron este modo. A día de hoy la especificación está implementada en Chrome, Opera, Safari y Firefox, quedando fuera Internet Explorer.

Demo

Para comprobar el efecto sobre un elemento, he insertado la siguiente imagen:

Si hacemos doble clic sobre la misma conseguiremos entrar en modo pantalla completa. De hecho, podemos observar incluso un borde coloreado alrededor de la imagen. Veamos cómo conseguirlo :D

Compatibilidad entre navegadores

Lo primero con lo que tenemos que lidiar es con los prefijos utilizados por cada uno de los navegadores en esta fase temprana de la API. A día de hoy la especificación cita los siguientes miembros:

element requestFullscreen
Muestra el elemento que lo invoca (y sus hijos) en pantalla completa.
document.fullscreenEnabled
Devuelve true si la página tiene la habilidad de mostrar elementos en pantalla completa.
document.fullscreenElement
Devuelve el elemento que está mostrado en pantalla completa. Si en ese momento no hubiera ninguno mostrándose en este modo se devolvería null.
document.exitFullscreen
Detiene el modo pantalla completa

Chrome y Firefox han prefijado estos miembros, por lo que es necesario tenerlos en cuenta antes de lanzar cualquier petición a la API:

        var fullScreenChangeEvent = "fullscreenchange";

        if (container.webkitRequestFullscreen) {
            console.log("webkit");
            fullScreenChangeEvent = "webkitfullscreenchange";
            container.requestFullscreen = container.webkitRequestFullscreen;
            document.fullscreenEnabled = document.webkitFullscreenEnabled;
            document.exitFullscreen = document.webkitCancelFullScreen;
        }
        else if (container.mozRequestFullScreen) {
            console.log("moz");
            fullScreenChangeEvent = "mozfullscreenchange";
            container.requestFullscreen = container.mozRequestFullScreen;
            document.fullscreenEnabled = document.mozFullScreenEnabled;
            document.exitFullscreen = document.mozCancelFullScreen;
        }

Por el contrario, Opera sigue el estándar por lo que no será necesario ninguna medida adicional. Internet Explorer por el momento no da soporte a esta característica.

Implementación

Una vez que hemos lidiado con las compatibilidades de los distintos navegadores, la implementación es bastante sencilla:

        if (document.fullscreenEnabled) {

            document.addEventListener(fullScreenChangeEvent, function (e) {
                console.log("fullscreenchange event! ", e);
                if (e.type === "webkitfullscreenchange")
                    console.log("document.webkitFullscreenElement:" + document.webkitFullscreenElement);
                else if (e.type === "mozfullscreenchange")
                    console.log("document.mozFullScreenElement:" + document.mozFullScreenElement);
                else
                    console.log("document.fullscreenElement:" + document.fullscreenElement);
            });

            document.addEventListener("keydown", function (e) {

                if (e.keyCode == 69) { //e
                    document.exitFullscreen();
                }
            });

            container.requestFullscreen();
        }

Para cerciorarnos de que podemos invocar la pantalla completa de un elemento hacemos uso de document.fullscreenEnabled. Si queremos realizar ciertas acciones cuando el evento fullscreenchange es lanzado basta con añadir un handler a nivel de document (se lanza tanto cuando pasamos a pantalla completa como cuando salimos de ella). En este caso se muestra el valor de document.fullscreenElement. Como veis, en este caso he tenido que discernir qué tipo de evento se lanzó para saber de dónde leer esa información. El motivo de hacerlo aquí y no en el apartado anterior es que los elementos document.webkitFullscreenElement y document.mozFullScreenElement no son funciones sino simples propiedades que se actualizan cuando el evento fullscreenchange se lanza, por lo que no puedo recoger esta información antes de que el evento ocurra. Por último, he agregado un handler al evento keydown, para capturar en el momento que el usuario/lector :) pulsa la tecla e, para salir del modo pantalla completa utilizando document.exitFullscreen

Estilos CSS

Por último, comentar que existen pseudo-clases para controlar los estilos del elemento en pantalla completa. Sería necesario contemplar los siguientes:

        #stickercontainer {
            padding: 15px;
        }

            #stickercontainer:full-screen { /*html*/
                background-color: pink;
            }

            #stickercontainer:-webkit-full-screen { /*webkit*/
                background-color: blue;
            }

            #stickercontainer:-moz-full-screen { /*mozilla*/
                background-color: green;
            }

        @media screen and (view-mode: fullscreen) { /*opera*/
            #stickercontainer {
                background-color: yellow;
            }
        }

En el caso de Opera es necesario hacer uso de media query, indicando el view-mode fullscreen.

Espero que sea de utilidad :D

¡Saludos!

dotPeek: El descompilador de JetBrains ¡y gratis!

dotPeek
Cuando .NET Reflector dejó de ser gratuito y pasó a manos de Red Gate perdimos una herramienta muy valiosa en el mundo .NET. Sigue siendo una maravillosa utilidad, pero algunos desarrolladores a titulo personal no pueden permitirse un mínimo de 69€ en la más barata de sus licencias. Es por ello que me gustaría presentaros dotPeek, el descompilador gratuito de JetBrains.

¿Qué ofrece dotPeek?

dotPeek es capaz de descompilar cualquier assembly en .NET y mostrarlo como código C#. Los tipos soportados son:

  • Librerías (.dll)
  • Ejecutables (.exe)
  • Archivos de metadatos de Windows 8 (.winmd)
  • Archivos comprimidos (.zip)
  • Paquetes de Nuget
  • Extensiones de Visual Studio (.vsix)

Cómo usarlo

doPeek, al igual que otras herramientas de JetBrains, se acoge al EAP (Early Access Program), con el objetivo de conseguir una pronta adopción de la herramienta por los desarrolladores y poder probar las últimas novedades del producto antes de la release final. Para descargar la última versión de dotPeek podemos hacerlo a través del siguiente enlace (Si quieres descargar la release oficial haz clic en este otro sitio). Una vez instalado podemos acceder a la herramienta y visualizar una interfaz como esta:

dotPeek 1.1 EAP

La forma de trabajar con esta utilidad es muy similar que con Resharper, ya tenemos una serie de accesos rápidos a las distintas funciones que trae la herramienta (Podemos consultarlos a través de este enlace).

Más información.

¡Saludos!

Trazar la carga de módulos en RequireJS

Hace tiempo estuve hablando de la potencia que nos da RequireJS, cargando nuestros archivos/módulos JavaScript bajo demanda. Lo cierto es que, cuando tenemos aplicaciones grandes con muchos módulos, puede ser complicado saber cuántos se han cargado, si tienen correctamente sus dependencias, etcétera. Investigando un poco sobre cómo conseguir esta información descubrí el evento onResourceLoad.

onResourceLoad

Se trata de una API privada de requirejs que, tal y como comentan en su espacio en Github, puede estar sujeta a cambio en cualquier momento. De hecho, puedes agregar tu contacto en la propia wiki con el fin de ser informado si se produce algún cambio. Siguiendo el ejemplo del post anterior, he ampliado el mismo para agregar una función que maneje dicho evento antes de comenzar a utilizar require.js en el script app.js.

(function () {

    requirejs.config({
        baseUrl: 'scripts'
    });

    requirejs.onResourceLoad = function (context, map, depArray) {
        console.log(["{0} module loaded; Url {1}; Dependencies".format(map.name, map.url), depArray]);
    };

    require(['messenger'], function (messenger) {
        messenger.showMessage();
    });

})();
La forma de concatenar el texto de console.log es una extensión de la clase String contada en este otro post.

Esta función, a día de hoy, recibe tres argumentos:

context
Se trata del objeto que utiliza RequireJS para almacenar información relacionada con la carga y exportación de módulos.
map
Es el argumento más importante para nosotros ya que tiene toda la información relacionada con el módulo que se acaba de cargar. En este ejemplo se han utilizado el nombre del módulo (map.name) y la url de dónde está alojado(map.url). Más información.
depArray
Se trata de un array con las dependencias del módulo.

Si arrancamos el ejemplo podremos ver en la consola la información de cada uno de los módulos cargados:

onResourceLoad

Espero que sea de utilidad :D

¡Saludos!

Evento online en HTML 5 Spain: Javascript API’s que molan un montón

HTML5 Spain logo
El 22 de Abril (el lunes de la semana que viene) tengo el placer de poder juntarme con la gente de HTML 5 Spain, para realizar un hangout sobre algunas de las Apis de HTML 5 con las que he estado jugando :) El evento será online a las 20:30 (UTC +01:00).
Os invito a acompañarnos en este recorrido, para echar un vistazo a un futuro no muy lejano y pensar en todas las posibilidades que nos esperan desde el lado Web. La idea inicial es poder hablaros de las siguientes APIs:

  • Web Notificacions
  • getUserMedia
  • Battery Status
  • Page Visibility
  • Fullscreen
  • /*Application cache*/

Si estás interesado/a en asistir, puedes suscribirte a través del enlace del evento en meetup: Online! Javascript API’s que molan un montón

¡Os esperamos!

String.Format en JavaScript

Cuando trabajas de una manera más intensiva en el lado del cliente empiezas a echar en falta aquellas utilidades disponibles en servidor y que no tenemos por defecto en JavaScript. Una de ellas es el uso de String.Format para poder trabajar de una forma más eficiente con cadenas.
Normalmente, cuando queremos concatenar variables con literales en nuestros scripts solemos hacerlo de la siguiente manera:

"Hi! I'm " + me + " and you can <a href='" + twitter + "'>follow me on Twitter</a> or <a href='" + facebook + "'>Facebook</a>";

Una alternativa para mejorar este tipo de concatenaciones sería la simulación de String.Format, modificando el prototipo de la clase String de JavaScript, con el fin de poder trabajar de una forma más legible y cómoda:

"Hi! I'm {0} and you can <a href='{1}'>follow me on Twitter</a> or <a href='{2}'>Facebook</a> {test}".format(me, twitter, facebook);

La implementación de esta extensión podría ser la siguiente:

String.prototype.format = function () {
    var literal = this;

    for (var i = 0; i < arguments.length; i++) {
        var regex = new RegExp('\\{' + i + '\\}', 'g');
        literal = literal.replace(regex, arguments[i]);
    }

    return literal;
};

Lo único que hacemos en la función format es tomar como literal la cadena que llama a dicho método y recorrer cada uno de los argumentos para poder reemplazar todas las coincidencias con la ayuda de la expresión regular ‘\\{posicion del argumento\\}. Con el flag g conseguimos reemplazar todas las coincidencias que encuentre para un mismo argumento, si es que hacemos uso repetido del mismo valor en distintos sitios del literal.

Otra opción es hacer uso de la librería JavaScript sprintf() la cual es mucho más elaborada :)

Espero que sea de utilidad.

¡Saludos!

App cache y los navegadores

En el último post estuve comentando las bondades de trabajar con una de las nuevas APIs que HTML 5 trae bajo su ala: Application Cache.
En este post quiero mostrar cuáles son las opciones que nos ofrece cada uno de los navegadores:

Internet Explorer 10

La última versión del navegador de Microsoft posee un apartado llamado Cached and Databases, el cual podemos localizar a través de Internet Options –> General –> Apartado Browsing history –> Settings –> Caches and Databases.

Caches and databases settings

Por defecto, tenemos habilitados los siguientes valores:

  1. Se permite el almacenamiento en caché y la creación de bases de datos.
  2. Se lanza una notificación cuando un sitio va a exceder los 10 MB de almacenamiento, aunque es ampliable.
  3. Listado de todos los dominios que han almacenado información. Si seleccionamos cualquiera de ellos, podemos hacer clic en la opción Exceed limit para permitir sobrepasar el valor por defecto o incluso podemos borrar los datos para un sitio en concreto.

Chrome

.
En el caso del navegador de Google, dispone de un apartado dentro de su configuración llamado AppCache Internals, al cual podemos acceder escribiendo en la URL del navegador chrome://appcache-internals

appcache-internals chrome

Lo cierto es que nos aporta mucha más información como el tamaño, la fecha de creación, la última vez que se actualizó, el último acceso e incluso podemos ver, haciendo clic en View Entries, todos los recursos que se han descargado, los flags asociados y el peso de cada uno de ellos:

appcache-internals chrome view entries

Del mismo modo podemos eliminar la caché para un dominio en concreto.

Firefox

En el caso de Firefox, cuando accedemos a un sitio web que necesita hacer uso de Application Cache, nos solicita permiso de manera previa. Una vez concedido, podemos ver la información relacionada en las opciones del navegador –> Advanced –> Network.

Firefox appCache Options

En esta ventana podemos ver únicamente los dominios autorizados y el espacio utilizado por cada uno de ellos, además de borrar todo el contenido o sólo de algunos dominios. Por otro lado, tenemos un apartado de excepciones donde nos permite visualizar todos aquellos sitios a los que se les denegó el uso de Application Cache, con el objetivo además de sacarlo de la lista negra.

Opera

En el caso de Opera dispone de un apartado en la parte avanzada de sus opciones llamado Storage, donde podemos encontrar las acciones más básicas al respecto:

Advanced Storage AppCache Opera

Por defecto está habilitado el uso de la API pero existe la posibilidad de que se nos pregunte antes de aceptar un sitio, al igual que Firefox.

Safari

Entre las opciones para Safari, tanto en su versión Windows como Mac, podemos encontrar en el apartado Privacy una sección llamada Cookies and other website data aunque debemos utilizar un botón llamado Details… para ver la información:

AppCache Safari for Windows

Hasta ahora es el navegador que menos información nos da al respecto, ya que ni siquiera puedo saber cuánto es el espacio que ocupa cada uno de los dominios. Únicamente disponemos de un botón para eliminar uno o todos los sitios registrados.

Espero que haya sido de utilidad.

¡Saludos!

HTML 5 Application Cache: Aplicaciones Offline

Si bien uno de los grandes motivos de anteponer las aplicaciones nativas sobre las aplicaciones web era la posibilidad de que estas primeras pudieran seguir trabajando a pesar de no disponer de Internet o acceso a un servidor determinado, con la especificación de HTML 5 que vamos a ver hoy cambia totalmente el panorama: Application Cache.

¿Qué es Application Cache?

Esta especificación está pensada para escenarios donde podemos experimentar situaciones en las que no disponemos de conectividad, como en un avión, zonas con poca cobertura, desconexiones intermitentes, etcétera. Hasta ahora con el modo de caché habitual no eramos capaces de obtener recursos a los que no hubiéramos accedido previamente, ni podíamos indicar a qué páginas era posible navegar aún cuando no disponíamos de acceso al servidor, por falta de Internet o porque el mismo no estuviera disponible en ese momento. Pero no sólo los escenarios relacionados con la conectividad son los candidatos de disfrutar de esta API, sino que también nos permite mejorar rendimiento del servidor reduciendo las peticiones de recursos en usuarios recurrentes, manteniendo los más estáticos cacheados en el navegador de forma explícita.

Esta característica está siendo muy utilizada en diferentes escenarios como juegos (como la aplicación de Angry Birds de Chrome Web Store), aplicaciones offline de acceso al correo electrónico, clientes web de música por streaming como Deezer y todas aquellas que puedan trabajar en local y sean capaces de sincronizar su contenido más tarde, o necesiten reducir el flujo de peticiones.

¿Cómo funciona?

Para poder trabajar con App Cache lo primero que debemos conocer es el archivo llamado cache manifest. Se trata de un archivo de texto plano con la siguiente estructura:

CACHE MANIFEST
#version 5.9

CACHE:
#This section list all resources that should be downloaded and store locally

#HTML files
index.html

#JavaScript files
/Scripts/appCache.js
/Content/images/glyphicons-halflings.png
/Scripts/bootstrap.min.js
/Scripts/jquery-1.9.1.min.js

#CSS files
/Content/bootstrap.min.css

NETWORK:
#This section list all URLs that may be loaded over Internet.
online.html

FALLBACK:
#Lists replacements for network URLs to be used when the browser is offline or the remote server is unavailable
/home.html /offline.html
/Content/images/glyphicons-halflings-white.png /Content/offline.png

En primer lugar, todo manifiesto debe comenzar con CACHE MANIFEST en la primera línea del archivo, de lo contrario podemos encontrar problemas cuando se intenta procesar por el navegador.

También es importante tener cuidado con los códigos de control que inserta Visual Studio al crear este fichero dentro de una solución, por lo que recomiendo guardar el mismo como Unicode (UTF-8 without signature) – Codepage 65001 a través de File –> Advanced Save Options.

Todas aquellas líneas que comiencen con una almohadilla (#) se tratan de comentarios. A continuación revisamos las siguientes secciones del manifiesto:

CACHE
Listado de todos aquellos recursos que deben ser descargados y almacenados localmente.
NETWORK
Lista de todas las URL que necesitan acceso al servidor remoto/Internet. Es posible utilizar el asterisco para indicar que cualquier recurso que no esté listado en el apartado CACHE necesita acceso a la red para acceder a él. Es posible hacer uso de URLs relativasy absolutas.
FALLBACK
En el caso de no tener acceso al servidor remoto se pueden especificar qué recursos deben responder por aquellos que no pueden ser recuperados. Los mismos pueden ser de cualquier tipo (una página HTML, una imágen, código JavaScript, etcétera). El primer parámetro es el recurso al que nos gustaría acceder y el segundo el que tomará su lugar cuando existan problemas de conectividad.
¡Nunca almacenes en cache el propio manifiesto!

Para indicar que un sitio tiene un manifiesto relacionado, basta con asociarlo en la etiqueta html de la página:

<!DOCTYPE html>
<html manifest="manifest.appcache">
<head>

El mismo puede tener el nombre y extensión que se prefiera, pero con convención se sugiere *.appcache. Como MIME type del mismo es recomendable servir text/cache-manifest. Si trabajamos con aplicaciones .NET podemos establecerlo a nivel del archivo web.config de la siguiente forma:

<?xml version="1.0"?>

<!--
  For more information on how to configure your ASP.NET application, please visit

http://go.microsoft.com/fwlink/?LinkId=169433

  -->

<configuration>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
  </system.web>
  <system.webServer>
    <staticContent>
      <mimeMap fileExtension=".appcache" mimeType="text/cache-manifest"/>
    </staticContent>
  </system.webServer>
</configuration>

Ciclo de vida de Application Cache

Una vez que la página asociada al manifiesto haya cargado, lo primero que el navegador comprobará es si tiene la versión más reciente del manifiesto para ese sitio. De no ser así descargará del servidor la última versión. Una vez obtenida comenzará a descargar todos los recursos nombrados en el apartado CACHE y aquellos que podrían ser utilizados como FALLBACK, en el caso de que no pudiéramos alcanzar el servidor de destino. A partir de este momento se trabajará con los recursos cacheados y no volverán a descargarse hasta que el manifiesto vuelva a sufrir una nueva actualización o limpiemos la datos temporales de nuestro navegador.
Si quisiéramos comprobar su efectividad, basta con desconectar el dispositivo de Internet e intentar navegar a aquellas páginas y recursos que fueron nombrados en el manifiesto. Del mismo modo, si intentáramos acceder a aquellos recursos que poseen un fallback, el navegador nos desviaría directamente a aquellas URL indicadas como segundo parámetro.

Es muy importante recordar que aunque alguno de los recursos listados en el manifiesto sufran modificaciones los navegadores de los clientes no dispondrán de esos cambios hasta que la fecha de modificación del manifiesto se vea alterada (Es buena práctica indicar la versión del manifiesto en un comentario para forzar la modificación del archivo si los recursos no varían en número y sólo en forma). App Cache tampoco auto refresca la página y esto hará que los últimos cambios descargados por el navegador no sean aplicados hasta la próxima carga del sitio. Por lo tanto, será necesario forzar la recarga de la página de forma manual o automática basándose en los eventos que producen esta característica.

Eventos

En las versiones más recientes de Chrome (En el momento del post Chrome 26) es posible ver los eventos ocurridos a través de la consola de la herramienta de desarrollo:

App Cache Events

No obstante, es posible añadir manejadores a cada uno de ellos con el siguiente código JavaScript:

window.onload = function () {

    //App Cache
    if (window.applicationCache) {
        console.log("Application cache is available");

        var appCache = window.applicationCache;

        appCache.addEventListener("checking", appCacheHandler, false);
        appCache.addEventListener("downloading", appCacheHandler, false);
        appCache.addEventListener("noupdate", appCacheHandler, false);
        appCache.addEventListener("obsolete", appCacheHandler, false);
        appCache.addEventListener("progress", appCacheHandler, false);
        appCache.addEventListener("error", appCacheHandler, false);
        appCache.addEventListener("updateready", appCacheHandler, false);
        appCache.addEventListener("cached", appCacheHandler, false);

        function appCacheHandler(event) {
            var status = appCache.status;
            var msg = null;

            switch (status) {
                case appCache.CHECKING:
                    msg = "<span class='label label-warning'>CHECKING</span>";
                    break;
                case appCache.DOWNLOADING:
                    msg = "<span class='label label-info'>DOWNLOADING</span>";
                    break;
                case appCache.UNCACHED:
                    msg = "<span class='label label-inverse'>UNCACHED</span>";
                    break;
                case appCache.OBSOLETE:
                    msg = "<span class='label label-important'>OBSOLETE</span>";
                    break;
                case appCache.IDLE:
                    msg = "<span class='label label-info'>IDLE</span>";
                    break;
                case appCache.UPDATEREADY:
                    msg = "<span class='label label-success'>UPDATEREADY</label>";
                    break;
                default:
                    msg = "UNKNOWN STATUS";
                    break;
            }

            var log = document.getElementById("log");
            log.innerHTML += '<li> Event: <span class="label label-inverse">' + event.type + '</span> Status: ' + msg + "</li>";

            if (status === appCache.UPDATEREADY) {
                appCache.swapCache();
                if (window.confirm('App cache was updated, you need refresh this page... May I?'))                  
                    window.location.reload();
                else {
                    log.innerHTML += '<li><span class="label label-important">You need to refresh this page to see the changes!</span></li>';
                }
            }

        }
    }
};
Las clases CSS que he utilizado para mostrar la información pertenecen al framework Bootstrap de Twitter.

Lo primero que comprobamos en nuestro código es si Application Cache está soportado por nuestro navegador (las últimas versiones de los navegadores más populares ya lo implementan). Para tener una visión más completa de la disponibilidad de esta característica podemos consultar esta tabla.

En cuanto a los eventos tenemos los siguientes:

checking
Se lanza cuando el navegador está comprobando la última versión del manifiesto alojada en el servidor remoto.
downloading
Indica que el manifiesto ha sido descargado y comienza la obtención de los recursos.
noupdate
Indica que tenemos la última versión del manifiesto y no es necesario descargar ningún recurso.
obsolete
La versión del servidor está obsoleta.
progress
Se lanza por cada uno de los recursos incluídos en la sección CACHE
error
Como su propio nombre indica, se trata del evento reservado para los errores durante la obtención de los recursos indicados en el manifiesto.
updateready
Nos permite conocer cuándo todos los recursos han sido actualizados con la nueva versión. En este ejemplo, además de mostrar el texto correspondiente, hacemos una comprobación al terminar el switch donde verificamos si hemos llegado a este punto y forzamos el cambio de cache a los nuevos elementos a través de la función swapCache(). Si bien esta función nos ahorra una recarga en el sitio, swapCache no nos permite servir los recursos actualizados de manera automática, por lo que debemos preguntar al usuario si podemos refrescar la página para aplicar los nuevos cambios.
cached
Los recursos han sido descargados y la aplicación ha sido cacheada. Este evento sólo ocurre la primera vez que se cachea el contenido. En las siguientes descargas será updateready el que se lance.
También es posible solicitar la actualización de la cache a través de script (sin tener que refrescar la página) utilizando window.applicationCache.update(), aunque será necesario igualmente el swap de la caché y el refresco de la página para poder aplicar los últimos cambios. Suele utilizarse cuando el usuario ha estado trabajando durante mucho tiempo en el sitio sin tener que refrescar la página y recuperar así el nuevo manifiesto durante una recarga.

En este ejemplo la misma función controla todos los eventos posibles y, a través de un switch, muestra además el estado que refleja cada uno de ellos donde curiosamente no tiene por qué coincidir con el nombre del evento. Podemos verlo en la siguiente imagen:

App Cache demo

Eventos Online y Offline

Por último, aunque no pertenezca a la API de App Cache, me gustaría mostrar la forma de conocer cuándo una aplicación no dispone de acceso a Internet… ¡Efectivamente! Existe otra API para ello :D En este caso debemos hablar de los eventos online y offline pertenecientes al objeto window. A través de la propiedad navigator.onLine podemos conocer cuál es nuestro estado actual e incluso podemos manejar los eventos online y offline de la siguiente manera:

    //Internet status
    function isOnline() {
        return navigator.onLine;
    }

    function internetStatus() {
        var className = isOnline() ? "badge-success" : "badge-important";
        var statusName = isOnline() ? "online" : "offline";

        var internet = document.getElementById("internet");
        internet.className = "badge " + className;
        internet.innerText = statusName;
    }

    window.addEventListener("online", internetStatus, false);
    window.addEventListener("offline", internetStatus, false);

    internetStatus();

Como podemos ver en el ejemplo, hemos encapsulado el valor de [window.]navigator.onLine en la función isOnline con el objetivo de comprobar dentro de internetStatus cuál es su valor y asociar una clase y un texto a una etiqueta con id “internet” en nuestra página. Además asociamos esta función a los eventos online y offline de window para que, cada vez que ocurran, se vuelta a comprobar el estado de nuestro acceso a Internet. Por último se llama a internetStatus una primera vez al cargar la página para conocer en qué estado nos encontramos inicialmente.

Podéis encontrar más información y demos en la página Appcache Facts.
Existe una página llamada Cache Manifest Validator que nos permite comprobar si hemos creado nuestro manifiesto correctamente. De hecho, también disponemos de una extensión para Chrome que realiza la misma función.

Espero que haya sido de utilidad :D

¡Saludos!