JavaScript Template Engine & Framework Rendering

JavaScript performance comparison

Revision 2 of this test case created by tomByrer

Info

v2: Cut window.sharedModel.forEach & window.sharedModel down from looping 320 times to 2 loop to increase variation of results.

Comparison of rendering performance for the following JavaScript template engines and frameworks.

Pure Template Engines: * Mustache * Handlebars * Underscore * Dust * JsRender * Hogan

Frameworks: * React * Angular * Knockout * Ember

Rendering is performed in a deferred manner via window.requestAnimationFrame so that layout and rendering times are captured, in addition to any JavaScript processing overhead.

Preparation code

<!-- RxJs 2.2.17 -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/rxjs/2.2.17/rx.min.js"></script>

<!-- Mustache 0.8.2 -->
<script src="http://cdn.jsdelivr.net/mustache.js/0.8.1/mustache.min.js"></script>

<!-- Handlebars 2.0.0-alpha2 -->
<!-- <script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars.min-latest.js"></script> -->

<!-- Handlebars 1.3.0 (Ember version dependency) -->
<script src="http://builds.handlebarsjs.com.s3.amazonaws.com/handlebars-v1.3.0.js"></script>

<!-- Underscore 1.6.0 -->
<script src="http://cdn.jsdelivr.net/underscorejs/1.6.0/underscore-min.js"></script>

<!-- DustJs 2.3.4 -->
<script src="https://rawgithub.com/linkedin/dustjs/master/dist/dust-full.min.js"></script>

<!-- JsRender 1.0.0-beta -->
<script src="https://rawgithub.com/BorisMoore/jsrender/master/jsrender.min.js"></script>

<!-- Hogan 2.0.0 -->
<script src="http://twitter.github.com/hogan.js/builds/2.0.0/hogan-2.0.0.js"></script>

<!-- React 0.10.0 -->
<script src="http://fb.me/react-0.10.0.js"></script>

<!-- Angular 1.2.16 -->
<script src="http://code.angularjs.org/1.2.16/angular.min.js"></script>
<script src="http://code.angularjs.org/1.2.16/angular-route.js"></script>

<!-- Knockout 3.1.0 -->
<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/3.1.0/knockout-min.js"></script>

<!-- Ember 1.5.0 and dependencies -->
<script src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/ember.js/1.5.0/ember.min.js"></script>
<script src="http://cdnjs.cloudflare.com/ajax/libs/ember-data.js/1.0.0-beta.7/ember-data.min.js"></script>

<!-- Angular Template via Live DOM -->
<div id="angular-container" style="display: none">
  <div ng-controller="angular-controller-livedom" ng-show="loaded">
    <h1 class="header">{{data.header}}</h1>
    <h2 class="header2">{{data.header2}}</h2>
    <h3 class="header3">{{data.header3}}</h3>
    <h4 class="header4">{{data.header4}}</h4>
    <h5 class="header5">{{data.header5}}</h5>
    <h6 class="header6">{{data.header6}}</h6>
    <ul class="list">
      <li ng-repeat="item in data.list track by $index" class="item">{{item}}</li>
    </ul>
  </div>
</div>

<!-- Angular Template via Script-Based DOM -->
<div id="angular-container-scriptdom" style="display: none">
  <div ng-view></div>
  <script type="text/ng-template" id="angular-template.html">
    <div ng-controller="angular-controller-scriptdom" ng-show="loaded">
      <h6 class="header6">{{data.header6}}</h6>
      <h5 class="header5">{{data.header5}}</h5>
      <h4 class="header4">{{data.header4}}</h4>
      <h3 class="header3">{{data.header3}}</h3>
      <h2 class="header2">{{data.header2}}</h2>
      <h1 class="header">{{data.header}}</h1>
      <ul class="list">
        <li ng-repeat="item in data.list track by $index" class="item">{{item}}</li>
      </ul>
    </div>
  </script>
</div>

<!-- Angular Template via String in Component -->
<div id="angular-container-component" style="display: none">
  <tmpl></tmpl>
</div>

