I am trying to write a google chrome extension where I use a contextmenu. This contextmenu is available on editable elements only (input texts for example). When the contextmenu is clicked and executed I would like to retrieve in the callback function the element (the input text) on which the contextmenu has been executed in order to update the value associated to this input text.
Here is the skeleton of my extension:
function mycallback(info, tab) {
// missing part that refers to the question:
// how to retrieve elt which is assumed to be
// the element on which the contextMenu has been executed ?
elt.value = "my new value"
}
var id = chrome.contextMenus.create({
"title": "Click me",
"contexts": ["editable"],
"onclick": mycallback
});
The parameters associated to the mycallback function contain no useful information to retrieve the right clicked element. It seems this is a known issue (http://code.google.com/p/chromium/issues/detail?id=39507) but there is no progress since several months. Does someone knows a workaround: without jquery and/or with jquery?
You can inject content script with contextmenu event listener and store element that was clicked:
manifest.json
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content.js"],
"all_frames": true,
"match_about_blank": true
}]
content script.js
//content script
var clickedEl = null;
document.addEventListener("contextmenu", function(event){
clickedEl = event.target;
}, true);
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request == "getClickedEl") {
sendResponse({value: clickedEl.value});
}
});
background.js
//background
function mycallback(info, tab) {
chrome.tabs.sendMessage(tab.id, "getClickedEl", {frameId: info.frameId}, data => {
elt.value = data.value;
});
}
Related
I am trying to add text to an editable field with a context menu.
I tried to follow this SO but I cannot seem to get it to add the text to the field.
This is my content, which seems to make sense. I believe it is adding the context for what the background script is looking for.
var clickedEl = null;
document.addEventListener("mousedown", function(event){
//right click
if(event.button == 2) {
clickedEl = event.target;
}
}, true);
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if(request == "getClickedEl") {
sendResponse({value: clickedEl.value});
}
});
And here is what I have for my Background script. This is the part where I am not sure if I am doing it correctly.
function onClickHandler(info, tab) {
if (info.menuItemId.indexOf("context") > -1) {
var type = info.menuItemId.replace('context', '');
theLog = type;
function mycallback(info, tab) {
chrome.tabs.sendMessage(tab.id, "getClickedEl", function(clickedEl) {
elt.value = theLog.value;
});
}
}
}
Your background script runs in a separate hidden page with its own URL and DOM, which cannot access the web page directly, see the architecture overview in the documentation. Simply send the text to the content script, which will then use document.execCommand to insert the value into the active element.
Solution 1.
content script:
chrome.runtime.onMessage.addListener(msg => {
document.execCommand('insertText', false, msg);
});
background script:
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId.includes('context')) {
const text = info.menuItemId.replace('context', '');
chrome.tabs.sendMessage(tab.id, text, {frameId: info.frameId || 0});
}
}
Note we're sending directly to the frame where the context menu was invoked, which is needed in the general case (maybe not in yours) with the content script running in all iframes which is declared in manifest.json:
"content_scripts": [{
"matches": ["<all_urls>"],
"all_frames": true,
"match_about_blank": true,
"js": ["content.js"]
}]
Solution 2.
However, if this is the only function of the content script, it's better not to declare it in manifest.json at all, but instead inject dynamically in the background script:
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId.includes('context')) {
const text = info.menuItemId.replace('context', '');
chrome.tabs.executeScript(tab.id, {
frameId: info.frameId || 0,
matchAboutBlank: true,
code: `document.execCommand('insertText', false, ${JSON.stringify(text)})`,
});
}
}
And add the permission in manifest.json that doesn't require a user confirmation on installation (documentation):
"permissions": ["activeTab"]
Is there a way to show context menu actions, only when the user right-clicks on classes that start with "story".
For example: if the user right-clicks on an object in the page of class "story ....", the context menu buttons should appear, otherwise nothing should happen.
Here is my code (though it does not work):
var divs = document.querySelectorAll("[class^=story]"); //get all classes that start with "Story"
window.oncontextmenu = function() {
for(var i=0; i < divs.length; i++)
{
divs[i].onclick = function() {
chrome.contextMenus.create
(
{"id": "butto1", "title": "1", "contexts":["all"], "onclick": genericOnClick}
);
chrome.contextMenus.create
(
{"id": "button2", "title": "2", "contexts":["all"], "onclick": genericOnClick}
);
chrome.contextMenus.create
(
{"id": "button3", "title": "3", "contexts":["all"], "onclick": genericOnClick}
);
};
}
return true;
};
function genericOnClick(info, tab) {
//do some crap here
chrome.contextMenus.removeAll();
}
In this related answer, I explained that context menu items cannot be created on the fly, because the time between a contextmenu event and the appearance of the context menu item is not sufficient to get a chrome.contextMenus.create call in between.
The other answer explains how to make sure that the context menu entry shows the selected text. This was done by listening to the selectionchange event. For your purpose, we want to use an event which has the desired timing.
I'm going to use the mouseover and mouseout events. By depending on mouse events, the context menu will not work when you use the keyboard, e.g. by focusing an element using JavaScript or the Tab key, followed by pressing the context menu key. I did not implement it in the solution below.
The demo consists of three files (plus an HTML page to test it). I put all files in a zip file, available at https://robwu.nl/contextmenu-dom.zip.
manifest.json
Every Chrome extension requires this file in order to work. For this application, a background page and content script is used. In addition, the contextMenus permission is required.
{
"name": "Contextmenu based on activated element",
"description": "Demo for https://stackoverflow.com/q/14829677",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"content_scripts": [{
"run_at": "document_idle",
"js": ["contentscript.js"],
"matches": ["<all_urls>"]
}],
"permissions": [
"contextMenus"
]
}
background.js
The background page will create the context menus on behalf of the content script. This is achieved by binding an event listener to chrome.runtime.onConnect, which offers a simple interface to replace context menu entries.
chrome.contextMenus.create is called whenever a message is received over this port (from the content script). All properties, except for onclick are JSON-serializable, so only the "onclick" handler needs a special treatment. I've used a string to identify a pre-defined function in a dictionary (var clickHandlers).
var lastTabId;
// Remove context menus for a given tab, if needed
function removeContextMenus(tabId) {
if (lastTabId === tabId) chrome.contextMenus.removeAll();
}
// chrome.contextMenus onclick handlers:
var clickHandlers = {
'example': function(info, tab) {
// This event handler receives two arguments, as defined at
// https://developer.chrome.com/extensions/contextMenus#property-onClicked-callback
// Example: Notify the tab's content script of something
// chrome.tabs.sendMessage(tab.id, ...some JSON-serializable data... );
// Example: Remove contextmenus for context
removeContextMenus(tab.id);
}
};
chrome.runtime.onConnect.addListener(function(port) {
if (!port.sender.tab || port.name != 'contextMenus') {
// Unexpected / unknown port, do not interfere with it
return;
}
var tabId = port.sender.tab.id;
port.onDisconnect.addListener(function() {
removeContextMenus(tabId);
});
// Whenever a message is posted, expect that it's identical to type
// createProperties of chrome.contextMenus.create, except for onclick.
// "onclick" should be a string which maps to a predefined function
port.onMessage.addListener(function(newEntries) {
chrome.contextMenus.removeAll(function() {
for (var i=0; i<newEntries.length; i++) {
var createProperties = newEntries[i];
createProperties.onclick = clickHandlers[createProperties.onclick];
chrome.contextMenus.create(createProperties);
}
});
});
});
// When a tab is removed, check if it added any context menu entries. If so, remove it
chrome.tabs.onRemoved.addListener(removeContextMenus);
contentscript.js
The first part of this script creates simple methods for creating context menus.
In the last 5 lines, the context menu entries are defined and bound to all current and future elements which match the given selector. Like I said in the previous section, the argument type is identical to the createProperties argument of chrome.contextMenus.create except for "onclick", which is a string which maps to a function in the background page.
// Port management
var _port;
var getPort = function() {
if (_port) return _port;
_port = chrome.runtime.connect({name: 'contextMenus'});
_port.onDisconnect.addListener(function() {
_port = null;
});
return _port;
}
// listOfCreateProperties is an array of createProperties, which is defined at
// https://developer.chrome.com/extensions/contextMenus#method-create
// with a single exception: "onclick" is a string which corresponds to a function
// at the background page. (Functions are not JSON-serializable, hence this approach)
function addContextMenuTo(selector, listOfCreateProperties) {
// Selector used to match an element. Match if an element, or its child is hovered
selector = selector + ', ' + selector + ' *';
var matches;
['matches', 'webkitMatchesSelector', 'webkitMatches', 'matchesSelector'].some(function(m) {
if (m in document.documentElement) {
matches = m;
return true;
}
});
// Bind a single mouseover+mouseout event to catch hovers over all current and future elements.
var isHovering = false;
document.addEventListener('mouseover', function(event) {
if (event.target && event.target[matches](selector)) {
getPort().postMessage(listOfCreateProperties);
isHovering = true;
} else if(isHovering) {
getPort().postMessage([]);
isHovering = false;
}
});
document.addEventListener('mouseout', function(event) {
if (isHovering && (!event.target || !event.target[matches](selector))) {
getPort().postMessage([]);
isHovering = false;
}
});
}
// Example: Bind the context menus to the elements which contain a class attribute starts with "story"
addContextMenuTo('[class^=story]', [
{"id": "butto1", "title": "1", "contexts":["all"], "onclick": 'example'},
{"id": "button2", "title": "2", "contexts":["all"], "onclick": 'example'},
{"id": "button3", "title": "3", "contexts":["all"], "onclick": 'example'}
]);
The previous code assumes that all context menu clicks are handled by the background page. If you want to handle the logic in the content script instead, you need to bind message events in the content script. I've shown an (commented) instance of chrome.tabs.sendMessage in the background.js example, to show where this event should be triggered.
If you need to identify which element triggered the event, don't use a predefined function (in a dictionary) as shown in my example, but an inline function or a factory function. To identify the element, a message needs to be paired with an unique identifier. I'll leave the task of creating this implementation to the reader (it's not difficult).
Till date
"all", "page", "frame", "selection", "link", "editable", "image", "video", "audio", "launcher" are the contexts only supported. It is not possible to customize per class of an Object.
Work Around
If you are looking to show context menu actions, only when the user right-clicks on certain div.class on the page use mouse over menu as shown here
I am having issues with getting access to the Chrome's tab ID. I can fetch it, but it remains inside the extension and I cannot use it outside the extension, despite the fact that I was able to record keyboard events outside the extension.
Here's what I'm trying to do:
User navigates to a tab and fetches the tabId with a 'capture' button
The tabId is stored as a global variable
User then can navigate to any other tab inside his browser and from there with a key combination the user can reload the captured tab at any given moment by pressing CTRL + SHIFT simultaneously
extension.html
<!DOCTYPE html>
<html>
<head>
<title>Extension</title>
<style>
body {
min-width: 357px;
overflow-x: hidden;
}
</style>
<p>Step 1. Navigate to tab you want to refresh and click the 'capture' button</p>
<button type="button" id="capture">Capture!</button>
<p id="page"></p>
<p>Step 2. Now you can reload that tab from anywhere by pressing CTRL+SHIFT simultaneously</p>
</div>
<script src="contentscript.js"></script>
</head>
<body>
</body>
</html>
manifest.json
{
"manifest_version": 2,
"name": "Extension",
"description": "This extension allows you to trigger page refresh on key combinations from anywhere",
"version": "1.0",
"content_scripts": [
{
"matches": ["http://*/*","https://*/*"],
"run_at": "document_end",
"js": ["contentscript.js"]
}
],
"browser_action": {
"default_icon": "icon.png",
"default_popup": "extension.html"
},
"web_accessible_resources": ["script.js"],
"permissions": [
"tabs"
],
}
contentscript.js
var s = document.createElement('script');
s.src = chrome.extension.getURL("script.js");
(document.head||document.documentElement).appendChild(s);
s.parentNode.removeChild(s);
script.js
'use strict';
var isCtrl = false;
var tabId = 0;
document.onkeyup=function(e){
if(e.which === 17) {
isCtrl=false;
}
};
document.onkeydown=function(e){
if(e.which === 17) {
isCtrl=true;
}
if(e.which === 16 && isCtrl === true) {
/* the code below will execute when CTRL + SHIFT are pressed */
/* end of code */
return false;
}
};
document.getElementById('capture').onclick = function(){
chrome.tabs.getSelected(null, function(tab) {
tabId = tab.id;
document.getElementById('page').innerText = tab.id;
});
};
I thought this would be the solution, but it didn't work:
/* the code below will execute when CTRL + SHIFT are pressed */
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.reload(tabId);
});
/* end of code */
Having var tabId = 0; as a global variable seems pointless so I thought message passing should be the solution, but the problem with that is that I don't understand how I should implement it.
Any suggestions on how to refresh the tab from anywhere based on its ID?
Your contentscript.js is just a file with programmatic instructions written in JavaScript. Those instructions are interpreted as fresh and new each time they are loaded into a particular execution environment. Your popup and your content scripts are separate execution environments.
The contentscript.js file itself does not store state. When contentscript.js is loaded in a content script environment, the content script execution environment has no idea where else contentscript.js has been included.
The correct pattern to use here would be to have a background page maintain state and remember the tab ID of the last captured tab. The popup would use message passing to send the current tab ID to the background page (using chrome.runtime.sendMessage in the popup and chrome.runtime.onMessage in the background page). Then, later, the content script would send a message to the background page when it saw a Ctrl+Shift press, and the background page would invoke chrome.tabs.reload(tabId).
Inside extension.html, instead of your current <script> tag:
document.getElementById("capture").onclick = function() {
chrome.tabs.getSelected(null, function(tab) {
tabId = tab.id;
// send a request to the background page to store a new tabId
chrome.runtime.sendMessage({type:"new tabid", tabid:tabId});
});
};
Inside contentscript.js:
/* the code below will execute when CTRL + SHIFT are pressed */
// signal to the background page that it's time to refresh
chrome.runtime.sendMessage({type:"refresh"});
/* end of code */
background.js:
// maintaining state in the background
var tabId = null;
// listening for new tabIds and refresh requests
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
// if this is a store request, save the tabid
if(request.type == "new tabid") {
tabId = request.tabid;
}
// if this is a refresh request, refresh the tab if it has been set
else if(request.type == "refresh" && tabId !== null) {
chrome.tabs.reload(tabId);
}
});
Is there a way to show context menu actions, only when the user right-clicks on classes that start with "story".
For example: if the user right-clicks on an object in the page of class "story ....", the context menu buttons should appear, otherwise nothing should happen.
Here is my code (though it does not work):
var divs = document.querySelectorAll("[class^=story]"); //get all classes that start with "Story"
window.oncontextmenu = function() {
for(var i=0; i < divs.length; i++)
{
divs[i].onclick = function() {
chrome.contextMenus.create
(
{"id": "butto1", "title": "1", "contexts":["all"], "onclick": genericOnClick}
);
chrome.contextMenus.create
(
{"id": "button2", "title": "2", "contexts":["all"], "onclick": genericOnClick}
);
chrome.contextMenus.create
(
{"id": "button3", "title": "3", "contexts":["all"], "onclick": genericOnClick}
);
};
}
return true;
};
function genericOnClick(info, tab) {
//do some crap here
chrome.contextMenus.removeAll();
}
In this related answer, I explained that context menu items cannot be created on the fly, because the time between a contextmenu event and the appearance of the context menu item is not sufficient to get a chrome.contextMenus.create call in between.
The other answer explains how to make sure that the context menu entry shows the selected text. This was done by listening to the selectionchange event. For your purpose, we want to use an event which has the desired timing.
I'm going to use the mouseover and mouseout events. By depending on mouse events, the context menu will not work when you use the keyboard, e.g. by focusing an element using JavaScript or the Tab key, followed by pressing the context menu key. I did not implement it in the solution below.
The demo consists of three files (plus an HTML page to test it). I put all files in a zip file, available at https://robwu.nl/contextmenu-dom.zip.
manifest.json
Every Chrome extension requires this file in order to work. For this application, a background page and content script is used. In addition, the contextMenus permission is required.
{
"name": "Contextmenu based on activated element",
"description": "Demo for https://stackoverflow.com/q/14829677",
"version": "1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"content_scripts": [{
"run_at": "document_idle",
"js": ["contentscript.js"],
"matches": ["<all_urls>"]
}],
"permissions": [
"contextMenus"
]
}
background.js
The background page will create the context menus on behalf of the content script. This is achieved by binding an event listener to chrome.runtime.onConnect, which offers a simple interface to replace context menu entries.
chrome.contextMenus.create is called whenever a message is received over this port (from the content script). All properties, except for onclick are JSON-serializable, so only the "onclick" handler needs a special treatment. I've used a string to identify a pre-defined function in a dictionary (var clickHandlers).
var lastTabId;
// Remove context menus for a given tab, if needed
function removeContextMenus(tabId) {
if (lastTabId === tabId) chrome.contextMenus.removeAll();
}
// chrome.contextMenus onclick handlers:
var clickHandlers = {
'example': function(info, tab) {
// This event handler receives two arguments, as defined at
// https://developer.chrome.com/extensions/contextMenus#property-onClicked-callback
// Example: Notify the tab's content script of something
// chrome.tabs.sendMessage(tab.id, ...some JSON-serializable data... );
// Example: Remove contextmenus for context
removeContextMenus(tab.id);
}
};
chrome.runtime.onConnect.addListener(function(port) {
if (!port.sender.tab || port.name != 'contextMenus') {
// Unexpected / unknown port, do not interfere with it
return;
}
var tabId = port.sender.tab.id;
port.onDisconnect.addListener(function() {
removeContextMenus(tabId);
});
// Whenever a message is posted, expect that it's identical to type
// createProperties of chrome.contextMenus.create, except for onclick.
// "onclick" should be a string which maps to a predefined function
port.onMessage.addListener(function(newEntries) {
chrome.contextMenus.removeAll(function() {
for (var i=0; i<newEntries.length; i++) {
var createProperties = newEntries[i];
createProperties.onclick = clickHandlers[createProperties.onclick];
chrome.contextMenus.create(createProperties);
}
});
});
});
// When a tab is removed, check if it added any context menu entries. If so, remove it
chrome.tabs.onRemoved.addListener(removeContextMenus);
contentscript.js
The first part of this script creates simple methods for creating context menus.
In the last 5 lines, the context menu entries are defined and bound to all current and future elements which match the given selector. Like I said in the previous section, the argument type is identical to the createProperties argument of chrome.contextMenus.create except for "onclick", which is a string which maps to a function in the background page.
// Port management
var _port;
var getPort = function() {
if (_port) return _port;
_port = chrome.runtime.connect({name: 'contextMenus'});
_port.onDisconnect.addListener(function() {
_port = null;
});
return _port;
}
// listOfCreateProperties is an array of createProperties, which is defined at
// https://developer.chrome.com/extensions/contextMenus#method-create
// with a single exception: "onclick" is a string which corresponds to a function
// at the background page. (Functions are not JSON-serializable, hence this approach)
function addContextMenuTo(selector, listOfCreateProperties) {
// Selector used to match an element. Match if an element, or its child is hovered
selector = selector + ', ' + selector + ' *';
var matches;
['matches', 'webkitMatchesSelector', 'webkitMatches', 'matchesSelector'].some(function(m) {
if (m in document.documentElement) {
matches = m;
return true;
}
});
// Bind a single mouseover+mouseout event to catch hovers over all current and future elements.
var isHovering = false;
document.addEventListener('mouseover', function(event) {
if (event.target && event.target[matches](selector)) {
getPort().postMessage(listOfCreateProperties);
isHovering = true;
} else if(isHovering) {
getPort().postMessage([]);
isHovering = false;
}
});
document.addEventListener('mouseout', function(event) {
if (isHovering && (!event.target || !event.target[matches](selector))) {
getPort().postMessage([]);
isHovering = false;
}
});
}
// Example: Bind the context menus to the elements which contain a class attribute starts with "story"
addContextMenuTo('[class^=story]', [
{"id": "butto1", "title": "1", "contexts":["all"], "onclick": 'example'},
{"id": "button2", "title": "2", "contexts":["all"], "onclick": 'example'},
{"id": "button3", "title": "3", "contexts":["all"], "onclick": 'example'}
]);
The previous code assumes that all context menu clicks are handled by the background page. If you want to handle the logic in the content script instead, you need to bind message events in the content script. I've shown an (commented) instance of chrome.tabs.sendMessage in the background.js example, to show where this event should be triggered.
If you need to identify which element triggered the event, don't use a predefined function (in a dictionary) as shown in my example, but an inline function or a factory function. To identify the element, a message needs to be paired with an unique identifier. I'll leave the task of creating this implementation to the reader (it's not difficult).
Till date
"all", "page", "frame", "selection", "link", "editable", "image", "video", "audio", "launcher" are the contexts only supported. It is not possible to customize per class of an Object.
Work Around
If you are looking to show context menu actions, only when the user right-clicks on certain div.class on the page use mouse over menu as shown here
I am trying to create entries on the Chrome context menu based on what is selected.
I found several questions about this on Stackoverflow, and for all of them the answer is: use a content script with a "mousedown" listener that looks at the current selection and creates the Context Menu.
I implemented this, but it does not always work. Sometimes all the log messages say that the context menu was modified as I wanted, but the context menu that appears is not updated.
Based on this I suspected it was a race condition: sometimes chrome starts rendering the context menu before the code ran completely.
I tried adding a eventListener to "contextmenu" and "mouseup". The later triggers when the user selects the text with the mouse, so it changes the contextmenu much before it appears (even seconds). Even with this technique, I still see the same error happening!
This happens very often in Chrome 22.0.1229.94 (Mac), occasionally in Chromium 20.0.1132.47 (linux) and it did not happen in 2 minutes trying on Windows (Chrome 22.0.1229.94).
What is happening exactly? How can I fix that? Is there any other workaround?
Here is a simplified version of my code (not so simple because I am keeping the log messages):
manifest.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({});
});
The contextMenus API is used to define context menu entries. It does not need to be called right before a context menu is opened. So, instead of creating the entries on the contextmenu event, use the selectionchange event to continuously update the contextmenu entry.
I will show a simple example which just displays the selected text in the context menu entry, to show that the entries are synchronized well.
Use this content script:
document.addEventListener('selectionchange', function() {
var selection = window.getSelection().toString().trim();
chrome.runtime.sendMessage({
request: 'updateContextMenu',
selection: selection
});
});
At the background, we're going to create the contextmenu entry only once. After that, we update the contextmenu item (using the ID which we get from chrome.contextMenus.create).
When the selection is empty, we remove the context menu entry if needed.
// 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);
}
}
}
});
To keep this example simple, I assumed that there's only one context menu entry. If you want to support more entries, create an array or hash to store the IDs.
Tips
Optimization - To reduce the number of chrome.contextMenus API calls, cache the relevant values of the parameters. Then, use a simple === comparison to check whether the contextMenu item need to be created/updated.
Debugging - All chrome.contextMenus methods are asynchronous. To debug your code, pass a callback function to the .create, .remove or .update methods.
MDN doc for menus.create(), 'title' param
You can use "%s" in the string. If you do this in a menu item, and some text is selected in the page when the menu is shown, then the selected text will be interpolated into the title.
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/menus/create
Thus
browser.contextMenus.create({
id: 'menu-search',
title: "Search '%s'", // selected text as %s
contexts: ['selection'], // show only if selection exist
})