2008-02-24

Cooperating Greasemonkey scripts

I should apologize in advance that most of the scripts I link here use a not-yet-released @require feature of the svn development version of Greasemonkey (the equivalent of #include in C/C++, so if you exert yourself, you can paste together the scripts by hand by inlining the code at the top of the script). Pardon the nuisance; at least it won't ruin this post for you.

I have taken another stab at stigmergy computing, or writing scripts that communicate by changing common environment, here the web page they execute in.

Anyway, last time, I wanted a decent portable web photo album browser; functionality that would follow me around irregardless of where I roam on the web. Something to flip through images with the arrow keys, perhaps with some additional handy features too.

The album browser would consume the very basic microformat <a href="*.image extension">, and turn it into a gallery. This meant that I could use my familiar album browser mostly everywhere without any work at all. It also meant that, when encountering some photo site working differently, I could adapt not my album to the site, but the site to my album. A minimal script that changes a single site only very slightly, is trivial -- adapting old code rarely is. So this little ecology convened around a minimal ad-hoc image album microformat.

This time I have several microformat consumers convening around a microformat I came up with for destructuring pagination into what is popularly (if somewhat incorrectly) called endless scrolling; a page that, when you approach the end of it, loads another pageful or so by means of XMLHttpRequest. Usually an abomination, when some site designer forces that browsing mode for you, but ever so handy at times, if you only got to choose for yourself.

Anyway, I started off basing it on the old <link rel="next"> tag, which is actually standardized, hoping that I would, at least once in a while, not need to produce this bit of the microformat myself, but could ride on data in the web page itself. So far, that has not saved me any work but cluttered the microformat into picking next links in two orthogonal ways, depending on what is available, so I'm inclined to write that off as a bad, yet well-intended, idea (see my first reply below for the messy details of how standards bloat like this typically come about).

I needed three data items: an url to the next page in the sequence, an XPath expression that matches the items on every page, and an (optional) XPath expression that identifies a pagination pane to update with the last page loaded so far. I came up with three meta tags, named "next-xpath", "items-xpath" and "pagination-container". If the web page gave me those, my pagination script could deal only with undoing pagination, and care less about whether the data it actually handles is Flickr photos, vBullentin forum posts, Google or Yahoo search results, or something else entirely.

After doing some crazy limbo (which deserves a post of its own) loading the next page into a hidden iframe (both for letting Greasemonkey scripts producing the microformat run, and to get a correct DOM for the page to run the XPaths -- as it turns out that, amazing as it may sound, browsers can't turn an html string into a DOM without ruining the html, head and body tags; at least not Firefox 2 and Opera 9), the remainder of the script was a walk in the park.

Having written up a few microformat producers for various sites to exercise the microformat and see if it addresses the problems that show up well enough, found and straightened out some minor quirks, I felt the urge to do more with this smallish, but oh, so useful, microformat, telling apart content from non-content in web pages, so I decided to hook up my custom keybindings script with this hack, making it assign "p" and "m" to scrolling up and down among the items of these pages. That came down to a whopping ten new lines of code, to make a dozen, or, factoring in future microformat producers, an unbounded number of sites scrollable in that way.

And -- best saved for last -- both microformat consumer scripts coexist happily, independently and work orthogonally; the unpaginated whatevers that get added to the end of the page are browsable with the new keyboard bindings, so you can work mostly any web page as comfortably as ever your feed, mail or whatnot reader, right there.

If you craft any microformat producer or microformat consumer user scripts, please tag them as such on userscripts.org, and also tag them with the name or names of the microformat(s) they handle too (I picked, somewhat arbitrarily, photo album microformat and pagination microformat for mine), so they form findable microformat ecologies for other users. Any site your microformat gets a producer for, improves the value of all the microformat consumers of that format, and any microformat consumer using the format in some useful way, improves the value of all the producers.

It is very rewarding doing stigmergy / ecology programming; the sense of organics and growth is very palpable. There are some sharp corners to overcome, and Greasemonkey could help out more, but I'll have to get back to that at some later time.

2008-02-23

High cpu load web page diagnosis

For web development, a laptop with a case fan that starts screaming like a jet engine when your cpu load skyrockets is sometimes a Really Good Thing, since it gives you cues to what happens in your browser, whether you set out do do profiling or not when, bad enough things happen. (A bit like how you could debug your programs on the C64 from the overtones that would leak from cpu to audio circuitry, giving distinct audio signatures to the occasional choke point in your code.)

Anyway, a Swedish social site I occasionally frequent used to have this effect for me, and get worse the more tabs I had open to it (and it was bad already with a modest three). I used to think it was a bugged Flash app (which they use for ads), but when I realized I had killed them all with AdBlock and FlashBlock, it occurred to me that it might be run-amok javascript.

Only that javascript that runs amok usually gives you the annoying modal dialog asking whether you want to keep running the cpu hogging script. That, however, only happens when page javascript hogs the cpu from the UI, so the single UI thread gets starved, and you, the user, get annoyed with an unresponsive UI. That can be avoided by running smaller loop fragments that call one another with setTimeout calls, leaving some breathing room for the UI thread in-between.

So how do you debug if that is what is happening? Well, for one thing, at least in Firefox 2, you can call setTimeout yourself two times in a row, some second or so apart, and compare the timeout id:s you get back, because consecutive calls get consecutive id:s. So if the difference is one, no setTimeout call was made in-between.

I crafted a bookmarklet to count setTimeout() calls per second (well, I honestly started with something smaller in the Firebug console, but figured it was useful enough to save, share and reuse later), and sure enough, that site was calling setTimeout 178 times per second. (It's not very interesting to run it on this site at present, but you can always race with yourself and the UI responsivity of your browser, attack clicking it to see how many setTimeout calls you can fire off in a second by hand. I believe it is not one of the performance tests we will see in any side by side browser shootouts any time soon. My personal highscore with Firefox 2 is 3. :-)

To do what? Well, time for another bookmarklet that shows which arguments setTimeout gets called with (it won't run on Internet Explorer; sorry: kindly advise Microsoft to implement the toSource prototype for all javascript native datatypes -- I would want to say it would not be a decade too late, but that would make me a liar :-), and lets you restore the original setTimeout implementation when you are done peeking.

It turned out the site was trying to fetch a node with an advertisement I had AdBlocked away. Over and over again. A tiny Greasemonkey script later, and the laptop fan was happily put to sleep, once again. A toast to audial debugging!

2008-02-21

Reducing user scripts to atoms

Over the past few weeks, I have found myself to prod ever more frequently at two user scripts; a UI reducing one and a UI extending one. Both @include the entire web, but are tailored to sites individually on a site by site basis, as I realize that I miss either feature somewhere, and add it, for my own comfort -- some cruft I rarely want to see here, some action I want bound to a key there.

It was only a question of time, and I just hit that tell-tale moment where these two growing (yet, due to factoring it down into convention based descriptive more than programmatic notation, still fairly maintainable) beasts want to marry one another and finally gain independent sentient thought and a soul, uhm, augment features of one another to become greater than the sum of their respective parts.

I'll be back to that in a moment, after summarizing how they came to be and what they do. Each started on the premise of recognizing an improvement I would like to see for web sites mostly everywhere, that would entail approximately the same code for any site I wanted to apply it on, but with minute changes to parameters.

In the first case it was the all-too-popular tendency on the web (if, however, probably mostly also equally unintentional) -- of giving gratuitous focus to visitor voices, at the expense of my taking in the author voice -- that is less popular with me. A few swift DOM cuts, and the excess commentary is folded away.

In the second case, it was recognizing what a great improvement in usability changing web pages to offer comfortable keyboard commands, and more so, comfortable standardized keyboard commands, that I could pick myself, but would work on lots of web sites that I use in a similar fashion. (More on that after this hyperbole. :-)

A few years ago, I was wary of web comics, mostly because I read them seldom and would never remember where I trailed off last time, so I could continue from there, yet I neither wanted to reread lots of them to find my bookmark (and browser bookmarks were too much labour overhead), or miss a strip. One early hack was a bookmarklet that would set a cookie to the present url, or, if invoked from the frontpage, go to the url in that cookie. Instant recollection of last location, and a single bookmarklet for any web site. Excellent.

Eventually, I found and had a love affair with Greasemonkey, and it seems in 2006, or perhaps some year earlier, that bookmarklet, too, was too much to keep track of, and I crafted an automated user script to keep track of last-read for me. The main net benefit with crafting hacks like that is often finding the additional features that are so easy to add, yet prove much more valuable than hard to implement. In this case, it was realizing that this little repository keeping track of all comics I read could link them to a little comics ring that would follow me around, always linking to the next in the ring, and specifically the last one I had read, ready to continue traversing. Better still, that way I wouldn't forget those least frequently updated or read comics, as I often would otherwise. Handy!

And when I started assigning keyboard shortcuts to web sites, adding them for comics was another eye opener -- arrow left and right (or j/k) to head backwards and forwards in the archive, "n" for next site in the ring. Instant gratification, all over again. After I fell in love with Questionable Content, I extended that to also include hotkeys to scroll up/down a panel, p/m (for giving that comfy lazy restful right hand resting on m/k/p, to zoom through its bountiful archive in no time flat), and next thing I knew, my spine expected those handy scrolling keys to work on interesting forums too.

If Jon Udell has a thing for overcoming data friction, I guess one of my bigger things is overcoming user interface friction. With the tiny convention based system I crafted, adding a keyboard binding somewhere was just pasting a hostname, deciding on a regexp for what on-site pages to augment, deciding on keys and crafting a quick xpath expression for picking up which link or page entity to click on with that button, or one to slice out which nodes to snap to for the scroll actions. Done in essentially no time at all, for a geek such as myself. Complete comfort, at essentially no time or work investment on my part.

And then it dawned on me. I want that handy scroll-to-the-next-something on all those sites where I taught that first script how to fold comments. And, as I had stylishly also taught that script how to count comments for each site, it already knew how to find each comment node, so with a quick marriage of these scripts, lots of sites would gain that feature in the blink of an eye.

Except I don't really want to do that. It's conceptually wrong; it makes every script that could use a detail of another clump up into big chunks of things to satisfy the exact preferences of me at the expense of other users that only want, say, the hotkey feature, or perhaps only want it for a single site they care about. It would be much, much better if these scripts could cooperate like in a symbiotic biological environment, each script specializing in one thing and doing that well.

It would reduce each script to a small axiom; one script handling the core aspects of registering keyboard listeners that do not interfere with form input, while assigning key bindings to specific page elements or page actions common throughout the web, from a recipe entailing which keys to bind to what features of pages on a specific site. One script handling the logic of folding entities of some sort, given recipes for the same thing. Lots of little dumb work drone scripts that handle recognizing and marking up page features for other scripts -- comments on this page are here, a link to the next, previous and index pages of this site are here. For the latter, there is even a meta tag microformat which good browsers like Opera handle natively, so these scripts, when possible, should decorate the page with that microformat so other entities which consume the microformat get it for free. And so on; one script to connect microformat with keyboard shortcuts, one to connect comments with folding functionality.

That way, we would get specialization, and we would get decentralization, as anybody on the web can easily, independently, craft a new recognizer targeting sites they use, and I wouldn't even hear about it; they would post their scripts to userscripts.org, and all the Japanese who frequent that site would pick it up, and we basically have a lovely SOA system right there. Each part can evolve and iterate independently, at landslide speeds, and people would find these scripts by looking for scripts catering their sites, not through looking for keyboard shortcut scripts, which no doubt very few do.

I think this could use some catering in Greasemonkey, though, to tie the components together and help users tie together producer (those tiny custom drones) and consumer (the feature implementing agents) with one another, maybe even associate them with one another visually in the Greasemonkey script manager. Signals in the user script header like @produces and @consumes tags, of some sort, seem like one good way of doing it.

I am not sure of exactly which all this plumbing should do, nor how, but I think that there is a lot of good that would come out of it, just like how all the above evolution of three user scripts has changed my web experience to a very luxorous one, compared by the counterpart of only last year. I had no idea how much I would lavish in neither using the mouse nor arrow keys nor page up/down to do my web navigation, but factor in the amount of time you spend with those tools, and it should be quite apparent to anyone.

Your thoughts and ideas are welcome, especially if you blog them after noodling on it for a while; I think we deal in largish ideas that fit the comments field below less than well. Especially as my own site, too, so far, still suffers the "comments dwarf original posts" syndrome when subjected to great visitor attention.