0

I'm trying to display a different menu text based on the selection:

manifest.js:

{
  "name": "Test app",
  "description": "",
  "version": "1.0.1",
  "icons": {"16": "icons/16.png", "48": "icons/48.png", "128": "icons/128.png"},
  "permissions": ["contextMenus"],
  "background": {
    "scripts": ["js/test.js"],
    "persistent": true
  },
  "manifest_version": 2
}

js/test.js

function validateSelection(number) {
  if (parseInt(number) > 5) {
    return "Result: x > 5; (x = " + number +")";
  } else {
    return "Result: x <= 5; (x = " + number +")";
  }
}

var s_item = chrome.contextMenus.create({"title"   : validateSelection("%s") + " => Selection: %s",
                                         "contexts": ["selection"],
                                         "onclick" : genericOnClick,
                                         "enabled" : true
                                         });

However no matter what number I select I always get the output from the else clause in my validateSelection() function. What kind of object is passed to my function, is it string? How otherwise I can validate the selection?

  1. Selected number = 4


  2. Selected number = 9

NarūnasK
  • 4,564
  • 8
  • 50
  • 76
  • Here is the feature request for more dynamic contextMenus: https://code.google.com/p/chromium/issues/detail?id=60758 – Rob W Oct 19 '15 at 18:28

1 Answers1

3

You pass a literal string "%s" to validateSelection, so parseInt("%s") is NaN which is why the else branch is always executed. This is an immediately executing function which effectively returns Result: x > 5; (x = %s) and this string is used to create the menu item ONE TIME. Then every time you invoke the menu Chrome simply expands %s when the menu is displayed. It doesn't and can't pass this %s AGAIN into validateSelection because it wasn't a callback, it was an immeditely executed function as per javascript syntax.

The context menu title is not dynamic and there is no function callback parameter for the title.

To change it dynamically define a content script that will be injected on <all_urls> with a selectionchanged event listener that will change the context menu title before it's shown.

content script:

document.addEventListener("selectionchange", function(e) {
  chrome.runtime.sendMessage({selection: getSelection().toString()});
});

background script:

chrome.contextMenus.create({id: "menuSel", title: "", contexts: ["selection"]});

chrome.contextMenus.onClicked.addListener(function(info, tab) {
    .......
});

chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
    if ("selection" in msg) {
        chrome.contextMenus.update("menuSel", {title: validateSelection(msg.selection)});
    }
});

The pitfall is that if you select something in one tab without invoking the context menu, then switch to another tab and select something, then switch back and immediately rightclick the already selected text, the result will be wrong. This can be handled, though, in a chrome.tabs.onActivated listener in the background script (may require "permissions: ["tabs"] in manifest.json):

chrome.tabs.onActivated.addListener(function(info) {
    chrome.tabs.executeScript(info.tabId, {code: "getSelection().toString()"}, 
        function(results) {
            chrome.contextMenus.update("menuSel", {title: validateSelection(results[0])});
        }
    );
});

If you want to make it work on frames/iframes, use allFrames: true parameter in executeScript and determine which frame is active.

wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • Thank you for the clarification. Please see added images. You can see `(x = 4)` and `(x = 9)` which are respectively defined in my function statements `"(x = " + number +")"`. If the literal string `"%s"` has been passed to my function, shouldn't I be seeing the `(x = %s)`? – NarūnasK Oct 20 '15 at 15:09
  • Added the explanation into the answer. – wOxxOm Oct 20 '15 at 16:06