Investigating this issue a bit more I came to some conclusions. I've also extended the test tool to measure/display both interframe time (grey) and intraframe time (red) and added options for different synchronizing methods. The conclusions first, the observational evidence after.
In order to avoid going below 60fps where all the microstepping and degradation dragons live, it's required to know when that would happen.
I have proposed to solve that problem with an API, but the resonance from UA vendors is neglible, and so I have to assume it's going to take considerable time (if ever) when that issue gets addressed (wtg here WebGL, competing with native platforms, naaat)
In lieu of a solution emerging, it's up to JS app developers to get around that problem somehow
The "getting around it" part is degrading performance and force a synchronize start and end of a frame, so as to get a measure how close we are to stepping below 60fps.
Unfortunately, forcing a synchronize (proverbially gl.finish) doesn't work in every user-agent, and so we'll have to find methods to do that which UAs haven't borked up.
If you are on google chrome or IE, insert a gl.readPixels at the start and end of a frame. Don't use gl.finish, it doesn't do anything.
If you are on firefox or safari, never use gl.readPixels, it seriously degrades everything. However gl.finish works as intended.
Even if we find a solution to consistently across browsers synchronize, it's going to cost us considerable performance. However if given a choice to ridiculously undershoot hardware capabilities (by a factor of say 10000%) in order not to fall below 60fps, or sacrifice ~30% of performance to get a smooth/jitter-free application, or live with ridiculously low framerates and lots of jittering for a sizable percentage of our users, it's not really that much of a contest.
Chrome on windows
Here is where we want to be (ideally). This is a rising JS load test (no webgl) in chrome which nicely shows how well behaved this can be (and what we want to get out of UAs).
Of course if we toggle to a WebGL load test without any synchronization it looks like this, no indication whatsoever when we're going to drive into a wall.
Unfortunately, the same is true in chrome when calling gl.finish as well:
I have tried various other methods (flush, texImage2D, bufferData) and they don't have any appreciatable effect. The only two that do have an effect is readPixels and getError. Get error is not very helpful however.
So the last command to synchronize things that can work is readPixels, and low and behold, it does work as intended. However this costs us roughly 30% of performance.
Internet Explorer
Internet explorer doesn't implement even frame division stepping. So at a rising load and no finish no attempt at FPS stepping is evident (other than the 60fps clamping).
And like in Chrome, gl.finish doesn't seem to do a whole lot (though it does a little more than in Chrome).
Also like in Chrome, gl.readPixels is about the only solution left:
Firefox on windows
Firefox also doesn't implement even frame division stepping. And like IE, after the 60fps clamping, interframe times just keep rising. Unlike chrome, firefox seems to spend a considerable amount of time in JS for a few simple loops.
Here however readPixels seriously mucks things up.
Luckily, gl.finish is much better behaved:
I have run these tests on Linux so far also comparing UAs, and have arrived at similar results. It's possible this is different on OSX, and it might also depend on the GPU.