Damian Mehers' Blog Evernote and Wearable devices. All opinions my own.

23Nov/0611

Using the Active Scripting Debug API to log all JavaScript that IE executes

You can download the free TraceJS program here. Run it, select the Internet Explorer instance you are interested in, and you'll be able to see all JavaScript it executes.  If you get an error starting it up, it is likely you do not have Microsoft's Active Scripting Debug API installed.  You can download the Microsoft Script Debugger, which will install it, here

Sometimes I get the germ of an idea and I won't let go until I have implemented it. In this case I wanted to see every line of JavaScript code that was being executed inside Internet explorer. There are plenty of library routines that let you instrument your code, but I wanted something different.

I wanted to actually hook into Internet explorer somehow. A debugger API seemed like the obvious way to go. But which debugger the API? There are so many to choose from. In the end I discovered that the Active Scripting API was what I needed. Unfortunately it's been a long time since anyone has used that it API. Especially from .NET. There were a smattering of articles on the web but none did exactly what I wanted.

In order to use it from .NET I needed a type library in order to generate a COM Interop assembly. Eventually I found one in an old version of the Internet explorer SDK. Having generated to the Interop assembly I found I needed to make some corrections. So I generated an IL file corrected it and regenerated the assembly.

For a long time when I first used the API it did not seem to be working. I could not connect to Internet Explorer. The call to connect failed every time. I eventually discovered that it had actually worked the first time and that subsequent attempts to connect were failing because there was already a debugger attached. DOH!

From there on it was pretty plain sailing. As soon as I attached the debugger I requested a breakpoint. I received a call back when the breakpoint occurred and then logged all the information about the current statement including the filename, the function name, the current stack depth, the line number, and of course the current chunk of JavaScript that was being executed. This is the callback code that gets executed when a breakpoint occurs:

// Called on breakpoint, which is for each statement in the target app
public void onHandleBreakPoint(IRemoteDebugApplicationThread prpt,
                               tagBREAKREASON br,
                               IActiveScriptErrorDebug pError)
{
    try
    {
        // Properties of the current breakpoint which we will be fetching
        string fileName;
        string functionName;
        string code = "";

        // Various interfaces and structs used for active script debugging
        IEnumDebugStackFrames enumDebugStackFrames;
        tagDebugStackFrameDescriptor debugStackFrameDescriptor;
        IDebugCodeContext debugCodeContext;
        IDebugDocumentContext debugDocumentContext;
        IDebugDocument debugDocument;

        // Get the function name from the first stack frame
        prpt.EnumStackFrames(out enumDebugStackFrames);
        uint stackFrameCount;
        enumDebugStackFrames.RemoteNext(1, out debugStackFrameDescriptor,
                                out stackFrameCount);
        if (stackFrameCount != 1)
        {
            return;
        }

        debugStackFrameDescriptor.pdsf.GetDescriptionString(0,
                                                            out functionName);

        // Get the file name
        debugStackFrameDescriptor.pdsf.GetCodeContext(out debugCodeContext);
        debugCodeContext.GetDocumentContext(out debugDocumentContext);
        debugDocumentContext.GetDocument(out debugDocument);
        debugDocument.GetName(tagDOCUMENTNAMETYPE.DOCUMENTNAMETYPE_URL,
                              out fileName);

        // If we are not in the same file and function as previous
        // breakpoint, then find the stack depth
        if (fileName != lastFile || functionName != lastFunction)
        {
            stackDepth = 0;

            while (stackFrameCount == 1)
            {
                tagDebugStackFrameDescriptor tmpDebugStackFrameDescriptor;
                enumDebugStackFrames.RemoteNext(1,
                                                out tmpDebugStackFrameDescriptor,
                                                out stackFrameCount);
                stackDepth++;
            }
        }

        // The DebugDocumentText lets us get at the document text
        IDebugDocumentText ddt = debugDocument as IDebugDocumentText;
        if (ddt == null)
        {
            return;
        }

        // Find out the location of the chunk of code is currently
        // being stepped through
        uint charPosition = 0;
        uint nrChars = 0;
        ddt.GetPositionOfContext(debugDocumentContext,
                                 out charPosition,
                                 out nrChars);

        // Find out what line it is on
        uint lineNr = 0;
        uint offsetInLine;
        ddt.GetLineOfPosition(charPosition, out lineNr, out offsetInLine);

        // Get the chunk of code that is being stepped through.
        if (nrChars >= 0)
        {
            if(nrChars > codeBuffer.Length - 1) {
                nrChars = (uint)codeBuffer.Length - 1;
            }

            uint charsReturned = 0;
            ddt.GetText(charPosition, codeBufferPtr, IntPtr.Zero,
                        ref charsReturned,
                        nrChars);
            code = new String(codeBuffer, 0, (int)charsReturned);
        }

        // Tell any listeners about the new trace message
        TraceEventArgs traceEventArgs = new TraceEventArgs(fileName,
                                                           functionName,
                                                           stackDepth,
                                                           (int)lineNr,
                                                           code);
        OnTraceStep(traceEventArgs);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex);
    }
    finally
    {
        try
        {
            // Resume the process, indicating we want to break at the next available point
            debuggableApplication.RemoteDebugApplication.ResumeFromBreakPoint(prpt,
                  tagBREAKRESUME_ACTION.BREAKRESUMEACTION_STEP_INTO,
                  tagERRORRESUMEACTION.ERRORRESUMEACTION_AbortCallAndReturnErrorToCaller);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Error resuming: " + ex);
        }
    }
}

