Safari BroadcastChannel API: Real-Time Same-Origin Messaging
This article explores how the BroadcastChannel API in the Safari web browser facilitates seamless, real-time communication between different browsing contexts—such as tabs, windows, and iframes—that share the same origin. We will cover the underlying mechanism of the API, how to initialize a channel, the process of sending and receiving messages using JavaScript, and the specific security and memory management practices required for optimal performance in Safari.
The Core Mechanism
Supported in Safari since version 15.4, the BroadcastChannel API
operates on a publish-subscribe model directly within Apple's WebKit
browser engine. Instead of relying on an external server via WebSockets
or using workarounds like localStorage event listeners,
Safari routes messages internally.
For different browsing contexts to communicate, they must meet two strict criteria:
- They must share the exact same origin (identical protocol, domain, and port).
- They must connect to a
BroadcastChannelinstance using the exact same channel name.
Initializing a Channel
To establish a communication bridge, each Safari tab, window, or
iframe must instantiate a BroadcastChannel object and pass
the same string identifier into the constructor.
// Executed in Tab A, Tab B, and an Iframe
const appChannel = new BroadcastChannel('app_sync_channel');As soon as this line executes, the browsing context automatically joins the underlying channel inside Safari's memory and is immediately ready to transmit or listen for data.
Broadcasting Data in Real Time
Sending data requires the postMessage() method. When one
tab calls this method, Safari instantly dispatches the message to every
other active tab, window, or iframe subscribed to that specific channel
name. The context that sent the message does not receive its own
broadcast.
// Executed in Tab A
appChannel.postMessage({
action: 'theme_change',
value: 'dark'
});Safari utilizes the structured clone algorithm to serialize the data being sent. This allows developers to pass complex JavaScript types—such as Objects, Arrays, Blobs, ArrayBuffers, and Maps—directly through the channel without needing to manually convert the data into JSON strings first.
Receiving Messages
To react to incoming broadcasts in real time, the receiving contexts
must attach an event listener to their local instance of the
BroadcastChannel.
// Executed in Tab B and the Iframe
appChannel.onmessage = (event) => {
const data = event.data;
if (data.action === 'theme_change') {
document.body.className = data.value;
}
};When Tab A sends the message, Safari triggers the
message event in Tab B and the iframe simultaneously. The
transmitted payload is accessed directly via the event.data
property. If the browser fails to deserialize the message, an
onmessageerror event is fired instead.
Safari Security and Iframe Partitioning
Safari enforces strict privacy rules through Intelligent Tracking Prevention (ITP), which directly impacts how iframes use the BroadcastChannel API.
While top-level Safari tabs from the same origin can always
communicate freely, third-party iframes are subject to partitioning. If
an iframe from origin-b.com is embedded inside
site-1.com in one tab, and the exact same
origin-b.com iframe is embedded inside
site-2.com in another tab, Safari blocks them from
communicating via the BroadcastChannel API. To prevent cross-site
tracking, Safari partitions the API so that iframes can only communicate
if both the iframe's origin and the top-level webpage's origin
match.
Closing the Channel
Maintaining an open BroadcastChannel with an active
onmessage listener prevents Safari's garbage collector from
freeing the object from memory. When a component unmounts or a tab no
longer requires synchronization, the connection should be explicitly
terminated.
appChannel.close();Calling the close() method instructs WebKit to sever the
link between the JavaScript object and the browser's internal
communication bus, allowing Safari to safely reclaim the memory.