取决于所选文本的动态扩展上下文菜单

IT技术 javascript google-chrome-extension contextmenu
2021-01-23 08:18:41

我正在尝试根据所选内容在 Chrome 上下文菜单上创建条目。我在 Stackoverflow 上发现了几个关于此的问题,对于所有这些问题,答案是:使用带有“mousedown”侦听器的内容脚本来查看当前选择并创建上下文菜单。

我实现了这个,但它并不总是有效。有时所有的日志消息都说上下文菜单按照我的意愿进行了修改,但出现的上下文菜单没有更新。

基于此,我怀疑这是一种竞争条件:有时 chrome 会在代码完全运行之前开始呈现上下文菜单。

我尝试向“contextmenu”和“mouseup”添加一个事件监听器。当用户用鼠标选择文本时,后者会触发,因此它会在上下文菜单出现之前(甚至几秒钟)改变它。即使使用这种技术,我仍然看到同样的错误发生!

这种情况在 Chrome 22.0.1229.94 (Mac) 中经常发生,偶尔在 Chromium 20.0.1132.47 (linux) 中发生,并且在 Windows (Chrome 22.0.1229.94) 上尝试 2 分钟后没有发生。

究竟发生了什么?我该如何解决?还有其他解决方法吗?


这是我的代码的简化版本(不是那么简单,因为我保留了日志消息):

清单.json:

{
  "name": "Test",
  "version": "0.1",
  "permissions": ["contextMenus"],
  "content_scripts": [{
    "matches": ["http://*/*", "https://*/*"],
    "js": ["content_script.js"]
  }],
  "background": {
    "scripts": ["background.js"]
  },
  "manifest_version": 2
}

content_script.js

function loadContextMenu() {
  var selection = window.getSelection().toString().trim();
  chrome.extension.sendMessage({request: 'loadContextMenu', selection: selection}, function (response) {
    console.log('sendMessage callback');
  });
}

document.addEventListener('mousedown', function(event){
  if (event.button == 2) {
    loadContextMenu();
  }
}, true);

背景.js

function SelectionType(str) {
  if (str.match("^[0-9]+$"))
    return "number";
  else if (str.match("^[a-z]+$"))
    return "lowercase string";
  else
    return "other";
}

chrome.extension.onMessage.addListener(function(msg, sender, sendResponse) {
  console.log("msg.request = " + msg.request);
  if (msg.request == "loadContextMenu") {
    var type = SelectionType(msg.selection);
    console.log("selection = " + msg.selection + ", type = " + type);
    if (type == "number" || type == "lowercase string") {
      console.log("Creating context menu with title = " + type);
      chrome.contextMenus.removeAll(function() {
        console.log("contextMenus.removeAll callback");
        chrome.contextMenus.create(
            {"title": type,
             "contexts": ["selection"],
             "onclick": function(info, tab) {alert(1);}},
            function() {
                console.log("ContextMenu.create callback! Error? " + chrome.extension.lastError);});
      });
    } else {
      console.log("Removing context menu")
      chrome.contextMenus.removeAll(function() {
          console.log("contextMenus.removeAll callback");
      });
    }
    console.log("handling message 'loadContextMenu' done.");
  }
  sendResponse({});
});
1个回答

contextMenusAPI是用来定义上下文菜单项。不需要在打开上下文菜单之前立即调用它。因此,不要在 contextmenu 事件上创建条目,而是使用该selectionchange事件来不断更新 contextmenu 条目。

我将展示一个简单的例子,它只在上下文菜单条目中显示选定的文本,以显示条目同步良好。

使用此内容脚本:

document.addEventListener('selectionchange', function() {
    var selection = window.getSelection().toString().trim();
    chrome.runtime.sendMessage({
        request: 'updateContextMenu',
        selection: selection
    });
});

在后台,我们将只创建一次上下文菜单条目。之后,我们更新上下文菜单项(使用我们从中获得的 ID chrome.contextMenus.create)。
选择为空时,如果需要,我们会删除上下文菜单条目。

// ID to manage the context menu entry
var cmid;
var cm_clickHandler = function(clickData, tab) {
    alert('Selected ' + clickData.selectionText + ' in ' + tab.url);
};

chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
    if (msg.request === 'updateContextMenu') {
        var type = msg.selection;
        if (type == '') {
            // Remove the context menu entry
            if (cmid != null) {
                chrome.contextMenus.remove(cmid);
                cmid = null; // Invalidate entry now to avoid race conditions
            } // else: No contextmenu ID, so nothing to remove
        } else { // Add/update context menu entry
            var options = {
                title: type,
                contexts: ['selection'],
                onclick: cm_clickHandler
            };
            if (cmid != null) {
                chrome.contextMenus.update(cmid, options);
            } else {
                // Create new menu, and remember the ID
                cmid = chrome.contextMenus.create(options);
            }
        }
    }
});

为了让这个例子简单,我假设只有一个上下文菜单条目。如果您想支持更多条目,请创建一个数组或散列来存储 ID。

尖端

  • 优化- 为了减少chrome.contextMenusAPI 调用次数,缓存参数的相关值。然后,使用简单的===比较来检查是否需要创建/更新 contextMenu 项。
  • 调试- 所有chrome.contextMenus方法都是异步的。要调试代码,请将回调函数传递给.create,.remove.update方法。
这仍然是最佳答案吗?在绘制菜单时在某处设置一个监听器似乎更直观。
2021-03-30 08:18:41
如果用户开始选择文本,然后在松开左按钮之前按下右按钮,这会失败吗?
2021-04-02 08:18:41
非常感谢,我会奖励赏金,尽管我真的很想你能解释为什么以前的代码(在问题中)不起作用,或者随机工作:)
2021-04-03 08:18:41
我刚才说的有道理,但如果你想自己看看:放在方法alert('Hi');之前chrome.contextMenus.create您将看到上下文菜单在闪烁:它由于右键单击事件而显示,但由于显示了模式对话框(警报)而隐藏。回想一下alert在您创建/更新上下文菜单条目之前放置的,因此这证明chrome.contextMenu.*调用为时已晚。
2021-04-05 08:18:41
@epoch 当用户使用鼠标右键时,会异步地向后台页面发送一条消息然后后台页面异步删除所有现有的上下文菜单条目最后,创建上下文菜单条目,也是异步的您可以想象这些操作花费的时间相对较多。在没有 触发event.preventDefault()上下文菜单/鼠标事件后,上下文菜单将打开。不久之后,该chrome.contextMenus.create方法结束。
2021-04-06 08:18:41