Recover Closed Tabs Instantly with a Simple Chrome Extension

This guide explains how to build and use a lightweight Chrome extension that lists recently closed tabs, lets you restore individual pages or all at once, shows site icons and timestamps, and runs with minimal permissions using the Chrome Sessions, Tabs, and Favicon APIs.

Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Rare Earth Juejin Tech Community
Recover Closed Tabs Instantly with a Simple Chrome Extension
Girlfriend keeps accidentally closing tabs? Suggest Ctrl/Cmd+Shift+T, but she can’t find the key. After a failed attempt to view history, the "Recent Closed" list shows only eight items, not enough. The solution is a custom Chrome extension called "Regret Medicine" that instantly shows recently closed tabs and lets you restore them with one click.

Feature Highlights

Automatically lists up to 25 recently closed tabs.

Supports restoring a single tab or all tabs at once.

Displays website favicon, title, URL, and close time.

Smooth animations and a clean UI.

Zero background processes; minimal permissions (sessions, tabs, favicon).

Core Technical Implementation

The extension relies on three Chrome APIs to implement its core functionality:

// Get the list of recently closed tabs
chrome.sessions.getRecentlyClosed({ maxResults: 25 }, (sessions) => {
  // sessions contains closed tabs and window data
});

// Restore a specific tab
chrome.sessions.restore(sessionId, (restoredSession) => {
  // callback after successful restore
});

// Get favicon URL for a site
function getFaviconURL(url) {
  const faviconUrl = new URL(chrome.runtime.getURL('/_favicon/'));
  faviconUrl.searchParams.set('pageUrl', url);
  faviconUrl.searchParams.set('size', '16');
  return faviconUrl.toString();
}

API Quick Reference

https://developer.chrome.google.cn/docs/extensions/reference/api/sessions#getRecentlyClosed

https://developer.chrome.google.cn/docs/extensions/reference/api/sessions#restore

https://developer.chrome.google.cn/docs/extensions/reference/api/runtime#getURL

Data Recovery Flow

User clicks restore button
↓
chrome.sessions.restore(sessionId)
↓
Chrome finds session record
↓
Creates new tab and loads original URL
↓
Callback handles success/failure
↓
UI updates (removes restored item)

Installation (Developer Mode)

Open Chrome and go to chrome://extensions/.

Enable "Developer mode" in the top right.

Click "Load unpacked" and select the recently-closed-tabs folder.

Pin the extension icon to the toolbar and click it to use.

Installation steps
Installation steps

Directory Structure

recently-closed-tabs
├─ manifest.json
├─ popup.html
├─ styles.css
└─ popup.js

Full Code Samples

manifest.json (configuration)

{
  "manifest_version": 3,
  "name": "最近关闭标签页管理器",
  "version": "1.0",
  "description": "查看和恢复最近关闭的标签页",
  "permissions": ["sessions", "tabs", "favicon"],
  "action": {
    "default_popup": "popup.html",
    "default_title": "最近关闭的标签页"
  }
}

popup.html (UI markup)

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <link rel="stylesheet" href="styles.css"/>
  </head>
  <body>
    <div class="container">
      <div class="header">
        <div class="header-content">
          <h1>最近关闭</h1>
          <div id="tabCount">0 个标签页</div>
        </div>
        <button id="restoreAllBtn">全部恢复</button>
      </div>
      <div id="tabList" class="tab-list"><div class="loading"></div></div>
    </div>
    <script src="popup.js"></script>
  </body>
</html>

popup.js (logic)

// DOM elements
const elements = {
  tabList: document.getElementById('tabList'),
  restoreAllBtn: document.getElementById('restoreAllBtn'),
  tabCount: document.getElementById('tabCount')
};

const CONFIG = {
  MAX_TABS: 25,
  MESSAGES: {
    EMPTY: '<div class="empty-message">没有找到最近关闭的标签页</div>',
    RESTORED: '<div class="empty-message">所有标签页已恢复</div>'
  }
};

