// 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.
// .............................................................................

// This module is the Caja runtime library. It is written in
// Javascript, not Caja, and would be rejected by the Caja
// translator. This module exports two globals: 
// * "___" for use by the output of the Caja translator and by some
//   other untranslated Javascript code.
// * "caja" providing some common services to the Caja programmer.

// TODO(erights): All code text in comments should be enclosed in
// &lt;tt&gt;code&lt;/tt&gt;.


////////////////////////////////////////////////////////////////////////
// Caja adds the following common Javascript extensions to ES3
// TODO(erights): Move such extensions to a separate extensions.js,
//                and change the conflict rule (for now) to fail on
//                detecting a conflict.
////////////////////////////////////////////////////////////////////////

if (Array.prototype.indexOf === undefined) {
  /** 
   * Returns the first index at which the specimen is found (by
   * "===") or -1 if none.  
   */
  Array.prototype.indexOf = function(specimen) {
    var len = this.length;
    for (var i = 0; i < len; i += 1) {
      if (this[i] === specimen) {
        return i;
      }
    }
    return -1;
  };
}

if (Array.prototype.lastIndexOf === undefined) {
  /** 
   * Returns the last index at which the specimen is found (by
   * "===") or -1 if none.  
   */
  Array.prototype.lastIndexOf = function(specimen) {
    for (var i = this.length; --i >= 0; ) {
      if (this[i] === specimen) {
        return i;
      }
    }
    return -1;
  };
}

if (Date.prototype.toISOString === undefined) {
  /**
   * Like the date.toJSONString() method defined in json.js, but
   * without the surrounding quotes.
   */
  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');
  };
}


// caja.js exports the following names to the Javascript global
// namespace. Caja code can only use the "caja" object. The "___"
// object is for use by code generated by the Caja translator, and by
// Javascript code (such as a powerbox) in the embedding application.

// Caja virtually adds Object.prototype.freeze_(), so that a
// constructed object can freeze itself, but its clients cannot freeze
// it. However, in order not to disrupt innocent code (see
// canInnocentEnum() below), the only property names Caja may add to
// primordial objects are names ending in triple underbar. Therefore,
// Caja instead adds Object.prototype.freeze__handler___().

var caja;
var ___;

// Explicitly passing in the actual global object to avoid
// ReferenceErrors when referring to potentially nonexistent objects
// like HTMLDivElement.

