Marco controlado

Demián Renzulli
Demián Renzulli
Simon Hangl
Simon Hangl

Por lo general, el elemento <iframe> se usa para incorporar recursos externos en un contexto de navegación. Los iframes aplican las políticas de seguridad de la Web aislando el contenido incorporado de origen cruzado de la página host y viceversa. Si bien este enfoque mejora la seguridad, ya que garantiza un límite seguro entre los orígenes, limita algunos casos de uso. Por ejemplo, es posible que los usuarios deban cargar y administrar contenido de forma dinámica desde diferentes fuentes, como un profesor que activa un evento de navegación para mostrar una página web en la pantalla de un aula. Sin embargo, muchos sitios web bloquean explícitamente la incorporación en iframes con encabezados de seguridad como X-Frame-Options y la Política de Seguridad del Contenido (CSP). Además, las restricciones de iframe impiden que las páginas incorporadas administren directamente la navegación o el comportamiento del contenido incorporado.

La API de Controlled Frame aborda esta limitación, ya que permite cargar cualquier contenido web, incluso si aplica políticas de incorporación restrictivas. Esta API está disponible exclusivamente en las aplicaciones web aisladas (IWA), que incorporan medidas de seguridad adicionales para proteger a los usuarios y desarrolladores de posibles riesgos.

Implementa Controlled Frames

Antes de usar un Controlled Frame, deberás configurar una IWA funcional. Luego, puedes integrar los Controlled Frames en tus páginas.

Agregar política de permisos

Para usar Controlled Frames, habilita el permiso correspondiente agregando un campo permissions_policy con el valor "controlled-frame" a tu manifiesto de la IWA. Además, incluye la clave cross-origin-isolated. Esta clave no es específica de los Controlled Frames, pero se requiere para todas las IWA y determina si el documento puede acceder a las APIs que requieren aislamiento de origen cruzado.

{
   ...
  "permissions_policy": {
     ...
     "controlled-frame": ["self"],
     "cross-origin-isolated": ["self"]
     ...
  }
   ...
}

La clave controlled-frame en un manifiesto de app web aislada (IWA) define una lista de entidades permitidas de la política de permisos, en la que se especifican los orígenes que pueden usar Controlled Frames. Si bien el manifiesto admite la sintaxis completa de la Permissions Policy, lo que permite valores como *, orígenes específicos o palabras clave como self y src, es fundamental tener en cuenta que las APIs específicas de las IWA no se pueden delegar a otros orígenes. Incluso si la lista de entidades permitidas incluye comodines o orígenes externos, estos permisos no se aplicarán a las funciones de las IWA, como controlled-frame. A diferencia de las apps web estándar, las IWA establecen de forma predeterminada todas las funciones controladas por políticas como ninguna, lo que requiere declaraciones explícitas. En el caso de las funciones específicas de las IWA, esto significa que solo los valores como self (el origen propio de la IWA) o src (el origen de un iframe integrado) son efectivos funcionalmente.

Agrega un elemento de marco controlado

Inserta un elemento <controlledframe> en tu código HTML para incorporar contenido de terceros en tu AWA.

<controlledframe id="controlledframe_1" src="https://example.com">
</controlledframe>

El atributo opcional partition configura la partición del almacenamiento para el contenido incorporado, lo que te permite aislar datos como las cookies y el almacenamiento local para conservar los datos entre sesiones.

Ejemplo: Partición de almacenamiento en memoria

Crea un Controlled Frame con una partición de almacenamiento en memoria llamada "session1". Los datos almacenados en esta partición (por ejemplo, cookies y localStorage) se borrarán cuando se destruya el iframe o finalice la sesión de la aplicación.

<controlledframe id="controlledframe_1" src="https://example.com">
</controlledframe>

Ejemplo: Partición de almacenamiento persistente

Crea un Controlled Frame con una partición de almacenamiento persistente llamada "user_data". El prefijo "persist:" garantiza que los datos almacenados en esta partición se guarden en el disco y estén disponibles en todas las sesiones de la aplicación.

<controlledframe id="frame_2" src="..." partition="persist:user_data">
</controlledframe>

