45

This is a bit of an oddball use case, but I have my reasons:

I'd like to be able to write

<script type="text/javascript" src="first.js"></script>
<script type="text/javascript" src="second.js"></script>

in my markup and, using the code in first.js, prevent or delay the execution of second.js. Is this possible, in any browser? What if the contents of first.js are inlined? (If it helps, assume that the second script tag has an id attribute.)

Since I've gotten a couple of answers that missed what I'm getting at, I should clarify:

  1. The solution must be entirely within first.js. Anything that require changes to the original HTML of the page, or to second.js, is not acceptable.
  2. It is acceptable to load second.js via Ajax and execute it using eval. That's the easy part. The hard part is preventing the immediate execution of second.js.
  3. Assume that you don't know what's in second.js. So, you can't just replace each global function called by second.js with a no-op function. (Plus, this would almost certainly lead to errors.)

If you know of a solution that works in some browsers but not in others, I'd love to hear it.

Example: To make this a little more concrete, let's say that the code

<script type="text/javascript">
  function func() {
    window.meaningOfLife = 42;
    window.loadSecond();
  };
  setTimeout(func, 10);
</script>

precedes the two script includes, and that second.js contains the line

if (window.meaningOfLife !== 42) {throw new Error();}

first.js should be able to prevent this error by delaying second.js from executing until window.loadSecond is run. (Assume the implementation of window.loadSecond is also in first.js.) It is not allowed to touch window.meaningOfLife.

Update: Alohci's answer meets these requirements, but only on the condition that the second script tag comes immediately after the first, with nothing but whitespace in between. If someone could extend his hack to avoid that requirement, without introducing other unwanted consequences, that would be amazing...

0

7 Answers 7

39
+200

Given your specific requirements set, this is actually quite simple and should work completely cross-browser. It does require however, that first.js immediately precedes second.js without anything between them except white space.

First, let's assume that the HTML looks like this:

<!DOCTYPE html>
<html>
  <head>
    <title>Test Case</title>
    <meta charset="UTF-8" />
    <script type="text/javascript">
      function func() {
        window.meaningOfLife = 42;
        window.loadSecond();
      };
    </script>
    <script type="text/javascript" src="first.js"></script>
    <script type="text/javascript" src="second.js"></script>
  </head>
  <body>
    <p>Lorem ipsum dolor sit amet ...</p>
    <a href="javascript:func()">Run Func()</a>
  </body>
</html>

I've removed the setTimeout because that can cause func() to run before start.js runs causing a "loadSecond is not defined" error. Instead, I've provided an anchor to be clicked on to run func().

Second, let's assume that second.js looks like this:

document.body.appendChild(document.createTextNode("second.js has run. "));
if (window.meaningOfLife !== 42) {throw new Error();}

Here, I've just added a line to append some text to the document body, so that it is easier to see when second.js actually runs.

Then the solution for first.js is this:

function loadSecond()
{
    var runSecond = document.createElement("script");
    runSecond.setAttribute("src", "second.js"); 
    document.body.appendChild(runSecond);
}

document.write("<script type='application/x-suppress'>");

The loadSecond function is just there to run second.js when func() runs.

The key to the solution is the document.write line. It will inject into the HTML <script type='application/x-suppress'> between the close script tag of first.js and the open script tag of second.js.

The parser will see this and start a new script element. Because the type attribute has a value which is not one that the browser recognises as being JavaScript, it will not attempt to run its content. (So there are an infinite number of possible type attribute values you could use here, but you must include a type attribute, as in its absence, the browser will assume that the script's content is JavaScript.)

The second.js script's opening tag will then be parsed as text content of the new script element and not executed. Finally the second.js script's closing tag will be re-purposed to close the new script element instead, which means that the remainder of the HTML is parsed correctly.

You can see a working version at http://www.alohci.net/static/jsprevent/jsprevent.htm

Sign up to request clarification or add additional context in comments.

4 Comments

@Ms2ger, that is completely irrelevant to the question... Alohci, good answer!
This is a gut wrenchingly ugly hack, but it seems to be the best shot given the lack of a real mechanism for doing this. Anyone doing who uses this should be aware that it is a fragile and ugly hack that browsers may choose to crush at any time. If this the non-execution of a script is really mission critical, I suggest working with the original authors of the html and other js files.
They said it couldn't be done, but you've done it! While I agree that this probably shouldn't be used in production, it works without a hitch in every browser I've tried it in. Congrats, Alohci, the bounty is yours. :)
You are a genius. @Alohci
8

In first.js, set var shouldILoad = true

Then, load second.js this way:

<script>
    if (shouldILoad) {
        (function() {
            var myscript = document.createElement('script');
            myscript.type = 'text/javascript';
            myscript.src = ('second.js');
            var s = document.getElementById('myscript');
            s.parentNode.insertBefore(myscript, s);
        })();
    }
</script>

