5

This is code from my contentScript.js:

function loadScript(script_url)
  {
      var head= document.getElementsByTagName('head')[0];
      var script= document.createElement('script');
      script.type= 'text/javascript';
      script.src= chrome.extension.getURL('mySuperScript.js');
      head.appendChild(script);
      someFunctionFromMySuperScript(request.widgetFrame);// ReferenceError: someFunctionFromMySuperScript is not defined
  }

but i got an error when calling a function from injected script:

ReferenceError: someFunctionFromMySuperScript is not defined

Is there is a way to call this function without modifying mySuperScript.js?

askona
  • 390
  • 1
  • 5
  • 16
  • 1
    http://stackoverflow.com/questions/14705593/javascript-can-not-call-content-script-js-function You might want to take a look at this question. – Arnold Stoba Mar 22 '16 at 12:13

3 Answers3

2

Your code suffers from multiple problems:

  1. As you've noticed, the functions and variables from the injected script (mySuperScript.js) are not directly visible to the content script (contentScript.js). That is because the two scripts run in different execution environments.
  2. Inserting a <script> element with a script referenced through a src attribute does not immediately cause the script to execute. Therefore, even if the scripts were to run in the same environment, then you can still not access it.

To solve the issue, first consider whether it is really necessary to run mySuperScript.js in the page. If you don't to access any JavaScript objects from the page itself, then you don't need to inject a script. You should try to minimize the amount of code that runs in the page itself to avoid conflicts.

If you don't have to run the code in the page, then run mySuperScript.js before contentScript.js, and then any functions and variables are immediately available (as usual, via the manifest or by programmatic injection). If for some reason the script really needs to be loaded dynamically, then you could declare it in web_accessible_resources and use fetch or XMLHttpRequest to load the script, and then eval to run it in your content script's context.

For example:

function loadScript(scriptUrl, callback) {
    var scriptUrl = chrome.runtime.getURL(scriptUrl);
    fetch(scriptUrl).then(function(response) {
        return response.text();
    }).then(function(responseText) {
        // Optional: Set sourceURL so that the debugger can correctly
        // map the source code back to the original script URL.
        responseText += '\n//# sourceURL=' + scriptUrl;
        // eval is normally frowned upon, but we are executing static
        // extension scripts, so that is safe.
        window.eval(responseText);
        callback();
    });
}

// Usage:
loadScript('mySuperScript.js', function() {
    someFunctionFromMySuperScript();
});

If you really have to call a function in the page from the script (i.e. mySuperScript.js must absolutely run in the context of the page), then you could inject another script (via any of the techniques from Building a Chrome Extension - Inject code in a page using a Content script) and then pass the message back to the content script (e.g. using custom events).

For example:

var script = document.createElement('script');
script.src = chrome.runtime.getURL('mySuperScript.js');
// We have to use .onload to wait until the script has loaded and executed.
script.onload = function() {
    this.remove(); // Clean-up previous script tag
    var s = document.createElement('script');
    s.addEventListener('my-event-todo-rename', function(e) {
        // TODO: Do something with e.detail
        // (= result of someFunctionFromMySuperScript() in page)
        console.log('Potentially untrusted result: ', e.detail);
        // ^ Untrusted because anything in the page can spoof the event.
    });
    s.textContent = `(function() {
        var currentScript = document.currentScript;
        var result = someFunctionFromMySuperScript();
        currentScript.dispatchEvent(new CustomEvent('my-event-todo-rename', {
            detail: result,
        }));
    })()`;

    // Inject to run above script in the page.
    (document.head || document.documentElement).appendChild(s);
    // Because we use .textContent, the script is synchronously executed.
    // So now we can safely remove the script (to clean up).
    s.remove();
};
(document.head || document.documentElement).appendChild(script);

(in the above example I'm using template literals, which are supported in Chrome 41+)

Rob W
  • 341,306
  • 83
  • 791
  • 678
1

As long as the someFunctionFromMySuperScript function is global you can call it, however you need to wait for the code actually be loaded.

function loadScript(script_url)
  {
      var head= document.getElementsByTagName('head')[0];
      var script= document.createElement('script');
      script.type= 'text/javascript';
      script.src= chrome.extension.getURL('mySuperScript.js');
      script.onload = function () {
          someFunctionFromMySuperScript(request.widgetFrame);         
      }
      head.appendChild(script);
  }

You can also use jQuery's getScript method.

Xan
  • 74,770
  • 16
  • 179
  • 206
Georgi-it
  • 3,676
  • 1
  • 20
  • 23
1

This doesn't work, because your content script and the injected script live in different contexts: what you inject into the page is in the page context instead.

  1. If you just want to load code dynamically into the content script context, you can't do it from the content script - you need to ask a background page to do executeScript on your behalf.

    // Content script
    chrome.runtime.sendMessage({injectScript: "mySuperScript.js"}, function(response) {
      // You can use someFunctionFromMySuperScript here
    });
    
    // Background script
    chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
      if (message.injectScript) {
        chrome.tabs.executeScript(
          sender.tab.id,
          {
            frameId: sender.frameId},
            file: message.injectScript
          },
          function() { sendResponse(true); }
        );
        return true; // Since sendResponse is called asynchronously
      }
    });
    
  2. If you need to inject code in the page context, then your method is correct but you can't call it directly. Use other methods to communicate with it, such as custom DOM events.

Xan
  • 74,770
  • 16
  • 179
  • 206
  • Thanks for reply! I used 1st method for running myScript from background.js. But unfortunately, myScript (which add new div to DOM) works everywhere except NewTab page, and only way to modify NewTab page DOM is doing it from content script. So i need to execute myScript from context of content script. It`s possible to do this way: **eval(myScript.plain_code); someFunctionFromMySuperScript (); ** This works fine, but i`m trying to find a way to avoid using eval. – askona Mar 22 '16 at 15:59
  • There is no need to avoid `eval` if the only thing you feed to it is your own file. Eval is evil when input is untrusted. But then again, I don't see how you can be executing anything on a `chrome:` page - it's supposed to be impossible. You should look into [overriding New Tab page](https://developer.chrome.com/extensions/override) instead of trying to modify an existing one (though it's not recommended for small modifications). – Xan Mar 22 '16 at 16:02
  • A much simpler solution without involving the background page is to use fetch or XMLHttpRequest and then `window.eval`. – Rob W Mar 22 '16 at 22:20
  • @RobW And I encourage you to post that as an answer! It probably needs to be web-accessible then though. – Xan Mar 22 '16 at 22:21