Summary : A patchy, inconsistent picture. I would invite all browser manufacturers to take a good look at these tests. I do not wish to decry any browser as such, as I think all browsers today are pretty amazing. My rude comments below are simply concerning their performance on this point alone.
This blog entry is a follow-up to my JS Mentors thread on this subject at the JS Mentors Google Group.
If you have any comments on this blog entry, please leave them at the above link.
This entry sets out my explorations of how you can implement progress bars in browsers, and how (shockingly), Internet Explorer has IHMO the best developer experience at present in this regard. Indeed I would plead with other browser mamanufacturers to look at how IE does the following: -
- Giving access to the
window.open()
document before the JavaScript function that calledwindow.open()
has returned. At present you cannot dynamically update the DOM without callingsetTimeout
and returning. - Making windows returned by
window.open()
run in a separate "thread" like aWorker
, so that they show updates sent by a parent JavaScript process. - Implementing
showModelessDialog
as a more configurable version ofwindow.open()
.
It won't happen, but you can dream.
Overall, my experience of the browsers in this test was:-
- Internet Explorer - Hands down the best IMHO.
- Opera - Implements
Workers
andprogress
elements how I would expect. Slick. - Firefox - Not bad.
- Chrome/Safari - Total rubbish. Could not get any of the new technologies involving
Workers
andprogress
elements to work. Could be my mistake, but then, Opera did work.
Statement of the Requirement
I want JavaScript and HTML to do more; to be an application development environment.
As a result I have long-running functions, and I want to keep the user updated.
These updates may be:-
- a graphical progress bar
- a stream of log messages
In effect I want a Progress Dialog Box (or should I say, Monolog Box), with a stop button to let the user cancel an operation mid-flow.
Nothing unusual here.
Statement of the Problem
When a JavaScript function runs, the browser ceases to update the display.
All updates are stored until the JavaScript function returns; and then applied in one go.
This means that you cannot update the display, whilst the function is running.
So no progress bar.
There is a good reason for this, as updating a browser display can have side effects. But surely it is not beyond the wit of developers to create an element area that is updatable whilst a JavaScript function is running. I thought the progress
element in HTML5 was for this purposes, but not all browsers seem to agree.
My ideal solution
- A window or element which can be created and updated by a JavaScript function whilst that JavaScript function is running.
- It can be integrated simply into your existing code, with no need for complex continuation-style programming or message passing (e.g. using
setTimeout
orWorkers
).
Approaches to the problem
I discuss briefly below the approaches to the problem, and you can come to your own conclusion.
This web page contains the code to test against the browsers: Test Page
This web page lets you browse the test page source: Test Page Source
Approach 1 : showModelessDialog (SUCCESS - IE ONLY)
This is by far the best solution I have found, and pretty much meets my needs, except for the fact that it requires a child window to be spawned.
It has the following advantages:-
- The dialog window seems to run in a separate "thread", so it can be updated independently.
- The dialog window is very configurable (more than for
window.open
). - You can immediately start writing to its
document
in the same JavaScript function that opened the window (no need to wait for the function to exit).
This is great in a HTA file, runing in an unsecure browser instance.
In an normal HTML file (running in a secure browser instance), you have to remember to point the URL in showModelessDialog
to a small empty htm file (mine is called stub.htm
) in the same domain as the parent page, otherwise you will get cross-domain scriping access denied messages. "about:blank"
fails as this is seen as a different unsecure domain by IE.
Unfortunately this approach is IE only, and the clever folks benhind HTML5 saw fit to omit it from their specification.
Approach 2 : showModalDialog (FAIL - ALL BROWSERS)
showModalDialog
will eventually be supported by all browsers, but this fails entirely because the "modal" element obviously blocks things from happening.
I did try getting the modal window to itself call the Javascript function in the parent window, but the updates to the modal window (as the the caller) only happened once the parent JavaScript function had returned (logically).
The modal dialog calls back to the parent window to call the loop function. Unfortunately as the call comes from the modal dialog in the first place, it cannot update itself. A call to a window setTimeout
on the parent window will not work, because the timeout function will not be called until the dialog is closed!
So this is a complete NO-GO.
Approach 3 : window.open (SUCCESS - IE ONLY!)
Again this is a good solution, but, surprisingly, IE only.
It is less promising than showModelessDialog because:-
- window.open shows a new window in the task bar
- you cannot exclude the address bar
- it is slighly slower to load because it is a whole new browser instance
Why IE only?
Because in all other browsers, the child window is not run in a separate "thread". So it will not update its display until the parent window process completes.
It can of course be made to work for other browsers, if you combine it with Approach 5 below, but that really defeats the object.
In any case, it is a pain to implement for other browsers. If you carry out a window.open()
you cannot access the DOM of the loaded URL until the JavaScript function that called window.open()
itself returns. Oh yes, if you look for window.document
, immediately after calling window.open
, you will find it, but it feels like a "dummy" object, and any changes you make to its DOM will be completely ignored. So the only way to implement it is to first open the window, exit the function, and allow the child window to complete its loading, and use a setTimeout
to run the javascript function that will make use of the window. But all of this is academic, because it still will not update until the JavaScript function with the loop has returned.
Another problem is with Chrome, which seems to have a problem with locally stored files in the same folder directory (it does not treat them as the same domain). See: Issue raised on Chrome website.
Approach 4 : iframe (FAIL - ALL BROWSERS)
In this case, all browsers run the iframe in the same thread as the parent window, or at least store up the updates to the iframe until the JavaScript function has run.
Again, another problem is with Chrome, which seems to have a problem with locally stored files in the same folder directory (it does not treat them as the same domain). See: Issue raised on Chrome website.
Approach 5 : setTimeout and continuations (SUCCESS - OF A SORT - ALL BROWSERS)
This is the universally recommended approach if you search, and involves using the continuation passing style of programming.
What you do is call your loop for a "chunk" of 1 or more iterations, and at the end of that chunk, call setTimeout on a function that processes the next "chunk" of your loop, until eventually you reach the end. At the end of each "chunk" you update your progress dialog, and these updates appear in the DOM, prior to the setTimeout on the next "chunk".
The main problem with this is that you have to re-factor your code to use the continuation programming style, which does involve longer code that is more difficult to follow.
This may be successful for simple loops, but for a more complex process involving a pipeline of different operations, you have to design your code from the start to anticipate the continuation style of programming.
In some ways I quite like the continuation style, but none of my existing code fits it.
The other problem with this approach is that setTimeout
typically has a minimum time period of 4 to 15ms, which will impose a limit on loop speed. Of course as long as the user can see "progress" this may not be a major issue.
There is a solution I have been pointed to for zero setTimeout on non-IE browsers, if you need to optimise further. See David Baron's Blog. This involves using postMessage
back to the same window.
To be fair, if you are updating a progress bar, then zero timeout is probably the least of your worries, as the code involved in updating the display probably takes up a good chunk of processing time.
You will probably find in the end that Chrome is just about visible, but Opera is blinding.
Approach 6 : HTML5 Web Workers (WEIRD RESULTS - UNRELIABLE)
The basic idea is that you run your loop with a Worker, and send progress bar update messages back to the progress bar in your main page.
Sounds simple?
No good I am afraid.
- Only Opera and Firefox worked.
- Posting messages was very slow.
- Updating of the main window was patchy on Firefox.
- I did not like this solution as it generally required you to pack up your loop code in a separate js file for the worker to load.
- If you want to implement a stop button it requires you to adopt the continuation style of programming again, as you cannot pass a reference to an object in the main window, just a plain data type or JSON object. This means your worker has to give up time for the onmessage to process the stop.
Overall, not viable at the moment. I would welcome anyone else's views.
Approach 7 : HTML5 progress element
My last great hope was the Progress element.
Surely this was designed specifically for my problem?
No. At most it is a means of displaying progress, but it gives no guarantees as to how it will be updated.
In my particular test, only Opera worked as I would have hoped.
Chrome froze, and in any case does not update the progress until the JavaScript function has returned. What is the point in that?
I think the progress element at the moment is a diaster of implementation.
Conclusion
Only IE gives me what I want.
Only setTimeout
offers a cross-browser solution.
The new technologies of Worker
and progress
are a disappointment of inconsistent implementation.
For all the ire and hatred thrown at Internet Explorer, it was my only friend in this area.
As a final point, why do browsers still require just []
for collections, when HTML5 clearly recognises ()
: HTML Collection?
No comments made on this entry.