原创 不一样的少年_ 2025-10-30 08:30 重庆
点击关注公众号,技术干货及时达。
Ctrl/Cmd+Shift+T,她皱眉:“键盘上哪有这个键!!!”让她翻历史记录,她摇头:“根本找不到,全是我今天打开的!”最后指向左上角的“最近关闭”,她叹气:“才8个,根本不够用行,那就不讲道理,直接解决问题。于是我写了这个 Chrome 扩展—— 「“后悔药”」点一下就能看到“最近关闭的标签页”,想恢复单个点一下,想全恢复一键搞定。还有网站图标、关闭时间、顺滑的小动画,装上就能用。代码已经开源,想改界面随便改。「5分钟搞定安装」:复制代码 → 创建文件 → 加载扩展 → 开始使用!❞先看效果: 打开弹窗 → 点击恢复单个 → 全部恢复「API 文档速查」// 获取最近关闭的标签页列表chrome.sessions.getRecentlyClosed({ maxResults: 25 }, (sessions) => {// sessions 包含关闭的标签页、窗口数据});// 恢复特定标签页chrome.sessions.restore(sessionId, (restoredSession) => {// 恢复成功后的回调});// Chrome 内部获取网站图标的专用方式function getFaviconURL(url) {const faviconUrl = new URL(chrome.runtime.getURL('/_favicon/'));faviconUrl.searchParams.set('pageUrl', url);faviconUrl.searchParams.set('size', '16');return faviconUrl.toString();}
https://developer.chrome.google.cn/docs/extensions/reference/api/sessions?hl=zh-cn#method-getRecentlyClosed
https://developer.chrome.google.cn/docs/extensions/reference/api/sessions?hl=zh-cn#method-restore
https://developer.chrome.google.cn/docs/extensions/reference/api/runtime?hl=zh-cn#method-getURL
立即尝试「5分钟搞定安装」:复制代码 → 创建文件 → 加载扩展 → 开始使用!🚀 「浏览项目的完整代码可以点击这里 https://github.com/Teernage/recently-closed-tabs,如果对你有帮助欢迎Star。」目录结构用户点击恢复按钮↓chrome.sessions.restore(sessionId)↓Chrome 内部查找会话记录↓创建新标签页并加载原URL↓回调函数执行成功/失败处理↓更新界面状态(移除已恢复项)
完整代码「创建文件夹」「recently-closed-tabs├─ manifest.json├─ popup.html├─ styles.css└─ popup.js
recently-closed-tabs」「创建manifest.json文件」这是扩展的「配置文件」,定义了扩展的基本信息、权限要求和行为规范。关键点解读:{"manifest_version": 3,"name": "最近关闭标签页管理器","version": "1.0","description": "查看和恢复最近关闭的标签页","permissions": ["sessions", "tabs", "favicon"],"action": {"default_popup": "popup.html","default_title": "最近关闭的标签页"}}
字段
说明
manifest_version: 3使用最新的 Manifest V3 扩展规范,更安全、性能更好
name插件在应用商店和工具栏中显示的名称
version插件版本号,遵循语义化版本规范
description插件的功能描述,在管理页面中显示
permissions「申请的API权限」: • sessions - 访问浏览器会话数据,获取关闭的标签页
• tabs - 管理标签页,用于恢复关闭的页面
• favicon - 获取网站图标显示
action工具栏图标的行为配置: • default_popup - 点击图标弹出的页面
• default_title - 鼠标悬停时显示的提示文字
popup.html文件」 (弹窗界面UI)「创建<html><head><meta charset="utf-8" /><link rel="stylesheet" href="styles.css" /></head><body><div class="container"><div class="header"><div class="header-content"><div><h1>最近关闭</h1><div class="tab-count" id="tabCount">0 个标签页</div></div><button class="restore-all" id="restoreAllBtn">全部恢复</button></div></div><div class="tab-list" id="tabList"><div class="loading"></div></div></div><script src="popup.js"></script></body></html>
popup.js文件」 (弹窗界面交互)「创建// DOM元素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;});});}/*** 转义HTML*/function escapeHTML(str) {const map = {'&': '&','<': '<','>': '>','"': '"',"'": ''',};return str.replace(/[&<>"']/g, (m) => map[m]);}/*** 获取网站图标URL*/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, format] of units) {const value = Math.floor(diff / unit);if (value > 0) return format(value);}return '刚刚';}// 启动应用document.addEventListener('DOMContentLoaded', init);
styles.css文件」 (弹窗界面样式)安装方式(开发者模式)* {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: #ffffff;color: #000000;}.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);-webkit-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;letter-spacing: -0.5px;color: #000000;}.tab-count {font-size: 13px;color: #8e8e93;font-weight: 400;margin-top: 2px;}.restore-all {background: #007aff;color: white;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;overflow-x: hidden;background: #ffffff;padding: 12px 16px;}.tab-list::-webkit-scrollbar {width: 6px;}.tab-list::-webkit-scrollbar-track {background: transparent;}.tab-list::-webkit-scrollbar-thumb {background: rgba(0, 0, 0, 0.15);border-radius: 3px;}.tab-list::-webkit-scrollbar-thumb:hover {background: rgba(0, 0, 0, 0.25);}.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:nth-child(1) {animation-delay: 0.05s;}.tab-item:nth-child(2) {animation-delay: 0.1s;}.tab-item:nth-child(3) {animation-delay: 0.15s;}.tab-item:nth-child(4) {animation-delay: 0.2s;}.tab-item:nth-child(5) {animation-delay: 0.25s;}.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;flex-shrink: 0;background: white;padding: 2px;}.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: #000000;letter-spacing: -0.2px;line-height: 1.3;}.tab-url {font-size: 12px;color: #8e8e93;margin: 0;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;font-weight: 400;}.closed-time {font-size: 12px;color: #007aff;margin-left: 12px;padding: 5px 10px;background: rgba(0, 122, 255, 0.1);border-radius: 8px;white-space: nowrap;font-weight: 500;}.empty-message {text-align: center;color: #8e8e93;padding: 80px 20px;font-size: 15px;font-weight: 400;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);}}
