Help with message passing from background to content script

I have a small test extension that works with opera and chrome. I have troubles with sending message from the backgroud script to the content script.
The manifest file is

    "background": {
    "scripts": ["background.js"],
    "persistent": false

   "content_scripts": [ {
      "js": [ "content2.js" ],
      "matches": [ "\u003Call_urls>" ]
   } ],
   "description": "test",
   "manifest_version": 3,
   "name": "test",
   "optional_permissions": [  ],
     "permissions": ["contextMenus", "activeTab", "clipboardRead", "clipboardWrite" ],
   "version": "0.0.1"

The background script is

function buildContextMenu () {
	  let fcb =[ {fcb_context: "fcb_copy", title: "copy", context: ["selection", "link"]}, 
          {fcb_context:"fcb_paste", context:["editable"], title:"paste"}];
	 for (let i=0; i< fcb.length; i++) {
		 let menu = fcb[i];
	//title: "Look up: %s",
		title: menu.title,
		id: menu.fcb_context,
		contexts: menu.context,

	} // for i
} //buildContextMenu

function log(msg, force=false) {
var debug = true;
	if (force || debug){console.log(msg);}

//icon in child menu not possible

chrome.runtime.onInstalled.addListener( function () {
     log("onInstalled called");
}); //add listener

chrome.contextMenus.onClicked.addListener(function(info, tabs){ 
     let data, sel, fcb_context;
     let	id = info.menuItemId;
	//data =  skim.regex[id];
	log(" id: " + id ) ;
    sel = info.selectionText;
	fcb_context = info.parentMenuItemId;
    chrome.tabs.query({currentWindow: true,	active: true}, 
			function(tabs) {
			chrome.tabs.sendMessage(tabs[0].id, {data: "go", context: fcb_context, sel: sel}, function(){} );
   ( async () =>{
       const [tab] = await chrome.tabs.query({active: true, currentWindow: true});
       const response = await chrome.tabs.sendMessage(, {data: "go", context: fcb_context, sel: sel}, function(){} );
 }); //onClickedadd

chrome.runtime.onStartup.addListener(function() {
	 log("onStartup called");
}); //addListerner

The content script content2.js:

		function(request, sender, sendResponse) {
			console.log("received in cs: " + JSON.stringify(request));
            //var val;
                    let field =  document.activeElement;    
                    console.log("active element name: " +;
                     let val = "Clipboard content : " + clipText;
                         insertTextAtCursor(field, val);
                    return val;
                ).then((val) =>
                    console.log("val: " + val);
                    navigator.clipboard.writeText(val).then(()=> {
                    console.log("Set " + val + " to the clipboard");
            //let field =  document.activeElement;
           //let val = paste();
            //insertTextAtCursor(field, "Clipboard contenttt : " + val);

function insertTextAtCursor(el, text) {
    console.log("insertTextAtCursor: " + text)
    //if (! el) { el = document.activeElement; }
    console.log("el name: " +;
    let val = el.value;
    let endIndex;
    let range;
    let doc = el.ownerDocument;
    if (typeof el.selectionStart === 'number' &&
        typeof el.selectionEnd === 'number') {
        endIndex = el.selectionEnd;
        el.value = val.slice(0, endIndex) + text + val.slice(endIndex);
        el.selectionStart = el.selectionEnd = endIndex + text.length;
        // && doc.selection.createRange)
    } else if (doc.selection !== 'undefined' && doc.selection.createRange) {
        range = doc.selection.createRange();
        range.text = text;;


My problem is that most of the time a page loaded when the extension runs does not react to the past command which appears when the cursor is in a textarea. What am I doing wrong here ?

I don’t undestand what your extension does, or how to use it.
Please explain.

Is the content script actually executed? The “matches” value looks wrong. Also <all_urls> was not supported for some MV3 fields in Firefox and one had to use the alternative, something like *://*/*.

Regarding this piece:

Why supply the third parameter (callback) when using await?

I have to say, this code is crazy ugly formatted. If you are using IDE, look for a shortcut for auto-formatting :slight_smile:. If you are not using IDE, start now :slight_smile:.

Once the background script is running, go to a page with a search field (duckduckgo for example), put some string in the clipboard. Put the cursor in the search field and call paste, the last entry in the context menu. You should see "Clipbpoard content: … " in the field. It works on opera and chrome but fails most of the time with FF.

I’m using gvim to edit my code and tried to format with perltidy (found a .pertidy.rc somewhere which was supposed to clean the code, but it it’s that good). Do you have a tool to format javascript with gvim ?
The failures could well be that the content script is not loaded …
Thanks for helping anyway

You should really check if the content script is actually executed, like juraj.masiar asked.

Put this code at the beginning of content2.js:

I suspect the content script is not executed.
Here’s what I did to get your code working:

  1. Install the extension temporarily with
    web-ext run
  2. Go to
  3. Open the “Tools for Web Developers” with Ctrl-Shift-I and click the Console tab.
  4. Enable your extension on
    a. Left-click on the puzzle icon in the upper right corner
    b. Either hover the mouse pointer over the extension description, and left-click it.
    c. Or left-click the cogwheel icon and select "Always allow on" and reload the page.
    d. Or right-click the extension description and select "Always allow on" and reload the page.
1 Like

I installed npm on ubuntu 22.04

I install web-ext as explained here

When I run web-ext --version

I got

web-ext --version
await webExt.main(absolutePackageDir); ^^^^^ SyntaxError: Unexpected reserved word at Loader.moduleStrategy (internal/modules/esm/translators.js:133:18) at async link (internal/modules/esm/module_job.js:42:21)

What Node version are you running?

npm --version gives 8.5.1

That’s NPM, to check the node version, run:

node -v

Also check the issue I’ve linked:

Node -v says 12.22.9

I managed to upgrade Nodejs (now 19.7) and install a working web-ext.
I had to create a tmp dir in my home and give it with TMPDIR. Now following what Hans_squared said above, I see it works… and so what ? what should I do to have it working without web-ext ?

Thanks for advices !

Thanks for replying. I managed to get web-ext working and could see that the code in content2.js is called and the test works.

Where can I get from here ? how can I have this working in ff without web-ext ?

Well, I have found this is by design with MV3.

Changing reverting to manifest version 2 cures the problem. The content script is loaded when I open the page…

Was this obvious for you guys ? @hans_squared @juraj.masiar
Compared to opera and chrome, I find this button feature in ff quiet stupid


Nope, it’s not obvious at all.

It’s actually a combinations of two concepts - one the Chrome-like button that unites all extensions and one the Safari “extreme privacy” where no extensions are allowed to run on any website unless you explicitly enable them.

But I think this is still just an experiment and hopefully things will change for better once Firefox is more MV3 ready and once more people starts to complain. :smiley:

An afterthought: At the moment my extension manifest version remains at 2, while all the rest of the code is mv3 compatible. If the extension management tab showed a “allow this extension on all pages” checkbox that would bypass the control handled by the button, I could upgrade to version 3 of the manifest.