Controlled Frame

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

L'elemento <iframe> viene in genere utilizzato per incorporare risorse esterne in un contesto di navigazione. Gli iframe applicano le norme di sicurezza del web isolando i contenuti incorporati multiorigine dalla pagina host e viceversa. Sebbene questo approccio migliori la sicurezza garantendo un confine sicuro tra le origini, limita alcuni casi d'uso. Ad esempio, gli utenti potrebbero dover caricare e gestire dinamicamente contenuti provenienti da fonti diverse, ad esempio un insegnante che attiva un evento di navigazione per visualizzare una pagina web sullo schermo di una classe. Tuttavia, molti siti web bloccano esplicitamente l'incorporamento negli iframe utilizzando intestazioni di sicurezza come X-Frame-Options e Content Security Policy (CSP). Inoltre, le limitazioni degli iframe impediscono alle pagine incorporate di gestire direttamente la navigazione o il comportamento dei contenuti incorporati.

L'API Controlled Frame risolve questa limitazione consentendo il caricamento di qualsiasi contenuto web, anche se applica policy di incorporamento restrittive. Questa API è disponibile esclusivamente all'interno delle applicazioni web isolate (IWA), che incorporano misure di sicurezza aggiuntive per proteggere utenti e sviluppatori da potenziali rischi.

Implementare Controlled Frame

Prima di utilizzare un frame controllato, devi configurare un'IWA funzionale. Puoi quindi integrare i frame controllati nelle tue pagine.

Aggiungi policy di autorizzazione

Per utilizzare i frame controllati, attiva l'autorizzazione corrispondente aggiungendo un campo permissions_policy con il valore "controlled-frame" al manifest dell'IWA. Inoltre, includi la chiave cross-origin-isolated. Questa chiave non è specifica per i frame controllati, ma è richiesta per tutte le IWA e determina se il documento può accedere alle API che richiedono l'isolamento cross-origin.

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

Il tasto controlled-frame nel manifest di un'app web isolata (IWA) definisce una lista consentita di policy delle autorizzazioni, che specifica quali origini possono utilizzare Controlled Frame. Sebbene il manifest supporti la sintassi completa dei criteri di autorizzazione, consentendo valori come *, origini specifiche o parole chiave come self e src, è fondamentale notare che le API specifiche delle app web installabili non possono essere delegate ad altre origini. Anche se la lista consentita include caratteri jolly o origini esterne, queste autorizzazioni non avranno effetto per le funzionalità IWA come controlled-frame. A differenza delle app web standard, le IWA impostano tutte le funzionalità controllate dalle norme su nessuna, richiedendo dichiarazioni esplicite. Per le funzionalità specifiche delle IWA, ciò significa che sono funzionalmente efficaci solo valori come self (l'origine della IWA) o src (l'origine di un frame incorporato).

Aggiungere un elemento Controlled Frame

Inserisci un elemento <controlledframe> nel codice HTML per incorporare contenuti di terze parti all'interno della tua app web integrata.

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

L'attributo facoltativo partition configura il partizionamento dell'archiviazione per i contenuti incorporati, consentendoti di isolare i dati come i cookie e l'archiviazione locale per conservarli tra le sessioni.

Esempio: partizione di archiviazione in memoria

Crea un frame controllato utilizzando una partizione di archiviazione in memoria denominata "session1". I dati memorizzati in questa partizione (ad esempio, cookie e localStorage) verranno cancellati quando il frame viene eliminato o la sessione dell'applicazione termina.

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

Esempio: partizione di archiviazione permanente

Crea un Controlled Frame utilizzando una partizione di archiviazione permanente denominata "user_data". Il prefisso "persist:" garantisce che i dati archiviati in questa partizione vengano salvati su disco e saranno disponibili in tutte le sessioni dell'applicazione.

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

Ottieni riferimento elemento

Ottieni un riferimento all'elemento <controlledframe> in modo da poter interagire con esso come qualsiasi elemento HTML standard:

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

Scenari e casi d'uso frequenti

Come regola generale, scegli la tecnologia migliore per soddisfare le tue esigenze evitando complessità inutili. Negli ultimi anni, le app web progressive (PWA) hanno colmato il divario con le app native, consentendo potenti esperienze web. Se un'applicazione web deve incorporare contenuti di terze parti, è consigliabile esplorare prima l'approccio <iframe> regolare. Se i requisiti superano le funzionalità degli iframe, i frame controllati nelle IWA potrebbero essere la migliore alternativa. I casi d'uso comuni sono descritti nelle sezioni seguenti.

Incorporamento di contenuti web di terze parti

Molte applicazioni devono essere in grado di caricare e visualizzare contenuti di terze parti all'interno della propria interfaccia utente. Tuttavia, quando sono coinvolti più proprietari di app web, uno scenario comune con le applicazioni incorporate, diventa difficile stabilire criteri end-to-end coerenti. Ad esempio, le impostazioni di sicurezza possono impedire a un <iframe> tradizionale di incorporare determinati tipi di contenuti, anche quando le attività hanno una necessità legittima di farlo. A differenza degli elementi <iframe>, i frame controllati sono progettati per aggirare queste limitazioni, consentendo alle applicazioni di caricare e visualizzare i contenuti anche se vietano esplicitamente l'incorporamento standard.

Casi d'uso

  • Presentazioni in classe: un insegnante utilizza un touchscreen in classe per passare da una risorsa didattica all'altra, che normalmente bloccherebbero l'incorporamento di iframe.
  • Segnaletica digitale in negozi o centri commerciali: un chiosco di un centro commerciale mostra a rotazione i siti web di diversi negozi. I frame controllati garantiscono che queste pagine vengano caricate correttamente anche se limitano l'incorporamento.

Esempi di codice

Le seguenti API Controlled Frame sono utili per gestire i contenuti incorporati.

Navigazione: i frame controllati forniscono più metodi per gestire e controllare a livello di programmazione la navigazione e la cronologia di navigazione dei contenuti incorporati.

L'attributo src recupera o imposta l'URL dei contenuti visualizzati nel frame, funzionando allo stesso modo dell'attributo HTML.

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

Il metodo back() torna indietro di un passaggio nella cronologia del frame. La promessa restituita si risolve in un valore booleano che indica se la navigazione è andata a buon fine.

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);
   });
});

