2

I just want to inject jQuery into a webpage from a safari extension. But only to some pages because adding jQuery as a start-/endscript would inject it to all pages and this makes browsing slow. I tried it by creating a script tag using its onload function:

var node = document.createElement('script');    
node.onload = function(){
    initjquerycheck(function($) {
        dosomethingusingjQuery($);
    });
};
node.async = "async";
node.type = "text/javascript";
node.src = "https://code.jquery.com/jquery-2.0.3.min.js";
document.getElementsByTagName('head')[0].appendChild(node);

to check if jquery is loaded i use:

initjquerycheck: function(callback) {
    if(typeof(jQuery) != 'undefined'){
        callback(jQuery);
    }else {
        window.setTimeout(function() { initjquerycheck(callback); }, 100);
    }
}

But typeof(jQuery) remains undefined. (checked that using console.log()). Only if I call console.log(typeof(jQuery)) from the debugging console it returns 'function'. Any ideas how to fix that? Thanks in advance!

Malte Goetz
  • 792
  • 6
  • 16

1 Answers1

5

Extension injected scripts cannot access the web page's JavaScript namespace. Your injected script creates a <script> element and adds it to the page's DOM, but then the jQuery object instantiated by the script belongs to the page's namespace, not to your injected script's.

There are at least two potential solutions. One, inject jQuery into the page the normal way, using the extension API. This method is only viable if the web pages that you are targeting can be categorized using URL patterns.

Two, use Window.postMessage to communicate between your injected script and the web page's namespace. You will need to add another <script> to the page, and in this script, have a listener for the message event. The listener will be able to use jQuery as if it were "native" to the page itself.

Here's some code to get you started, if needed.

In the extension injected script:

var s0 = document.createElement('script');
s0.type = 'text/javascript';
s0.src = 'https://code.jquery.com/jquery-2.0.3.min.js';
document.head.appendChild(s0);

var s1 = document.createElement('script');
s1.type = 'text/javascript';
s1.src = safari.extension.baseURI + 'bridge.js';
document.head.appendChild(s1);

window.addEventListener('message', function (e) {
    if (e.origin != window.location.origin)
        return;
    console.log(e.data);
}, false);

window.postMessage('What jQuery version?', window.location.origin);

In bridge.js:

window.addEventListener('message', function (e) {
    if (e.origin != window.location.origin)
        return;
    if (e.data == 'What jQuery version?') {
        e.source.postMessage('Version ' + $.fn.jquery, window.location.origin);
    }
}, false);
chulster
  • 2,809
  • 15
  • 14
  • Could you expand on the first option ("inject jQuery into the page the normal way"), do you mean via `addContentScriptFromURL`? – meleyal Apr 16 '14 at 14:01
  • Also, if the injected script has a different namespace, how does it access the `window` object (i.e. how can it access `window` but not `$`)? It seems to be essentially posting messages to itself. – meleyal Apr 16 '14 at 14:12
  • 1
    @meleyal, yes I mean `addContentScript` and `addContentScriptFromURL`. Regarding how the injected script accesses the window object, it seems to me that the injected script and the page's own script act as if they inhabit two different windows; one might think of them as virtual windows that happen to take up the same space in the browser. But these virtual windows share the same location and origin, which is why it's possible for one script to send messages to the other virtual window. (They also share the same DOM, but that is not relevant for the purposes of message passing.) – chulster Apr 20 '14 at 02:37