Global Scope Differences in UserScripts in Chrome and Firefox

5

I started programming UserScripts in Chrome and almost all of the code snippets I find work, but when it comes to Firefox it's a problem. In Chrome I use Tampermonkey and in Firefox Scriptish (which is a fork of Greasemonkey).

[Note to future visitors, I strongly advise against working with Scriptish, there are things that run smoothly on GM and TM but not on it.]

For example, if I add a script in the DOM whose return is a JSONP, the callback (global) function of JSONP gives undefined to the FF. Or, if I make an AJAX call, at the time of executing a global function within success it also gives undefined in FF.

I did a reduced version that demonstrates the problem. The global_function function runs in Chrome but not in FF. The execution domain is SOPT and the AJAX call is to the Stack Exchange API.

// ==UserScript==
// @name           (SOPT) FF problems
// @namespace      sopt.se
// @author         brasofilo
// @include        http://pt.stackoverflow.com/*
// @description    Testando problemas no FF
// ==/UserScript==

jquery_url = '//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js'

global_function = function() {
    alert( 'yes!' );
}

/**
 * Chamada depois que o jQuery foi carregado
 */
function jqueryLoaded() {
    jQ.ajax({
        url: 'http://api.stackexchange.com/2.2/users',
        data: { order: 'desc', sort: 'reputation', site: 'stackoverflow' },
        type: 'get',
        dataType: 'json',
        cache: false,
        success: function(data) {
            console.log(data);
            global_function();
        }
    });
}

/**
 * Carrega jQuery e chama callback quando carregar
 * Nota: jQ substitui $ para evitar conflitos
 * 
 * https://stackoverflow.com/a/3550261/1287812
 */
function addJQuery( callback ) {
    var script = document.createElement( 'script' );
    script.setAttribute( 'src', jquery_url );
script.addEventListener( 'load', function() {
        var script = document.createElement('script');
        script.textContent = 'window.jQ=jQuery.noConflict(true);(' + callback.toString() + ')();';
        document.body.appendChild( script );
    }, false );
    document.body.appendChild( script );
}

addJQuery( jqueryLoaded );

I've tried window.global_function = function(){ } , function global_function() {} , unsafeWindow.global_function = function(){ } and follow undefined . And I checked the following discussions but did not clarify anything ...

How to resolve this scope issue? And besides, are there other basic differences when programming UserScripts for Chrome and Firefox?

    
asked by anonymous 13.09.2014 / 02:49

1 answer

2

For security reasons 1 , greasemonkey userscripts run in a sandbox separate from the normal scripts on your page. In particular, the greasemonkey script has a window differs from the window of the rest of the page's scripts: global variables defined in userscript do not leak to page scripts and vice versa.

In particular, the code inside the script tags you created will run in the context of the page, which is different from the original context of your userscript.

The links you found suggest using the unsafeWindow property to access the page window from the greasemonkey window but this unfortunately does not work anymore since Firefox recently removed support for unsafeWindow , which can no longer be used by greasemonkey from < a version 2.0 .

To solve your problem, you have more than one approach you can take:

  • Use the new API export_function to export global_function from the greasemonkey scope to the page scope, where you are loading jQuery.

    I think this solution only works in Firefox.

  • Load jQuery into the scope of the greasemonkey script, using require . So your code runs around in the greasemonkey scope and you can ignore the page scope (and dynamically inserted script tags).

    I think avoiding dynamic script tags would be the least "gambiarra" solution. The problem is that I do not know if you can do this in tampermonkey and not every library that you want to use accepts to be run in greasemonkey scope.

  • Do the opposite of the second alternative: rotate your entire code in the scope of the page and ignore the greasemonkey scope. The easiest way to do this would be for all its functions within jqueryLoaded, which is the function you are serializing into the tag tag:

    function jQueryLoaded(){
        function global_function(){ ... }
    
        jQ.ajax({
            success: function(){
                 global_function();
            }
        });
    }
    

    Putting your whole code inside the injected script tag is very simple and cross browser and is also the only way to override variables defined by the page's scripts (if you need to do this). The main drawback is that the line numbers of the error messages you get do not match the line numbers of your original file.

1 greasemonkey provides some more privileged APIs, which we do not want to be accessed by the scripts we upload from the web.

    
13.09.2014 / 06:11