2

I'm having a really weird issue, I've developped a webextension that uses messaging between content script and background script (using chrome.runtime.connect) and nativemessaging.

The issue i'm facing is that when I install the extension (manually from the store beforehand and then connect to my website, everything works as expected, the chrome.runtime.connect works and returns a valid port to the background script.

But when i do an inline install of the extension from my website to get around the fact to have to navigate to have the content script in the webpage, i manually inject the content script into my page using

 function injectContentScript() {
             var s = document.createElement("script");
             s.setAttribute("src", "chrome-extension://<extensionid>/content.js");
             document.head.appendChild(s);
         }

and the exact same content script but manually injected doesn't behave the same. chrome.runtime.connect returns a null object and chrome.runtime.lastError gives me

Could not establish connection. Receiving end does not exist.

I'm calling on the sender side (content.js - manually injected content script) chrome.runtime.connect(extensionID) where extension id is the id of the extension generated by the chrome webstore. And on the receiving side (background.js - extension background script) chrome.runtime.onConnect.addListener(onPortConnected);

I'm not really sure how to debug this issue, maybe it's a timing issue? The background script is well executed even with the inline install (i've added logs and debugged it through the background.html in chrome extension manager)

Any help would be greatly appreciated!

MeeKaaH
  • 211
  • 2
  • 11
  • It's not clear from your question what the _sending_ side is and how it's executed. – Xan Aug 24 '17 at 10:20
  • indeed, (i just edited the question) but yes the "sending" side is the content script and the "receiving" side is the background script. hope that's a bit clearer – MeeKaaH Aug 24 '17 at 10:38
  • You should _not_ provide the extension ID to `connect()` inside the extension! That's only for cross-extension messaging. Also, it's unclear when this happens to you after the install. – Xan Aug 24 '17 at 10:39
  • oh wow how did i miss that.. ok this might explain the weird behaviour i'll try without it! As for your other question, the sequence is basically 1. install 2. send a msg from website to extension (content script) 3. content script sends a msg to background script. – MeeKaaH Aug 24 '17 at 10:45
  • 1
    Ah, you may be misunderstanding something. Content scripts are _not_ injected on install into matching pages. Only new navigations since install will have content scripts running. Are you trying to send this message without navigating after install? – Xan Aug 24 '17 at 10:50
  • Ah yes this part i'm aware of. after the inline install i manually inject the content script into my page so that it's available. The content script is listed in the manifest under content script and web_accessible_resources. – MeeKaaH Aug 24 '17 at 10:52
  • 1
    This again rings some alarm bells. Can you include the injection code? – Xan Aug 24 '17 at 11:05
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/152740/discussion-between-meekaah-and-xan). – MeeKaaH Aug 24 '17 at 11:13

1 Answers1

3

You have two scenarios.

  1. Your content script content.js is executed as normal upon navigation, as a content script defined in the manifest.

    In this case, it executes in a special JS context attached to the page and reserved for your content scripts. See Execution Environment docs section for explanation. It is isolated from the webpage and is considered part of the extension (albeit with lower privileges).

    When you connect from a content script, chrome.runtime.connect() is treated as internal communication between parts of the extension. So while you can provide the extension ID, it is not needed.

    More importantly, the event raised in this case is chrome.runtime.onConnect.

  2. Your supposed "inject content script immediately" code called from the webpage does something completely different.

    Instead of creating a new execution context, the code is instead added directly to the page; it is not considered part of the extension and has no access to extension API.

    Normally, a call to chrome.runtime.connect() would simply fail, as this is not a function exposed to webpages; however, you also declared externally_connectable, so it is exposed specifically to your webpage.

    In this case, passing the extension ID is mandatory for the connect. You were doing this already, so the call was succeeding.

    However, and that's what made it fail: the corresponding event is no longer onConnect, but onConnectExternal!

What you should be doing is:

  • Not mixing code that is run in very different contexts.

  • If you need communication from the webpage to background, always do it from the webpage, not sometimes-from-content-sometimes-from-page.

    That way you only have to listen to onConnectExternal and it cuts out the need for a content script (if it was its only function).

    See the docs as well: Sending messages from web pages.

  • You don't have to source the code from chrome-extension://<extensionid>/; you can directly add this to your website's code and potentially avoid web_accessible_resources.

And if you actually want to inject content scripts on first run, see for example this answer.

Related reading: How to properly handle chrome extension updates from content scripts

Xan
  • 74,770
  • 16
  • 179
  • 206