Kontrollierter Frame

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

Das Element <iframe> wird in der Regel verwendet, um externe Ressourcen in einen Browsing-Kontext einzubetten. iFrames setzen die Sicherheitsrichtlinien des Webs durch, indem sie eingebettete Inhalte mit unterschiedlichen Ursprüngen von der Hostseite isolieren und umgekehrt. Dieser Ansatz verbessert zwar die Sicherheit, da eine sichere Grenze zwischen Herkünften gewährleistet wird, schränkt aber einige Anwendungsfälle ein. Beispielsweise müssen Nutzer möglicherweise Inhalte aus verschiedenen Quellen dynamisch laden und verwalten, z. B. wenn ein Lehrer ein Navigationsereignis auslöst, um eine Webseite auf einem Bildschirm im Klassenzimmer anzuzeigen. Viele Websites blockieren jedoch das Einbetten in iFrames explizit mithilfe von Sicherheitsheadern wie X-Frame-Options und Content Security Policy (CSP). Außerdem verhindern iFrame-Einschränkungen, dass eingebettete Seiten die Navigation oder das Verhalten der eingebetteten Inhalte direkt verwalten.

Die Controlled Frame API behebt diese Einschränkung, indem sie das Laden beliebiger Webinhalte ermöglicht, auch wenn restriktive Einbettungsrichtlinien erzwungen werden. Diese API ist ausschließlich in Isolated Web Applications (IWAs) verfügbar, die zusätzliche Sicherheitsmaßnahmen enthalten, um Nutzer und Entwickler vor potenziellen Risiken zu schützen.

Controlled Frames implementieren

Bevor Sie einen Controlled Frame verwenden können, müssen Sie eine funktionale IWA einrichten. Anschließend können Sie Controlled Frames in Ihre Seiten einfügen.

Berechtigungsrichtlinie hinzufügen

Wenn Sie Controlled Frames verwenden möchten, aktivieren Sie die entsprechende Berechtigung, indem Sie Ihrem IWA-Manifest das Feld permissions_policy mit dem Wert "controlled-frame" hinzufügen. Außerdem muss der Schlüssel cross-origin-isolated enthalten sein. Dieser Schlüssel ist nicht spezifisch für Controlled Frames, sondern für alle IWAs erforderlich. Er bestimmt, ob das Dokument auf APIs zugreifen kann, für die eine ursprungsübergreifende Isolation erforderlich ist.

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

Mit dem Schlüssel controlled-frame im Manifest einer isolierten Web-App (IWA) wird eine Zulassungsliste für Berechtigungsrichtlinien definiert, in der angegeben wird, welche Ursprünge Controlled Frames verwenden dürfen. Das Manifest unterstützt zwar die vollständige Syntax der Berechtigungsrichtlinie, sodass Werte wie *, bestimmte Ursprünge oder Keywords wie self und src verwendet werden können. Es ist jedoch wichtig zu beachten, dass IWA-spezifische APIs nicht an andere Ursprünge delegiert werden können. Auch wenn die Zulassungsliste einen Platzhalter oder externe Quellen enthält, werden diese Berechtigungen nicht für IWA-Funktionen wie controlled-frame wirksam. Im Gegensatz zu Standard-Web-Apps sind bei IWAs alle richtlinienkonformen Funktionen standardmäßig deaktiviert. Sie müssen explizit deklariert werden. Für IWA-spezifische Funktionen bedeutet das, dass nur Werte wie self (der eigene Ursprung der IWA) oder src (der Ursprung eines eingebetteten Frames) funktional wirksam sind.

Element „Kontrollierter Frame“ hinzufügen

Fügen Sie ein <controlledframe>-Element in Ihren HTML-Code ein, um Drittanbieterinhalte in Ihre IWA einzubetten.

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

Mit dem optionalen Attribut partition wird die Speicherpartitionierung für eingebettete Inhalte konfiguriert. So können Sie Daten wie Cookies und den lokalen Speicher isolieren, um Daten sitzungsübergreifend beizubehalten.

Beispiel: In-Memory-Speicherpartition

Erstellen Sie einen Controlled Frame mit einer In-Memory-Speicherpartition mit dem Namen "session1". In dieser Partition gespeicherte Daten (z. B. Cookies und localStorage) werden gelöscht, wenn der Frame zerstört oder die Anwendungssitzung beendet wird.

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