<!-- Knockout Template -->
<div id="knockout-container" style="display: none">
  <div>
    <h1 class="header" data-bind="text: data().header"></h1>
    <h2 class="header2" data-bind="text: data().header2"></h2>
    <h3 class="header3" data-bind="text: data().header3"></h3>
    <h4 class="header4" data-bind="text: data().header4"></h4>
    <h5 class="header5" data-bind="text: data().header5"></h5>
    <h6 class="header6" data-bind="text: data().header6"></h6>
    <ul class="list" data-bind="foreach: data().list">
      <li class="item" data-bind="text: $data"></li>
    </ul>
  </div>
</div>

<!-- Ember Template via Script-Based DOM -->
<div id="ember-container">
  <script type="text/x-handlebars">
    <div>
      <h1 class="header">{{EmberApp.data.header}}</h1>
      <h2 class="header2">{{EmberApp.data.header2}}</h2>
      <h3 class="header3">{{EmberApp.data.header3}}</h3>
      <h4 class="header4">{{EmberApp.data.header4}}</h4>
      <h5 class="header5">{{EmberApp.data.header5}}</h5>
      <h6 class="header6">{{EmberApp.data.header6}}</h6>
      <ul class="list">{{#each EmberApp.data.list}}<li class="item">{{this}}</li>{{/each}}</ul>
    </div>
  </script>
</div>

<!-- Ember Template via String in View -->
<div id="ember-view-container" style="display: none">
</div>

<!-- Container used for rendering templates from all other libraries -->
<div id="container"></div>

<script>
// Data to be rendered.
window.sharedModel = [
  { header: "Header", header2: "Header2", header3: "Header3", header4: "Header4", header5: "Header5", header6: "Header6", list: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"] },
  { header6: "Header", header5: "Header5", header4: "Header2", header3: "Header3", header2: "Header2", header: "Header6", list: ["10", "2", "8", "4", "6", "5", "7", "3", "9", "1"] }
];
// Pad the lists to test rendering of more than 10 item
window.sharedModel.forEach(function(item) {
  // (2^5)*10 = 320 items in all
  for(var i = 0; i < 1; i++) {
    item.list.map(function(listItem) {
      item.list.push(listItem);
    });
  }
});

// Use requestAnimationFrame to render the data
window.requestAnimationFrame = window.requestAnimationFrame ||
  window.webkitRequestAnimationFrame ||
  window.mozRequestAnimationFrame ||
  window.msRequestAnimationFrame ||
  function(fn) { return window.setTimeout(fn, 0); };

// Make these DOM elements globally available
window.angularContainer = document.getElementById("angular-container");
window.angularContainerScriptDOM = document.getElementById("angular-container-scriptdom");
window.angularContainerComponent = document.getElementById("angular-container-component");
window.knockoutContainer = document.getElementById("knockout-container");
window.emberContainer = document.getElementById("ember-container");
window.emberViewContainer = document.getElementById("ember-view-container");
window.container = document.getElementById("container");

// Mustache template
window.mustacheTemplate = "<div><h1 class='header'>{{{header}}}</h1><h2 class='header2'>{{{header2}}}</h2><h3 class='header3'>{{{header3}}}</h3><h4 class='header4'>{{{header4}}}</h4><h5 class='header5'>{{{header5}}}</h5><h6 class='header6'>{{{header6}}}</h6><ul class='list'>{{#list}}<li class='item'>{{{.}}}</li>{{/list}}</ul></div>";
window.renderMustacheTemplate = function(data) {
  container.innerHTML = Mustache.render(mustacheTemplate, data);
};

// Handlebars template
window.handlebarsTemplate = Handlebars.compile("<div><h1 class='header'>{{header}}</h1><h2 class='header2'>{{header2}}</h2><h3 class='header3'>{{header3}}</h3><h4 class='header4'>{{header4}}</h4><h5 class='header5'>{{header5}}</h5><h6 class='header6'>{{header6}}</h6><ul class='list'>{{#each list}}<li class='item'>{{this}}</li>{{/each}}</ul></div>");
window.renderHandlebarsTemplate = function(data) {
  container.innerHTML = handlebarsTemplate(data);
};

// Precompiled Handlebars template - 2.0.0
// !function(){var a=Handlebars.template,e=Handlebars.templates=Handlebars.templates||{};e.jsPerfHandlebars=a({1:function(a){var e="function",h=this.escapeExpression;return"<li class='item'>"+h(typeof a===e?a.apply(a):a)+"</li>"},compiler:[5,">= 2.0.0"],main:function(a,e,h,s){var r,d,l="function",t=this.escapeExpression,n="<div><h1 class='header'>"+t((d=e.header||a&&a.header,typeof d===l?d.call(a,{name:"header",hash:{},data:s}):d))+"</h1><h2 class='header2'>"+t((d=e.header2||a&&a.header2,typeof d===l?d.call(a,{name:"header2",hash:{},data:s}):d))+"</h2><h3 class='header3'>"+t((d=e.header3||a&&a.header3,typeof d===l?d.call(a,{name:"header3",hash:{},data:s}):d))+"</h3><h4 class='header4'>"+t((d=e.header4||a&&a.header4,typeof d===l?d.call(a,{name:"header4",hash:{},data:s}):d))+"</h4><h5 class='header5'>"+t((d=e.header5||a&&a.header5,typeof d===l?d.call(a,{name:"header5",hash:{},data:s}):d))+"</h5><h6 class='header6'>"+t((d=e.header6||a&&a.header6,typeof d===l?d.call(a,{name:"header6",hash:{},data:s}):d))+"</h6><ul class='list'>";return r=e.each.call(a,a&&a.list,{name:"each",hash:{},fn:this.program(1,s),inverse:this.noop,data:s}),(r||0===r)&&(n+=r),n+"</ul></div>"},useData:!0})}();

// Precompiled Handlebars template - 1.3.0
!function(){var a=Handlebars.template,e=Handlebars.templates=Handlebars.templates||{};e.jsPerfHandlebars=a(function(a,e,h,l,s){function t(a){var e="";return e+="<li class='item'>"+i(typeof a===n?a.apply(a):a)+"</li>"}this.compilerInfo=[4,">= 1.0.0"],h=this.merge(h,a.helpers),s=s||{};var d,r,c="",n="function",i=this.escapeExpression,p=this;return c+="<div><h1 class='header'>",(r=h.header)?d=r.call(e,{hash:{},data:s}):(r=e&&e.header,d=typeof r===n?r.call(e,{hash:{},data:s}):r),c+=i(d)+"</h1><h2 class='header2'>",(r=h.header2)?d=r.call(e,{hash:{},data:s}):(r=e&&e.header2,d=typeof r===n?r.call(e,{hash:{},data:s}):r),c+=i(d)+"</h2><h3 class='header3'>",(r=h.header3)?d=r.call(e,{hash:{},data:s}):(r=e&&e.header3,d=typeof r===n?r.call(e,{hash:{},data:s}):r),c+=i(d)+"</h3><h4 class='header4'>",(r=h.header4)?d=r.call(e,{hash:{},data:s}):(r=e&&e.header4,d=typeof r===n?r.call(e,{hash:{},data:s}):r),c+=i(d)+"</h4><h5 class='header5'>",(r=h.header5)?d=r.call(e,{hash:{},data:s}):(r=e&&e.header5,d=typeof r===n?r.call(e,{hash:{},data:s}):r),c+=i(d)+"</h5><h6 class='header6'>",(r=h.header6)?d=r.call(e,{hash:{},data:s}):(r=e&&e.header6,d=typeof r===n?r.call(e,{hash:{},data:s}):r),c+=i(d)+"</h6><ul class='list'>",d=h.each.call(e,e&&e.list,{hash:{},inverse:p.noop,fn:p.program(1,t,s),data:s}),(d||0===d)&&(c+=d),c+="</ul></div>"})}();
window.renderPrecompiledHandlebarsTemplate = function(data) {
  container.innerHTML = Handlebars.templates['jsPerfHandlebars'](data);
};

// Underscore template
window.underscoreTemplate = _.template("<div><h1 class='header'><%= header %></h1><h2 class='header2'><%= header2 %></h2><h3 class='header3'><%= header3 %></h3><h4 class='header4'><%= header4 %></h4><h5 class='header5'><%= header5 %></h5><h6 class='header6'><%= header6 %></h6><ul class='list'><% for (var i = 0, l = list.length; i < l; i++) { %><li class='item'><%= list[i] %></li><% } %></ul></div>");
window.renderUnderscoreTemplate = function(data) {
  container.innerHTML = underscoreTemplate(data);
};

// Dust template
var dustTemplate = dust.compile("<div><h1 class='header'>{header}</h1><h2 class='header2'>{header2}</h2><h3 class='header3'>{header3}</h3><h4 class='header4'>{header4}</h4><h5 class='header5'>{header5}</h5><h6 class='header6'>{header6}</h6><ul class='list'>{#list}<li class='item'>{.}</li>{/list}</ul></div>", "dustTemplate");
dust.loadSource(dustTemplate);
window.renderDustTemplate = function(data) {
  dust.render("dustTemplate", data, function(err, output) {
    container.innerHTML = output;
  });
};

// Precompiled Dust template
(function(){dust.register("jsPerfDust.dust",body_0);function body_0(chk,ctx){return chk.write("<div><h1 class='header'>").reference(ctx.get(["header"], false),ctx,"h").write("</h1><h2 class='header2'>").reference(ctx.get(["header2"], false),ctx,"h").write("</h2><h3 class='header3'>").reference(ctx.get(["header3"], false),ctx,"h").write("</h3><h4 class='header4'>").reference(ctx.get(["header4"], false),ctx,"h").write("</h4><h5 class='header5'>").reference(ctx.get(["header5"], false),ctx,"h").write("</h5><h6 class='header6'>").reference(ctx.get(["header6"], false),ctx,"h").write("</h6><ul class='list'>").section(ctx.get(["list"], false),ctx,{"block":body_1},null).write("</ul></div>");}function body_1(chk,ctx){return chk.write("<li class='item'>").reference(ctx.getPath(true, []),ctx,"h").write("</li>");}return body_0;})();
window.renderPrecompiledDustTemplate = function(data) {
  dust.render("jsPerfDust.dust", data, function(err, output) {
    container.innerHTML = output;
  });
}

// Precompiled and cached Dust template
var dustTemplatePrecompiledCached = (function(){dust.register("jsPerfDustCached.dust",body_0);function body_0(chk,ctx){return chk.write("<div><h1 class='header'>").reference(ctx.get(["header"], false),ctx,"h").write("</h1><h2 class='header2'>").reference(ctx.get(["header2"], false),ctx,"h").write("</h2><h3 class='header3'>").reference(ctx.get(["header3"], false),ctx,"h").write("</h3><h4 class='header4'>").reference(ctx.get(["header4"], false),ctx,"h").write("</h4><h5 class='header5'>").reference(ctx.get(["header5"], false),ctx,"h").write("</h5><h6 class='header6'>").reference(ctx.get(["header6"], false),ctx,"h").write("</h6><ul class='list'>").section(ctx.get(["list"], false),ctx,{"block":body_1},null).write("</ul></div>");}function body_1(chk,ctx){return chk.write("<li class='item'>").reference(ctx.getPath(true, []),ctx,"h").write("</li>");}return body_0;})();
dust.loadSource(dustTemplatePrecompiledCached, "dustTemplatePrecompiledCached");
window.renderPrecompiledCachedDustTemplate = function(data) {
  dust.render("jsPerfDustCached.dust", data, function(err, output) {
    container.innerHTML = output;
  });
};

// JsRender template
jsviews.templates({jsRenderTemplate: "<div><h1 class='header'>{{:header}}</h1><h2 class='header2'>{{:header2}}</h2><h3 class='header3'>{{:header3}}</h3><h4 class='header4'>{{:header4}}</h4><h5 class='header5'>{{:header5}}</h5><h6 class='header6'>{{:header6}}</h6><ul class='list'>{{for list}}<li class='item'>{{>#data}}</li>{{/for}}</ul></div>"});
window.renderJsRenderTemplate = function(data) {
  container.innerHTML = jsviews.render.jsRenderTemplate(data);
};

// Hogan template
window.hoganTemplate = Hogan.compile("<div><h1 class='header'>{{{header}}}</h1><h2 class='header2'>{{{header2}}}</h2><h3 class='header3'>{{{header3}}}</h3><h4 class='header4'>{{{header4}}}</h4><h5 class='header5'>{{{header5}}}</h5><h6 class='header6'>{{{header6}}}</h6><ul class='list'>{{#list}}<li class='item'>{{{.}}}</li>{{/list}}</ul></div>");
window.renderHoganTemplate = function(data) {
  container.innerHTML = hoganTemplate.render(data);
};

// React component
/** @jsx React.DOM */
var JsPerfReactComponent = React.createClass({displayName: 'JsPerfReactComponent',
  render: function() {
      var data = this.props.data;
      var i = 0;
      return (
        React.DOM.div(null,
          React.DOM.h1( {className:"header"}, data.header),
          React.DOM.h2( {className:"header2"}, data.header2),
          React.DOM.h3( {className:"header3"}, data.header3),
          React.DOM.h4( {className:"header4"}, data.header4),
          React.DOM.h5( {className:"header5"}, data.header5),
          React.DOM.h6( {className:"header6"}, data.header6),
          React.DOM.ul( {className:"list"},
            data.list.map(function(item) {
              return React.DOM.li( {className:"item", key:i++}, item)
            })
          )
        )
      );
  }
});
window.renderReactComponent = function(data) {
  React.renderComponent(JsPerfReactComponent({data:data}), container);
}

// Angular application rendering via Live DOM
var angularAppLiveDOM = angular.module("angular-app-livedom", []);
angularAppLiveDOM.controller("angular-controller-livedom", function($scope) {
});
window.renderAngularLiveDOM = function(data) {
  angularLiveDOMScope.data = data;
  angularLiveDOMScope.loaded = true;
  angularLiveDOMScope.$apply();
}
ui.benchmarks[10].setup = function() {
  if (!window.angularLiveDOMScope) {
    angular.bootstrap(angularContainer, ["angular-app-livedom"]);
    window.angularLiveDOMScope = angular.element(angularContainer).scope();
  }
  angularContainer.style.display = "block";
}

// Angular application rendering via component directive
var angularAppComponent = angular.module("angular-app-component", []);
angularAppComponent.
  directive("tmpl", function() {
    return {
      restrict: "E",
      transclude: true,
      controller: function($scope, $element) {
        // angularContainerComponent.style.display = "block";
      },
      template: "<div ng-show='loaded'>" +
        "<h6 class='header6'>{{data.header6}}</h6>" +
        "<h5 class='header5'>{{data.header5}}</h5>" +
        "<h4 class='header4'>{{data.header4}}</h4>" +
        "<h3 class='header3'>{{data.header3}}</h3>" +
        "<h2 class='header2'>{{data.header2}}</h2>" +
        "<h1 class='header'>{{data.header}}</h1>" +
        "<ul class='list'>" +
          "<li ng-repeat='item in data.list track by $index' class='item'>{{item}}</li>" +
        "</ul>" +
      "</div>",
      replace: true
    }
});
window.renderAngularComponent = function(data) {
  angularCompontentScope.data = data;
  angularCompontentScope.loaded = true;
  angularCompontentScope.$apply();
}
ui.benchmarks[11].setup = function() {
  if (!window.angularCompontentScope) {
    angular.bootstrap(angularContainerComponent, ["angular-app-component"]);
    window.angularCompontentScope = angular.element(angularContainerComponent).scope();
  }
  angularContainerComponent.style.display = "block";
}

// Knockout model to be bound to the template
var knockoutModel = function() {
  this.data = ko.observable({});
};
window.renderKnockoutTemplate = function(data) {
  knockoutModelInstance.data(data);
}
ui.benchmarks[12].setup = function() {
  if (!window.knockoutModelInstance) {
    window.knockoutModelInstance = new knockoutModel();
    ko.applyBindings(knockoutModelInstance);
  }
  knockoutContainer.style.display = "block";
}

// Ember application rendering via Script-Based DOM
window.EmberApp = Ember.Application.create({
  rootElement: $("#ember-container")
});
EmberApp.data = Ember.Object.create({});
window.renderEmberTemplate = function(data) {
  EmberApp.data.setProperties(data);
};
ui.benchmarks[13].setup = function() {
  emberContainer.style.display = "block";
}

// Ember application rendering via String-Based DOM
window.EmberCompiledApp = Ember.Application.create({
  rootElement: $("#ember-view-container")
});
window.emberCompiledTemplate = Ember.Handlebars.compile("<div><h1 class='header'>{{view.data.header}}</h1><h2 class='header2'>{{view.data.header2}}</h2><h3 class='header3'>{{view.data.header3}}</h3><h4 class='header4'>{{view.data.header4}}</h4><h5 class='header5'>{{view.data.header5}}</h5><h6 class='header6'>{{view.data.header6}}</h6><ul class='list'>{{#each view.data.list}}<li class='item'>{{this}}</li>{{/each}}</ul></div>");
EmberCompiledApp.View = Ember.View.extend({
  templateName: "ember-compiled-template",
  template: emberCompiledTemplate,
  data: Ember.Object.create({})
});
window.emberView = EmberCompiledApp.View.create();
emberView.appendTo("#ember-view-container");
window.renderEmberCompiledTemplate = function(data) {
  emberView.set("data", data);
}
ui.benchmarks[14].setup = function() {
  emberViewContainer.style.display = "block";
}

/**
 * Returns an Rx Observable that emits items from the specified data model, one per rendering frame.
 * @param {Array} model e.g. window.sharedModel.
 */

window.getData = function(model) {
  var length = model ? model.length : 0;
  var index = 0;
  return Rx.Observable.create(function(o) {
    function callback() {
      if (index < length) {
        o.onNext(model[index++]);
        window.requestAnimationFrame(callback);
      } else {
        o.onCompleted();
      }
    }
    window.requestAnimationFrame(callback);
  });
};

/**
 * Run test that iterates through and renders window.sharedModel data.
 * @param {Function} renderFunction Rendering function that accepts a single argument representing
 * the data object to be rendered.
 * @param {Function} doneCallback Callback function that is called when test is complete.
 */

window.runTest = function(renderFunction, doneCallback) {
  getData(sharedModel).subscribe(
    // onNext
    function(data) {
      renderFunction(data);
    },
    // onError
    function() {
      console.error("window.runTest: Failed to retrieve items from window.sharedModel.");
    },
    // onComplete
    function() {
      if (typeof doneCallback === "function") {
        doneCallback();
      }
    }
  );
}

</script>
<script>
Benchmark.prototype.teardown = function() {
    angularContainer.style.display = "none";
    angularContainerComponent.style.display = "none";
    knockoutContainer.style.display = "none";
    emberContainer.style.display = "none";
    emberViewContainer.style.display = "none";
    container.innerHTML = "";
};
</script>

Preparation code output

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
Mustache
// async test
runTest(renderMustacheTemplate, function() {
  deferred.resolve();
});
pending…
Handlebars
// async test
runTest(renderHandlebarsTemplate, function() {
  deferred.resolve();
});
pending…
Handlebars Precompiled
// async test
runTest(renderPrecompiledHandlebarsTemplate, function() {
  deferred.resolve();
});
pending…
Underscore
// async test
runTest(renderUnderscoreTemplate, function() {
  deferred.resolve();
});
pending…
Dust
// async test
runTest(renderDustTemplate, function() {
  deferred.resolve();
});
pending…
Dust Precompiled
// async test
runTest(renderPrecompiledDustTemplate, function() {
  deferred.resolve();
});
pending…
Dust Precompiled (Cached)
// async test
runTest(renderPrecompiledCachedDustTemplate, function() {
  deferred.resolve();
});
pending…
JsRender
// async test
runTest(renderJsRenderTemplate, function() {
  deferred.resolve();
});
pending…
Hogan
// async test
runTest(renderHoganTemplate, function() {
  deferred.resolve();
});
pending…
React
// async test
runTest(renderReactComponent, function() {
  deferred.resolve();
});
pending…
Angular (Live DOM)
// async test
runTest(renderAngularLiveDOM, function() {
  deferred.resolve();
});
pending…
Angular (Component)
// async test
runTest(renderAngularComponent, function() {
  deferred.resolve();
});
pending…
Knockout
// async test
runTest(renderKnockoutTemplate, function() {
  deferred.resolve();
});
pending…
Ember
// async test
runTest(renderEmberTemplate, function() {
  deferred.resolve();
});
pending…
Ember (View)
// async test
runTest(renderEmberCompiledTemplate, function() {
  deferred.resolve();
});
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

1 comment

tomByrer (revision owner) commented :

I don't think this or previous version are useful tests.

Add a comment