function init() {
  loadRecentlyClosedTabs();
  elements.restoreAllBtn.addEventListener('click', restoreAllTabs);
}

function loadRecentlyClosedTabs() {
  chrome.sessions.getRecentlyClosed({ maxResults: CONFIG.MAX_TABS }, (sessions) => {
    const tabs = sessions
      .filter(s => s.tab)
      .map(s => ({
        id: s.tab.sessionId,
        title: s.tab.title || '无标题',
        url: s.tab.url,
        closedTime: s.lastModified * 1000
      }));
    updateTabCount(tabs.length);
    renderTabList(tabs);
  });
}

function updateTabCount(count) {
  if (elements.tabCount) {
    elements.tabCount.textContent = `${count} 个标签页`;
  }
}

function renderTabList(tabs) {
  if (tabs.length === 0) {
    elements.tabList.innerHTML = CONFIG.MESSAGES.EMPTY;
    return;
  }
  const fragment = document.createDocumentFragment();
  tabs.forEach(tab => fragment.appendChild(createTabElement(tab)));
  elements.tabList.innerHTML = '';
  elements.tabList.appendChild(fragment);
}

function createTabElement(tab) {
  const div = document.createElement('div');
  div.className = 'tab-item';
  div.dataset.sessionId = tab.id;
  div.innerHTML = `
    <img class="favicon" src="${getFaviconURL(tab.url)}" alt="" />
    <div class="tab-info">
      <h3 class="tab-title">${escapeHTML(tab.title)}</h3>
      <p class="tab-url">${escapeHTML(tab.url)}</p>
    </div>
    <div class="closed-time">${formatTime(tab.closedTime)}</div>
  `;
  div.addEventListener('click', () => restoreTab(tab.id, div));
  return div;
}

function restoreTab(sessionId, element) {
  element.style.opacity = '0.5';
  element.style.pointerEvents = 'none';
  chrome.sessions.restore(sessionId, (restored) => {
    if (chrome.runtime.lastError) {
      console.error('恢复失败:', chrome.runtime.lastError);
      element.style.opacity = '1';
      element.style.pointerEvents = 'auto';
      return;
    }
    loadRecentlyClosedTabs();
  });
}

function restoreAllTabs() {
  chrome.sessions.getRecentlyClosed({ maxResults: CONFIG.MAX_TABS }, (sessions) => {
    const tabs = sessions.filter(s => s.tab);
    if (tabs.length === 0) return;
    elements.restoreAllBtn.disabled = true;
    Promise.all(tabs.map(s => new Promise(resolve => chrome.sessions.restore(s.tab.sessionId, resolve))))
      .then(() => {
        elements.tabList.innerHTML = CONFIG.MESSAGES.RESTORED;
        elements.restoreAllBtn.disabled = false;
      });
  });
}

