Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

require jQuery to a safe variable in Tampermonkey script and console

Normally I have used the following code snippet, and this usually works:

(function () {
    function main() {
        //...script body...
    }

    var OGloboCSS = { // var could be named anything, appropriate to the page
        addJQuery: function (callback) {
            var script = document.createElement("script");
            script.setAttribute("src", "https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js");
            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);
        }
    };

    OGloboCSS.addJQuery(main);

})();

But occasionally I get an error like this one on https://steamcommunity.com:

Refused to load the script 'https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js' because it violates the following Content Security Policy directive: "script-src 'unsafe-eval' 'self' 'unsafe-inline' 'unsafe-eval' https://steamcommunity-a.akamaihd.net/ https://api.steampowered.com/ http://www.google-analytics.com https://ssl.google-analytics.com https://www.google.com https://www.gstatic.com https://apis.google.com".

And the solution is basically, you have to use TamperMonkey's @require header directive instead, something like this:

// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js
// ==/UserScript==

window.jQ = $;

(function () {
/*this used to be: function main() */
    //...same script body, without container function...

})();

And this works just fine, but it doesn't safely load jQuery to the jQ variable, it clobbers $. Is it really a problem? Yes, you can see that in how pagination breaks in steamcommunity.com's discussion threads.

Is there some way to @require jQ= and then @grant jQ - still giving me jQ both in the Tampermonkey script and the console, without clobbering in-page variables?

like image 671
roberto tomás Avatar asked Feb 01 '15 16:02

roberto tomás


People also ask

How to avoid the confirmation dialog in Tampermonkey?

This way the confirmation dialog can be avoided for most of the users. Additionally add "@connect *" to the script. By doing so Tampermonkey will still ask the user whether the next connection to a not mentioned domain is allowed, but also offer a "Always allow all domains" button .

What is the GitHub issue number for Tampermonkey?

· Issue #979 · Tampermonkey/tampermonkey · GitHub Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community. By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement.

Does Tampermonkey support a way to store meta data?

---> scriptSource: "// ==UserScript== // @name Local File Test ...." <><! [CDATA [your_text_here]]></> Tampermonkey supports this way of storing meta data.

How do I allow all domains in Tampermonkey?

By doing so Tampermonkey will still ask the user whether the next connection to a not mentioned domain is allowed, but also offer a "Always allow all domains" button . If the user clicks at this button then all future requests will be permitted automatically. both, the initial and the final URL will be checked!


2 Answers

Apart from avoiding version conflicts and breaking the content page, I do a handful of other things in my default userscript template that includes jQuery.

The template below strives to meet the following goals in the interest of being a universal template for including jQuery in new scripts regardless of the content page's current or future status with regards to it's own inclusion, or lack thereof, of jQuery of any version.

  • avoid security sandbox with @grant none
  • in general remain agnostic about where script is inserted/when its executed
  • include specific version of jQuery for our UserScript
  • Preserve the content page's $ and jQuery references to whatever they may be, jQuery or not.
  • Allow our script to use the $ alias for its jQuery version

The use of @grant none is key here

//@grant          none

@grant none works for 99% of my scripts. It allows easy access to the content page's variables, functions, and objects by maintaining a global scope for the UserScript(this), and providing unqualified access to the content page's global scope(window) by searching both scopes for variable, function identifiers in this, window order. So if you avoid duplicating identifiers that exist in the content page, you can access content page variables, functions, and function return values without qualifying ("this." or "window."). And you can do so from anywhere in your script.

This highlights the OP's orignal issue. The convenience that @grant none provides is also the reason the @require directive overwrites the window's $ and jQuery references when it loads. The jQuery library has included the noConflict() function for quite some time to handle just this issue.

We can do some quick improvements right away to the answer above to increase our safety/compatibility/reuse.

window.jQ = $.noConflict(true);
  • This call doesn't explicitly qualify the $ to a scope
  • And it will not work particularly well if the content page also includes some other library that has aliased $. Especially if the content page has already called the jQuery noConflict itself before we get here. This is an unnecessary rabbit hole to get caught in.
  • This code somewhat alarmingly places the reference to our library in the window's scope where it could very well be altered by the content page in some way later. It is generally bad practice to expose anything to the window scope for obvious reasons.

This code instead

this.$ = window.jQuery.noConflict(true);
  • Keeps our library in our scope where it belongs
  • gives us the $ alias in our scope to continue using in our script
  • restores the window's $ and jQuery aliases to what the were when our jQuery loaded whether they were actually jQuery or not
  • gives us the best chance to be using the jQuery object loaded by the script to call noConflict

That last point is probably overkill, but why not be explicit and use the jQuery identifier to make the noConflict call. While it would be our fault at this point if $ wasn't jQuery when this call runs (@require for another library that uses $, using it yourself before this code runs, etc,) it's free prevention of later debugging effort.

While we could be done and happy there, we can also go a bit further to ensure compatibility across the widest variety of pages, and through the largest variety of content page changes.

(function ($, undefined) {
  $(function () {
    //Your code here;
  });
})(window.jQuery.noConflict(true));

While still not guaranteed, this pattern gives your functional code the best chance to execute in the largest variety of page lifecyle possibilities by keeping the $ alias out of the global scope and leveraging jQuery's DOM Ready event to ensure page completion. As an aside it also ensures a consistent undefined value for your code.

// ==UserScript==
// @name          jQuery safe inclusion template
// @description   include jQuery and make sure window.$ is the content page's jQuery version, and this.$ is our jQuery version.
// @version       0.0.1
// @author        Sonic Beard
// @match         http://*.site.com/*
// @require       http://cdn.jsdelivr.net/jquery/2.1.3/jquery.min.js
// @grant         none
// ==/UserScript==

(function ($, undefined) {
  $(function () {
    //Your code here;
  });
})(window.jQuery.noConflict(true));
like image 71
Sonic Beard Avatar answered Oct 09 '22 06:10

Sonic Beard


TamperMonkey does not appear to have such functionality, but an alternative would be to immediately call $.noConflict with the removeAll argument set to true. This will cause jQuery to reset the original $ and jQuery back to their original values.

Example UserScript:

// ==UserScript==
// @name         jQuery noConflict test
// @namespace    http://example.com/
// @version      0.1
// @description  test jQuery noConflict
// @author       You
// @match        https://steamcommunity.com/
// @require      https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js
// @grant        none
// ==/UserScript==

window.jQ = $.noConflict(true);

And you can see from the example console input below, jQ is the jQuery 2.1.3 that was loaded by @require, jQuery is the version of jQuery the page loads, and $ is still the original prototype library object, as evidenced by not having jQuery's fn property.

Example Console Input/Output:

> jQ.fn.jquery
< "2.1.3"
> jQuery.fn.jquery
< "1.11.1"
> $.fn
< undefined
like image 24
Alexander O'Mara Avatar answered Oct 09 '22 06:10

Alexander O'Mara