(function(global) {
  
  ////////////////////////////////////////////////////////////////////////
  // 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. 
   */
  var myLogFunc_ = function(str, opt_stop) {};

  /**
   * 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
   * concatentation 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) {
    (typeof console !== 'undefined') && console.trace();
    var message = Array.prototype.slice.call(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.prototype.slice.call(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
  ////////////////////////////////////////////////////////////////////////
  
  /**
   * 
   */
  var myKeeper_ = {

    /**
     *
     */
    toString: function() { return '<Logging Keeper>'; },

    /**
     * 
     */
    handleRead: function(obj, name) {
      log('Not readable: (' + obj + ').' + name);
      return undefined; 
    },

    /**
     * 
     */
    handleCall: function(obj, name, args) {
      fail('Not callable: (', obj, ').', name);
    },

    /**
     * 
     */
    handleSet: function(obj, name, val) {
      fail('Not settable: (', obj, ').', name);
    },

    /**
     * 
     */
    handleDelete: function(obj, name) {
      fail('Not deletable: (', obj, ').', name);
    }
  };

  /**
   * 
   */
  function getKeeper() { return myKeeper_; }

  /**
   * 
   */
  function setKeeper(newKeeper) { myKeeper_ = newKeeper; }

  /**
   * 
   */
  Object.prototype.handleRead___ = function(name) {
    var handlerName = name + '_getter___';
    if (this[handlerName]) {
      return this[handlerName]();
    }
    return myKeeper_.handleRead(this, name);
  };

  /**
   * 
   */
  Object.prototype.handleCall___ = function(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(name, val) {
    var handlerName = name + '_setter___';
    if (this[handlerName]) {
      return this[handlerName](val);
    }
    return myKeeper_.handleSet(this, name, val);
  };

  /**
   * 
   */
  Object.prototype.handleDelete___ = function(name) {
    var handlerName = name + '_deleter___';
    if (this[handlerName]) {
      return this[handlerName]();
    }
    return myKeeper_.handleDelete(this, name);
  };
  
  ////////////////////////////////////////////////////////////////////////
  // Overriding some very basic primordial methods
  ////////////////////////////////////////////////////////////////////////
  
  /**
   * Returns true only if we can call
   * Object.prototype.hasOwnProperty on this object without
   * exploding. 
   * <p>
   * On Firefox, it seems that calling hasOwnProperty on an
   * HTMLDivElement sometimes causes an
   * "Illegal operation on WrappedNative prototype object".
   * <p>
   * SECURITY BUG STOPGAP TODO(erights)
   */
  var canCallHasOwnProperty = function(obj) { return true; };
  
  // When we're in a non-browser environment, such that there isn't
  // a global HTMLDivElement, then we don't need to worry about
  // this bug.
  if (typeof global.HTMLDivElement === 'function') {
    canCallHasOwnProperty = function(obj) {
      return !(obj instanceof global.HTMLDivElement);
    };
  }
  
  var originalHOP_ = Object.prototype.hasOwnProperty;
  
  /**
   * <tt>hasOwnProp(obj.prop)</tt> means what
   * <tt>obj.hasOwnProperty(prop)</tt> would normally mean in an
   * unmodified Javascript system.
   */
  function hasOwnProp(obj, name) { 
    var t = typeof obj;
    if (t !== 'object' && t !== 'function') { 
      return false; 
    }
    if (canCallHasOwnProperty(obj)) {
      // Fails in Firefox for some DOM objects intermittently(?!) 
      // with "Illegal operation on WrappedNative prototype object".
      // For these, canCallHasOwnProperty must say false.
      return originalHOP_.call(obj, name); 
    } else {
      return false;
    }
  }
  
  ////////////////////////////////////////////////////////////////////////
  // walking prototype chain, checking JSON containers
  ////////////////////////////////////////////////////////////////////////
  
  /**
   * Does str end with suffix? 
   */
  function endsWith(str, suffix) {
    enforceType(str, 'string');
    enforceType(suffix, 'string');
    var strLen = str.length;
    var sufLen = suffix.length;
    return strLen >= sufLen && 
      (str.substring(strLen-sufLen, strLen) === suffix);
  }
  
  /**
   * Returns the 'constructor' property of obj's prototype.
   * <p>
   * By "obj's prototype", we mean the object that obj directly
   * inherits from, not the value of its 'prototype' property. If
   * obj has a '__proto__' property, then we assume we're on a
   * platform (like Firefox) in which this reliably gives us obj's
   * prototype. Otherwise, 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 undefined; }
    try {
      if (typeof obj !== 'object') {
        // Note that functions thereby return undefined,
        // so directConstructor() doesn't provide access to the
        // forbidden Function constructor.
        return undefined;
      }
      // The following test will initially return false in IE
      if (hasOwnProp(obj, '__proto__')) { 
        if (obj.__proto__ === null) { return undefined; }
        return obj.__proto__.constructor; 
      }
      var result;
      if (!hasOwnProp(obj, 'constructor')) { 
        result = obj.constructor;
      } else {
        var oldConstr = obj.constructor;
        if (!(delete obj.constructor)) { return undefined; }
        result = obj.constructor;
        obj.constructor = oldConstr;
      }
      if (result.prototype.constructor === result) {
        // Memoize, so it'll be faster next time.
        obj.__proto__ = result.prototype;
      }
      return result;
    } catch (ex) {
      return null;
    }
  }
  
  /**
   * A JSON container is an 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);
    return constr === Object || constr === Array;
  }
  
  /**
   * If obj is frozen, Caja code cannot directly assign to
   * properties of obj, nor directly add or delete 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), then (we hope) B must be
   * frozen regardless, but A may or may not be frozen.
   * <p>
   * If typeof <tt>obj</tt> is neither 'object' nor 'function', then
   * it's currently considered frozen.
   */
  function isFrozen(obj) { 
    var t = typeof obj;
    if (t !== 'object' && t !== 'function') { 
      return true; 
    }
    return hasOwnProp(obj, '___FROZEN___'); 
  }
  
  /**
   * Mark obj as frozen so that Caja code cannot directly assign to its
   * properties.
   * <p>
   * If obj is a function, also freeze obj.prototype.
   * <p>
   * This appears as <tt>___.primFreeze(obj)</tt> and is wrapped by
   * the virtual <tt>Object.prototype.freeze_()</tt>.
   */
  function primFreeze(obj) {
    if (null === obj) { return obj; }
    if (isFrozen(obj)) { return obj; }
    var typ = typeof obj;
    if (typ !== 'object' && typ !== 'function') { 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(k, '_canSet___') || endsWith(k, '_canDelete___')) { 
        if (obj[k]) {
          badFlags.push(k);
        }
      }
    }
    for (var i = 0; i < badFlags.length; i++) {
      var flag = badFlags[i];
      if (hasOwnProp(obj, flag)) {
        if (!(delete obj[flag])) {
          fail('internal: failed delete: ', obj, '.', flag);
        }
      }
      if (obj[flag]) {
        // At the time of this writing, this case
        // should never be able to happen, since
        // prototypes are always frozen before use,
        // and frozen objects cannot have these flags
        // set on them. We code it this way to allow
        // for a future optimization, where the
        // prototype can record as canSet those
        // properties that appear in instances that
        // inherit from this prototype. 
        obj[flag] = false;
      }
    }
    obj.___FROZEN___ = true;
    if (typ === 'function') {
      // Do last to avoid possible infinite recursion.
      primFreeze(obj.prototype);
    }
    return obj;
  }
  
  /**
   * Like primFreeze(obj), but applicable only to JSON containers.
   */
  function freeze(obj) {
    if (!isJSONContainer(obj)) {
      fail('caja.freeze(obj) applies only to JSON Containers: ', 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.
   */
  function copy(obj) {
    if (!isJSONContainer(obj)) {
      fail('caja.copy(obj) applies only to JSON Containers: ', obj);
    }
    var result = (obj instanceof Array) ? [] : {};
    each(obj, simpleFunc(function(k, v) {
      result[k] = v;
    }));
    return result;
  }
  
  /**
   * A snapshot of a JSON container is a frozen 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)   { return !!obj[name + '_canRead___']; }
  /** Tests whether the fast-path canEnum flag is set. */
  function canEnum(obj, name)   { return !!obj[name + '_canEnum___']; }
  /** Tests whether the fast-path canCall flag is set. */
  function canCall(obj, name)   { return !!obj[name + '_canCall___']; }
  /** Tests whether the fast-path canSet flag is set. */
  function canSet(obj, name)    { return !!obj[name + '_canSet___']; }
  /** Tests whether the fast-path canDelete flag is set. */
  function canDelete(obj, name) { return !!obj[name + '_canDelete___']; }
  
  /** 
   * Sets the fast-path canRead flag.
   * <p>
   * The various <tt>allow*</tt> functions are called externally by
   * Javascript code to express whitelisting taming decisions. And
   * they are called internally to memoize decisions arrived at by
   * other means. 
   */
  function allowRead(obj, name) { 
    obj[name + '_canRead___'] = true; 
  }
  
  /** allowEnum implies allowRead */
  function allowEnum(obj, name) { 
    allowRead(obj, name);
    obj[name + '_canEnum___'] = true;
  }
  
  /** 
   * Simple functions should callable and readable, but methods
   * should only be callable.
   */
  function allowCall(obj, name) { 
    obj[name + '_canCall___'] = true; 
  }
  
  /**
   * allowSet implies allowEnum and allowRead.
   */
  function allowSet(obj, name) {
    if (isFrozen(obj)) {
      fail("Can't set .", name, ' on frozen (', obj, ')');
    }
    allowEnum(obj, name);
    obj[name + '_canSet___'] = true;
  }
  
  /**
   * BUG TODO(erights): allowDelete is not yet specified or
   * implemented. 
   */
  function allowDelete(obj, name) {
    if (isFrozen(obj)) {
      fail("Can't delete .", name, ' on frozen (', obj, ')');
    }
    fail('TODO(erights): allowDelete() not yet implemented');
    obj[name + '_canDelete___'] = true;
  }
  
  ////////////////////////////////////////////////////////////////////////
  // Classifying functions
  ////////////////////////////////////////////////////////////////////////
  
  function isCtor(constr)    { return !!constr.___CONSTRUCTOR___; }
  function isMethod(meth)    { return '___METHOD_OF___' in meth; }
  function isSimpleFunc(fun) { return !!fun.___SIMPLE_FUNC___; }
  
  /** 
   * Mark constr as a constructor.
   * <p>
   * If opt_Sup is provided, set constr.Super = opt_Sup.
   * <p>
   * A function is tamed and classified by calling one of ctor(),
   * method(), or simpleFunc(). 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>
   * opt_name, 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 (isMethod(constr)) {
      fail("Methods can't be constructors: ", constr);
    }
    if (isSimpleFunc(constr)) {
      fail("Simple functions can't be constructors: ", constr);
    }
    constr.___CONSTRUCTOR___ = true;
    if (opt_Sup) {
      opt_Sup = asCtor(opt_Sup);
      if (hasOwnProp(constr, 'Super')) {
        if (constr.Super !== opt_Sup) {
          fail("Can't inherit twice: ", constr, ',', opt_Sup);
        }
      } else {
        if (isFrozen(constr)) {
          fail('Derived constructor already frozen: ', constr);
        }
        constr.Super = opt_Sup;
      }
    }
    return constr;  // translator freezes constructor later
  }
  
  /** 
   * Mark meth as a method of instances of constr. 
   * <p>
   * opt_name, if provided, should be the message name associated
   * with the method. Currently, this is used only to generate
   * friendlier error messages.
   */
  function method(constr, meth, opt_name) {
    enforceType(meth, 'function', opt_name);
    if (isCtor(meth)) {
      fail("constructors can't be methods: ", meth);
    }
    if (isSimpleFunc(meth)) {
      fail("Simple functions can't be methods: ", meth);
    }
    meth.___METHOD_OF___ = asCtorOnly(constr);
    return primFreeze(meth);
  }
  
  /** 
   * 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 simpleFunc(fun, opt_name) {
    enforceType(fun, 'function', opt_name);
    if (isCtor(fun)) {
      fail("Constructors can't be simple functions: ", fun);
    }
    if (isMethod(fun)) {
      fail("Methods can't be simple function: ", fun);
    }
    fun.___SIMPLE_FUNC___ = true;
    fun.apply_canCall___ = true;
    fun.call_canCall___ = true;
    return fun;  // translator freezes fun later
  }
  
  /** This "Only" form doesn't freeze */
  function asCtorOnly(constr) {
    if (isCtor(constr) || isSimpleFunc(constr)) { 
      return constr; 
    }
    
    enforceType(constr, 'function');
    if (isMethod(constr)) {
      fail("Methods can't be called as constructors: ", constr);
    }
    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 methods and simple functions can be called as methods */
  function asMethod(meth) {
    if (isSimpleFunc(meth) || isMethod(meth)) { 
      if (!isFrozen(meth)) {
        fail('internal: non-frozen func stored as method: ', meth);
      }
      return meth; 
    }
    
    enforceType(meth, 'function');
    if (isCtor(meth)) {
      fail("Constructors can't be called as methods: ", meth);
    }
    fail("Untamed functions can't be called as methods: ", meth);
  }
  
  /** Only simple functions can be called as simple functions */
  function asSimpleFunc(fun) {
    if (isSimpleFunc(fun)) { 
      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>simpleFunc</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 fun;
      }
      fail("Constructors can't be called as simple functions: ", fun);
    }
    if (isMethod(fun)) {
      fail("Methods can't be called as simple functions: ", fun);
    }
    fail("Untamed functions can't be called as simple functions: ", fun);
  }
  
  /** 
   * Sets constr.prototype[name] = member.
   * <p>
   * If member is a method of constr, make it callable.
   * If member is a simple function, make it callable and readable.
   * Else make it readable.
   */
  function setMember(constr, name, member) {
    name = String(name);
    if (endsWith(name, '__')) {
      fail('Reserved name: ', name);
    }
    var proto = readPub(asCtorOnly(constr), 'prototype');
    // We allow prototype members to end in a single "_".
    if (!canSetProp(proto, name)) {
      fail('not settable: ', name);
    }
    if (member.___METHOD_OF___ === constr) {
      allowCall(proto, name);  // grant
    } else if (isSimpleFunc(member)) {
      allowCall(proto, name);  // grant
      allowSet(proto, name);  // grant
    } else {
      allowSet(proto, name);  // grant
    }
    proto[name] = member;
  }
  
  ////////////////////////////////////////////////////////////////////////
  // Accessing properties
  ////////////////////////////////////////////////////////////////////////
  
  /** 
   * Can a constructed Caja object read this property on itself? 
   * <p>
   * Can a Caja method whose <tt>this</tt> is bound to <tt>that</tt>
   * read its own <tt>name</tt> property? For properties added to
   * the object by Caja code, the answer is yes. For other
   * properties, which must therefore be inherited from a prototype
   * written in Javascript rather than Caja, the answer is: iff they
   * were whitelisted.
   */
  function canReadProp(that, name) {
    name = String(name);
    if (endsWith(name, '__')) { return false; }
    return canRead(that, name);
  }
  
  /** 
   * A constructed Caja object's attempt to read this property on
   * itself.
   * <p>
   * If it can't,  it reads <tt>undefined</tt> instead.
   */
  function readProp(that, name) {
    name = String(name);
    return canReadProp(that, name) ? that[name] : that.handleRead___(name);
  }
  
  /** 
   * Can a Caja client of <tt>obj</tt> read its <name> property? 
   * <p>
   * If the property is Internal (i.e. ends in an '_'), then no.
   * If the property was defined by Caja code, then yes. If it was
   * whitelisted, then yes. Or if the property is an own property of
   * a JSON container, then yes.
   */
  function canReadPub(obj, name) {
    name = String(name);
    if (endsWith(name, '_')) { return false; }
    if (canRead(obj, name)) { return true; }
    if (!isJSONContainer(obj)) { return false; }
    if (!hasOwnProp(obj, name)) { return false; }
    allowRead(obj, name);  // memoize
    return true;
  }
  
  /**
   * Caja code attempting to read a property on something besides
   * <tt>this</tt>.
   * <p>
   * If it can't, it reads <tt>undefined</tt> instead.
   */
  function readPub(obj, name) {
    name = String(name);
    return canReadPub(obj, name) ? obj[name] : obj.handleRead___(name);
  }
  
  /**
   * 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. Caja 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 Caja
   * 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(name, '___')) { return false; }
    return true;
  }
  
  /** 
   * Would a Caja for/in loop on <tt>this</tt> see this name? 
   * <p>
   * For properties defined in Caja, this is generally the same as
   * canReadProp. Otherwise according to whitelisting.
   */
  function canEnumProp(that, name) {
    name = String(name);
    if (endsWith(name, '__')) { return false; }
    return canEnum(that, name);
  }
  
  /** 
   * Would a Caja for/in loop by a client of obj see this name? 
   * <p>
   * For properties defined in Caja, this is generally the same as
   * canReadProp. Otherwise according to whitelisting.
   */
  function canEnumPub(obj, name) {
    name = String(name);
    if (endsWith(name, '_')) { return false; }
    if (canEnum(obj, name)) { return true; }
    if (!isJSONContainer(obj)) { return false; }
    if (!hasOwnProp(obj, name)) { return false; }
    allowEnum(obj, name);  // memoize
    return true;
  }
  
  /**
   * Like canEnumPub, but allows only non-inherited properties.
   */
  function canEnumOwn(obj, name) {
    name = String(name);
    return hasOwnProp(obj, name) && canEnumPub(obj, name);
  }
  
  /**
   * Inside a <tt>caja.each()</tt>, the body function can terminate
   * early, as if with a conventional <tt>break;</tt>, by doing a
   * <pre>return caja.BREAK;</pre>
   */
  var BREAK = {};
  
  /**
   * For each sensible key/value pair in obj, call fn with that
   * pair.
   * <p>
   * If <tt>obj instanceof Array</tt>, then enumerate
   * indexes. Otherwise, enumerate the canEnumOwn() property names.
   */
  function each(obj, fn) {
    fn = asSimpleFunc(fn);
    if (obj instanceof Array) {
      var len = obj.length;
      for (var i = 0; i < len; i++) {
        if (fn(i, readPub(obj, i)) === BREAK) {
          return;
        }
      }
    } else {
      for (var k in obj) {
        if (canEnumOwn(obj, k)) {
          if (fn(k, readPub(obj, k)) === BREAK) {
            return;
          }
        }
      }
    }
  }
  
  /**
   * Can this be called as an internal 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.
   * <p>
   * SECURITY HAZARD TODO(erights): If a settable property is
   * first set to a 
   * simple function, which is then called, memoizing canCall, and
   * then set to some other kind of function which leaked (such as
   * an untamed function), then that other function can be
   * inappropriately called as a method on that. We currently
   * classify this as a hazard and not a bug per se, since no such
   * function value should ever leak into value space. If one does,
   * there's a bug either in Caja or in the embedding app's taming
   * decisions.
   * <p>
   * In any case, the not-yet-implemented plan to fix this hazard is
   * to have two canSet flags: one that records the grant of
   * settability, and one to be tested in the fast-path. The
   * fast-path canCall and fast-path canSet flags will be exclusive,
   * to be faulted in by the last successful use. This way, repeated
   * calls are fast, and repeated sets are fast, but the first call
   * after a set will re-check the value to be called.
   * <p>
   * This plan will need to be thought through again when we
   * implement property deletion. 
   */
  function canCallProp(that, name) {
    name = String(name);
    if (endsWith(name, '__')) { return false; }
    if (canCall(that, name)) { return true; }
    if (!canReadProp(that, name)) { return false; }
    var func = that[name];
    if (!isSimpleFunc(func)) { return false; }
    allowCall(that, name);  // memoize
    return true;
  }
  
  /**
   * A Caja method tries to call one of its Internal methods.
   */
  function callProp(that, name, args) {
    name = String(name);
    if (canCallProp(that, name)) {
      var meth = that[name];
      return meth.apply(that, args);
    } else {
      return that.handleCall___(name, args);
    }
  }
  
  /**
   * Like canCallProp(), with differences that parallel the
   * differences between canReadProp vs canReadPub.
   */
  function canCallPub(obj, name) {
    name = String(name);
    if (endsWith(name, '_')) { return false; }
    if (canCall(obj, name)) { return true; }
    if (!canReadPub(obj, name)) { return false; }
    var func = obj[name];
    if (!isSimpleFunc(func)) { return false; }
    allowCall(obj, name);  // memoize
    return true;
  }
  
  /**
   * A client of obj tries to call one of its methods.
   */
  function callPub(obj, name, args) {
    name = String(name);
    if (canCallPub(obj, name)) {
      var meth = obj[name];
      return meth.apply(obj, args);
    } else if (obj.handleCall___) {
      return obj.handleCall___(name, args);
    } else {
      fail('not callable %o %s', obj, name);
    }
  }
  
  /** 
   * Can a method of a Caja constructed object directly assign to
   * this property of its object?
   * <p>
   * Iff this object isn't frozen.
   */
  function canSetProp(that, name) {
    name = String(name);
    if (endsWith(name, '__')) { return false; }
    if (canSet(that, name)) { return true; }
    return !isFrozen(that);
  }
  
  /**
   * A Caja method tries to assign to this property of its object.
   */
  function setProp(that, name, val) {
    name = String(name);
    if (canSetProp(that, name)) {
      allowSet(that, name);  // grant
      that[name] = val;
      return val;
    } else {
      return that.handleSet___(name, val);
    }
  }
  
  /**
   * Can a client of obj directly assign to its name property?
   * <p>
   * If this property is Internal (i.e., ends with a '_') or if this
   * object is frozen, then no. 
   * If this property is not Internal and was defined by Caja code,
   * then yes. If the object is a JSON container, then
   * yes. Otherwise according to whitelisting decisions.
   * <p>
   * The non-obvious implication of this rule together with the
   * canSetProp rule is that a Caja client of a Caja constructed
   * object cannot add new properties to it. But a Caja constructed
   * object can add new properties to itself, and its clients can
   * then assign to these properties.
   */
  function canSetPub(obj, name) {
    name = String(name);
    if (endsWith(name, '_')) { return false; }
    if (canSet(obj, name)) { return true; }
    return !isFrozen(obj) && isJSONContainer(obj);
  }
  
  /** A client of obj attempts to assign to one of its properties. */
  function setPub(obj, name, val) {
    name = String(name);
    if (canSetPub(obj, name)) {
      allowSet(obj, name);  // grant
      obj[name] = val;
      return val;
    } else {
      return obj.handleSet___(name, val);
    }
  }
  
  /**
   * Can a Caja constructed object delete the named property?
   * <p>
   * BUG TODO(erights): This is not yet supported. The precise
   * enabling conditions are not yet determined, as is the implied
   * bookkeeping. 
   */
  function canDeleteProp(that, name) {
    fail('TODO(erights): deletion not yet supported');
    return false;
  }
  
  /**
   * A Caja constructed object attempts to delete one of its own
   * properties. 
   * <p>
   * BUG TODO(erights): This is not yet supported. The precise
   * enabling conditions are not yet determined, as is the implied
   * bookkeeping.
   */
  function deleteProp(that, name) {
    name = String(name);
    if (canDeleteProp(that, name)) {
      fail('TODO(erights): deletion not yet supported');
      return (delete that[name]) ||
        fail('not deleted: ', name);
    } else {
      return that.handleDelete___(name);
    }
  }
  
  /**
   * Can a client of obj delete the named property?
   * <p>
   * BUG TODO(erights): This is not yet supported. The precise
   * enabling conditions are not yet determined, as is the implied
   * bookkeeping. 
   */
  function canDeletePub(obj, name) {
    fail('TODO(erights): deletion not yet supported');
    return false;
  }
  
  /**
   * A client of obj can only delete a property of obj if obj is a
   * non-frozen JSON container.
   * <p>
   * BUG TODO(erights): This is not yet supported. The precise
   * enabling conditions are not yet determined, as is the implied
   * bookkeeping. 
   */
  function deletePub(obj, name) {
    name = String(name);
    if (canDeletePub(obj, name)) {
      if (!isJSONContainer(obj)) {
        fail('unable to delete: ', name);
      }
      fail('TODO(erights): deletion not yet supported');
      return (delete obj[name]) ||
        fail('not deleted: ', name);
    } else {
      return obj.handleDelete___(name);
    }
  }
  
  ////////////////////////////////////////////////////////////////////////
  // Other
  ////////////////////////////////////////////////////////////////////////
  
  /**
   * This returns a frozen array copy of the original array or
   * array-like object.
   * <p>
   * If a Caja 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 Caja 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.prototype.slice.call(original, 0));
  }
  
  /**
   *
   */
  function setMemberMap(sub, members) {
    each(members, simpleFunc(function(mname, member) {
      setMember(sub, mname, member);
    }));
  }
  
  /**
   * Provides a shorthand for a class-like declaration of a fresh
   * Caja constructor.
   * <p>
   * Given that sub is a Caja constructor in formation, whose 'prototype'
   * property hasn't been initialized yet, initialize sub and its
   * 'prototype' property so that it acts as a subclass of opt_Sup,
   * with opt_members added as members to sub.prototype, and
   * opt_statics added as members to sub.
   * <p>
   * TODO(erights): return a builder object that allows further
   * initialization. 
   */
  function def(sub, opt_Sup, opt_members, opt_statics) {
    var sup = opt_Sup || Object;
    var members = opt_members || {};
    var statics = opt_statics || {};
    if ('Super' in statics) {
      fail('The static name "Super" is reserved ',
           'for the super-constructor');
    }
    
    ctor(sub, sup);
    function PseudoSuper() {}
    PseudoSuper.prototype = sup.prototype;
    sub.prototype = new PseudoSuper();
    sub.prototype.constructor = sub;
    
    setMemberMap(sub, members);
    each(statics, simpleFunc(function(sname, staticMember) {
      setPub(sub, sname, staticMember);
    }));
    
    // translator freezes sub and sub.prototype later.
  }
  
  ////////////////////////////////////////////////////////////////////////
  // 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 allowRead().
   */
  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 allowCall(), allowSimpleFunc(), or
   * allowMethod().
   */
  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 allowCall(), allowSimpleFunc(), or
   * allowMethod().
   */
  function useCallHandler(obj, name, callHandler) {
    useApplyHandler(obj, name, function(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 allowSet().
   */
  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 allowDelete().
   */
  function useDeleteHandler(obj, name, deleteHandler) {
    obj[name + '_deleter___'] = deleteHandler;
  }

  /**
   * Whilelist obj[name] as a simple function that can be either
   * called or read.
   */
  function allowSimpleFunc(obj, name) {
    simpleFunc(obj[name], name);
    allowCall(obj, name);
    allowRead(obj, name);
  }
  
  /**
   * Whitelist constr.prototype[name] as a method that can be called
   * on instances of constr.
   */
  function allowMethod(constr, name) {
    method(constr, constr.prototype[name], name);
    allowCall(constr.prototype, name);
  }
  
  /**
   * Virtually replace constr.prototype[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 Caja semantics. In order for this fault
   * handler to get control, it's important that no one does an
   * allowCall(), allowSimpleFunc(), or allowMethod() on the
   * original method. 
   */
  function allowMutator(constr, name) {
    var original = constr.prototype[name];
    useApplyHandler(constr.prototype, name, function(args) {
      if (isFrozen(this)) {
        fail("Can't .", name, ' a frozen object');
      }
      return original.apply(this, args);
    });
  }
  
  /**
   * 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.
   */
  function enforceMatchable(regexp) {
    if (regexp instanceof RegExp) {
      if (isFrozen(regexp)) {
        fail("Can't match with frozen RegExp: ", regexp);
      }
    }
  }
  
  /**
   * 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
  ////////////////////////////////////////////////////////////////////////


  all2(allowRead, Math, [
    'E', 'LN10', 'LN2', 'LOG2E', 'LOG10E', 'PI', 'SQRT1_2', 'SQRT2'
  ]);
  all2(allowSimpleFunc, Math, [
    'abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor',
    'log', 'max', 'min', 'pow', 'random', 'round', 'sin', 'sqrt', 'tan'
  ]);
  
  
  ctor(Object, undefined, 'Object');
  all2(allowMethod, Object, [
    'toString', 'toLocaleString', 'valueOf', 'isPrototypeOf'
  ]);
  allowRead(Object.prototype, 'length');
  useCallHandler(Object.prototype, 'hasOwnProperty',  function(name) {
    name = String(name);
    return canReadPub(this, name) && hasOwnProp(this, name);
  });
  var pie_ = Object.prototype.propertyIsEnumerable;
  useCallHandler(Object.prototype, 'propertyIsEnumerable', function(name) {
    name = String(name);
    return canReadPub(this, name) && pie_.call(this, name);
  });


  /**
   * A method of a constructed object can freeze its object by saying
   * <tt>this.freeze_()</tt>.
   * <p>
   * Because this method ends in a "_", it is internal, so clients
   * of a constructed object (a non-JSON container) cannot freeze it
   * without its cooperation.
   */
  useCallHandler(Object.prototype, 'freeze_', function() {
    return primFreeze(this);
  });
  
  
  // SECURITY HAZARD TODO(erights): Seems dangerous, but doesn't add
  // risk. Or does it? 
  ctor(Function, Object, 'Function');
  // SECURITY HAZARD TODO(erights): Seems dangerous, but doesn't add
  // risk. Or does it? 
  allowRead(Function.prototype, 'prototype');

  useCallHandler(Function.prototype, 'apply', function(that, realArgs) {
    return asSimpleFunc(this).apply(that, realArgs[0]);
  });
  useCallHandler(Function.prototype, 'call', function(that, realArgs) {
    return asSimpleFunc(this).apply(that, realArgs);
  });
  
  
  ctor(Array, Object, 'Array');
  all2(allowMethod, Array, [
    'concat', 'join', 'slice', 'indexOf', 'lastIndexOf'
  ]);
  all2(allowMutator, Array, [
    'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'
  ]);
  
  
  ctor(String, Object, 'String');
  allowSimpleFunc(String, 'fromCharCode');
  all2(allowMethod, String, [
    'charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf',
    'localeCompare', 'slice', 'substring',
    'toLowerCase', 'toLocaleLowerCase', 'toUpperCase', 'toLocaleUpperCase'
  ]);
  useCallHandler(String.prototype, 'match', function(regexp) {
    enforceMatchable(regexp);
    return this.match(regexp);
  });
  useCallHandler(String.prototype, 'replace', function(searchValue, 
                                                       replaceValue) {
    enforceMatchable(searchValue);
    return this.replace(searchValue, replaceValue);
  });
  useCallHandler(String.prototype, 'search', function(regexp) {
    enforceMatchable(regexp);
    return this.search(regexp);
  });
  useCallHandler(String.prototype, 'split', function(separator, limit) {
    enforceMatchable(separator);
    return this.split(separator, limit);
  });
  
  
  ctor(Boolean, Object, 'Boolean');
  
  
  ctor(Number, Object, 'Number');
  all2(allowRead, Number, [
    'MAX_VALUE', 'MIN_VALUE', 'NaN',
    'NEGATIVE_INFINITY', 'POSITIVE_INFINITY'
  ]);
  all2(allowMethod, Number, [
    'toFixed', 'toExponential', 'toPrecision'
  ]);
  
  
  ctor(Date, Object, 'Date');
  allowSimpleFunc(Date, 'parse');
  allowSimpleFunc(Date, 'UTC');
  
  all2(allowMethod, Date, [
    '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(allowMutator, Date, [
    'setTime', 'setFullYear', 'setUTCFullYear', 'setMonth', 'setUTCMonth',
    'setDate', 'setUTCDate', 'setHours', 'setUTCHours',
    'setMinutes', 'setUTCMinutes', 'setSeconds', 'setUTCSeconds',
    'setMilliseconds', 'setUTCMilliseconds'
  ]);
  
  
  ctor(RegExp, Object, 'RegExp');
  allowMutator(RegExp, 'exec');
  allowMutator(RegExp, 'test');
  
  all2(allowRead, RegExp, [
    'source', 'global', 'ignoreCase', 'multiline', 'lastIndex'
  ]);
  
  
  ctor(Error, Object, 'Error');
  allowRead(Error, 'name');
  allowRead(Error, '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 sharedOuters;
  
  ////////////////////////////////////////////////////////////////////////
  // 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 Caja
   * modules can be loaded from a trusted site with the
   * &lt;script&gt; tag, which runs its script as a statement, not
   * an expression. The callback is of the form
   * <tt>moduleHandler.handle(newModule)</tt>.
   */
  function setNewModuleHandler(newModuleHandler) {
    myNewModuleHandler = newModuleHandler;
  }
  
  /**
   * A new-module-handler which does nothing.
   */
  var ignoreNewModule = freeze({
    handle: simpleFunc(function(newModule){})
  });
  
  /**
   * Makes and returns a fresh "normal" module handler whose outers
   * are initialized to a copy of the sharedOuters.
   * <p>
   * This handles a new module by calling it, passing it the outers
   * 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 outers = copy(sharedOuters);
    return freeze({
      getOuters: simpleFunc(function() { return outers; }),
      setOuters: simpleFunc(function(newOuters) { outers = newOuters; }),
      handle: simpleFunc(function(newModule) {
        newModule(outers);
      })
    });
  }
  
  /**
   * A module is a plugin-maker function.
   * <p>
   * loadModule(module) marks module as a simpleFunc, freezes it,
   * asks the current new-module-handler to handle it (thereby
   * notifying the handler), and returns the new module.  
   */
  function loadModule(module) {
    callPub(myNewModuleHandler, 'handle',
            [primFreeze(simpleFunc(module))]);
    return module;
  }

  var registeredOuters = [];

  /**
   * Gets or assigns the id associated with this (assumed to be)
   * outers object, registering it so that 
   * <tt>getOuters(getId(outers)) ==== outers</tt>.
   * <p>
   * This system of registration and identification allows us to
   * cajole html such as
   * <pre>&lt;a onmouseover="alert(1)"&gt;Mouse here&lt;/a&gt;</pre>
   * into html-writing JavaScript such as<pre>
   * ___OUTERS___.document.innerHTML = "
   *  &lt;a onmouseover=\"
   *    (function(___OUTERS___) {
   *      ___OUTERS___.alert(1);
   *    })(___.getOuters(" + ___.getId(___OUTERS___) + "))
   *  \"&gt;Mouse here&lt;/a&gt;
   * ";
   * </pre>
   * If this is executed by a plugin whose outers is assigned id 42,
   * it generates html with the same meaning as<pre>
   * &lt;a onmouseover="___.getOuters(42).alert(1)"&gt;Mouse here&lt;/a&gt;
   * </pre>
   * <p>
   * An outers is not registered and no id is assigned to it until the
   * first call to <tt>getId</tt>. This way, an outers 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(outers) {
    enforceType(outers, 'object', 'outers');
    var id;
    if ('id___' in outers) {
      id = enforceType(outers.id___, 'number', 'id');
    } else {
      id = outers.id___ = registeredOuters.length;
    }
    registeredOuters[id] = outers;
    return id;
  }

  /**
   * Gets the outers object registered under this id.
   * <p>
   * If it has been <tt>unregistered</tt> since the last
   * <tt>getId</tt> on it, then <tt>getOuters</tt> will fail.
   */
  function getOuters(id) {
    var result = registeredOuters[enforceType(id, 'number', 'id')];
    if (result === undefined) {
      fail('outers#', id, ' unregistered');
    }
    return result;
  }

  /**
   * If you know that this <tt>outers</tt> no longers needs to be
   * accessed by <tt>getOuters</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 outers
   * remembers its id. If asked for another <tt>getId</tt>, it
   * reregisters itself at its old id.
   */
  function unregister(outers) {
    enforceType(outers, 'object', 'outers');      
    if ('id___' in outers) {
      var id = enforceType(outers.id___, 'number', 'id');
      registeredOuters[id] = undefined;
    }
  }
  
  ////////////////////////////////////////////////////////////////////////
  // Exports
  ////////////////////////////////////////////////////////////////////////
  
  caja = {

    // Diagnostics and condition enforcement
    getLogFunc: getLogFunc, 
    setLogFunc: setLogFunc,
    log: log,

    fail: fail,
    enforce: enforce,
    enforceType: enforceType,
    enforceNat: enforceNat,
    
    // walking prototype chain, checking JSON containers
    isJSONContainer: isJSONContainer,
    freeze: freeze,
    copy: copy,
    snapshot: snapshot,
    
    // Accessing properties
    canReadPub: canReadPub,       readPub: readPub,
    canEnumPub: canEnumPub,
    canEnumOwn: canEnumOwn,       
    BREAK: BREAK,                 each: each,                   
    canCallPub: canCallPub,       callPub: callPub,
    canSetPub: canSetPub,         setPub: setPub,
    canDeletePub: canDeletePub,   deletePub: deletePub,
    
    // Other
    def: def
  };
  
  sharedOuters = {
    caja: caja,
    
    'null': null,
    'false': false,
    'true': true,
    'NaN': NaN,
    'Infinity': Infinity,
    'undefined': undefined,
    parseInt: parseInt,
    parseFloat: parseFloat,
    isNaN: isNaN,
    isFinite: isFinite,
    decodeURI: decodeURI,
    decodeURIComponent: decodeURIComponent,
    encodeURI: encodeURI,
    encodeURIComponent: encodeURIComponent,
    Math: Math,
    
    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
  };
  
  each(sharedOuters, simpleFunc(function(k, v) {
    switch (typeof v) {
    case 'object':
      if (v !== null) { primFreeze(v); }
      break;
    case 'function':
      primFreeze(v);
      break;
    }
  }));
  primFreeze(sharedOuters);
  
  ___ = {

    // Privileged fault handlers
    getKeeper: getKeeper,
    setKeeper: setKeeper,

    // walking prototype chain, checking JSON containers
    directConstructor: directConstructor,
    isFrozen: isFrozen,
    primFreeze: primFreeze,
    
    // Accessing property attributes
    canRead: canRead,             allowRead: allowRead,
    canEnum: canEnum,             allowEnum: allowEnum,
    canCall: canCall,             allowCall: allowCall,
    canSet: canSet,               allowSet: allowSet,
    canDelete: canDelete,         allowDelete: allowDelete,
    
    // Classifying functions
    isCtor: isCtor,
    isMethod: isMethod,
    isSimpleFunc: isSimpleFunc,
    ctor: ctor,                   asCtorOnly: asCtorOnly,
    asCtor: asCtor,
    method: method,               asMethod: asMethod,
    simpleFunc: simpleFunc,       asSimpleFunc: asSimpleFunc,
    setMember: setMember,
    setMemberMap: setMemberMap,
    
    // Accessing properties
    canReadProp: canReadProp,     readProp: readProp,
    canInnocentEnum: canInnocentEnum,
    canEnumProp: canEnumProp,
    canCallProp: canCallProp,     callProp: callProp,
    canSetProp: canSetProp,       setProp: setProp,
    canDeleteProp: canDeleteProp, deleteProp: deleteProp,
    
    // Other
    hasOwnProp: hasOwnProp,
    args: args,
    
    // Taming mechanism
    useGetHandler: useGetHandler, 
    useApplyHandler: useApplyHandler,
    useCallHandler: useCallHandler,
    useSetHandler: useSetHandler,
    useDeleteHandler: useDeleteHandler,

    allowSimpleFunc: allowSimpleFunc,
    allowMethod: allowMethod,
    allowMutator: allowMutator,
    enforceMatchable: enforceMatchable,
    all2: all2,
    
    // Taming decisions
    sharedOuters: sharedOuters,
    
    // Module loading
    getNewModuleHandler: getNewModuleHandler,
    setNewModuleHandler: setNewModuleHandler,
    ignoreNewModule: ignoreNewModule,
    makeNormalNewModuleHandler: makeNormalNewModuleHandler,
    loadModule: loadModule,

    getId: getId,
    getOuters: getOuters,
    unregister: unregister
  };
  
  each(caja, simpleFunc(function(k, v) {
    if (k in ___) {
      fail('internal: initialization conflict: ', k);
    }
    if (typeof v === 'function') {
      simpleFunc(v);
      allowCall(caja, k);
    }
    ___[k] = v;
  }));
  primFreeze(caja);
  
  setNewModuleHandler(makeNormalNewModuleHandler());
  
})(this);

