JavaScript Queues

One of the most annoying parts of VandalSniper from a maintenance perspective was how the "JavaScript Queue" was run. To perform some complex action such as rolling back an article and posting a warning to the talk page of the editor required a sequence of JavaScript snippets to be run, one each time the browser finished loading a page. But the way this was implemented was absolutely horrendous. (But it was my first C# project, so I'm okay with that.)

When the queue was started, the entire window was set insensitive to prevent the user from messing with the fragile sequence of events that would take place. Two variables local to the main window class were set: a Queue containing the snippets to run, and a WebBrowser on which to execute these snippets. Once the queue was empty, or the script reported an error, these variables would be set to null and the main window made sensitive again.

Ick. Aside from being horribly annoying to the user (I mean we've got multiple browser tabs here... why shouldn't they be able to use another?) it was just plain hackish.

I just finished implementing this same idea in WikiBench and I think I've done a lot better this time. I already have a BrowserTabState class that represents the state of one tab. Instances of this class are passed around as sort of identifiers, as well as being handles into a tab's state. I added a boolean Locked property to this class; when set to true, all of the browser's navigation controls are disabled, excluding the stop button. This includes the browser, the address entry, and the close tab button in the tab label. Other tabs remain functional. Oh, and the stop button that remains enabled will, when clicked, raise an event on the BrowserTabState instead of stopping navigation. This can be handled to abort whatever operation is in progress and restore control to the user.

The other part of this puzzle is the new JavaScriptQueue class. It just needs to be instantiated, given a BrowserTabState and an array of strings to work with, and it will manage all of the events.

Now this wouldn't be complete without a little bit of an ugly hack. Part of the reason the entire main window was disabled in VandalSniper was because of the pads below the browser; if an item in the Recent Changes pad was clicked, for example, the browser would navigate away from the page that the JavaScript snippets expected it to be on. In WikiBench this could happen either through BrowserTabState.LoadUrl() or BrowserTabState.InvokeJavaScript(). The solution? These methods simply don't work when BrowserTabState.Locked is true. I added two more methods: BrowserTabState.LoadUrlThroughLock() and BrowserTabState.InvokeJavaScriptThroughLock(). Things that expect to be working while a tab is locked (such as JavaScriptQueue) use these methods; things that don't, like the recent changes pad, use the regular methods. A little bit screwy, but not a terrible solution.

I've added the "Report to AIV" user menu item both as a proof of concept for this system, and... well, because it was in VandalSniper, and that's the feature set I'm copying.