Injecting JS functions into the page from a Greasemonkey script on Chrome

71,125

Solution 1

The only way to communicate with the code running on the page in Chrome is through the DOM, so you'll have to use a hack like inserting a <script> tag with your code. Note that this may prove buggy if your script needs to run before everything else on the page.

EDIT: Here's how the Nice Alert extension does this:

function main () {
  // ...
  window.alert = function() {/* ... */};
  // ...
}

var script = document.createElement('script');
script.appendChild(document.createTextNode('('+ main +')();'));
(document.body || document.head || document.documentElement).appendChild(script);

Solution 2

I have this :

contentscript.js :

function injectJs(link) {
var scr = document.createElement('script');
scr.type="text/javascript";
scr.src=link;
document.getElementsByTagName('head')[0].appendChild(scr)
//document.body.appendChild(scr);
}

injectJs(chrome.extension.getURL('injected.js'));

injected.js :

function main() {
     alert('Hello World!');
}

main();

Solution 3

The other answers either force you to use function expressions, import an external additional file or use a long patched hack.

This answer will add the javascript into the page directly from your source code. It will use ECMAScript 6 (ES6) template literals to get the multi-line javascript string effortlessly onto the page.

var script = document.createElement('script'); 
script.type = "text/javascript"; 
script.innerHTML = ` 
   function test() {
      alert(1);
   }
`;
document.getElementsByTagName('head')[0].appendChild(script);

Please note the backticks `` that define the beginning and the end of a multi-line string.

Solution 4

I took a quick look at the alternatives to unsafeWindow on the Greasemonkey wiki, but they all look pretty ugly. Am I completely on the wrong track here or should I look more closely into these?

You should look, because it's only available option. I'd prefer to use location hack.

myscript.user.js:

function myFunc(){
  alert('Hello World!');
}

location.href="javascript:(function(){" + myFunc + "})()"

example.com/mypage.html

<script>
myFunc() // Hello World!
</script>

Sure, it's ugly. But it's working well.


Content Scope Runner method, mentioned by Max S. is better than location hack, because its easier to debug.

Share:
71,125
Jonathan Geisler
Author by

Jonathan Geisler

Updated on July 09, 2022

