Search code examples
firefoxember.jsalertback-buttonhtml5-history

code in willTransition callback executed after model callback after a window.alert


I recently encountered a bug with Ember.js. Basically when I click the browser back button in Firefox, Ember.js executes the code in wrong order. The code in willTransition callback right after the line alert("..."); is executed after model callback, which should normally be executed right after alert("..."); is returned.

The code is:

App = Ember.Application.create();
App.Router.reopen({
  location: 'history'
});
App.Router.map(function() {
  this.route('foo');
});
App.IndexRoute = Ember.Route.extend({
  actions:{
    willTransition: function(transition){
      console.log('start IndexRoute#willTransition');
      alert('See console logs');
      console.log('end IndexRoute#willTransition');
    }
  }
});
App.FooRoute = Ember.Route.extend({
  model: function() {
    console.log('start FooRoute#model');
    return [];
  }
});

An instruction on how to reproduce the bug can be found here: https://github.com/goooooouwa/location/blob/master/README.md

See this bug in action with Firefox( version 12+ on OS X, version 7+ on Windows) on JSBin: http://emberjs.jsbin.com/tefoka/


Solution

  • The root cause of this behaviour is this Firefox bug.

    How this bug causes the behaviour in question

    What's happening under the hood

    1. After user hit browser back button, the URL is changed, the browser history is then changed, which triggers a PopStateEvent, and Ember then handles this event with onUpdateURL() callback
    2. As the callback, Ember starts a transition by calling this._doURLTransition('handleURL', url);
    3. Inside the transition, a Promise is created to determine the resolution of this transition. Ember schedules promises in runloop by calling run.backburner.schedule('actions', function(){...}), since no runloop is created, an autorun will be created by calling Backburner.createAutoRun():
    function createAutorun(backburner) {
      backburner.begin();
      backburner._autorun = global.setTimeout(function() {
        backburner._autorun = null; 
        backburner.end();
      });   
    }
    

    before the auto-created runloop ends, the following code is executed:

    willTransition: function(transition){
      console.log('-------------- 1. start IndexRoute#willTransition -------------- ');
      alert('See console logs');
      console.log('-------------- 2. end IndexRoute#willTransition -------------- ');
    }
    

    when the runloop ends, the flush process begins, and the following code is executed:

    model: function() {
      console.log('-------------- 3. start FooRoute#model -------------- ');
      return [];
    }
    

    Simplified version

    The above code is essencailly the same as:

    console.log('processing: task #1');
    setTimeout(function(){
      console.log('processing: task #3');
    },0);
    alert('See console logs');
    console.log('processing: task #2');
    

    The above code runs wrongly with the Firefox bug, which causes the Ember behaviour in question.