Chapter 50: Web Workers & Multi-Threaded JavaScript

Shared Workers & Multi-Tab Synchronization

Coordinating state across multiple browser tabs, windows, and iframes using `SharedWorker`, `SharedWorkerGlobalScope`, and the `MessagePort` protocol.

LEARNING OBJECTIVES
  • Understand the architectural difference between Dedicated Workers (1:1 per tab) and Shared Workers (N:1 multi-tab).
  • Instantiate shared workers with new SharedWorker(scriptURL, name).
  • Handle client connections inside SharedWorkerGlobalScope using the onconnect event and MessagePort array.
  • Master explicit port.start() vs. implicit port.onmessage event listener activation.
  • Implement a multi-tab broadcast bus and single-connection WebSocket proxy.
🎬 INTERACTIVE VISUAL PIPELINE Core Architecture Simulation
🌐
1. Input
Directives & Tags
⚙️
2. Parse
Tokenizer & AST
🌳
3. Layout
Box Model & Flow
🎨
4. Render
GPU Paint & Composite
PHASE 1: INPUT & DIRECTIVES
Browser receives declarative markup stream, parsing tag tokens and initializing component state.

📖 The Mental Model & Story (Intuitive Foundation)

Imagine an apartment building where ten different tenants (ten open browser tabs) all want access to the current daily news bulletin.

                  APPROACH 1: DEDICATED WORKERS (10 PRIVATE COURIERS)
  Tab 1 =======> [ Private Dedicated Worker 1 ] (10 separate OS threads!)
  Tab 2 =======> [ Private Dedicated Worker 2 ] (10 separate WebSocket connections!)
  Tab 3 =======> [ Private Dedicated Worker 3 ] (Zero cross-tab communication)

                  APPROACH 2: SHARED WORKER (1 CENTRAL BUILDING CONCIERGE)
  Tab 1 (Lobby)   =====\
  Tab 2 (Kitchen) ======> [ MessagePort Connections ] ===> [ 1 Shared Worker Concierge ]
  Tab 3 (Balcony) =====/                                   (Single shared OS thread)
                                                           (Single shared WebSocket)
                                                           (Broadcasts to all tabs!)
  • Dedicated Workers: Every apartment tenant hires their own private butler. If 10 tabs are open, there are 10 separate worker threads, 10 separate WebSocket connections to the backend server, and no way for Tab 1 to know what Tab 2 is doing.
  • Shared Worker: The building employs one central concierge in the lobby.
    • When Tab 1 opens, it connects a direct telephone wire (MessagePort) to the concierge.
    • When Tab 2 opens, it connects its own telephone wire to the same concierge.
    • The concierge maintains a single WebSocket connection to the cloud server and broadcasts real-time notifications to all connected telephone lines simultaneously.

If the user closes Tab 1, the concierge stays alive for Tab 2 and Tab 3. The Shared Worker is only destroyed when every connected tab is closed.


Technical Deep Dive & Specifications

Dedicated vs. Shared Workers: Architectural Comparison

Dimension Dedicated Worker (Worker) Shared Worker (SharedWorker)
Scope Object DedicatedWorkerGlobalScope SharedWorkerGlobalScope
Relationship 1-to-1: Bound to a single page N-to-1: Shared across tabs, windows, iframes
Connection Protocol Direct worker.postMessage() Port-based worker.port.postMessage()
Lifecycle Dies when the owner tab is closed Persists until all connected tabs are closed
Global Connection Hook Top-level script evaluation self.onconnect = (e) => { ... }
Debugging Chrome DevTools > Sources > Threads chrome://inspect/#workers
+---------------------------------------------------------------------------------------------------+
|                                  SHARED WORKER ARCHITECTURE                                       |
+---------------------------------------------------------------------------------------------------+

   Browser Tab 1 (Origin A)       Browser Tab 2 (Origin A)       Browser Tab 3 (Origin A)
   +------------------------+     +------------------------+     +------------------------+
   | worker.port            |     | worker.port            |     | worker.port            |
   +------------------------+     +------------------------+     +------------------------+
               \                              |                              /
          MessagePort 1                  MessagePort 2                  MessagePort 3
                 \                            |                            /
                  v                           v                           v
   +------------------------------------------------------------------------------------------------+
   |                              SharedWorkerGlobalScope (Singleton)                               |
   |   self.onconnect = (e) => { const port = e.ports[0]; connections.push(port); };               |
   |                                                                                                |
   |   - Central Shared State Store                                                                 |
   |   - 1 Single WebSocket Connection to Backend Server                                            |
   |   - Multi-Tab Broadcast Dispatcher                                                             |
   +------------------------------------------------------------------------------------------------+

The MessagePort Connection Handshake

Unlike Dedicated Workers where communication is attached directly to the Worker instance, Shared Workers communicate through a MessagePort channel.

1. On the Main Thread (Tab):

// Instantiating the shared worker
const sharedWorker = new SharedWorker('./shared-hub.js', 'AppSharedHub');

// Option A: Explicit start (Required when using addEventListener)
sharedWorker.port.addEventListener('message', (event) => {
  console.log('Received from SharedWorker:', event.data);
});
sharedWorker.port.start(); // MUST call start()!

// Option B: Implicit start (Automatically starts port)
sharedWorker.port.onmessage = (event) => {
  console.log('Received:', event.data);
};

