Bug 705074 - All uses of DOMTemplate should use new template function; r=dcamp
authorJoe Walker <jwalker@mozilla.com>
Thu, 08 Dec 2011 12:39:04 +0000
changeset 82261 360995b11488d98e3afd13a8c235d875b7bccf9f
parent 82260 c84bdc1137e1c91dca9332f9176ee547a1ffd24a
child 82262 5e39ff32da8253ce691853deffe628a384103baa
push id21589
push usertim.taubert@gmx.de
push dateFri, 09 Dec 2011 04:57:11 +0000
treeherdermozilla-central@9e7239c0f557 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdcamp
bugs705074
milestone11.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 705074 - All uses of DOMTemplate should use new template function; r=dcamp
browser/devtools/shared/test/browser_templater_basic.js
browser/devtools/styleinspector/CssHtmlTree.jsm
browser/devtools/webconsole/HUDService.jsm
browser/devtools/webconsole/gcli.jsm
--- a/browser/devtools/shared/test/browser_templater_basic.js
+++ b/browser/devtools/shared/test/browser_templater_basic.js
@@ -17,17 +17,17 @@ function runTest(index) {
   var options = tests[index] = tests[index]();
   var holder = content.document.createElement('div');
   holder.id = options.name;
   var body = content.document.body;
   body.appendChild(holder);
   holder.innerHTML = options.template;
 
   info('Running ' + options.name);
-  new Templater().processNode(holder, options.data);
+  template(holder, options.data, options.options);
 
   if (typeof options.result == 'string') {
     is(holder.innerHTML, options.result, options.name);
   }
   else {
     ok(holder.innerHTML.match(options.result), options.name);
   }
 
@@ -83,55 +83,60 @@ var tests = [
     template: '<div id="ex1">${nested.value}</div>',
     data: { nested:{ value:'pass 1' } },
     result: '<div id="ex1">pass 1</div>'
   };},
 
   function() { return {
     name: 'returnDom',
     template: '<div id="ex2">${__element.ownerDocument.createTextNode(\'pass 2\')}</div>',
+    options: { allowEval: true },
     data: {},
     result: '<div id="ex2">pass 2</div>'
   };},
 
   function() { return {
     name: 'srcChange',
     template: '<img _src="${fred}" id="ex3">',
     data: { fred:'green.png' },
     result: /<img( id="ex3")? src="green.png"( id="ex3")?>/
   };},
 
   function() { return {
     name: 'ifTrue',
     template: '<p if="${name !== \'jim\'}">hello ${name}</p>',
+    options: { allowEval: true },
     data: { name: 'fred' },
     result: '<p>hello fred</p>'
   };},
 
   function() { return {
     name: 'ifFalse',
     template: '<p if="${name !== \'jim\'}">hello ${name}</p>',
+    options: { allowEval: true },
     data: { name: 'jim' },
     result: ''
   };},
 
   function() { return {
     name: 'simpleLoop',
     template: '<p foreach="index in ${[ 1, 2, 3 ]}">${index}</p>',
+    options: { allowEval: true },
     data: {},
     result: '<p>1</p><p>2</p><p>3</p>'
   };},
 
   function() { return {
     name: 'loopElement',
     template: '<loop foreach="i in ${array}">${i}</loop>',
     data: { array: [ 1, 2, 3 ] },
     result: '123'
   };},
 
+  // Bug 692028: DOMTemplate memory leak with asynchronous arrays
   // Bug 692031: DOMTemplate async loops do not drop the loop element
   function() { return {
     name: 'asyncLoopElement',
     template: '<loop foreach="i in ${array}">${i}</loop>',
     data: { array: delayReply([1, 2, 3]) },
     result: '<span></span>',
     later: '123'
   };},
@@ -145,16 +150,17 @@ var tests = [
       ok(options.data.element.innerHTML, 'pass 9', 'saveElement saved');
       delete options.data.element;
     }
   };},
 
   function() { return {
     name: 'useElement',
     template: '<p id="pass9">${adjust(__element)}</p>',
+    options: { allowEval: true },
     data: {
       adjust: function(element) {
         is('pass9', element.id, 'useElement adjust');
         return 'pass 9b'
       }
     },
     result: '<p id="pass9">pass 9b</p>'
   };},
