Sobre el papel, una extensión moderna debería ser casi automáticamente un producto multiplataforma. Chrome, Firefox, Edge y Safari admiten WebExtensions, utilizan un archivo manifest.json similar y ofrecen mecanismos comparables: content scripts, almacenamiento, comunicación entre contextos, un botón en la barra de herramientas y páginas de configuración.
Eso es cierto, pero solo hasta cierto punto.
Después de publicar y portar nueve extensiones de POLPROG, sabemos que compartir código no significa compartir exactamente el mismo producto. La mayor parte de la lógica de negocio puede reutilizarse, pero el comportamiento del navegador, el ciclo de vida del proceso en segundo plano, la interfaz, los permisos, el empaquetado y la revisión de la tienda siguen siendo problemas independientes.
El catálogo incluye:
- AutoScroll,
- ClickClean,
- DevScope,
- LeakLens,
- LoopIt,
- Loudly,
- OneMoreJump,
- TabZoo,
- TubePilot.
No son nueve copias del mismo tipo de complemento. El conjunto incluye herramientas para vídeo, amplificación de audio, privacidad, limpieza de datos, desarrollo, gestión de pestañas y un juego ligero. Por eso los problemas no se limitaron a una única API. Afectaron a service workers, pop-ups, content scripts, audio, almacenamiento, iconos, permisos y el proceso de publicación.
POLPROG mantiene públicamente nueve productos de extensión, publicados inicialmente sobre todo para Chrome y Firefox, y posteriormente portados también a Edge y Safari. [1][2]
Este artículo no es un benchmark de laboratorio. No registramos desde el primer commit cada minuto dedicado a cada tienda, por lo que no diremos que Safari cuesta exactamente 2,7 veces más que Chrome ni que el 94 % del código es común. Eso sería una precisión falsa.
Sí podemos mostrar algo más útil: qué suposiciones resultaron equivocadas, dónde aparecieron fallos reales y cómo diseñaríamos hoy todo el proceso desde cero.
Última verificación de las fuentes: 17 de julio de 2026.
Cuatro navegadores, cuatro funciones diferentes
| Plataforma | Función en nuestro proceso | Mayor ventaja | Trampa más habitual |
|---|---|---|---|
| Chrome | Base principal de compatibilidad con Manifest V3 | Documentación más amplia y mayor mercado de extensiones | Service worker suspendido y requisitos estrictos de permisos |
| Edge | Port basado en Chromium | Pocas diferencias de código respecto a Chrome | Partner Center, metadatos y certificación separados |
| Firefox | Implementación independiente de WebExtensions | Descubre muy bien las suposiciones dependientes de Chromium | Diferencias de API y manifest, firma y requisitos sobre fuentes |
| Safari | Producto separado dentro del ecosistema Apple | Acceso a usuarios de macOS, iOS y iPadOS | Xcode, aplicación contenedora, firma y comportamiento de runtime distinto |
MDN destaca que WebExtensions pretende permitir que los complementos funcionen en los principales navegadores con cambios mínimos, pero también documenta diferencias importantes entre Chrome, Firefox y Safari en disponibilidad de API, manifests y gestión de operaciones asíncronas. [7][8]
Esto describe bien nuestra experiencia. Los “cambios mínimos” son realistas para un complemento sencillo. Cuanto más interactúa una extensión con la página, los medios, el ciclo de vida de las pestañas o el proceso de fondo, menos útil resulta la palabra “mínimos”.
Lección 1: una base de código sí, un solo manifest no necesariamente
El mejor punto de partida es una lógica compartida con configuraciones de build separadas.
En la práctica, la estructura más cómoda fue aquella en la que se comparten:
- la lógica de las funciones,
- los modelos de datos,
- los módulos de storage,
- la validación,
- la mayor parte de la interfaz del pop-up y de ajustes,
- las localizaciones,
- los tests unitarios.
Permanecen separados:
- las variantes del manifest,
- los identificadores de los complementos,
- las versiones mínimas del navegador,
- la configuración del background,
- algunos permisos,
- los iconos y recursos de tienda,
- el empaquetado,
- la firma,
- los scripts de publicación.
No se trata de mantener cuatro directorios copiados manualmente. Eso provoca divergencias rápidamente. Un modelo mejor consiste en fuentes compartidas más un generador o una capa de configuración que prepare un artefacto independiente para cada plataforma.
Firefox, por ejemplo, requiere ajustes propios en browser_specific_settings. En Manifest V3, el identificador de la extensión es necesario para firmarla y, desde noviembre de 2025, los nuevos complementos enviados a AMO también deben declarar la configuración relacionada con la recogida de datos. [9]
Safari, por su parte, requiere un proyecto Xcode y una aplicación que contenga la extensión. Apple proporciona un conversor WebExtension, pero el resultado sigue teniendo que tratarse como un proyecto nativo destinado a firmarse y distribuirse mediante App Store. [3][4]
Por tanto, un manifest compartido sería un ahorro artificial. Es más fácil mantener unas pocas diferencias pequeñas y explícitas que un único archivo lleno de excepciones y condiciones.
Lección 2: Manifest V3 no unificó el comportamiento de los navegadores
Manifest V3 mejoró parte de la compatibilidad y organizó el modelo de extensiones, pero no creó un único runtime idéntico.
El cambio más visible en Chrome es la sustitución de las páginas de fondo persistentes por un extension service worker. Google lo describe como un manejador central de eventos que se inicia cuando es necesario. El proceso puede finalizar, y las variables almacenadas solo en memoria desaparecen con él. [5][6]
Esto tiene consecuencias directas.
Código como:
let activeSessions = new Map();
let currentTab = null;
let isEnabled = false;
no puede ser la única fuente de verdad si esos datos son necesarios después de que el service worker vuelva a iniciarse.
El estado debería dividirse en tres grupos:
- estado temporal que puede desaparecer con seguridad;
- estado reconstruible a partir de las pestañas activas o del DOM;
- estado persistente que debe guardarse en
storage.local,storage.sessionu otro lugar adecuado.
El registro de listeners es igual de importante. Chrome recomienda declarar los manejadores de eventos en el ámbito global del script para que se registren de forma síncrona cuando el service worker se reactive. Un listener creado solo después de una inicialización asíncrona puede perder el mismo evento que despertó al worker. [10]
Es fácil no detectarlo durante el desarrollo. DevTools abierto puede influir en el ciclo de vida del service worker y el error puede aparecer solo durante el uso normal. Google describió problemas similares en un análisis de migraciones a MV3: suspender el proceso puede interrumpir temporizadores y eliminar el estado mantenido en memoria. [11]
La regla principal es:
El proceso en segundo plano de una extensión debe diseñarse como un manejador de eventos interrumpible, no como un pequeño servidor Node.js que funciona permanentemente.
Lección 3: Chrome es un buen punto de partida, pero un mal único entorno de pruebas
Chrome es una base natural para un proyecto Manifest V3. Tiene documentación extensa, ejemplos, herramientas de desarrollo y el mayor ecosistema de complementos.
Al mismo tiempo, probar únicamente en Chrome genera una falsa sensación de compatibilidad.
Si el código funciona en Chrome y Edge, confirma sobre todo la compatibilidad con Chromium. No confirma:
- el comportamiento correcto en Gecko,
- el comportamiento correcto en WebKit,
- la resistencia a otro ciclo de vida del pop-up,
- el funcionamiento con un modelo de permisos diferente,
- el escalado correcto de los iconos,
- el comportamiento correcto del audio,
- una semántica idéntica de las API.
Chrome Web Store exige que una extensión tenga un único propósito, limitado y fácil de entender. También exige el conjunto mínimo de permisos, una justificación de cada uno y declaraciones coherentes sobre las prácticas de datos. Los permisos excesivamente amplios pueden provocar un rechazo. Manifest V3 tampoco permite cargar y ejecutar código alojado de forma remota. [12]
Estos requisitos influyen en la arquitectura antes de lo que podría parecer. Añadir acceso a todas las páginas “por si acaso” no es neutral. Más adelante habrá que justificarlo ante el revisor y ante el usuario. Una solución mejor puede ser activeTab, host permissions opcionales o acceso limitado a dominios concretos.
Chrome nos enseñó dos cosas opuestas:
- es un buen lugar para establecer la variante básica de MV3;
- no debería definir todas las suposiciones de la capa compartida.
Lección 4: Edge es el port técnico más sencillo, pero no un canal gratuito de distribución
Microsoft describe oficialmente la migración de una extensión existente de Chrome a Edge como un escenario separado y compatible. Como ambos navegadores utilizan Chromium, las diferencias en el código básico suelen ser pequeñas. [13]
En nuestros proyectos, Edge normalmente requirió menos cambios técnicos. Eso no significó una publicación “con un solo clic”.
La extensión se envía a un Partner Center separado, donde hay que:
- subir un paquete ZIP propio,
- establecer disponibilidad y mercados,
- elegir una categoría,
- describir el propósito único,
- justificar cada permiso,
- declarar el uso de código remoto,
- describir las prácticas de datos,
- preparar fichas para distintos idiomas,
- subir gráficos y capturas independientes,
- añadir notas para el equipo de certificación. [14]
Al igual que Google, Microsoft exige un objetivo claramente definido y el menor conjunto necesario de permisos. Partner Center también indica que las declaraciones incompletas o incoherentes pueden alargar la certificación o terminar en rechazo. [14]
Edge es, por tanto, un buen ejemplo de la diferencia entre el coste del port y el coste del canal.
El código puede ser casi idéntico, pero aun así hay que mantener:
- un registro de producto independiente,
- un estado de revisión independiente,
- metadatos independientes,
- variantes gráficas independientes,
- un proceso de actualización separado,
- coherencia entre las declaraciones de privacidad.
Para una sola extensión es una carga moderada. Para nueve productos se convierte en un proceso operativo periódico.
Lección 5: Firefox detecta mejor el código escrito “por accidente solo para Chrome”
Firefox utiliza WebExtensions, pero no es Chromium. Precisamente por eso es un entorno de pruebas tan valioso.
La diferencia más conocida afecta al namespace y al código asíncrono. Históricamente Chrome utilizaba chrome y callbacks, mientras que Firefox y Safari usaban browser y promises. En Manifest V3, Chrome y Edge añadieron soporte de promises para muchos métodos, pero no desaparecieron todas las diferencias. Mozilla recomienda escribir código con browser y promises y utilizar WebExtension browser API Polyfill cuando sea necesario. [8][15]
No merece la pena dispersar por todo el proyecto condiciones como:
if (isFirefox) {
// ...
} else {
// ...
}
en cada llamada de API. Es mejor crear una pequeña capa adaptadora:
export const extensionApi = {
storageGet: (keys) => browser.storage.local.get(keys),
storageSet: (value) => browser.storage.local.set(value),
sendMessage: (message) => browser.runtime.sendMessage(message),
};
y resolver las diferencias en un único lugar.
Firefox también obliga a una mayor disciplina de publicación. Las extensiones deben estar firmadas incluso cuando se distribuyen de forma independiente en la configuración estándar. AMO valida el paquete, y el código minificado o difícil de leer puede exigir el envío del paquete completo de fuentes. Mozilla también puede realizar una revisión manual después de la publicación y rechazar la versión actual o una anterior. [16][17][18]
Desde el punto de vista de la calidad, esto es una ventaja. El proceso obliga a que el build sea:
- reproducible,
- documentado,
- libre de ofuscación innecesaria,
- coherente con el comportamiento declarado,
- revisable sin conocimientos internos del autor.
Firefox también reveló problemas en lugares donde Chromium era más permisivo. Afectaban al manejo de mensajes, la espera de una respuesta promise, las diferencias en los pop-ups y las suposiciones sobre la presencia de determinadas propiedades de API.
La lección principal:
Firefox no es una casilla adicional cuando el proyecto ya está terminado. Es un segundo runtime independiente que debería entrar pronto en las pruebas.
Lección 6: Safari no es un port de tienda, sino una fase de producto independiente
Apple admite WebExtensions y ofrece una herramienta para convertir extensiones de Chrome, Firefox o Edge. Aun así, el proceso de Safari es fundamentalmente distinto. [3][4]
La extensión pasa a formar parte de una aplicación creada en Xcode. Hay que gestionar:
- el proyecto de la aplicación,
- el target de la extensión,
- el bundle identifier de la aplicación,
- un bundle identifier independiente para la extensión,
- la firma,
- perfiles y certificados,
- el icono de la aplicación,
- los entitlements,
- el archivado,
- App Store Connect,
- la revisión de App Store.
Apple exige que la extensión funcione con la versión actual de Safari para el sistema correspondiente, que no interfiera con la interfaz de Safari y que no solicite acceso a más sitios de los necesarios. [19]
Aquí aparecieron los problemas más concretos de nuestro catálogo.
Loudly: la interfaz funcionaba, pero el sonido no se amplificaba
Loudly modifica el audio reproducido en una página. En la versión para Safari se podía ver la interfaz, cambiar los ajustes y pensar que el producto funcionaba. Sin embargo, la señal de audio real no se amplificaba.
Es una clase de bug peor que un fallo evidente. El smoke test “el pop-up se abre” pasa, pero no se cumple la promesa principal del producto.
Conclusión: en extensiones multimedia, la prueba funcional debe medir el efecto final, no solo el estado de la interfaz.
TabZoo: una pantalla blanca en lugar de la aplicación
La versión para Safari de TabZoo podía mostrar una página en blanco. Este problema puede deberse a un error de inicialización, rutas de recursos, un contexto incorrecto o una excepción previa al renderizado.
En Chromium, un error parecido puede permanecer oculto si el orden de inicio o la disponibilidad de la API son distintos.
Conclusión: cada vista de la extensión debería disponer de un mecanismo mínimo de diagnóstico y manejo de errores de inicialización. Una página vacía no puede ser el estado final del producto.
LoopIt: un clic abría dos interfaces
En LoopIt, pulsar el icono en Safari hacía aparecer un pop-up y un panel adicional de la extensión. Era un conflicto entre un pop-up configurado de forma declarativa y la gestión manual del clic.
En un navegador el comportamiento parecía correcto; en otro provocaba una doble reacción.
Conclusión: hay que definir claramente quién es responsable del clic del botón. O el botón tiene default_popup, o el evento se gestiona mediante código. Combinar ambos mecanismos requiere un diseño muy consciente.
Estos tres casos muestran por qué ejecutar el conversor de Apple no completa el port. El conversor traslada la estructura. No confirma la semántica del comportamiento.
Lección 7: las extensiones de vídeo y audio son más difíciles de lo que sugiere el pop-up
Cuatro de nuestros productos dependen mucho de los medios:
- AutoScroll,
- LoopIt,
- Loudly,
- TubePilot.
Cada uno debe coordinar varios mundos:
- el DOM de la página,
- el elemento de audio o vídeo,
- el content script,
- el pop-up,
- el proceso en segundo plano,
- los ajustes guardados,
- la navegación SPA,
- los cambios de pestaña activa.
La mayoría de los errores aparece no dentro de una función, sino en el límite entre esos contextos.
Un escenario de ejemplo:
- el usuario abre el pop-up;
- el pop-up pregunta al content script por el estado actual;
- la página acaba de cambiar de vídeo sin recargarse por completo;
- el antiguo elemento de vídeo ha sido eliminado;
- el content script todavía conserva una referencia al elemento anterior;
- el pop-up muestra un valor correcto, pero modifica un reproductor inactivo.
Añadir otro setTimeout no es la solución. Se necesita un modelo de estado explícito:
- identificar el elemento activo,
- observar cambios del DOM,
- volver a conectar handlers,
- confirmar la ejecución del comando,
- sincronizar la interfaz solo después de la respuesta,
- restaurar el estado tras reiniciar el proceso en segundo plano.
En servicios como YouTube también hay que tener en cuenta la navegación SPA. Cambiar de vídeo no siempre provoca una recarga completa del documento, por lo que una inicialización única del content script no basta.
Lección 8: el icono y el pop-up forman parte de la implementación, no solo del branding
Un build multiplataforma puede ser técnicamente correcto y, al mismo tiempo, verse mal.
En nuestras pruebas aparecieron iconos:
- demasiado grandes para el área disponible,
- sin margen de seguridad,
- tocando los bordes,
- visualmente más pequeños que otros iconos pese a tener el mismo tamaño de archivo,
- correctos en la tienda, pero poco legibles en la barra del navegador.
El tamaño del PNG no resuelve el problema. Importa el tamaño visual del símbolo dentro del lienzo.
Lo mismo ocurre con los pop-ups. Una misma anchura CSS puede mostrarse de forma diferente debido a:
- el renderizado de fuentes,
- los estilos predeterminados de los controles de formulario,
- el comportamiento de la barra de desplazamiento,
- el cálculo de la altura,
- los márgenes del sistema,
- el escalado de pantalla,
- el modo claro y oscuro.
Por eso ahora preparamos los assets como un conjunto, no como una única imagen:
- SVG fuente,
- variantes raster necesarias para el manifest,
- safe area controlada,
- icono de la aplicación Safari,
- gráficos de la ficha,
- capturas de la tienda.
La interfaz de la extensión también debería tener dimensiones mínimas y máximas y seguir siendo legible cuando cambia la longitud de las traducciones.
Lección 9: la publicación es un producto operativo independiente
El mayor error organizativo sería asumir que el trabajo termina al generar el ZIP.
Cada tienda tiene su propio modelo de confianza.
Chrome Web Store
Chrome exige un propósito único y claro, permisos mínimos, declaraciones sobre datos y una política de privacidad. En Manifest V3 está prohibido ejecutar código remoto. [12]
La descripción del producto debe corresponder al comportamiento real. Si la ficha promete una función que el revisor no puede usar, no es un problema de marketing, sino de certificación.
Firefox Add-ons
AMO valida automáticamente el paquete, puede requerir los archivos fuente del código compilado y conserva la posibilidad de una revisión manual posterior. Merece la pena corregir los avisos de seguridad y privacidad incluso si técnicamente se puede avanzar. [16][17]
Microsoft Edge Add-ons
Partner Center requiere información independiente sobre disponibilidad, privacidad y fichas por idioma. Cada permiso debería justificarse y la información debe ser coherente con el manifest y el comportamiento de la extensión. [14]
Safari y App Store
Safari añade la capa de aplicación, la firma, los metadatos de App Store y las normas que se aplican a las aplicaciones de Apple. La extensión debe funcionar en la versión actual de Safari y limitar el acceso a las páginas al mínimo necesario. [19]
Como resultado, nueve extensiones no significan nueve fichas. Con cobertura completa de cuatro plataformas pueden existir hasta 36 combinaciones producto-navegador, sin contar versiones lingüísticas y variantes separadas para dispositivos Apple.
Es una razón suficiente para mantener los metadatos en el repositorio.
Lección 10: se necesita una matriz de pruebas, no una nota que diga “comprobar la extensión”
Para cada extensión mantenemos el mismo núcleo de pruebas y después añadimos casos relacionados con su función.
Smoke test mínimo
Después de cada build comprobamos:
- instalación limpia;
- actualización desde la versión anterior;
- aparición del icono;
- apertura del pop-up;
- ausencia de errores de inicialización;
- guardado y nueva lectura de ajustes;
- funcionamiento tras cerrar y reiniciar el navegador;
- funcionamiento tras la suspensión del background;
- comportamiento sin permiso para la página;
- comportamiento en una página no compatible.
Prueba de comunicación
Probamos por separado:
- pop-up → background,
- pop-up → content script,
- content script → background,
- respuesta asíncrona,
- ausencia de pestaña activa,
- recarga de pestaña,
- navegación SPA,
- reinyección del content script,
- cierre del pop-up antes de recibir la respuesta.
Prueba específica de la función
En Loudly no basta con mover el deslizador. Hay que confirmar el cambio del nivel de audio.
En LoopIt hay que comprobar que la reproducción vuelve realmente al intervalo elegido.
En AutoScroll hay que probar distintos layouts, pantalla completa, cambio de vídeo y desplazamiento manual.
En ClickClean hay que verificar el efecto sobre datos de prueba reales, no solo el mensaje “hecho”.
En DevScope y LeakLens hay que validar el informe en páginas con distintas políticas CSP, número de frames y conjuntos de cabeceras.
Prueba de plataforma
Al final se añaden casos específicos del navegador:
- reinicio del service worker en Chromium,
- paquete firmado de Firefox,
- host permissions en Firefox,
- instalación y activación de la extensión en Safari,
- versiones de macOS e iOS cuando el producto las admite,
- actualización mediante la tienda y no solo sideload local.
Lección 11: las pruebas automáticas no sustituyen el browser QA real
La automatización es necesaria, pero tiene límites.
Los tests unitarios cubren bien:
- la lógica de negocio,
- la validación,
- las migraciones de storage,
- el formateo de datos,
- las reglas para activar funciones.
Los tests de integración pueden comprobar:
- el manifest,
- la presencia de archivos,
- la comunicación entre módulos,
- las llamadas básicas a la API,
- la integridad de las localizaciones.
Las pruebas E2E en Chromium detectan una gran parte de las regresiones de interfaz y content scripts.
No confirman que:
- Firefox interprete todos los permisos de forma idéntica,
- Safari modifique correctamente el audio,
- App Store ejecute el target correcto,
- el pop-up no se abra dos veces,
- el icono se vea correctamente en la barra del sistema.
Los errores más costosos de nuestros ports no eran fáciles de detectar con un test unitario. Eran problemas de integración entre la extensión, el navegador, la página y el sistema operativo.
Lección 12: la capa de compatibilidad debe describir capacidades, no nombres de navegadores
Al inicio de un proyecto resulta tentadora una condición como:
if (browserName === "safari") {
// comportamiento especial
}
A veces es inevitable, pero no debería ser el modelo arquitectónico principal.
Preguntas mejores son:
- ¿el entorno admite esa API?
- ¿el pop-up es declarativo?
- ¿el background es un service worker?
- ¿está disponible un método concreto?
- ¿la función necesita un contenedor nativo?
- ¿la página expone el elemento multimedia que necesitamos?
El código basado en capacidades es más resistente a los cambios de versión y más fácil de probar.
Una capa de plataforma puede exponer, por ejemplo:
export const capabilities = {
supportsSessionStorage: Boolean(browser.storage?.session),
supportsSidePanel: Boolean(browser.sidePanel),
usesServiceWorker: import.meta.env.BACKGROUND_MODE === "service-worker",
isSafariContainer: import.meta.env.TARGET === "safari",
};
Esto no elimina las diferencias. Las hace explícitas y las concentra en un solo lugar.
Lección 13: un enfoque privacy-first simplifica realmente la publicación
Las extensiones de POLPROG se diseñan sin analítica, seguimiento ni backend externo cuando no son necesarios.
No es solo una decisión de marca. Simplifica:
- la descripción del flujo de datos,
- la política de privacidad,
- la justificación de permisos,
- la revisión del código,
- el riesgo de fuga,
- la coherencia entre la ficha y el código.
No significa que una extensión respetuosa con la privacidad no necesite documentación. Si un complemento lee el contenido de una página, el historial, las pestañas o las cookies, debe explicar claramente para qué, aunque no envíe nada a un servidor.
Chrome y Edge exigen justificar permisos y declarar prácticas de datos. Firefox exige que los nuevos complementos informen sobre permisos relacionados con la recogida de datos. Safari recomienda limitar el acceso a páginas al mínimo necesario. [9][12][14][19]
Diseñar con permisos mínimos desde el primer día es mucho más sencillo que eliminar permisos excesivos después de un rechazo.
Lección 14: el pipeline de release debe construir productos, no un archivo final-final.zip
Con nueve extensiones, empaquetar manualmente deja de ser seguro muy rápido.
Un buen pipeline debería:
- comprobar la versión;
- validar el manifest base;
- generar el manifest de la plataforma;
- construir el código;
- copiar los iconos correctos;
- comprobar que no haya código remoto;
- ejecutar las pruebas;
- generar artefactos separados;
- crear checksums;
- preparar el changelog y la información de tienda.
Ejemplo de resultado:
dist/
loudly/
chrome/loudly-1.4.0.zip
edge/loudly-1.4.0.zip
firefox/loudly-1.4.0.xpi
safari/Loudly.xcarchive
La versión debería proceder de una única fuente de verdad, pero los artefactos no tienen que ser idénticos.
También conviene comprobar automáticamente:
- archivos no permitidos,
- sourcemaps,
- secretos,
- permisos no utilizados,
- localizaciones ausentes,
- rutas incorrectas de iconos,
- diferencias de versión entre manifests.
La automatización no elimina la revisión de las tiendas. Elimina errores que nunca deberían llegar a ellas.
¿Qué navegador resultó más sencillo?
La respuesta depende de lo que se mida.
El inicio más sencillo: Chrome
La mayor cantidad de documentación, ejemplos y herramientas. Una buena opción para construir la versión base de MV3.
El port de código más sencillo: Edge
Si la versión de Chrome es correcta, las diferencias técnicas suelen ser pequeñas. Sigue existiendo el trabajo de publicación independiente.
La mejor prueba de independencia: Firefox
No siempre es el más sencillo, pero ofrece mucho valor. Descubre código ligado accidentalmente a Chromium y obliga a mantener orden en el build.
El mayor esfuerzo total: Safari
No solo por la API. Se añaden Xcode, la aplicación, la firma, App Store y las pruebas en dispositivos Apple. Los problemas de Loudly, TabZoo y LoopIt también demostraron que una interfaz que funciona no significa que la función funcione.
¿Qué haríamos de otra manera si empezáramos hoy?
1. Safari formaría parte del plan desde el primer sprint
No tendría que recibir todas las funciones de inmediato. Sin embargo, debería existir un esqueleto funcional del proyecto Xcode y pruebas regulares del flujo básico.
2. Firefox entraría antes en CI
No solo el build, sino también pruebas ejecutadas en un Firefox real.
3. Cada función tendría un propietario del estado
Antes de implementar, decidiríamos si el estado pertenece al content script, al background, al pop-up o al storage. Muchos errores de comunicación nacen de no tener una respuesta clara.
4. Los permisos se diseñarían junto con la función
Cada permiso nuevo debería tener:
- una justificación técnica,
- un texto para la tienda,
- un escenario de denegación,
- una prueba del comportamiento sin consentimiento.
5. La ficha viviría en el repositorio
La descripción, el resumen corto, las release notes, los términos de búsqueda, la política de privacidad y los gráficos deberían tener historial de cambios.
6. Mediríamos los datos desde la primera versión
La siguiente fase del informe debería incluir:
- tiempo desde el envío hasta la publicación,
- número de rechazos,
- motivo de cada rechazo,
- número de cambios específicos de plataforma,
- número de regresiones detectadas solo fuera de Chrome,
- tiempo necesario para preparar una actualización,
- tamaño del código compartido y del específico.
No reconstruimos hoy esas cifras de memoria porque queremos separar experiencia de estadística. En el futuro permitirán publicar informes aún más precisos.
Arquitectura recomendada para una extensión multiplataforma
src/
core/
domain/
storage/
validation/
platform/
api.ts
capabilities.ts
messaging.ts
background/
content/
popup/
options/
locales/
manifests/
chrome.json
edge.json
firefox.json
safari.json
store/
chrome/
edge/
firefox/
safari/
tests/
unit/
integration/
e2e/
manual/
Lo importante es la división de responsabilidades:
coreno conoce un navegador concreto;platformtraduce WebExtensions a una interfaz común;- las superficies de UI se comparten siempre que sea razonable;
- los manifests y los recursos de tienda son explícitamente específicos de plataforma;
- las pruebas manuales tienen escenarios escritos y no existen solo en la memoria del autor.
Veredicto
La lección más importante de nueve extensiones es sencilla:
WebExtensions permite mantener un producto común, pero no elimina la necesidad de crear cuatro versiones correctas.
Chrome proporciona el mejor punto de partida. Edge suele ser el port técnico más sencillo. Firefox es la prueba más valiosa de compatibilidad real. Safari exige tratar la extensión como parte de una aplicación Apple, no como otra variante ZIP.
Los mayores problemas no aparecieron donde faltaba un equivalente para un método de API. Aparecieron en la intersección de:
- el ciclo de vida del proceso,
- el estado,
- la página web,
- la interfaz,
- los permisos,
- el empaquetado,
- la revisión de la tienda.
Por eso un equipo eficaz no empieza preguntando: “¿Cómo copiamos la extensión de Chrome a otras tres tiendas?”. Empieza preguntando:
- ¿cuál es el núcleo común del producto?
- ¿qué capacidades dependen de la plataforma?
- ¿dónde se almacena el estado?
- ¿cómo confirmaremos el efecto real de una función?
- ¿cómo limitaremos los permisos?
- ¿cómo reproduciremos cada build?
- ¿cómo probaremos una actualización y no solo una instalación limpia?
Después de estas experiencias, seguimos considerando una única base de código como la estrategia correcta. Simplemente no fingimos que cuatro navegadores sean un único entorno.
Aceptar las diferencias es precisamente lo que permite compartir código sin reducir la calidad.
Lee a continuación
Fuentes
- POLPROG, página principal e información sobre el catálogo de extensiones
- POLPROG, perfil del editor en Firefox Add-ons
- Apple Developer, Safari Extensions
- Apple Developer, Meet Safari Web Extensions
- Chrome for Developers, Migrate to a service worker
- Chrome for Developers, Extension service worker lifecycle
- MDN, Chrome incompatibilities
- MDN, Differences between API implementations
- MDN,
browser_specific_settings - Chrome for Developers, Events in extension service workers
- Chrome for Developers, Testing MV3 service worker suspension
- Chrome for Developers, Chrome Web Store privacy fields and permissions
- Microsoft Learn, Port a Chrome extension to Microsoft Edge
- Microsoft Learn, Publish a Microsoft Edge extension
- MDN, Build a cross-browser extension
- Mozilla Extension Workshop, Submitting an add-on
- Mozilla Extension Workshop, Signing and distribution overview
- Mozilla Extension Workshop, Add-on Policies
- Apple Developer, App Review Guidelines, Safari extensions
- Mozilla, Approach to Manifest V3
- Pantelaios y Kapravelos, Manifest V3 Unveiled
- Polčák et al., Developers Insight on Manifest V3

