@nuxeo/nuxeo-ui-elements
Version:
Nuxeo UI Web Components.
1,514 lines (1,417 loc) • 138 kB
JavaScript
/**
* @license
* Copyright 2013 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Interpreting JavaScript in JavaScript.
* @author fraser@google.com (Neil Fraser)
*/
'use strict';
import { Parser as acorn } from 'acorn';
/**
* Create a new interpreter.
* @param {string|!Object} code Raw JavaScript text or AST.
* @param {Function=} opt_initFunc Optional initialization function. Used to
* define APIs. When called it is passed the interpreter object and the
* global scope object.
* @constructor
*/
var Interpreter = function(code, opt_initFunc) {
if (typeof code === 'string') {
code = acorn.parse(code, Interpreter.PARSE_OPTIONS);
}
// Get a handle on Acorn's node_t object.
this.nodeConstructor = code.constructor;
// Clone the root 'Program' node so that the AST may be modified.
var ast = new this.nodeConstructor({ options: {} });
for (var prop in code) {
ast[prop] = prop === 'body' ? code[prop].slice() : code[prop];
}
this.ast = ast;
this.initFunc_ = opt_initFunc;
this.paused_ = false;
this.polyfills_ = [];
// Unique identifier for native functions. Used in serialization.
this.functionCounter_ = 0;
// Map node types to our step function names; a property lookup is faster
// than string concatenation with "step" prefix.
this.stepFunctions_ = Object.create(null);
var stepMatch = /^step([A-Z]\w*)$/;
var m;
for (var methodName in this) {
if (typeof this[methodName] === 'function' && (m = methodName.match(stepMatch))) {
this.stepFunctions_[m[1]] = this[methodName].bind(this);
}
}
// Create and initialize the global scope.
this.globalScope = this.createScope(this.ast, null);
this.globalObject = this.globalScope.object;
// Run the polyfills.
this.ast = acorn.parse(this.polyfills_.join('\n'), Interpreter.PARSE_OPTIONS);
this.polyfills_ = undefined; // Allow polyfill strings to garbage collect.
Interpreter.stripLocations_(this.ast, undefined, undefined);
var state = new Interpreter.State(this.ast, this.globalScope);
state.done = false;
this.stateStack = [state];
this.run();
this.value = undefined;
// Point at the main program.
this.ast = ast;
var state = new Interpreter.State(this.ast, this.globalScope);
state.done = false;
this.stateStack.length = 0;
this.stateStack[0] = state;
// Preserve publicly properties from being pruned/renamed by JS compilers.
// Add others as needed.
this['stateStack'] = this.stateStack;
};
/**
* Completion Value Types.
* @enum {number}
*/
Interpreter.Completion = {
NORMAL: 0,
BREAK: 1,
CONTINUE: 2,
RETURN: 3,
THROW: 4,
};
/**
* @const {!Object} Configuration used for all Acorn parsing.
*/
Interpreter.PARSE_OPTIONS = {
ecmaVersion: 5,
};
/**
* Property descriptor of readonly properties.
*/
Interpreter.READONLY_DESCRIPTOR = {
configurable: true,
enumerable: true,
writable: false,
};
/**
* Property descriptor of non-enumerable properties.
*/
Interpreter.NONENUMERABLE_DESCRIPTOR = {
configurable: true,
enumerable: false,
writable: true,
};
/**
* Property descriptor of readonly, non-enumerable properties.
*/
Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR = {
configurable: true,
enumerable: false,
writable: false,
};
/**
* Property descriptor of variables.
*/
Interpreter.VARIABLE_DESCRIPTOR = {
configurable: false,
enumerable: true,
writable: true,
};
/**
* Unique symbol for indicating that a step has encountered an error, has
* added it to the stack, and will be thrown within the user's program.
* When STEP_ERROR is thrown in the JS-Interpreter, the error can be ignored.
*/
Interpreter.STEP_ERROR = { STEP_ERROR: true };
/**
* Unique symbol for indicating that a reference is a variable on the scope,
* not an object property.
*/
Interpreter.SCOPE_REFERENCE = { SCOPE_REFERENCE: true };
/**
* Unique symbol for indicating, when used as the value of the value
* parameter in calls to setProperty and friends, that the value
* should be taken from the property descriptor instead.
*/
Interpreter.VALUE_IN_DESCRIPTOR = { VALUE_IN_DESCRIPTOR: true };
/**
* Unique symbol for indicating that a RegExp timeout has occurred in a VM.
*/
Interpreter.REGEXP_TIMEOUT = { REGEXP_TIMEOUT: true };
/**
* For cycle detection in array to string and error conversion;
* see spec bug github.com/tc39/ecma262/issues/289
* Since this is for atomic actions only, it can be a class property.
*/
Interpreter.toStringCycles_ = [];
/**
* Node's vm module, if loaded and required.
* @type {Object}
*/
Interpreter.vm = null;
/**
* Code for executing regular expressions in a thread.
*/
Interpreter.WORKER_CODE = [
'onmessage = function(e) {',
'var result;',
'var data = e.data;',
'switch (data[0]) {',
"case 'split':",
// ['split', string, separator, limit]
'result = data[1].split(data[2], data[3]);',
'break;',
"case 'match':",
// ['match', string, regexp]
'result = data[1].match(data[2]);',
'break;',
"case 'search':",
// ['search', string, regexp]
'result = data[1].search(data[2]);',
'break;',
"case 'replace':",
// ['replace', string, regexp, newSubstr]
'result = data[1].replace(data[2], data[3]);',
'break;',
"case 'exec':",
// ['exec', regexp, lastIndex, string]
'var regexp = data[1];',
'regexp.lastIndex = data[2];',
'result = [regexp.exec(data[3]), data[1].lastIndex];',
'break;',
'default:',
"throw Error('Unknown RegExp operation: ' + data[0]);",
'}',
'postMessage(result);',
'};',
];
/**
* Is a value a legal integer for an array length?
* @param {Interpreter.Value} x Value to check.
* @return {number} Zero, or a positive integer if the value can be
* converted to such. NaN otherwise.
*/
Interpreter.legalArrayLength = function(x) {
var n = x >>> 0;
// Array length must be between 0 and 2^32-1 (inclusive).
return n === Number(x) ? n : NaN;
};
/**
* Is a value a legal integer for an array index?
* @param {Interpreter.Value} x Value to check.
* @return {number} Zero, or a positive integer if the value can be
* converted to such. NaN otherwise.
*/
Interpreter.legalArrayIndex = function(x) {
var n = x >>> 0;
// Array index cannot be 2^32-1, otherwise length would be 2^32.
// 0xffffffff is 2^32-1.
return String(n) === String(x) && n !== 0xffffffff ? n : NaN;
};
/**
* Remove start and end values from AST, or set start and end values to a
* constant value. Used to remove highlighting from polyfills and to set
* highlighting in an eval to cover the entire eval expression.
* @param {!Object} node AST node.
* @param {number=} start Starting character of all nodes, or undefined.
* @param {number=} end Ending character of all nodes, or undefined.
* @private
*/
Interpreter.stripLocations_ = function(node, start, end) {
if (start) {
node['start'] = start;
} else {
delete node['start'];
}
if (end) {
node['end'] = end;
} else {
delete node['end'];
}
for (var name in node) {
if (node.hasOwnProperty(name)) {
var prop = node[name];
if (prop && typeof prop === 'object') {
Interpreter.stripLocations_(prop, start, end);
}
}
}
};
/**
* Some pathological regular expressions can take geometric time.
* Regular expressions are handled in one of three ways:
* 0 - throw as invalid.
* 1 - execute natively (risk of unresponsive program).
* 2 - execute in separate thread (not supported by IE 9).
*/
Interpreter.prototype['REGEXP_MODE'] = 2;
/**
* If REGEXP_MODE = 2, the length of time (in ms) to allow a RegExp
* thread to execute before terminating it.
*/
Interpreter.prototype['REGEXP_THREAD_TIMEOUT'] = 1000;
/**
* Flag indicating that a getter function needs to be called immediately.
* @private
*/
Interpreter.prototype.getterStep_ = false;
/**
* Flag indicating that a setter function needs to be called immediately.
* @private
*/
Interpreter.prototype.setterStep_ = false;
/**
* Add more code to the interpreter.
* @param {string|!Object} code Raw JavaScript text or AST.
*/
Interpreter.prototype.appendCode = function(code) {
var state = this.stateStack[0];
if (!state || state.node['type'] !== 'Program') {
throw Error('Expecting original AST to start with a Program node.');
}
if (typeof code === 'string') {
code = acorn.parse(code, Interpreter.PARSE_OPTIONS);
}
if (!code || code['type'] !== 'Program') {
throw Error('Expecting new AST to start with a Program node.');
}
this.populateScope_(code, state.scope);
// Append the new program to the old one.
Array.prototype.push.apply(state.node['body'], code['body']);
state.done = false;
};
/**
* Execute one step of the interpreter.
* @return {boolean} True if a step was executed, false if no more instructions.
*/
Interpreter.prototype.step = function() {
var stack = this.stateStack;
do {
var state = stack[stack.length - 1];
if (!state) {
return false;
}
var node = state.node,
type = node['type'];
if (type === 'Program' && state.done) {
return false;
} else if (this.paused_) {
return true;
}
try {
var nextState = this.stepFunctions_[type](stack, state, node);
} catch (e) {
// Eat any step errors. They have been thrown on the stack.
if (e !== Interpreter.STEP_ERROR) {
// Uh oh. This is a real error in the JS-Interpreter. Rethrow.
throw e;
}
}
if (nextState) {
stack.push(nextState);
}
if (this.getterStep_) {
// Getter from this step was not handled.
throw Error('Getter not supported in this context');
}
if (this.setterStep_) {
// Setter from this step was not handled.
throw Error('Setter not supported in this context');
}
// This may be polyfill code. Keep executing until we arrive at user code.
} while (!node['end']);
return true;
};
/**
* Execute the interpreter to program completion. Vulnerable to infinite loops.
* @return {boolean} True if a execution is asynchronously blocked,
* false if no more instructions.
*/
Interpreter.prototype.run = function() {
while (!this.paused_ && this.step()) {}
return this.paused_;
};
/**
* Initialize the global object with buitin properties and functions.
* @param {!Interpreter.Object} globalObject Global object.
*/
Interpreter.prototype.initGlobal = function(globalObject) {
// Initialize uneditable global properties.
this.setProperty(globalObject, 'NaN', NaN, Interpreter.READONLY_DESCRIPTOR);
this.setProperty(globalObject, 'Infinity', Infinity, Interpreter.READONLY_DESCRIPTOR);
this.setProperty(globalObject, 'undefined', undefined, Interpreter.READONLY_DESCRIPTOR);
this.setProperty(globalObject, 'window', globalObject, Interpreter.READONLY_DESCRIPTOR);
this.setProperty(globalObject, 'this', globalObject); // WEBUI-60: Editable
this.setProperty(globalObject, 'self', globalObject); // Editable.
// Create the objects which will become Object.prototype and
// Function.prototype, which are needed to bootstrap everything else.
this.OBJECT_PROTO = new Interpreter.Object(null);
this.FUNCTION_PROTO = new Interpreter.Object(this.OBJECT_PROTO);
// Initialize global objects.
this.initFunction(globalObject);
this.initObject(globalObject);
// Unable to set globalObject's parent prior (OBJECT did not exist).
// Note that in a browser this would be `Window`, whereas in Node.js it would
// be `Object`. This interpreter is closer to Node in that it has no DOM.
globalObject.proto = this.OBJECT_PROTO;
this.setProperty(globalObject, 'constructor', this.OBJECT, Interpreter.NONENUMERABLE_DESCRIPTOR);
this.initArray(globalObject);
this.initString(globalObject);
this.initBoolean(globalObject);
this.initNumber(globalObject);
this.initDate(globalObject);
this.initRegExp(globalObject);
this.initError(globalObject);
this.initMath(globalObject);
this.initJSON(globalObject);
// Initialize global functions.
var thisInterpreter = this;
var func = this.createNativeFunction(function(x) {
throw EvalError("Can't happen");
}, false);
func.eval = true;
this.setProperty(globalObject, 'eval', func);
this.setProperty(globalObject, 'parseInt', this.createNativeFunction(parseInt, false));
this.setProperty(globalObject, 'parseFloat', this.createNativeFunction(parseFloat, false));
this.setProperty(globalObject, 'isNaN', this.createNativeFunction(isNaN, false));
this.setProperty(globalObject, 'isFinite', this.createNativeFunction(isFinite, false));
var strFunctions = [
[escape, 'escape'],
[unescape, 'unescape'],
[decodeURI, 'decodeURI'],
[decodeURIComponent, 'decodeURIComponent'],
[encodeURI, 'encodeURI'],
[encodeURIComponent, 'encodeURIComponent'],
];
for (var i = 0; i < strFunctions.length; i++) {
var wrapper = (function(nativeFunc) {
return function(str) {
try {
return nativeFunc(str);
} catch (e) {
// decodeURI('%xy') will throw an error. Catch and rethrow.
thisInterpreter.throwException(thisInterpreter.URI_ERROR, e.message);
}
};
})(strFunctions[i][0]);
this.setProperty(
globalObject,
strFunctions[i][1],
this.createNativeFunction(wrapper, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
}
// Preserve publicly properties from being pruned/renamed by JS compilers.
// Add others as needed.
this['OBJECT'] = this.OBJECT;
this['OBJECT_PROTO'] = this.OBJECT_PROTO;
this['FUNCTION'] = this.FUNCTION;
this['FUNCTION_PROTO'] = this.FUNCTION_PROTO;
this['ARRAY'] = this.ARRAY;
this['ARRAY_PROTO'] = this.ARRAY_PROTO;
this['REGEXP'] = this.REGEXP;
this['REGEXP_PROTO'] = this.REGEXP_PROTO;
this['DATE'] = this.DATE;
this['DATE_PROTO'] = this.DATE_PROTO;
// Run any user-provided initialization.
if (this.initFunc_) {
this.initFunc_(this, globalObject);
}
};
/**
* Initialize the Function class.
* @param {!Interpreter.Object} globalObject Global object.
*/
Interpreter.prototype.initFunction = function(globalObject) {
var thisInterpreter = this;
var wrapper;
var identifierRegexp = /^[A-Za-z_$][\w$]*$/;
// Function constructor.
wrapper = function(var_args) {
if (arguments.length) {
var code = String(arguments[arguments.length - 1]);
} else {
var code = '';
}
var argsStr = Array.prototype.slice
.call(arguments, 0, -1)
.join(',')
.trim();
if (argsStr) {
var args = argsStr.split(/\s*,\s*/);
for (var i = 0; i < args.length; i++) {
var name = args[i];
if (!identifierRegexp.test(name)) {
thisInterpreter.throwException(thisInterpreter.SYNTAX_ERROR, 'Invalid function argument: ' + name);
}
}
argsStr = args.join(', ');
}
// Acorn needs to parse code in the context of a function or else `return`
// statements will be syntax errors.
try {
var ast = acorn.parse('(function(' + argsStr + ') {' + code + '})', Interpreter.PARSE_OPTIONS);
} catch (e) {
// Acorn threw a SyntaxError. Rethrow as a trappable error.
thisInterpreter.throwException(thisInterpreter.SYNTAX_ERROR, 'Invalid code: ' + e.message);
}
if (ast['body'].length !== 1) {
// Function('a', 'return a + 6;}; {alert(1);');
thisInterpreter.throwException(thisInterpreter.SYNTAX_ERROR, 'Invalid code in function body.');
}
var node = ast['body'][0]['expression'];
// Note that if this constructor is called as `new Function()` the function
// object created by stepCallExpression and assigned to `this` is discarded.
// Interestingly, the scope for constructed functions is the global scope,
// even if they were constructed in some other scope.
return thisInterpreter.createFunction(node, thisInterpreter.globalScope);
};
this.FUNCTION = this.createNativeFunction(wrapper, true);
this.setProperty(globalObject, 'Function', this.FUNCTION);
// Throw away the created prototype and use the root prototype.
this.setProperty(this.FUNCTION, 'prototype', this.FUNCTION_PROTO, Interpreter.NONENUMERABLE_DESCRIPTOR);
// Configure Function.prototype.
this.setProperty(this.FUNCTION_PROTO, 'constructor', this.FUNCTION, Interpreter.NONENUMERABLE_DESCRIPTOR);
this.FUNCTION_PROTO.nativeFunc = function() {};
this.FUNCTION_PROTO.nativeFunc.id = this.functionCounter_++;
this.setProperty(this.FUNCTION_PROTO, 'length', 0, Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR);
var boxThis = function(value) {
// In non-strict mode `this` must be an object.
if (!(value instanceof Interpreter.Object) && !thisInterpreter.getScope().strict) {
if (value === undefined || value === null) {
// `Undefined` and `null` are changed to the global object.
value = thisInterpreter.globalObject;
} else {
// Primitives must be boxed in non-strict mode.
var box = thisInterpreter.createObjectProto(thisInterpreter.getPrototype(value));
box.data = value;
value = box;
}
}
return value;
};
wrapper = function(thisArg, args) {
var state = thisInterpreter.stateStack[thisInterpreter.stateStack.length - 1];
// Rewrite the current CallExpression state to apply a different function.
state.func_ = this;
// Assign the `this` object.
state.funcThis_ = boxThis(thisArg);
// Bind any provided arguments.
state.arguments_ = [];
if (args !== null && args !== undefined) {
if (args instanceof Interpreter.Object) {
state.arguments_ = thisInterpreter.arrayPseudoToNative(args);
} else {
thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, 'CreateListFromArrayLike called on non-object');
}
}
state.doneExec_ = false;
};
this.setNativeFunctionPrototype(this.FUNCTION, 'apply', wrapper);
wrapper = function(thisArg /*, var_args */) {
var state = thisInterpreter.stateStack[thisInterpreter.stateStack.length - 1];
// Rewrite the current CallExpression state to call a different function.
state.func_ = this;
// Assign the `this` object.
state.funcThis_ = boxThis(thisArg);
// Bind any provided arguments.
state.arguments_ = [];
for (var i = 1; i < arguments.length; i++) {
state.arguments_.push(arguments[i]);
}
state.doneExec_ = false;
};
this.setNativeFunctionPrototype(this.FUNCTION, 'call', wrapper);
this.polyfills_.push(
// Polyfill copied from:
// developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_objects/Function/bind
"Object.defineProperty(Function.prototype, 'bind',",
'{configurable: true, writable: true, value:',
'function(oThis) {',
"if (typeof this !== 'function') {",
"throw TypeError('What is trying to be bound is not callable');",
'}',
'var aArgs = Array.prototype.slice.call(arguments, 1),',
'fToBind = this,',
'fNOP = function() {},',
'fBound = function() {',
'return fToBind.apply(this instanceof fNOP',
'? this',
': oThis,',
'aArgs.concat(Array.prototype.slice.call(arguments)));',
'};',
'if (this.prototype) {',
'fNOP.prototype = this.prototype;',
'}',
'fBound.prototype = new fNOP();',
'return fBound;',
'}',
'});',
'',
);
// Function has no parent to inherit from, so it needs its own mandatory
// toString and valueOf functions.
wrapper = function() {
return String(this);
};
this.setNativeFunctionPrototype(this.FUNCTION, 'toString', wrapper);
this.setProperty(
this.FUNCTION,
'toString',
this.createNativeFunction(wrapper, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
wrapper = function() {
return this.valueOf();
};
this.setNativeFunctionPrototype(this.FUNCTION, 'valueOf', wrapper);
this.setProperty(
this.FUNCTION,
'valueOf',
this.createNativeFunction(wrapper, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
};
/**
* Initialize the Object class.
* @param {!Interpreter.Object} globalObject Global object.
*/
Interpreter.prototype.initObject = function(globalObject) {
var thisInterpreter = this;
var wrapper;
// Object constructor.
wrapper = function(value) {
if (value === undefined || value === null) {
// Create a new object.
if (thisInterpreter.calledWithNew()) {
// Called as `new Object()`.
return this;
} else {
// Called as `Object()`.
return thisInterpreter.createObjectProto(thisInterpreter.OBJECT_PROTO);
}
}
if (!(value instanceof Interpreter.Object)) {
// Wrap the value as an object.
var box = thisInterpreter.createObjectProto(thisInterpreter.getPrototype(value));
box.data = value;
return box;
}
// Return the provided object.
return value;
};
this.OBJECT = this.createNativeFunction(wrapper, true);
// Throw away the created prototype and use the root prototype.
this.setProperty(this.OBJECT, 'prototype', this.OBJECT_PROTO, Interpreter.NONENUMERABLE_DESCRIPTOR);
this.setProperty(this.OBJECT_PROTO, 'constructor', this.OBJECT, Interpreter.NONENUMERABLE_DESCRIPTOR);
this.setProperty(globalObject, 'Object', this.OBJECT);
/**
* Checks if the provided value is null or undefined.
* If so, then throw an error in the call stack.
* @param {Interpreter.Value} value Value to check.
*/
var throwIfNullUndefined = function(value) {
if (value === undefined || value === null) {
thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, "Cannot convert '" + value + "' to object");
}
};
// Static methods on Object.
wrapper = function(obj) {
throwIfNullUndefined(obj);
var props = obj instanceof Interpreter.Object ? obj.properties : obj;
return thisInterpreter.arrayNativeToPseudo(Object.getOwnPropertyNames(props));
};
this.setProperty(
this.OBJECT,
'getOwnPropertyNames',
this.createNativeFunction(wrapper, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
wrapper = function(obj) {
throwIfNullUndefined(obj);
if (obj instanceof Interpreter.Object) {
obj = obj.properties;
}
return thisInterpreter.arrayNativeToPseudo(Object.keys(obj));
};
this.setProperty(
this.OBJECT,
'keys',
this.createNativeFunction(wrapper, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
wrapper = function(proto) {
// Support for the second argument is the responsibility of a polyfill.
if (proto === null) {
return thisInterpreter.createObjectProto(null);
}
if (!(proto instanceof Interpreter.Object)) {
thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, 'Object prototype may only be an Object or null');
}
return thisInterpreter.createObjectProto(proto);
};
this.setProperty(
this.OBJECT,
'create',
this.createNativeFunction(wrapper, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
// Add a polyfill to handle create's second argument.
this.polyfills_.push(
'(function() {',
'var create_ = Object.create;',
'Object.create = function(proto, props) {',
'var obj = create_(proto);',
'props && Object.defineProperties(obj, props);',
'return obj;',
'};',
'})();',
'',
);
wrapper = function(obj, prop, descriptor) {
prop = String(prop);
if (!(obj instanceof Interpreter.Object)) {
thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, 'Object.defineProperty called on non-object');
}
if (!(descriptor instanceof Interpreter.Object)) {
thisInterpreter.throwException(thisInterpreter.TYPE_ERROR, 'Property description must be an object');
}
if (!obj.properties[prop] && obj.preventExtensions) {
thisInterpreter.throwException(
thisInterpreter.TYPE_ERROR,
"Can't define property '" + prop + "', object is not extensible",
);
}
// The polyfill guarantees no inheritance and no getter functions.
// Therefore the descriptor properties map is the native object needed.
thisInterpreter.setProperty(obj, prop, Interpreter.VALUE_IN_DESCRIPTOR, descriptor.properties);
return obj;
};
this.setProperty(
this.OBJECT,
'defineProperty',
this.createNativeFunction(wrapper, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
this.polyfills_.push(
// Flatten the descriptor to remove any inheritance or getter functions.
'(function() {',
'var defineProperty_ = Object.defineProperty;',
'Object.defineProperty = function(obj, prop, d1) {',
'var d2 = {};',
"if ('configurable' in d1) d2.configurable = d1.configurable;",
"if ('enumerable' in d1) d2.enumerable = d1.enumerable;",
"if ('writable' in d1) d2.writable = d1.writable;",
"if ('value' in d1) d2.value = d1.value;",
"if ('get' in d1) d2.get = d1.get;",
"if ('set' in d1) d2.set = d1.set;",
'return defineProperty_(obj, prop, d2);',
'};',
'})();',
"Object.defineProperty(Object, 'defineProperties',",
'{configurable: true, writable: true, value:',
'function(obj, props) {',
'var keys = Object.keys(props);',
'for (var i = 0; i < keys.length; i++) {',
'Object.defineProperty(obj, keys[i], props[keys[i]]);',
'}',
'return obj;',
'}',
'});',
'',
);
wrapper = function(obj, prop) {
if (!(obj instanceof Interpreter.Object)) {
thisInterpreter.throwException(
thisInterpreter.TYPE_ERROR,
'Object.getOwnPropertyDescriptor called on non-object',
);
}
prop = String(prop);
if (!(prop in obj.properties)) {
return undefined;
}
var descriptor = Object.getOwnPropertyDescriptor(obj.properties, prop);
var getter = obj.getter[prop];
var setter = obj.setter[prop];
var pseudoDescriptor = thisInterpreter.createObjectProto(thisInterpreter.OBJECT_PROTO);
if (getter || setter) {
thisInterpreter.setProperty(pseudoDescriptor, 'get', getter);
thisInterpreter.setProperty(pseudoDescriptor, 'set', setter);
} else {
thisInterpreter.setProperty(pseudoDescriptor, 'value', descriptor.value);
thisInterpreter.setProperty(pseudoDescriptor, 'writable', descriptor.writable);
}
thisInterpreter.setProperty(pseudoDescriptor, 'configurable', descriptor.configurable);
thisInterpreter.setProperty(pseudoDescriptor, 'enumerable', descriptor.enumerable);
return pseudoDescriptor;
};
this.setProperty(
this.OBJECT,
'getOwnPropertyDescriptor',
this.createNativeFunction(wrapper, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
wrapper = function(obj) {
throwIfNullUndefined(obj);
return thisInterpreter.getPrototype(obj);
};
this.setProperty(
this.OBJECT,
'getPrototypeOf',
this.createNativeFunction(wrapper, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
wrapper = function(obj) {
return Boolean(obj) && !obj.preventExtensions;
};
this.setProperty(
this.OBJECT,
'isExtensible',
this.createNativeFunction(wrapper, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
wrapper = function(obj) {
if (obj instanceof Interpreter.Object) {
obj.preventExtensions = true;
}
return obj;
};
this.setProperty(
this.OBJECT,
'preventExtensions',
this.createNativeFunction(wrapper, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
// Instance methods on Object.
this.setNativeFunctionPrototype(this.OBJECT, 'toString', Interpreter.Object.prototype.toString);
this.setNativeFunctionPrototype(this.OBJECT, 'toLocaleString', Interpreter.Object.prototype.toString);
this.setNativeFunctionPrototype(this.OBJECT, 'valueOf', Interpreter.Object.prototype.valueOf);
wrapper = function(prop) {
throwIfNullUndefined(this);
if (this instanceof Interpreter.Object) {
return String(prop) in this.properties;
}
// Primitive.
return this.hasOwnProperty(prop);
};
this.setNativeFunctionPrototype(this.OBJECT, 'hasOwnProperty', wrapper);
wrapper = function(prop) {
throwIfNullUndefined(this);
if (this instanceof Interpreter.Object) {
return Object.prototype.propertyIsEnumerable.call(this.properties, prop);
}
// Primitive.
return this.propertyIsEnumerable(prop);
};
this.setNativeFunctionPrototype(this.OBJECT, 'propertyIsEnumerable', wrapper);
wrapper = function(obj) {
while (true) {
// Note, circular loops shouldn't be possible.
obj = thisInterpreter.getPrototype(obj);
if (!obj) {
// No parent; reached the top.
return false;
}
if (obj === this) {
return true;
}
}
};
this.setNativeFunctionPrototype(this.OBJECT, 'isPrototypeOf', wrapper);
};
/**
* Initialize the Array class.
* @param {!Interpreter.Object} globalObject Global object.
*/
Interpreter.prototype.initArray = function(globalObject) {
var thisInterpreter = this;
var wrapper;
// Array constructor.
wrapper = function(var_args) {
if (thisInterpreter.calledWithNew()) {
// Called as `new Array()`.
var newArray = this;
} else {
// Called as `Array()`.
var newArray = thisInterpreter.createArray();
}
var first = arguments[0];
if (arguments.length === 1 && typeof first === 'number') {
if (isNaN(Interpreter.legalArrayLength(first))) {
thisInterpreter.throwException(thisInterpreter.RANGE_ERROR, 'Invalid array length');
}
newArray.properties.length = first;
} else {
for (var i = 0; i < arguments.length; i++) {
newArray.properties[i] = arguments[i];
}
newArray.properties.length = i;
}
return newArray;
};
this.ARRAY = this.createNativeFunction(wrapper, true);
this.ARRAY_PROTO = this.ARRAY.properties['prototype'];
this.setProperty(globalObject, 'Array', this.ARRAY);
// Static methods on Array.
wrapper = function(obj) {
return obj && obj.class === 'Array';
};
this.setProperty(
this.ARRAY,
'isArray',
this.createNativeFunction(wrapper, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
// Instance methods on Array.
this.setProperty(this.ARRAY_PROTO, 'length', 0, { configurable: false, enumerable: false, writable: true });
this.ARRAY_PROTO.class = 'Array';
wrapper = function() {
return Array.prototype.pop.call(this.properties);
};
this.setNativeFunctionPrototype(this.ARRAY, 'pop', wrapper);
wrapper = function(var_args) {
return Array.prototype.push.apply(this.properties, arguments);
};
this.setNativeFunctionPrototype(this.ARRAY, 'push', wrapper);
wrapper = function() {
return Array.prototype.shift.call(this.properties);
};
this.setNativeFunctionPrototype(this.ARRAY, 'shift', wrapper);
wrapper = function(var_args) {
return Array.prototype.unshift.apply(this.properties, arguments);
};
this.setNativeFunctionPrototype(this.ARRAY, 'unshift', wrapper);
wrapper = function() {
Array.prototype.reverse.call(this.properties);
return this;
};
this.setNativeFunctionPrototype(this.ARRAY, 'reverse', wrapper);
wrapper = function(index, howmany /*, var_args*/) {
var list = Array.prototype.splice.apply(this.properties, arguments);
return thisInterpreter.arrayNativeToPseudo(list);
};
this.setNativeFunctionPrototype(this.ARRAY, 'splice', wrapper);
wrapper = function(opt_begin, opt_end) {
var list = Array.prototype.slice.call(this.properties, opt_begin, opt_end);
return thisInterpreter.arrayNativeToPseudo(list);
};
this.setNativeFunctionPrototype(this.ARRAY, 'slice', wrapper);
wrapper = function(opt_separator) {
return Array.prototype.join.call(this.properties, opt_separator);
};
this.setNativeFunctionPrototype(this.ARRAY, 'join', wrapper);
wrapper = function(var_args) {
var list = [];
var length = 0;
// Start by copying the current array.
var iLength = thisInterpreter.getProperty(this, 'length');
for (var i = 0; i < iLength; i++) {
if (thisInterpreter.hasProperty(this, i)) {
var element = thisInterpreter.getProperty(this, i);
list[length] = element;
}
length++;
}
// Loop through all arguments and copy them in.
for (var i = 0; i < arguments.length; i++) {
var value = arguments[i];
if (thisInterpreter.isa(value, thisInterpreter.ARRAY)) {
var jLength = thisInterpreter.getProperty(value, 'length');
for (var j = 0; j < jLength; j++) {
if (thisInterpreter.hasProperty(value, j)) {
list[length] = thisInterpreter.getProperty(value, j);
}
length++;
}
} else {
list[length] = value;
}
}
return thisInterpreter.arrayNativeToPseudo(list);
};
this.setNativeFunctionPrototype(this.ARRAY, 'concat', wrapper);
wrapper = function(searchElement, opt_fromIndex) {
return Array.prototype.indexOf.apply(this.properties, arguments);
};
this.setNativeFunctionPrototype(this.ARRAY, 'indexOf', wrapper);
wrapper = function(searchElement, opt_fromIndex) {
return Array.prototype.lastIndexOf.apply(this.properties, arguments);
};
this.setNativeFunctionPrototype(this.ARRAY, 'lastIndexOf', wrapper);
wrapper = function() {
Array.prototype.sort.call(this.properties);
return this;
};
this.setNativeFunctionPrototype(this.ARRAY, 'sort', wrapper);
// WEBUI-60: make Array.includes() available
wrapper = function(searchElement, opt_fromIndex) {
return Array.prototype.includes.call(this.properties, arguments);
};
this.setNativeFunctionPrototype(this.ARRAY, 'includes', wrapper);
this.polyfills_.push(
// Polyfill copied from:
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/every
"Object.defineProperty(Array.prototype, 'every',",
'{configurable: true, writable: true, value:',
'function(callbackfn, thisArg) {',
"if (!this || typeof callbackfn !== 'function') throw TypeError();",
'var T, k;',
'var O = Object(this);',
'var len = O.length >>> 0;',
'if (arguments.length > 1) T = thisArg;',
'k = 0;',
'while (k < len) {',
'if (k in O && !callbackfn.call(T, O[k], k, O)) return false;',
'k++;',
'}',
'return true;',
'}',
'});',
// Polyfill copied from:
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
"Object.defineProperty(Array.prototype, 'filter',",
'{configurable: true, writable: true, value:',
'function(fun/*, thisArg*/) {',
"if (this === void 0 || this === null || typeof fun !== 'function') throw TypeError();",
'var t = Object(this);',
'var len = t.length >>> 0;',
'var res = [];',
'var thisArg = arguments.length >= 2 ? arguments[1] : void 0;',
'for (var i = 0; i < len; i++) {',
'if (i in t) {',
'var val = t[i];',
'if (fun.call(thisArg, val, i, t)) res.push(val);',
'}',
'}',
'return res;',
'}',
'});',
// Polyfill copied from:
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach
"Object.defineProperty(Array.prototype, 'forEach',",
'{configurable: true, writable: true, value:',
'function(callback, thisArg) {',
"if (!this || typeof callback !== 'function') throw TypeError();",
'var T, k;',
'var O = Object(this);',
'var len = O.length >>> 0;',
'if (arguments.length > 1) T = thisArg;',
'k = 0;',
'while (k < len) {',
'if (k in O) callback.call(T, O[k], k, O);',
'k++;',
'}',
'}',
'});',
// Polyfill copied from:
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/map
"Object.defineProperty(Array.prototype, 'map',",
'{configurable: true, writable: true, value:',
'function(callback, thisArg) {',
"if (!this || typeof callback !== 'function') new TypeError;",
'var T, A, k;',
'var O = Object(this);',
'var len = O.length >>> 0;',
'if (arguments.length > 1) T = thisArg;',
'A = new Array(len);',
'k = 0;',
'while (k < len) {',
'if (k in O) A[k] = callback.call(T, O[k], k, O);',
'k++;',
'}',
'return A;',
'}',
'});',
// Polyfill copied from:
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce
"Object.defineProperty(Array.prototype, 'reduce',",
'{configurable: true, writable: true, value:',
'function(callback /*, initialValue*/) {',
"if (!this || typeof callback !== 'function') throw TypeError();",
'var t = Object(this), len = t.length >>> 0, k = 0, value;',
'if (arguments.length === 2) {',
'value = arguments[1];',
'} else {',
'while (k < len && !(k in t)) k++;',
'if (k >= len) {',
"throw TypeError('Reduce of empty array with no initial value');",
'}',
'value = t[k++];',
'}',
'for (; k < len; k++) {',
'if (k in t) value = callback(value, t[k], k, t);',
'}',
'return value;',
'}',
'});',
// Polyfill copied from:
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/ReduceRight
"Object.defineProperty(Array.prototype, 'reduceRight',",
'{configurable: true, writable: true, value:',
'function(callback /*, initialValue*/) {',
"if (null === this || 'undefined' === typeof this || 'function' !== typeof callback) throw TypeError();",
'var t = Object(this), len = t.length >>> 0, k = len - 1, value;',
'if (arguments.length >= 2) {',
'value = arguments[1];',
'} else {',
'while (k >= 0 && !(k in t)) k--;',
'if (k < 0) {',
"throw TypeError('Reduce of empty array with no initial value');",
'}',
'value = t[k--];',
'}',
'for (; k >= 0; k--) {',
'if (k in t) value = callback(value, t[k], k, t);',
'}',
'return value;',
'}',
'});',
// Polyfill copied from:
// developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/some
"Object.defineProperty(Array.prototype, 'some',",
'{configurable: true, writable: true, value:',
'function(fun/*, thisArg*/) {',
"if (!this || typeof fun !== 'function') throw TypeError();",
'var t = Object(this);',
'var len = t.length >>> 0;',
'var thisArg = arguments.length >= 2 ? arguments[1] : void 0;',
'for (var i = 0; i < len; i++) {',
'if (i in t && fun.call(thisArg, t[i], i, t)) {',
'return true;',
'}',
'}',
'return false;',
'}',
'});',
'(function() {',
'var sort_ = Array.prototype.sort;',
'Array.prototype.sort = function(opt_comp) {',
// Fast native sort.
"if (typeof opt_comp !== 'function') {",
'return sort_.call(this);',
'}',
// Slow bubble sort.
'for (var i = 0; i < this.length; i++) {',
'var changes = 0;',
'for (var j = 0; j < this.length - i - 1; j++) {',
'if (opt_comp(this[j], this[j + 1]) > 0) {',
'var swap = this[j];',
'this[j] = this[j + 1];',
'this[j + 1] = swap;',
'changes++;',
'}',
'}',
'if (!changes) break;',
'}',
'return this;',
'};',
'})();',
"Object.defineProperty(Array.prototype, 'toLocaleString',",
'{configurable: true, writable: true, value:',
'function() {',
'var out = [];',
'for (var i = 0; i < this.length; i++) {',
"out[i] = (this[i] === null || this[i] === undefined) ? '' : this[i].toLocaleString();",
'}',
"return out.join(',');",
'}',
'});',
'',
);
};
/**
* Initialize the String class.
* @param {!Interpreter.Object} globalObject Global object.
*/
Interpreter.prototype.initString = function(globalObject) {
var thisInterpreter = this;
var wrapper;
// String constructor.
wrapper = function(value) {
value = arguments.length ? String(value) : '';
if (thisInterpreter.calledWithNew()) {
// Called as `new String()`.
this.data = value;
return this;
} else {
// Called as `String()`.
return value;
}
};
this.STRING = this.createNativeFunction(wrapper, true);
this.setProperty(globalObject, 'String', this.STRING);
// Static methods on String.
this.setProperty(
this.STRING,
'fromCharCode',
this.createNativeFunction(String.fromCharCode, false),
Interpreter.NONENUMERABLE_DESCRIPTOR,
);
// Instance methods on String.
// Methods with exclusively primitive arguments.
var functions = [
'charAt',
'charCodeAt',
'concat',
'indexOf',
'lastIndexOf',
'slice',
'substr',
'substring',
'toLocaleLowerCase',
'toLocaleUpperCase',
'toLowerCase',
'toUpperCase',
'trim',
];
for (var i = 0; i < functions.length; i++) {
this.setNativeFunctionPrototype(this.STRING, functions[i], String.prototype[functions[i]]);
}
wrapper = function(compareString, locales, options) {
locales = locales ? thisInterpreter.pseudoToNative(locales) : undefined;
options = options ? thisInterpreter.pseudoToNative(options) : undefined;
return String(this).localeCompare(compareString, locales, options);
};
this.setNativeFunctionPrototype(this.STRING, 'localeCompare', wrapper);
wrapper = function(separator, limit, callback) {
var string = String(this);
limit = limit ? Number(limit) : undefined;
// Example of catastrophic split RegExp:
// 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaac'.split(/^(a+)+b/)
if (thisInterpreter.isa(separator, thisInterpreter.REGEXP)) {
separator = separator.data;
thisInterpreter.maybeThrowRegExp(separator, callback);
if (thisInterpreter['REGEXP_MODE'] === 2) {
if (Interpreter.vm) {
// Run split in vm.
var sandbox = {
string: string,
separator: separator,
limit: limit,
};
var code = 'string.split(separator, limit)';
var jsList = thisInterpreter.vmCall(code, sandbox, separator, callback);
if (jsList !== Interpreter.REGEXP_TIMEOUT) {
callback(thisInterpreter.arrayNativeToPseudo(jsList));
}
} else {
// Run split in separate thread.
var splitWorker = thisInterpreter.createWorker();
var pid = thisInterpreter.regExpTimeout(separator, splitWorker, callback);
splitWorker.onmessage = function(e) {
clearTimeout(pid);
callback(thisInterpreter.arrayNativeToPseudo(e.data));
};
splitWorker.postMessage(['split', string, separator, limit]);
}
return;
}
}
// Run split natively.
var jsList = string.split(separator, limit);
callback(thisInterpreter.arrayNativeToPseudo(jsList));
};
this.setAsyncFunctionPrototype(this.STRING, 'split', wrapper);
wrapper = function(regexp, callback) {
var string = String(this);
if (thisInterpreter.isa(regexp, thisInterpreter.REGEXP)) {
regexp = regexp.data;
} else {
regexp = new RegExp(regexp);
}
// Example of catastrophic match RegExp:
// 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaac'.match(/^(a+)+b/)
thisInterpreter.maybeThrowRegExp(regexp, callback);
if (thisInterpreter['REGEXP_MODE'] === 2) {
if (Interpreter.vm) {
// Run match in vm.
var sandbox = {
string: string,
regexp: regexp,
};
var code = 'string.match(regexp)';
var m = thisInterpreter.vmCall(code, sandbox, regexp, callback);
if (m !== Interpreter.REGEXP_TIMEOUT) {
callback(m && thisInterpreter.arrayNativeToPseudo(m));
}
} else {
// Run match in separate thread.
var matchWorker = thisInterpreter.createWorker();
var pid = thisInterpreter.regExpTimeout(regexp, matchWorker, callback);
matchWorker.onmessage = function(e) {
clearTimeout(pid);
callback(e.data && thisInterpreter.arrayNativeToPseudo(e.data));
};
matchWorker.postMessage(['match', string, regexp]);
}
return;
}
// Run match natively.
var m = string.match(regexp);
callback(m && thisInterpreter.arrayNativeToPseudo(m));
};
this.setAsyncFunctionPrototype(this.STRING, 'match', wrapper);
wrapper = function(regexp, callback) {
var string = String(this);
if (thisInterpreter.isa(regexp, thisInterpreter.REGEXP)) {
regexp = regexp.data;
} else {
regexp = new RegExp(regexp);
}
// Example of catastrophic search RegExp:
// 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaac'.search(/^(a+)+b/)
thisInterpreter.maybeThrowRegExp(regexp, callback);
if (thisInterpreter['REGEXP_MODE'] === 2) {
if (Interpreter.vm) {
// Run search in vm.
var sandbox = {
string: string,
regexp: regexp,
};
var code = 'string.search(regexp)';
var n = thisInterpreter.vmCall(code, sandbox, regexp, callback);
if (n !== Interpreter.REGEXP_TIMEOUT) {
callback(n);
}
} else {
// Run search in separate thread.
var searchWorker = thisInterpreter.createWorker();
var pid = thisInterpreter.regExpTimeout(regexp, searchWorker, callback);
searchWorker.onmessage = function(e) {
clearTimeout(pid);
callback(e.data);
};
searchWorker.postMessage(['search', string, regexp]);
}
return;
}
// Run search natively.
callback(string.search(regexp));
};
this.setAsyncFunctionPrototype(this.STRING, 'search', wrapper);
wrapper = function(substr, newSubstr, callback) {
// Support for function replacements is the responsibility of a polyfill.
var string = String(this);
newSubstr = String(newSubstr);
// Example of catastrophic replace RegExp:
// 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaac'.replace(/^(a+)+b/, '')
if (thisInterpreter.isa(substr, thisInterpreter.REGEXP)) {
substr = substr.data;
thisInterpreter.maybeThrowRegExp(substr, callback);
if (thisInterpreter['REGEXP_MODE'] === 2) {
if (Interpreter.vm) {
// Run replace in vm.
var sandbox = {
string: string,
substr: substr,
newSubstr: newSubstr,
};
var code = 'string.replace(substr, newSubstr)';
var str = thisInterpreter.vmCall(code, sandbox, substr, callback);
if (str !== Interpreter.REGEXP_TIMEOUT) {
callback(str);
}
} else {
// Run replace in separate thread.
var replaceWorker = thisInterpreter.createWorker();
var pid = thisInterpreter.regExpTimeout(substr, replaceWorker, callback);
replaceWorker.onmessage = function(e) {
clearTimeout(pid);
callback(e.data);
};
replaceWorker.postMessage(['replace', string, substr, newSubstr]);
}
return;
}
}
// Run replace natively.
callback(string.replace(substr, newSubstr));
};
this.setAsyncFunctionPrototype(this.STRING, 'replace', wrapper);
// Add a polyfill to handle replace's second argument being a function.
this.polyfills_.push(
'(function() {',
'var replace_ = String.prototype.replace;',
'String.prototype.replace = function(substr, newSubstr) {',
"if (typeof newSubstr !== 'function') {",
// string.replace(string|regexp, string)
'return replace_.call(this, substr, newSubstr);',
'}',
'var str = this;',
'if (substr instanceof RegExp) {', // string.replace(regexp, function)
'var subs = [];',
'var m = substr.exec(str);',
'while (m) {',
'm.push(m.index, str);',
'var inject = newSubstr.apply(null, m);',
'subs.push([m.index, m[0].length, inject]);',
'm = substr.global ? substr.exec(str) : null;',
'}',
'for (var i = subs.length - 1; i >= 0; i--) {',
'str = str.substring(0, subs[i][0]) + subs[i][2] + ' + 'str.substring(subs[i][0] + subs[i][1]);',
'}',
'} else {', // string.replace(string, function)
'var i = str.indexOf(substr);',
'if (i !== -1) {',
'var inject = newSubstr(str.substr(i, substr.length), i, str);',
'str = str.substring(0, i) + inject + ' + 'str.substring(i + substr.length);',
'}',
'}',
'return str;',
'};',
'})();',
'',
);
};
/**
* Initialize the Boolean class.
* @param {!Interpreter.Object} globalObject Global object.
*/
Interpreter.prototype.initBoolean = function(globalObject) {
var thisInterpreter = this;
var wrapper;
// Boolean constructor.
wrapper = function(value) {
value = Boolean(value);
if (thisInterpreter.calledWithNew()) {
// Called as `new Boolean()`.
this.data = value;
return this;
} else {
// Called as `Boolean()`.
return value;
}
};
this.BOOLEAN = this.createNativeFunction(wrapper, true);
this.setProperty(globalObject, 'Boolean', this.BOOLEAN);
};
/**
* Initialize the Number class.
* @param {!Interpreter.Object} globalObject Global object.
*/
Interpreter.prototype.initNumber = function(globalObject) {
var thisInterpreter = this;
var wrapper;
// Number constructor.
wrapper = function(value) {
value = arguments.length ? Number(value) : 0;
if (thisInterpreter.calledWithNew()) {
// Called as `new Number()`.
this.data = value;
return this;
} else {
// Called as `Number()`.
return value;
}
};
this.NUMBER = this.createNativeFunction(wrapper, true);
this.setProperty(globalObject, 'Number', this.NUMBER);
var numConsts = ['MAX_VALUE', 'MIN_VALUE', 'NaN', 'NEGATIVE_INFINITY', 'POSITIVE_INFINITY'];
for (var i = 0; i < numConsts.length; i++) {
this.setProperty(this.NUMBER, numConsts[i], Number[numConsts[i]], Interpreter.READONLY_NONENUMERABLE_DESCRIPTOR);
}
// Instance methods on Number.
wrapper = function(fractionDigits) {
try {
return Number(this).toExponential(fractionDigits);
} catch (e) {
// Throws if fractionDigits isn't within 0-20.