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:

  1. They must share the exact same origin (identical protocol, domain, and port).
  2. They must connect to a BroadcastChannel instance 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.