(where 'myscript' is the ID of some element before which you'd like to insert the new Script element)

2 Comments

Sorry, my original phrasing may have been unclear: Assume that I have no control over the original HTML of the page; I only control the content of first.js.
Ah, in that case I have no clue. I'll leave my answer up, as this is still a useful technique if you do have that access :)
8

Following article describes the way you could block (3-rd party) scripts loading/execution from your script (including the both tag in the page head and dynamically added tags).

To handle existing tags on a page:

  1. Use a MutationObserver to observe script elements insertion and inside the MutationObserver callback backup the script (to enable/insert it later) and change the script type to "javascript/blocked" (not works in IE, Edge, Firefox). Also you could handle deprecated (but working) beforescriptexecute event in Firefox to prevent script load.
  2. Manually set type "javascript/blocked" (works everywhere including IE and Edge) like <script type="text/javascript" type="javascript/blocked" src="second.js"></script>, then backup it in MutationObserver callback and re-add it later.

To handle dynamically added tags

  1. Monkey-patch the document.createElement.
  2. Override ‘src’ and ‘type’ descriptors on the HTMLScriptElement prototype.

Also this guys provide a yett library with the approach described in the article.

3 Comments

The monkey-patch of the YETT-library is an absolute nightmare for third some party scripts. Releasing the third party scripts will not work for all of them.
If this still works, it seems that it would be a much more comprehensive and flexible approach than the accepted answer.
It works. It works in most major browsers (IE11 including). It works with older IE with polyfill. The approach it uses is used by many cookie blocking solutions out there. It should be the accepted answer.
6

As far as I know, you can't. If the markup looks like

<script type="text/javascript" src="first.js"></script>
<script type="text/javascript" src="second.js"></script>

you can't access the second script element from within first.js, as it hasn't been added to the DOM at the moment the first script runs (even not if you assign an id to the second element). It doesn't matter whether the code of second.js is put inline or in an external file.

The only thing I don't understand is your second point. First you say that you can't control the markup of the document, but then you state it is possible to load second.js dynamically (using AJAX).

4 Comments

Why is this voted down? I hope not because it is not the answer the OP hoped for. Please explain.
Right, I agree that it's seemingly impossible... and yet, galambalazs has a solution that works in some cases. :) My first point is that you don't have control of the original markup of the document; you can only manipulate it from first.js. I was responding to dorkitude's solution, which only works if you omit the original <script src="second.js"> tag.
It was established in a past SO (sorry couldn't find it) thread that <script> tags evaluate individually, not all-at-once.
@user: Sure, but still you can't manipulate DOM elements that have not been added yet.
3

All <script> tags have their own execution context, which makes it nearly impossible to interfere with each other. Of course you've got the (infamous) global object (referenced by window in browsers).

Preventing the execution of second.js is rather simple: break it! Assuming that second.js tries to call document.getElementById for example:

Working example of breaking jQuery, then loading later (with dependecies).
Tested on: IE 6+, FF 3.6+, Chrome

end of first.js

var execute;

// saving our position
var scripts = document.getElementsByTagName("script");
var i = scripts.length;

// breaking getElementById
var byId = document.getElementById;
document.getElementById = null;

var interval = setInterval(function () {
    if (i != scripts.length) {
      var second = scripts[i];
      // stop polling
      clearInterval(interval);
      // fix getElementById
      document.getElementById = byId;
      // set the delayed callback
      execute = function (onload) {
        var script = document.createElement("script");
        script.src = second.src;
        script.onload = script.onreadystatechange = onload;
        document.getElementsByTagName("head")[0].appendChild(script);
      };
    }
}, 100);

anytime you wanna execute second.js

execute(function(){
   // second.js dependant code goes here...
});

Note: the onload parameter for execute is optional.

6 Comments

Although I should also note that the whole idea is rather spooky. :)
There are a couple of problems with this solution. For one (and I should've been clearer in my original post), I want a solution that will work even when the contents of second.js are unknown. And 2) if you break things that second.js calls, you'll almost certainly get errors. I don't want errors. Even replacing document.getElementById with a no-op function would cause problems when second.js tries to do something with the resulting element.
I'm still upvoting this as the most comprehensive answer given so far—the working example is terrific. If only it could be generalized...
@Trevor - There's probably no other way, the point is that you can only stop the execution if there's an error. Why the heck do you need this anyway? If you don't own the markup & the other file you shouldn't mess with them at all.
An important technical question for this solution: The code seems to treat the "scripts" variable like some kind of dynamic object that will magically update itself as soon as another script is added to the page (since the setInterval() code polls the length of this "script" variable without first reassigning it with another document.getElementsByTagName() call)? Are all results from document.getElementsByTagName() really dynamic in this way (i.e. modifying themselves automagically after they have been assigned), or am I missing something else important here?
|
0

Just to see if this was possible, I had first.js send a synchronous XHR to a PHP file, and had the PHP file delete second.js. When the readyState reached '4', I had the JS alert something, to stop the thread. Then I went and checked the server... Yeah, second.js was deleted. And yet, it wouldn't work. I'd close the alert box, and the code that was in second.js would still be executed, despite the fact that the file was gone.

I don't really know what this means, but the answer to your question is probably, "No, it's not possible."

2 Comments

Both JavaScript files may be loaded simultaneously, so you probably delete second.js after it was already fully sent to the browser.
That's an amusing method. But yeah, I'm not surprised that it didn't work. And let's assume that we're not allowed to manipulate the server, shall we?
-4

you may use setTimeout() to delay the execution of some code

1 Comment

Right, but I'm asking: What if I have control over first.js, and not second.js?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.