Let's say I am working with the following web page:
<html>
<body>
<span id="click">click me</span>
<script>
var hello = function() {
alert('hello');
}
document.getElementById('click').addEventListener('click', function(e) {
hello();
});
</script>
</body>
</html>
and my Greasemonkey script is:
// ==UserScript==
// @name My Script
// @include http://example.com/hello.html
// @version 1
// @grant none
// ==/UserScript==
window.hello = function() {
alert('goodbye');
}
With the Greasemonkey script disabled, clicking the #click
element on the page displays the 'hello' alert. With the script enabled, clicking the element displays the 'goodbye' alert.
Simple enough. The hello
function from the web page is being replaced by the function in the Greasemonkey script.
Now let's say I want to use a Greasemonkey API. When I set the @grant
value to a valid value other than 'none' (e.g. // @grant GM_setClipboard
) [which causes Greasemonkey to run the script as a "content script", rather than in the page's scope like with 'none'], the Greasemonkey script fails to work.
window.hello
is no longer targeting the correct object on the page.
Replacing window.hello
with unsafeWindow.hello
looks like it would work, but instead, the following error is thrown in the JS console:
Error: Permission denied to access object
How can I rewrite the Greasemonkey script while having @grant GM_setClipboard
set to target and replace the original hello
function on the page?
System information:
When you set a @grant
value other than none, Greasemonkey activates its sandbox and Greasemonkey 2.0 radically changed unsafeWindow handling.
Now, in order to create or overwrite variables in the target-page scope, you must correctly chose from a menu of techniques. EG:
A simple variable:
Target page sets: var foo = "bar";
GM script can read: unsafeWindow.foo //-- "bar"
A simple object:
Target page sets: var obj = {A: 1};
GM script can read: unsafeWindow.obj //-- Object { A: 1 }
A complex object: This is not always possible.
A simple function:
Target page sets: function func () {console.log ('Hi');}
GM script can call: unsafeWindow.func() //-- "Hi"
A complex function: This is not always possible.
A simple variable:
unsafeWindow.foo = "Apple";
A simple object:
var gmObject = {X: "123"};
unsafeWindow.obj = cloneInto (gmObject, unsafeWindow);
A simple function:
function gmFunc () {
console.log ("Lorem ipsum");
//-- Can use GM_ functions in here! :)
}
unsafeWindow.func = exportFunction (gmFunc, unsafeWindow);
Consider this HTML:
<button id="helloBtn">Say "Hello".</button>
And this javascript:
var simpleGlobalVar = "A simple, global var in the page scope.";
var globalObject = {Letter: "A", Number: 2};
function simpleFunction () {
console.log ("The target page's simpleFunction was called.");
}
var sayHello = function() {
console.log ('Hello.');
}
document.getElementById ('helloBtn').addEventListener ('click', function () {
sayHello ();
} );
which you can see live at this jsFiddle page.
If you install and run this Greasemonkey script against that page:
// ==UserScript==
// @name _Demonstrate accessing target-page variables with @grant values set
// @include http://fiddle.jshell.net/sepwL7n6/*/show/
// @require http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js
// @grant GM_addStyle
// ==/UserScript==
console.log ("*** Greasemonkey script start.");
$("body").append ('<div id="gmArea">Added by Greasemonkey:<p></p></div>');
$("#gmArea > p:first").append ('<button id="gmShow">Access select target-page variables and functions</button>');
$("#gmArea > p:first").append ('<button id="gmChange">Change javascript things in the target-page scope.</button>');
$("#gmShow").click ( function () {
//-- Access things from the target-page scope:
console.log ("----------------");
console.log ("==> simpleGlobalVar is: ", unsafeWindow.simpleGlobalVar);
console.log ("==> globalObject is: ", unsafeWindow.globalObject);
console.log ("==> Calling target's simpleFunction():");
unsafeWindow.simpleFunction ();
//-- WARNING! This next technique is not robust, but works in some cases.
console.log ("==> Calling target's button's click().");
unsafeWindow.document.getElementById ('helloBtn').click ();
} );
$("#gmChange").click ( function () {
this.disabled = true; //-- Can only click once.
unsafeWindow.simpleGlobalVar = "Simple var... Intercepted by GM!";
unsafeWindow.globalObject = cloneInto (gmObject, unsafeWindow);
unsafeWindow.sayHello = exportFunction (sayHello, unsafeWindow);
console.log ("==> Target page objects were changed.");
} );
var gmMessageStr = "Function... Intercepted by GM, but also can use GM_ functions!";
function sayHello () {
sayHello.K = (sayHello.K || 0) + 1;
console.log (gmMessageStr);
GM_addStyle ('body {background: ' + (sayHello.K % 2 ? "lime" : "white") + ';}');
}
var gmObject = {message: "Object overridden by GM."};
Open the console and press the buttons and you will see that the GM script is able to read and change the page's variables and functions.
Notes:
GM_
functions.Wrap your code inside a lambda function like:
(function(window){ // and more arguments if you need it
console.log(window); // here, should be the real 'window'
})(window.unsafeWindow)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With