问题
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.
回答1:
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);
}
回答2:
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!).
来源:https://stackoverflow.com/questions/10472569/changing-javascript-on-an-html-page-out-of-my-control