Wednesday, August 19, 2009

ok so I'm a moron

Unexplained programming terminology ahead, so beware.

Regarding the recent Greasemonkey script I wrote, I realized that event hooks placed by a Greasemonkey script can call functions defined in a Greasemonkey script if you hook the event correctly (which I was doing) and give it a reference to the function rather than the typical string containing the function call (I was also giving it the proper reference, hence the title of this post). It's not so much a Greasemonkey security thing as it is a Greasemonkey functionality thing.

When a page loads, Greasemonkey checks to see if you've got a script that should run on the page. If you do, it creates its sandboxed environment, loads the script, allows it to run, and then erases the sandboxed environment. It's sandboxing the target page to prevent it from accessing all our delicious privileged code, so we can call its API functions and the target page can't even tell that Greasemonkey exists.

When you give the typical string, that works so long as the function still exists. With Greasemonkey, the function doesn't exist for very long. Giving it a reference to the function, however, causes the Javascript engine to say "hey something's still got a reference to this function, I'll keep it around" and then it works as intended.

What all of that means is that I don't need to inject a script tag into the head of every single web page I view. It runs entirely client-side now, with the exception of the CSS which is still remote loaded. I could probably insert the CSS client side too, but the code would look even more messy. Overall it's just easier to stick an extra stylesheet link in the head of the document than to dick around embedding the CSS in a string or doing it via the appropriate Javascript methods.

Also, after figuring this out, I went ahead and implemented the 500 millisecond (half a second) delay before showing the target. Doing this pretty much requires a queue. Luckily I found a public domain Javascript queue object so I didn't have to write my own. With a queue it's really simple. As you mouse over a ton of links without leaving your mouse on one of them, mouseover events are triggered in the order you mouse over them. My mouseover event hook is now three lines that start the timer that calls my actual "show the link target" function, instantiate an object I created to encapsulate the object references and timer id I need later on, and load that object into the queue.

The beauty of this is that the mouseout events that are triggered when your mouse leaves the area of each link are triggered in the same order, so all I care about is the least recently triggered event in the last 500ms. Which is exactly what a queue provides me with. My function that shows the link target simply reads the info of the event I care about off of the queue without popping it, and then mouseout takes care of popping it and cancelling the timer. I found no clean way to tell if the timer id was still valid, but it doesn't seem to matter as clearTimeout doesn't appear to bitch at me if given an expired timer id.

Unfortunately, I'll still have to allow a site to run Javascript to see link targets, but it doesn't flicker like crazy when I mouse over a bunch of links quickly anymore. The only remaining problem has to do with AJAX-inserted links, which my script tries to take into account but doesn't fully take care of.

My test case for that is's shoutbox. The contents of it are inserted after page load, which my event hook for adding a new DOM node picks up on just fine and adds the mouseover and mouseout event hooks to all the links. But the shoutbox code insists on updating a string telling me how long ago the comment was made. I can clearly tell when each comment was made because it has a timestamp right fucking next to it. It does this stupid update every minute for every comment in the shoutbox. When it does that my event hooks on all the links in the shoutbox disappear. Event hooks elsewhere are fine. I tried adding them back in by hooking the generic DOM modified event but it didn't work. I don't really know what can be done to fix this, nor do I really feel like going through the effort for a script that's supposedly a temporary workaround.

Partial scripting victory still counts as a victory. I'm going to go to sleep now.

No comments:

Post a Comment

I moderate comments because when Blogger originally implemented a spam filter it wouldn't work without comment moderation enabled. So if your comment doesn't show up right away, that would be why.