Se estandariza el enrutamiento del cliente a través de una API nueva que revisa por completo la compilación de aplicaciones de una sola página.
Las aplicaciones de una sola página, o SPA, se definen por una característica principal: reescriben su contenido de forma dinámica a medida que el usuario interactúa con el sitio, en lugar del método predeterminado de cargar páginas completamente nuevas desde el servidor.
Si bien las SPAs pudieron ofrecerte esta función a través de la API de History (o, en casos limitados, ajustando la parte #hash del sitio), es una API torpe desarrollada mucho antes de que las SPAs fueran la norma, y la Web necesita un enfoque completamente nuevo. La API de Navigation es una API propuesta que revisa por completo este espacio, en lugar de intentar simplemente corregir los problemas de la API de History. (Por ejemplo, Scroll Restoration aplicó un parche a la History API en lugar de intentar reinventarla).
En esta publicación, se describe la API de Navigation en términos generales. Para leer la propuesta técnica, consulta el borrador de informe en el repositorio de WICG.
Ejemplo de uso
Para usar la API de Navigation, primero agrega un objeto de escucha "navigate"
en el objeto navigation
global.
Este evento está centralizado en esencia: se activará para todos los tipos de navegaciones, ya sea que el usuario haya realizado una acción (como hacer clic en un vínculo, enviar un formulario o avanzar y retroceder) o cuando la navegación se active de manera programática (es decir, mediante el código de tu sitio).
En la mayoría de los casos, permite que tu código anule el comportamiento predeterminado del navegador para esa acción.
En el caso de las SPA, es probable que eso signifique mantener al usuario en la misma página y cargar o cambiar el contenido del sitio.
Se pasa un NavigateEvent
al objeto de escucha "navigate"
que contiene información sobre la navegación, como la URL de destino, y te permite responder a la navegación en un lugar centralizado.
Un objeto de escucha "navigate"
básico podría verse de la siguiente manera:
navigation.addEventListener('navigate', navigateEvent => {
// Exit early if this navigation shouldn't be intercepted.
// The properties to look at are discussed later in the article.
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname === '/') {
navigateEvent.intercept({handler: loadIndexPage});
} else if (url.pathname === '/cats/') {
navigateEvent.intercept({handler: loadCatsPage});
}
});
Puedes controlar la navegación de una de estas dos maneras:
- Llamar a
intercept({ handler })
(como se describió anteriormente) para controlar la navegación - Llamar a
preventDefault()
, que puede cancelar la navegación por completo
En este ejemplo, se llama a intercept()
en el evento.
El navegador llama a tu devolución de llamada handler
, que debería configurar el siguiente estado de tu sitio.
Esto creará un objeto de transición, navigation.transition
, que otro código puede usar para realizar un seguimiento del progreso de la navegación.
Por lo general, se permiten intercept()
y preventDefault()
, pero hay casos en los que no se puede llamar a ellos.
No puedes controlar las navegaciones a través de intercept()
si es de origen cruzado.
No puedes cancelar una navegación a través de preventDefault()
si el usuario presiona los botones Atrás o Adelante en su navegador. Deberías no poder atrapar a los usuarios en tu sitio.
(Esto se analiza en GitHub).
Incluso si no puedes detener o interceptar la navegación, se activará el evento "navigate"
.
Es informativo, por lo que tu código podría, por ejemplo, registrar un evento de Analytics para indicar que un usuario está saliendo de tu sitio.
¿Por qué agregar otro evento a la plataforma?
Un objeto de escucha de eventos "navigate"
centraliza el control de los cambios de URL dentro de un SPA.
Esta es una propuesta difícil con APIs anteriores.
Si alguna vez escribiste el enrutamiento de tu propio SPA con la API de History, es posible que hayas agregado código como el siguiente:
function updatePage(event) {
event.preventDefault(); // we're handling this link
window.history.pushState(null, '', event.target.href);
// TODO: set up page based on new URL
}
const links = [...document.querySelectorAll('a[href]')];
links.forEach(link => link.addEventListener('click', updatePage));
Esto está bien, pero no es exhaustivo. Los vínculos pueden aparecer y desaparecer en tu página, y no son la única forma en que los usuarios pueden navegar por las páginas. Por ejemplo, pueden enviar un formulario o incluso usar un mapa de imágenes. Es posible que tu página las maneje, pero hay una larga cola de posibilidades que podrían simplificarse, algo que logra la nueva API de Navigation.
Además, lo anterior no controla la navegación hacia atrás o hacia adelante. Hay otro evento para eso, "popstate"
.
En lo personal, la API de History a menudo parece que podría ayudar con estas posibilidades.
Sin embargo, en realidad solo tiene dos áreas de superficie: responder si el usuario presiona Atrás o Adelante en su navegador, además de enviar y reemplazar URLs.
No tiene una analogía con "navigate"
, excepto si configuras manualmente objetos de escucha para eventos de clic, por ejemplo, como se demostró anteriormente.
Cómo decidir cómo controlar una navegación
navigateEvent
contiene mucha información sobre la navegación que puedes usar para decidir cómo administrar una navegación en particular.
Las propiedades clave son las siguientes:
canIntercept
- Si es falso, no puedes interceptar la navegación. No se pueden interceptar las navegaciones entre orígenes ni los recorridos entre documentos.
destination.url
- Probablemente, la información más importante que se debe tener en cuenta cuando se controla la navegación.
hashChange
- Es verdadero si la navegación es del mismo documento y el hash es la única parte de la URL que es diferente de la URL actual.
En las SPA modernas, el hash debe usarse para vincular a diferentes partes del documento actual. Por lo tanto, si
hashChange
es verdadero, es probable que no necesites interceptar esta navegación. downloadRequest
- Si esto es verdadero, la navegación se inició con un vínculo que tiene un atributo
download
. En la mayoría de los casos, no es necesario que lo interceptes. formData
- Si no es nulo, esta navegación forma parte del envío de un formulario POST.
Asegúrate de tener esto en cuenta cuando controles la navegación.
Si solo deseas controlar las navegaciones GET, evita interceptar las navegaciones en las que
formData
no sea nulo. Consulta el ejemplo sobre cómo controlar los envíos de formularios más adelante en el artículo. navigationType
- Es uno de
"reload"
,"push"
,"replace"
o"traverse"
. Si es"traverse"
, esta navegación no se puede cancelar a través depreventDefault()
.
Por ejemplo, la función shouldNotIntercept
que se usó en el primer ejemplo podría ser algo como lo siguiente:
function shouldNotIntercept(navigationEvent) {
return (
!navigationEvent.canIntercept ||
// If this is just a hashChange,
// just let the browser handle scrolling to the content.
navigationEvent.hashChange ||
// If this is a download,
// let the browser perform the download.
navigationEvent.downloadRequest ||
// If this is a form submission,
// let that go to the server.
navigationEvent.formData
);
}
Interceptación
Cuando tu código llama a intercept({ handler })
desde su objeto de escucha "navigate"
, le informa al navegador que ahora está preparando la página para el estado nuevo y actualizado, y que la navegación puede tardar un poco.
El navegador comienza por capturar la posición de desplazamiento del estado actual, de modo que se pueda restablecer de forma opcional más adelante, y luego llama a tu devolución de llamada handler
.
Si tu handler
muestra una promesa (lo que sucede automáticamente con las funciones asíncronas), esa promesa le indica al navegador cuánto tiempo tarda la navegación y si se realiza correctamente.
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
},
});
}
});
Por lo tanto, esta API presenta un concepto semántico que el navegador comprende: se está produciendo una navegación de SPA en el tiempo, lo que cambia el documento de una URL y un estado anteriores a uno nuevo. Esto tiene varios beneficios potenciales, incluida la accesibilidad: los navegadores pueden mostrar el principio, el final o la posible falla de una navegación. Chrome, por ejemplo, activa su indicador de carga nativo y permite que el usuario interactúe con el botón de detención. (Actualmente, esto no ocurre cuando el usuario navega con los botones Atrás/Adelante, pero se solucionará pronto).
Confirmación de navegación
Cuando se intercepten las navegaciones, la URL nueva se aplicará justo antes de que se llame a la devolución de llamada de handler
.
Si no actualizas el DOM de inmediato, se crea un período en el que se muestra el contenido anterior junto con la URL nueva.
Esto afecta elementos como la resolución de URLs relativas cuando se recuperan datos o se cargan subrecursos nuevos.
Se analiza en GitHub una forma de retrasar el cambio de URL, pero, por lo general, se recomienda actualizar la página de inmediato con algún tipo de marcador de posición para el contenido entrante:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
// The URL has already changed, so quickly show a placeholder.
renderArticlePagePlaceholder();
// Then fetch the real data.
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
},
});
}
});
Esto no solo evita problemas de resolución de URL, sino que también se siente rápido porque respondes al usuario de forma instantánea.
Indicadores de aborto
Dado que puedes realizar trabajo asíncrono en un controlador intercept()
, es posible que la navegación sea redundante.
Esto sucede en los siguientes casos:
- El usuario hace clic en otro vínculo o algún código realiza otra navegación. En este caso, se abandona la navegación anterior en favor de la nueva.
- El usuario hace clic en el botón "detener" del navegador.
Para abordar cualquiera de estas posibilidades, el evento que se pasa al objeto de escucha "navigate"
contiene una propiedad signal
, que es un AbortSignal
.
Para obtener más información, consulta Recuperación abortable.
La versión corta es que básicamente proporciona un objeto que activa un evento cuando debes dejar de trabajar.
En particular, puedes pasar un AbortSignal
a cualquier llamada que realices a fetch()
, lo que cancelará las solicitudes de red en curso si se anula la navegación.
Esto ahorrará el ancho de banda del usuario y rechazará el Promise
que devuelve fetch()
, lo que evitará que cualquier código posterior realice acciones como actualizar el DOM para mostrar una navegación de página ahora no válida.
Este es el ejemplo anterior, pero con getArticleContent
intercalado, que muestra cómo se puede usar AbortSignal
con fetch()
:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
// The URL has already changed, so quickly show a placeholder.
renderArticlePagePlaceholder();
// Then fetch the real data.
const articleContentURL = new URL(
'/get-article-content',
location.href
);
articleContentURL.searchParams.set('path', url.pathname);
const response = await fetch(articleContentURL, {
signal: navigateEvent.signal,
});
const articleContent = await response.json();
renderArticlePage(articleContent);
},
});
}
});
Manejo del desplazamiento
Cuando intercept()
una navegación, el navegador intentará controlar el desplazamiento automáticamente.
Para las navegaciones a una nueva entrada de historial (cuando navigationEvent.navigationType
es "push"
o "replace"
), esto significa intentar desplazarse hasta la parte indicada por el fragmento de URL (el fragmento después de #
) o restablecer el desplazamiento hasta la parte superior de la página.
Para las recargas y los recorridos, esto significa restablecer la posición de desplazamiento al lugar donde estaba la última vez que se mostró esta entrada de historial.
De forma predeterminada, esto sucede una vez que se resuelve la promesa que muestra tu handler
, pero si tiene sentido desplazarse antes, puedes llamar a navigateEvent.scroll()
:
navigation.addEventListener('navigate', navigateEvent => {
if (shouldNotIntercept(navigateEvent)) return;
const url = new URL(navigateEvent.destination.url);
if (url.pathname.startsWith('/articles/')) {
navigateEvent.intercept({
async handler() {
const articleContent = await getArticleContent(url.pathname);
renderArticlePage(articleContent);
navigateEvent.scroll();
const secondaryContent = await getSecondaryContent(url.pathname);
addSecondaryContent(secondaryContent);
},
});
}
});
Como alternativa, puedes inhabilitar por completo el control de desplazamiento automático configurando la opción scroll
de intercept()
en "manual"
:
navigateEvent.intercept({
scroll: 'manual',
async handler() {
// …
},
});
Control del enfoque
Una vez que se resuelva la promesa que muestra tu handler
, el navegador enfocará el primer elemento con el atributo autofocus
establecido o el elemento <body>
si ningún elemento tiene ese atributo.
Para inhabilitar este comportamiento, configura la opción focusReset
de intercept()
en "manual"
:
navigateEvent.intercept({
focusReset: 'manual',
async handler() {
// …
},
});
Eventos de éxito y error
Cuando se llame a tu controlador intercept()
, ocurrirá una de las siguientes dos situaciones:
- Si se cumple el
Promise
que se muestra (o no llamaste aintercept()
), la API de Navigation activará"navigatesuccess"
con unEvent
. - Si se rechaza la
Promise
que se muestra, la API activará"navigateerror"
con unErrorEvent
.
Estos eventos permiten que tu código maneje el éxito o el fracaso de forma centralizada. Por ejemplo, puedes ocultar un indicador de progreso que se mostró anteriormente para indicar que se realizó correctamente, de la siguiente manera:
navigation.addEventListener('navigatesuccess', event => {
loadingIndicator.hidden = true;
});
También puedes mostrar un mensaje de error en caso de falla:
navigation.addEventListener('navigateerror', event => {
loadingIndicator.hidden = true; // also hide indicator
showMessage(`Failed to load page: ${event.message}`);
});
El objeto de escucha de eventos "navigateerror"
, que recibe un ErrorEvent
, es particularmente útil, ya que garantiza que reciba cualquier error de tu código que configure una página nueva.
Puedes simplemente await fetch()
sabiendo que, si la red no está disponible, el error se redireccionará a "navigateerror"
.
Entradas de navegación
navigation.currentEntry
proporciona acceso a la entrada actual.
Es un objeto que describe dónde se encuentra el usuario en este momento.
Esta entrada incluye la URL actual, los metadatos que se pueden usar para identificar esta entrada a lo largo del tiempo y el estado proporcionado por el desarrollador.
Los metadatos incluyen key
, una propiedad de cadena única de cada entrada que representa la entrada actual y su posición.
Esta clave permanece igual incluso si cambia la URL o el estado de la entrada actual.
Sigue en el mismo lugar.
Por el contrario, si un usuario presiona Atrás y, luego, vuelve a abrir la misma página, key
cambiará, ya que esta entrada nueva crea un espacio nuevo.
Para un desarrollador, key
es útil porque la API de Navigation te permite dirigir al usuario directamente a una entrada con una clave coincidente.
Puedes mantenerlo, incluso en los estados de otras entradas, para saltar fácilmente entre páginas.
// On JS startup, get the key of the first loaded page
// so the user can always go back there.
const {key} = navigation.currentEntry;
backToHomeButton.onclick = () => navigation.traverseTo(key);
// Navigate away, but the button will always work.
await navigation.navigate('/another_url').finished;
Estado
La API de Navigation muestra un concepto de "estado", que es la información que proporciona el desarrollador y que se almacena de forma persistente en la entrada de historial actual, pero que el usuario no puede ver directamente.
Esto es muy similar a history.state
, aunque se mejoró con respecto a, en la API de History.
En la API de Navigation, puedes llamar al método .getState()
de la entrada actual (o cualquier entrada) para mostrar una copia de su estado:
console.log(navigation.currentEntry.getState());
De forma predeterminada, será undefined
.
Estado del parámetro de configuración
Aunque los objetos de estado se pueden mutar, esos cambios no se guardan con la entrada de historial, por lo que sucede lo siguiente:
const state = navigation.currentEntry.getState();
console.log(state.count); // 1
state.count++;
console.log(state.count); // 2
// But:
console.info(navigation.currentEntry.getState().count); // will still be 1
La forma correcta de establecer el estado es durante la navegación de la secuencia de comandos:
navigation.navigate(url, {state: newState});
// Or:
navigation.reload({state: newState});
Donde newState
puede ser cualquier objeto clonable.
Si deseas actualizar el estado de la entrada actual, es mejor realizar una navegación que reemplace la entrada actual:
navigation.navigate(location.href, {state: newState, history: 'replace'});
Luego, tu objeto de escucha de eventos "navigate"
puede detectar este cambio a través de navigateEvent.destination
:
navigation.addEventListener('navigate', navigateEvent => {
console.log(navigateEvent.destination.getState());
});
Actualiza el estado de manera síncrona
Por lo general, es mejor actualizar el estado de forma asíncrona a través de navigation.reload({state: newState})
, de modo que tu objeto de escucha "navigate"
pueda aplicar ese estado. Sin embargo, a veces, el cambio de estado ya se aplicó por completo cuando tu código lo detecta, por ejemplo, cuando el usuario activa o desactiva un elemento <details>
o cambia el estado de una entrada de formulario. En estos casos, te recomendamos que actualices el estado para que estos cambios se conserven a través de las recargas y los recorridos. Esto es posible con updateCurrentEntry()
:
navigation.updateCurrentEntry({state: newState});
También hay un evento para conocer este cambio:
navigation.addEventListener('currententrychange', () => {
console.log(navigation.currentEntry.getState());
});
Sin embargo, si reaccionas a los cambios de estado en "currententrychange"
, es posible que estés dividiendo o incluso duplicando tu código de control de estado entre el evento "navigate"
y el evento "currententrychange"
, mientras que navigation.reload({state: newState})
te permitiría controlarlo en un solo lugar.
Parámetros de estado en comparación con los de URL
Dado que el estado puede ser un objeto estructurado, es tentador usarlo para todo el estado de tu aplicación. Sin embargo, en muchos casos, es mejor almacenar ese estado en la URL.
Si esperas que el estado se retenga cuando el usuario comparte la URL con otro usuario, almacénala en la URL. De lo contrario, el objeto de estado es la mejor opción.
Acceso a todas las entradas
Sin embargo, la "entrada actual" no es todo.
La API también proporciona una forma de acceder a toda la lista de entradas por las que navegó un usuario mientras usaba tu sitio a través de su llamada a navigation.entries()
, que muestra un array de instantáneas de entradas.
Esto se puede usar para, p.ej., mostrar una IU diferente en función de cómo el usuario navegó a una página determinada o solo para mirar las URLs anteriores o sus estados.
Esto es imposible con la API de historial actual.
También puedes escuchar un evento "dispose"
en NavigationHistoryEntry
individuales, que se activa cuando la entrada ya no forma parte del historial del navegador. Esto puede ocurrir como parte de la limpieza general, pero también puede ocurrir durante la navegación. Por ejemplo, si retrocedes 10 lugares y, luego, navegas hacia adelante, se descartarán esas 10 entradas de historial.
Ejemplos
El evento "navigate"
se activa para todos los tipos de navegación, como se mencionó anteriormente.
(En realidad, hay un apéndice largo en la especificación de todos los tipos posibles).
Si bien para muchos sitios el caso más común será cuando el usuario haga clic en un <a href="...">
, hay dos tipos de navegación notables y más complejos que vale la pena abarcar.
Navegación programática
La primera es la navegación programática, en la que una llamada de método dentro del código del cliente causa la navegación.
Puedes llamar a navigation.navigate('/another_page')
desde cualquier parte del código para activar la navegación.
El objeto de escucha de eventos centralizado registrado en el objeto de escucha "navigate"
se encargará de esto, y se llamará de forma síncrona a tu objeto de escucha centralizado.
Se trata de una agregación mejorada de métodos más antiguos, como location.assign()
y sus amigos, además de los métodos pushState()
y replaceState()
de la API de History.
El método navigation.navigate()
muestra un objeto que contiene dos instancias de Promise
en { committed, finished }
.
Esto permite que el llamador espere hasta que la transición esté "confirmada" (la URL visible cambió y hay un NavigationHistoryEntry
nuevo disponible) o "finalizada" (todas las promesas que devuelve intercept({ handler })
están completas o rechazadas debido a una falla o a que otra navegación las anula).
El método navigate
también tiene un objeto de opciones, en el que puedes establecer lo siguiente:
state
: Es el estado de la nueva entrada de historial, como está disponible a través del método.getState()
enNavigationHistoryEntry
.history
: que se puede configurar como"replace"
para reemplazar la entrada de historial actual.info
: Es un objeto que se pasa al evento de navegación a través denavigateEvent.info
.
En particular, info
podría ser útil para, por ejemplo, denotar una animación en particular que haga que aparezca la siguiente página.
(La alternativa podría ser establecer una variable global o incluirla como parte del #hash. Ambas opciones son un poco incómodas).
En particular, este info
no se volverá a reproducir si un usuario más adelante causa la navegación, p. ej., a través de los botones Atrás y Adelante.
De hecho, siempre será undefined
en esos casos.
navigation
también tiene otros métodos de navegación, que muestran un objeto que contiene { committed, finished }
.
Ya mencioné traverseTo()
(que acepta un key
que denota una entrada específica en el historial del usuario) y navigate()
.
También incluye back()
, forward()
y reload()
.
Al igual que navigate()
, el objeto de escucha de eventos "navigate"
centralizado controla estos métodos.
Envíos de formularios
En segundo lugar, el envío de <form>
HTML a través de POST es un tipo especial de navegación, y la API de Navigation puede interceptarlo.
Si bien incluye una carga útil adicional, el objeto de escucha "navigate"
sigue controlando la navegación de forma centralizada.
Para detectar el envío de un formulario, busca la propiedad formData
en NavigateEvent
.
Este es un ejemplo que simplemente convierte cualquier envío de formulario en uno que permanece en la página actual a través de fetch()
:
navigation.addEventListener('navigate', navigateEvent => {
if (navigateEvent.formData && navigateEvent.canIntercept) {
// User submitted a POST form to a same-domain URL
// (If canIntercept is false, the event is just informative:
// you can't intercept this request, although you could
// likely still call .preventDefault() to stop it completely).
navigateEvent.intercept({
// Since we don't update the DOM in this navigation,
// don't allow focus or scrolling to reset:
focusReset: 'manual',
scroll: 'manual',
handler() {
await fetch(navigateEvent.destination.url, {
method: 'POST',
body: navigateEvent.formData,
});
// You could navigate again with {history: 'replace'} to change the URL here,
// which might indicate "done"
},
});
}
});
¿Qué información falta?
A pesar de la naturaleza centralizada del objeto de escucha de eventos "navigate"
, la especificación actual de la API de Navigation no activa "navigate"
en la primera carga de una página.
Y en el caso de los sitios que usan la renderización del servidor (SSR) en todos los estados, esto puede estar bien, ya que el servidor podría mostrar el estado inicial correcto, que es la forma más rápida de enviar contenido a los usuarios.
Pero los sitios que aprovechan el código del cliente para crear sus páginas podrían necesitar crear una función adicional para inicializar su página.
Otra decisión de diseño intencional de la API de Navigation es que solo opera dentro de un solo marco, es decir, la página de nivel superior o un solo <iframe>
específico.
Esto tiene varias implicaciones interesantes que se documentan con más detalle en la especificación, pero, en la práctica, reducirá la confusión de los desarrolladores.
La API de History anterior tiene varios casos extremos confusos, como la compatibilidad con marcos, y la API de Navigation rediseñada controla estos casos extremos desde el principio.
Por último, aún no hay un consenso respecto de modificar o reorganizar de forma programática la lista de entradas por las que navegó el usuario. Actualmente, se está analizando, pero una opción podría ser permitir solo las eliminaciones: ya sea de las entradas históricas o de "todas las entradas futuras". La última opción permitiría el estado temporal. Por ejemplo, como desarrollador, podría hacer lo siguiente:
- navegar a una URL o un estado nuevos para hacerle una pregunta al usuario
- Permite que el usuario complete su trabajo (o volver atrás).
- quitar una entrada del historial cuando se completa una tarea
Esto podría ser perfecto para ventanas modales temporales o intersticiales: la nueva URL es algo desde lo que un usuario puede usar el gesto de retroceso, pero no puede avanzar accidentalmente para volver a abrirla (porque se quitó la entrada). Esto no es posible con la API de History actual.
Prueba la API de Navigation
La API de Navigation está disponible en Chrome 102 sin marcas. También puedes probar una demostración de Domenic Denicola.
Si bien la versión clásica de la API de History parece sencilla, no está muy bien definida y presenta una gran cantidad de problemas en casos puntuales y cómo se ha implementado de manera diferente en los navegadores. Esperamos que consideres proporcionar comentarios sobre la nueva API de Navigation.
Referencias
- WICG/navigation-api
- Posición de los estándares de Mozilla
- Intención de crear un prototipo
- Revisión de TAG
- Entrada de Chromestatus
Agradecimientos
Gracias a Thomas Steiner, Domenic Denicola y Nate Chapin por revisar esta publicación.