Greasemonkey access violation

From GreaseSpot Wiki
Jump to: navigation, search

Greasemonkey 0.7.20080121.0 introduced a change to work around a potential security problem which could break some previously working scripts.

Starting at this version, GM "double-checks" the call stack of all calls into potentially unsafe Greasemonkey APIs to make sure that every frame on the stack is either from a user script, or from the browser DOM. The APIs that currently have this check are:

  • GM_deleteValue
  • GM_getResourceText
  • GM_getResourceURL
  • GM_getValue
  • GM_listValues
  • GM_openInTab
  • GM_registerMenuCommand
  • GM_setValue
  • GM_xmlhttpRequest

This means that you cannot register a callback with content-defined JavaScript and then call one of these GM APIs from that callback. If you do attempt this, an error message will be sent to the console, like below, and the function will simply fail.

Error: Greasemonkey access violation: unsafeWindow cannot call GM_getValue.

Code like this will not work:

unsafeWindow.someObject.registerCallback(function() {
 var value = "bar";
 GM_setValue("foo", value);
});

Here is a workaround for this limitation:

unsafeWindow.someObject.registerCallback(function() {
  var value = "bar";
  setTimeout(function() {
    GM_setValue("foo", value);
  }, 0);
});

The code above is only useful when javascript is enabled in the page. If, for example, the NoScript extension prevents execution of javascript code in the current domain, the setTimeout function will never be called. To circumvent this limitation a slightly more elaborate construction can be used:

function fakeTimeout(callback) {
  // Register event listener
  window.document.body.addEventListener("timeoutEvent", callback, false);
  // Generate and dispatch synthetic event
  var ev = document.createEvent("HTMLEvents");
  ev.initEvent("timeoutEvent", true, false);
  window.document.body.dispatchEvent(ev);
}

unsafeWindow.someObject.registerCallback(function() {
  fakeTimeout(function() {
    GM_setValue("foo", new Date().getTime());
  });
});