// Send message to shared worker
sharedWorker.port.postMessage({ action: 'SUBSCRIBE' });

2. Inside the Shared Worker (shared-hub.js):

// Set to hold all active tab ports
const ports = new Set();

self.onconnect = function(event) {
  // Extract the newly connected tab's port
  const port = event.ports[0];
  ports.add(port);

  port.onmessage = function(e) {
    const { action, payload } = e.data;

    if (action === 'BROADCAST_CHAT') {
      // Broadcast message to ALL connected tabs
      for (const p of ports) {
        p.postMessage({ type: 'NEW_CHAT', text: payload });
      }
    }
  };

  port.start(); // Start listening on this port
};

[!IMPORTANT] When using port.addEventListener('message', fn), you must call port.start(). If you use the property assignment port.onmessage = fn, the browser calls start() implicitly.


💻 Interactive Code Playground

Below is a complete, runnable Multi-Tab Synchronized Counter & Broadcast Hub. Open this page in two separate browser tabs or windows side-by-side to watch them update in real-time across tabs.

Starter Code

Line-by-Line Code Breakdown

  • Line 66 (const ports = new Set()): Inside the Shared Worker, a Set tracks every connected tab's MessagePort.
  • Line 74 (self.onconnect = function(event)): Triggers every time a new browser tab instantiates new SharedWorker().
  • Line 75 (const port = event.ports[0]): Retrieves the distinct communication port for the incoming tab.
  • Lines 86–98: When a tab posts { action: 'CHANGE_COUNTER' }, the shared worker updates sharedState.counter and calls broadcast(), sending the updated number to every open tab simultaneously.
  • Line 126 (worker.port.start()): Opens the bidirectional port channel on the client tab.

Expected Browser Render Output


SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
HTML STARSHIP CODE TERMINAL playground.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...
🌐 Multi-Tab Shared Worker Synchronizer
Instructions: Open this same HTML file in two separate browser tabs.

Connected Tabs Count: 2
Shared Global Counter:
[ 42 ]

[ Button: + Increment Shared Counter ] [ Button: - Decrement Shared Counter ] [ Button: Reset to Zero ]

Cross-Tab Activity Log:
[02:20:10] Connected to SharedWorker. Current counter: 0
[02:20:12] Active tabs connected: 2
[02:20:15] Counter modified by a connected tab -> New Value: 1
[02:20:16] Counter modified by a connected tab -> New Value: 2

🏋️ Hands-On Exercise

🎯 The Challenge: Multi-Tab Broadcast Chat Bridge

Instructions:

  1. Build a Shared Worker that acts as a Local Cross-Tab Chat Room.
  2. When a tab posts { action: 'SEND_CHAT', username: 'Alice', text: 'Hello World' }, the shared worker timestamps the message and broadcasts it to all connected ports.
  3. Keep an in-memory history of the last 10 chat messages inside the Shared Worker. When a 3rd tab opens, send it the existing 10 messages immediately in INIT_CHAT.

🏁 Starter Code Sandbox

SYS: ACTIVE
HULL: 98%
CORE: STABLE
NET: ONLINE
STARTER CODE SANDBOX exercise.html
LIVE RENDER & DIAGNOSTICS CORE TEMP: 45°C
INSPECTING DOM: VALID
TAGS: SCANNING...

⚠️ Common Pitfalls

  1. Forgetting port.start(): If you use worker.port.addEventListener('message', handler) instead of worker.port.onmessage = handler, messages will be queued forever and never fire until worker.port.start() is called.
  2. Different Script URLs Creating Separate Workers: new SharedWorker('a.js') and new SharedWorker('a.js?v=2') are treated as two distinct shared workers. The script URL and name must match exactly.
  3. Debugging Shared Workers: Shared workers do not show up in the standard page DevTools console. In Google Chrome, navigate to chrome://inspect/#workers to open a dedicated DevTools window for shared workers.

💡 Pro Tips

  1. Single WebSocket Multiplexer: In large SaaS applications (e.g., Slack, Figma, Trading dashboards), open one single WebSocket connection inside a Shared Worker. All 30 open tabs can multiplex their subscriptions through this single socket, reducing backend server connection overhead by over 90%.
  2. Safari Compatibility Note: Shared Workers are supported in modern versions of Safari, Chrome, Edge, and Firefox. However, always feature-detect with if ('SharedWorker' in window) and provide a fallback to BroadcastChannel or localStorage events.

📌 Key Takeaways

  • SharedWorker creates a single singleton worker shared across multiple tabs and windows of the same origin.
  • Connections are handled via the onconnect event, which receives a MessagePort for each client tab.
  • Main threads access communication through worker.port.postMessage() and worker.port.onmessage.
  • If using addEventListener('message'), you must explicitly call port.start().
  • Shared workers are ideal for centralized WebSocket connections, multi-tab state sync, and cross-window coordination.
  • --
⭐ LEARN: HTML 🌟 ⚔️ QUIZ BATTLE ARENA // ACTIVE
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 1 / 3

How does a Shared Worker know that a new browser tab has connected to it?

Question 1 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 2 / 3

Under what condition is a Shared Worker finally terminated by the browser?

Question 2 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP
3x
STREAK!
BONUS ACTIVE
COMBO
? Question 3 / 3

Where can you inspect and debug running Shared Workers in Google Chrome?

Question 3 / 3 Topic: HTML Fundamentals
00:45 REMAINING
XP REWARD
+250 XP