0

I am building a Chrome Extension and when clicked on the toolbar icon, I am executing background.js, and in background.js I am running execute script. Like this:

chrome.tabs.executeScript(null, { file: "libs/jquery.min.js" }, function() {
    chrome.tabs.executeScript(null, { file: "scripts/myScript.js" })
});

In myScript.js, I can confirm that it has executed jQuery and running my script too.


myScript.js:

console.log('test');

$(document).ready(function() {
   console.log('jQuery test');
})

They both work


At this point, I am trying to achieve a variable that is inside the web page (not written by me); let's say it is var helloWorld = "the content".

If I try in the console (dev tools), console.log(helloWorld), it works. However, if I try in myScript.js, it says:

$(document).ready(function() {
    console.log(helloWorld)

    setTimeout(function() {
       console.log(helloWorld)
    }, 1500)
})

Uncaught ReferenceError: helloWorld is not defined


I have seen some workaround for content script approach, but I couldn't make it work for the executeScript approach. How can I access the helloWorld variable inside myScript.js?


I tried using Predator's approach. Here are the codes:

injected.js:

chrome.runtime.sendMessage("njfhnpgecmbmbipcjpkkglbkpcbjammb", {varName: "test"});
// I tried returning a string first

background.js

chrome.tabs.executeScript(null, { file: "libs/jquery.min.js" }, function() {
    chrome.tabs.executeScript(null, { file: "scripts/myScript.js" })
});

chrome.runtime.onMessageExternal.addListener( function(message, sender, sendResponse){
    console.log(message.varName);
    if( message.varName !== undefined ){
        console.log(message.varName);
    }
});

manifest:

"background": { 
   "scripts": ["scripts/background.js"],
   "persistent": true
 },

according to your first answer, myScript.js:

$(document).ready(function() {
    setTimeout(function() {
        var s = document.createElement('script');

        s.src = chrome.extension.getURL('scripts/fetcher.js');
        s.onload = function() {
            this.remove();
        };
        (document.head || document.documentElement).appendChild(s);
    }, 500)
})

according to your updated answer, myScript.js:

function exec(fn) {
    var script = document.createElement('script');
    script.setAttribute("type", "application/javascript");
    script.textContent = '(' + fn + ')();';
    document.body.appendChild(script); //run the script
    document.body.removeChild(script); //clean up
}

background.js is not catching the message

