- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / common / extensions / docs / examples / extensions / benchmark / jst / jsevalcontext.js
1 // Copyright 2006 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12 // implied. See the License for the specific language governing
13 // permissions and limitations under the License.
14 /**
15  * Author: Steffen Meschkat <mesch@google.com>
16  *
17  * @fileoverview This class is used to evaluate expressions in a local
18  * context. Used by JstProcessor.
19  */
20
21
22 /**
23  * Names of special variables defined by the jstemplate evaluation
24  * context. These can be used in js expression in jstemplate
25  * attributes.
26  */
27 var VAR_index = '$index';
28 var VAR_count = '$count';
29 var VAR_this = '$this';
30 var VAR_context = '$context';
31 var VAR_top = '$top';
32
33
34 /**
35  * The name of the global variable which holds the value to be returned if
36  * context evaluation results in an error. 
37  * Use JsEvalContext.setGlobal(GLOB_default, value) to set this.
38  */
39 var GLOB_default = '$default';
40
41
42 /**
43  * Un-inlined literals, to avoid object creation in IE6. TODO(mesch):
44  * So far, these are only used here, but we could use them thoughout
45  * the code and thus move them to constants.js.
46  */
47 var CHAR_colon = ':';
48 var REGEXP_semicolon = /\s*;\s*/;
49
50
51 /**
52  * See constructor_()
53  * @param {Object|null} opt_data
54  * @param {Object} opt_parent
55  * @constructor
56  */
57 function JsEvalContext(opt_data, opt_parent) {
58   this.constructor_.apply(this, arguments);
59 }
60
61 /**
62  * Context for processing a jstemplate. The context contains a context
63  * object, whose properties can be referred to in jstemplate
64  * expressions, and it holds the locally defined variables.
65  *
66  * @param {Object|null} opt_data The context object. Null if no context.
67  *
68  * @param {Object} opt_parent The parent context, from which local
69  * variables are inherited. Normally the context object of the parent
70  * context is the object whose property the parent object is. Null for the
71  * context of the root object.
72  */
73 JsEvalContext.prototype.constructor_ = function(opt_data, opt_parent) {
74   var me = this;
75
76   /**
77    * The context for variable definitions in which the jstemplate
78    * expressions are evaluated. Other than for the local context,
79    * which replaces the parent context, variable definitions of the
80    * parent are inherited. The special variable $this points to data_.
81    *
82    * If this instance is recycled from the cache, then the property is
83    * already initialized.
84    *
85    * @type {Object}
86    */
87   if (!me.vars_) {
88     me.vars_ = {};
89   }
90   if (opt_parent) {
91     // If there is a parent node, inherit local variables from the
92     // parent.
93     copyProperties(me.vars_, opt_parent.vars_);
94   } else {
95     // If a root node, inherit global symbols. Since every parent
96     // chain has a root with no parent, global variables will be
97     // present in the case above too. This means that globals can be
98     // overridden by locals, as it should be.
99     copyProperties(me.vars_, JsEvalContext.globals_);
100   }
101
102   /**
103    * The current context object is assigned to the special variable
104    * $this so it is possible to use it in expressions.
105    * @type Object
106    */
107   me.vars_[VAR_this] = opt_data;
108
109   /**
110    * The entire context structure is exposed as a variable so it can be
111    * passed to javascript invocations through jseval.
112    */
113   me.vars_[VAR_context] = me;
114
115   /**
116    * The local context of the input data in which the jstemplate
117    * expressions are evaluated. Notice that this is usually an Object,
118    * but it can also be a scalar value (and then still the expression
119    * $this can be used to refer to it). Notice this can even be value,
120    * undefined or null. Hence, we have to protect jsexec() from using
121    * undefined or null, yet we want $this to reflect the true value of
122    * the current context. Thus we assign the original value to $this,
123    * above, but for the expression context we replace null and
124    * undefined by the empty string.
125    *
126    * @type {Object|null}
127    */
128   me.data_ = getDefaultObject(opt_data, STRING_empty);
129
130   if (!opt_parent) {
131     // If this is a top-level context, create a variable reference to the data
132     // to allow for  accessing top-level properties of the original context
133     // data from child contexts.
134     me.vars_[VAR_top] = me.data_;
135   }
136 };
137
138
139 /**
140  * A map of globally defined symbols. Every instance of JsExprContext
141  * inherits them in its vars_.
142  * @type Object
143  */
144 JsEvalContext.globals_ = {}
145
146
147 /**
148  * Sets a global symbol. It will be available like a variable in every
149  * JsEvalContext instance. This is intended mainly to register
150  * immutable global objects, such as functions, at load time, and not
151  * to add global data at runtime. I.e. the same objections as to
152  * global variables in general apply also here. (Hence the name
153  * "global", and not "global var".)
154  * @param {string} name
155  * @param {Object|null} value
156  */
157 JsEvalContext.setGlobal = function(name, value) {
158   JsEvalContext.globals_[name] = value;
159 };
160
161
162 /**
163  * Set the default value to be returned if context evaluation results in an 
164  * error. (This can occur if a non-existent value was requested). 
165  */
166 JsEvalContext.setGlobal(GLOB_default, null);
167
168
169 /**
170  * A cache to reuse JsEvalContext instances. (IE6 perf)
171  *
172  * @type Array.<JsEvalContext>
173  */
174 JsEvalContext.recycledInstances_ = [];
175
176
177 /**
178  * A factory to create a JsEvalContext instance, possibly reusing
179  * one from recycledInstances_. (IE6 perf)
180  *
181  * @param {Object} opt_data
182  * @param {JsEvalContext} opt_parent
183  * @return {JsEvalContext}
184  */
185 JsEvalContext.create = function(opt_data, opt_parent) {
186   if (jsLength(JsEvalContext.recycledInstances_) > 0) {
187     var instance = JsEvalContext.recycledInstances_.pop();
188     JsEvalContext.call(instance, opt_data, opt_parent);
189     return instance;
190   } else {
191     return new JsEvalContext(opt_data, opt_parent);
192   }
193 };
194
195
196 /**
197  * Recycle a used JsEvalContext instance, so we can avoid creating one
198  * the next time we need one. (IE6 perf)
199  *
200  * @param {JsEvalContext} instance
201  */
202 JsEvalContext.recycle = function(instance) {
203   for (var i in instance.vars_) {
204     // NOTE(mesch): We avoid object creation here. (IE6 perf)
205     delete instance.vars_[i];
206   }
207   instance.data_ = null;
208   JsEvalContext.recycledInstances_.push(instance);
209 };
210
211
212 /**
213  * Executes a function created using jsEvalToFunction() in the context
214  * of vars, data, and template.
215  *
216  * @param {Function} exprFunction A javascript function created from
217  * a jstemplate attribute value.
218  *
219  * @param {Element} template DOM node of the template.
220  *
221  * @return {Object|null} The value of the expression from which
222  * exprFunction was created in the current js expression context and
223  * the context of template.
224  */
225 JsEvalContext.prototype.jsexec = function(exprFunction, template) {
226   try {
227     return exprFunction.call(template, this.vars_, this.data_);
228   } catch (e) {
229     log('jsexec EXCEPTION: ' + e + ' at ' + template +
230         ' with ' + exprFunction);
231     return JsEvalContext.globals_[GLOB_default];
232   }
233 };
234
235
236 /**
237  * Clones the current context for a new context object. The cloned
238  * context has the data object as its context object and the current
239  * context as its parent context. It also sets the $index variable to
240  * the given value. This value usually is the position of the data
241  * object in a list for which a template is instantiated multiply.
242  *
243  * @param {Object} data The new context object.
244  *
245  * @param {number} index Position of the new context when multiply
246  * instantiated. (See implementation of jstSelect().)
247  * 
248  * @param {number} count The total number of contexts that were multiply
249  * instantiated. (See implementation of jstSelect().)
250  *
251  * @return {JsEvalContext}
252  */
253 JsEvalContext.prototype.clone = function(data, index, count) {
254   var ret = JsEvalContext.create(data, this);
255   ret.setVariable(VAR_index, index);
256   ret.setVariable(VAR_count, count);
257   return ret;
258 };
259
260
261 /**
262  * Binds a local variable to the given value. If set from jstemplate
263  * jsvalue expressions, variable names must start with $, but in the
264  * API they only have to be valid javascript identifier.
265  *
266  * @param {string} name
267  *
268  * @param {Object?} value
269  */
270 JsEvalContext.prototype.setVariable = function(name, value) {
271   this.vars_[name] = value;
272 };
273
274
275 /**
276  * Returns the value bound to the local variable of the given name, or
277  * undefined if it wasn't set. There is no way to distinguish a
278  * variable that wasn't set from a variable that was set to
279  * undefined. Used mostly for testing.
280  *
281  * @param {string} name
282  *
283  * @return {Object?} value
284  */
285 JsEvalContext.prototype.getVariable = function(name) {
286   return this.vars_[name];
287 };
288
289
290 /**
291  * Evaluates a string expression within the scope of this context
292  * and returns the result.
293  *
294  * @param {string} expr A javascript expression
295  * @param {Element} opt_template An optional node to serve as "this"
296  *
297  * @return {Object?} value
298  */
299 JsEvalContext.prototype.evalExpression = function(expr, opt_template) {
300   var exprFunction = jsEvalToFunction(expr);
301   return this.jsexec(exprFunction, opt_template);
302 };
303
304
305 /**
306  * Uninlined string literals for jsEvalToFunction() (IE6 perf).
307  */
308 var STRING_a = 'a_';
309 var STRING_b = 'b_';
310 var STRING_with = 'with (a_) with (b_) return ';
311
312
313 /**
314  * Cache for jsEvalToFunction results.
315  * @type Object
316  */
317 JsEvalContext.evalToFunctionCache_ = {};
318
319
320 /**
321  * Evaluates the given expression as the body of a function that takes
322  * vars and data as arguments. Since the resulting function depends
323  * only on expr, we cache the result so we save some Function
324  * invocations, and some object creations in IE6.
325  *
326  * @param {string} expr A javascript expression.
327  *
328  * @return {Function} A function that returns the value of expr in the
329  * context of vars and data.
330  */
331 function jsEvalToFunction(expr) {
332   if (!JsEvalContext.evalToFunctionCache_[expr]) {
333     try {
334       // NOTE(mesch): The Function constructor is faster than eval().
335       JsEvalContext.evalToFunctionCache_[expr] =
336         new Function(STRING_a, STRING_b, STRING_with + expr);
337     } catch (e) {
338       log('jsEvalToFunction (' + expr + ') EXCEPTION ' + e);
339     }
340   }
341   return JsEvalContext.evalToFunctionCache_[expr];
342 }
343
344
345 /**
346  * Evaluates the given expression to itself. This is meant to pass
347  * through string attribute values.
348  *
349  * @param {string} expr
350  *
351  * @return {string}
352  */
353 function jsEvalToSelf(expr) {
354   return expr;
355 }
356
357
358 /**
359  * Parses the value of the jsvalues attribute in jstemplates: splits
360  * it up into a map of labels and expressions, and creates functions
361  * from the expressions that are suitable for execution by
362  * JsEvalContext.jsexec(). All that is returned as a flattened array
363  * of pairs of a String and a Function.
364  *
365  * @param {string} expr
366  *
367  * @return {Array}
368  */
369 function jsEvalToValues(expr) {
370   // TODO(mesch): It is insufficient to split the values by simply
371   // finding semi-colons, as the semi-colon may be part of a string
372   // constant or escaped.
373   var ret = [];
374   var values = expr.split(REGEXP_semicolon);
375   for (var i = 0, I = jsLength(values); i < I; ++i) {
376     var colon = values[i].indexOf(CHAR_colon);
377     if (colon < 0) {
378       continue;
379     }
380     var label = stringTrim(values[i].substr(0, colon));
381     var value = jsEvalToFunction(values[i].substr(colon + 1));
382     ret.push(label, value);
383   }
384   return ret;
385 }
386
387
388 /**
389  * Parses the value of the jseval attribute of jstemplates: splits it
390  * up into a list of expressions, and creates functions from the
391  * expressions that are suitable for execution by
392  * JsEvalContext.jsexec(). All that is returned as an Array of
393  * Function.
394  *
395  * @param {string} expr
396  *
397  * @return {Array.<Function>}
398  */
399 function jsEvalToExpressions(expr) {
400   var ret = [];
401   var values = expr.split(REGEXP_semicolon);
402   for (var i = 0, I = jsLength(values); i < I; ++i) {
403     if (values[i]) {
404       var value = jsEvalToFunction(values[i]);
405       ret.push(value);
406     }
407   }
408   return ret;
409 }