Location hack: Difference between revisions

From GreaseSpot Wiki
Jump to navigationJump to search
m Text replace - "</pre>}}" to "</pre>"
m move content to "Generate Click Events" page
Line 11: Line 11:
== Basic usage: page functions ==
== Basic usage: page functions ==


Suppose the page contains a function called <code>pageFunc</code>, or <code>window.pageFunc</code>. The user script knows this function as <code>unsafeWindow.pageFunc</code>.
Suppose the page contains a function called <code>pageFunc</code>, or <code>window.pageFunc</code>.
The user script knows this function as <code>unsafeWindow.pageFunc</code>.


The user script could simply call <code>unsafeWindow.pageFunc()</code>, but this can leak the sandbox. Instead, the user script can take advantage of <code>javascript:</code> URLs, which always run in the content scope. Just entering this URL into the browser's location bar does not leak a GreaseMonkey sandbox:
The user script could simply call <code>unsafeWindow.pageFunc()</code>, but this can leak the sandbox.
Instead, the user script can take advantage of <code>javascript:</code> URLs, which always run in the content scope.
Just entering this URL into the browser's location bar does not leak a GreaseMonkey sandbox:


{{Samp |why=WARNING: Cross-platform Location hack failure |1=<pre style="border: none; margin: inherit;">
javascript:pageFunc();void(0)
javascript:void(pageFunc())
</pre>


Similarly, a user script can set location.href to this URL to safely call the function:
A user script can programmaticaly navigate to this URL, to safely call the function:


{{Samp |why=WARNING: Cross-platform Location hack failure |1=<pre style="border: none; margin: inherit;">
location.assign( "javascript:pageFunc();void(0)" );
location.href = "javascript:void(pageFunc())";
</pre>


== Invoke onclick behavior ==
That, in a nutshell, is the location hack!
Essentially, it is wrapping a [[wikipedia:bookmarklet]] into a user script.


Sometimes a userscript wants to simulate the behavior of clicking a link that has an ''onclick'' handler. For example, on a YouTube video page (like [http://www.youtube.com/watch?v=k8x14cLGh5o this]) in the video description there is the link '''more''' with an  ''onclick'' handler that as of this writing can be found with the XPath
It's important to add the <code>javascript:</code> scheme to the front, to turn it into a URL, and the <code>;void(0)</code> to the end, which keeps the browser from actually navigating to this URL after it is run.
 
"//div[@id='videoDetailsDiv']//a[text() = 'more']/@onclick"
 
and it contains the string:
 
addClass(_gel('videoDetailsDiv'), 'expanded'); return false;
 
If the variable <code>onclick</code> is bound to the XPath result, then this handler can be invoked through location thusly:
 
location.href = "javascript:void((function(){" + onclick.nodeValue + "})());";
 
Note that the ''onclick'' has to be wrapped in a function so that its <code>return</code> has a scope and the whole needs to be wrapped in <code>void</code>.
 
This example is rather robust because it will still work if YouTube redefines the contents of its ''onclick''s.
 
=== Really simulating a click ===
If a link has one or more anonymous <code>eventListener</code>s and/or it is within the DOM scope of some element with an explicit 'onclick' handler or an anonymous <code>eventListener</code> then just evaluating an 'onclick' handler will not simulate a click. One really has to just just send the link a fake [http://developer.mozilla.org/en/docs/DOM:document.createEvent event]. Assume the variable <code>link</code> is bound to some link. Then the following code will send it a "click" event.
 
var evt = document.createEvent("HTMLEvents");
evt.initEvent("click", true, true);
link.dispatchEvent(evt);
 
'''Note:''' The <code>click</code> event is really a concatenation of the [http://developer.mozilla.org/en/docs/DOM:element.onmousedown mousedown] and [http://developer.mozilla.org/en/docs/DOM:element.onmouseup mouseup] events. '''However''' I don't know if sending a link a fake "mousedown" followed by a fake "mouseup" will cause it to recognize it as a "click".
 
== Trigger javascript: links ==
 
This hack can also be used to trigger <code>javascript:</code> links – simply do
 
location.href = someLink.href;


== Modifying the page ==
== Modifying the page ==
Line 127: Line 98:
   return(GM_getGlobalElement.value);
   return(GM_getGlobalElement.value);
  }
  }
[[Category:Coding Tips:Interacting With The Page]]

Revision as of 17:16, 4 February 2010

The location hack is an ugly but useful way to interact with the content scope of the page being user scripted. It does this by indirectly evaling strings within that scope.

Background

For security reasons, Greasemonkey uses XPCNativeWrappers and sandbox to isolate it from the web page. Under this system, the user script can access and manipulate the page using event listeners, the DOM API, and GM_* functions.

Sometimes the sandbox is too limiting, in which case the user script can access other parts of the page using unsafeWindow. As the name unsafeWindow implies, this can often be unsafe, and expose security holes.

In December 2005, Jesse Ruderman came up with the location hack, to be an alternative to unsafeWindow in many cases.

Basic usage: page functions

Suppose the page contains a function called pageFunc, or window.pageFunc. The user script knows this function as unsafeWindow.pageFunc.

The user script could simply call unsafeWindow.pageFunc(), but this can leak the sandbox. Instead, the user script can take advantage of javascript: URLs, which always run in the content scope. Just entering this URL into the browser's location bar does not leak a GreaseMonkey sandbox:

javascript:pageFunc();void(0)

A user script can programmaticaly navigate to this URL, to safely call the function:

location.assign( "javascript:pageFunc();void(0)" );

That, in a nutshell, is the location hack! Essentially, it is wrapping a wikipedia:bookmarklet into a user script.

It's important to add the javascript: scheme to the front, to turn it into a URL, and the ;void(0) to the end, which keeps the browser from actually navigating to this URL after it is run.

Modifying the page

The location hack can do anything a page script or bookmarklet can do, so it can modify content variables and such as easily as it can access them. For example:

location.href = "javascript:void(window.someVariable = 'someValue')";

Executing large blocks of code

Executing more than one statement can become unreadable very easily. Luckily, JavaScript can convert functions to strings, so you can use:

location.href = "javascript:(" + function() {
  // do something
} + ")()";

Even though the function is defined in the sandbox, it is not a closure of the sandbox scope. It is converted to a string and then back to a function in page scope. It cannot access anything in the sandbox scope, which is a limitation, but is also essential to making this technique secure.

Percent encoding issue

Sometimes percent-encoding the percent symbol is required. For example,

location.href = ("javascript:(" + function() {
  var n = 44;
  if(!(n%22)) alert('n is a multiple of 22');
} + ")()");

The above code will cause error because %22 is interpreted as double quotation mark. The workaround is:

location.href = "javascript:(" + encodeURI(
 function() {
  var n = 44;
  if(!(n%22)) alert('n is a multiple of 22');
 }) + ")()";

See also encodeURI().

Returning values

Functions called through the location hack cannot return data directly to the user script scope. To communicate between location hack code and regular user script code, data must be placed where the user script can see it, for example, by writing it into the DOM, or by triggering an event. A simple example:

var oldBodyTitle = document.body.title;
location.href = "javascript:void(document.body.title = pageFunc())";
var fauxReturnValue = document.body.title;
document.body.title = oldBodyTitle;

This trick, however, can be defeated by the hosting page by simply overriding the document.body.title property with a setter.

document.body.__defineSetter__("title", function(t){} );

While this does not mean a threat to the user visiting the page, it effectively prevents the userscript from accessing the returned value of the global function. A working example is available here.

However, __defineSetter__ itself can be overridden using the delete operator. A modified version of the linked user script above, using delete to ignore the setter, is available.

Function to get values of global variables

The following function can be used to access the values of global variables using var value = GM_getGlobalElement('globalVariable');. Please note that the returned value is converted to a string.

// function to get values of global variables using the "location hack"
window.GM_getGlobalElement = null;
window.getGlobalValue = function(name) {
  if (GM_getGlobalElement == null) {
    GM_getGlobalElement = document.createElement("textarea");
    GM_getGlobalElement.id = "GM_getGlobalElement";
    GM_getGlobalElement.style.visibility = "hidden";
    GM_getGlobalElement.style.display = "none";
    document.body.appendChild(GM_getGlobalElement);
  }
  location.href = "javascript:void(typeof("+ name + ")!=\"undefined\"?document.getElementById(\"GM_getGlobalElement\").value=" + name + ":null)";
  return(GM_getGlobalElement.value);
}