JavaScript Debugger in JavaScript for Mozilla
John BandhauerOverview
The existing JavaScript Debugger for Communicator has had over 50,000 downloads since December 1997 (not counting approx 5 times that many downloads bundled with VJS). It is implemented as a Java applet and makes no provisions for end user extensibility and customization. We have not distributed source code for this old debugger. We have no specific plans to update it. It is considerably less than perfect in various ways.
This proposal suggests making the underlying native JavaScript debugging api (already built into Navigator) securely accessible to user JavaScript code. We will produce a graphical JavaScript debugger for Navigator 5.0 which is written entirely in JavaScript and dynamic html. It will not require the use of Java. We will release the html and JavaScript source to this new debugger to the JavaScript programmer community. Perhaps we will even enlist that community in writing some of the code for the debugger itself. The debugger will be completely -- and dynamically -- extensible. The debugger will be able to debug instances of itself. It will be possible for users (i.e. JavaScript programmers) to modify and reload the debugger while using it. We will fully document the api used by the debugger so that anyone could write their own debugger if they don't like ours.
This new debugger will be a showcase of the power of html and JavaScript in Navigator. It will be an essential tool for the growing community of JavaScript programmers.
Goals
- Provide a tool which will best support the needs of the JavaScript programming community.
- Fully expose the debugger api in a powerful but safe way into the language most useful to the debugger's users -- JavaScript!
- Build and distribute a fully extensible self debugging debugger.
- Shift as much as possible of the activity and responsibility of evolving and maintaining a world class debugger to the community who uses it.
- Promote and display the power of html and JavaScript in our client software.
Features
- 100% html and JavaScript.
- 100% free and open.
- Full support for just-in-time debugging.
- Capable of exploring properties of all objects associated with all windows.
- Support arbitrary inspection/evaluation in any stack frame.
- Breakpoints and data watchpoints.
- Capable of intercepting runtime errors and showing their origins
- Fully event driven like any other html/JavaScript document.
- Capable of debugging instances of itself.
- Fully extensible and reloadable while in use.
- All scarey multi-threaded plumbing hidden under the covers.
Architecture of builtin support
The critical change to Navigator needed to make this all possible is Mike McCool's multi mocha thread code. This is a change that is already required for OPS and for the new Security dialogs. Currently there are two threads of interest in Navigator: the mozilla thread and the mocha thread. The mozilla thread is the Navigator window UI thread. The mocha thread is the thread on which all windows' JavaScript code runs. Mike's change allows Navigator windows created by JavaScript to optionally be created with their own mocha thread. The purpose of this change is to allow JavaScript code in one window to open another window containing html and JavaScript while the original window's mocha thread is blocked (for instance, to ask the user a question in the secondary window while waiting in the JS code in the first window for the user's answer before continuing).
This change will allow JavaScript execution to be blocked for debugging while the user interacts with the debugger window (which runs its JavaScript code on its own thread).
The bulk of the new code to enable the debugger will be in libmocha. The js and jsd modules already fully support the debug api (exposed to native code and reflected into Java). However, some additional work is already being done to enhance that debug api. These enhancements will help for this use of the api.
A file called lm_debug.c will be added to libmocha. It will reflect the debug api into JavaScript via a natively implemented JavaScript object called 'jsdapi'. jsdapi will mostly be a wrapper around the existing js/jsd/jsdebug.h. I have already built a single threaded console JavaScript debugger written in JavaScript using this same approach and can leverage the reflection layer built there.
In addition to reflecting the debug api, jsdapi will provide special debugger startup code and thread gateway code to hide that level of plumbing from the debugger JavaScript code.
Navigator startup sequence
- libmocha initializes
- calls js/jsd to initialize
- calls lm_debug to initialize
- if debugger.html is in specific place on disk
- put js/jsd in an 'active' state - register callback for debugger keyword and js errors so that if the events occur, then lm_debug will be called and will start a debugger window (loading the afore mentioned debugger.html). js/jsd will track all html and js files loaded in Navigator (being careful to discard copies of pages abandoned by Navigator) to allow for just-in-time access to the source for the debugger.
- else
- js/jsd not activated and debugging is not available (except via the legacy Java applet).
- if debugger.html is in specific place on disk
Debugger startup sequence
The debugger can be invoked by either hitting a JavaScript error or
the 'debugger' keyword (which can either be on a page or by
typing javascript:debugger
in the location bar or via bookmark)
- the hook that lm_debug set at startup is called. It creates a new window
(with a separate JS thread) and initializes an instance of a nativly
implemented JavaScript object called 'jsdapi' which is attached to the new
window. This jsdapi object can only be created in this fashion and is not
accessible from non-debugger windows. The debugger html document is
loaded into the window.
- the debugger.html file then sets up its UI and sets hooks into jsdapi. It notifies jsdapi that it is done loading.
- at this point a hook event is posted to the debugger and debugging begins.
Hook handling sequence
- hook is encountered on the target mocha thread (breakpoint, interrupt, error, or debugger keyword) and calls through jsd to hook handler in lm_debug. lm_debug maps this to the function in the debugger which needs to be called to handle the hook. That handler, however, needs to be called on a different thread in order to allow the user to interact with the debugger.
- lm_debug posts a message to the FE using the same mechanism used for events like timeouts. The target mocha thread then goes into an event wait loop ready to be told by the debugger to continue execution, or do an expression evaluation, etc.
- the FE retrieves the hook event on the mozilla thread and routes it to the debugger in the same fashion a timeout or mouse event is routed.
- the debugger responds to the hook by gathering data about the target thread state (ie. stack, source code, etc.) by calling methods of jsdapi. It then can display information to the user in nice fancy interactive windows using the full range of layers, tables, frames, and JavaScript.
- Some calls that the debugger makes into jsdapi can be handled directly by
calling functions in js/jsd/jsdebug.h (e.g. gather stack information). Other
calls will require thread handoffs (e.g. evaluating an expression in the
context of a stack frame in the stopped target thread). jsdapi will hide this
complexity:
- the debugger will call a jsdapi method.
- that method will post an event into the queue in lm_debug where the target thread has been parked since the hook happened. The debugger's mocha thread will then enter its own event queue awaiting the response from the target mocha thread.
- the target mocha thread will call into jsdebug.h to do the eval. When the eval returns the target mocha thread will post it back to the debugger's queue and go back to waiting for another event.
- the debugger mocha thread will receive the answer and return it to the debugger. The debugger will go about its business without knowing that this dance happened.
While this all sounds very complicated (and there are complications not mentioned here), I've already had to implement this same stuff to make the ssjs debugger work over a Corba connection. It is all very doable.
Architecture of the Debugger UI
There is a lot of flexibility in determining the architecture of the debugger UI. Upgrading the UI will not require changes to Navigator itself. Part of the point of the whole project is to have an easily upgradable -- even user upgradable -- UI.
I think that it will be possible to make at least as rich and smooth a UI using dhtml as that of the current Java-based debugger. I consider the experience gained by the debugger team in doing this much sophisticated html and JavaScript to be one of the big benefits of the project. By becoming real-world users of the technology the debugger is designed to support we assure that the design of the tool and decisions about low-level engine support fit the needs of its users.
The debugger will be fully event driven. While it will be primarily graphical, it will have a console to allow manual inspection of the target code and arbitrary access to the methods of the objects which comprise the debugger itself. I expect that early development iterations of the debugger UI will be primarily console driven.
There is a lot of good dhtml code available to reuse. Our visual dhtml tool is full of treasures. My inclination is to port much of the logic used in the Java version of the debugger into the dhtml world. This is made significantly simpler by the code in jsapi which hides the cross-thread communication and synchronization between code running on the debugger UI/mocha threads and the target mocha thread.
I am hoping that by doing frequent and open updates of the UI source (and by integrating contributions of code) we can get a small community of JavaScript programmers actively contributing to the tool and supporting each other in extending it. I would very much like to see it take on a life of its own.
How to get there from here
New and modified native code
- new file: lm_debug.c in libmocha will reflect jsd, start debugger, and route events.
- js/jsd will need to support JSContext filtering for hooks; i.e. each hook will (optionally) have a list of JSContexts (essentially windows) for which it would like to be notified about debug events. For instance, a given debugger might want to be notified of JavaScript errors in all windows except its own, while a second debugger might only want to catch errors in the other debugger.
- The debugger html and JavaScript code needs to be evolved into existence.
Dependencies and Unknowns
- The code to for multi mocha threads it required. mlm has this working and it is scheduled to be part of Nav 5.0.
- It is critical that no cases exist where code can be stopped in a target mocha thread while layout is locked and unable to process the UI for the debugger. Any problems that may exist in this area are bugs and will need fixing for any client of the multi thread code (though the debugger will be 'testing' this a lot and may be the best 'finder' of potential bugs of this sort).
- We'll have to be very careful with potential new security holes. The main protection is that none of the new code will run unless the user has installed the debugger.html file on his disk. Still, we need to exercise due diligence to assure that pages from the web can not exploit anything here.
- Navigator 4.x really sucks up memory in code that does a lot of document.writes to layers. This is said to be caused by layout's use of arenas - memory is not reclaimed until the Window closes. This is bad for a rich dhtml page. This is likely to be fixed in Navigator 5.
- Time is tight. Approval and support are critical.
Engineering Sequence (rough outline - details and schedule TBD)
- libmocha work to implement app and debugger startup code
- write code for jsdapi object
- write a simple console to debug code in simulated 'other' window
- write some proof of concept dhtml parts - e.g. simple inspector
- Add extensions to JSD for multi-context debugging, fast inspection,...
- integration of multi mocha thread code - work with real tragets
- Improve UI and get it all working smoothly
Addendum 1 - Status Update - 25 Aug. 1998
Despite the utter coolness of this project, I've decided that now is not the right time to pursue it. Rewriting so much UI and working around layers issues does not appear to be the best use of time. Nor is it likely to produce a tool which will most benefit the greatest number of customers. Instead, I'm concentrating on improving the existing Java-based UI; smoothing out the user experience and adding a significantly more powerful data browser. Incorporating Rhino into the debugger to facilitate scripting of the debugger's behavior is a strong possibility.
I still would like to do the JSD-in-JS debugger proposed here. Possibly the Raptor based client will be the right platform to support (and be supported by) this debugger. This will certainly be revisited...