Obtén la referencia del elemento

Obtén una referencia al elemento <controlledframe> para que puedas interactuar con él como con cualquier elemento HTML estándar:

const controlledframe = document.getElementById('controlledframe_1');

Situaciones y casos de uso frecuentes

Como regla general, elige la mejor tecnología para satisfacer tus necesidades y evita la complejidad innecesaria. En los últimos años, las apps web progresivas (AWP) cerraron la brecha con las apps nativas, lo que permitió crear experiencias web potentes. Si una aplicación web necesita incorporar contenido de terceros, se recomienda explorar primero el enfoque <iframe> habitual. Si los requisitos superan las capacidades de los iframes, los Controlled Frames en las IWA pueden ser la mejor alternativa. En las siguientes secciones, se describen los casos de uso comunes.

Cómo incorporar contenido web de terceros

Muchas aplicaciones necesitan la capacidad de cargar y mostrar contenido de terceros dentro de su interfaz de usuario. Sin embargo, cuando hay varios propietarios de apps web involucrados (una situación común con las aplicaciones integradas), se dificulta establecer políticas coherentes de extremo a extremo. Por ejemplo, la configuración de seguridad puede impedir que un <iframe> tradicional incorpore ciertos tipos de contenido, incluso cuando las empresas tienen una necesidad legítima de hacerlo. A diferencia de los elementos <iframe>, los Controlled Frames están diseñados para omitir estas restricciones, lo que permite que las aplicaciones carguen y muestren contenido incluso si se prohíbe explícitamente la incorporación estándar.

Casos de uso

  • Presentaciones en el aula: Un profesor usa una pantalla táctil en el aula para cambiar entre recursos educativos que normalmente bloquearían la incorporación de iframe.
  • Señalización digital en tiendas minoristas o centros comerciales: Un kiosco de un centro comercial muestra sitios web de diferentes tiendas. Los Controlled Frames garantizan que estas páginas se carguen correctamente, incluso si restringen la incorporación.

Muestras de código

Las siguientes APIs de Controlled Frame son útiles para administrar contenido integrado.

Navegación: Los Controlled Frames proporcionan varios métodos para administrar y controlar de forma programática la navegación y el historial de navegación del contenido integrado.

El atributo src obtiene o establece la URL del contenido que se muestra en el marco, y funciona de la misma manera que el atributo HTML.

controlledframe.src = "https://example.com";

El método back() navega un paso hacia atrás en el historial del iframe. La promesa devuelta se resuelve en un valor booleano que indica si la navegación se realizó correctamente.

document.getElementById('backBtn').addEventListener('click', () => {
controlledframe.back().then((success) => {
console.log(`Back navigation ${success ? 'succeeded' : 'failed'}`); }).catch((error) => {
   console.error('Error during back navigation:', error);
   });
});

El método forward() navega un paso hacia adelante en el historial del fotograma. La promesa devuelta se resuelve en un valor booleano que indica si la navegación se realizó correctamente.

document.getElementById('forwardBtn').addEventListener('click', () => {
controlledframe.forward().then((success) => {
   console.log(`Forward navigation ${success ? 'succeeded' : 'failed'}`);
}).catch((error) => {
    console.error('Error during forward navigation:', error);
  });
});

El método reload() vuelve a cargar la página actual en el iframe.

document.getElementById('reloadBtn').addEventListener('click', () => {
   controlledframe.reload();
});

Además, los Controlled Frames proporcionan eventos que te permiten hacer un seguimiento del ciclo de vida completo de las solicitudes de navegación, desde el inicio y los redireccionamientos hasta la carga, la finalización o la cancelación del contenido.

  • loadstart: Se activa cuando comienza una navegación en el fotograma.
  • loadcommit: Se activa cuando se procesó la solicitud de navegación y comienza a cargarse el contenido del documento principal.
  • contentload: Se activa cuando se completó la carga del documento principal y sus recursos esenciales (similar a DOMContentLoaded).
  • loadstop: Se activa cuando se terminan de cargar todos los recursos de la página (incluidos los submarcos y las imágenes).
  • loadabort: Se activa si se aborta una navegación (por ejemplo, por la acción del usuario o el inicio de otra navegación).
  • loadredirect: Se activa cuando se produce un redireccionamiento del servidor durante la navegación.
