Thursday 11 July 2013

How to dynamically inject debuggable javascript into Chrome Dev Tools

I love Chrome Dev tools, I love how you can insert breakpoints and step through your source code. However, during my escapades in re-writing js source code on the fly for the realtime coding feature of the 3d multiplayer game maker I'm writing (Multi). I found a few interesting pain points.

For the following examples, please imagine that the code in the variable scriptText has been loaded from a cache.


Using Eval
If you use eval, the injected code will not be shown in Chrome Dev Tools.

jstest.html
<html><head></head><body></body>

<script>
var scriptText = "console.log( 'hello' );";
eval( scriptText );
</script>

</html>


Using script.text
If you create a script element then include the javascript code in the text tag for parsing, again, the script will not be shown in Chrome Dev Tools.


jstest.html
<html><head></head><body></body>

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

</html>


jstest.js
var head = document.getElementsByTagName( 'head' )[0];
var script = document.createElement( 'script' );
script.type = 'text/javascript';
head.appendChild( script );

var scriptText = "console.log( 'hello' );";
script.text = scriptText;

There is an open bug from 2011 on this issue which I've left a comment towards. So hopefully when this gets fixed, you'll no longer be able to do this trick.


Using Blobs
If you would like to see your code in the sources tab, then you should use the .src field to point to a blob containing your script.


jstest.js
var head = document.getElementsByTagName( 'head' )[0];
var script = document.createElement( 'script' );
script.type = 'text/javascript';
head.appendChild( script );

var scriptText = "console.log( 'hello' );";
script.src = window.URL.createObjectURL( new Blob( [scriptText] ) );



Notes
If you would like to get up to nefarious activities, eval and script.text is a great approach. However, if you're trying to generate some dynamic coding environments, then you're going to run into some problems. While using blobs is great to see the injected script, there are a few issues I'm still working through with them. It seems that if you inject a script to override a class you've previously injected, the interpreter will ignore your new script. So what I've ended up doing in the meantime, is using .src for a server side generated url, which works, but requires server-side setup.

If you have any solutions around this problem, I'd love to hear.

4 comments:

  1. If you need to debug your dynamically appended JavaScript string, adding a line with `debugger;` to the string will cause the debugging tools to break on it upon execution:

    var script = document.createElement('script');
    var js = 'debugger;\n' + //MAGIC HERE.
    'console.log("test");';
    script.append(document.createTextNode(js));
    document.body.append(script);

    You'll probably want to be able to remove or toggle that as part of your JS "build" process in Grunt or whatever. But that should work in most browser dev tools. (Blob isn't supported by IE9 and under, I don't think)


    ReplyDelete
    Replies
    1. For what it's worth, it works for eval() too...

      eval('debugger; console.log("test")');

      Delete
    2. Final post (sorry for the spam):

      The hidden feature to adding "debugger" to a string of JS you're creating (presumably) programmatically and evaluating, is it gives you the ability to set a breakpoint programmatically as well.

      So let's say you have some huge amount of multiline JS:

      var multilineJS = getAWholeCrapTonOfJS();

      function insertBreakpoint(js, lineNo) {
      var lines = js.split('\n');
      lines.splice(lineNo - 1, 0, 'debugger;\n');
      return lines.join('\n');
      }

      var withBreakpointLine10 = insertBreakpoint(multilineJS, 10);


      ... or something like that.

      Either way... cool article, happy coding.

      Delete
    3. Thanks for that info! I wonder if that will work inside an Android WebView to hack together some line by line remote webview debugging.
      As hacky as JavaScript can be sometimes, I do love the crazy flexibility it gives you.

      Delete