senty
  • 12,385
  • 28
  • 130
  • 260
  • I recommend reading [Message Passing](https://developer.chrome.com/extensions/messaging). – Luka Čelebić Sep 15 '17 at 22:07
  • How can I read the data on the 3rd party website DOM, so that I can use message passing? I have used Message Passing before to communicate between my scripts (background.js and content_script.js) but I stuck at the first part of the question (reading js variable on 3rd party website DOM) – senty Sep 15 '17 at 22:11
  • You inject the content script, get the variable and then send it to your background script to process. – Luka Čelebić Sep 15 '17 at 22:12
  • 1
    @PredatorIWD That is not possible. You don't have access to that. – Derek 朕會功夫 Sep 15 '17 at 22:12
  • @Derek朕會功夫 What part is not possible? – Luka Čelebić Sep 15 '17 at 22:13
  • @PredatorIWD Getting the variable. – Derek 朕會功夫 Sep 15 '17 at 22:14
  • @Derek朕會功夫 https://stackoverflow.com/a/9517879/6586663 – Luka Čelebić Sep 15 '17 at 22:15
  • @PredatorIWD Exactly. Your scripts are run in an "isolated environment" which means you cannot access the page's global scope in your content scripts. You can trick the system by injecting your extension's scripts, or via `location` as described in my answer. – Derek 朕會功夫 Sep 15 '17 at 22:18
  • 1
    @Derek朕會功夫 Once you injected some code from your content script into the web page and got the var you can send it to the background page with code: `chrome.runtime.sendMessage("extensionIdHere", {varName: varValue});` – Luka Čelebić Sep 15 '17 at 22:22
  • @PredatorIWD That's why I'm saying. It is impossible to access variables in your injected scripts, but you can trick the page itself to send a message to your extension. Also note that the message is serialized. – Derek 朕會功夫 Sep 15 '17 at 22:25
  • @senty No you did not access it. Keep in mind that you only received a serialized message. Try "accessing" an object or a function. Won't work. – Derek 朕會功夫 Sep 15 '17 at 22:26
  • @Derek朕會功夫 Yes, with the injected code you can also call any function on the site that you can also call when you open the console. I don't think it can get any more accessible. – Luka Čelebić Sep 15 '17 at 22:28
  • @PredatorIWD You can remotely call them, but fundamentally the scopes are strictly separated. There is absolutely no way you can access variables across this boundary. – Derek 朕會功夫 Sep 15 '17 at 22:29
  • I am trying to make it work and will let you know in a few. It makes sense that in injected script, I can `console.log(helloWorld)` however i am still trying to send it through Messaging. I'll let you know the message out. Also hardcoding the `extensionId` seem pretty ugly already – senty Sep 15 '17 at 22:29
  • @PredatorIWD If i use `chrome.runtime.sendMessage("extensionIdHere", {varName: varValue});`, how do I listen for it in content script? I couldn't make it work. – senty Sep 15 '17 at 22:35
  • @senty I just added that in my answer below. – Luka Čelebić Sep 15 '17 at 22:37

2 Answers2

1

Your content script is run in a separate context, which means it cannot access the webpage's global scope. However I did managed previously to get around this by injecting codes via

location.href = "javascript: /* some messaging code */";

Probably not be the best practice, but it worked for me (and there might be newer APIs that lets you do without this.)

Derek 朕會功夫
  • 92,235
  • 44
  • 185
  • 247
  • Can you please expand your approach more? I couldn't understand how exactly you got the data and passed it. I'd be grateful – senty Sep 15 '17 at 22:49
0

Like I said above in the comments after injecting the function or code from the content script to the website page you can externally send a message from that page to the background.js of your extension.

chrome.runtime.sendMessage("extensionIdHere", {varName: varValue});

After which you can have this code to listen to these external messages and respond to them or use the value that got sent.

chrome.runtime.onMessageExternal.addListener( function(message, sender, sendResponse){

    if( message.varName !== undefined ){
        //process the info
    }

});

Edit: First Try this and if it doesnt work read more below:

Add this at the bottom of your manifest.json and try again:

"externally_connectable": {
    "matches": [ "https://theSiteWhereYouAreInjectinTheCode.com/*" ]
}

If it still didn't work after that try adding this function to your content script that will inject that code to the web page.

//Injects functions directly into the dom
function exec(fn) {
    var script = document.createElement('script');
    script.setAttribute("type", "application/javascript");
    script.textContent = '(' + fn + ')();';
    document.body.appendChild(script); //run the script
    document.body.removeChild(script); //clean up
}

And then call it there like so:

exec( function(){
    chrome.runtime.sendMessage("njfhnpgecmbmbipcjpkkglbkpcbjammb", {varName: "helloWorld"});
});
Luka Čelebić
  • 1,083
  • 11
  • 21
  • @senty In your manifest at `"background"`, do you have `"persistent"` set to `true`? – Luka Čelebić Sep 15 '17 at 22:42
  • I just added it. It is still not returning anything. I couldn't catch it in `background.js`. In the injected script's sendMessage method, I tried seting up a callback, and it is indeed sending the message, but I couldn't get it in background.js as I said – senty Sep 15 '17 at 22:46
  • @senty Can you update the post with the exact code in the `background.js`, `manifest.json` and the code that you inject inside the web page which includes sending that external message? – Luka Čelebić Sep 15 '17 at 22:48
  • I updated the question and added the snippets. Can you please take a look? – senty Sep 15 '17 at 22:55
  • @senty First try sending `"helloWorld"` as a string for example to see if the messaging is working. – Luka Čelebić Sep 15 '17 at 22:57
  • It isn't sending as String either :/ I mean, it sends it (because if i set a callback, it goes into it), but I can't catch it on background.js – senty Sep 15 '17 at 22:59
  • @senty See my **Edit** on this answer. – Luka Čelebić Sep 15 '17 at 23:06
  • I edited my question and added both ways. But, for your recent update, where do I put `fetcher.js` ? I didn't use the file name at all.. no? – senty Sep 15 '17 at 23:12
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/154570/discussion-between-predatoriwd-and-senty). – Luka Čelebić Sep 15 '17 at 23:14