Beispiel: Partition für nichtflüchtigen Speicher

Erstellen Sie einen kontrollierten Frame mit einer persistenten Speicherpartition mit dem Namen "user_data". Das Präfix "persist:" sorgt dafür, dass in dieser Partition gespeicherte Daten auf der Festplatte gespeichert werden und über Anwendungssitzungen hinweg verfügbar sind.

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

Elementreferenz abrufen

Rufen Sie eine Referenz auf das <controlledframe>-Element ab, damit Sie wie bei jedem Standard-HTML-Element damit interagieren können:

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

Häufige Szenarien und Anwendungsfälle

Wählen Sie in der Regel die beste Technologie aus, um Ihre Anforderungen zu erfüllen, und vermeiden Sie unnötige Komplexität. In den letzten Jahren haben progressive Web-Apps (PWAs) die Lücke zu nativen Apps geschlossen und ermöglichen leistungsstarke Weboberflächen. Wenn in eine Webanwendung Inhalte von Drittanbietern eingebettet werden müssen, empfiehlt es sich, zuerst den regulären <iframe>-Ansatz zu verwenden. Wenn die Anforderungen die Möglichkeiten von iFrames übersteigen, sind Controlled Frames in IWAs möglicherweise die beste Alternative. Gängige Anwendungsfälle werden in den folgenden Abschnitten beschrieben.

Webinhalte von Drittanbietern einbetten

Viele Anwendungen müssen in der Lage sein, Drittanbieterinhalte in ihrer Benutzeroberfläche zu laden und anzuzeigen. Wenn jedoch mehrere Inhaber von Webanwendungen beteiligt sind, was bei eingebetteten Anwendungen häufig der Fall ist, wird es schwierig, einheitliche End-to-End-Richtlinien festzulegen. Sicherheitseinstellungen können beispielsweise verhindern, dass in einem herkömmlichen <iframe> bestimmte Arten von Inhalten eingebettet werden, auch wenn Unternehmen ein berechtigtes Interesse daran haben. Im Gegensatz zu <iframe>-Elementen sind Controlled Frames so konzipiert, dass diese Einschränkungen umgangen werden. So können Anwendungen Inhalte laden und anzeigen, auch wenn das Standard-Embedding explizit untersagt ist.

Anwendungsfälle

  • Präsentationen im Unterricht: Eine Lehrkraft verwendet einen Touchscreen im Klassenzimmer, um zwischen Lernressourcen zu wechseln, die normalerweise das Einbetten von iFrames blockieren.
  • Digitale Beschilderung im Einzelhandel oder in Einkaufszentren: Ein Kiosk in einem Einkaufszentrum zeigt abwechselnd Websites verschiedener Geschäfte an. Mit Controlled Frames wird dafür gesorgt, dass diese Seiten auch dann korrekt geladen werden, wenn das Einbetten eingeschränkt ist.

Codebeispiele

Die folgenden Controlled Frame APIs sind hilfreich für die Verwaltung eingebetteter Inhalte.

Navigation: Controlled Frames bieten mehrere Methoden, um die Navigation und den Navigationsverlauf der eingebetteten Inhalte programmatisch zu verwalten und zu steuern.

Mit dem Attribut src wird die URL des im Frame angezeigten Inhalts abgerufen oder festgelegt. Es funktioniert genauso wie das HTML-Attribut.

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

Die Methode back() navigiert einen Schritt zurück im Verlauf des Frames. Das zurückgegebene Promise wird zu einem booleschen Wert aufgelöst, der angibt, ob die Navigation erfolgreich war.

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

Mit der Methode forward() wird im Verlauf des Frames ein Schritt vorwärts navigiert. Das zurückgegebene Promise wird zu einem booleschen Wert aufgelöst, der angibt, ob die Navigation erfolgreich war.

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

Die reload()-Methode lädt die aktuelle Seite im Frame neu.

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