Of course this has an impact on performance, but it does let you see every line of code that has run. It can be quite illuminating to trace through the Microsoft ASP.NET AJAX runtime and see exactly how much work it does on your behalf. You can also use it to see if your JavaScript code is doing what you think it is doing, without having to step through it in the debugger.

For example this is the trace from the first update panel example in the ASP.NET AJAX documentation.

I put a simple graphical interface on top of the program:

TraceJS ScreenShot

You can download the program here, and the complete source is available here.

I can imagine that this could be extended to include filtering, displaying parameters, code coverage and possibly performance metrics but as it stands I think it's already quite useful to see every line of code that has run without having to manually step through the debugger. I'm happy to contribute it to CodePlex if anyone wants to take it further.

About the author.

Filed under: AJAX Leave a comment
Comments (11) Trackbacks (2)
  1. Hi Damian,

    Do you have any idea why your tool is not working with Windows XP x64? .NET Framework is installed, Script Debugger is installed and works.
    The error message is ‘Error enumerating applications using the MachineDebugger’.

    Thanks,

    Frank.

  2. Hello Frank,

    No idea off of the top of my head I’m afraid, and I don’t have a x64 box here to try it on.

    Do you have script debugging enabled in IE? Are you able to start the script debugger manually?

    Regards,
    Damian

  3. Hi Damian,

    Scipt debugging is enabled and yes, I am able to start the debugger manually. Thanks anyway..

    Frank

  4. Hi Damian,

    Great work! it is working on my PC. I would like to try to take it somewhat further for testing (coverage) purposes.
    What were exactly the required changes in the ProcessDebugManager.dll. and why was it necessary?

    I tried to replace the that part using the Microsoft.VisualStudio.Debugger.Interop.dll. I found in the the VS2005 SDK. But it looks like I need something more to instantiate a ‘MachineDebugManager’. Propably you could give me some hints? I am quit new in this kind of stuff.

    Johan.

  5. Hey Damian:

    Great idea with this tool. Do you know of any problems running it on Vista or with IE7? The call to EnumApplications in InitializeProcessList is always returning null. I installed the MSFT Script Debugger, and made sure to configure IE to allow script debugging, but nothing changed.

    Devin

  6. Nice article.. Damian. I worked with ur tool in Windows Xp…. no problem working good…

  7. Hi Damian,

    First of all thanks a ton for developing such a good tool.

    I have tried executing the TraceJS and it was not giving any instance of internet explorer, then I have downloaded the source and tried executing the debug enabled TraceJS and in the detail of the exception, I am getting the following, can you help me with this ?

    =======================================================

    See the end of this message for details on invoking
    just-in-time (JIT) debugging instead of this dialog box.

    ************** Exception Text **************
    System.Runtime.InteropServices.COMException (0×80004005): Error HRESULT E_FAIL has been returned from a call to a COM component.
    at ProcessDebugManagerLib.IEnumRemoteDebugApplications.RemoteNext(UInt32 celt, IRemoteDebugApplication& ppda, UInt32& pceltFetched)
    at TraceJS.Form1.InitializeProcessList() in C:\Users\Damian\Documents\Visual Studio 2005\Projects\TraceJS\TraceJS\Form1.cs:line 95
    at TraceJS.Form1.Form1_Load(Object sender, EventArgs e) in C:\Users\Damian\Documents\Visual Studio 2005\Projects\TraceJS\TraceJS\Form1.cs:line 31
    at System.Windows.Forms.Form.OnLoad(EventArgs e)
    at System.Windows.Forms.Form.OnCreateControl()
    at System.Windows.Forms.Control.CreateControl(Boolean fIgnoreVisible)
    at System.Windows.Forms.Control.CreateControl()
    at System.Windows.Forms.Control.WmShowWindow(Message& m)
    at System.Windows.Forms.Control.WndProc(Message& m)
    at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
    at System.Windows.Forms.ContainerControl.WndProc(Message& m)
    at System.Windows.Forms.Form.WmShowWindow(Message& m)
    at System.Windows.Forms.Form.WndProc(Message& m)
    at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
    at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
    at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)

    ************** Loaded Assemblies **************
    mscorlib
    Assembly Version: 2.0.0.0
    Win32 Version: 2.0.50727.832 (QFE.050727-8300)
    CodeBase: file:///C:/WINDOWS/Microsoft.NET/Framework/v2.0.50727/mscorlib.dll
    —————————————-
    TraceJS
    Assembly Version: 1.0.0.0
    Win32 Version: 1.0.0.0
    CodeBase: file:///C:/sanjay/WPC/JavaScriptTools/TraceJS_Source/TraceJS/TraceJS/bin/Debug/TraceJS.exe
    —————————————-
    System.Windows.Forms
    Assembly Version: 2.0.0.0
    Win32 Version: 2.0.50727.832 (QFE.050727-8300)
    CodeBase: file:///C:/WINDOWS/assembly/GAC_MSIL/System.Windows.Forms/2.0.0.0__b77a5c561934e089/System.Windows.Forms.dll
    —————————————-
    System
    Assembly Version: 2.0.0.0
    Win32 Version: 2.0.50727.832 (QFE.050727-8300)
    CodeBase: file:///C:/WINDOWS/assembly/GAC_MSIL/System/2.0.0.0__b77a5c561934e089/System.dll
    —————————————-
    System.Drawing
    Assembly Version: 2.0.0.0
    Win32 Version: 2.0.50727.832 (QFE.050727-8300)
    CodeBase: file:///C:/WINDOWS/assembly/GAC_MSIL/System.Drawing/2.0.0.0__b03f5f7f11d50a3a/System.Drawing.dll
    —————————————-
    ProcessDebugManagerLib
    Assembly Version: 1.0.0.0
    Win32 Version:
    CodeBase: file:///C:/sanjay/WPC/JavaScriptTools/TraceJS_Source/TraceJS/TraceJS/bin/Debug/ProcessDebugManagerLib.DLL
    —————————————-

    ************** JIT Debugging **************
    To enable just-in-time (JIT) debugging, the .config file for this
    application or computer (machine.config) must have the
    jitDebugging value set in the system.windows.forms section.
    The application must also be compiled with debugging
    enabled.

    For example:

    When JIT debugging is enabled, any unhandled exception
    will be sent to the JIT debugger registered on the computer
    rather than be handled by this dialog box.

    =======================================================

    Regards,
    Sanjay

  8. I am receiving the following error: “System.UnauthorizedAccessException: Retrieving the COM class factory for component with CLSID {0C0A3666-30C9-11D0-8F20-00805F2CD064} failed due to the following error: 80070005. at TraceJS.Form1.InitializeMachineDebugManager() in C:\TraceJS\TraceJS\Form1.cs:line 84″

    CLSID {0C0A3666-30C9-11D0-8F20-00805F2CD064} is the Machine Debug Manager. Using Component Services, I have tried to make security wide open, but to no avail. Do you have any suggestions? Thank you!

  9. Hi Steve,

    What OS are you running under? If Vista, could you try running as an administrator?

    Thanks,
    Damian

  10. Hi Damian,

    I am running XP Pro. I did try running as an admin (i.e. right click on tracejs, select run as, log on as admin), but get the same result. Thoughts?

    Thank You!
    Steve

  11. Hey!

    Sounds like a cool program and perfect for what I’m working on … trying to debug JS in IE can be a real bugger!

    Sadly the download link is gone … is it possible to get a copy of this program somewhere?


Leave a comment