0

I want my background script in my chrome extension to send a message to the content script and then wait for the response. I tried a couple of different solutions I found but I always get the same error:

_generated_background_page.html:1 Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.

_generated_background_page.html:1 Error handling response: TypeError: Cannot read property 'getSelected' of undefined at chrome-extension://pkolpfkgeblgjiiaklpppfmeomlkbhop/background_script.js:11:25

I also already tried disabling all the other chrome extensions.

All the code that might be important:

//background_script.js

chrome.contextMenus.onClicked.addListener(function(info, tab){
    chrome.tabs.sendMessage(tab.id, {
        content: "getSelected"
    },  function(response) {
            console.log(response.getSelected());
        });
});
//content_script.js

chrome.runtime.onMessage.addListener(function(message, sender, callback) {
        if (message.content == "getSelected") {
            callback({getSelected: getSelected()});
    }
});
//manifest.json

{
  "manifest_version": 2,
  "content_scripts":[ {
    "matches": ["<all_urls>"],
    "js": ["content_script.js"]
  }],
  "background":{
    "scripts": ["background_script.js"]
  },
  "permissions": [
   "*://*/*",
   "activeTab",
   "declarativeContent",
   "storage",
   "contextMenus",
   "tabs"
   ]
}

Thanks in advance for the help :)

1 Answers1

0
  1. You need to define getSelected() in the content script, it's not a built-in function. You probably meant getSelection() which is a built-in function.
  2. When the response is sent it's not a function so it can't be invoked: you need to remove () in response.getSelected()
  3. Receiving end does not exist usually means the tab doesn't run the content script:

Apparently you want to get the currently selected text, in which case you don't need a declared content script at all so you can remove content_scripts section and instead use programmatic injection:

chrome.contextMenus.onClicked.addListener((info, tab) => {
  chrome.tabs.executeScript(tab.id, {
    frameId: info.frameId,
    runAt: 'document_start',
    code: 'getSelection().toString()',
  }, ([sel] = []) => {
    if (!chrome.runtime.lastError) {
      console.log(sel);
    }
  })
});

Notes:

  • getSelection() returns a Selection object which is a complex DOM object so it cannot be transferred, hence we explicitly extract the selection as a string.
  • Only simple types like strings, numbers, boolean, null, and arrays/objects of such simple types can be transferred.
  • We're using the frameId where the user invoked the menu
  • Using runAt: 'document_start' ensures the code runs immediately even if the page is still loading its initial HTML
  • For this particular task you don't need "*://*/*" or tabs in permissions.
  • Where to read console messages from background.js in a Chrome extension?

P.S. For more complex data extraction, use file: 'content.js', instead of code: .... and transfer the last expression from content.js like this:

function foo() {
  let results;
  // .......
  return results;
}

foo(); // this line should be the last executed expression in content.js
wOxxOm
  • 65,848
  • 11
  • 132
  • 136
  • thanks for the quick reply: i didnt send all the code... the getselection stuff was also in there but i figured it might be confusing. i tried the reloading after the extension is re-enabled - but still no success. I'm going to try the code-injection thing now. Thanks again :) –  Jun 29 '20 at 16:48
  • the thing is my content script doesnt just return the selection but instead an array of the dom path to get to the parent object of that selection. So my content script code is much longer than just "window.getSelection()". Can I still do all that in the code-injection? –  Jun 29 '20 at 17:00
  • Yes. You can use a function ([example](https://stackoverflow.com/a/4532567)) or a separate file via `file: 'content.js'` (its last evaluated expression will be returned). – wOxxOm Jun 29 '20 at 17:19
  • when i do this:``` chrome.tabs.executeScript(tab.id, { frameId: info.frameId, runAt: 'document_start', file: 'content_script_injection.js', }, ([sel] = []) => { if (!chrome.runtime.lastError) { console.log(sel); } })``` it correctly runs all the code (thanks :) ) but doesnt return the right variable (array) –  Jun 29 '20 at 18:37
  • Added an example in the answer. Also note that only simple types like strings, numbers, boolean, null, and arrays/objects of such simple types can be transferred. – wOxxOm Jun 30 '20 at 03:43
  • Hi, this is another question but im not going to create another question just for this (I hope you dont mind).Do you know how i could get all direct child elements from another element. i use: el.getElementsByTagName() but i think that not only returns the direct children but instead all of them. I've tried a couple of solutions but nothing works so far. I want to access a specific element of a page that doesnt have an id or anything –  Jun 30 '20 at 10:44
  • `[...element.children]` – wOxxOm Jun 30 '20 at 10:45
  • thanks xD i just realized how stupid my question was. when i have the children how can then search for a specific tag (for example all divs) in there? –  Jun 30 '20 at 10:50
  • so just element.children? –  Jun 30 '20 at 11:37
  • There's a better one: element.querySelector(':scope > div') and the same with querySelectorAll. Alternatively use the standard array methods with element.children, you can ask a new question or find a tutorial. – wOxxOm Jun 30 '20 at 11:50