Rive en una extensión de Chrome (Manifest V3): incluye el WASM en el paquete
Por el equipo de Charmotion · Actualizado el

Rive falla en una extensión Manifest V3 porque el runtime web descarga rive.wasm desde unpkg por defecto, y Chrome prohíbe el código alojado de forma remota, WebAssembly incluido. Empaqueta @rive-app/canvas, copia sus archivos WASM dentro del paquete, llama a RuntimeLoader.setWasmUrl() con chrome.runtime.getURL() antes de crear la animación, incluye el .riv en el paquete y agrega 'wasm-unsafe-eval' a la CSP.
Puntos clave
- Chrome define como código alojado de forma remota todo lo que se ejecuta y se carga desde fuera de los archivos propios de la extensión, y menciona WASM de forma explícita.
- La CSP predeterminada de las extensiones desactiva WebAssembly;
script-src 'self' 'wasm-unsafe-eval'; object-src 'self';es la política que necesitas. - El runtime descarga
rive.wasmdesde unpkg y una alternativa desde jsDelivr; reemplaza ambas con URL locales. - Mantén los archivos WASM y el paquete npm exactamente en la misma versión; Rive advierte que una diferencia puede romper el renderizado.
- Electron y Tauri necesitan el mismo WASM autoalojado, además de
'wasm-unsafe-eval'en cualquier CSP que definas.
¿Ya tienes un personaje o logo? Mira tu personaje en pantallas reales de una app: pantallas de splash, onboarding, carga y estado vacío. Es gratis, funciona en tu navegador y no sube nada.
¿Por qué Rive no carga en mi extensión de Chrome?
Dos reglas de Manifest V3 chocan con los valores predeterminados del runtime.
1. El código remoto está prohibido. Chrome define el código alojado de forma remota como "todo lo que ejecuta el navegador y se carga desde un lugar distinto de los archivos propios de la extensión", y da como ejemplos "cosas como JavaScript y WASM". Las extensiones deben "empaquetar todo el código que usan dentro de la propia extensión". El runtime web de Rive, tal como viene, descarga su motor desde una CDN. En @rive-app/canvas 2.47.1 el valor predeterminado es https://unpkg.com/@rive-app/canvas@2.47.1/rive.wasm, y si falla intenta con rive_fallback.wasm desde jsDelivr.
2. La CSP predeterminada desactiva WebAssembly. Sin una clave content_security_policy, las páginas de la extensión reciben script-src 'self'; object-src 'self';, y la documentación de Chrome dice que con ella "WebAssembly estará desactivado". La política más permisiva que Chrome acepta para las páginas de extensión es script-src 'self' 'wasm-unsafe-eval'; object-src 'self';.
Por eso los síntomas son una solicitud de red bloqueada para rive.wasm o un error de CSP sobre la compilación de WebAssembly, seguidos de un canvas en blanco. Corrige ambos.
¿Qué CSP necesita el manifest para WebAssembly?
Agrega 'wasm-unsafe-eval' a extension_pages. Es la política mínima según la documentación de Chrome, así que también es lo máximo que puedes flexibilizarla:
{
"manifest_version": 3,
"name": "Mascot",
"version": "1.0.0",
"action": { "default_popup": "popup.html" },
"content_security_policy": {
"extension_pages": "script-src 'self' 'wasm-unsafe-eval'; object-src 'self';"
}
}
- No agregues
'unsafe-eval'ni un host de CDN; Chrome rechaza durante la instalación las políticas más permisivas que la mínima. - Esta política cubre las páginas de la extensión: el popup, el panel lateral, la página de opciones y el reemplazo de la pestaña nueva.
- El .riv se descarga desde el propio origen de la extensión, que
'self'permite.
¿Cómo empaqueto rive.wasm y el .riv en la extensión?
Instala una versión exacta del runtime, copia sus dos archivos WASM en la carpeta de la extensión y apunta el runtime a ellos antes de crear la primera animación. El paquete npm incluye ambos archivos (en 2.47.1, rive.wasm pesa 2 165 388 bytes y rive_fallback.wasm 2 191 884 bytes).
npm install --save-exact @rive-app/canvas@2.47.1
mkdir -p extension/vendor
cp node_modules/@rive-app/canvas/rive.wasm extension/vendor/
cp node_modules/@rive-app/canvas/rive_fallback.wasm extension/vendor/
Luego, en el script del popup (empaquetado con Vite, esbuild, Rollup o webpack, ya que las páginas de extensión no pueden resolver imports de npm sin ruta):
import { Rive, RuntimeLoader } from "@rive-app/canvas";
// Must run before the first new Rive(): no CDN requests from now on.
RuntimeLoader.setWasmUrl(chrome.runtime.getURL("vendor/rive.wasm"));
RuntimeLoader.setWasmFallbackUrl(chrome.runtime.getURL("vendor/rive_fallback.wasm"));
const canvas = document.getElementById("mascot");
const r = new Rive({
src: chrome.runtime.getURL("assets/character.riv"), // shipped in the package
canvas,
stateMachine: "CharacterViewModelMachine",
autoplay: !matchMedia("(prefers-reduced-motion: reduce)").matches,
autoBind: true,
onLoad: () => r.resizeDrawingSurfaceToCanvas(),
});
document.addEventListener("visibilitychange", () => {
if (document.hidden) r.pause(); else r.play();
});
setWasmUrles la llamada que Rive documenta para alojar el archivo tú mismo. La documentación de Rive añade que la versión del WASM "debe coincidir con la versión del paquete@rive-app", porque ambos se compilan en pareja; actualiza los archivos copiados en el mismo commit que cualquier cambio de versión.setWasmFallbackUrlexiste en el código fuente del runtime desde la 2.40.1 (lo comprobamos de la 2.40.1 a la 2.47.1). Apuntarla al archivo alternativo empaquetado hace que incluso el reintento sea local; en versiones anteriores la llamada no existe.- Incluye el .riv en el paquete y cárgalo con
chrome.runtime.getURL(). Así funciona sin conexión y no hay nada remoto por lo que un revisor pueda preguntar.
El código de la librería sigue conteniendo las URL de CDN predeterminadas como cadenas de texto, aunque nunca se usan una vez que las reemplazas. La guía de Chrome sugiere buscar https:// en el código compilado para encontrar infracciones, así que si un revisor lo menciona, señala la llamada a setWasmUrl.
¿Dónde puede ejecutarse una animación de Rive: popup, panel lateral o content script?
Las páginas de extensión son el caso fácil: el popup, el panel lateral, las opciones y la pestaña nueva se ejecutan con la CSP de tu extensión, así que la configuración anterior funciona tal cual.
- Popup: Chrome lo ajusta a su contenido, entre 25×25 y 800×600 píxeles. Dale al canvas un ancho y un alto explícitos en CSS, o el popup se colapsará alrededor de un canvas vacío.
- Panel lateral y opciones: el mismo código; cambia el tamaño de la superficie de dibujo cuando cambie el ancho del panel.
- En páginas web: en lugar de ejecutar el runtime dentro de un content script en la página de otra persona, inserta un iframe que apunte a una página de tu extensión. Incluye esa página en
web_accessible_resourcespara que los sitios web puedan mostrarla en un frame; así la animación se ejecuta en el origen de tu extensión con tu CSP, aislada de los scripts y estilos del sitio.
"web_accessible_resources": [
{
"resources": ["mascot-frame.html"],
"matches": ["https://*.example.com/*"]
}
]
Usa la lista matches más acotada posible; controla qué sitios pueden cargar el recurso.
¿Cómo mantengo rápida una mascota en la pestaña nueva?
El reemplazo de la pestaña nueva (chrome_url_overrides) se carga cada vez que alguien abre una pestaña, así que los costos pequeños se acumulan.
- Precarga el WASM. La guía de precarga de Rive usa
<link rel="preload" as="fetch" crossorigin>; la URL de precarga debe coincidir exactamente con la URL desetWasmUrl(), o el archivo se descargará dos veces. Una ruta relativavendor/rive.wasmen una página de extensión se resuelve a la misma URL quechrome.runtime.getURL("vendor/rive.wasm"). - Pausa cuando no se ve. El ejemplo se pausa con
visibilitychange, así las pestañas en segundo plano no siguen animando. - Una sola instancia. Usa un único personaje con estados de ánimo, no varios canvas.
- Respeta el movimiento reducido. La reproducción automática se desactiva cuando el usuario pide menos movimiento; en su lugar, muestra el primer fotograma o una imagen fija.
Rive también recomienda @rive-app/webgl2 para la mayoría de los proyectos web; se usan las mismas llamadas a RuntimeLoader, pero copia los archivos WASM de ese paquete.
¿Esta misma configuración funciona en Firefox?
Sí, con las mismas reglas. La referencia del manifiesto de MDN indica que en Manifest V3 los únicos valores permitidos en script-src para las páginas de la extensión son 'self', 'none' y 'wasm-unsafe-eval', y que "Manifest V3 no permite URL remotas en script-src". También señala que las políticas de complementos de Firefox no permiten scripts remotos. El WASM incluido en el paquete, el .riv local y la misma cadena de CSP sirven sin cambios.
En los complementos con Manifest V2, MDN indica que Firefox actualmente puede ejecutar WebAssembly sin 'wasm-unsafe-eval' por compatibilidad con versiones anteriores, pero que "este comportamiento no está garantizado", y recomienda declararlo.
¿Las apps de Electron y Tauri necesitan la misma solución?
El principio es idéntico: incluye rive.wasm con la app, llama a RuntimeLoader.setWasmUrl() con una URL local y permite WebAssembly en tu CSP.
- Electron. La lista de seguridad de Electron desaconseja cargar y ejecutar código remoto y recomienda servir las páginas locales desde un protocolo personalizado en lugar de
file://. ApuntasetWasmUrla una URL de ese protocolo. Si defines una CSP (con una etiqueta meta para las páginas locales), incluye'wasm-unsafe-eval'enscript-src. - Tauri. La CSP de Tauri solo se aplica si la defines en la configuración; cuando lo haces, su documentación indica que hay que "incluir
'wasm-unsafe-eval'comoscript-src" y desaconseja los scripts desde CDN. El runtime descarga el WASM y el .riv confetch, así que asegúrate de queconnect-src(odefault-src) permita el origen desde el que los sirves.
¿Qué cambia en un .riv de Charmotion dentro de una extensión?
Nada en el archivo. El .riv de Charmotion (17 animaciones a partir de una imagen, con un view model Character: un número mood de 0 a 9 y triggers success, error, yes, no, enter y exit) es autónomo, así que funciona sin conexión desde el paquete. El código web que lo acompaña carga el runtime desde un CDN; para una extensión, reemplaza esa etiqueta script por el import empaquetado y las dos llamadas a RuntimeLoader que se muestran arriba. El .riv forma parte de todos los planes de pago (desde $20/mes).

