Is it possible to remove script tags in the <head>
of an HTML document client-side and prior to execution of those tags?
On the server-side I am able to insert a <script>
above all other <script>
tags in the <head>
, except one, and I would like to be able to remove all subsequent scripts. I do not have the ability to remove <script>
tags from the server side.
(function (c,h) {
var i, s = h.getElementsByTagName('script');
c.log("Num scripts: " + s.length);
i = s.length - 1;
while(i > 1) {
h.removeChild(s[i]);
i -= 1;
}
})(console, document.head);
However, the logged number of scripts comes out to only 1, since (as @ryan pointed out) the code is being executed prior to the DOM being ready. Although wrapping the code above in a document.ready
event callback does enable proper calculation of the number of <script>
tags in the <head>
, waiting until the DOM is ready fails to prevent the scripts from loading.
Is there a reliable means of manipulating the HTML prior to the DOM being ready?
If you want more context, this is part of an attempt to consolidate scripts where no option for server-side aggregation is available. Many of the JS libraries being loaded are from a CMS with limited configuration options. The content is mostly static, so there is very little concern about manually aggregating the JavaScript and serving it from a different location. Any suggestions for alternative applicable aggregation techniques would also be welcome.
Since you cannot prevent future <script>
tags from evaluating (whenever the </script>
tag has been found, the corresponding code of <script>
is fetched and evaluated. <script src>
will block a document from loading further till the source is fetched unless the async
attribute is set), a different approach need to be taken.
Before I present the solution, I ask: What can prevent a script within a <script>
tag from executing? Indeed,
<script>
from the source code.1 is obvious, and 2 can be derived from the documentation, so I'll focus on 3. The examples below are obvious, and need to be adjusted for real-world use cases.
Here's a general pattern for proxying existing methods:
(function(Math) {
var original_method = Math.random;
Math.random = function() {
// use arguments.callee to read source code of caller function
if (/somepattern/.test(arguments.callee.caller)) {
Math.random = original_method; // Restore (run once)
throw 'Prevented execution!';
}
return random.apply(this, arguments); // Generic method proxy
};
})(Math);
// Demo:
function ok() { return Math.random(); }
function notok() { var somepattern; return Math.random(); }
In this example, the code-blocker runs only once. You can remove the restoration line, or add var counter=0;
and if(++counter > 1337)
to restore the method after 1337 calls.
arguments.callee.caller
is null
if the caller is not a function (eg. top-level code). Not a disaster, you can read from the arguments or the this
keyword, or any other environment variable to determine whether the execution must be stopped.
Demo: http://jsfiddle.net/qFnMX/
Here's a general pattern for breaking setters:
Object.defineProperty(window, 'undefinable', {set:function(){}});
/*fail*/ function undefinable() {} // or window.undefinable = function(){};
Demo: http://jsfiddle.net/qFnMX/2/
And getters, of course:
(function() {
var actualValue;
Object.defineProperty(window, 'unreadable', {
set: function(value) {
// Allow all setters for example
actualValue = value;
},
get: function() {
if (/somepattern/.test(arguments.callee.caller)) {
// Restore, by deleting the property, then assigning value:
delete window.unreadable;
window.unreadable = actualValue;
throw 'Prevented execution!';
}
return actualValue;
},
configurable: true // Allow re-definition of property descriptor
});
})();
function notok() {var somepattern = window.unreadable; }
// Now OK, because
function nowok() {var somepattern = window.unreadable; }
function ok() {return unreadable;}
Demo: http://jsfiddle.net/qFnMX/4/
And so on. Look in the source code of the scripts you want to block, and you should be able to create a script-specific (or even generic) script-breaking pattern.
The only downside of the error-triggering method is that the error is logged in the console. For normal users, this should not be a problem at all.