161

As some of you may know, Google Chrome has put some severe limitation on Greasemonkey scripts.

Chromium does not support @require, @resource, unsafeWindow, GM_registerMenuCommand, GM_setValue, or GM_getValue.

Without require, I can't find a way to include the jQuery library in Greasemonkey script under Google Chrome.

Does anybody have some advice in this matter?

Brock Adams
  • 90,639
  • 22
  • 233
  • 295
Alekc
  • 4,682
  • 6
  • 32
  • 35
  • 19
    It's worth noting that Google Chrome with Tampermonkey has support for `@require` now, which is a far simpler approach than the ones in the answers. – Steen Schütt Jun 06 '13 at 11:54
  • 2
    Tampermonkey also supports unsafeWindow, which is very nice for pages that already have jQuery. `var $ = unsafeWindow.jQuery;` – Tim Goodman Oct 09 '13 at 13:21
  • 1
    `@require` works great on sites where you are not worried about conflicting with any of the thousands or millions of other JS libraries that bind to $ upon loading. However, if you are writing a script for a site using $ for something else, or even worse writing a script to run on every site ever, use the comparatively safe loading mechanism explained by **tghw** below. – GDorn Nov 10 '16 at 05:43

11 Answers11

194

From "User Script Tip: Using jQuery - Erik Vold's Blog"

// ==UserScript==
// @name         jQuery For Chrome (A Cross Browser Example)
// @namespace    jQueryForChromeExample
// @include      *
// @author       Erik Vergobbi Vold & Tyler G. Hicks-Wright
// @description  This userscript is meant to be an example on how to use jQuery in a userscript on Google Chrome.
// ==/UserScript==

// a function that loads jQuery and calls a callback function when jQuery has finished loading
function addJQuery(callback) {
  var script = document.createElement("script");
  script.setAttribute("src", "//ajax.googleapis.com/ajax/libs/jquery/1/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);
}

// the guts of this userscript
function main() {
  // Note, jQ replaces $ to avoid conflicts.
  alert("There are " + jQ('a').length + " links on this page.");
}

// load jQuery and execute the main function
addJQuery(main);
Rublacava
  • 414
  • 1
  • 4
  • 16
tghw
  • 25,208
  • 13
  • 70
  • 96
  • Instead of the 3 lines inside addEventListener for 'load', wouldn't "callback();" just work? – crdx Jul 10 '11 at 00:33
  • I'm not entirely sure, but I believe it has to do with the way chrome separates extensions and web pages. If it were just called in the script, it would execute in the extension context, instead of the page, so you wouldn't have access to anything. – tghw Aug 17 '11 at 18:46
  • 6
    My page already includes jQuery, but it seems that the above code is still needed to use jQuery in the userscript. However, two jQuery includes can cause a conflict, so the first line of your main() function might need to be jQuery.noConflict(); – slolife Sep 09 '11 at 23:58
  • Can I import several external JS libraries to a single userscript using this method?? – Isuru Dec 30 '11 at 16:02
  • epic method! thanks! but dont use "1.4.2"! write "1" and it returns the last jquery – puchu Jan 08 '12 at 09:16
  • 2
    I have modified the line `script.textContent = "(" + callback.toString() + ")();";` to `script.textContent = "jQuery.noConflict();(" + callback.toString() + ")();";` in my own user script template so there will not be any surprising conflicts. :) – RCE Jan 30 '12 at 11:21
  • It's not immediately obvious how to modify this code to also add `jQuery UI` as well as just `jQuery` itself )-: – hippietrail Jun 13 '12 at 12:45
  • 1
    @hippietrail In `main()`, you can use `$.loadScript()`, and then have everything else run when loadScript is finished loading jQueryUI. – tghw Jun 19 '12 at 16:19
  • Works perfectly. Use '//code.jquery.com/jquery-latest.min.js' for script source. Refers to the latest script and it doesn't care if you are using http or https – Germstorm Jul 13 '12 at 04:22
  • There seems to be some race condition in this code. When I use it on StackExchange it regularly (but not always) prevents the toolbar and preview from appearing when editing posts. (This is under Windows 7 Starter on a netbook.) – hippietrail Sep 23 '12 at 11:15
  • @MichaelS. I can confirm that it does still work. You just have to drag the `foo.user.js` file onto the extensions page to install/reinstall it. – tghw Jan 25 '13 at 04:24
  • Thank you for sharing - I have uploaded an example greasemonkey script on gist: https://gist.github.com/netsi1964/4721303 – Netsi1964 Feb 06 '13 at 09:04
  • tghw you are correct, it does still work. A userscript that runs on Facebook, it doesn't. Here's the error: Refused to load the script 'https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js' because it violates the following Content Security Policy directive: "script-src https://*.facebook.com http://*.facebook.com https://*.fbcdn.net http://*.fbcdn.net *.facebook.net *.google-analytics.com *.virtualearth.net *.google.com 127.0.0.1:* *.spotilocal.com:* chrome-extension://lifbcibllhkdhoafpjfnlhfpfgnpldfl 'unsafe-inline' 'unsafe-eval' https://*.akamaihd.net http://*.akamaihd.net". – WebDevNerdStuff Feb 08 '13 at 23:58
  • This seems to be specific to Facebook on Chrome also. If anybody has a work around without putting all the jquery min code in, it would be greatly appreciated. – WebDevNerdStuff Feb 09 '13 at 00:12
  • @MichaelS. Open a new question. It'll be easier to answer there. – tghw Feb 09 '13 at 03:51
  • 1
    -1: With that method, the code in `main` will execute in the context of the target page, meaning that (among other things) the cross-site request policy of the target page applies - (e.g. I had to reinject `GM_xmlhttpRequest` before seeing that it did not help me). In the end I simply copy pasted the code of `jquery.min.js`. – Jean Hominal Mar 16 '13 at 14:51
  • @JeanHominal It's true, this won't work for some sites, if they lock that down. But, aside from putting all of jquery into the script, it's the only way to do it. – tghw Mar 16 '13 at 18:47
  • Well, it also became important to me because I wanted to do a cross-site XML HTTP request and I was puzzled to still be blocked by Chrome's cross-site scripting policy (and even doing it in TamperMonkey did not solve that!), until I realized that Chrome applied to me the cross-site scripting policy of the host site because the script was actually executing in that context. – Jean Hominal Mar 17 '13 at 00:10
  • `Refused to load the script 'https://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js' because it violates the following Content Security Policy directive: "script-src *.company.com *.company.com:* 'unsafe-inline' 'unsafe-eval' 'self'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.` – juanmf May 06 '22 at 01:39