Comments

  • Jonathan Geisler
    Jonathan Geisler almost 2 years

    I have a Greasemonkey script that works just fine in Firefox and Opera. I struggle with getting it to work in Chrome, however. The problem is injecting a function into the page that can be invoked by code from the page. Here's what I'm doing so far:

    First, I get a helper reference to the unsafeWindow for Firefox. This allows me to have the same code for FF and Opera (and Chrome, I thought).

    var uw = (this.unsafeWindow) ? this.unsafeWindow : window;
    

    Next, I inject a function into the page. It's really just a very thin wrapper that does nothing but invoking the corresponding function in the context of my GM script:

    uw.setConfigOption = function(newValue) {
        setTimeout(setConfigOption, 0, newValue);
    }
    

    Then, there's the corresponding function right in my script:

    setConfigOption = function(newValue) {
        // do something with it, e.g. store in localStorage
    }
    

    Last, I inject some HTML into the page with a link to invoke the function.

    var p = document.createElement('p');
    p.innerHTML = '<a href="javascript:setConfigOption(1)">set config option to 1</a>';
    document.getElementById('injection-point').appendChild(p);
    

    To summarize: In Firefox, when the user clicks that injected link, it will execute the function call on the unsafeWindow, which then triggers a timeout that invokes the corresponding function in the context of my GM script, which then does the actual processing. (Correct me if I'm wrong here.)

    In Chrome, I just get a "Uncaught ReferenceError: setConfigOption is not defined" error. And indeed, entering "window.setConfigOption" into the console yields an "undefined". In Firebug and the Opera developer console, the function is there.

    Maybe there's another way to do this, but a few of my functions are invoked by a Flash object on the page, which I believe makes it necessary that I have functions in the page context.

    I took a quick look at the alternatives to unsafeWindow on the Greasemonkey wiki, but they all look pretty ugly. Am I completely on the wrong track here or should I look more closely into these?

    RESOLUTION: I followed Max S.' advice and it works in both Firefox and Chrome now. Because the functions I needed to be available to the page had to call back into the regular ones, I moved my whole script to the page, i.e. it is completely wrapped into the function he called 'main()'.

    To make the extra uglyness of that hack a little bit more bearable, I could at least drop the usage of unsafeWindow and wrappedJSObject now.

    I still haven't managed to get the content scope runner from the Greasemonkey wiki working. It should do the same and it seems to execute just fine, but my functions are never accessible to <a> elements from the page, for example. I haven't yet figured out why that is.

  • Jonathan Geisler
    Jonathan Geisler about 14 years
    Could you please post an example of how that can be used to define a function that can be invoked by an anchor or a Flash object?
  • Jonathan Geisler
    Jonathan Geisler about 14 years
    I tried the 'content scope runner' (which does exactly that, if I'm not mistaken), and while the functions were available to my script, they didn't seem to be available to the page (e.g. an anchor tag). Do you have any example code or anything you can link to? I'm not very concerned about the order of execution. I can work around that when I manage to call an injected function from the page. :)
  • NVI
    NVI about 14 years
    What do you mean "by an anchor"?
  • NVI
    NVI about 14 years
    Order of content script execution can be defined via run_at statement code.google.com/chrome/extensions/content_scripts.html
  • Jonathan Geisler
    Jonathan Geisler about 14 years
    With an <a> tag, as in my example code, e.g. <a href="javascript:setConfigOption(1)">. (I think if this works, it should from the Flash object, too.)
  • Jonathan Geisler
    Jonathan Geisler about 14 years
    re run_at: I'm not writing a Chrome extension, though (and I don't intend to, too much work to maintain two scripts). This is just a Greasemonkey script that I'm trying to make compatible enough so that Chrome can do its 'auto-conversion' of GM scripts to extensions.
  • NVI
    NVI about 14 years
    myscript.user.js: location.href="javascript:(function(){window.setConfigOption‌​=function(){ /* ... */ }})()"
  • Jonathan Geisler
    Jonathan Geisler about 14 years
    While your isolated example works for me, I haven't gotten that working in my full script yet. I guess, unlike when using unsafeWindow, I can't call back into functions that are directly in my script now. I guess I'll have to move everything into the page context then. I'll try that tomorrow.
  • Max Shawabkeh
    Max Shawabkeh about 14 years
    @NV: run_at=document_start will probably have problems with inserting the <script> into a DOM that is not yet loaded. @hheimbuerger: yes, wrap all your code into a function that insert it with a script.
  • Jonathan Geisler
    Jonathan Geisler about 14 years
    It works now in both FF and Chrome using your approach. I'm still a bit stumped why it doesn't work with the content scope runner (can't see a real difference), but I've given up for now and using this method. Thanks for your help!
  • Jonathan Geisler
    Jonathan Geisler about 14 years
    I guess that would work too, but I went for Max S.' content scope runner-like approach for now. Thanks for your help!
  • Duncan Bayne
    Duncan Bayne over 13 years
    Thanks - this is just what I need for my own project. Isn't it funny though how it's 2010 and we're still asking questions like "this Javascript works on Browser X, why won't it work on Browser Y?" I wonder what the total worldwide productivity cost of this sort of browser compatibility issue is?
  • Raja
    Raja over 13 years
    In my case, I really must callback into the userscript scope. My current plan is to drop messages from the content scope into the DOM which the userscript scope will check for on a timer. When a message is seen, the userscript can call a function in its own scope, with arguments picked up from the message.
  • Piotrek Okoński
    Piotrek Okoński about 12 years
    This one's great! Also shortcut for people using jQuery: function injectjs(link) { $('<script type="text/javascript" src="'+link+'"/>').appendTo($('head')); }
  • baptx
    baptx over 7 years
    Good but if you use document.getElementsByTagName("head")[0] it will work in any browser without the need of a fallback. And it is even cleaner to use Content Script Injection: wiki.greasespot.net/Content_Script_Injection Functions can be available outside GreaseMonkey scope by simply writing window.functionName = function() {alert("test");};
  • user136036
    user136036 about 6 years
    ""In Firefox version 39.0.3 Mozilla patched a known security vulnerability. As a side effect, this broke the location hack.""