Просмотр исходного кода

fix(mv3): 解决 Service Worker 休眠后右键菜单失效问题

- menu.js: 改用单一全局 onClicked 监听器 + handler map,避免 SW 重启后
  per-item 监听器丢失(致谢 PR #540 @maomao-cloud 的思路)
- background.js: 添加 onStartup/onInstalled 事件重建菜单,确保 SW 每次
  唤醒后菜单可用;DynamicToolRunner 增加参数校验和 catch 兜底
- popup/index.js: popup 打开时主动发消息唤醒 SW;runHelper 增加 try/catch
  降级为直接 tabs.create
- 清理冗余 console.log 和已废弃的注释代码

Made-with: Cursor
Alien 13 часов назад
Родитель
Сommit
3ed36cafd0
3 измененных файлов с 46 добавлено и 48 удалено
  1. 13 25
      apps/background/background.js
  2. 17 13
      apps/background/menu.js
  3. 16 10
      apps/popup/index.js

+ 13 - 25
apps/background/background.js

@@ -172,12 +172,14 @@ let BgPageInstance = (function () {
      */
     FeHelperBg.DynamicToolRunner = async function (configs) {
 
+        if (!configs || typeof configs !== 'object') return;
         let tool = configs.tool || configs.page;
+        if (!tool) return;
+
         let withContent = configs.withContent;
         let activeTab = null;
         let query = configs.query;
 
-        // 如果是noPage模式,则表名只完成content-script的工作,直接发送命令即可
         if (configs.noPage) {
             let toolFunc = tool.replace(/-/g, '');
             chrome.tabs.query({active: true, currentWindow: true}, tabs => {
@@ -231,12 +233,14 @@ let BgPageInstance = (function () {
                     chrome.tabs.create({
                         url,
                         active: true
-                    }).then(tab => { FeJson[tab.id] = { content: withContent }; });
+                    }).then(tab => {
+                        FeJson[tab.id] = { content: withContent };
+                    }).catch(() => {});
                 } else {
                     chrome.tabs.update(tabId, {highlighted: true}).then(tab => {
                         FeJson[tab.id] = { content: withContent };
                         chrome.tabs.reload(tabId);
-                    });
+                    }).catch(() => {});
                 }
 
             });
@@ -637,12 +641,10 @@ let BgPageInstance = (function () {
             switch (reason) {
                 case 'install':
                     chrome.runtime.openOptionsPage();
-                    // 记录新安装用户
                     Statistics.recordInstallation();
                     break;
                 case 'update':
                     _animateTips('+++1');
-                    // 记录更新安装
                     Statistics.recordUpdate(previousVersion);
                     if (previousVersion === '2019.12.2415') {
                         notifyText({
@@ -650,18 +652,14 @@ let BgPageInstance = (function () {
                             autoClose: 5000
                         });
                     }
-
-                    // 从V2020.02.1413版本开始,本地的数据存储大部分迁移至chrome.storage.local
-                    // 这里需要对老版本升级过来的情况进行强制数据迁移
-                    let getAbsNum = num => parseInt(num.split(/\./).map(n => n.padStart(4, '0')).join(''), 10);
-                    // let preVN = getAbsNum(previousVersion);
-                    // let minVN = getAbsNum('2020.02.1413');
-                    // if (preVN < minVN) {
-                    //     Awesome.makeStorageUnlimited();
-                    //     setTimeout(() => chrome.runtime.reload(), 1000 * 5);
-                    // }
                     break;
             }
+            Menu.rebuild();
+        });
+
+        // MV3: Service Worker 每次重新启动时确保菜单可用
+        chrome.runtime.onStartup.addListener(() => {
+            Menu.rebuild();
         });
         
         // 卸载
@@ -697,24 +695,14 @@ let BgPageInstance = (function () {
      * 初始化
      */
     let _init = function () {
-        console.log(`[FeHelper] Background初始化开始 - ${new Date().toLocaleString()}`);
-        console.log(`[FeHelper] 扩展版本: ${chrome.runtime.getManifest().version}`);
-        console.log(`[FeHelper] Service Worker启动原因: ${chrome.runtime.getContexts ? 'Context API可用' : '传统模式'}`);
-        
         _checkUpdate();
         _addExtensionListener();
-        
-        // 初始化统计功能
         Statistics.init();
-        
         Menu.rebuild();
         
-        // 定期清理冗余的垃圾
         setTimeout(() => {
             Awesome.gcLocalFiles();
         }, 1000 * 10);
-        
-        console.log(`[FeHelper] Background初始化完成 - ${new Date().toLocaleString()}`);
     };
 
     /**

+ 17 - 13
apps/background/menu.js

@@ -12,7 +12,8 @@ import Settings from '../options/settings.js';
 export default (function () {
 
     let FeJson = {
-        contextMenuId:"fhm_main"
+        contextMenuId:"fhm_main",
+        menuClickHandlers: {}
     };
 
     // 邮件菜单配置项
@@ -115,6 +116,14 @@ export default (function () {
         });
     })();
 
+    // MV3: 单一全局监听器,通过 handler map 分发点击事件(避免 SW 重启后监听器丢失)
+    chrome.contextMenus.onClicked.addListener((info, tab) => {
+        let handler = FeJson.menuClickHandlers[info.menuItemId];
+        if (handler) {
+            handler(info, tab);
+        }
+    });
+
     /**
      * 创建一个menu 菜单
      * @param toolName
@@ -125,25 +134,19 @@ export default (function () {
     let _createItem = (toolName, menuList) => {
         menuList && menuList.forEach && menuList.forEach(menu => {
 
-            // 确保每次创建出来的是一个新的主菜单,防止onClick事件冲突
-            let menuItemId = 'fhm_c' + escape(menu.text).replace(/\W/g,'') + new Date*1;
+            let menuItemId = 'fhm_c' + escape(menu.text).replace(/\W/g,'') + Date.now() + Math.floor(Math.random() * 1000);
 
             chrome.contextMenus.create({
                 id: menuItemId,
                 title: menu.icon + '  ' + menu.text,
                 contexts: menu.contexts || ['all'],
                 parentId: FeJson.contextMenuId
+            }, () => {
+                if (chrome.runtime.lastError) return;
+                FeJson.menuClickHandlers[menuItemId] = menu.onClick || (() => {
+                    globalThis.FeHelperBg.DynamicToolRunner({ tool: toolName });
+                });
             });
-
-            chrome.contextMenus.onClicked.addListener(((tool,mId,mFunc) => (info, tab) => {
-                if(info.menuItemId === mId) {
-                    if(mFunc) {
-                        mFunc(info,tab);
-                    }else{
-                        globalThis.FeHelperBg.DynamicToolRunner({ tool });
-                    }
-                }
-            })(toolName,menuItemId,menu.onClick));
         });
     };
 
@@ -164,6 +167,7 @@ export default (function () {
      * 创建扩展专属的右键菜单
      */
     let _initMenus = function () {
+        FeJson.menuClickHandlers = {};
         _removeContextMenu(() => {
             let id = chrome.contextMenus.create({
                 id: FeJson.contextMenuId ,

+ 16 - 10
apps/popup/index.js

@@ -46,10 +46,11 @@ new Vue({
     },
 
     created: function () {
-        // 获取当前ctx的version
         this.manifest = chrome.runtime.getManifest();
         
-        // 立即开始加载工具列表,不阻塞页面渲染
+        // 主动唤醒 Service Worker,确保后台就绪
+        chrome.runtime.sendMessage({ type: 'fh-popup-opened' }).catch(() => {});
+        
         this.loadTools();
 
         // 页面加载时自动获取并注入popup页面的补丁
@@ -240,17 +241,26 @@ new Vue({
         runHelper: async function (toolName) {
             if (!toolName || !this.fhTools[toolName]) return;
             
+            let tool = this.fhTools[toolName];
             let request = {
                 type: MSG_TYPE.OPEN_DYNAMIC_TOOL,
                 page: toolName,
-                noPage: !!this.fhTools[toolName].noPage
+                noPage: !!tool.noPage
             };
-            if(this.fhTools[toolName]._devTool) {
+            if (tool._devTool) {
                 request.page = 'dynamic';
                 request.query = `tool=${toolName}`;
             }
-            chrome.runtime.sendMessage(request);
-            !!this.fhTools[toolName].noPage && setTimeout(window.close, 200);
+            try {
+                await chrome.runtime.sendMessage(request);
+                !!tool.noPage && setTimeout(window.close, 200);
+            } catch (e) {
+                // SW 可能已休眠,降级为直接打开工具页
+                chrome.tabs.create({
+                    url: `/${toolName}/index.html` + (request.query ? `?${request.query}` : ''),
+                    active: true
+                });
+            }
         },
 
         openOptionsPage: () => {
@@ -276,12 +286,8 @@ new Vue({
             // 根据工具数量添加相应的类
             if (installedCount <= 1) {
                 container.classList.add('very-few-tools');
-                console.log('Popup布局:应用very-few-tools类 (工具数量:', installedCount, ')');
             } else if (installedCount <= 3) {
                 container.classList.add('few-tools');
-                console.log('Popup布局:应用few-tools类 (工具数量:', installedCount, ')');
-            } else {
-                console.log('Popup布局:使用默认布局 (工具数量:', installedCount, ')');
             }
         }
     }