Communication inter-fenêtres en JavaScript
Apprenez la communication inter-fenêtres en JavaScript : postMessage, la politique de même origine, les références de fenêtres, les événements localStorage et l'API Broadcast Channel.
La communication inter-fenêtres est l'échange de données entre des contextes de navigation distincts — une page parente et une popup qu'elle a ouverte, une page et une iframe intégrée, ou deux onglets du même site. Le navigateur isole délibérément ces contextes pour des raisons de sécurité, de sorte qu'ils ne peuvent pas lire librement les variables ou le DOM des autres. JavaScript propose à la place un petit ensemble de canaux bien définis pour transmettre des messages entre eux.
Ce chapitre explique quand la communication inter-fenêtres est nécessaire, la politique de même origine qui la régit, ainsi que quatre mécanismes pratiques : postMessage(), les références directes de fenêtres, les événements de stockage et l'API Broadcast Channel. Il s'appuie sur window.open() et les popups ; si vous débutez avec le modèle navigateur, commencez par la vue d'ensemble de l'environnement navigateur.
Comprendre la communication inter-fenêtres
Un contexte de navigation est tout ce qui possède son propre objet window : un onglet, une popup ou une iframe. Deux contextes ne peuvent interagir que via une API contrôlée, et l'étendue de leurs échanges dépend de leur origine — la combinaison du protocole, de l'hôte et du port (par exemple https://www.w3docs.com:443).
La politique de même origine
La politique de même origine (SOP) est la règle qui détermine ce qu'un contexte peut faire à un autre :
- Même origine (protocole, hôte et port identiques) : les contextes peuvent lire directement le DOM de l'autre et appeler
postMessage()librement. - Origine différente (toute partie diffère) : l'accès direct au DOM est bloqué. Le seul canal autorisé est
postMessage(), que le récepteur doit valider.
C'est pourquoi postMessage() est l'approche recommandée dans presque tous les cas : elle fonctionne de la même manière que les fenêtres partagent ou non une origine, et vous oblige à être explicite sur ce en quoi vous avez confiance.
Quand en avez-vous besoin
- Fenêtres popup. Une fenêtre ouverte avec
window.open()doit souvent renvoyer des résultats à la page qui l'a lancée (une popup de connexion OAuth, un sélecteur de fichiers). - Iframes. Les widgets intégrés — formulaires de paiement, cartes, lecteurs tiers — échangent des données avec la page hôte.
- Onglets et autres contextes. Deux onglets d'une même application peuvent avoir besoin de rester synchronisés (une déconnexion dans un onglet doit déconnecter les autres).
Méthodes de communication inter-fenêtres
Utiliser window.postMessage()
La méthode window.postMessage() est le moyen le plus sûr et le plus portable d'envoyer des données entre fenêtres ou frames — elle fonctionne aussi bien pour les contextes de même origine que d'origine différente. L'émetteur appelle targetWindow.postMessage(data, targetOrigin), et le récepteur écoute un événement message.
Deux arguments importent pour la sécurité :
targetOrigin(le deuxième argument depostMessage) restreint qui peut recevoir le message. Passez l'origine exacte attendue ('https://example.com') ; utilisez le caractère générique'*'uniquement lorsque les données ne sont pas sensibles, car toute fenêtre à cette cible pourra alors les lire.event.origin(côté récepteur) indique qui a envoyé le message. Vérifiez-le toujours avant de faire confiance àevent.data— c'est ainsi que vous rejetez les messages provenant de pages non fiables.
<!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>Dans cet exemple combiné, la fenêtre parente ouvre une popup et intègre une iframe. La popup et l'iframe peuvent toutes deux communiquer avec la fenêtre parente via postMessage(). Les messages s'affichent dans les éléments div correspondants pour une visibilité claire.
Bien que document.write() fonctionne pour des démonstrations simples, les bonnes pratiques modernes recommandent d'utiliser DOMParser ou des URL Blob pour injecter du contenu dans les popups de façon sécurisée.
Accéder aux références de fenêtres
Lorsque vous ouvrez une nouvelle fenêtre avec window.open(), la valeur de retour est une référence à cette fenêtre. La fenêtre ouverte peut à son tour accéder à son ouvreur via window.opener, et un parent peut accéder à une iframe via iframe.contentWindow. Ces références directes ne fonctionnent que lorsque les deux contextes partagent la même origine — sinon, la SOP génère une erreur de sécurité. Utilisez-les pour des pages de même origine étroitement couplées ; recourez à postMessage() dès qu'une frontière d'origine est impliquée.
<!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>Dans cet exemple, la fenêtre parente ouvre une fenêtre enfant et modifie directement son contenu une fois chargé. Elle met également à jour le contenu d'une iframe intégrée.
La manipulation directe du DOM via contentWindow.document ou window.opener est restreinte par la politique de même origine (SOP) pour les contextes d'origines différentes. Pour une communication sécurisée et fiable, préférez toujours postMessage(). Pour les popups de même origine, window.opener peut être utilisé comme alternative pour accéder directement à la fenêtre parente.
Lors de l'utilisation de srcdoc, le contenu de l'iframe se charge de façon asynchrone. Le gestionnaire onload garantit que le DOM est prêt, mais pour des scénarios complexes, envisagez de déclencher la communication via un événement DOMContentLoaded émis depuis l'intérieur de l'iframe.
Utiliser localStorage et sessionStorage
localStorage est partagé par tous les onglets et fenêtres de même origine, et y écrire déclenche un événement storage dans tous les autres contextes. Cela en fait un moyen simple de diffuser un changement entre onglets sans aucune référence directe de fenêtre. (sessionStorage est propre à chaque onglet et ne se propage pas, il n'est donc pas utile pour la messagerie inter-onglets.) Pour un examen plus approfondi des objets de stockage eux-mêmes, consultez localStorage et 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>Dans cet exemple, la fenêtre parente stocke des données dans localStorage et les récupère lors de clics sur les boutons. Pour activer la synchronisation inter-fenêtres, un écouteur d'événement storage est ajouté. Notez que l'événement storage ne se déclenche que dans les autres contextes de navigation, pas dans celui qui a déclenché le changement.
API Broadcast Channel
L'API Broadcast Channel est l'outil conçu spécialement pour la messagerie de même origine entre onglets, fenêtres et iframes. Tout contexte qui ouvre un canal avec le même nom reçoit chaque message qui y est envoyé — sans références de fenêtres ni contournements via les événements storage. Elle ne peut pas traverser les origines, donc pour les iframes tierces, vous aurez toujours besoin de postMessage().
<!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>Dans cet exemple, un Broadcast Channel est créé et un message est envoyé lorsque le bouton est cliqué. Le message est reçu et affiché dans un élément div.
Pour tester cet exemple correctement :
- Cliquez deux fois sur le bouton 'Try it Yourself' pour avoir la page exemple dans deux onglets différents.
- Ensuite, cliquez sur le bouton "Send Message" dans l'un des onglets/fenêtres.
- Vous devriez voir le message apparaître dans l'autre onglet/fenêtre.
L'API BroadcastChannel est conçue pour la communication inter-onglets, donc le message sera envoyé d'un onglet/fenêtre à tous les autres ouverts sur la même origine (le même fichier HTML dans ce cas).
Choisir la bonne méthode
| Méthode | Inter-origines ? | Idéale pour |
|---|---|---|
postMessage() | Oui | Le choix par défaut. Popups et iframes tierces, partout où une frontière d'origine existe. |
| Références directes de fenêtres | Non (même origine uniquement) | Popups/iframes de même origine étroitement couplées que vous contrôlez entièrement. |
Événement storage | Non (même origine uniquement) | Diffusion de changements d'état vers d'autres onglets sans API supplémentaire. |
| Broadcast Channel | Non (même origine uniquement) | Messagerie propre de type many-to-many entre onglets et frames de même origine. |
En cas de doute, utilisez postMessage() — c'est la seule méthode qui fonctionne entre origines différentes et la seule dotée d'un modèle de sécurité intégré.
Bonnes pratiques
Sérialisez les données complexes en JSON. postMessage() utilise l'algorithme de clone structuré et peut transmettre des objets directement, mais utiliser JSON explicitement clarifie le contrat et fonctionne avec les événements storage (qui ne transportent que des chaînes de caractères) :
const message = { type: 'greeting', content: 'Hello, Child Window!' };
// JSON.stringify produces: {"type":"greeting","content":"Hello, Child Window!"}
childWindow.postMessage(JSON.stringify(message), '*');Gérez les cibles fermées ou inaccessibles. Une popup peut être fermée par l'utilisateur, et une fenêtre d'origine différente lèvera une exception si vous y accédez directement. Protégez vos appels :
if (childWindow && !childWindow.closed) {
try {
childWindow.postMessage('Hello, Child Window!', '*');
} catch (e) {
console.error('Failed to send message:', e);
}
}Validez toujours l'expéditeur. Du côté récepteur, vérifiez event.origin par rapport à une liste d'autorisation avant d'agir sur event.data, et n'utilisez jamais eval() sur un message entrant.
Conclusion
La communication inter-fenêtres en JavaScript est une fonctionnalité puissante qui, utilisée correctement, peut considérablement améliorer l'interactivité et l'expérience utilisateur des applications web. En utilisant des méthodes telles que window.postMessage(), le stockage local et l'API Broadcast Channel, les développeurs peuvent gérer efficacement l'échange de données entre différentes fenêtres, onglets et frames. Suivez les bonnes pratiques pour garantir une communication sécurisée et robuste, et exploitez les exemples fournis pour intégrer ces techniques dans vos projets.