Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use JavaScript to prevent a later `<script>` tag from being evaluated?

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...

like image 854
Trevor Burnham Avatar asked Jan 18 '11 16:01

Trevor Burnham


2 Answers

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

like image 85
Alohci Avatar answered Oct 10 '22 01:10

Alohci


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)

like image 33
Kyle Wild Avatar answered Oct 10 '22 02:10

Kyle Wild