4

I'm using an HTML page out of my control. It defines a Javascript function in an inline <script> tag and calls it in <body onload="..."> :

<html>
...
<body onload="init()">
<script type="text/javascript" language="javascript">
    function init() {
        ...
    }
</script>
...

How can I change that function before it's called? I've tried to use Greasemonkey to modify the script or to insert another script just after it to override the function, but it doesn't seem to have any effect.

palm3D
  • 7,970
  • 6
  • 28
  • 33
  • how much is this HTML page out of your control if you can still modify the JavaScript on it? – Hawken May 06 '12 at 03:28
  • @Hawken: Greasemonkey is a browser add-on for rewriting incoming HTML just before it's rendered – palm3D May 07 '12 at 15:35

2 Answers2

10

Greasemonkey can now usually do this kind of thing by leveraging the beforescriptexecute event and @run-at document-start. Note that only Firefox seems to support that event and so this will not work on Chrome. See here and here for more tedious approaches.

To change that init() function before it is called, leverage the checkForBadJavascripts() function that is defined below.

You would call it like this:

//--- New "init" function to replace the bad one.
function init () {
    //... Do what you want here...
}

checkForBadJavascripts ( [
    [false, /function\s+init(/, function () {addJS_Node (init);} ]
] );

Where function\s+init( must be unique to the <script> tag you are targeting. (Note that addJS_Node() is defined below, too.)


For example, visit this page at jsBin. You will see 3 lines of text, two of them added by JS.

Now, install the following script and revisit the page. You will see that the GM-script deleted one bad <script> tag and replaced another with our "good" JS.

// ==UserScript==
// @name        _Replace evil Javascript
// @include     http://output.jsbin.com/tezoni*
// @run-at      document-start
// @grant       none
// ==/UserScript==

/****** New "init" function that we will use
    instead of the old, bad "init" function.
*/
function init () {
    var newParagraph            = document.createElement ('p');
    newParagraph.textContent    = "I was added by the new, good init() function!";
    document.body.appendChild (newParagraph);
}

/*--- Check for bad scripts to intercept and specify any actions to take.
*/
checkForBadJavascripts ( [
    [false, /old, evil init()/, function () {addJS_Node (init);} ],
    [true,  /evilExternalJS/i,  null ]
] );

function checkForBadJavascripts (controlArray) {
    /*--- Note that this is a self-initializing function.  The controlArray
        parameter is only active for the FIRST call.  After that, it is an
        event listener.

        The control array row is  defines like so:
        [bSearchSrcAttr, identifyingRegex, callbackFunction]
        Where:
            bSearchSrcAttr      True to search the SRC attribute of a script tag
                                false to search the TEXT content of a script tag.
            identifyingRegex    A valid regular expression that should be unique
                                to that particular script tag.
            callbackFunction    An optional function to execute when the script is
                                found.  Use null if not needed.
    */
    if ( ! controlArray.length) return null;

    checkForBadJavascripts      = function (zEvent) {

        for (var J = controlArray.length - 1;  J >= 0;  --J) {
            var bSearchSrcAttr      = controlArray[J][0];
            var identifyingRegex    = controlArray[J][1];

            if (bSearchSrcAttr) {
                if (identifyingRegex.test (zEvent.target.src) ) {
                    stopBadJavascript (J);
                    return false;
                }
            }
            else {
                if (identifyingRegex.test (zEvent.target.textContent) ) {
                    stopBadJavascript (J);
                    return false;
                }
            }
        }

        function stopBadJavascript (controlIndex) {
            zEvent.stopPropagation ();
            zEvent.preventDefault ();

            var callbackFunction    = controlArray[J][2];
            if (typeof callbackFunction == "function")
                callbackFunction ();

            //--- Remove the node just to clear clutter from Firebug inspection.
            zEvent.target.parentNode.removeChild (zEvent.target);

            //--- Script is intercepted, remove it from the list.
            controlArray.splice (J, 1);
            if ( ! controlArray.length) {
                //--- All done, remove the listener.
                window.removeEventListener (
                    'beforescriptexecute', checkForBadJavascripts, true
                );
            }
        }
    }

    /*--- Use the "beforescriptexecute" event to monitor scipts as they are loaded.
        See https://developer.mozilla.org/en/DOM/element.onbeforescriptexecute
        Note that it does not work on acripts that are dynamically created.
    */
    window.addEventListener ('beforescriptexecute', checkForBadJavascripts, true);

    return checkForBadJavascripts;
}

function addJS_Node (text, s_URL, funcToRun) {
    var D                                   = document;
    var scriptNode                          = D.createElement ('script');
    scriptNode.type                         = "text/javascript";
    if (text)       scriptNode.textContent  = text;
    if (s_URL)      scriptNode.src          = s_URL;
    if (funcToRun)  scriptNode.textContent  = '(' + funcToRun.toString() + ')()';

    var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
    //--- Don't error check here. if DOM not available, should throw error.
    targ.appendChild (scriptNode);
}
Community
  • 1
  • 1
Brock Adams
  • 90,639
  • 22
  • 233
  • 295
  • This code didn't work for me. It is also incredibly long and complex. Not to mention it redefines the `checkForBadJavascripts` function within itself. – Rebs Oct 08 '15 at 01:15
  • @Rebs, (1) Open a question with a *proper* problem description if you need to. The code still works as you can see by following the directions in the post using Firefox+Greasemonkey. ... I just updated the target page but the old one was only a little corrupted by jsbin. As for your last 2 points, you are very wrong. The code is straightforward for what it does and that is a valid, and useful, JS technique. Kindly remove your mistaken downvote. – Brock Adams Oct 08 '15 at 02:35
-1

The following Greasemonkey userscript (based on this source) finally worked for me. It overrides the existing function by defining another one with the same name in a new script tag just after the existing script tag. No @run-at or beforescriptexecute was required.

var firstScript = document.body.getElementsByTagName('script')[0];
var newScript = document.createElement('script');
var scriptArray = new Array();
scriptArray.push('function init() {');
scriptArray.push('    ...');
scriptArray.push('}');
newScript.innerHTML = scriptArray.join('\n');
scriptArray.length = 0; // free this memory
firstScript.parentNode.insertBefore(newScript, firstScript.nextSibling);

I didn't have much previous experience with Greasemonkey or even Javascript, so I found the Firefox Web Developer tools indispensable, specifically:

  • the Error Console to catch the many small errors that you're bound to make. and
  • the Inspect tool to see the resulting HTML (because the regular View Source doesn't show that!).
palm3D
  • 7,970
  • 6
  • 28
  • 33
  • 1
    If this works, I believe it's pure luck. It sets up a "race condition" which means that though it might work on some pages, some of the time, it shouldn't work on other pages, depending upon their composition. – Brock Adams May 07 '12 at 22:51