JavaScript Fensterübergreifende Kommunikation
Fensterübergreifende Kommunikation in JavaScript: postMessage, Same-Origin-Policy, Fensterreferenzen, localStorage-Events und die Broadcast Channel API mit Beispielen.
Fensterübergreifende Kommunikation bezeichnet den Datenaustausch zwischen separaten Browser-Kontexten — einer übergeordneten Seite und einem geöffneten Popup, einer Seite und einem eingebetteten iframe oder zwei Tabs derselben Website. Der Browser isoliert diese Kontexte absichtlich aus Sicherheitsgründen, sodass sie nicht frei die Variablen oder das DOM des jeweils anderen lesen können. Stattdessen bietet JavaScript eine kleine Reihe klar definierter Kanäle, um Nachrichten zwischen ihnen auszutauschen.
Dieses Kapitel behandelt, wann fensterübergreifende Kommunikation erforderlich ist, die Same-Origin-Policy, die sie regelt, sowie vier praktische Methoden: postMessage(), direkte Fensterreferenzen, Storage-Events und die Broadcast Channel API. Es baut auf window.open() und Popups auf; wer noch keine Erfahrung mit dem Browser-Modell hat, sollte mit der Übersicht der Browserumgebung beginnen.
Fensterübergreifende Kommunikation verstehen
Ein Browsing-Kontext ist alles, was ein eigenes window-Objekt besitzt: ein Tab, ein Popup oder ein iframe. Zwei Kontexte können nur über eine kontrollierte API miteinander kommunizieren, und wie viel sie tun dürfen, hängt von ihrem Origin ab — der Kombination aus Protokoll, Host und Port (zum Beispiel https://www.w3docs.com:443).
Die Same-Origin-Policy
Die Same-Origin-Policy (SOP) ist die Regel, die bestimmt, was ein Kontext mit einem anderen tun darf:
- Gleicher Origin (identisches Protokoll, Host und Port): Kontexte können das DOM des jeweils anderen direkt lesen und
postMessage()uneingeschränkt nutzen. - Fremder Origin (irgendein Teil weicht ab): Direkter DOM-Zugriff ist gesperrt. Der einzige erlaubte Kanal ist
postMessage(), den der Empfänger validieren muss.
Deshalb ist postMessage() fast überall die empfohlene Vorgehensweise: Es funktioniert gleich, egal ob die Fenster denselben Origin haben oder nicht, und es zwingt dazu, explizit anzugeben, wem man vertraut.
Wann wird es benötigt?
- Popup-Fenster. Ein mit
window.open()geöffnetes Fenster muss häufig Ergebnisse an die aufrufende Seite zurücksenden (ein OAuth-Login-Popup, ein Dateiauswahl-Dialog). - Iframes. Eingebettete Widgets — Zahlungsformulare, Karten, Drittanbieter-Player — tauschen Daten mit der Host-Seite aus.
- Tabs und andere Kontexte. Zwei Tabs derselben App müssen möglicherweise synchron bleiben (ein Logout in einem Tab sollte alle anderen ausloggen).
Methoden der fensterübergreifenden Kommunikation
window.postMessage() verwenden
Die Methode window.postMessage() ist der sicherste und portabelste Weg, Daten zwischen Fenstern oder Frames zu senden — sie funktioniert sowohl für gleiche als auch für fremde Origins. Der Sender ruft targetWindow.postMessage(data, targetOrigin) auf, und der Empfänger lauscht auf ein message-Event.
Zwei Argumente sind für die Sicherheit entscheidend:
targetOrigin(das zweite Argument vonpostMessage) schränkt ein, wer die Nachricht empfangen darf. Übergebe den genauen erwarteten Origin ('https://example.com'); verwende den Platzhalter'*'nur, wenn die Daten nicht sensibel sind, da jedes Fenster an diesem Ziel sie dann lesen kann.event.origin(auf der Empfängerseite) gibt an, wer die Nachricht gesendet hat. Prüfe ihn immer, bevor duevent.datavertraust — so lehnst du Nachrichten von nicht vertrauenswürdigen Seiten ab.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Cross-Window Communication</title>
<style>
#childIframe, #childPopup {
width: 100%;
height: 200px;
border: 1px solid black;
margin-top: 20px;
}
</style>
</head>
<body>
<h1>Cross-Window Communication Examples</h1>
<!-- Button to Open Popup -->
<button id="openPopup">Open Popup</button>
<div id="parentPopupDisplay"></div>
<!-- Iframe -->
<iframe id="childIframe" srcdoc="
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>Child Iframe</title>
</head>
<body>
<div id='childIframeDisplay'></div>
<script>
window.addEventListener('message', (event) => {
// Note: For cross-origin contexts, replace window.location.origin with the hardcoded parent origin.
if (event.origin !== window.location.origin) return;
document.getElementById('childIframeDisplay').innerText = 'Message from parent: ' + event.data;
event.source.postMessage('Hello, Parent Window!', event.origin);
});
</script>
</body>
</html>
"></iframe>
<div id="iframeDisplay"></div>
<!-- Scripts for Parent Window -->
<script>
// Handle Popup Communication
document.getElementById('openPopup').addEventListener('click', () => {
const popup = window.open('', 'popupWindow', 'width=600,height=400');
popup.document.write(`
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>Popup Window</title>
</head>
<body>
<div id='popupDisplay'></div>
<script>
window.addEventListener('message', (event) => {
// Note: For cross-origin contexts, replace window.location.origin with the hardcoded parent origin.
if (event.origin !== window.location.origin) return;
document.getElementById('popupDisplay').innerText = 'Message from parent: ' + event.data;
event.source.postMessage('Hello, Parent Window!', event.origin);
});
<\/script>
</body>
</html>
`);
setTimeout(() => {
// For cross-origin, replace '*' with the exact target origin (e.g., 'https://example.com')
popup.postMessage('Hello from parent!', '*');
}, 1000);
});
// Handle Iframe Communication
const iframe = document.getElementById('childIframe');
iframe.onload = () => {
iframe.contentWindow.postMessage('Hello from parent window!', '*');
};
window.addEventListener('message', (event) => {
if (event.origin !== window.location.origin) return;
if (event.source === iframe.contentWindow) {
document.getElementById('iframeDisplay').innerText = 'Message from iframe: ' + event.data;
} else {
document.getElementById('parentPopupDisplay').innerText = 'Message from popup: ' + event.data;
}
});
</script>
</body>
</html>In diesem kombinierten Beispiel öffnet das übergeordnete Fenster ein Popup und bettet einen iframe ein. Sowohl das Popup als auch der iframe können über postMessage() mit dem übergeordneten Fenster kommunizieren. Nachrichten werden in den jeweiligen div-Elementen angezeigt.
Obwohl document.write() für einfache Demos funktioniert, empfehlen moderne Best Practices die Verwendung von DOMParser oder Blob-URLs, um Inhalte sicher in Popups einzufügen.
Auf Fensterreferenzen zugreifen
Wenn du ein neues Fenster mit window.open() öffnest, ist der Rückgabewert eine Referenz auf dieses Fenster. Das geöffnete Fenster kann über window.opener auf seinen Öffner zurückgreifen, und ein übergeordnetes Fenster kann einen iframe über iframe.contentWindow erreichen. Diese direkten Referenzen funktionieren nur, wenn beide Kontexte denselben Origin haben — andernfalls wirft die SOP einen Sicherheitsfehler. Nutze sie für eng gekoppelte, gleich-ursprüngliche Seiten; greife auf postMessage() zurück, wenn eine Origin-Grenze involviert ist.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Direct Manipulation Example</title>
<style>
#childIframe {
width: 100%;
height: 200px;
border: 1px solid black;
margin-top: 20px;
}
</style>
</head>
<body>
<h1>Direct Manipulation Example</h1>
<!-- Button to Open Popup -->
<button id="openChild">Open Child Window</button>
<div id="parentChildDisplay"></div>
<!-- Iframe -->
<iframe id="childIframe" srcdoc="
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>Child Iframe</title>
</head>
<body>
<div id='childIframeContent'>Initial Content</div>
</body>
</html>
"></iframe>
<!-- Scripts for Parent Window -->
<script>
document.getElementById('openChild').addEventListener('click', () => {
const childWindow = window.open('', 'childWindow', 'width=600,height=400');
childWindow.document.write(`
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8' />
<title>Child Window</title>
</head>
<body>
<div id='childContent'>Initial Content</div>
</body>
</html>
`);
// Ensure the content is updated after the window has fully loaded
setTimeout(() => {
childWindow.document.body.innerHTML += '<p>Message from parent window</p>';
}, 1000); // Adjust the timeout duration as necessary
});
const iframe = document.getElementById('childIframe');
iframe.onload = () => {
const iframeDoc = iframe.contentWindow.document;
iframeDoc.getElementById('childIframeContent').innerText += ' - Updated by Parent Window';
};
</script>
</body>
</html>In diesem Beispiel öffnet das übergeordnete Fenster ein untergeordnetes Fenster und ändert dessen Inhalt direkt, sobald es geladen wurde. Außerdem wird der Inhalt eines eingebetteten iframes aktualisiert.
Die direkte DOM-Manipulation über contentWindow.document oder window.opener ist durch die Same-Origin-Policy (SOP) für fremde Origins eingeschränkt. Für sichere und zuverlässige Kommunikation sollte immer postMessage() bevorzugt werden. Bei Popups mit gleichem Origin kann window.opener als Alternative genutzt werden, um direkt auf das übergeordnete Fenster zuzugreifen.
Bei der Verwendung von srcdoc wird der iframe-Inhalt asynchron geladen. Der onload-Handler stellt sicher, dass das DOM bereit ist, aber für komplexe Szenarien sollte die Kommunikation über ein DOMContentLoaded-Event ausgelöst werden, das innerhalb des iframes gesendet wird.
Local Storage und Session Storage verwenden
localStorage wird von jedem gleichursprünglichen Tab und Fenster gemeinsam genutzt, und das Schreiben in ihn löst ein storage-Event in allen anderen Kontexten aus. Das macht es zu einem einfachen Weg, eine Änderung zwischen Tabs zu übertragen, ohne direkte Fensterreferenzen. (sessionStorage ist tab-spezifisch und wird nicht weitergegeben, daher ist er für tab-übergreifende Nachrichten nicht geeignet.) Einen genaueren Blick auf die Storage-Objekte selbst findest du unter localStorage und sessionStorage.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Local Storage Example</title>
</head>
<body>
<h1>Local Storage Example</h1>
<button id="storeData">Store Data</button>
<button id="retrieveData">Retrieve Data</button>
<div id="storageDisplay"></div>
<script>
// Listen for changes triggered by other windows/tabs
window.addEventListener('storage', (event) => {
if (event.key === 'sharedData') {
document.getElementById('storageDisplay').innerText = 'Updated Data: ' + event.newValue;
}
});
document.getElementById('storeData').addEventListener('click', () => {
localStorage.setItem('sharedData', 'This is shared data');
});
document.getElementById('retrieveData').addEventListener('click', () => {
const data = localStorage.getItem('sharedData');
document.getElementById('storageDisplay').innerText = 'Stored Data: ' + data;
});
</script>
</body>
</html>In diesem Beispiel speichert das übergeordnete Fenster Daten in localStorage und ruft sie per Klick auf einen Button ab. Um die fensterübergreifende Synchronisation zu ermöglichen, wird ein storage-Event-Listener hinzugefügt. Beachte, dass das storage-Event nur in anderen Browsing-Kontexten ausgelöst wird, nicht in dem, der die Änderung veranlasst hat.
Broadcast Channel API
Die Broadcast Channel API ist das zweckmäßig entwickelte Werkzeug für das Messaging zwischen gleichursprünglichen Tabs, Fenstern und iframes. Jeder Kontext, der einen Kanal mit demselben Namen öffnet, empfängt jede Nachricht, die daran gesendet wird — ohne Fensterreferenzen und ohne storage-Event-Umwege. Origins können damit nicht überschritten werden; für Drittanbieter-iframes ist weiterhin postMessage() erforderlich.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Broadcast Channel Example</title>
</head>
<body>
<h1>Broadcast Channel Example</h1>
<button id="sendMessage">Send Message</button>
<div id="broadcastDisplay"></div>
<script>
const channel = new BroadcastChannel('example_channel');
channel.onmessage = (event) => {
document.getElementById('broadcastDisplay').innerText = 'Broadcast message received: ' + event.data;
};
document.getElementById('sendMessage').addEventListener('click', () => {
channel.postMessage('Hello from another context!');
});
</script>
</body>
</html>In diesem Beispiel wird ein Broadcast Channel erstellt und eine Nachricht gesendet, wenn der Button geklickt wird. Die Nachricht wird empfangen und in einem div-Element angezeigt.
So testest du dieses Beispiel korrekt:
- Klicke zweimal auf die Schaltfläche 'Try it Yourself', um die Beispielseite in zwei verschiedenen Tabs zu öffnen.
- Klicke dann auf den Button „Send Message" in einem der Tabs/Fenster.
- Die Nachricht sollte im anderen Tab/Fenster erscheinen.
Die BroadcastChannel API ist für die Kommunikation zwischen Tabs ausgelegt, sodass die Nachricht von einem Tab/Fenster an alle anderen gesendet wird, die zum gleichen Origin geöffnet sind (in diesem Fall dieselbe HTML-Datei).
Die richtige Methode wählen
| Methode | Fremder Origin? | Am besten geeignet für |
|---|---|---|
postMessage() | Ja | Der Standard. Popups und Drittanbieter-iframes, überall dort, wo eine Origin-Grenze existiert. |
| Direkte Fensterreferenzen | Nein (nur gleicher Origin) | Eng gekoppelte, gleichursprüngliche Popups/iframes, die vollständig kontrolliert werden. |
storage-Event | Nein (nur gleicher Origin) | Zustandsänderungen ohne zusätzliche API an andere Tabs übertragen. |
| Broadcast Channel | Nein (nur gleicher Origin) | Sauberes Many-to-Many-Messaging zwischen gleichursprünglichen Tabs und Frames. |
Im Zweifelsfall postMessage() verwenden — es ist die einzige Methode, die über Origins hinweg funktioniert, und die einzige mit einem eingebauten Sicherheitsmodell.
Best Practices
Komplexe Daten als JSON serialisieren. postMessage() verwendet den Structured Clone Algorithm und kann Objekte direkt übergeben, aber explizites JSON hält den Vertrag klar und funktioniert mit storage-Events (die nur Strings übertragen):
const message = { type: 'greeting', content: 'Hello, Child Window!' };
// JSON.stringify produces: {"type":"greeting","content":"Hello, Child Window!"}
childWindow.postMessage(JSON.stringify(message), '*');Geschlossene oder nicht zugängliche Ziele behandeln. Ein Popup kann vom Benutzer geschlossen werden, und ein fremdursprüngliches Fenster wirft einen Fehler, wenn es direkt angesprochen wird. Schütze deine Aufrufe:
if (childWindow && !childWindow.closed) {
try {
childWindow.postMessage('Hello, Child Window!', '*');
} catch (e) {
console.error('Failed to send message:', e);
}
}Den Absender immer validieren. Prüfe auf der Empfängerseite event.origin gegen eine Erlaubnisliste, bevor du auf event.data reagierst, und führe niemals eval() auf einer eingehenden Nachricht aus.
Fazit
Fensterübergreifende Kommunikation in JavaScript ist eine leistungsstarke Funktion, die bei korrekter Anwendung die Interaktivität und Benutzererfahrung von Webanwendungen erheblich verbessern kann. Durch den Einsatz von Methoden wie window.postMessage(), Local Storage und der Broadcast Channel API können Entwickler den Datenaustausch zwischen verschiedenen Fenstern, Tabs und Frames effizient verwalten. Befolge Best Practices, um eine sichere und robuste Kommunikation zu gewährleisten, und nutze die bereitgestellten Beispiele, um diese Techniken in deine Projekte zu integrieren.