@@ -162,44 +168,78 @@ var tests = [
   function() { return {
     name: 'asyncInline',
     template: '${delayed}',
     data: { delayed: delayReply('inline') },
     result: '<span></span>',
     later: 'inline'
   };},
 
+  // Bug 692028: DOMTemplate memory leak with asynchronous arrays
   function() { return {
     name: 'asyncArray',
     template: '<p foreach="i in ${delayed}">${i}</p>',
     data: { delayed: delayReply([1, 2, 3]) },
     result: '<span></span>',
     later: '<p>1</p><p>2</p><p>3</p>'
   };},
 
   function() { return {
     name: 'asyncMember',
     template: '<p foreach="i in ${delayed}">${i}</p>',
     data: { delayed: [delayReply(4), delayReply(5), delayReply(6)] },
     result: '<span></span><span></span><span></span>',
     later: '<p>4</p><p>5</p><p>6</p>'
   };},
 
+  // Bug 692028: DOMTemplate memory leak with asynchronous arrays
   function() { return {
     name: 'asyncBoth',
     template: '<p foreach="i in ${delayed}">${i}</p>',
     data: {
       delayed: delayReply([
         delayReply(4),
         delayReply(5),
         delayReply(6)
       ])
     },
     result: '<span></span>',
     later: '<p>4</p><p>5</p><p>6</p>'
+  };},
+
+  // Bug 701762: DOMTemplate fails when ${foo()} returns undefined
+  function() { return {
+    name: 'functionReturningUndefiend',
+    template: '<p>${foo()}</p>',
+    options: { allowEval: true },
+    data: {
+      foo: function() {}
+    },
+    result: '<p>undefined</p>'
+  };},
+
+  // Bug 702642: DOMTemplate is relatively slow when evaluating JS ${}
+  function() { return {
+    name: 'propertySimple',
+    template: '<p>${a.b.c}</p>',
+    data: { a: { b: { c: 'hello' } } },
+    result: '<p>hello</p>'
+  };},
+
+  function() { return {
+    name: 'propertyPass',
+    template: '<p>${Math.max(1, 2)}</p>',
+    options: { allowEval: true },
+    result: '<p>2</p>'
+  };},
+
+  function() { return {
+    name: 'propertyFail',
+    template: '<p>${Math.max(1, 2)}</p>',
+    result: '<p>${Math.max(1, 2)}</p>'
   };}
 ];
 
 function delayReply(data) {
   var p = new Promise();
   executeSoon(function() {
     p.resolve(data);
   });
--- a/browser/devtools/styleinspector/CssHtmlTree.jsm
+++ b/browser/devtools/styleinspector/CssHtmlTree.jsm
@@ -209,17 +209,20 @@ CssHtmlTree.processTemplate = function C
 {
   if (!aPreserveDestination) {
     aDestination.innerHTML = "";
   }
 
   // All the templater does is to populate a given DOM tree with the given
   // values, so we need to clone the template first.
   let duplicated = aTemplate.cloneNode(true);
-  new Templater().processNode(duplicated, aData);
+
+  // See https://github.com/mozilla/domtemplate/blob/master/README.md
+  // for docs on the template() function
+  template(duplicated, aData, { allowEval: true });
   while (duplicated.firstChild) {
     aDestination.appendChild(duplicated.firstChild);
   }
 };
 
 XPCOMUtils.defineLazyGetter(CssHtmlTree, "_strings", function() Services.strings
         .createBundle("chrome://browser/locale/devtools/styleinspector.properties"));
 
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -87,20 +87,20 @@ XPCOMUtils.defineLazyGetter(this, "CssRu
 });
 
 XPCOMUtils.defineLazyGetter(this, "NetUtil", function () {
   var obj = {};
   Cu.import("resource://gre/modules/NetUtil.jsm", obj);
   return obj.NetUtil;
 });
 
