Search code examples
jqueryfirefoxmootoolsgreasemonkeyuserscripts

jQuery.noConflict weird behavior in userscript, in Firefox only


I have a userscript containing this:

// userscript header
(function() {
    // here is jquery source code
    var $ = $.noConflict();
})();

The site I'm using it on, is using mootools, so the site's code depends on $. The noConflict doesn't help in Firefox (23.0.1) for some weird reason. The site still gets jQuery in $, which breaks site's original functionality.

However, when I change it to: var $ = jQuery.noConflict();

It works. Why?

I can't put userscript on jsfiddle, so here is a gif with all the code (HTML on left, userscript on right), showing the problem:

jQuery.noConflict weird behavior

Versions: everything is latest, Firefox 23.0.1, Greasemonkey 1.11, jQuery v1.10.2, Mootools 1.4.5-nc

"Bug" does not happen in: Chrome 29.0.1547.66m, Opera 12.16


Solution

  • Let's see how noConflict() is actually implemented...

    if ( window.$ === jQuery ) {
      window.$ = _$; // where $_ is window.$ before jquery reassigns it.
    }
    

    Now we need to remember that the window the user script operates on is not actually the same thing as the window the site sees. It is a sandboxed wrapper instead (at least in Greasemonkey and Scriptish). That wrapper actually hides all "expandos", i.e. added or overwritten properties on the original object.

    Hence in your user script window.$ === undefined while in the actual page it is defined as that mootools helper. unsafewindow.$ is also the mootools helper, as unsafeWindow is the unwrapped page window.

    Now, when your userscript includes jQuery, $ will on be set on the wrapped window. The original page window.$ is still mootools from the perspective of the website.

    Next, the call to .noConflict(), as implemented above, will revert window.$ back, but on the sandbox wrapper. Hence window.$ in the user-script sandbox becomes undefined again, while the page window.$ (aka. unsafeWindow.$ in the user script sandbox) still is the moo helper (and was actually never changed).

    Update: Greasemonkey actively disables these wrappers explicitly in their "no-grants" branch of createSandox() by setting wantXRays = false. I'd consider this to be a bug.

    Now, that's the reason why you need .noConflict() in the first place in GM.

    var $ = $.noConflict() cannot work because it is an error. The var $ will be hoisted and hence it is immediately undefined. jQuery will not actually set it (it just sets window.$, not local-scope $), and hence the $.noConflict() call becomes undefined.noConflict().