Außerdem enthalten Controlled Frames Ereignisse, mit denen Sie den gesamten Lebenszyklus von Navigationsanfragen verfolgen können – von der Initiierung und Weiterleitungen bis zum Laden, Abschluss oder Abbruch von Inhalten.

  • loadstart: Wird ausgelöst, wenn eine Navigation im Frame beginnt.
  • loadcommit: Wird ausgelöst, wenn die Navigationsanfrage verarbeitet wurde und der Inhalt des Hauptdokuments geladen wird.
  • contentload: Wird ausgelöst, wenn das Hauptdokument und die zugehörigen wichtigen Ressourcen geladen wurden (ähnlich wie DOMContentLoaded).
  • loadstop: Wird ausgelöst, wenn alle Ressourcen für die Seite (einschließlich untergeordneter Frames und Bilder) geladen wurden.
  • loadabort: Wird ausgelöst, wenn eine Navigation abgebrochen wird, z. B. durch eine Nutzeraktion oder den Start einer anderen Navigation.
  • loadredirect: Wird ausgelöst, wenn während der Navigation eine serverseitige Weiterleitung erfolgt.
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}`);
});

Sie können auch bestimmte Interaktionen oder Anfragen überwachen und möglicherweise abfangen, die von den im kontrollierten Frame geladenen Inhalten initiiert werden, z. B. Versuche, Dialogfelder zu öffnen, Berechtigungen anzufordern oder neue Fenster zu öffnen.

  • dialog: Wird ausgelöst, wenn die eingebetteten Inhalte versuchen, ein Dialogfeld zu öffnen (alert, confirm, prompt). Sie erhalten Details und können antworten.
  • consolemessage: Wird ausgelöst, wenn eine Nachricht im Frame in der Konsole protokolliert wird.
  • permissionrequest: Wird ausgelöst, wenn die eingebetteten Inhalte eine Berechtigung anfordern (z. B. Standortbestimmung und Benachrichtigungen). Sie erhalten Details und können die Anfrage zulassen oder ablehnen.
  • newwindow: Wird ausgelöst, wenn der eingebettete Inhalt versucht, ein neues Fenster oder einen neuen Tab zu öffnen (z. B. mit window.open oder einem Link mit target="_blank"). Sie erhalten Details und können die Aktion verarbeiten oder blockieren.
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');
 });

Es gibt auch Ereignisse für Statusänderungen, die Sie über Änderungen am Rendering-Status des kontrollierten Frames informieren, z. B. Änderungen an seinen Abmessungen oder am Zoomfaktor.

  • sizechanged: Wird ausgelöst, wenn sich die Abmessungen des Frame-Inhalts ändern.
  • zoomchange: Wird ausgelöst, wenn sich die Zoomstufe des Inhalts des Frames ändert.
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});
});

Speichermethoden: Controlled Frames bieten APIs zum Verwalten von Daten, die in der Partition eines Frames gespeichert sind.

Verwenden Sie clearData(), um alle gespeicherten Daten zu entfernen. Das ist besonders nützlich, um den Rahmen nach einer Nutzersitzung zurückzusetzen oder einen sauberen Zustand zu gewährleisten. Die Methode gibt ein Promise zurück, das aufgelöst wird, wenn der Vorgang abgeschlossen ist. Optionale Konfigurationsoptionen können ebenfalls angegeben werden:

  • types: Ein Array von Strings, das angibt, welche Datentypen gelöscht werden sollen (z. B. ['cookies', 'localStorage', 'indexedDB']). Wenn dieser Parameter weggelassen wird, werden in der Regel alle anwendbaren Datentypen gelöscht.
  • options: Steuern Sie den Löschvorgang, z. B. durch Angabe eines Zeitbereichs mit der Eigenschaft „since“ (Zeitstempel in Millisekunden seit der Epoche), um nur Daten zu löschen, die nach diesem Zeitpunkt erstellt wurden.

Beispiel: Gesamten Speicherplatz löschen, der mit dem kontrollierten Frame verknüpft ist

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

Beispiel: Nur Cookies und localStorage löschen, die in der letzten Stunde erstellt wurden

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

Drittanbieteranwendungen erweitern oder ändern

Über das einfache Einbetten hinaus bieten Controlled Frames Mechanismen, mit denen die eingebettete IWA die eingebetteten Webinhalte von Drittanbietern steuern kann. Sie können Skripts im eingebetteten Inhalt ausführen, Netzwerkanfragen abfangen und Standardkontextmenüs überschreiben – alles in einer sicheren, isolierten Umgebung.

Anwendungsfälle

  • Branding auf Websites von Drittanbietern erzwingen: Sie können benutzerdefiniertes CSS und JavaScript in eingebettete Websites einfügen, um ein einheitliches visuelles Design zu erzwingen.
  • Navigations- und Linkverhalten einschränken: Bestimmte <a>-Tag-Verhaltensweisen mit Script-Injection abfangen oder deaktivieren.
  • Wiederherstellung nach Abstürzen oder Inaktivität automatisieren: Eingebettete Inhalte auf Fehlerzustände (z. B. leerer Bildschirm, Skriptfehler) überwachen und die Sitzung nach einem Zeitlimit programmatisch neu laden oder zurücksetzen.

Codebeispiele

Script-Injection: Mit executeScript() können Sie JavaScript in den kontrollierten Frame einfügen. So können Sie das Verhalten anpassen, Overlays hinzufügen oder Daten aus eingebetteten Drittanbieterseiten extrahieren. Sie können entweder Inline-Code als String angeben oder auf eine oder mehrere Skriptdateien verweisen (mit relativen Pfaden innerhalb des IWA-Pakets). Die Methode gibt ein Promise zurück, das mit dem Ergebnis der Ausführung des Skripts aufgelöst wird, in der Regel dem Wert der letzten Anweisung.

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

Stile einfügen: Mit insertCSS() können Sie benutzerdefinierte Stile auf Seiten anwenden, die in einem Controlled Frame geladen werden.

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

Abfangen von Netzwerkanfragen: Mit der WebRequest API können Sie Netzwerkanfragen von der eingebetteten Seite beobachten und möglicherweise ändern, z. B. Anfragen blockieren, Header ändern oder die Nutzung protokollieren.

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

Benutzerdefinierte Kontextmenüs hinzufügen: Mit der contextMenus API können Sie benutzerdefinierte Kontextmenüs im eingebetteten Frame hinzufügen, entfernen und verarbeiten. In diesem Beispiel wird gezeigt, wie Sie in einem Controlled Frame ein benutzerdefiniertes Menü „Auswahl kopieren“ hinzufügen. Wenn Text ausgewählt ist und der Nutzer mit der rechten Maustaste klickt, wird das Menü angezeigt. Durch Klicken wird der ausgewählte Text in die Zwischenablage kopiert, was eine einfache, nutzerfreundliche Interaktion mit eingebetteten Inhalten ermöglicht.

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

In der Controlled Frame-Demo finden Sie eine Übersicht über die Methoden von Controlled Frames.

Demo für Controlled Frame

Alternativ dazu bietet IWA Kitchen Sink eine App mit mehreren Tabs, auf denen jeweils eine andere IWA-API wie Controlled Frames oder Direct Sockets demonstriert wird.

IWA Kitchen Sink

Fazit

Controlled Frames bieten eine leistungsstarke und sichere Möglichkeit, Web-Inhalte von Drittanbietern in isolierte Web-Apps (IWAs) einzubetten, zu erweitern und mit ihnen zu interagieren. Durch die Überwindung der Einschränkungen von iFrames werden neue Funktionen ermöglicht, z. B. das Ausführen von Skripts in eingebetteten Inhalten, das Abfangen von Netzwerkanfragen und die Implementierung benutzerdefinierter Kontextmenüs – und das alles unter Beibehaltung strenger Isolationsgrenzen. Da diese APIs jedoch eine detaillierte Steuerung eingebetteter Inhalte ermöglichen, unterliegen sie zusätzlichen Sicherheitsbeschränkungen und sind nur in IWAs verfügbar, die entwickelt wurden, um sowohl für Nutzer als auch für Entwickler stärkere Garantien zu bieten. In den meisten Anwendungsfällen sollten Entwickler zuerst Standardelemente für <iframe> in Betracht ziehen, da diese einfacher sind und in vielen Szenarien ausreichen. Controlled Frames sollten in Betracht gezogen werden, wenn iframe-basierte Lösungen durch Einbettungsbeschränkungen blockiert werden oder die erforderlichen Steuerungs- und Interaktionsfunktionen fehlen. Ob Sie Kioskanwendungen entwickeln, Drittanbietertools einbinden oder modulare Plug-in-Systeme entwerfen – mit Controlled Frames können Sie die Steuerung in einer strukturierten, berechtigten und sicheren Umgebung optimieren. Sie sind daher ein wichtiges Tool für die nächste Generation fortschrittlicher Webanwendungen.

Weitere Ressourcen