1

I'm trying to load some data from Reddit via a Safari extension. I'm using a JSONP pattern to create the callback function and attach the new src to the script. However, it looks like there are two window namespaces, and the function that I dynamically create is not available to the context of the dynamically added script.

This link seems to detail the problem for chrome, which I'm guessing is similar to mine in Safari.

JSONP request in chrome extension, callback function doesn't exist?

Here's the code (works outside of extension):

function jsonp(url, callback) {
    var callbackName = 'jsonp_callback_' + Math.round(100000 * Math.random());
    window[callbackName] = function(data) {
        delete window[callbackName];
        callback(data);
    };
    console.log('about to create script');
    var script = document.createElement('script');
    script.src = url + (url.indexOf('?') >= 0 ? '&' : '?') + 'jsonp=' + callbackName;
    document.body.appendChild(script);
    console.log('script should be appended');
}


function getImages(){
    jsonp('http://www.reddit.com/r/cats/.json', function(data) {
        data.data.children.forEach(function(el){
            console.log(el.data.url);
        });
    });    
}

Any ideas, work arounds?

Thanks!

Community
  • 1
  • 1
4m1r
  • 12,234
  • 9
  • 46
  • 58

1 Answers1

2

You're right about there being two namespaces in the same window. Injected scripts cannot access a web page's globals, and vice versa. When you initiate JSONP in an injected script, the script of the inserted <script> tag runs in the web page's namespace.

I know of two ways to work around this limitation in a Safari extension.

The probably better way is to use your extension's global page to communicate with the external API through standard XHR or jQuery Ajax methods (which are supported in global page scripts) and use messages to pass the fetched data to an injected script.

The other way is to go ahead and use JSONP from the injected script, but have the JSONP callback function add the fetched data to the page's DOM, which is accessible by the injected script. For example, you could put the data in a hidden <pre> element and then use the element's innerHTML property to retrieve it. Of course, this technique does make the content visible to the page's own scripts, so exercise caution.

chulster
  • 2,809
  • 15
  • 14
  • Forgot to add that, unlike Chrome, Safari extensions cannot use cross-domain XHR in an injected script -- thus the need to run it in the global script. – chulster Oct 25 '14 at 18:03
  • I ended up sending messages to the global script which does the XHR and responds via another message to the injected. Note: safari.application.activeBrowserWindow.activeTab.page.dispatchMessage() seems to be the right way to send messages. The documentation seems deprecated. Super helpful. Thanks! – 4m1r Oct 30 '14 at 16:10