Cómo ejecutar una animación de Rive en una extensión de Chrome con Manifest V3
- Instala el runtime. Instala una versión exacta de @rive-app/canvas y empaqueta el script de tu popup con un bundler.
- Copia el WASM. Copia rive.wasm y rive_fallback.wasm de node_modules a la carpeta de la extensión.
- Apunta el runtime a esos archivos. Llama a RuntimeLoader.setWasmUrl y setWasmFallbackUrl con chrome.runtime.getURL antes de new Rive().
- Incluye el .riv. Coloca el .riv en el paquete y cárgalo con chrome.runtime.getURL.
- Define la CSP. Agrega script-src 'self' 'wasm-unsafe-eval'; object-src 'self'; en content_security_policy.extension_pages.
- Revisa la red. Abre las DevTools del popup y confirma que ninguna solicitud va a unpkg ni a jsDelivr.
Preguntas frecuentes
¿Mi extensión puede descargar archivos .riv desde un servidor?
Las reglas de Chrome sobre código remoto apuntan al código que se ejecuta, y su guía indica que los datos como JSON o CSS no son código remoto. Un .riv es principalmente datos, pero los archivos que usan scripting de Rive contienen código, y un archivo incluido en el paquete funciona sin conexión y evita preguntas en la revisión. Inclúyelo en el paquete, salvo que de verdad necesites actualizar el contenido de forma remota.
¿Por qué mi popup de Rive se ve diminuto o vacío?
Chrome ajusta el tamaño del popup a su contenido, entre 25×25 y 800×600 píxeles. Un canvas sin dimensiones en CSS le da poco que medir. Define un ancho y un alto explícitos en el canvas y luego llama a resizeDrawingSurfaceToCanvas() en onLoad para que el búfer de dibujo coincida.
¿Necesito web_accessible_resources para rive.wasm?
No para las páginas de la extensión, como el popup o el panel lateral, que cargan sus propios archivos del paquete. Necesitas web_accessible_resources cuando un sitio web debe cargar algo de tu extensión, por ejemplo, una página de la extensión insertada como iframe en un sitio.
Respuestas rápidas de esta guía
Fuentes
- Deal with remote hosted code violations, Chrome for Developers
- Manifest: content_security_policy, Chrome for Developers
- chrome.action (popup size limits), Chrome for Developers
- Manifest: web_accessible_resources, Chrome for Developers
- Override Chrome pages, Chrome for Developers
- Preloading WASM, Rive
- @rive-app/canvas package, npm
- content_security_policy (WebExtensions manifest), MDN Web Docs
- Security, Electron
- Content Security Policy (CSP), Tauri