Xeevis
Xeevis

Reputation: 4521

Call to a complex page function works in Tampermonkey but not Greasemonkey?

I'm trying to open an overlay dialog for the XenForo forums, reusing an existing library:

// ==UserScript==
// @name         FooBar
// @match        https://xenforo.com/community/
// @grant        GM_getValue
// ==/UserScript==

(function() {
'use strict';

unsafeWindow.XenForo.createOverlay(null, $(`
<div class="xenOverlay">
    <form id="efd_form">
        <div class="section">
            <h2 class="heading h1">Greasemonkey test</h2>
            <h3 class="primaryContent">${GM_getValue('lorem', 'Lorem ipsum dolor sit amet …')}</h3>
        </div>
    </form>
</div>
`), { noCache: true }).load();
})();

When you visit https://xenforo.com/community/ with Tampermonkey (Firefox/Chromium) this script will open simple dialog.
But when you try it with Greasemonkey (Firefox) nothing happens. Is there a way to achieve this while having access to GM_getValue?

Upvotes: 1

Views: 298

Answers (1)

Brock Adams
Brock Adams

Reputation: 93473

If you check the Browser Console of Firefox, you will see an error message like:

$ is not defined ... FooBar.user.js

This is because using @grant other than none switched on the sandbox in Greasemonkey (and also Tampermonkey-ish). This means that the script cannot see the page's javascript directly and thus jQuery is undefined for the script***.

In your code, you cannot use unsafeWindow techniques to call XenForo.createOverlay() because that function requires a jQuery object defined/valid in the page's scope (and for more complex reasons).
Refer to How to access `window` (Target page) objects when @grant values are set?. This scenario falls under the "A complex function: This is not always possible" case.

So, you need to inject the code that calls XenForo.createOverlay().
But, there is a hitch. Because GM_getValue() won't be available in the page's scope, you need to inject the result of the GM_getValue call separately.

This script illustrates the process and works on both browsers:

// ==UserScript==
// @name     FooBar
// @match    https://xenforo.com/community/
// @grant    GM_getValue
// ==/UserScript==

unsafeWindow.GM_simplevar = GM_getValue ('lorem', 'Hello world!');

function GM_usePagesOverlay ($) {
    XenForo.createOverlay (null, $(`
    <div class="xenOverlay">
        <form id="efd_form">
            <div class="section">
                <h2 class="heading h1">Greasemonkey test</h2>
                <h3 class="primaryContent">${GM_simplevar}</h3>
            </div>
        </form>
    </div>
    `), { noCache: true }).load ();
}

withPages_jQuery (GM_usePagesOverlay);

function withPages_jQuery (NAMED_FunctionToRun) {
    //--- Use named functions for clarity and debugging...
    var funcText        = NAMED_FunctionToRun.toString ();
    var funcName        = funcText.replace (/^function\s+(\w+)\s*\((.|\n|\r)+$/, "$1");
    var script          = document.createElement ("script");
    script.textContent  = funcText + "\n\n";
    script.textContent += 'jQuery(document).ready(function() {'+funcName+'(jQuery);});';
    document.body.appendChild (script);
}


For more complex data interchange, between page scope and userscript scope, you may need to use messaging.


*** Tampermonkey's behavior violates sandbox paradigms and might be a security hole that allows bad web pages to access privileged GM_ functions -- need to investigate this one day... :)

Upvotes: 2

Related Questions