controlledframe.addEventListener('loadstart', (event) => {
   console.log('Navigation started:', event.url);
   // Example: Show loading indicator
 });
controlledframe.addEventListener('loadcommit', (event) => {
   console.log('Navigation committed:', event.url);
 });
controlledframe.addEventListener('contentload', (event) => {
   console.log('Content loaded for:', controlledframe.src);
   // Example: Hide loading indicator, maybe run initial script
 });
controlledframe.addEventListener('loadstop', (event) => {
   console.log('All resources loaded for:', controlledframe.src);
 });
controlledframe.addEventListener('loadabort', (event) => {
   console.warn(`Navigation aborted: ${event.url}, Reason: ${event.detail.reason}`);
 });
controlledframe.addEventListener('loadredirect', (event) => {
   console.log(`Redirect detected: ${event.oldUrl} -> ${event.newUrl}`);
});

También puedes supervisar y, potencialmente, interceptar interacciones o solicitudes específicas iniciadas por el contenido cargado dentro del iframe controlado, como intentos de abrir diálogos, solicitar permisos o abrir ventanas nuevas.

  • dialog: Se activa cuando el contenido incorporado intenta abrir un diálogo (alerta, confirmación, mensaje). Recibes detalles y puedes responder.
  • consolemessage: Se activa cuando se registra un mensaje en la consola dentro del iframe.
  • permissionrequest: Se activa cuando el contenido incorporado solicita un permiso (por ejemplo, ubicación geográfica y notificaciones). Recibirás detalles y podrás permitir o rechazar la solicitud.
  • newwindow: Se activa cuando el contenido incorporado intenta abrir una nueva ventana o pestaña (por ejemplo, con window.open o un vínculo con target="_blank"). Recibes detalles y puedes controlar o bloquear la acción.
controlledframe.addEventListener('dialog', (event) => {
   console.log(Dialog opened: Type=${event.messageType}, Message=${event.messageText});
   // You will need to respond, e.g., event.dialog.ok() or .cancel()
 });

controlledframe.addEventListener('consolemessage', (event) => {
   console.log(Frame Console [${event.level}]: ${event.message});
 });

controlledframe.addEventListener('permissionrequest', (event) => {
   console.log(Permission requested: Type=${event.permission});
   // You must respond, e.g., event.request.allow() or .deny()
   console.warn('Permission request needs handling - Denying by default');
   if (event.request && event.request.deny) {
     event.request.deny();
   }
});

controlledframe.addEventListener('newwindow', (event) => {
   console.log(New window requested: URL=${event.targetUrl}, Name=${event.name});
   // Decide how to handle this, e.g., open in a new controlled frame and call event.window.attach(), ignore, or block
   console.warn('New window request needs handling - Blocking by default');
 });

También hay eventos de cambio de estado que te notifican sobre los cambios relacionados con el estado de renderización del propio fotograma controlado, como las modificaciones en sus dimensiones o el nivel de zoom.

  • sizechanged: Se activa cuando cambian las dimensiones del contenido del frame.
  • zoomchange: Se activa cuando cambia el nivel de zoom del contenido del fotograma.
controlledframe.addEventListener('sizechanged', (event) => {
  console.log(Frame size changed: Width=${event.width}, Height=${event.height});
});

controlledframe.addEventListener('zoomchange', (event) => {
  console.log(Frame zoom changed: Factor=${event.newZoomFactor});
});

Métodos de almacenamiento: Los Controlled Frames ofrecen APIs para administrar los datos almacenados dentro de la partición de un frame.