Il metodo forward() consente di andare avanti di un passaggio nella cronologia del frame. La promessa restituita si risolve in un valore booleano che indica se la navigazione è andata a buon fine.

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);
  });
});

Il metodo reload() ricarica la pagina corrente nel frame.

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

Inoltre, i frame controllati forniscono eventi che consentono di monitorare l'intero ciclo di vita delle richieste di navigazione, dall'avvio e dai reindirizzamenti al caricamento, al completamento o all'interruzione dei contenuti.

  • loadstart: attivato quando inizia una navigazione nel frame.
  • loadcommit: Attivato quando la richiesta di navigazione è stata elaborata e inizia il caricamento dei contenuti del documento principale.
  • contentload: Attivato quando il documento principale e le relative risorse essenziali hanno terminato il caricamento (simile a DOMContentLoaded).
  • loadstop: si attiva quando tutte le risorse della pagina (inclusi i subframe e le immagini) sono state caricate.
  • loadabort: attivato se una navigazione viene interrotta (ad esempio, dall'azione dell'utente o dall'avvio di un'altra navigazione).
  • loadredirect: attivato quando si verifica un reindirizzamento lato server durante la navigazione.
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}`);
});

Puoi anche monitorare e potenzialmente intercettare interazioni o richieste specifiche avviate dai contenuti caricati all'interno del frame controllato, ad esempio tentativi di aprire finestre di dialogo, richiedere autorizzazioni o aprire nuove finestre.

  • dialog: attivato quando i contenuti incorporati tentano di aprire una finestra di dialogo (avviso, conferma, prompt). Ricevi i dettagli e puoi rispondere.
  • consolemessage: attivato quando un messaggio viene registrato nella console all'interno del frame.
  • permissionrequest: attivato quando i contenuti incorporati richiedono un'autorizzazione (ad esempio, geolocalizzazione e notifiche). Ricevi i dettagli e puoi accettare o rifiutare la richiesta.
  • newwindow: attivato quando i contenuti incorporati tentano di aprire una nuova finestra o scheda (ad esempio, con window.open o un link con target="_blank"). Ricevi i dettagli e puoi gestire o bloccare l'azione.
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');
 });

Esistono anche eventi di modifica dello stato che ti informano sulle modifiche relative allo stato di rendering del frame controllato, ad esempio modifiche alle dimensioni o al livello di zoom.

  • sizechanged: attivato quando cambiano le dimensioni dei contenuti del frame.
  • zoomchange: attivato quando cambia il livello di zoom dei contenuti del frame.
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});
});

Metodi di archiviazione: i frame controllati offrono API per la gestione dei dati archiviati all'interno della partizione di un frame.

Utilizza clearData() per rimuovere tutti i dati archiviati, il che è particolarmente utile per reimpostare il frame dopo una sessione utente o garantire uno stato pulito. Il metodo restituisce una promessa che viene risolta al termine dell'operazione. Possono essere fornite anche opzioni di configurazione facoltative:

  • types: un array di stringhe che specifica i tipi di dati da cancellare (ad esempio, ['cookies', 'localStorage', 'indexedDB']). Se omesso, in genere vengono cancellati tutti i tipi di dati applicabili.
  • options: controlla il processo di cancellazione, ad esempio specificando un intervallo di tempo utilizzando una proprietà since (timestamp in millisecondi dall'epoca) per cancellare solo i dati creati dopo quell'ora.

Esempio: cancellare tutto lo spazio di archiviazione associato al frame controllato

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);
     });
}

Esempio: cancellare solo i cookie e localStorage creati nell'ultima ora

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);
});
}

Estendere o modificare applicazioni di terze parti

Oltre al semplice incorporamento, i frame controllati offrono meccanismi per l'incorporamento di IWA per esercitare il controllo sui contenuti web di terze parti incorporati. Puoi eseguire script all'interno dei contenuti incorporati, intercettare le richieste di rete ed eseguire l'override dei menu contestuali predefiniti, il tutto in un ambiente sicuro e isolato.

Casi d'uso

  • Applicazione del branding su siti di terze parti: inserisci CSS e JavaScript personalizzati nei siti web incorporati per applicare un tema visivo unificato.
  • Limita la navigazione e il comportamento dei link: intercetta o disattiva determinati comportamenti dei tag <a> con l'inserimento di script.
  • Automatizza il recupero dopo arresti anomali o inattività: monitora i contenuti incorporati per rilevare stati di errore (ad esempio, schermo vuoto, errori di script) e ricarica o reimposta la sessione in modo programmatico dopo un timeout.

Esempi di codice

Inserimento di script: utilizza executeScript() per inserire JavaScript nel frame controllato, consentendoti di personalizzare il comportamento, aggiungere overlay o estrarre dati da pagine di terze parti incorporate. Puoi fornire il codice incorporato come stringa o fare riferimento a uno o più file di script (utilizzando percorsi relativi all'interno del pacchetto IWA). Il metodo restituisce una promessa che si risolve nel risultato dell'esecuzione dello script, in genere il valore dell'ultima istruzione.

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);
   });
});

Inserimento di stili: utilizza insertCSS() per applicare stili personalizzati alle pagine caricate all'interno di un frame controllato.

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);
  });
});

Intercettazione delle richieste di rete: utilizza l'API WebRequest per osservare e potenzialmente modificare le richieste di rete dalla pagina incorporata, ad esempio bloccando le richieste, modificando le intestazioni o registrando l'utilizzo.

// 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);
});

Aggiunta di menu contestuali personalizzati: utilizza l'API contextMenus per aggiungere, rimuovere e gestire menu personalizzati con il tasto destro del mouse all'interno del frame incorporato. Questo esempio mostra come aggiungere un menu personalizzato "Copia selezione" all'interno di un frame controllato. Quando viene selezionato del testo e l'utente fa clic con il tasto destro del mouse, viene visualizzato il menu. Se fai clic, il testo selezionato viene copiato negli appunti, consentendo interazioni semplici e intuitive all'interno dei contenuti incorporati.

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));
    }
});

Demo

Dai un'occhiata alla demo di Controlled Frame per una panoramica dei metodi di Controlled Frame.

Demo di Controlled Frame

In alternativa, IWA Kitchen Sink dispone di un'app con più schede, ognuna delle quali mostra un'API IWA diversa, ad esempio Controlled Frames, Direct Sockets e altro ancora.

IWA Kitchen Sink

Conclusione

I frame controllati offrono un modo efficace e sicuro per incorporare, estendere e interagire con contenuti web di terze parti nelle app web isolate (IWA). Superando i limiti degli iframe, consentono nuove funzionalità come l'esecuzione di script all'interno dei contenuti incorporati, l'intercettazione delle richieste di rete e l'implementazione di menu contestuali personalizzati, il tutto mantenendo limiti di isolamento rigorosi. Tuttavia, poiché queste API offrono un controllo approfondito sui contenuti incorporati, sono soggette a ulteriori vincoli di sicurezza e sono disponibili solo all'interno delle IWA, progettate per garantire garanzie più solide sia per gli utenti che per gli sviluppatori. Per la maggior parte dei casi d'uso, gli sviluppatori dovrebbero prima considerare l'utilizzo di elementi <iframe> standard, che sono più semplici e sufficienti in molti scenari. I frame controllati devono essere valutati quando le soluzioni basate su iframe sono bloccate dalle limitazioni all'incorporamento o non dispongono delle funzionalità di controllo e interazione necessarie. Che tu stia creando esperienze per chioschi, integrando strumenti di terze parti o progettando sistemi di plug-in modulari, i frame controllati consentono un controllo granulare in un ambiente strutturato, basato su autorizzazioni e sicuro, il che li rende uno strumento fondamentale nella prossima generazione di applicazioni web avanzate.

Altre risorse