LEARNING OBJECTIVES ⌵
- Attach and manage event listeners for the four core
Notificationinstance events:onshow,onclick,onclose, andonerror. - Programmatically bring background or minimized browser tabs to the foreground using
window.focus(). - Close active notifications programmatically using
notification.close(). - Prevent garbage collection pitfalls where unreferenced
Notificationinstances miss asynchronous click events.
📖 The Mental Model & Story (Intuitive Foundation)
Imagine sending a courier with an urgent contract. Handing the courier the envelope is only the first step. You need a two-way radio to receive continuous status updates:
- "Contract delivered and presented to the client." (
onshow) - "Client tapped the signature line to open the document." (
onclick) - "Client folded the document and put it away in a drawer." (
onclose) - "Courier encountered a road closure and couldn't deliver." (
onerror)
+─────────────────────────────────────────────────────────────────────────────+
| NOTIFICATION LIFECYCLE PIPELINE |
+─────────────────────────────────────────────────────────────────────────────+
| |
| [ Web Tab JS ] ─── `const notif = new Notification(...)` ─────────────────┐|
| │ │|
| ▼ ▼|
| [ OS Native Bridge ] ──── (Renders Toast on Desktop) ──────────► [ onshow ]|
| │ │|
| │ │|
| ┌────┴──────────────────────────────┐ │|
| ▼ ▼ │|
| [ User Clicks Toast ] [ User Swipes / Dismisses ] │|
| │ │ │|
| ▼ ▼ │|
| [ onclick ] [ onclose ] │|
| │ │ │|
| ├──► `window.focus()` └──► Clean up memory references │|
| ├──► Route to chat message │|
| └──► `notif.close()` │|
| |
+─────────────────────────────────────────────────────────────────────────────+
When a user clicks your desktop notification, they expect the browser to instantly come forward and navigate straight to the relevant conversation, document, or order. The Notification instance events provide the exact hooks needed to coordinate this seamless handoff.
Technical Deep Dive & Specifications
The Four Notification Instance Events
Every Notification instance inherits from EventTarget and implements four lifecycle event handlers:
interface Notification : EventTarget {
attribute EventHandler onshow; // Dispatched when OS displays the toast
attribute EventHandler onclick; // Dispatched when user clicks notification
attribute EventHandler onclose; // Dispatched when dismissed by user or system
attribute EventHandler onerror; // Dispatched if OS fails to render/display
void close(); // Programmatically dismisses the notification
};
| Event Handler | Event Type | When It Fires | Common Use Case |
|---|---|---|---|
onshow |
Event |
When the host operating system has successfully rendered the visual popup. | Starting telemetry timers, logging impression metrics. |
onclick |
Event |
When the user left-clicks or taps the body of the notification. | Calling window.focus(), routing to specific URLs, loading data. |
onclose |
Event |
When the user clicks the "X" button, swipes away, or the system auto-dismisses. | Garbage cleanup, analytics logging of dismissal rates. |
onerror |
Event |
When the browser cannot communicate with the OS daemon or asset loading fails. | Fallback to in-app toast display, error reporting. |
Focusing the Window with window.focus()
When a user interacts with a notification, the browser tab is often hidden in the background or minimized. Inside the onclick handler, you can bring the origin window into focus:
notification.onclick = (event) => {
event.preventDefault(); // Prevent default OS behavior
// 1. Bring browser window and tab to the foreground
window.focus();
// 2. Navigate to the relevant in-app view
window.location.hash = `#message-${event.target.data.messageId}`;
// 3. Programmatically close the notification toast
notification.close();
};
Security Note: Browsers only permit
window.focus()during a verified User Activation window. Becauseonclickis triggered directly by a physical user click on the notification, modern browsers grant the activation privilege to bring the tab forward.
The Garbage Collection (GC) Memory Trap
One of the most elusive bugs in JavaScript notification programming is the Garbage Collector Drop:
// ❌ DANGEROUS: The notification variable is scoped locally and unreferenced
function sendFragileNotification() {
const notif = new Notification('New Message', { body: 'Click to open' });
notif.onclick = () => {
window.focus(); // ⚠️ May never execute!
};
} // 'notif' goes out of scope here.
If the V8 / JavaScriptCore engine runs a garbage collection cycle before the user clicks the notification, the unreferenced JavaScript Notification object may be destroyed in memory. When the OS bridge sends the click event back to the browser process, there is no longer a JS event listener to receive it!
The Enterprise Retention Pattern:
// ✅ ROBUST: Retain active notification references in a Set
const activeNotifications = new Set();
function sendRobustNotification(title, options) {
const notif = new Notification(title, options);
activeNotifications.add(notif);
notif.onclick = (event) => {
window.focus();
notif.close();
};
// Clean up memory only after the notification is completely closed
notif.onclose = () => {
activeNotifications.delete(notif);
};
notif.onerror = () => {
activeNotifications.delete(notif);
};
}
💻 Interactive Code Playground
Starter Code
Save this file as index.html and test it in your browser:
Line-by-Line Code Breakdown
- Line 99:
activeSet.add(notif)stores a strong reference to theNotificationinstance, preventing JavaScript garbage collection from pruning the instance before the user clicks it. - Lines 103–109: The
onclickhandler executeswindow.focus(), updates the DOM target element, and invokesnotif.close()to immediately remove the desktop banner. - Lines 111–114:
notif.oncloseacts as the deterministic lifecycle destructor, removing the closed instance fromactiveSetto prevent memory leaks. - Lines 134–137: Inside
onshow, asetTimeoutinvokesnotif.close()after 4000ms, proving programmatic closure control.
Expected Browser Render Output
+────────────────────────────────────────────────────────────+
| 📡 Notification Lifecycle Monitor |
| Trigger alerts, minimize or switch tabs... |
| |
| [ Send Alert in 3s (Switch Tab!) ] [ Send Auto-Dismiss ] |
| |
| +────────────────────────────────────────────────────────+ |
| | [10:32:01] SYSTEM: System initialized. | |
| | [10:32:04] SHOW: Native notification displayed on OS. | |
| | [10:32:07] CLICK: User clicked notification payload... | |
| | [10:32:07] CLOSE: Notification closed and dismissed. | |
| +────────────────────────────────────────────────────────+ |
| |
| 🎯 Destination Target Area (Highlighted Green on Click) |
+────────────────────────────────────────────────────────────+🏋️ Hands-On Exercise
🎯 The Challenge: Build a Chat Message Dispatcher & Router
Instructions:
- Create a function
sendChatNotification(sender, message, channelId)with the following requirements:- Construct a
Notificationtitled"New message from " + sender. - Set
bodytomessageand attach{ channelId: channelId, sender: sender }indata. - On
onclick, prevent default behavior, callwindow.focus(), call a global routing helpernavigateToChannel(channelId), and invoke.close(). - Retain the notification reference in a tracking structure and remove it on
oncloseoronerror.
- Construct a
- Implement a mock
navigateToChannel(channelId)that updates an in-page#current-channelcontainer.
🏁 Starter Code Sandbox
⚠️ Common Pitfalls
- Failing to Retain Notification References: Relying on unreferenced variables (
new Notification(...)) will cause random, intermittentonclicklistener dropouts due to browser garbage collection cycles. - Forgetting
notification.close()insideonclick: On Windows and Android, clicking a notification body does NOT automatically close the toast unless you explicitly invoke.close(). - Blocking Main Thread in
onshow: Executing heavy synchronous computations insideonshowwill stutter UI animations on the host desktop.
💡 Pro Tips
- Handling Multi-Tab Focus: If the user has 5 tabs open for your domain, clicking a notification from a single tab will only focus that specific tab instance. For multi-tab coordination, use the BroadcastChannel API or Service Workers (covered in Lesson 53.7).
- Telemetry & Impression Tracking: Use the
onshowandoncloseevents to calculate the exact user engagement click-through rate (CTR) and dismiss-without-click rate.
📌 Key Takeaways
- The four instance lifecycle events are
onshow,onclick,onclose, andonerror. window.focus()brings the origin browser tab to the foreground inside the user-activatedonclickhandler.- Notifications do not auto-close on click in many desktop OSes; always invoke
notification.close()insideonclick. - Always maintain a JavaScript reference (e.g.
Set) to active notifications to prevent garbage collection from killing uninvokedonclicklisteners. - Clean up memory references deterministically inside the
oncloseandonerrorhandlers. - --