Usa clearData() para quitar todos los datos almacenados, lo que es especialmente útil para restablecer el fotograma después de una sesión del usuario o garantizar un estado limpio. El método devuelve una promesa que se resuelve cuando se completa la operación. También se pueden proporcionar opciones de configuración opcionales:

  • types: Es un array de cadenas que especifica qué tipos de datos borrar (por ejemplo, ['cookies', 'localStorage', 'indexedDB']). Si se omite, se suelen borrar todos los tipos de datos aplicables.
  • options: Controla el proceso de borrado, por ejemplo, especifica un período con una propiedad since (marca de tiempo en milisegundos desde la época) para borrar solo los datos creados después de ese momento.

Ejemplo: Borra todo el almacenamiento asociado con el fotograma controlado

function clearAllPartitionData() {
   console.log('Clearing all data for partition:', controlledframe.partition);
   controlledframe.clearData()
     .then(() => {
       console.log('Partition data cleared successfully.');
     })
     .catch((error) => {
       console.error('Error clearing partition data:', error);
     });
}

Ejemplo: Borra solo las cookies y el almacenamiento local creados en la última hora

function clearRecentCookiesAndStorage() {
   const oneHourAgo = Date.now() - (60 * 60 * 1000);
   const dataTypesArray = ['cookies', 'localStorage'];
   const dataTypesToClearObject = {};
   for (const type of dataTypesArray) {
      dataTypesToClearObject[type] = true;
   }
   const clearOptions = { since: oneHourAgo };
   console.log(`Clearing ${dataTypesArray.join(', ')} since ${new    Date(oneHourAgo).toISOString()}`); controlledframe.clearData(clearOptions, dataTypesToClearObject) .then(() => {
   console.log('Specified partition data cleared successfully.');
}).catch((error) => {
   console.error('Error clearing specified partition data:', error);
});
}

Extender o alterar aplicaciones de terceros

Más allá de la simple incorporación, los Controlled Frames ofrecen mecanismos para que la IWA incorporada ejerza control sobre el contenido web de terceros incorporado. Puedes ejecutar secuencias de comandos dentro del contenido incorporado, interceptar solicitudes de red y anular los menús contextuales predeterminados, todo en un entorno seguro y aislado.

Casos de uso

  • Aplicación de la marca en sitios de terceros: Inserta CSS y JavaScript personalizados en sitios web incorporados para aplicar un tema visual unificado.
  • Restringir la navegación y el comportamiento de los vínculos: Intercepta o inhabilita ciertos comportamientos de la etiqueta <a> con la inserción de secuencias de comandos.
  • Automatiza la recuperación después de fallas o inactividad: Supervisa el contenido integrado para detectar estados de falla (por ejemplo, pantalla en blanco, errores de secuencia de comandos) y vuelve a cargar o restablecer la sesión de forma programática después de un tiempo de espera.

Muestras de código

Inyección de secuencias de comandos: Usa executeScript() para inyectar JavaScript en el marco controlado, lo que te permite personalizar el comportamiento, agregar superposiciones o extraer datos de páginas de terceros incorporadas. Puedes proporcionar código intercalado como una cadena o hacer referencia a uno o más archivos de secuencias de comandos (con rutas relativas dentro del paquete de la IWA). El método devuelve una promesa que se resuelve en el resultado de la ejecución de la secuencia de comandos, por lo general, el valor de la última instrucción.

document.getElementById('scriptBtn').addEventListener('click', () => {
   controlledframe.executeScript({
      code: `document.body.style.backgroundColor = 'lightblue';
             document.querySelectorAll('a').forEach(link =>    link.style.pointerEvents = 'none');
             document.title; // Return a value
            `,
      // You can also inject files:
      // files: ['./injected_script.js'],
}) .then((result) => {
   // The result of the last statement in the script is usually returned.
   console.log('Script execution successful. Result (e.g., page title):', result); }).catch((error) => {
   console.error('Script execution failed:', error);
   });
});

Inyección de estilos: Usa insertCSS() para aplicar estilos personalizados a las páginas cargadas dentro de un Controlled Frame.

document.getElementById('cssBtn').addEventListener('click', () => {
  controlledframe.insertCSS({
    code: `body { font-family: monospace; }`
    // You can also inject files:
    // files: ['./injected_styles.css']
  })
  .then(() => {
    console.log('CSS injection successful.');
  })
  .catch((error) => {
    console.error('CSS injection failed:', error);
  });
});

