20

選択内容に基づいて Chrome コンテキスト メニューにエントリを作成しようとしています。Stackoverflow でこれに関するいくつかの質問を見つけましたが、そのすべてに対する答えは、現在の選択を見てコンテキスト メニューを作成する「マウスダウン」リスナーを備えたコンテンツ スクリプトを使用することです。

これを実装しましたが、常に機能するとは限りません。すべてのログ メッセージで、コンテキスト メニューが意図したとおりに変更されたと表示されることがありますが、表示されるコンテキスト メニューは更新されません。

これに基づいて、競合状態であると考えました。コードが完全に実行される前に、chrome がコンテキスト メニューのレンダリングを開始することがあります。

「contextmenu」と「mouseup」にeventListenerを追加してみました。後者は、ユーザーがマウスでテキストを選択したときにトリガーされるため、コンテキストメニューが表示されるずっと前に (数秒でも) 変更されます。この手法を使用しても、同じエラーが発生しています。

これは、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);

background.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({});
});
4

2 に答える 2

36

contextMenusAPI は、コンテキスト メニュー エントリを定義するために使用されますコンテキスト メニューを開く直前に呼び出す必要はありません。したがって、contextmenu イベントでエントリを作成する代わりに、イベントを使用しselectionchangeて contextmenu エントリを継続的に更新します。

エントリが適切に同期されていることを示すために、選択したテキストをコンテキスト メニュー エントリに表示するだけの簡単な例を示します。

このコンテンツ スクリプトを使用します。

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

バックグラウンドで、contextmenu エントリを 1 回だけ作成します。その後、contextmenu 項目を更新します (から取得した 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);
            }
        }
    }
});

この例を単純にするために、コンテキスト メニュー エントリは 1 つだけであると仮定しました。より多くのエントリをサポートする場合は、配列またはハッシュを作成して ID を格納します。

チップ

  • 最適化- API 呼び出しの数を減らすにchrome.contextMenusは、パラメーターの関連値をキャッシュします。次に、単純な===比較を使用して、contextMenu アイテムを作成/更新する必要があるかどうかを確認します。
  • デバッグ- すべてのchrome.contextMenusメソッドは非同期です。コードをデバッグするには、コールバック関数を または メソッドに.create渡し.removeます.update
于 2012-12-02T21:17:49.037 に答える