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
?
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... :)