43

I have written a few functions based on the Erik Vold's script to help run me run functions, code and other scripts in a document. You can use them to load jQuery into the page and then run code under the global window scope.

Example Usage

// ==UserScript==
// @name           Example from http://stackoverflow.com/q/6834930
// @version        1.3
// @namespace      http://stackoverflow.com/q/6834930
// @description    An example, adding a border to a post on Stack Overflow.
// @include        http://stackoverflow.com/questions/2246901/*
// ==/UserScript==

var load,execute,loadAndExecute;load=function(a,b,c){var d;d=document.createElement("script"),d.setAttribute("src",a),b!=null&&d.addEventListener("load",b),c!=null&&d.addEventListener("error",c),document.body.appendChild(d);return d},execute=function(a){var b,c;typeof a=="function"?b="("+a+")();":b=a,c=document.createElement("script"),c.textContent=b,document.body.appendChild(c);return c},loadAndExecute=function(a,b){return load(a,function(){return execute(b)})};

loadAndExecute("//ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js", function() {
    $("#answer-6834930").css("border", ".5em solid black");
});

You can click here to install it, if you trust that I'm not trying to trick you into installing something malicious and that nobody has edited my post to point to something else. Reload the page and you should see a border around my post.

Functions

load(url, onLoad, onError)

Loads the script at url into the document. Optionally, callbacks may be provided for onLoad and onError.

execute(functionOrCode)

Inserts a function or string of code into the document and executes it. The functions are converted to source code before being inserted, so they lose their current scope/closures and are run underneath the global window scope.

loadAndExecute(url, functionOrCode)

A shortcut; this loads a script from url, then inserts and executes functionOrCode if successful.

Code

function load(url, onLoad, onError) {
    e = document.createElement("script");
    e.setAttribute("src", url);

    if (onLoad != null) { e.addEventListener("load", onLoad); }
    if (onError != null) { e.addEventListener("error", onError); }

    document.body.appendChild(e);

    return e;
}

function execute(functionOrCode) {
    if (typeof functionOrCode === "function") {
        code = "(" + functionOrCode + ")();";
    } else {
        code = functionOrCode;
    }

    e = document.createElement("script");
    e.textContent = code;

    document.body.appendChild(e);

    return e;
}

function loadAndExecute(url, functionOrCode) {
    load(url, function() { execute(functionOrCode); });
}
Jeremy
  • 1
  • 85
  • 340
  • 366
  • @cyphunk Yeah, it was crucial for me to save those few characters. Actually, I'm feeling pretty dumb for having left this post using it so pointlessly. I'll remove it. – Jeremy Oct 04 '12 at 15:12
