IE8 "stop unresponsive script" issue with EmberJS DOM renders


#1

I have a fairly large EmberJS app. It loads great on IE9, Chrome and Firefox. On IE8 it loads very slowly (no big deal), and then pops up a “Stop unresponsive script” dialog (big deal, most users bail at this point).

PROBLEM: On IE7/8 with an older computer, the insertElement() call graph takes long enough to trigger the “stop unresponsive script” dialog.

Has anyone else run into this? Any suggestions how to work around it? If this was slow because of my own javascript, I could easily break it into “work chunks” and avoid this problem, but I can’t figure out a reasonable approach to make handlebars/emberjs break a DOM-(re)render into chunks.

I suspect many large+complex EmberJS applications will run into this issue with IE7/8. Its also a bit of a submarine issue because on fast development machines, you may not encounter the issue, but it will manifest on the type of slow computers still running IE8.

-Seth


#2

You could look at some of the ideas we’re experimenting with at Ember is very slow at rendering lists

If it is because of a big list, you might break the work into chunks by adding to the bound array in steps with a setTimeout. Without knowing what you’re app is taking time on I can’t suggest anything more specific.

The idea that ember could somehow detect situations where it was running slowly and pause execution for a moment to avoid the warning is interesting.


#3

My codebase is a survey authoring tool for climate change scientists, so its not a fixed codepath I can performance tune until its fast enough to avoid the dialog, because a survey author could always engineer more complex content that triggers the dialog.

I’d like to consider breaking the recursive DOM render itself into a time-bounded operation to avoid a “one slow render = application broken on IE8” cliff. One of those idea that’s easy to say, hard to do (probably).

Does anyone with render/render-states knowledge have ideas where I should start to investigate this? The rendering pipelines is a pretty twisty maze, I’d appreciate any advice where to start looking.

-Seth


#4

The sketch of the idea would be to locate a function in the render codepath (Ember.View.states.preRender.insertElement() ?), call it renderFunction(), that:

  • Appears in the call-tree once-per-view rendered
  • Can be converted to an async function

Then at the start of a render (I can’t even figure out where or how a render starts! Something with scheduling? Feeling pretty dumb!) you start a timer, and in renderFunction() you check if you’ve exceeded the drop-dead time (infinte on browsers except IE7/8), and if you have, you schedule another render to pick up where you left off with the async callback.

Ugh. I don’t even understand the Ember bits here well enough to figure out how to phrase this! Any guidance appreciated.

-Seth


#5

We’re dealing with the very same issue here. The best thing that I’ve been able to come up with is this. Our app has a large front page that renders all the days in a month and then some detail about each day. What I’ve done is wrap the day view in a block that looks like this:

 {{#if shouldRenderViewBody}}
     <!-- View contents go here -->
 {{/if}

On the first rendering pass, the day view gets rendered but without any contents. Then, I put code in the view’s didInsertElement() method to queue up the view for rendering:

didInsertElement: function() {
    var view = this;
    
    // For IE8 do progressive rendering
    if (isIE.eight()) {
        window.setTimeout(function() {
            view.get('controller').markAsElementInserted();
        }, 1);
    }
}

The markAsElementInserted() method basically queues up this day into a list of days. I then have some centralized code that pulls days off the queue and sets a timer for them to render. This causes IE8’s instruction counter to reset. It has the effect of making the days show up one at a time, but it’s the only way I could get IE 8 to stop barfing.

If you’d like more details, let me know - I’d be happy to help out.


#6

@sdwr98’s approach inspires me for how to write a semi-generic workaround in the form of an Ember.View subclass (and/or mixin): “RateLimitedView”. On sane browsers, this does nothing. On IE7/8 RateLimitedViews start not-in-the-dom, and are added to the DOM in chunks, always keeping on eye on the clock.

For example something like (totally untested):

var toShowQueue = [];

var RateLimitedView = Ember.View.extend({
	init: function () {
		this._super();

		if (isIE7or8()) {
			this.set('isVisible', false);
			toShowQueue.push(this);
			Ember.run.scheduleOnce("render", CONTEXT, showChunkOfViews);
		}
	}
});

var OLD_IE_DEADLINE = 500; // ms
function showChunkOfViews () {
	var endAtTime = Date.now() + OLD_IE_DEADLINE;

	while (toShowQueue.length > 0 && Date.now() < endAtTime) {
		Ember.run(function() {
			 toShowQueue.pop().set('isVisible', true);
		});
	}

	if (toShowQueue.length > 0) {
          // We ran out of time, but there's still work to be done!
		Ember.run.scheduleOnce("render", CONTEXT, showChunkOfViews);
	}
}

 exports.RateLimitedView = RateLimitedView;
  1. I’m not really sure about the validity/wisdom/performance of invoking Ember.run() inside an existing Ember runloop
  2. This can defeat binding batching (and thus DOM-change batching), e.g. if the RateLimitedView sub-classes change lots of properties inside their didInsertElement() calls, all bindings will be synced AND then rendered for each element separately, causing potentially lots of unncessary work.
  3. If anyone has an idea how to tell a view to render (go from isVisible false to isVisible true) synchronously WITHOUT syncing bindings in between this could be a good improvement on the nested runloop approach.

Even if this makes performance much worse on IE7/8, I think its a better user experience than the “stop script” dialog popping up.

I’d still like to figure out how to address this in EmberJS core. Unless IE7/8 are not supported by Ember, this seems like a core-worthy issue.


#7

I am working to solve this problem as well. @sdwr98 I would like know know more about your solution. @snickell did you get a solution up and running?


#8

@sdwr98 @snickell I also face a similar issue when rendering larger number of Views Recursively. When I call around 5K to 6K nodes of render with Views, “Stop Script” is thrown in Firefox.

JSBIn Link

If there is any solution to render large number of Views at the same time, kindly share the same.