-XPCOMUtils.defineLazyGetter(this, "Templater", function () {
+XPCOMUtils.defineLazyGetter(this, "template", function () {
   var obj = {};
   Cu.import("resource:///modules/devtools/Templater.jsm", obj);
-  return obj.Templater;
+  return obj.template;
 });
 
 XPCOMUtils.defineLazyGetter(this, "PropertyPanel", function () {
   var obj = {};
   try {
     Cu.import("resource:///modules/PropertyPanel.jsm", obj);
   } catch (err) {
     Cu.reportError(err);
@@ -6860,31 +6860,33 @@ GcliTerm.prototype = {
 
     let output = aEvent.output.output;
     if (aEvent.output.command.returnType == "html" && typeof output == "string") {
       output = this.document.createRange().createContextualFragment(
           '<div xmlns="' + HTML_NS + '" xmlns:xul="' + XUL_NS + '">' +
           output + '</div>').firstChild;
     }
 
+    // See https://github.com/mozilla/domtemplate/blob/master/README.md
+    // for docs on the template() function
     let element = this.document.createRange().createContextualFragment(
       '<richlistitem xmlns="' + XUL_NS + '" clipboardText="${clipboardText}"' +
       '    timestamp="${timestamp}" id="${id}" class="hud-msg-node">' +
       '  <label class="webconsole-timestamp" value="${timestampString}"/>' +
       '  <vbox class="webconsole-msg-icon-container" style="${iconContainerStyle}">' +
       '    <image class="webconsole-msg-icon"/>' +
       '    <spacer flex="1"/>' +
       '  </vbox>' +
       '  <hbox flex="1" class="gcliterm-msg-body">${output}</hbox>' +
       '  <hbox align="start"><label value="1" class="webconsole-msg-repeat"/></hbox>' +
       '</richlistitem>').firstChild;
 
     let hud = HUDService.getHudReferenceById(this.hudId);
     let timestamp = ConsoleUtils.timestamp();
-    new Templater().processNode(element, {
+    template(element, {
       iconContainerStyle: "margin-left=" + (hud.groupDepth * GROUP_INDENT) + "px",
       output: output,
       timestamp: timestamp,
       timestampString: ConsoleUtils.timestampString(timestamp),
       clipboardText: output.innerText,
       id: "console-msg-" + HUDService.sequenceId()
     });
 
--- a/browser/devtools/webconsole/gcli.jsm
+++ b/browser/devtools/webconsole/gcli.jsm
@@ -5053,18 +5053,17 @@ define('gcli/promise', ['require', 'expo
 
 define('gcli/commands/help', ['require', 'exports', 'module' , 'gcli/canon', 'gcli/util', 'gcli/l10n', 'gcli/ui/domtemplate', 'text!gcli/commands/help.css', 'text!gcli/commands/help_intro.html', 'text!gcli/commands/help_list.html', 'text!gcli/commands/help_man.html'], function(require, exports, module) {
 var help = exports;
 
 
 var canon = require('gcli/canon');
 var util = require('gcli/util');
 var l10n = require('gcli/l10n');
-
-var Templater = require('gcli/ui/domtemplate').Templater;
+var domtemplate = require('gcli/ui/domtemplate');
 
 var helpCss = require('text!gcli/commands/help.css');
 var helpStyle = undefined;
 var helpIntroHtml = require('text!gcli/commands/help_intro.html');
 var helpIntroTemplate = undefined;
 var helpListHtml = require('text!gcli/commands/help_list.html');
 var helpListTemplate = undefined;
 var helpManHtml = require('text!gcli/commands/help_man.html');
@@ -5098,26 +5097,28 @@ help.startup = function() {
     returnType: 'html',
 
     exec: function(args, context) {
       help.onFirstUseStartup(context.document);
 
       var match = canon.getCommand(args.search);
       if (match) {
         var clone = helpManTemplate.cloneNode(true);
-        new Templater().processNode(clone, getManTemplateData(match, context));
+        domtemplate.template(clone, getManTemplateData(match, context),
+                { stack: 'help_man.html' });
         return clone;
       }
 
       var parent = util.dom.createElement(context.document, 'div');
       if (!args.search) {
         parent.appendChild(helpIntroTemplate.cloneNode(true));
       }
       parent.appendChild(helpListTemplate.cloneNode(true));
-      new Templater().processNode(parent, getListTemplateData(args, context));
+      domtemplate.template(parent, getListTemplateData(args, context),
+              { allowEval: true, stack: 'help_intro.html | help_list.html' });
       return parent;
     }
   };
 
   canon.addCommand(helpCommandSpec);
 };
 
 help.shutdown = function() {
@@ -5238,18 +5239,19 @@ function getManTemplateData(command, con
 /*
  * Copyright 2009-2011 Mozilla Foundation and contributors
  * Licensed under the New BSD license. See LICENSE.txt or:
  * http://opensource.org/licenses/BSD-3-Clause
  */
 
 define('gcli/ui/domtemplate', ['require', 'exports', 'module' ], function(require, exports, module) {
 
-  Components.utils.import("resource:///modules/devtools/Templater.jsm");
-  exports.Templater = Templater;
+  var obj = {};
+  Components.utils.import('resource:///modules/devtools/Templater.jsm', obj);
+  exports.template = obj.template;
 
 });
 define("text!gcli/commands/help.css", [], void 0);
 define("text!gcli/commands/help_intro.html", [], "\n" +
   "<h2>Welcome to GCLI</h2>\n" +
   "\n" +
   "<p>GCLI is an experiment to create a highly usable JavaScript command line for developers.</p>\n" +
   "\n" +
@@ -6136,17 +6138,17 @@ exports.History = History;
 define('gcli/ui/arg_fetch', ['require', 'exports', 'module' , 'gcli/util', 'gcli/types', 'gcli/ui/field', 'gcli/ui/domtemplate', 'text!gcli/ui/arg_fetch.css', 'text!gcli/ui/arg_fetch.html'], function(require, exports, module) {
 var argFetch = exports;
 
 
 var dom = require('gcli/util').dom;
 var Status = require('gcli/types').Status;
 
 var getField = require('gcli/ui/field').getField;
-var Templater = require('gcli/ui/domtemplate').Templater;
+var domtemplate = require('gcli/ui/domtemplate');
 
 var editorCss = require('text!gcli/ui/arg_fetch.css');
 var argFetchHtml = require('text!gcli/ui/arg_fetch.html');
 
 
 /**
  * A widget to display an inline dialog which allows the user to fill out
  * the arguments to a command.
@@ -6165,17 +6167,16 @@ function ArgFetcher(options) {
     throw new Error('No document');
   }
 
   this.element =  dom.createElement(this.document, 'div');
   this.element.className = options.argFetcherClass || 'gcli-argfetch';
   // We cache the fields we create so we can destroy them later
   this.fields = [];
 
-  this.tmpl = new Templater();
   // Populated by template
   this.okElement = null;
 
   // Pull the HTML into the DOM, but don't add it to the document
   if (editorCss != null) {
     this.style = dom.importCss(editorCss, this.document);
   }
 
@@ -6222,17 +6223,18 @@ ArgFetcher.prototype.onCommandChange = f
       // Just the text has changed
       return;
     }
 
     this.fields.forEach(function(field) { field.destroy(); });
     this.fields = [];
 
     var reqEle = this.reqTempl.cloneNode(true);
-    this.tmpl.processNode(reqEle, this);
+    domtemplate.template(reqEle, this,
+            { allowEval: true, stack: 'arg_fetch.html' });
     dom.clearElement(this.element);
     this.element.appendChild(reqEle);
 
     var status = this.requisition.getStatus();
     this.okElement.disabled = (status === Status.VALID);
 
     this.element.style.display = 'block';
   }
@@ -6277,17 +6279,17 @@ ArgFetcher.prototype.getInputFor = funct
 
     // Bug 681894: we add the field as a property of the assignment so that
     // #linkMessageElement() can call 'field.setMessageElement(element)'
     assignment.field = newField;
 
     return newField.element;
   }
   catch (ex) {
-    // This is called from within Templater which can make tracing errors hard
+    // This is called from within template() which can make tracing errors hard
     // so we log here if anything goes wrong
     console.error(ex);
     return '';
   }
 };
 
 /**
  * Called by the template to setup an mutable message field
@@ -7063,17 +7065,17 @@ define('gcli/ui/menu', ['require', 'expo
 
 
 var dom = require('gcli/util').dom;
 
 var Conversion = require('gcli/types').Conversion;
 var Argument = require('gcli/argument').Argument;
 var canon = require('gcli/canon');
 
-var Templater = require('gcli/ui/domtemplate').Templater;
+var domtemplate = require('gcli/ui/domtemplate');
 
 var menuCss = require('text!gcli/ui/menu.css');
 var menuHtml = require('text!gcli/ui/menu.html');
 
 
 /**
  * Menu is a display of the commands that are possible given the state of a
  * requisition.
@@ -7146,17 +7148,17 @@ Menu.prototype.show = function(items, er
   this.items = items;
 
   if (this.error == null && this.items.length === 0) {
     this.element.style.display = 'none';
     return;
   }
 
   var options = this.optTempl.cloneNode(true);
-  new Templater().processNode(options, this);
+  domtemplate.template(options, this, { allowEval: true, stack: 'menu.html' });
 
   dom.clearElement(this.element);
   this.element.appendChild(options);
 
   this.element.style.display = 'block';
 };
 
 /**