19

Use jQuery without fear of conflicts, by calling jQuery.noConflict(true). Like so:

function GM_main ($) {
    alert ('jQuery is installed with no conflicts! The version is: ' + $.fn.jquery);
}

add_jQuery (GM_main, "1.7.2");

function add_jQuery (callbackFn, jqVersion) {
    jqVersion       = jqVersion || "1.7.2";
    var D           = document;
    var targ        = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
    var scriptNode  = D.createElement ('script');
    scriptNode.src  = 'http://ajax.googleapis.com/ajax/libs/jquery/'
                    + jqVersion
                    + '/jquery.min.js'
                    ;
    scriptNode.addEventListener ("load", function () {
        var scriptNode          = D.createElement ("script");
        scriptNode.textContent  =
            'var gm_jQuery  = jQuery.noConflict (true);\n'
            + '(' + callbackFn.toString () + ')(gm_jQuery);'
        ;
        targ.appendChild (scriptNode);
    }, false);
    targ.appendChild (scriptNode);
}


But, For cross-browser scripts, why not take advantage of a nice, fast, local copy of jQuery, when you can?

The following works as a Chrome userscript and a Greasemonkey script, and it uses the nice local @require copy of jQuery, if the platform supports it.

// ==UserScript==
// @name     _Smart, cross-browser jquery-using script
// @include  http://YOUR_SERVER.COM/YOUR_PATH/*
// @require  http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js
// @grant    GM_info
// ==/UserScript==

function GM_main ($) {
    alert ('jQuery is installed with no conflicts! The version is: ' + $.fn.jquery);
}

if (typeof jQuery === "function") {
    console.log ("Running with local copy of jQuery!");
    GM_main (jQuery);
}
else {
    console.log ("fetching jQuery from some 3rd-party server.");
    add_jQuery (GM_main, "1.7.2");
}