function escapeHTML(str) {
  const map = {
    '&': '&',
    '<': '<',
    '>': '>',
    '"': '"',
    "'": '''
  };
  return str.replace(/[&<>"']/g, m => map[m]);
}

function getFaviconURL(url) {
  const faviconUrl = new URL(chrome.runtime.getURL('/_favicon/'));
  faviconUrl.searchParams.set('pageUrl', url);
  faviconUrl.searchParams.set('size', '16');
  return faviconUrl.toString();
}

function formatTime(timestamp) {
  const diff = Date.now() - timestamp;
  const units = [
    [86400000, d => `${d}天前`],
    [3600000, h => `${h}小时前`],
    [60000, m => `${m}分钟前`]
  ];
  for (const [unit, fmt] of units) {
    const value = Math.floor(diff / unit);
    if (value > 0) return fmt(value);
  }
  return '刚刚';
}

document.addEventListener('DOMContentLoaded', init);

styles.css (styling)

* { margin:0; padding:0; box-sizing:border-box; }
@keyframes fadeIn { from { opacity:0; transform:translateY(10px); } to { opacity:1; transform:translateY(0); } }
@keyframes float { 0%,100% { transform:translateY(0); } 50% { transform:translateY(-10px); } }
html,body { width:440px; height:600px; overflow:hidden; font-family:-apple-system,BlinkMacSystemFont,'SF Pro Display','Segoe UI',Roboto,sans-serif; background:#fff; color:#000; }
.container { width:100%; height:100%; display:flex; flex-direction:column; overflow:hidden; }
.header { flex-shrink:0; background:rgba(255,255,255,0.8); backdrop-filter:saturate(180%) blur(20px); padding:20px 20px 16px; border-bottom:0.5px solid rgba(0,0,0,0.06); }
.header-content { display:flex; justify-content:space-between; align-items:center; }
h1 { font-size:26px; font-weight:600; color:#000; }
.tab-count { font-size:13px; color:#8e8e93; margin-top:2px; }
.restore-all { background:#007aff; color:#fff; border:none; padding:9px 18px; border-radius:18px; cursor:pointer; font-size:14px; font-weight:500; transition:all 0.2s ease; box-shadow:0 2px 8px rgba(0,122,255,0.3); }
.restore-all:hover { background:#0051d5; transform:scale(1.02); box-shadow:0 4px 12px rgba(0,122,255,0.4); }
.restore-all:active { transform:scale(0.98); }
.tab-list { flex:1; overflow-y:auto; background:#fff; padding:12px 16px; }
.tab-item { display:flex; align-items:center; padding:12px 14px; margin-bottom:6px; background:#f9f9f9; border-radius:10px; cursor:pointer; transition:all 0.2s ease; animation:fadeIn 0.3s ease-out backwards; }
.tab-item:hover { background:#f0f0f0; transform:scale(1.005); }
.tab-item:active { transform:scale(0.995); background:#e8e8e8; }
.favicon { width:24px; height:24px; margin-right:12px; border-radius:6px; background:#fff; padding:2px; flex-shrink:0; }
.tab-info { flex:1; overflow:hidden; min-width:0; }
.tab-title { font-size:14px; font-weight:500; margin:0 0 3px 0; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; color:#000; }
.tab-url { font-size:12px; color:#8e8e93; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.closed-time { font-size:12px; color:#007aff; margin-left:12px; padding:5px 10px; background:rgba(0,122,255,0.1); border-radius:8px; font-weight:500; }
.empty-message { text-align:center; color:#8e8e93; padding:80px 20px; font-size:15px; animation:fadeIn 0.5s ease-out; }
.empty-message::before { content:'📭'; display:block; font-size:64px; margin-bottom:16px; animation:float 3s ease-in-out infinite; }
.empty-message::after { content:'没有最近关闭的标签页'; display:block; margin-top:8px; font-size:14px; color:#c7c7cc; }
.loading { text-align:center; padding:60px 20px; }
.loading::before { content:''; display:inline-block; width:40px; height:40px; border:3px solid rgba(0,122,255,0.2); border-top-color:#007aff; border-radius:50%; animation:spin 0.8s linear infinite; }
@keyframes spin { to { transform:rotate(360deg); } }

Usage Instructions

Open the extension popup to see the list of recently closed tabs.

Click a list item to restore that tab.

Click the "Restore All" button to recover all tabs at once.

If the list is empty, a message "没有最近关闭的标签页" will be shown.

FAQ

Can't see any records? You must have actually closed tabs recently; after a browser restart the records may be cleared.

Want to change the number of items displayed? Edit CONFIG.MAX_TABS in popup.js; the default maximum is 25.

Privacy and Permissions

The extension does not collect any user data.

All data comes from the built‑in chrome.sessions API and runs locally.

Minimal permissions required: sessions, tabs, and favicon.

Extension icon in toolbar
Extension icon in toolbar
JavaScriptfrontend developmentChrome ExtensionChrome APITab Recovery
Rare Earth Juejin Tech Community
Written by

Rare Earth Juejin Tech Community

Juejin, a tech community that helps developers grow.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.