views:

1587

answers:

3

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.

+4  A: 

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);
Max Shawabkeh
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. :)
hheimbuerger
Order of content script execution can be defined via `run_at` statement http://code.google.com/chrome/extensions/content_scripts.html
NV
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.
hheimbuerger
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.
hheimbuerger
@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.
Max Shawabkeh
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!
hheimbuerger
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?
Duncan Bayne
+2  A: 

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.

NV
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?
hheimbuerger
What do you mean "by an anchor"?
NV
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.)
hheimbuerger
myscript.user.js: `location.href="javascript:(function(){window.setConfigOption=function(){ /* ... */ }})()"`
NV
I guess that would work too, but I went for Max S.' content scope runner-like approach for now. Thanks for your help!
hheimbuerger
A: 

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();
Bernard Choi