function add_jQuery (callbackFn, jqVersion) {
    var jqVersion   = jqVersion || "1.7.2";
    var D           = document;
    var targ        = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
    var scriptNode  = D.createElement ('script');
    scriptNode.src  = 'http://ajax.googleapis.com/ajax/libs/jquery/'
                    + jqVersion
                    + '/jquery.min.js'
                    ;
    scriptNode.addEventListener ("load", function () {
        var scriptNode          = D.createElement ("script");
        scriptNode.textContent  =
            'var gm_jQuery  = jQuery.noConflict (true);\n'
            + '(' + callbackFn.toString () + ')(gm_jQuery);'
        ;
        targ.appendChild (scriptNode);
    }, false);
    targ.appendChild (scriptNode);
}
Brock Adams
  • 90,639
  • 22
  • 233
  • 295
  • 4
    +1 Impressive, out of all these answers yours is the only one that says to use @require, you can also use `$ = unsafeWindow.jQuery` if the page has jQuery (I've only tested this in Tampermonkey). – A.M.K Nov 20 '12 at 20:47
  • Caveat: some versions of IE don't have console.log() unless you've installed dev tools, so the script would crash. Usually you'll discover this only once you've released your script beyond developers and QA folk. – Parsingphase May 28 '13 at 15:43
  • 1
    @Parsingphase, IE is pretty much NA here. Last I checked, IE still doesn't support userscripts well (¿at all?). Has that changed with IE 10? – Brock Adams May 28 '13 at 16:35
  • Brock - good point, I fail at holding IE's incompatibility matrix in my head. So, not directly applicable to userscripts (although it does appear that people occasionally try to implement an IE solution), but more of a general gotcha. – Parsingphase Jun 03 '13 at 11:42
16

If the page already has jQuery, then just follow this template:

// ==UserScript==
// @name          My Script
// @namespace     my-script
// @description   Blah
// @version       1.0
// @include       http://site.com/*
// @author        Me
// ==/UserScript==

var main = function () {

    // use $ or jQuery here, however the page is using it

};

// Inject our main script
var script = document.createElement('script');
script.type = "text/javascript";
script.textContent = '(' + main.toString() + ')();';
document.body.appendChild(script);
Camilo Martin
  • 37,236
  • 20
  • 111
  • 154
Mottie
  • 84,355
  • 30
  • 126
  • 241
  • I don't think this works because the userscript does not have access to the documents window? – Christoph Nov 16 '12 at 12:53
  • @Christoph It does work, I have and I am still using a [userscript](http://userscripts.org/scripts/show/123354) using this method. – Mottie Nov 16 '12 at 13:15
  • 1
    This is actually injecting the greasemonkey script into the page. So probably by-passes some of the safeguards that greasemonkey has. – Thymine Jan 21 '13 at 22:56
  • 1
    @Thymine I have noticed that this method does inject the userscript into undesired pages. I've had to wrap the inject portion in an `if` statement that checks the `window.location`. – Mottie Jan 21 '13 at 23:43
13

The simple way is using required keyword:

// @require     http://code.jquery.com/jquery-latest.js
Stiger
  • 1,189
  • 2
  • 14
  • 29
7

There's a really easy way to get around including a full copy of jQuery for Chrome scripts when those scripts don't actually use any privileged features (GM_* functions, etc)...

Simply insert the script itself into the page DOM and execute! The best part is that this technique works just as well on Firefox+Greasemonkey, so you can use the same script for both:

var script = document.createElement("script");
script.type = "text/javascript";
script.textContent = "(" + threadComments.toString() + ")(jQuery)";
document.body.appendChild(script);

function threadComments($) {
    // taken from kip's http://userscripts-mirror.org/scripts/review/62163
    var goodletters = Array('\u00c0','\u00c1','\u00c2','\u00c3','\u00c4','\u00c5','\u00c6','\u00c7'
                             ,'\u00c8','\u00c9','\u00ca','\u00cb','\u00cc','\u00cd','\u00ce','\u00cf'
                                      ,'\u00d1','\u00d2','\u00d3','\u00d4','\u00d5','\u00d6'         
                             ,'\u00d8','\u00d9','\u00da','\u00db','\u00dc','\u00dd'                  
                             ,'\u00e0','\u00e1','\u00e2','\u00e3','\u00e4','\u00e5','\u00e6','\u00e7'
                             ,'\u00e8','\u00e9','\u00ea','\u00eb','\u00ec','\u00ed','\u00ee','\u00ef'
                                      ,'\u00f1','\u00f2','\u00f3','\u00f4','\u00f5','\u00f6'         
                             ,'\u00f8','\u00f9','\u00fa','\u00fb','\u00fc','\u00fd'         ,'\u00ff').join('');

    // from Benjamin Dumke's http://userscripts-mirror.org/scripts/review/68252
    function goodify(s)
      {
         good = new RegExp("^[" + goodletters + "\\w]{3}");
         bad = new RegExp("[^" + goodletters + "\\w]");
         original = s;
         while (s.length >3 && !s.match(good)) {
            s = s.replace(bad, "");
            }
         if (!s.match(good))
         {
           // failed, so we might as well use the original
           s = original;
         }
         return s;
      }  

    in_reply_to = {};


    function who(c, other_way) {


        if (other_way)
        {
            // this is closer to the real @-reply heuristics
            m = /@(\S+)/.exec(c);
        }
        else
        {
            m = /@([^ .:!?,()[\]{}]+)/.exec(c);
        }
        if (!m) {return}
        if (other_way) {return goodify(m[1]).toLowerCase().slice(0,3);}
        else {return m[1].toLowerCase().slice(0,3);}
    }

    function matcher(user, other_way) {
        if (other_way)
        {
            return function () {
                return goodify($(this).find(".comment-user").text()).toLowerCase().slice(0,3) == user
                }
        }
        else
        {
            return function () {
                return $(this).find(".comment-user").text().toLowerCase().slice(0,3) == user
                }
        }
    }

    function replyfilter(id) {
        return function() {
            return in_reply_to[$(this).attr("id")] == id;
        }
    }

    function find_reference() {
        comment_text = $(this).find(".comment-text").text();
        if (who(comment_text))
        {
            fil = matcher(who(comment_text));
            all = $(this).prevAll("tr.comment").filter(fil);
            if (all.length == 0)
            {
                // no name matched, let's try harder
                fil = matcher(who(comment_text, true), true);
                all = $(this).prevAll("tr.comment").filter(fil);
                if (all.length == 0) {return}
            }
            reference_id = all.eq(0).attr("id");
            in_reply_to[$(this).attr("id")] = reference_id;
        }
    }


    // How far may comments be indented?
    // Note that MAX_NESTING = 3 means there are
    // up to *four* levels (including top-level)
    MAX_NESTING = 3

    // How many pixels of indentation per level?
    INDENT = 30

    function indenter(parent) {

        for (var i = MAX_NESTING; i > 0; i--)
        {
            if (parent.hasClass("threading-" + (i-1)) || (i == MAX_NESTING && parent.hasClass("threading-" + i)))
            {
                return function() {
                    $(this).addClass("threading-" + i).find(".comment-text").css({"padding-left": INDENT*i});
                }
            }
        }

        return function() {
            $(this).addClass("threading-1").find(".comment-text").css({"padding-left": INDENT});
        }

    }

    function do_threading(){
        id = $(this).attr("id");
        replies = $(this).nextAll("tr.comment").filter(replyfilter(id));
        ind = indenter($(this));
        replies.each(ind);
        replies.insertAfter(this);
    }

    function go() {
        $("tr.comment").each(find_reference);
        $("tr.comment").each(do_threading);
    }

    $.ajaxSetup({complete: go});
    go();
}

(unapologetically stolen from Shog9 on meta.stackoverflow since he didn't move it here, and I have to delete the meta post..)

Adam Katz
  • 14,455
  • 5
  • 68
  • 83
Jeff Atwood
  • 63,320
  • 48
  • 150
  • 153
4

Easier solution: cut+paste the contents of jquery.min.js into the top of your user script. Done.

I found various problems with the recommended answers. The addJQuery() solution works on most pages but has bugs on many. If you run into issues just copy+paste the jquery contents into your script.

cyphunk
  • 169
  • 7
  • Yeah, I think this makes the most sense, because one can even wright a little build script that generates a chrome version by doing exactly what you suggest here. – dkinzer Aug 20 '13 at 13:25
4

Also, you could pack your script with jQuery to Chrome extension. See Google Chrome's Content Scripts.

Chrome extensions, unlike Greasemonkey scripts, can auto-update itself.

NVI
  • 14,907
  • 16
  • 65
  • 104
  • 2
    yep, it would be easier. But i really prefer to maintain my script through userscripts.org for now, and not create redundancy with google extensions repository. – Alekc Feb 17 '10 at 15:06
  • 4
    And it costs $5 to upload to Google Web Store. – Camilo Martin Feb 23 '12 at 08:44
2

I wonder if you couldn't rely on document.defaultView.jQuery in your GM script ala:

if (document.defaultView.jQuery) {
  jQueryLoaded(document.defaultView.jQuery);
} else {
  var jq = document.createElement('script');
  jq.src = 'http://jquery.com/src/jquery-latest.js';
  jq.type = 'text/javascript';
  document.getElementsByTagName('head')[0].appendChild(jq);
  (function() { 
    if (document.defaultView.jQuery) jQueryLoaded(document.defaultView.jQuery);
    else setTimeout(arguments.callee, 100);
  })();
}

function jQueryLoaded($) {
  console.dir($);
}
gnarf
  • 105,192
  • 25
  • 127
  • 161
1

Another approach would be to modify your script to load jQuery manually. Example from http://joanpiedra.com/jquery/greasemonkey/:

// Add jQuery
var GM_JQ = document.createElement('script');
GM_JQ.src = 'http://jquery.com/src/jquery-latest.js';
GM_JQ.type = 'text/javascript';
document.getElementsByTagName('head')[0].appendChild(GM_JQ);

// Check if jQuery's loaded
function GM_wait() {
    if(typeof unsafeWindow.jQuery == 'undefined') { window.setTimeout(GM_wait,100); }
else { $ = unsafeWindow.jQuery; letsJQuery(); }
}
GM_wait();

// All your GM code must be inside this function
function letsJQuery() {
    alert($); // check if the dollar (jquery) function works
}

EDIT: DRATS! After testing it appears this code does not work since Google Chrome runs userscripts/extensions in a separate scope/process from the actual webpage. You can download the jQuery code using an XmlhttpRequest and then Eval it, but you have to host the code on a server that allows Cross-Origin Resource Sharing using the Access-Control-Allow-Origin: * header. Sadly NONE of the current CDNs with jQuery support this.

Community
  • 1
  • 1
Greg Bray
  • 14,929
  • 12
  • 80
  • 104
-2

Perfect extension to embed jQuery into Chrome Console as simple as you can imagine. This extension also indocates if jQuery has been already embeded into page.

This extension used to embed jQuery into any page you want. It allows to use jQuery in the console shell (You can invoke Chrome console by "Ctrl+Shift+j").

To embed jQuery into selected tab click on extention button.

LINK to extension: https://chrome.google.com/extensions/detail/gbmifchmngifmadobkcpijhhldeeelkc

Andrey
  • 65
  • 1
  • That really isn't the answer. And why would I want to load jQuery if I didn't need to? – Vik Jul 28 '15 at 19:27