0

I meet a problem while creating a Chrome extension.

I create an <iframe> that is meant to be embedded by a content script in any web page at the moment the user clicks on my extension button.

In this <iframe> there is a select box that, when changed, should send a message back to the extension.

The extension should then get data from a Google spreadsheet and then send back information to the <iframe>.

My manifest.json is the following (editor note: filtered to relevant fields):

{
  // manifest.json

  "background" : {
    "scripts" : ["background.js"]
  },
  "browser_action": {
    "default_icon": "icon-small.png"
  },
  "content_scripts": [ {
    "js": ["jquery-1.11.1.min.js","selectionjfm.js","jquery-ui.min.js","nlp_compromise.js"],
    "css":["jquery-ui.css"],
    "matches": [ "http://*/*", "https://*/*"],
    "all_frames":true,
    "run_at": "document_end",
    "info.status":"complete", "status": "complete"
  }],
  // ...    
  "permissions": [
    "identity", "https://accounts.google.com/*", "https://www.googleapis.com/*","https://spreadsheets.google.com/*","tabs","storage","<all_urls>"
  ],
  "web_accessible_resources": [
    "jquery-ui.min.js","popup.html","popup.js","inject.js","jquery-1.11.1.min.js","loader.gif"
  ],
  "oauth2": {
    "client_id": "xxxxxxxxx.apps.googleusercontent.com",
    "scopes": ["https://www.googleapis.com/auth/plus.login","https://spreadsheets.google.com/feeds"]
  } 
}

This extension includes a selectionjfm.js as content script that inserts an <iframe> this way :

// selectionjfm.js - content script as included in manifest.json

var iframe = document.createElement('iframe');
iframe.setAttribute("id", "my_id");
iframe.src = chrome.runtime.getURL('popup.html');
document.body.appendChild(iframe);

But also injects a JavaScript file inject.js this way:

// selectionjfm.js - content script as included in manifest.json

var s = document.createElement('script');
s.src = chrome.runtime.getURL('inject.js');
(document.head || document.documentElement).appendChild(s);
console.log("[Content script 1] added inject.js to the content page");
var s = document.createElement('script');
s.src = chrome.runtime.getURL('jquery-1.11.1.min.js');
(document.head || document.documentElement).appendChild(s);

The contains a select box. When the user choses another value in this box, i need to run a request against Google Spreadsheet using the Google Spreadsheet API. That is why I want to send a message back to the extention, for I understand it is only from there I can use those API's.

inject.js waits for a message sent by the <iframe> when onchange event on the <iframe>'s select box is triggered.

// inject.js waitning for message from the iframe
window.addEventListener("message",
    function (e) {...

Message sent by the <iframe> :

//poupup.js (iframe source)
var sel = document.getElementById("databaseselect");
            sel.addEventListener("change", function (e) {               
                sendMessTobg("changedatabase",sel.selectedOptions[0].text);     


            });

It then try to send a message to the background.js :

// inject.js

if (e.data.message == "changedatabase") { // recieving message from the iframe : this works fine
  if (e.data.data != null) {
    console.log("[Content script] trying to send databasechange to extension");
    chrome.runtime.sendMessage({
      databasechange:true,
      content:e.data.data
    }, function (reply) {
      if (chrome.runtime.lastError) {
        console.log("[Content script] failure to send databasechange to extension");
      } else {
        console.log("[Content script] databasechange with backgroud is a success!");
      }
    });
  }
}

it is then the chrome.runtime.sendMessage that gives the error :

Uncaught Error: Invalid arguments to connect.

I have seen in another post something about sending the extension ID, but the documentation said it was not mandatory, since if not given, the ID will be the one of my own extension.

  • So, to clarify, the last bit of code is in `inject.js`? It would be best if you included a comment like `// inject.js` right in every code block, because you often talk about one piece of code doing something to another. – Xan Jul 19 '16 at 08:18
  • And expanding on _"waits for a message sent by the ` – Xan Jul 19 '16 at 08:37

1 Answers1

0

The problem here is that your inject.js is no longer a content script. You added it to the DOM, and it became indistinguishable from the page's own code. Chrome no longer even knows it's part of an extension (and which extension).

As such, it can't call chrome.runtime.sendMessage the usual way - which is what this error is about. You need to perform communication differently.

externally_connectable does not apply to your case, since you want it to work on any website, and that method requires whitelisting specific domains.

So the logical recourse is to use DOM events to communicate to a content script which will pass the message.

..but wait, you're already using a DOM event. You're just catching it in the wrong context! Your "message" listener just has to be a content script, not a page-level script. Either add it to selectionjfm.js or think if you really need to inject inject.js to the page.

Xan
  • 74,770
  • 16
  • 179
  • 206
  • Xan OK cheers, it is not that simple to understand the different layers ! So I finally moved the call to `chrome.runtime.sendMessage` to the "real" content script `selectionjfm.js` and it works fine. Many thanks Xan ! (not enough credits to vote for your answer though :() – Jean-François Monteil-Lemoine Jul 19 '16 at 11:21