2

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

Brock Adams
  • 90,639
  • 22
  • 233
  • 295
NobleUplift
  • 5,631
  • 8
  • 45
  • 87
  • I'm going to try getting this to work today, but hopefully there's an easy answer. – NobleUplift May 08 '18 at 15:49
  • The easy answer is that's the exact wrong approach. `@require` the jQuery WFKE and use a grant other than `none`. Provide your FF and GM versions. And per GM's developers, don't use GM4+. Use Tampermonkey or Violentmonkey instead. – Brock Adams May 09 '18 at 16:07
  • Thanks for the reply. This is just the script that I've had working for the past 3 years or so. Now I'm trying to update myself on everything I missed. I'm using FF 59.0.2 and GreaseMonkey 4.3. Isn't the reason people use Tampermonkey or Violentmonkey because they insert a ` – NobleUplift May 09 '18 at 19:52
  • People use Tampermonkey on FF because GM4 broke everybody's scripts and has a great many bugs, while losing many features. Tampermonkey was already better in terms of functionality and performance anyway. And [GM's developers say to switch](https://www.greasespot.net/2017/09/greasemonkey-4-for-users.html). – Brock Adams May 09 '18 at 20:28

1 Answers1

2

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() );
}
Brock Adams
  • 90,639
  • 22
  • 233
  • 295
  • Thanks a lot!!! I was able to fix two of my Greasemonkey scripts and while I was at it I brought my Stylish user styles up to date with the new format. I have one Greasemonkey script that's entering an infinite loop when it reaches line 53 of waitForKeyElements that I'm debugging now. – NobleUplift May 11 '18 at 22:31
  • You're welcome; glad to help. Line 53 is only called once per node, unless your callback returns a non zero/false/null value. Open a new question if you need to. – Brock Adams May 11 '18 at 22:47
  • Done. https://stackoverflow.com/questions/50333445/how-do-i-fix-an-infinite-waitforkeyelements-loop-in-greasemonkey – NobleUplift May 14 '18 at 15:08