Interceptación de solicitudes de red: Usa la API de WebRequest para observar y, potencialmente, modificar las solicitudes de red de la página incorporada, como bloquear solicitudes, alterar encabezados o registrar el uso.

// Get the request object
const webRequest = controlledframe.request;

// Create an interceptor for a specific URL pattern
const interceptor = webRequest.createWebRequestInterceptor({
  urlPatterns: ["*://evil.com/*"],
  blocking: true,
  includeHeaders: "all"
});

// Add a listener to block the request
interceptor.addEventListener("beforerequest", (event) => {
  console.log('Blocking request to:', event.url);
  event.preventDefault();
});

// Add a listener to modify request headers
interceptor.addEventListener("beforesendheaders", (event) => {
  console.log('Modifying headers for:', event.url);
  const newHeaders = new Headers(event.headers);
  newHeaders.append('X-Custom-Header', 'MyValue');
  event.setRequestHeaders(newHeaders);
});

Cómo agregar menús contextuales personalizados: Usa la API de contextMenus para agregar, quitar y controlar menús personalizados que aparecen al hacer clic con el botón derecho en el iframe incorporado. En este ejemplo, se muestra cómo agregar un menú personalizado "Copiar selección" dentro de un Controlled Frame. Cuando se selecciona texto y el usuario hace clic con el botón derecho, aparece el menú. Si haces clic en él, se copia el texto seleccionado en el portapapeles, lo que permite interacciones sencillas y fáciles de usar dentro del contenido incorporado.

const menuItemProperties = {
  id: "copy-selection",
  title: "Copy selection",
  contexts: ["selection"],
  documentURLPatterns: [new URLPattern({ hostname: '*.example.com'})]
};

// Create the context menu item using a promise
try {
  await controlledframe.contextMenus.create(menuItemProperties);
  console.log(`Context menu item "${menuItemProperties.id}" created successfully.`);
} catch (error) {
  console.error(`Failed to create context menu item:`, error);
}

// Add a standard event listener for the 'click' event
controlledframe.contextMenus.addEventListener('click', (event) => {
    if (event.menuItemId === "copy-selection" && event.selectionText) {
        navigator.clipboard.writeText(event.selectionText)
          .then(() => console.log("Text copied to clipboard."))
          .catch(err => console.error("Failed to copy text:", err));
    }
});

Demostración

Consulta la demostración de Controlled Frame para obtener una descripción general de los métodos de Controlled Frames.

La demostración de Controlled Frame

Como alternativa, IWA Kitchen Sink incluye una app con varias pestañas, cada una de las cuales demuestra una API de IWA diferente, como Controlled Frames, Direct Sockets y muchas más.

IWA Kitchen Sink

Conclusión

Los Controlled Frames ofrecen una forma potente y segura de incorporar, extender y manipular contenido web de terceros en las apps web aisladas (IWA). Al superar las limitaciones de los iframes, habilitan nuevas capacidades, como la ejecución de secuencias de comandos dentro del contenido incorporado, la interceptación de solicitudes de red y la implementación de menús contextuales personalizados, todo ello manteniendo límites de aislamiento estrictos. Sin embargo, debido a que estas APIs ofrecen un control profundo sobre el contenido incorporado, también incluyen restricciones de seguridad adicionales y solo están disponibles en las IWA, que están diseñadas para aplicar garantías más sólidas tanto para los usuarios como para los desarrolladores. En la mayoría de los casos de uso, los desarrolladores primero deben considerar usar elementos <iframe> estándar, que son más simples y suficientes en muchas situaciones. Los Controlled Frames se deben evaluar cuando las soluciones basadas en iframe están bloqueadas por restricciones de incorporación o no tienen las capacidades de control e interacción necesarias. Ya sea que estés creando experiencias de quiosco, integrando herramientas de terceros o diseñando sistemas de complementos modulares, los Controlled Frames permiten un control detallado en un entorno estructurado, con permisos y seguro, lo que los convierte en una herramienta fundamental en la próxima generación de aplicaciones web avanzadas.

Más recursos