Mercurial > caja-test
view js/ext/cajita.js @ 4:cf673c093b61 default tip
paver runserver now shows errors if cajoling failed.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Sun, 07 Jun 2009 20:44:44 -0700 |
parents | 633c9cb05555 |
children |
line wrap: on
line source
// Copyright (C) 2007 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. /** * @fileoverview the Cajita runtime library. * It is written in Javascript, not Cajita, and would be rejected by the Cajita * translator. This module exports two globals:<ol> * <li>"___" for use by the output of the Cajita translator and by some * other untranslated Javascript code. * <li>"cajita" providing some common services to the Cajita programmer. * </ol> * @author erights@gmail.com * @requires console * @provides ___, arraySlice, cajita, dateToISOString, funcBind * @overrides Array, Boolean, Date, Function, Number, Object, RegExp, String * @overrides Error, EvalError, RangeError, ReferenceError, SyntaxError, * TypeError, URIError */ // TODO(erights): All code text in comments should be enclosed in // {@code ...}. // TODO(ihab.awad): Missing tests in CajitaTest.java for the functionality // in this file. // Add a tag to whitelisted builtin constructors so we can check the class // cross-frame. Note that Function is not on the list. Array.typeTag___ = 'Array'; Object.typeTag___ = 'Object'; String.typeTag___ = 'String'; Boolean.typeTag___ = 'Boolean'; Number.typeTag___ = 'Number'; Date.typeTag___ = 'Date'; RegExp.typeTag___ = 'RegExp'; Error.typeTag___ = 'Error'; EvalError.typeTag___ = 'EvalError'; RangeError.typeTag___ = 'RangeError'; ReferenceError.typeTag___ = 'ReferenceError'; SyntaxError.typeTag___ = 'SyntaxError'; TypeError.typeTag___ = 'TypeError'; URIError.typeTag___ = 'URIError'; Object.prototype.proto___ = null; //////////////////////////////////////////////////////////////////////// // Cajita adds the following common Javascript extensions to ES3 // TODO(erights): Move such extensions to a separate extensions.js. //////////////////////////////////////////////////////////////////////// if (Date.prototype.toISOString === void 0) { /** In anticipation of ES3.1 */ Date.prototype.toISOString = function () { function f(n) { return n < 10 ? '0' + n : n; } return (this.getUTCFullYear() + '-' + f(this.getUTCMonth() + 1) + '-' + f(this.getUTCDate()) + 'T' + f(this.getUTCHours()) + ':' + f(this.getUTCMinutes()) + ':' + f(this.getUTCSeconds()) + 'Z'); }; } if (Date.prototype.toJSON === void 0) { /** In anticipation of ES3.1 */ Date.prototype.toJSON = Date.prototype.toISOString; } /** In anticipation of ES4, and because it's useful. */ if (Array.slice === void 0) { Array.slice = function (self, start, end) { return Array.prototype.slice.call(self, start || 0, end || self.length); }; } /** * In anticipation of ES3.1. * <p> * Bind this function to <tt>self</tt>, which will serve * as the value of <tt>this</tt> during invocation. Curry on a * partial set of arguments in <tt>var_args</tt>. Return the curried * result as a new function object. * <p> * Note: Like the built-in Function.prototype.call and * Function.prototype.apply, this one is not whitelisted. Instead, * it is provided as it would be in future JavaScripts without * special knowledge of Caja. This allows Caja code using bind() to * work today uncajoled as well. It also suppresses the overriding * of 'bind' by the 'in' test in setStatic(). * <p> * Note that this is distinct from the tamed form of bind() made * available to Cajita code. */ if (Function.prototype.bind === void 0) { Function.prototype.bind = function (self, var_args) { var thisFunc = this; var leftArgs = Array.slice(arguments, 1); function funcBound(var_args) { var args = leftArgs.concat(Array.slice(arguments, 0)); return thisFunc.apply(self, args); } return funcBound; }; } // Use json_sans_eval.js if there is no native implementation. if ('undefined' === typeof JSON) { JSON = { parse: jsonParse }; } // The following may or may not exist in the browser-supplied // global scope; declare as a 'var' to avoid errors when we // check for its existence later. var escape; // cajita.js exports the following names to the Javascript global // namespace. Cajita code can only use the "cajita" object. The "___" // object is for use by code generated by the Cajita translator, and by // Javascript code (such as a powerbox) in the embedding application. var cajita; var ___; var safeJSON; // Explicitly passing in the actual global object to avoid // ReferenceErrors when referring to potentially nonexistent objects // like HTMLDivElement. (function(global) { function ToInt32(alleged_int) { return alleged_int >> 0; } function ToUInt32(alleged_int) { return alleged_int >>> 0; } /** * Returns the first index at which the specimen is found (by * "identical()") or -1 if none, starting at offset i, if present. * If i < 0, the offset is relative to the end of the array. */ function arrayIndexOf(specimen, i) { var len = ToUInt32(this.length); i = ToInt32(i); if (i < 0) { if ((i += len) < 0) { i = 0; } } for (; i < len; ++i) { if (i in this && identical(this[i], specimen)) { return i; } } return -1; } Array.prototype.indexOf = arrayIndexOf; /** * Returns the last index at which the specimen is found (by * "identical()") or -1 if none, starting at offset i, if present. * If i < 0, the offset is relative to the end of the array. */ function arrayLastIndexOf(specimen, i) { var len = ToUInt32(this.length); if (isNaN(i)) { i = len - 1; } else { i = ToInt32(i); if (i < 0) { i += len; if (i < 0) { return -1; } } else if (i >= len) { i = len - 1; } } for (; i >= 0 ; --i) { if (i in this && identical(this[i], specimen)) { return i; } } return -1; } Array.prototype.lastIndexOf = arrayLastIndexOf; //////////////////////////////////////////////////////////////////////// // Some regular expressions checking for specific suffixes. //////////////////////////////////////////////////////////////////////// var endsWith_canDelete___ = /_canDelete___$/; var endsWith_canRead___ = /_canRead___$/; var endsWith_canSet___ = /_canSet___$/; var endsWith___ = /___$/; var endsWith__ = /__$/; //////////////////////////////////////////////////////////////////////// // Some very basic primordial methods //////////////////////////////////////////////////////////////////////// /** * A reliable typeof for use by Cajita code, and by uncajoled code * (like parts of cajita.js) that require a reliable typeof. * <p> * Cajita specifies that <tt>typeof new RegExp("x")</tt> * evaluate to <tt>'object'</tt>. Unfortunately, on some of Cajita's * current target platforms (including at least Safari 3 and Rhino), * it returns <tt>'function'</tt> instead. Since the distinction * between functions and non-functions is crucial to Cajita, we * translate the Cajita <tt>typeof</tt> operator into calls to this * <tt>typeOf</tt> function. */ function typeOf(obj) { var result = typeof obj; if (result !== 'function') { return result; } var ctor = obj.constructor; if (typeof ctor === 'function' && ctor.typeTag___ === 'RegExp' && obj instanceof ctor) { return 'object'; } return 'function'; } var myOriginalHOP = Object.prototype.hasOwnProperty; /** * <tt>hasOwnProp(obj, name)</tt> means what * <tt>obj.hasOwnProperty(name)</tt> would normally mean in an * unmodified Javascript system. */ function hasOwnProp(obj, name) { var t = typeof obj; if (t !== 'object' && t !== 'function') { // If obj is a primitive, Object(obj) still has no own properties. return false; } return myOriginalHOP.call(obj, name); } /** * Are x and y not observably distinguishable? */ function identical(x, y) { if (x === y) { // 0 === -0, but they are not identical return x !== 0 || 1/x === 1/y; } else { // NaN !== NaN, but they are identical. // NaNs are the only non-reflexive value, i.e., if x !== x, // then x is a NaN. return x !== x && y !== y; } } /** * Inherited by non-frozen simple functions, which freezes them and * installs an overriding fastpath CALL___ to themselves. */ function callFault(var_args) { return asFunc(this).apply(USELESS, arguments); } Object.prototype.CALL___ = callFault; //////////////////////////////////////////////////////////////////////// // Diagnostics and condition enforcement //////////////////////////////////////////////////////////////////////// /** * The initial default logging function does nothing. * <p> * Note: JavaScript has no macros, so even in the "does nothing" * case, remember that the arguments are still evaluated. */ function defaultLogger(str, opt_stop) {} var myLogFunc = frozenFunc(defaultLogger); /** * Gets the currently registered logging function. */ function getLogFunc() { return myLogFunc; } /** * Register newLogFunc as the current logging function, to be called * by <tt>___.log(str)</tt> and <tt>___.fail(...)</tt>. * <p> * A logging function is assumed to have the signature * <tt>(str, opt_stop)</tt>, where<ul> * <li><tt>str</tt> is the diagnostic string to be logged, and * <li><tt>opt_stop</tt>, if present and <tt>true</tt>, indicates * that normal flow control is about to be terminated by a * throw. This provides the logging function the opportunity to * terminate normal control flow in its own way, such as by * invoking an undefined method, in order to trigger a Firebug * stacktrace. * </ul> */ function setLogFunc(newLogFunc) { myLogFunc = newLogFunc; } /** * Calls the currently registered logging function. */ function log(str) { myLogFunc(String(str)); } /** * Throw, and optionally log, an error whose message is the * concatenation of the arguments. * <p> * The arguments are converted to strings (presumably by an * implicit call to ".toString()") and appended together to make * the message of the Error that's thrown. */ function fail(var_args) { var message = Array.slice(arguments, 0).join(''); myLogFunc(message, true); throw new Error(message); } /** * Like an assert that can't be turned off. * <p> * Either returns true (on success) or throws (on failure). The * arguments starting with <tt>var_args</tt> are converted to * strings and appended together to make the message of the Error * that's thrown. * <p> * TODO(erights) We may deprecate this in favor of <pre> * test || fail(var_args...) * </pre> or <pre> * if (!test) { fail(var_args...); } * </pre> */ function enforce(test, var_args) { return test || fail.apply({}, Array.slice(arguments, 1)); } /** * Enforces <tt>typeOf(specimen) === typename</tt>, in which case * specimen is returned. * <p> * If not, throws an informative TypeError * <p> * opt_name, if provided, should be a name or description of the * specimen used only to generate friendlier error messages. */ function enforceType(specimen, typename, opt_name) { if (typeOf(specimen) !== typename) { fail('expected ', typename, ' instead of ', typeOf(specimen), ': ', (opt_name || specimen)); } return specimen; } /** * Enforces that specimen is a non-negative integer within the range * of exactly representable consecutive integers, in which case * specimen is returned. * <p> * "Nat" is short for "Natural number". */ function enforceNat(specimen) { enforceType(specimen, 'number'); if (Math.floor(specimen) !== specimen) { fail('Must be integral: ', specimen); } if (specimen < 0) { fail('Must not be negative: ', specimen); } // Could pre-compute precision limit, but probably not faster // enough to be worth it. if (Math.floor(specimen - 1) !== specimen - 1) { fail('Beyond precision limit: ', specimen); } if (Math.floor(specimen - 1) >= specimen) { fail('Must not be infinite: ', specimen); } return specimen; } //////////////////////////////////////////////////////////////////////// // Privileged fault handlers //////////////////////////////////////////////////////////////////////// function debugReference(obj) { switch (typeOf(obj)) { case 'object': { if (obj === null) { return '<null>'; } var constr = directConstructor(obj); return '[' + ((constr && constr.name) || 'Object') + ']'; } default: { return '(' + obj + ':' + typeOf(obj) + ')'; } } } var myKeeper = { toString: function toString() { return '<Logging Keeper>'; }, handleRead: function handleRead(obj, name) { //log('Not readable: (' + debugReference(obj) + ').' + name); return void 0; }, handleCall: function handleCall(obj, name, args) { fail('Not callable: (', debugReference(obj), ').', name); }, handleSet: function handleSet(obj, name, val) { fail('Not settable: (', debugReference(obj), ').', name); }, handleDelete: function handleDelete(obj, name) { fail('Not deletable: (', debugReference(obj), ').', name); } }; Object.prototype.handleRead___ = function handleRead___(name) { var handlerName = name + '_getter___'; if (this[handlerName]) { return this[handlerName](); } return myKeeper.handleRead(this, name); }; Object.prototype.handleCall___ = function handleCall___(name, args) { var handlerName = name + '_handler___'; if (this[handlerName]) { return this[handlerName].call(this, args); } return myKeeper.handleCall(this, name, args); }; Object.prototype.handleSet___ = function handleSet___(name, val) { var handlerName = name + '_setter___'; if (this[handlerName]) { return this[handlerName](val); } return myKeeper.handleSet(this, name, val); }; Object.prototype.handleDelete___ = function handleDelete___(name) { var handlerName = name + '_deleter___'; if (this[handlerName]) { return this[handlerName](); } return myKeeper.handleDelete(this, name); }; //////////////////////////////////////////////////////////////////////// // walking prototype chain, checking JSON containers //////////////////////////////////////////////////////////////////////// /** * Does str end with suffix? */ function endsWith(str, suffix) { enforceType(str, 'string'); enforceType(suffix, 'string'); var d = str.length - suffix.length; return d >= 0 && str.lastIndexOf(suffix) === d; } /** * Returns the 'constructor' property of obj's prototype. * <p> * SECURITY TODO(erights): Analyze the security implications * of exposing this as a property of the cajita object. * <p> * By "obj's prototype", we mean the prototypical object that obj * most directly inherits from, not the value of its 'prototype' * property. We memoize the apparent prototype into 'proto___' to * speed up future queries. * <p> * If obj is a function or not an object, return undefined. */ function directConstructor(obj) { if (obj === null) { return void 0; } if (obj === void 0) { return void 0; } if (typeOf(obj) === 'function') { // Since functions return undefined, // directConstructor() doesn't provide access to the // forbidden Function constructor. return void 0; } obj = Object(obj); var result = null; if (myOriginalHOP.call(obj, 'proto___')) { var proto = obj.proto___; // At this point we know that (typeOf(proto) === 'object') if (proto === null) { return void 0; } result = proto.constructor; // rest of: if (!isPrototypical(result)) if (result.prototype !== proto || typeOf(result) !== 'function') { result = directConstructor(proto); } } else { if (!myOriginalHOP.call(obj, 'constructor')) { // TODO(erights): Detect whether this is a valid constructor // property in the sense that result is a proper answer. If // not, at least give a sensible error, which will be hard to // phrase. result = obj.constructor; } else { var oldConstr = obj.constructor; if (delete obj.constructor) { result = obj.constructor; obj.constructor = oldConstr; } else if (isPrototypical(obj)) { // A difficult case. In Safari, and perhaps according to // ES3, the prototypical object created for the default // value of a function's 'prototype' property has a // non-deletable 'constructor' property. If this is what we // have, then we assume it inherits directly from // Object.prototype, so the result should be Object. log('Guessing the directConstructor of : ' + obj); result = Object; } else { fail('Discovery of direct constructors unsupported when the ', 'constructor property is not deletable: ', oldConstr); } } if (typeOf(result) !== 'function' || !(obj instanceof result)) { fail('Discovery of direct constructors for foreign begotten ', 'objects not implemented on this platform.\n'); } if (result.prototype.constructor === result) { // Memoize, so it'll be faster next time. obj.proto___ = result.prototype; } } return result; } /** * The function category of the whitelisted global constructors * defined in ES is the string name of the constructor, allowing * isInstanceOf() to work cross-frame. Otherwise, the function * category of a function is just the function itself. */ function getFuncCategory(fun) { enforceType(fun, 'function'); if (fun.typeTag___) { return fun.typeTag___; } else { return fun; } } /** * Is <tt>obj</tt> a direct instance of a function whose category is * the same as the category of <tt>ctor</tt>? */ function isDirectInstanceOf(obj, ctor) { var constr = directConstructor(obj); if (constr === void 0) { return false; } return getFuncCategory(constr) === getFuncCategory(ctor); } /** * Is <tt>obj</tt> an instance of a function whose category is * the same as the category of <tt>ctor</tt>? */ function isInstanceOf(obj, ctor) { if (obj instanceof ctor) { return true; } if (isDirectInstanceOf(obj, ctor)) { return true; } // BUG TODO(erights): walk prototype chain. // In the meantime, this will fail should it encounter a // cross-frame instance of a "subclass" of ctor. return false; } /** * A Record is an object whose direct constructor is Object. * <p> * These are the kinds of objects that can be expressed as * an object literal ("<tt>{...}</tt>") in the JSON language. */ function isRecord(obj) { return isDirectInstanceOf(obj, Object); } /** * An Array is an object whose direct constructor is Array. * <p> * These are the kinds of objects that can be expressed as * an array literal ("<tt>[...]</tt>") in the JSON language. */ function isArray(obj) { return isDirectInstanceOf(obj, Array); } /** * A JSON container is a non-prototypical object whose direct * constructor is Object or Array. * <p> * These are the kinds of non-primitive objects that can be * expressed in the JSON language. */ function isJSONContainer(obj) { var constr = directConstructor(obj); if (constr === void 0) { return false; } var typeTag = constr.typeTag___; if (typeTag !== 'Object' && typeTag !== 'Array') { return false; } return !isPrototypical(obj); } /** * If obj is frozen, Cajita code cannot directly assign to * own properties of obj, nor directly add or delete own properties to * obj. * <p> * The status of being frozen is not inherited. If A inherits from * B (i.e., if A's prototype is B), A and B each may or may not be * frozen independently. (Though if B is prototypical, then it must * be frozen.) * <p> * If <tt>typeof obj</tt> is neither 'object' nor 'function', then * it's currently considered frozen. */ function isFrozen(obj) { if (!obj) { return true; } // TODO(erights): Object(<primitive>) wrappers should also be // considered frozen. if (obj.FROZEN___ === obj) { return true; } var t = typeof obj; return t !== 'object' && t !== 'function'; } /** * Mark obj as frozen so that Cajita code cannot directly assign to its * own properties. * <p> * If obj is a function, also freeze obj.prototype. * <p> * This appears as <tt>___.primFreeze(obj)</tt> and is wrapped by * <tt>cajita.freeze(obj)</tt>, which applies only to JSON containers. * It does a shallow freeze, i.e., if record y inherits from record x, * ___.primFreeze(y) will not freeze x. */ function primFreeze(obj) { // Fail silently on undefined, since // (function(){ // var f = Foo; // if (true) { function Foo() {} } // })(); // gets translated to (roughly) // (function(){ // var Foo; // var f = ___.primFreeze(Foo); // if (true) { Foo = function Foo() {}; } // })(); if (isFrozen(obj)) { return obj; } // badFlags are names of properties we need to turn off. // We accumulate these first, so that we're not in the midst of a // for/in loop on obj while we're deleting properties from obj. var badFlags = []; for (var k in obj) { if (endsWith_canSet___.test(k) || endsWith_canDelete___.test(k)) { if (obj[k]) { badFlags.push(k); } } } for (var i = 0; i < badFlags.length; i++) { var flag = badFlags[i]; if (myOriginalHOP.call(obj, flag)) { if (!(delete obj[flag])) { fail('internal: failed delete: ', debugReference(obj), '.', flag); } } if (obj[flag]) { obj[flag] = false; } } obj.FROZEN___ = obj; if (typeOf(obj) === 'function') { if (isFunc(obj)) { grantCall(obj, 'call'); grantCall(obj, 'apply'); obj.CALL___ = obj; } // Do last to avoid possible infinite recursion. if (obj.prototype) { primFreeze(obj.prototype); } } return obj; } /** * Like primFreeze(obj), but applicable only to JSON containers and * (pointlessly but harmlessly) to functions. */ function freeze(obj) { if (!isJSONContainer(obj)) { if (typeOf(obj) === 'function') { enforce(isFrozen(obj), 'Internal: non-frozen function: ' + obj); return obj; } fail('cajita.freeze(obj) applies only to JSON Containers: ', debugReference(obj)); } return primFreeze(obj); } /** * Makes a mutable copy of a JSON container. * <p> * Even if the original is frozen, the copy will still be mutable. * It does a shallow copy, i.e., if record y inherits from record x, * ___.copy(y) will also inherit from x. */ function copy(obj) { if (!isJSONContainer(obj)) { fail('cajita.copy(obj) applies only to JSON Containers: ', debugReference(obj)); } var result = isArray(obj) ? [] : {}; forOwnKeys(obj, frozenFunc(function(k, v) { result[k] = v; })); return result; } /** * A snapshot of a JSON container is a frozen shallow copy of that * container. */ function snapshot(obj) { return primFreeze(copy(obj)); } //////////////////////////////////////////////////////////////////////// // Accessing property attributes //////////////////////////////////////////////////////////////////////// /** * Tests whether the fast-path canRead flag is set. */ function canRead(obj, name) { if (obj === void 0 || obj === null) { return false; } return !!obj[name + '_canRead___']; } /** * Tests whether the fast-path canEnum flag is set. */ function canEnum(obj, name) { if (obj === void 0 || obj === null) { return false; } return !!obj[name + '_canEnum___']; } /** * Tests whether the fast-path canCall flag is set, or grantCall() has been * called. */ function canCall(obj, name) { if (obj === void 0 || obj === null) { return false; } if (obj[name + '_canCall___']) { return true; } if (obj[name + '_grantCall___']) { fastpathCall(obj, name); return true; } return false; } /** * Tests whether the fast-path canSet flag is set, or grantSet() has been * called, on this object itself as an own (non-inherited) attribute. */ function canSet(obj, name) { if (obj === void 0 || obj === null) { return false; } if (obj[name + '_canSet___'] === obj) { return true; } if (obj[name + '_grantSet___'] === obj) { fastpathSet(obj, name); return true; } return false; } /** * Tests whether the fast-path canDelete flag is set, on this * object itself as an own (non-inherited) attribute. */ function canDelete(obj, name) { if (obj === void 0 || obj === null) { return false; } return obj[name + '_canDelete___'] === obj; } /** * Sets the fast-path canRead flag. * <p> * These are called internally to memoize decisions arrived at by * other means. */ function fastpathRead(obj, name) { if (name === 'toString') { fail("internal: Can't fastpath .toString"); } obj[name + '_canRead___'] = obj; } function fastpathEnumOnly(obj, name) { obj[name + '_canEnum___'] = obj; } /** * Simple functions should callable and readable, but methods * should only be callable. */ function fastpathCall(obj, name) { if (name === 'toString') { fail("internal: Can't fastpath .toString"); } if (obj[name + '_canSet___']) { obj[name + '_canSet___'] = false; } if (obj[name + '_grantSet___']) { obj[name + '_grantSet___'] = false; } obj[name + '_canCall___'] = obj; } /** * fastpathSet implies fastpathEnumOnly and fastpathRead. It also * disables the ability to call. */ function fastpathSet(obj, name) { if (name === 'toString') { fail("internal: Can't fastpath .toString"); } if (isFrozen(obj)) { fail("Can't set .", name, ' on frozen (', debugReference(obj), ')'); } fastpathEnumOnly(obj, name); fastpathRead(obj, name); if (obj[name + '_canCall___']) { obj[name + '_canCall___'] = false; } if (obj[name + '_grantCall___']) { obj[name + '_grantCall___'] = false; } obj[name + '_canSet___'] = obj; } /** * fastpathDelete allows delete of a member on a constructed object via * the private API. * <p> * TODO(erights): Having a fastpath flag for this probably doesn't * make sense. */ function fastpathDelete(obj, name) { if (name === 'toString') { fail("internal: Can't fastpath .toString"); } if (isFrozen(obj)) { fail("Can't delete .", name, ' on frozen (', debugReference(obj), ')'); } obj[name + '_canDelete___'] = obj; } /** * The various <tt>grant*</tt> functions are called externally by * Javascript code to express whitelisting taming decisions. */ function grantRead(obj, name) { fastpathRead(obj, name); } // TODO(mikesamuel): none of the other grants grant enum, is "Only" operable? function grantEnumOnly(obj, name) { fastpathEnumOnly(obj, name); } function grantCall(obj, name) { fastpathCall(obj, name); obj[name + '_grantCall___'] = obj; } function grantSet(obj, name) { fastpathSet(obj, name); obj[name + '_grantSet___'] = obj; } function grantDelete(obj, name) { fastpathDelete(obj, name); } //////////////////////////////////////////////////////////////////////// // Classifying functions //////////////////////////////////////////////////////////////////////// function isCtor(constr) { return constr && !!constr.CONSTRUCTOR___; } function isFunc(fun) { return fun && !!fun.FUNC___; } function isXo4aFunc(func) { return func && !!func.XO4A___; } /** * Mark <tt>constr</tt> as a constructor. * <p> * A function is tamed and classified by calling one of * <tt>ctor()</tt>, <tt>method()</tt>, or <tt>func()</tt>. Each * of these checks that the function hasn't already been classified by * any of the others. A function which has not been so classified is an * <i>untamed function</i>. * <p> * If <tt>opt_Sup</tt> is provided, record that const.prototype * inherits from opt_Sup.prototype. This bookkeeping helps * directConstructor(). * <p> * <tt>opt_name</tt>, if provided, should be the name of the constructor * function. Currently, this is used only to generate friendlier * error messages. */ function ctor(constr, opt_Sup, opt_name) { enforceType(constr, 'function', opt_name); if (isFunc(constr)) { fail("Simple functions can't be constructors: ", constr); } if (isXo4aFunc(constr)) { fail("Exophoric functions can't be constructors: ", constr); } constr.CONSTRUCTOR___ = true; if (opt_Sup) { derive(constr, opt_Sup); } if (opt_name) { constr.NAME___ = String(opt_name); } return constr; // translator freezes constructor later } function derive(constr, sup) { sup = asCtor(sup); if (isFrozen(constr)) { fail('Derived constructor already frozen: ', constr); } if (!isFrozen(constr.prototype)) { // Some platforms, like Safari, actually conform to the part // of the ES3 spec which states that the constructor property // of implicitly created prototypical objects are not // deletable. But this prevents the inheritance-walking // algorithm (kludge) in directConstructor from working. Thus, // we set proto___ here so that directConstructor can skip // that impossible case. constr.prototype.proto___ = sup.prototype; } } /** * Enables first-class reification of exophoric functions as * pseudo-functions -- frozen records with call, bind, and apply * functions. */ function reifyIfXo4a(xfunc, opt_name) { if (!isXo4aFunc(xfunc)) { return asFirstClass(xfunc); } var result = { call: frozenFunc(function callXo4a(self, var_args) { if (self === null || self === void 0) { self = USELESS; } return xfunc.apply(self, Array.slice(arguments, 1)); }), apply: frozenFunc(function applyXo4a(self, args) { if (self === null || self === void 0) { self = USELESS; } return xfunc.apply(self, args); }), bind: frozenFunc(function bindXo4a(self, var_args) { var args = arguments; if (self === null || self === void 0) { self = USELESS; args = [self].concat(Array.slice(args, 1)); } return frozenFunc(xfunc.bind.apply(xfunc, args)); }), length: xfunc.length, toString: frozenFunc(function xo4aToString() { return xfunc.toString(); }) }; if (opt_name !== void 0) { result.name = opt_name; } return primFreeze(result); } /** * Marks an anonymous function as exophoric: * the function mentions <tt>this</tt>, * but only accesses the public interface. * <p> * @param opt_name if provided, should be the message name associated * with the method. Currently, this is used only to generate * friendlier error messages. */ function xo4a(func, opt_name) { enforceType(func, 'function', opt_name); if (isCtor(func)) { fail("Internal: Constructors can't be exophora: ", func); } if (isFunc(func)) { fail("Internal: Simple functions can't be exophora: ", func); } func.XO4A___ = true; return primFreeze(func); } /** * Mark fun as a simple function. * <p> * opt_name, if provided, should be the name of the * function. Currently, this is used only to generate friendlier * error messages. */ function func(fun, opt_name) { enforceType(fun, 'function', opt_name); if (isCtor(fun)) { fail("Constructors can't be simple functions: ", fun); } if (isXo4aFunc(fun)) { fail("Exophoric functions can't be simple functions: ", fun); } fun.FUNC___ = true; if (opt_name) { fun.NAME___ = String(opt_name); } return fun; // translator freezes fun later } /** * Mark fun as a simple function and freeze it. */ function frozenFunc(fun, opt_name) { return primFreeze(func(fun, opt_name)); } /** This "Only" form doesn't freeze */ function asCtorOnly(constr) { if (isCtor(constr) || isFunc(constr)) { return constr; } enforceType(constr, 'function'); fail("Untamed functions can't be called as constructors: ", constr); } /** Only constructors and simple functions can be called as constructors */ function asCtor(constr) { return primFreeze(asCtorOnly(constr)); } /** * Only simple functions can be called as simple functions. * <p> * It is now <tt>asFunc</tt>'s responsibility to * <tt>primFreeze(fun)</tt>. */ function asFunc(fun) { if (fun && fun.FUNC___) { // fastpath shortcut if (fun.FROZEN___ === fun) { return fun; } else { return primFreeze(fun); } } enforceType(fun, 'function'); if (isCtor(fun)) { if (fun === Number || fun === String || fun === Boolean) { // TODO(erights): To avoid accidents, <tt>method</tt>, // <tt>func</tt>, and <tt>ctor</tt> each ensure that // these classifications are exclusive. A function can be // classified as in at most one of these categories. However, // some primordial type conversion functions like // <tt>String</tt> need to be invocable both ways, so we // should probably relax this constraint. // <p> // But before we do, we should reexamine other // implications. For example, simple-functions, when called // reflectively by <tt>call</tt> or <tt>apply</tt> (and // therefore <tt>bind</tt>), ignore their first argument, // whereas constructors can be called reflectively by // <tt>call</tt> to do super-initialization on behalf of a // derived constructor. // <p> // Curiously, ES3 also defines function behavior different // from constructor behavior for <tt>Object</tt>, // <tt>Date</tt>, <tt>RegExp</tt>, and <tt>Error</tt>. (Not // sure about <tt>Array</tt>.) We should understand these as // well before introducing a proper solution. return primFreeze(fun); } fail("Constructors can't be called as simple functions: ", fun); } if (isXo4aFunc(fun)) { fail("Exophoric functions can't be called as simple functions: ", fun); } fail("Untamed functions can't be called as simple functions: ", fun); } /** * Is <tt>funoid</tt> an applicator -- a non-function object with a * callable <tt>apply</tt> method, such as a pseudo-function or * disfunction? * <p> * If so, then it can be used as a function in some contexts. */ function isApplicator(funoid) { if (typeof funoid !== 'object') { return false; } if (funoid === null) { return false; } return canCallPub(funoid, 'apply'); } /** * Coerces fun to a genuine simple-function. * <p> * If fun is an applicator, then return a simple-function that invokes * fun's apply method. Otherwise, asFunc(). */ function toFunc(fun) { if (isApplicator(fun)) { return frozenFunc(function applier(var_args) { return callPub(fun, 'apply', [USELESS, Array.slice(arguments, 0)]); }); } return asFunc(fun); } /** * An object is prototypical iff its 'constructor' property points * at a genuine function whose 'prototype' property points back at * it. * <p> * Cajita code cannot access or create prototypical objects since * the 'prototype' property of genuine functions is inaccessible, * and since the transient function used by <tt>beget</tt> to create * inheritance chains does not escape. */ function isPrototypical(obj) { if (typeOf(obj) !== 'object') { return false; } if (obj === null) { return false; } var constr = obj.constructor; if (typeOf(constr) !== 'function') { return false; } return constr.prototype === obj; } /** * Throws an exception if the value is an unmarked function or a * prototypical object. */ function asFirstClass(value) { switch (typeOf(value)) { case 'function': { if (isFunc(value) || isCtor(value)) { if (isFrozen(value)) { return value; } // TODO(metaweta): make this a cajita-uncatchable exception fail('Internal: non-frozen function encountered: ', value); } else if (isXo4aFunc(value)) { // TODO(metaweta): make this a cajita-uncatchable exception // TODO(erights): non-user-hostile error message fail('Internal: toxic exophora encountered: ', value); } else { // TODO(metaweta): make this a cajita-uncatchable exception fail('Internal: toxic function encountered: ', value); } break; } case 'object': { if (value !== null && isPrototypical(value)) { // TODO(metaweta): make this a cajita-uncatchable exception fail('Internal: prototypical object encountered: ', value); } return value; } default: { return value; } } } //////////////////////////////////////////////////////////////////////// // Accessing properties //////////////////////////////////////////////////////////////////////// /** * Can a Cajita client of <tt>obj</tt> read its {@code name} property? * <p> * If the property is unmentionable (i.e. ends in an '__'), then no. * If the property was defined by Cajita code, then yes. If it was * whitelisted, then yes. Or if the property is an own property of * <i>some</i> JSON container, then yes. * <p> * Why "some"? If record y inherits from record x, and 'foo' is an own * property of x, then canReadPub(y, 'foo') must be true. */ function canReadPub(obj, name) { if (typeof name === 'number') { return name in obj; } name = String(name); if (obj === null) { return false; } if (obj === void 0) { return false; } if (obj[name + '_canRead___']) { return true; } if (endsWith__.test(name)) { return false; } if (name === 'toString') { return false; } if (!isJSONContainer(obj)) { return false; } if (!myOriginalHOP.call(obj, name)) { return false; } fastpathRead(obj, name); return true; } function hasOwnPropertyOf(obj, name) { if (typeof name === 'number') { return hasOwnProp(obj, name); } name = String(name); if (obj && obj[name + '_canRead___'] === obj) { return true; } return canReadPub(obj, name) && myOriginalHOP.call(obj, name); } /** * Implements Cajita's <tt><i>name</i> in <i>obj</i></tt> */ function inPub(name, obj) { if (obj === null || obj === void 0) { throw new TypeError('invalid "in" operand: ' + obj); } obj = Object(obj); if (canReadPub(obj, name)) { return true; } if (canCallPub(obj, name)) { return true; } if ((name + '_getter___') in obj) { return true; } if ((name + '_handler___') in obj) { return true; } return false; } /** * Called by Caja code attempting to read a property. * <p> * If it can't then <tt>readPub</tt> returns <tt>undefined</tt> instead. */ function readPub(obj, name) { if (typeof name === 'number') { if (typeof obj === 'string') { // In partial anticipation of ES3.1. // TODO(erights): Once ES3.1 settles, revisit this and // correctly implement the agreed semantics. // Mike Samuel suggests also making it conditional on // (+name) === (name & 0x7fffffff) return obj.charAt(name); } else { return obj[name]; } } name = String(name); if (canReadPub(obj, name)) { return obj[name]; } if (obj === null || obj === void 0) { throw new TypeError("Can't read " + name + " on " + obj); } return obj.handleRead___(name); } /** * If <tt>obj</tt> is an object with a property <tt>name</tt> that * should be objectively readable from Valija, return * <tt>obj[name]</tt>, else <tt>pumpkin</tt>. * <p> * Provides a fastpath for Valija's <tt>read()</tt> function * <tt>$v.r()</tt>. The reason for returning the passed in pumpkin * rather than, for example, <tt>undefined</tt>, is so that the * caller can pass in a known unique value and distinguish it, on * return, from any possible valid value. * <p> * A property should be objectively readable iff<ul> * <li>It is readable from Cajita, and * <li><tt>obj</tt> is not a function, and * <li>either<ul> * <li><tt>name</tt> is an own property of <tt>obj</tt>, or * <li><tt>obj</tt> inherits <tt>name</tt> from an ancestor that * Cajita considers first-class. The one such possibility is * when <tt>obj</tt> is a record inheriting <tt>name</tt> * from another record. (A record is a non-prototypical * object whose directConstructor is Object.) * </ul> * </ul> */ function readOwn(obj, name, pumpkin) { if (typeof obj !== 'object' || !obj) { if (typeOf(obj) !== 'object') { return pumpkin; } } if (typeof name === 'number') { if (myOriginalHOP.call(obj, name)) { return obj[name]; } return pumpkin; } name = String(name); if (obj[name + '_canRead___'] === obj) { return obj[name]; } if (!myOriginalHOP.call(obj, name)) { return pumpkin; } // inline remaining relevant cases from canReadPub if (endsWith__.test(name)) { return pumpkin; } if (name === 'toString') { return pumpkin; } if (!isJSONContainer(obj)) { return pumpkin; } fastpathRead(obj, name); return obj[name]; } /** * Ensure that all the permitsUsed starting at result are forever * safe to allow without runtime checks. */ function enforceStaticPath(result, permitsUsed) { forOwnKeys(permitsUsed, frozenFunc(function(name, subPermits) { // Don't factor out since we don't enforce frozen if permitsUsed // are empty. // TODO(erights): Once we have ES3.1ish attribute control, it // will suffice to enforce that each used property is frozen // independent of the object as a whole. enforce(isFrozen(result), 'Assumed frozen: ', result); if (name === '()') { // TODO(erights): Revisit this case } else { enforce(canReadPub(result, name), 'Assumed readable: ', result, '.', name); if (inPub('()', subPermits)) { enforce(canCallPub(result, name), 'Assumed callable: ', result, '.', name, '()'); } enforceStaticPath(readPub(result, name), subPermits); } })); } /** * Privileged code attempting to read an imported value from a module's * <tt>IMPORTS___</tt>. This function is NOT available to Cajita code. * <p> * This delegates to <tt>readOwn()</tt>, and so will only read * those properties from module_imports that are objectively visible * from both Cajita and Valija. */ function readImport(module_imports, name, opt_permitsUsed) { var pumpkin = {}; var result = readOwn(module_imports, name, pumpkin); if (result === pumpkin) { log('Linkage warning: ' + name + ' not importable'); return void 0; } if (opt_permitsUsed) { enforceStaticPath(result, opt_permitsUsed); } return result; } /** * Can "innocent" code enumerate the named property on this object? * <p> * "Innocent" code is code which we assume to be ignorant of Caja, * not to be actively hostile, but which may be buggy (and * therefore accidentally harmful or exploitable). This * corresponds to legacy code, such as libraries, that we decide * to run untranslated, perhaps hidden or tamed, but which needs * to co-exist smoothly with the Caja runtime. * <p> * An earlier version of canInnocentEnum() filtered out exactly those * names ending with a double underbar. It now filters out exactly * those names ending in a triple underbar. Cajita code can't see names * ending in a double underbar, since existing platforms (like * Firefox) use such names for purposes that should be hidden from * Caja code. However, it is not up to Caja to shield innocent code * from seeing such platform properties. All the magic names Cajita * adds for its own internal bookkeeping end in triple underbar, so * that is all we need to hide from innocent code. */ function canInnocentEnum(obj, name) { name = String(name); if (endsWith___.test(name)) { return false; } return true; } /** * Would a Cajita for/in loop by a client of obj see this name? * <p> * For properties defined in Cajita, this is generally the same as * canReadPub. Otherwise according to whitelisting. */ function canEnumPub(obj, name) { if (obj === null) { return false; } if (obj === void 0) { return false; } name = String(name); if (obj[name + '_canEnum___']) { return true; } if (endsWith__.test(name)) { return false; } if (!isJSONContainer(obj)) { return false; } if (!myOriginalHOP.call(obj, name)) { return false; } fastpathEnumOnly(obj, name); if (name === 'toString') { return true; } fastpathRead(obj, name); return true; } /** * Like canEnumPub, but allows only non-inherited properties. */ function canEnumOwn(obj, name) { name = String(name); if (obj && obj[name + '_canEnum___'] === obj) { return true; } return canEnumPub(obj, name) && myOriginalHOP.call(obj, name); } /** * Returns a new object whose only utility is its identity and (for * diagnostic purposes only) its name. */ function Token(name) { name = String(name); return primFreeze({ toString: frozenFunc(function tokenToString() { return name; }) }); } frozenFunc(Token); /** * Inside a <tt>cajita.forOwnKeys()</tt>, or <tt>cajita.forAllKeys()</tt>, the * body function can terminate early, as if with a conventional * <tt>break;</tt>, by doing a <pre>return cajita.BREAK;</pre> */ var BREAK = Token('BREAK'); /** * A unique value that should never be made accessible to untrusted * code, for distinguishing the absence of a result from any * returnable result. * <p> * See makeNewModuleHandler's getLastOutcome(). */ var NO_RESULT = Token('NO_RESULT'); /** * For each sensible key/value pair in obj, call fn with that * pair. * <p> * If obj is an array, then enumerate indexes. Otherwise, enumerate * the canEnumOwn() property names. */ function forOwnKeys(obj, fn) { fn = toFunc(fn); var keys = ownKeys(obj); for (var i = 0; i < keys.length; i++) { if (fn(keys[i], readPub(obj, keys[i])) === BREAK) { return; } } } /** * For each sensible key/value pair in obj, call fn with that * pair. * <p> * If obj is an array, then enumerate indexes. Otherwise, enumerate * the canEnumPub() property names. */ function forAllKeys(obj, fn) { fn = toFunc(fn); var keys = allKeys(obj); for (var i = 0; i < keys.length; i++) { if (fn(keys[i], readPub(obj, keys[i])) === BREAK) { return; } } } /** * Return an array of the publicly readable own keys of obj. * <p> * If obj is an array, then enumerate indexes. Otherwise, enumerate * the canEnumOwn() property names. */ function ownKeys(obj) { var result = []; if (isArray(obj)) { var len = obj.length; for (var i = 0; i < len; i++) { result.push(i); } } else { for (var k in obj) { if (canEnumOwn(obj, k)) { result.push(k); } } if (obj !== void 0 && obj !== null && obj.handleEnum___) { result = result.concat(obj.handleEnum___(true)); } } return result; } /** * Return an array of the publicly readable own and inherited keys of obj. * <p> * If obj is an array, then enumerate indexes. Otherwise, enumerate * the canEnumPub() property names. */ function allKeys(obj) { if (isArray(obj)) { return ownKeys(obj); } else { var result = []; for (var k in obj) { if (canEnumPub(obj, k)) { result.push(k); } } if (obj !== void 0 && obj !== null && obj.handleEnum___) { result = result.concat(obj.handleEnum___(false)); } return result; } } /** * Can this be called as a public method? * <p> * For genuine methods, they are only callable if the canCall * attribute is set. Otherwise, if this property is readable and * holds a simple function, then it's also callable as a function, * which we can memoize. */ function canCallPub(obj, name) { if (obj === null) { return false; } if (obj === void 0) { return false; } name = String(name); if (obj[name + '_canCall___']) { return true; } if (obj[name + '_grantCall___']) { fastpathCall(obj, name); return true; } if (!canReadPub(obj, name)) { return false; } if (endsWith__.test(name)) { return false; } if (name === 'toString') { return false; } var func = obj[name]; if (!isFunc(func) && !isXo4aFunc(func)) { return false; } fastpathCall(obj, name); return true; } /** * A client of obj tries to call one of its methods. */ function callPub(obj, name, args) { name = String(name); if (obj === null || obj === void 0) { throw new TypeError("Can't call " + name + " on " + obj); } if (obj[name + '_canCall___'] || canCallPub(obj, name)) { return obj[name].apply(obj, args); } if (obj.handleCall___) { return obj.handleCall___(name, args); } fail('not callable:', debugReference(obj), '.', name); } /** * Can a client of obj directly assign to its name property? * <p> * If this property is unmentionable (i.e., ends with a '__') or if this * object is frozen, then no. * Else if this is an own property defined by Cajita code, * then yes. If the object is a JSON container, then * yes. Otherwise according to whitelisting decisions. */ function canSetPub(obj, name) { name = String(name); if (canSet(obj, name)) { return true; } if (endsWith__.test(name)) { return false; } if (name === 'valueOf') { return false; } if (name === 'toString') { return false; } return !isFrozen(obj) && isJSONContainer(obj); } /** A client of obj attempts to assign to one of its properties. */ function setPub(obj, name, val) { // asFirstClass() here would be a useful safety check, to prevent // the further propogation of, for example, a leaked toxic // function. However, its security benefit is questionable, and // the check is expensive in this position. // val = asFirstClass(val); if (typeof name === 'number' && // See issue 875 obj instanceof Array && obj.FROZEN___ !== obj) { return obj[name] = val; } name = String(name); if (obj === null || obj === void 0) { throw new TypeError("Can't set " + name + " on " + obj); } if (obj[name + '_canSet___'] === obj) { return obj[name] = val; } else if (canSetPub(obj, name)) { fastpathSet(obj, name); return obj[name] = val; } else { return obj.handleSet___(name, val); } } /** * Can the given constructor have the given static method added to it? * @param {Function} ctor * @param {string} staticMemberName an identifier in the public namespace. */ function canSetStatic(ctor, staticMemberName) { staticMemberName = '' + staticMemberName; if (typeOf(ctor) !== 'function') { log('Cannot set static member of non function', ctor); return false; } if (isFrozen(ctor)) { log('Cannot set static member of frozen function', ctor); return false; } // disallows prototype, call, apply, bind if (staticMemberName in ctor) { log('Cannot override static member ', staticMemberName); return false; } // statics are public if (endsWith__.test(staticMemberName) || staticMemberName === 'valueOf') { log('Illegal static member name ', staticMemberName); return false; } if (staticMemberName === 'toString') { // no diagnostic as this is a normal fault-handling case. return false; } return true; } /** * Sets a static members of a ctor, making sure that it can't be used to * override call/apply/bind and other builtin members of function. * @param {Function} ctor * @param {string} staticMemberName an identifier in the public namespace. * @param staticMemberValue the value of the static member. */ function setStatic(ctor, staticMemberName, staticMemberValue) { staticMemberName = '' + staticMemberName; if (canSetStatic(ctor, staticMemberName)) { ctor[staticMemberName] = staticMemberValue; fastpathEnumOnly(ctor, staticMemberName); fastpathRead(ctor, staticMemberName); } else { ctor.handleSet___(staticMemberName, staticMemberValue); } } /** * Can a client of obj delete the named property? */ function canDeletePub(obj, name) { name = String(name); if (isFrozen(obj)) { return false; } if (endsWith__.test(name)) { return false; } if (name === 'valueOf') { return false; } if (name === 'toString') { return false; } if (isJSONContainer(obj)) { return true; } return false; } /** * A client of obj can only delete a property of obj if obj is a * non-frozen JSON container. */ function deletePub(obj, name) { name = String(name); if (obj === null || obj === void 0) { throw new TypeError("Can't delete " + name + " on " + obj); } if (canDeletePub(obj, name)) { // See deleteFieldEntirely for reasons why we don't cache deletability. return deleteFieldEntirely(obj, name); } else { return obj.handleDelete___(name); } } /** * Deletes a field removing any cached permissions. * @param {object} obj * @param {string} name of field in obj to delete. * @return {boolean} * @throws {Error} if field not deletable or name not in field. * @private */ function deleteFieldEntirely(obj, name) { // Can't cache fastpath delete since deleting the field should remove // all privileges for that field. delete obj[name + '_canRead___']; delete obj[name + '_canEnum___']; delete obj[name + '_canCall___']; delete obj[name + '_grantCall___']; delete obj[name + '_grantSet___']; delete obj[name + '_canSet___']; delete obj[name + '_canDelete___']; return (delete obj[name]) || (fail('not deleted: ', name), false); } //////////////////////////////////////////////////////////////////////// // Other //////////////////////////////////////////////////////////////////////// /** * This returns a frozen array copy of the original array or * array-like object. * <p> * If a Cajita program makes use of <tt>arguments</tt> in any * position other than <tt>arguments.callee</tt>, this is * rewritten to use a frozen array copy of arguments instead. This * way, if Cajita code passes its arguments to someone else, they * are not giving the receiver the rights to access the passing * function nor to modify the parameter variables of the passing * function. */ function args(original) { return primFreeze(Array.slice(original, 0)); } /** * When a <tt>this</tt> value must be provided but nothing is * suitable, provide this useless object instead. */ var USELESS = Token('USELESS'); /** * A call to cajita.manifest(data) is dynamically ignored, but if the * data expression is valid static JSON text, its value is made * statically available to the module loader. */ function manifest(ignored) {} /** Sealer for call stacks as from {@code (new Error).stack}. */ var callStackSealer = makeSealerUnsealerPair(); /** * Receives the exception caught by a user defined catch block. * @param ex a value caught in a try block. * @return a tamed exception. */ function tameException(ex) { try { switch (typeOf(ex)) { case 'object': { if (ex === null) { return null; } if (isInstanceOf(ex, Error)) { // See Ecma-262 S15.11 for the definitions of these properties. var message = ex.message || ex.desc; var stack = ex.stack; var name = ex.constructor && ex.constructor.name; // S15.11.7.9 // Convert to undefined if falsy, or a string otherwise. message = !message ? void 0 : '' + message; stack = !stack ? void 0 : callStackSealer.seal('' + stack); name = !name ? void 0 : '' + name; return primFreeze({ message: message, name: name, stack: stack }); } return '' + ex; } case 'string': case 'number': case 'boolean': { // Immutable. return ex; } case 'undefined': { return (void 0); } case 'function': { // According to Pratap Lakhsman's "JScript Deviations" S2.11 // If the caught object is a function, calling it within the catch // supplies the head of the scope chain as the "this value". The // called function can add properties to this object. This implies // that for code of this shape: // var x; // try { // // ... // } catch (E) { // E(); // return s; // } // The reference to 'x' within the catch is not necessarily to the // local declaration of 'x'; this gives Catch the same performance // problems as with. // We return a different, powerless function instead. return ___.func(function () {}); } default: { log('Unrecognized exception type ' + (typeOf(ex))); return 'Unrecognized exception type ' + (typeOf(ex)); } } } catch (_) { // Can occur if coercion to string fails, or if ex has getters // that fail. This function must never throw an exception // because doing so would cause control to leave a catch block // before the handler fires. log('Exception during exception handling.'); return 'Exception during exception handling.'; } } /** * Makes a new empty object that directly inherits from <tt>proto</tt>. */ function primBeget(proto) { if (proto === null) { fail("Cannot beget from null."); } if (proto === (void 0)) { fail("Cannot beget from undefined."); } function F() {} F.prototype = proto; var result = new F(); result.proto___ = proto; return result; } //////////////////////////////////////////////////////////////////////// // Taming mechanism //////////////////////////////////////////////////////////////////////// /** * Arrange to handle read-faults on <tt>obj[name]</tt> * by calling <tt>getHandler()</tt> as a method on * the faulted object. * <p> * In order for this fault-handler to get control, it's important * that no one does a conflicting <tt>grantRead()</tt>. * FIXME(ben): and fastpathRead()? */ function useGetHandler(obj, name, getHandler) { obj[name + '_getter___'] = getHandler; } /** * Arrange to handle call-faults on <tt>obj[name](args...)</tt> by * calling <tt>applyHandler(args)</tt> as a method on the faulted * object. * <p> * Note that <tt>applyHandler</tt> is called with a single argument, * which is the list of arguments in the original call. * <p> * In order for this fault-handler to get control, it's important * that no one does a conflicting grantCall() or other grants which * imply grantCall(). * FIXME(ben): also fastpath? */ function useApplyHandler(obj, name, applyHandler) { obj[name + '_handler___'] = applyHandler; } /** * Arrange to handle call-faults on <tt>obj[name](args...)</tt> by * calling <tt>callHandler(args...)</tt> as a method on the faulted * object. * <p> * Note that <tt>callHandler</tt> is called with the same arguments * as the original call. * <p> * In order for this fault-handler to get control, it's important * that no one does a conflicting grantCall() or other grants which * imply grantCall(). * FIXME(ben): also fastpath? */ function useCallHandler(obj, name, callHandler) { useApplyHandler(obj, name, function callApplier(args) { return callHandler.apply(this, args); }); } /** * Arrange to handle set-faults on <tt>obj[name] = newValue</tt> by * calling <tt>setHandler(newValue)</tt> as a method on the faulted * object. * <p> * In order for this fault-handler to get control, it's important * that no one does a conflicting grantSet(). * FIXME(ben): also fastpath? */ function useSetHandler(obj, name, setHandler) { obj[name + '_setter___'] = setHandler; } /** * Arrange to handle delete-faults on <tt>delete obj[name]</tt> by * calling <tt>deleteHandler()</tt> as a method on the faulted object. * <p> * In order for this fault-handler to get control, it's important * that no one does a conflicting grantDelete(). * FIXME(ben): also fastpath? */ function useDeleteHandler(obj, name, deleteHandler) { obj[name + '_deleter___'] = deleteHandler; } /** * Whilelist obj[name] as a simple frozen function that can be either * called or read. */ function grantFunc(obj, name) { frozenFunc(obj[name], name); grantCall(obj, name); grantRead(obj, name); } /** * Whitelist proto[name] as a generic exophoric function that can * safely be called with its <tt>this</tt> bound to other objects. * <p> * Since exophoric functions are not first-class, reading * proto[name] returns the corresponding pseudo-function -- a record * with simple-functions for its call, bind, and apply. */ function grantGeneric(proto, name) { var func = xo4a(proto[name], name); grantCall(proto, name); var pseudoFunc = reifyIfXo4a(func, name); useGetHandler(proto, name, function xo4aGetter() { return pseudoFunc; }); } /** * Mark func as exophoric and use it as a virtual generic * exophoric function. * <p> * Since exophoric functions are not first-class, reading * proto[name] returns the corresponding pseudo-function -- a record * with simple-functions for its call, bind, and apply. */ function handleGeneric(obj, name, func) { xo4a(func); useCallHandler(obj, name, func); var pseudoFunc = reifyIfXo4a(func, name); useGetHandler(obj, name, function genericGetter() { return pseudoFunc; }); } /** * Virtually replace proto[name] with a fault-handler * wrapper that first verifies that <tt>this</tt> inherits from * proto. * <p> * When a pre-existing Javascript method may do something unsafe * when applied to a <tt>this</tt> of the wrong type, we need to * provide a fault-handler instead to prevent such mis-application. * <p> * In order for this fault handler to get control, it's important * that no one does an grantCall() or other grants which imply * grantCall(). * FIXME(ben): also fastpath? */ function grantTypedGeneric(proto, name) { var original = proto[name]; handleGeneric(proto, name, function guardedApplier(var_args) { if (!inheritsFrom(this, proto)) { fail("Can't call .", name, ' on a non ', directConstructor(proto), ': ', this); } return original.apply(this, arguments); }); } /** * Virtually replace proto[name] with a fault-handler * wrapper that first verifies that <tt>this</tt> isn't frozen. * <p> * When a pre-existing Javascript method would mutate its object, * we need to provide a fault handler instead to prevent such * mutation from violating Cajita semantics. * <p> * In order for this fault handler to get control, it's important * that no one does an grantCall() or other grants which imply * grantCall(). * FIXME(ben): also fastpath? */ function grantMutator(proto, name) { var original = proto[name]; handleGeneric(proto, name, function nonMutatingApplier(var_args) { if (isFrozen(this)) { fail("Can't .", name, ' a frozen object'); } return original.apply(this, arguments); }); } /** * Verifies that regexp is something that can appear as a * parameter to a Javascript method that would use it in a match. * <p> * If it is a RegExp, then this match might mutate it, which must * not be allowed if regexp is frozen. Otherwise it must be a string. */ function enforceMatchable(regexp) { if (isInstanceOf(regexp, RegExp)) { if (isFrozen(regexp)) { fail("Can't match with frozen RegExp: ", regexp); } } else { enforceType(regexp, 'string'); } } /** * A shorthand that happens to be useful here. * <p> * For all i in arg2s: func2(arg1,arg2s[i]). */ function all2(func2, arg1, arg2s) { var len = arg2s.length; for (var i = 0; i < len; i += 1) { func2(arg1, arg2s[i]); } } //////////////////////////////////////////////////////////////////////// // Taming decisions //////////////////////////////////////////////////////////////////////// /// Math all2(grantRead, Math, [ 'E', 'LN10', 'LN2', 'LOG2E', 'LOG10E', 'PI', 'SQRT1_2', 'SQRT2' ]); all2(grantFunc, Math, [ 'abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'random', 'round', 'sin', 'sqrt', 'tan' ]); /// toString function grantToString(proto) { proto.TOSTRING___ = xo4a(proto.toString, 'toString'); } useGetHandler(Object.prototype, 'toString', function toStringGetter() { if (hasOwnProp(this, 'toString') && typeOf(this.toString) === 'function' && !hasOwnProp(this, 'TOSTRING___')) { // This case is a kludge that doesn't work for undiagnosed reasons. // this.TOSTRING___ = xo4a(this.toString, 'toString'); // TODO(erights): This case is a different kludge that needs to // be explained. // TODO(erights): This is probably wrong in that it can leak xo4a. return this.toString; } return reifyIfXo4a(this.TOSTRING___, 'toString'); }); useApplyHandler(Object.prototype, 'toString', function toStringApplier(args) { return this.toString.apply(this, args); }); useSetHandler(Object.prototype, 'toString', function toStringSetter(meth) { if (isFrozen(this)) { return myKeeper.handleSet(this, 'toString', meth); } meth = asFirstClass(meth); this.TOSTRING___ = meth; this.toString = function delegatingToString(var_args) { var args = Array.slice(arguments, 0); if (typeOf(meth) === 'function') { return meth.apply(this, args); } var methApply = readPub(meth, 'apply'); if (typeOf(methApply) === 'function') { return methApply.call(meth, this, args); } var result = Object.toString.call(this); log('Not correctly printed: ' + result); return result; }; return meth; }); useDeleteHandler(Object.prototype, 'toString', function toStringDeleter() { if (isFrozen(this)) { return myKeeper.handleDelete(this, 'toString'); } return (delete this.toString) && (delete this.TOSTRING___); }); /// Object ctor(Object, void 0, 'Object'); grantToString(Object.prototype); all2(grantGeneric, Object.prototype, [ 'toLocaleString', 'valueOf', 'isPrototypeOf' ]); grantRead(Object.prototype, 'length'); handleGeneric(Object.prototype, 'hasOwnProperty', function hasOwnPropertyHandler(name) { return hasOwnPropertyOf(this, name); }); handleGeneric(Object.prototype, 'propertyIsEnumerable', function propertyIsEnumerableHandler(name) { name = String(name); return canEnumPub(this, name); }); /// Function handleGeneric(Function.prototype, 'apply', function applyHandler(self, realArgs) { return toFunc(this).apply(self, realArgs); }); handleGeneric(Function.prototype, 'call', function callHandler(self, var_args) { return toFunc(this).apply(self, Array.slice(arguments, 1)); }); handleGeneric(Function.prototype, 'bind', function bindHandler(self, var_args) { var thisFunc = this; var leftArgs = Array.slice(arguments, 1); function boundHandler(var_args) { var args = leftArgs.concat(Array.slice(arguments, 0)); return callPub(thisFunc, 'apply', [self, args]); } return frozenFunc(boundHandler); }); /// Array ctor(Array, Object, 'Array'); grantFunc(Array, 'slice'); grantToString(Array.prototype); all2(grantTypedGeneric, Array.prototype, [ 'toLocaleString' ]); all2(grantGeneric, Array.prototype, [ 'concat', 'join', 'slice', 'indexOf', 'lastIndexOf' ]); all2(grantMutator, Array.prototype, [ 'pop', 'push', 'reverse', 'shift', 'splice', 'unshift' ]); handleGeneric(Array.prototype, 'sort', function sortHandler(comparator) { if (isFrozen(this)) { fail("Can't sort a frozen array."); } if (comparator) { return Array.prototype.sort.call(this, toFunc(comparator)); } else { return Array.prototype.sort.call(this); } }); /// String ctor(String, Object, 'String'); grantFunc(String, 'fromCharCode'); grantToString(String.prototype); all2(grantTypedGeneric, String.prototype, [ 'toLocaleString', 'indexOf', 'lastIndexOf' ]); all2(grantGeneric, String.prototype, [ 'charAt', 'charCodeAt', 'concat', 'localeCompare', 'slice', 'substr', 'substring', 'toLowerCase', 'toLocaleLowerCase', 'toUpperCase', 'toLocaleUpperCase' ]); handleGeneric(String.prototype, 'match', function matchHandler(regexp) { enforceMatchable(regexp); return this.match(regexp); }); handleGeneric(String.prototype, 'replace', function replaceHandler(searcher, replacement) { enforceMatchable(searcher); if (isFunc(replacement)) { replacement = asFunc(replacement); } else if (isApplicator(replacement)) { replacement = toFunc(replacement); } else { replacement = '' + replacement; } return this.replace(searcher, replacement); }); handleGeneric(String.prototype, 'search', function searchHandler(regexp) { enforceMatchable(regexp); return this.search(regexp); }); handleGeneric(String.prototype, 'split', function splitHandler(separator, limit) { enforceMatchable(separator); return this.split(separator, limit); }); /// Boolean ctor(Boolean, Object, 'Boolean'); grantToString(Boolean.prototype); /// Number ctor(Number, Object, 'Number'); all2(grantRead, Number, [ 'MAX_VALUE', 'MIN_VALUE', 'NaN', 'NEGATIVE_INFINITY', 'POSITIVE_INFINITY' ]); grantToString(Number.prototype); all2(grantTypedGeneric, Number.prototype, [ 'toFixed', 'toExponential', 'toPrecision' ]); /// Date ctor(Date, Object, 'Date'); grantFunc(Date, 'parse'); grantFunc(Date, 'UTC'); grantToString(Date.prototype); all2(grantTypedGeneric, Date.prototype, [ 'toDateString','toTimeString', 'toUTCString', 'toLocaleString', 'toLocaleDateString', 'toLocaleTimeString', 'toISOString', 'getDay', 'getUTCDay', 'getTimezoneOffset', 'getTime', 'getFullYear', 'getUTCFullYear', 'getMonth', 'getUTCMonth', 'getDate', 'getUTCDate', 'getHours', 'getUTCHours', 'getMinutes', 'getUTCMinutes', 'getSeconds', 'getUTCSeconds', 'getMilliseconds', 'getUTCMilliseconds' ]); all2(grantMutator, Date.prototype, [ 'setTime', 'setFullYear', 'setUTCFullYear', 'setMonth', 'setUTCMonth', 'setDate', 'setUTCDate', 'setHours', 'setUTCHours', 'setMinutes', 'setUTCMinutes', 'setSeconds', 'setUTCSeconds', 'setMilliseconds', 'setUTCMilliseconds' ]); /// RegExp ctor(RegExp, Object, 'RegExp'); grantToString(RegExp.prototype); handleGeneric(RegExp.prototype, 'exec', function execHandler(specimen) { if (isFrozen(this)) { fail("Can't .exec a frozen RegExp"); } specimen = String(specimen); // See bug 528 return this.exec(specimen); }); handleGeneric(RegExp.prototype, 'test', function testHandler(specimen) { if (isFrozen(this)) { fail("Can't .test a frozen RegExp"); } specimen = String(specimen); // See bug 528 return this.test(specimen); }); all2(grantRead, RegExp.prototype, [ 'source', 'global', 'ignoreCase', 'multiline', 'lastIndex' ]); /// errors ctor(Error, Object, 'Error'); grantToString(Error.prototype); grantRead(Error.prototype, 'name'); grantRead(Error.prototype, 'message'); ctor(EvalError, Error, 'EvalError'); ctor(RangeError, Error, 'RangeError'); ctor(ReferenceError, Error, 'ReferenceError'); ctor(SyntaxError, Error, 'SyntaxError'); ctor(TypeError, Error, 'TypeError'); ctor(URIError, Error, 'URIError'); var sharedImports; //////////////////////////////////////////////////////////////////////// // Module loading //////////////////////////////////////////////////////////////////////// var myNewModuleHandler; /** * Gets the current module handler. */ function getNewModuleHandler() { return myNewModuleHandler; } /** * Registers a new-module-handler, to be called back when a new * module is loaded. * <p> * This callback mechanism is provided so that translated Cajita * modules can be loaded from a trusted site with the * <script> tag, which runs its script as a statement, not * an expression. The callback is of the form * <tt>newModuleHandler.handle(newModule)</tt>. */ function setNewModuleHandler(newModuleHandler) { myNewModuleHandler = newModuleHandler; } /** * A new-module-handler which returns the new module without * instantiating it. */ var obtainNewModule = freeze({ handle: frozenFunc(function handleOnly(newModule){ return newModule; }) }); /** * Makes and returns a fresh "normal" module handler whose imports * are initialized to a copy of the sharedImports. * <p> * This handles a new module by calling it, passing it the imports * object held in this handler. Successive modules handled by the * same "normal" handler thereby see a simulation of successive * updates to a shared global scope. */ function makeNormalNewModuleHandler() { var imports = copy(sharedImports); var lastOutcome = void 0; return freeze({ getImports: frozenFunc(function getImports() { return imports; }), setImports: frozenFunc(function setImports(newImports) { imports = newImports; }), /** * An outcome is a pair of a success flag and a value. * <p> * If the success flag is true, then the value is the normal * result of calling the module function. If the success flag is * false, then the value is the thrown error by which the module * abruptly terminated. * <p> * An html page is cajoled to a module that runs to completion, * but which reports as its outcome the outcome of its last * script block. In order to reify that outcome and report it * later, the html page initializes moduleResult___ to * NO_RESULT, the last script block is cajoled to set * moduleResult___ to something other than NO_RESULT on success * but to call handleUncaughtException() on * failure, and the html page returns moduleResult___ on * completion. handleUncaughtException() records a failed * outcome. This newModuleHandler's handle() method will not * overwrite an already reported outcome with NO_RESULT, so the * last script-block's outcome will be preserved. */ getLastOutcome: frozenFunc(function getLastOutcome() { return lastOutcome; }), /** * If the last outcome is a success, returns its value; * otherwise <tt>undefined</tt>. */ getLastValue: frozenFunc(function getLastValue() { if (lastOutcome && lastOutcome[0]) { return lastOutcome[1]; } else { return void 0; } }), /** * Runs the newModule's module function. * <p> * Updates the last outcome to report the module function's * reported outcome. Propogate this outcome by terminating in * the same manner. */ handle: frozenFunc(function handle(newModule) { lastOutcome = void 0; try { var result = newModule.instantiate(___, imports); if (result !== NO_RESULT) { lastOutcome = [true, result]; } } catch (ex) { lastOutcome = [false, ex]; } if (lastOutcome) { if (lastOutcome[0]) { return lastOutcome[1]; } else { throw lastOutcome[1]; } } else { return void 0; } }), /** * This emulates HTML5 exception handling for scripts as discussed at * http://code.google.com/p/google-caja/wiki/UncaughtExceptionHandling * and see HtmlCompiler.java for the code that calls this. * @param exception a raw exception. Since {@code throw} can raise any * value, exception could be any value accessible to cajoled code, or * any value thrown by an API imported by cajoled code. * @param onerror the value of the raw reference "onerror" in top level * cajoled code. This will likely be undefined much of the time, but * could be anything. If it is a func, it can be called with * three strings (message, source, lineNum) as the * {@code window.onerror} event handler. * @param {string} source a URI describing the source file from which the * error originated. * @param {string} lineNum the approximate line number in source at which * the error originated. */ handleUncaughtException: function handleUncaughtException(exception, onerror, source, lineNum) { lastOutcome = [false, exception]; // Cause exception to be rethrown if it is uncatchable. tameException(exception); var message = 'unknown'; if ('object' === typeOf(exception) && exception !== null) { message = String(exception.message || exception.desc || message); } // If we wanted to provide a hook for containers to get uncaught // exceptions, it would go here before onerror is invoked. // See the HTML5 discussion for the reasons behind this rule. if (isApplicator(onerror)) { onerror = toFunc(onerror); } var shouldReport = ( isFunc(onerror) ? onerror.CALL___(message, String(source), String(lineNum)) : onerror !== null); if (shouldReport !== false) { cajita.log(source + ':' + lineNum + ': ' + message); } } }); } /** * A module is an object literal containing metadata and an * <code>instantiate</code> member, which is a plugin-maker function. * <p> * loadModule(module) marks module's <code>instantiate</code> member as a * func, freezes the module, asks the current new-module-handler to handle it * (thereby notifying the handler), and returns the new module. */ function loadModule(module) { freeze(module); frozenFunc(module.instantiate); return callPub(myNewModuleHandler, 'handle', [module]); } var registeredImports = []; /** * Gets or assigns the id associated with this (assumed to be) * imports object, registering it so that * <tt>getImports(getId(imports)) === imports</tt>. * <p> * This system of registration and identification allows us to * cajole html such as * <pre><a onmouseover="alert(1)">Mouse here</a></pre> * into html-writing JavaScript such as<pre> * ___IMPORTS___.document.innerHTML = " * <a onmouseover=\" * (function(___IMPORTS___) { * ___IMPORTS___.alert(1); * })(___.getImports(" + ___.getId(___IMPORTS___) + ")) * \">Mouse here</a> * "; * </pre> * If this is executed by a plugin whose imports is assigned id 42, * it generates html with the same meaning as<pre> * <a onmouseover="___.getImports(42).alert(1)">Mouse here</a> * </pre> * <p> * An imports is not registered and no id is assigned to it until the * first call to <tt>getId</tt>. This way, an imports that is never * registered, or that has been <tt>unregister</tt>ed since the last * time it was registered, will still be garbage collectable. */ function getId(imports) { enforceType(imports, 'object', 'imports'); var id; if ('id___' in imports) { id = enforceType(imports.id___, 'number', 'id'); } else { id = imports.id___ = registeredImports.length; } registeredImports[id] = imports; return id; } /** * Gets the imports object registered under this id. * <p> * If it has been <tt>unregistered</tt> since the last * <tt>getId</tt> on it, then <tt>getImports</tt> will fail. */ function getImports(id) { var result = registeredImports[enforceType(id, 'number', 'id')]; if (result === void 0) { fail('imports#', id, ' unregistered'); } return result; } /** * If you know that this <tt>imports</tt> no longers needs to be * accessed by <tt>getImports</tt>, then you should * <tt>unregister</tt> it so it can be garbage collected. * <p> * After unregister()ing, the id is not reassigned, and the imports * remembers its id. If asked for another <tt>getId</tt>, it * reregisters itself at its old id. */ function unregister(imports) { enforceType(imports, 'object', 'imports'); if ('id___' in imports) { var id = enforceType(imports.id___, 'number', 'id'); registeredImports[id] = void 0; } } //////////////////////////////////////////////////////////////////////// // Trademarking //////////////////////////////////////////////////////////////////////// /** * Return a trademark object. */ function Trademark(name) { return Token(name); } frozenFunc(Trademark); /** * Returns true if the object has a list of trademarks * and the given trademark is in the list. */ function hasTrademark(trademark, obj) { if (!hasOwnProp(obj, 'trademarks___')) { return false; } var list = obj.trademarks___; for (var i = 0; i < list.length; ++i) { if (list[i] === trademark) { return true; } } return false; } /** * Throws an exception if the object does not have any trademarks or * the given trademark is not in the list of trademarks. */ function guard(trademark, obj) { if (!hasTrademark(trademark, obj)) { fail('Object "' + obj + '" does not have the "' + (trademark.toString() || '*unknown*') + '" trademark'); } } /** * This function adds the given trademark to the given object's list of * trademarks. * If the trademark list doesn't exist yet, this function creates it. * JSON containers and functions may be stamped at any time; constructed * objects may only be stamped during construction unless the third * parameter is truthy. */ function stamp(trademark, obj, opt_allow_constructed) { if (typeOf(trademark) !== 'object') { fail('The supplied trademark is not an object.'); } if (isFrozen(obj)) { fail('The supplied object ' + obj + ' is frozen.'); } if (!isJSONContainer(obj) && (typeOf(obj) !== 'function') && !obj.underConstruction___ && !opt_allow_constructed) { fail('The supplied object ', obj, ' has already been constructed and may not be stamped.'); } var list = obj.underConstruction___ ? 'delayedTrademarks___' : 'trademarks___'; if (!obj[list]) { obj[list] = []; } obj[list].push(trademark); return obj; } function initializeMap(list) { var result = {}; for (var i = 0; i < list.length; i += 2) { // Call asFirstClass() here to prevent, for example, a toxic // function being used at the toString property of an object // literal. setPub(result, list[i], asFirstClass(list[i + 1])); } return result; } //////////////////////////////////////////////////////////////////////// // Sealing and Unsealing //////////////////////////////////////////////////////////////////////// /** * Returns a pair of functions such that the seal(x) wraps x in an object * so that only unseal can get x back from the object. * * @return {object} of the form * { seal: function seal(x) { return {}; }, * unseal: function unseal(obj) { return x; } }. */ function makeSealerUnsealerPair() { var flag = false; // Was a box successfully unsealed var squirrel = null; // Receives the payload from an unsealed box. function seal(payload) { function box() { flag = true; squirrel = payload; } box.toString = frozenFunc(function toString() { return '(box)'; }); return frozenFunc(box); } function unseal(box) { // Start off in a known good state. flag = false; squirrel = null; try { // Don't do anything outside try to foil forwarding functions. box.CALL___(); if (!flag) { throw new Error('Sealer/Unsealer mismatch'); } return squirrel; } finally { // Restore to a known good state. flag = false; squirrel = null; } } return freeze({ seal: frozenFunc(seal), unseal: frozenFunc(unseal) }); } //////////////////////////////////////////////////////////////////////// // Needed for Valija //////////////////////////////////////////////////////////////////////// /** * <tt>cajita.construct(ctor, [args...])</tt> invokes a simple function as * a constructor using 'new'. */ function construct(ctor, args) { ctor = asCtor(ctor); // This works around problems with (new Array()) and (new Date()) where // the returned object is not really a Date or Array on SpiderMonkey and // other interpreters. switch (args.length) { case 0: return new ctor(); case 1: return new ctor(args[0]); case 2: return new ctor(args[0], args[1]); case 3: return new ctor(args[0], args[1], args[2]); case 4: return new ctor(args[0], args[1], args[2], args[3]); case 5: return new ctor(args[0], args[1], args[2], args[3], args[4]); case 6: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5]); case 7: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); case 8: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); case 9: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); case 10: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); case 11: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10]); case 12: return new ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9], args[10], args[11]); default: if (ctor.typeTag___ === 'Array') { return ctor.apply(USELESS, args); } var tmp = function (args) { return ctor.apply(this, args); }; tmp.prototype = ctor.prototype; return new tmp(args); } } /** * Create a unique identification of a given table identity that can * be used to invisibly (to Cajita code) annotate a key object to * index into a table. * <p> * magicCount and MAGIC_TOKEN together represent a * unique-across-frames value safe against collisions, under the * normal Caja threat model assumptions. magicCount and * MAGIC_NAME together represent a probably unique across frames * value, with which can generate strings in which collision is * unlikely but possible. * <p> * The MAGIC_TOKEN is a unique unforgeable per-Cajita runtime * value. magicCount is a per-Cajita counter, which increments each * time a new one is needed. */ var magicCount = 0; var MAGIC_NUM = Math.random(); var MAGIC_TOKEN = Token('MAGIC_TOKEN_FOR:' + MAGIC_NUM); // Using colons in the below causes it to fail on IE since getting a // property whose name contains a semicolon on a DOM table element causes // an exception. var MAGIC_NAME = '_index;'+ MAGIC_NUM + ';'; /** * Creates a new mutable associative table mapping from the * identity of arbitrary keys (as defined by tt>identical()</tt>) to * arbitrary values. * <p> * Once there is a conventional way for JavaScript implementations * to provide weak-key tables, this should feature-test and use that * where it is available, in which case the opt_useKeyLifetime flag * can be ignored. When no weak-key table is primitively provided, * this flag determines which of two possible approximations to * use. In all three cases (actual weak key tables, * opt_useKeyLifetime is falsy, and opt_useKeyLifetime is * truthy), the table returned * <ul> * <li>should work across frames, * <li>should have O(1) complexity measure within a frame where * collision is impossible, * <li>and should have O(1) complexity measure between frames with * high probability. * <li>the table should not retain its keys. In other words, if a * given table T is non-garbage but a given value K is otherwise * garbage, the presence of K as a key in table T will * not, by itself, prevent K from being garbage collected. (Note * that this is not quite as aggressive as the contract provided * by ephemerons.) * </ul> * Given that a K=>V association has been stored in table T, the * three cases differ according to how long they retain V: * <ul> * <li>A genuine weak-key table retains V only while both T and K * are not garbage. * <li>If opt_useKeyLifetime is falsy, retain V while T is not * garbage. * <li>If opt_useKeyLifetime is truthy, retain V while K is not * garbage. In this case, K must be an object rather than a * primitive value. * </ul> * <p> * To support Domita, the keys might be host objects. */ function newTable(opt_useKeyLifetime) { magicCount++; var myMagicIndexName = MAGIC_NAME + magicCount + '___'; function setOnKey(key, value) { if (key !== Object(key)) { fail("Can't use key lifetime on primitive keys: ", key); } var list = key[myMagicIndexName]; if (!list) { key[myMagicIndexName] = [MAGIC_TOKEN, value]; } else { var i = 0; for (; i < list.length; i += 2) { if (list[i] === MAGIC_TOKEN) { break; } } list[i] = MAGIC_TOKEN; list[i + 1] = value; } } function getOnKey(key) { if (key !== Object(key)) { fail("Can't use key lifetime on primitive keys: ", key); } var list = key[myMagicIndexName]; if (!list) { return void 0; } else { var i = 0; for (; i < list.length; i += 2) { if (list[i] === MAGIC_TOKEN) { return list[i + 1]; } } return void 0; } } if (opt_useKeyLifetime) { return primFreeze({ set: frozenFunc(setOnKey), get: frozenFunc(getOnKey) }); } var myValues = []; function setOnTable(key, value) { switch (typeof key) { case 'object': case 'function': { if (null === key) { myValues.prim_null = value; return; } var index = getOnKey(key); if (index === void 0) { index = myValues.length; setOnKey(key, index); } myValues[index] = value; return; } case 'string': { myValues['str_' + key] = value; return; } default: { myValues['prim_' + key] = value; return; } } } /** * If the key is absent, returns <tt>undefined</tt>. * <p> * Users of this table cannot distinguish an <tt>undefined</tt> * value from an absent key. */ function getOnTable(key) { switch (typeof key) { case 'object': case 'function': { if (null === key) { return myValues.prim_null; } var index = getOnKey(key); if (void 0 === index) { return void 0; } return myValues[index]; } case 'string': { return myValues['str_' + key]; } default: { return myValues['prim_' + key]; } } } return primFreeze({ set: frozenFunc(setOnTable), get: frozenFunc(getOnTable) }); } /** * Is <tt>allegedParent</tt> on <obj>'s prototype chain? * <p> * Although in raw JavaScript <tt>'foo' instanceof String</tt> is * false, to reduce the differences between primitives and their * wrappers, <tt>inheritsFrom('foo', String.prototype)</tt> is true. */ function inheritsFrom(obj, allegedParent) { if (null === obj) { return false; } if (void 0 === obj) { return false; } if (typeOf(obj) === 'function') { return false; } if (typeOf(allegedParent) !== 'object') { return false; } if (null === allegedParent) { return false; } function F() {} F.prototype = allegedParent; return Object(obj) instanceof F; } /** * Return func.prototype's directConstructor. * <p> * When following the "classical" inheritance pattern (simulating * class-style inheritance as a pattern of prototypical * inheritance), func may represent (the constructor of) a class; in * which case getSuperCtor() returns (the constructor of) its * immediate superclass. */ function getSuperCtor(func) { enforceType(func, 'function'); if (isCtor(func) || isFunc(func)) { var result = directConstructor(func.prototype); if (isCtor(result) || isFunc(result)) { return result; } } return void 0; } var attribute = new RegExp( '^([\\s\\S]*)_(?:canRead|canCall|getter|handler)___$'); /** * Returns a list of all cajita-readable own properties, whether or * not they are cajita-enumerable. */ function getOwnPropertyNames(obj) { var result = []; var seen = {}; // TODO(erights): revisit once we do es3.1ish attribute control. var implicit = isJSONContainer(obj); for (var k in obj) { if (hasOwnProp(obj, k)) { if (implicit && !endsWith__.test(k)) { if (!myOriginalHOP.call(seen, k)) { seen[k] = true; result.push(k); } } else { var match = attribute.exec(k); if (match !== null) { var base = match[1]; if (!myOriginalHOP.call(seen, base)) { seen[base] = true; result.push(base); } } } } } return result; } /** * Return the names of the accessible own properties of * func.prototype. * <p> * Since prototypical objects are not themselves accessible in * Cajita, this means in effect: the properties contributed by * func.prototype that would be accessible on objects that inherit * from func.prototype. */ function getProtoPropertyNames(func) { enforceType(func, 'function'); return getOwnPropertyNames(func.prototype); } /** * Return the value associated with func.prototype[name]. * <p> * Since prototypical objects are not themselves accessible in * Cajita, this means in effect: If x inherits name from * func.prototype, what would the value of x[name] be? If the value * associated with func.prototype[name] is an exophoric function * (resulting from taming a generic method), then return the * corresponding pseudo-function. See reifyIfXo4a(). */ function getProtoPropertyValue(func, name) { return asFirstClass(readPub(func.prototype, name)); } /** * Like primBeget(), but applicable only to records. */ function beget(parent) { if (!isRecord(parent)) { fail('Can only beget() records: ', parent); } return primBeget(parent); } //////////////////////////////////////////////////////////////////////// // Exports //////////////////////////////////////////////////////////////////////// cajita = { // Diagnostics and condition enforcement log: log, fail: fail, enforce: enforce, enforceType: enforceType, enforceNat: enforceNat, // walking prototype chain, checking JSON containers directConstructor: directConstructor, getFuncCategory: getFuncCategory, isDirectInstanceOf: isDirectInstanceOf, isInstanceOf: isInstanceOf, isRecord: isRecord, isArray: isArray, isJSONContainer: isJSONContainer, freeze: freeze, isFrozen: isFrozen, copy: copy, snapshot: snapshot, // Accessing properties canReadPub: canReadPub, readPub: readPub, hasOwnPropertyOf: hasOwnPropertyOf, readOwn: readOwn, canEnumPub: canEnumPub, canEnumOwn: canEnumOwn, canInnocentEnum: canInnocentEnum, BREAK: BREAK, allKeys: allKeys, forAllKeys: forAllKeys, ownKeys: ownKeys, forOwnKeys: forOwnKeys, canCallPub: canCallPub, callPub: callPub, canSetPub: canSetPub, setPub: setPub, canDeletePub: canDeletePub, deletePub: deletePub, // Trademarking Trademark: Trademark, hasTrademark: hasTrademark, guard: guard, // Sealing & Unsealing makeSealerUnsealerPair: makeSealerUnsealerPair, // Other USELESS: USELESS, manifest: manifest, // Needed for Valija construct: construct, newTable: newTable, inheritsFrom: inheritsFrom, getSuperCtor: getSuperCtor, getOwnPropertyNames: getOwnPropertyNames, getProtoPropertyNames: getProtoPropertyNames, getProtoPropertyValue: getProtoPropertyValue, beget: beget }; forOwnKeys(cajita, frozenFunc(function(k, v) { switch (typeOf(v)) { case 'object': { if (v !== null) { primFreeze(v); } break; } case 'function': { frozenFunc(v); break; } } })); var nativeJSON = global.JSON; safeJSON = primFreeze({ parse: frozenFunc(function (text, opt_reviver) { var attenuatedReviver; // In attenuatedReviver, key will be a string, and "this" will be an // object constructed by the JSON parser or attached to the JSON parser // during a previous call to the reviver. text = String(text); if (opt_reviver) { opt_reviver = toFunc(opt_reviver); throw new Error('JSON.parse with a reviver unimplemented'); // TODO(mikesamuel): implement me } else { return nativeJSON.parse(text, function (key, value) { return canSetPub(this, key) ? value : void 0; }); } }), stringify: frozenFunc(function (obj, opt_replacer, opt_space) { switch (typeof opt_space) { case 'object': case 'function': throw new TypeError('space must be a number or string'); } if (opt_replacer) { opt_replacer = toFunc(opt_replacer); throw new Error('JSON.stringify with a replacer unimplemented'); // TODO(mikesamuel): implement me } else { return nativeJSON.stringify(obj, function (key, value) { return (canReadPub(this, key)) ? value : void 0; }, opt_space); } }) }); sharedImports = { cajita: cajita, 'null': null, 'false': false, 'true': true, 'NaN': NaN, 'Infinity': Infinity, 'undefined': void 0, parseInt: frozenFunc(parseInt), parseFloat: frozenFunc(parseFloat), isNaN: frozenFunc(isNaN), isFinite: frozenFunc(isFinite), decodeURI: frozenFunc(decodeURI), decodeURIComponent: frozenFunc(decodeURIComponent), encodeURI: frozenFunc(encodeURI), encodeURIComponent: frozenFunc(encodeURIComponent), escape: escape ? frozenFunc(escape) : (void 0), Math: Math, JSON: safeJSON, Object: Object, Array: Array, String: String, Boolean: Boolean, Number: Number, Date: Date, RegExp: RegExp, Error: Error, EvalError: EvalError, RangeError: RangeError, ReferenceError: ReferenceError, SyntaxError: SyntaxError, TypeError: TypeError, URIError: URIError }; forOwnKeys(sharedImports, frozenFunc(function(k, v) { switch (typeOf(v)) { case 'object': { if (v !== null) { primFreeze(v); } break; } case 'function': { primFreeze(v); break; } } })); primFreeze(sharedImports); ___ = { // Diagnostics and condition enforcement getLogFunc: getLogFunc, setLogFunc: setLogFunc, primFreeze: primFreeze, // Accessing property attributes. canRead: canRead, grantRead: grantRead, canEnum: canEnum, grantEnumOnly: grantEnumOnly, // TODO(mikesamuel): should grantCall be exported? canCall: canCall, grantCall: grantCall, canSet: canSet, grantSet: grantSet, canDelete: canDelete, grantDelete: grantDelete, // Module linkage readImport: readImport, // Classifying functions isCtor: isCtor, isFunc: isFunc, isXo4aFunc: isXo4aFunc, ctor: ctor, func: func, frozenFunc: frozenFunc, asFunc: asFunc, toFunc: toFunc, xo4a: xo4a, initializeMap: initializeMap, // Accessing properties inPub: inPub, canSetStatic: canSetStatic, setStatic: setStatic, // Other typeOf: typeOf, hasOwnProp: hasOwnProp, identical: identical, args: args, tameException: tameException, primBeget: primBeget, callStackUnsealer: callStackSealer.unseal, RegExp: RegExp, // Available to rewrite rule w/o risk of masking stamp: stamp, asFirstClass: asFirstClass, // Taming mechanism useGetHandler: useGetHandler, useApplyHandler: useApplyHandler, useCallHandler: useCallHandler, useSetHandler: useSetHandler, useDeleteHandler: useDeleteHandler, grantFunc: grantFunc, grantGeneric: grantGeneric, handleGeneric: handleGeneric, grantTypedGeneric: grantTypedGeneric, grantMutator: grantMutator, enforceMatchable: enforceMatchable, all2: all2, // Taming decisions sharedImports: sharedImports, // Module loading getNewModuleHandler: getNewModuleHandler, setNewModuleHandler: setNewModuleHandler, obtainNewModule: obtainNewModule, makeNormalNewModuleHandler: makeNormalNewModuleHandler, loadModule: loadModule, NO_RESULT: NO_RESULT, getId: getId, getImports: getImports, unregister: unregister }; forOwnKeys(cajita, frozenFunc(function(k, v) { if (k in ___) { fail('internal: initialization conflict: ', k); } if (typeOf(v) === 'function') { grantFunc(cajita, k); } ___[k] = v; })); setNewModuleHandler(makeNormalNewModuleHandler()); })(this);