Search code examples
javascriptfirefoxgreasemonkeygreasemonkey-4

How do I replace unsafeWindow when migrating a pre-Firefox 30 Greasemonkey script to GM4+?


I'm trying to get a reference to the version of jQuery that exists on my webpage in a Greasemonkey script that worked until Firefox 30. In comments below my definition are the two other references I could find, but I just get ReferenceError: $ is not defined or ReferenceError: jQuery is not defined when I try to access jQuery on the window object.

var $ = unsafeWindow.jQuery;
//var jQuery = window.jQuery; // From https://stackoverflow.com/questions/24802606/binding-to-an-event-of-the-unsafewindow-in-firefox-30-with-greasemonkey-2-0
//var jQuery = $ || window.wrappedJSObject.$; // https://github.com/greasemonkey/greasemonkey/issues/2700#issuecomment-345538182
function addAccountNameToTitle(jNode) {
  $('title').text(session.name + " | " + $('title').text());
}

waitForKeyElements (".page-breadcrumb", addAccountNameToTitle, false);

/*--- waitForKeyElements():  A handy, utility function that
    does what it says.
*/
function waitForKeyElements (
    selectorTxt,    /* Required: The jQuery selector string that
                        specifies the desired element(s).
                    */
    actionFunction, /* Required: The code to run when elements are
                        found. It is passed a jNode to the matched
                        element.
                    */
    bWaitOnce,      /* Optional: If false, will continue to scan for
                        new elements even after the first match is
                        found.
                    */
    iframeSelector  /* Optional: If set, identifies the iframe to
                        search.
                    */
)
{
    var targetNodes, btargetsFound;

    if (typeof iframeSelector == "undefined")
        targetNodes     = $(selectorTxt);
    else
        targetNodes     = $(iframeSelector).contents ()
                                           .find (selectorTxt);

    if (targetNodes  &&  targetNodes.length > 0) {
        /*--- Found target node(s).  Go through each and act if they
            are new.
        */
        targetNodes.each ( function () {
            var jThis        = $(this);
            var alreadyFound = jThis.data ('alreadyFound')  ||  false;

            if (!alreadyFound) {
                //--- Call the payload function.
                actionFunction (jThis);
                jThis.data ('alreadyFound', true);
            }
        } );
        btargetsFound   = true;
    }
    else {
        btargetsFound   = false;
    }

    //--- Get the timer-control variable for this selector.
    var controlObj      = waitForKeyElements.controlObj  ||  {};
    var controlKey      = selectorTxt.replace (/[^\w]/g, "_");
    var timeControl     = controlObj [controlKey];

    //--- Now set or clear the timer as appropriate.
    if (btargetsFound  &&  bWaitOnce  &&  timeControl) {
        //--- The only condition where we need to clear the timer.
        clearInterval (timeControl);
        delete controlObj [controlKey]
    }
    else {
        //--- Set a timer, if needed.
        if ( ! timeControl) {
            timeControl = setInterval ( function () {
                    waitForKeyElements (    selectorTxt,
                                            actionFunction,
                                            bWaitOnce,
                                            iframeSelector
                                        );
                },
                500
            );
            controlObj [controlKey] = timeControl;
        }
    }
    waitForKeyElements.controlObj   = controlObj;
}

I'm using FF 59.0.2 and Greasemonkey 4.3


Solution

  • unsafeWindow.jQuery was never a good idea and rarely worked. Also, see Error: Permission denied to access property 'handler'.

    The smart thing to do with the question code is to use @require, like so:

    // ==UserScript==
    // @name     _Changing the title text on some page.
    // @match    *://YOUR_SERVER.COM/YOUR_PATH/*
    // @require  https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
    // @require  https://gist.github.com/raw/2625891/waitForKeyElements.js
    // @grant    GM_addStyle
    // @grant    GM.getValue
    // ==/UserScript==
    //- The @grant directives are needed to restore the proper sandbox.
    
    waitForKeyElements (".page-breadcrumb", addAccountNameToTitle);
    
    function addAccountNameToTitle (jNode) {
        $('title').text (session.name + " | " + $('title').text() );
    }
    

    Advantages:

    • Short, simple, and clear.
    • Not vulnerable to side effects from changing javascripts on target page. Especially, if the target page changes jQuery versions.
    • Uses speedy, local versions of jQuery and waitForKeyElements. They are stored on the local disk and often cached in memory. No server fetches nor delays.

    Note: if session is a target page global variable, you may need to access it like unsafeWindow.session.name. See: How to access `window` (Target page) objects when @grant values are set?.




    You state that you want to use the page's jQuery instance or version. There is seldom a good reason to do that. And the code, shown in this question, certainly wouldn't benefit from that.

    But, if your userscript doesn't use any GM functions, you can do that via @grant none mode like:

    // ==UserScript==
    // @name     _Changing the title text on some page.
    // @match    *://YOUR_SERVER.COM/YOUR_PATH/*
    // @require  https://gist.github.com/raw/2625891/waitForKeyElements.js
    // @grant    none
    // ==/UserScript==
    
    waitForKeyElements (".page-breadcrumb", addAccountNameToTitle);
    
    function addAccountNameToTitle (jNode) {
        $('title').text (session.name + " | " + $('title').text() );
    }