Comunicação Entre Janelas em JavaScript
Aprenda comunicação entre janelas em JavaScript: postMessage, política de mesma origem, referências de janelas, eventos localStorage e a Broadcast Channel API, com exemplos e segurança.
Comunicação entre janelas é a troca de dados entre contextos de navegação separados — uma página pai e um popup que ela abriu, uma página e um iframe incorporado, ou duas abas do mesmo site. O navegador isola deliberadamente esses contextos por questões de segurança, para que eles não possam ler livremente as variáveis ou o DOM uns dos outros. Em vez disso, o JavaScript oferece um pequeno conjunto de canais bem definidos para transmissão de mensagens entre eles.
Este capítulo aborda quando você precisa de comunicação entre janelas, a política de mesma origem que a governa, e quatro mecanismos práticos: postMessage(), referências diretas de janelas, eventos de armazenamento e a Broadcast Channel API. Ele se baseia em window.open() e popups; se você é novo no modelo de navegador, comece com a visão geral do ambiente do navegador.
Entendendo a Comunicação Entre Janelas
Um contexto de navegação é qualquer coisa com seu próprio objeto window: uma aba, um popup ou um iframe. Dois contextos podem se comunicar apenas por meio de uma API controlada, e o quanto eles podem fazer depende de sua origem — a combinação de protocolo, host e porta (por exemplo, https://www.w3docs.com:443).
A política de mesma origem
A política de mesma origem (SOP) é a regra que decide o que um contexto pode fazer com outro:
- Mesma origem (protocolo, host e porta idênticos): os contextos podem ler o DOM uns dos outros diretamente e chamar
postMessage()livremente. - Origem diferente (qualquer parte difere): o acesso direto ao DOM é bloqueado. O único canal permitido é
postMessage(), que o receptor deve validar.
É por isso que postMessage() é a abordagem recomendada em quase todos os casos: funciona da mesma forma independentemente de as janelas compartilharem uma origem ou não, e força você a ser explícito sobre em quem você confia.
Quando você precisa disso
- Janelas popup. Uma janela aberta com
window.open()frequentemente precisa enviar resultados de volta à página que a iniciou (um popup de login OAuth, um seletor de arquivos). - Iframes. Widgets incorporados — formulários de pagamento, mapas, players de terceiros — trocam dados com a página host.
- Abas e outros contextos. Duas abas do mesmo aplicativo podem precisar se manter sincronizadas (um logout em uma aba deve deslogar as outras).
Métodos de Comunicação Entre Janelas
Usando window.postMessage()
O método window.postMessage() é a forma mais segura e portátil de enviar dados entre janelas ou frames — funciona tanto para contextos de mesma origem quanto de origem diferente. O remetente chama targetWindow.postMessage(data, targetOrigin), e o receptor escuta um evento message.
Dois argumentos são importantes para a segurança:
targetOrigin(o segundo argumento depostMessage) restringe quem pode receber a mensagem. Passe a origem exata que você espera ('https://example.com'); use o curinga'*'apenas quando os dados não forem sensíveis, pois qualquer janela naquele destino poderá então lê-los.event.origin(no receptor) informa quem enviou a mensagem. Sempre verifique antes de confiar emevent.data— é assim que você rejeita mensagens de páginas não confiáveis.
<!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>Neste exemplo combinado, a janela pai abre um popup e incorpora um iframe. Tanto o popup quanto o iframe podem se comunicar com a janela pai usando postMessage(). As mensagens são exibidas dentro dos respectivos elementos div para maior clareza.
Embora document.write() funcione para demonstrações simples, as melhores práticas modernas recomendam usar DOMParser ou URLs Blob para injetar conteúdo em popups com segurança.
Acessando Referências de Janelas
Quando você abre uma nova janela com window.open(), o valor de retorno é uma referência àquela janela. A janela aberta, por sua vez, pode alcançar seu abridor por meio de window.opener, e um pai pode alcançar um iframe por meio de iframe.contentWindow. Essas referências diretas funcionam apenas quando os dois contextos compartilham a mesma origem — caso contrário, a SOP lança um erro de segurança. Use-as para páginas de mesma origem fortemente acopladas; recorra a postMessage() sempre que houver um limite de origem envolvido.
<!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>Neste exemplo, a janela pai abre uma janela filha e modifica diretamente seu conteúdo após o carregamento. Adicionalmente, ela atualiza o conteúdo de um iframe incorporado.
A manipulação direta do DOM via contentWindow.document ou window.opener é restrita pela Política de Mesma Origem (SOP) para contextos de origem diferente. Para comunicação segura e confiável, sempre prefira postMessage(). Para popups de mesma origem, window.opener pode ser usado como alternativa para acessar a janela pai diretamente.
Ao usar srcdoc, o conteúdo do iframe é carregado de forma assíncrona. O manipulador onload garante que o DOM esteja pronto, mas para cenários complexos, considere disparar a comunicação por meio de um evento DOMContentLoaded enviado de dentro do iframe.
Usando localStorage e sessionStorage
localStorage é compartilhado por todas as abas e janelas de mesma origem, e escrever nele dispara um evento storage em todos os outros contextos. Isso o torna uma forma simples de transmitir uma alteração entre abas sem qualquer referência direta de janela. (sessionStorage é por aba e não se propaga, portanto não é útil para mensagens entre abas.) Para uma análise mais aprofundada dos próprios objetos de armazenamento, veja localStorage e 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>Neste exemplo, a janela pai armazena dados no localStorage e os recupera ao clicar nos botões. Para habilitar a sincronização entre janelas, um ouvinte de evento storage é adicionado. Note que o evento storage só é disparado em outros contextos de navegação, não no que desencadeou a alteração.
Broadcast Channel API
A Broadcast Channel API é a ferramenta criada especificamente para mensagens de mesma origem entre abas, janelas e iframes. Qualquer contexto que abra um canal com o mesmo nome recebe cada mensagem postada nele — sem referências de janelas e sem soluções alternativas com eventos storage. Ela não pode cruzar origens, portanto para iframes de terceiros você ainda precisará 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>Neste exemplo, um Broadcast Channel é criado e uma mensagem é enviada quando o botão é clicado. A mensagem é recebida e exibida dentro de um elemento div.
Para testar este exemplo corretamente:
- Clique no botão 'Try it Yourself' duas vezes, para ter a página de exemplo em duas abas diferentes.
- Em seguida, clique no botão "Send Message" em uma das abas/janelas.
- Você deve ver a mensagem aparecer na outra aba/janela.
A API BroadcastChannel foi projetada para comunicação entre abas, portanto a mensagem será enviada de uma aba/janela para todas as outras abertas para a mesma origem (o mesmo arquivo HTML neste caso).
Escolhendo o método correto
| Método | Entre origens? | Melhor para |
|---|---|---|
postMessage() | Sim | O padrão. Popups e iframes de terceiros, em qualquer lugar onde exista um limite de origem. |
| Referências diretas de janelas | Não (somente mesma origem) | Popups/iframes de mesma origem fortemente acoplados que você controla totalmente. |
Evento storage | Não (somente mesma origem) | Transmissão de alterações de estado para outras abas sem API adicional. |
| Broadcast Channel | Não (somente mesma origem) | Mensagens muitos-para-muitos limpas entre abas e frames de mesma origem. |
Em caso de dúvida, use postMessage() — é o único método que funciona entre origens e o único com um modelo de segurança integrado.
Melhores práticas
Serialize dados complexos como JSON. postMessage() usa o algoritmo de clone estruturado e pode passar objetos diretamente, mas o JSON explícito mantém o contrato claro e funciona com eventos storage (que carregam apenas strings):
const message = { type: 'greeting', content: 'Hello, Child Window!' };
// JSON.stringify produces: {"type":"greeting","content":"Hello, Child Window!"}
childWindow.postMessage(JSON.stringify(message), '*');Lide com alvos fechados ou inacessíveis. Um popup pode ser fechado pelo usuário, e uma janela de origem diferente lançará um erro se você a tocar diretamente. Proteja suas chamadas:
if (childWindow && !childWindow.closed) {
try {
childWindow.postMessage('Hello, Child Window!', '*');
} catch (e) {
console.error('Failed to send message:', e);
}
}Sempre valide o remetente. No lado receptor, verifique event.origin em relação a uma lista de permissões antes de agir sobre event.data, e nunca execute eval() em uma mensagem recebida.
Conclusão
A comunicação entre janelas em JavaScript é um recurso poderoso que, quando usado corretamente, pode melhorar significativamente a interatividade e a experiência do usuário em aplicações web. Ao empregar métodos como window.postMessage(), armazenamento local e a Broadcast Channel API, os desenvolvedores podem gerenciar eficientemente a troca de dados entre diferentes janelas, abas e frames. Siga as melhores práticas para garantir uma comunicação segura e robusta, e aproveite os exemplos fornecidos para integrar essas técnicas em seus projetos.