Overview
Implement real-time communication between extension components using the Chrome messaging API. This allows the popup to notify content scripts when notes are saved, triggering instant ribbon updates without page reloads.
By the end, TabNotes will update the ribbon immediately when users save or delete notes.
What You’ll Learn
- Understand extension messaging architecture
- Send messages from popup directly to content scripts
- Listen for messages in content scripts
- Pass JSON data in messages
- Filter messages by type and context
- Create message-based update flows
Extension Messaging Overview
Extensions have isolated components that need to coordinate:
- Popup - User types notes
- Content Script - Displays ribbon on page
- Background - Monitors tab changes (can also send messages)
Problem: These components can’t directly call each other’s functions.
Solution: Message passing - components send JSON messages directly to each other, others listen and respond.
Message Flow Architecture
Sending Messages
Basic Message Syntax
await browser.tabs.sendMessage(tabId, messageObject);
Parameters:
tabId- Target tab IDmessageObject- Any JSON-serializable object
Common Message Structure:
{
type: 'messageType', // Identifies message purpose
data: { ... } // Message payload
}
Creating a Message Utility
// utils/sendNoteUpdateMessage.ts
import { storage } from 'wxt/storage';
import getPageKey from './getPageKey';
const sendNoteUpdateMessage = async (tabId: number) => {
// Get tab details
const tab = await browser.tabs.get(tabId);
if (!tab.url) return;
// Get note data for this page
const pageKey = getPageKey(tab.url);
const notesStorage = await storage.getItem('sync:notesStorage') || {};
const note = notesStorage[pageKey];
// Send message to content script
const message = {
type: 'noteUpdated',
pageKey,
note // Include note data (or undefined if no note)
};
try {
await browser.tabs.sendMessage(tabId, message);
} catch (error) {
// Content script might not be loaded yet (e.g., chrome:// pages)
// Silently ignore errors
}
};
export default sendNoteUpdateMessage;
Error Handling: Content scripts don’t run on all pages (e.g., chrome://). Catch errors gracefully.
Receiving Messages
Content scripts listen for messages using browser.runtime.onMessage:
// entrypoints/content/index.ts
import './style.css';
import getPageKey from '@/utils/getPageKey';
export default defineContentScript({
matches: ['https://*/*'],
main() {
const pageKey = getPageKey(window.location.href);
const showRibbon = () => {
if (document.getElementById('tabnotes-ribbon')) return;
const ribbon = document.createElement('div');
ribbon.id = 'tabnotes-ribbon';
ribbon.textContent = 'Has Note';
document.body.appendChild(ribbon);
};
const hideRibbon = () => {
const ribbon = document.getElementById('tabnotes-ribbon');
if (ribbon) ribbon.remove();
};
// Listen for messages
browser.runtime.onMessage.addListener((message) => {
// Filter: only process noteUpdated messages for this page
if (message.type !== 'noteUpdated') return;
if (message.pageKey !== pageKey) return;
// Show or hide ribbon based on note existence
if (message.note && message.note.note) {
showRibbon();
} else {
hideRibbon();
}
});
}
});
Message Filtering:
- Check
message.typeto ignore irrelevant messages - Check
message.pageKeyto ignore messages for other pages
Why Filter by pageKey? Messages might be sent to multiple tabs. Each content script should only respond to its own page’s updates.
Triggering Messages from Popup
When users save notes, send update messages:
// entrypoints/popup/main.ts
import changeIcon from '@/utils/changeIcon';
import sendNoteUpdateMessage from '@/utils/sendNoteUpdateMessage';
noteText.addEventListener('keyup', async () => {
const [tab] = await browser.tabs.query({
active: true,
currentWindow: true
});
if (!tab.id || !tab.url) return;
// Save note logic (from Lesson 6)
const pageKey = getPageKey(tab.url);
const notesStorage = await storage.getItem('sync:notesStorage') || {};
const existingNote = notesStorage[pageKey];
notesStorage[pageKey] = {
note: noteText.value,
createdAt: existingNote?.createdAt || new Date().toISOString(),
updatedAt: new Date().toISOString()
};
await storage.setItem('sync:notesStorage', notesStorage);
// Update icon and send message
await changeIcon(tab.id);
await sendNoteUpdateMessage(tab.id);
});
Testing:
- Open popup on any page, type note
- Ribbon appears immediately (no reload needed)
- Delete note text
- Ribbon disappears immediately
Triggering Messages from Background
Update ribbon when users switch tabs:
// entrypoints/background.ts
import changeIcon from '@/utils/changeIcon';
import sendNoteUpdateMessage from '@/utils/sendNoteUpdateMessage';
export default defineBackground(() => {
// Tab switched
browser.tabs.onActivated.addListener(async (activeInfo) => {
await changeIcon(activeInfo.tabId);
await sendNoteUpdateMessage(activeInfo.tabId);
});
// URL changed within tab
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.url && tab.active) {
changeIcon(tabId);
sendNoteUpdateMessage(tabId);
}
});
});
Complete Flow:
- User switches tabs
- Background detects via
onActivated - Background sends message with note data
- Content script shows/hides ribbon
Message Patterns
One-Way Messages (Fire and Forget)
// Sender doesn't wait for response
await browser.tabs.sendMessage(tabId, { type: 'update' });
Use Case: Notifications, status updates (like our ribbon)
Request-Response Messages
// Sender waits for response
const response = await browser.tabs.sendMessage(tabId, {
type: 'getData'
});
console.log('Received:', response);
// Listener sends response
browser.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'getData') {
sendResponse({ data: 'example' });
}
return true; // Required for async response
});
Use Case: Querying content script state, requesting data
TabNotes uses one-way messages - content scripts don’t need to respond.
What’s Next
Extension components now communicate in real-time. The ribbon updates instantly when notes are saved, providing immediate visual feedback without requiring page reloads.
Lesson 11 introduces custom HTML pages for extensions - creating a dedicated notes management page where users can view and edit all their saved notes in one place.