Revision: 5420
Author:   [email protected]
Date:     Thu May 23 10:22:00 2013
Log:      Refactor Domado property definitions.
https://codereview.appspot.com/9447044

Previously, Domado internally used a property definition helper called
'definePropertiesAwesomely' which wrapped getters/setters so that they
get an additional argument which is the property name. We now replace
'definePropertiesAwesomely' with 'Props.define', where Props also
contains functions for generating property descriptors. Props.define
accepts, in addition to property descriptors, functions which generate
property descriptors given context ('env') information including the
property name, the object (thus being able to provide setOwn-like
override functionality), and the applicable Confidence.

Including the Confidence in the context also means that PropertyTaming
(renamed PT) does not need to be explicitly instantiated for a
particular Confidence; thus the vocabulary used for amplifying access is
not dependent on the the particular type being defined.

The context-based design of Props is also intended to be of future use
in refactoring the canvas 2D context taming to prototypical style while
keeping its short taming definitions.

User-visible changes:
* There may be more cases where read-only inherited properties can be
  overridden by assignment on instances.

Additional changes:
* Nearly all methods are now defined using Props.define rather than as
  TameFoo.prototype.bar = nodeAmp(function(privates) { ... }).
  Reordered so accessor definitions come before method definitions.
* Introduced PT.ROView to express non-writable tamed properties with
  a value transformation.
* browser-test-case.js no longer defines a guest '$' function (because
  document.getElementById ends up being an accessor property, which
  can't be retrieved without using an inES5Mode conditional) and tests
  which used it now define it internally.
* window's reflexive properties are defined in its constructor rather
  than separately.

[email protected]

http://code.google.com/p/google-caja/source/detail?r=5420

Modified:
 /trunk/src/com/google/caja/plugin/domado.js
 /trunk/tests/com/google/caja/plugin/browser-test-case.js
 /trunk/tests/com/google/caja/plugin/es53-test-basic-functions-guest.html
 /trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html
 /trunk/tests/com/google/caja/plugin/es53-test-domado-forms-guest.html
 /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js

=======================================
--- /trunk/src/com/google/caja/plugin/domado.js Wed May 22 14:02:57 2013
+++ /trunk/src/com/google/caja/plugin/domado.js Thu May 23 10:22:00 2013
@@ -142,7 +142,7 @@
     return proxy[1] === 'ok';
   }());

-  domitaModules.canHaveEnumerableAccessors = (function () {
+  var canHaveEnumerableAccessors = (function() {
     // Firefox bug causes enumerable accessor properties to appear as own
     // properties of children. SES patches this by prohibiting enumerable
     // accessor properties. We work despite the bug by making all such
@@ -350,57 +350,77 @@
     // initial {} value which is not === to any property name.
   })();

+  function makeOverrideSetter(object, prop) {
+    return innocuous(function overrideSetter(newValue) {
+      if (object === this) {
+ throw new TypeError('Cannot set virtually frozen property: ' + prop);
+      }
+      if (!!Object.getOwnPropertyDescriptor(this, prop)) {
+        this[prop] = newValue;
+      }
+      // TODO(erights): Do all the inherited property checks
+      Object.defineProperty(this, prop, {
+        value: newValue,
+        writable: true,
+        enumerable: true,
+        configurable: true
+      });
+    });
+  }
+
+  /**
+ * Takes a property descriptor and, if it is a non-writable data property or
+   * an accessor with only a getter, returns a replacement descriptor which
+   * allows the property to be overridden by assignment.
+   *
+   * Note that the override behavior is only of value when the object is
+ * inherited from, so properties defined on "instances" should not use this as
+   * it is unnecessarily expensive.
+   */
+  function allowNonWritableOverride(object, prop, desc) {
+    if (!('value' in desc && !desc.writable)) {
+      return desc;
+    }
+    var value = desc.value;
+    // TODO(kpreid): Duplicate of tamperProof() from repairES5.js.
+ // We should extract that getter/setter pattern as a separate routine; but + // note that we need to make the same API available from ES5/3 (though not
+    // the same behavior, since ES5/3 rejects the 'override mistake',
+    // ASSIGN_CAN_OVERRIDE_FROZEN in repairES5 terms) available from ES5/3.
+    return {
+      configurable: desc.configurable,
+      enumerable: desc.enumerable,
+      get: innocuous(function overrideGetter() { return value; }),
+      set: makeOverrideSetter(object, prop)
+    };
+  }
+
   /**
    * Identical to Object.defineProperty except that if used to define a
-   * non-writable data property, it converts it to an accessor as cajVM.def
+ * non-writable data property, it converts it to an accessor as cajaVM.def
    * does.
    */
-  function definePropertyAllowOverride(obj, name, desc) {
-    var existing = Object.getOwnPropertyDescriptor(obj, name);
-    if ('value' in desc && !desc.writable &&
-        (!existing || existing.configurable)) {
-      var value = desc.value;
-      // TODO(kpreid): Duplicate of tamperProof() from repairES5.js.
- // We should extract that getter/setter pattern as a separate routine; but - // note that we need to make the same API available from ES5/3 (though not
-      // the same behavior, since ES5/3 rejects the 'override mistake',
- // ASSIGN_CAN_OVERRIDE_FROZEN in repairES5 terms) available from ES5/3.
-      desc = {
-        configurable: desc.configurable,
-        enumerable: desc.enumerable,
-        get: cajaVM.constFunc(function setOwnGetter() { return value; }),
-        set: cajaVM.constFunc(function setOwnSetter(newValue) {
-          if (obj === this) {
-            throw new TypeError('Cannot set virtually frozen property: ' +
-                                name);
-          }
-          if (!!Object.getOwnPropertyDescriptor(this, name)) {
-            this[name] = newValue;
-          }
-          // TODO(erights): Do all the inherited property checks
-          Object.defineProperty(this, name, {
-            value: newValue,
-            writable: true,
-            enumerable: true,
-            configurable: true
-          });
-        })
-      };
+  function definePropertyAllowOverride(obj, prop, desc) {
+    var existing = Object.getOwnPropertyDescriptor(obj, prop);
+ // .configurable test is necessary for e.g. <function>.prototype which is
+    // always own and non-configurable data.
+    if (!existing || existing.configurable) {
+      desc = allowNonWritableOverride(obj, prop, desc);
     }
-    return Object.defineProperty(obj, name, desc);
+    return Object.defineProperty(obj, prop, desc);
   }

   /**
-   * Like object[propName] = value, but DWIMs enumerability.
+ * Like object[propName] = value, but DWIMs enumerability and allows override.
    *
* The property's enumerability is inherited from the ancestor's property's
    * descriptor. The property is not writable or configurable.
    */
-  function setOwn(object, propName, value) {
-    propName += '';
+  function setOwn(object, prop, value) {
+    prop += '';
     // IE<=8, DOM objects are missing 'valueOf' property'
-    var desc = domitaModules.getPropertyDescriptor(object, propName);
-    definePropertyAllowOverride(object, propName, {
+    var desc = domitaModules.getPropertyDescriptor(object, prop);
+    definePropertyAllowOverride(object, prop, {
       enumerable: desc ? desc.enumerable : false,
       value: value
     });
@@ -410,8 +430,8 @@
    * Shortcut for an unmodifiable property. Currently used only where
* overriding is not of interest, so doesn't do definePropertyAllowOverride.
    */
-  function setFinal(object, propName, value) {
-    Object.defineProperty(object, propName, {
+  function setFinal(object, prop, value) {
+    Object.defineProperty(object, prop, {
       enumerable: true,
       value: value
     });
@@ -584,13 +604,277 @@
     return cajaVM.def(Confidence);
   })();

- // Explicit marker that this is a function intended to be exported that needs
-  // no other wrapping.
+  /**
+ * Explicit marker that this is a function intended to be exported that needs
+   * no other wrapping. Also, remove the function's .prototype object.
+   *
+ * As a matter of style, fn should always be a function literal; think of this
+   * as a modifier to function literal syntax. This ensures that it is not
+   * misapplied to functions which have more complex circumstances.
+   */
// TODO(kpreid): Verify this in tests, e.g. by adding a property and checking
   function innocuous(f) {
     return cajaVM.constFunc(f);
   }

+  var PROPERTY_DESCRIPTOR_KEYS = {
+    configurable: 0,
+    enumerable: 0,
+    writable: 0,
+    value: 0,
+    get: 0,
+    set: 0
+  };
+
+  /**
+   * Utilities for defining properties.
+   */
+  var Props = (function() {
+    var NO_PROPERTY = null;
+
+    /**
+     * Return a function returning an environment. Environments are context
+     * implicitly available to our extended property descriptors ('property
+ * specifiers') such as the name of the property being defined (so that a + * single spec can express "forward this property to the same-named property
+     * on another object", for example).
+     */
+    function makeEnvOuter(object, opt_confidence) {
+ // TODO(kpreid): confidence typename is not actually currently specific
+      // enough for debugging (node subclasses, in particular) but it's the
+      // only formal name we have right now.
+ var typename = opt_confidence ? opt_confidence.typename : String(object);
+      var amplifying = opt_confidence
+          ? opt_confidence.amplifying.bind(opt_confidence)
+          : function(fn) {
+ throw new Error('Props.define: no confidence, no amplifying');
+            };
+      return function(prop) {
+        var msgPrefix = 'Props.define: ' + typename + '.' + prop;
+        return {
+          object: object,
+          prop: prop,
+          msgPrefix: msgPrefix,
+          amplifying: amplifying
+        };
+      };
+    }
+
+    /**
+     * Convert a property spec (our notion) to a property descriptor (ES5
+     * notion) and validate/repair/kludge it.
+     *
+     * The returned property descriptor is fresh.
+     */
+    function specToDesc(env, propSpec) {
+      if (propSpec === NO_PROPERTY) { return propSpec; }
+      switch (typeof propSpec) {
+        case 'function':
+          if (!propSpec.isPropMaker) {
+            // TODO(kpreid): Temporary check for refactoring.
+            throw new TypeError(env.msgPrefix +
+                ' defined with a function not a prop maker');
+          }
+          return specToDesc(env, propSpec(env));
+
+        case 'object':
+          // Make a copy so that we can mutate desc, and validate.
+          var desc = copyAndValidateDesc(env, propSpec);
+
+          if ('get' in desc || 'set' in desc) {
+            // Firefox bug workaround; see canHaveEnumerableAccessors.
+ desc.enumerable = desc.enumerable && canHaveEnumerableAccessors;
+          }
+
+          if (desc.get && !Object.isFrozen(desc.get)) {
+            if (typeof console !== 'undefined') {
+ console.warn(env.msgPrefix + ' getter is not frozen; fixing.');
+            }
+            cajaVM.constFunc(desc.get);
+          }
+          if (desc.set && !Object.isFrozen(desc.set)) {
+            if (typeof console !== 'undefined') {
+ console.warn(env.msgPrefix + ' setter is not frozen; fixing.');
+            }
+            cajaVM.constFunc(desc.set);
+          }
+
+          return desc;
+
+        default:
+          throw new TypeError(env.msgPrefix +
+              ' spec not a function or descriptor (' + propSpec + ')');
+      }
+    }
+
+    function copyAndValidateDesc(env, inDesc) {
+      var desc = {};
+      for (var k in inDesc) {
+        if (PROPERTY_DESCRIPTOR_KEYS.hasOwnProperty(k)) {
+          // Could imagine doing a type-check here, but not bothering.
+          desc[k] = inDesc[k];
+        } else {
+          throw new TypeError(env.msgPrefix +
+              ': Unexpected key in property descriptor: ' + k);
+        }
+      }
+      return desc;
+    }
+
+    /**
+     * For each enumerable p: s in propSpecs, do
+     *
+     *   Object.defineProperty(object, p, specToDesc(..., s))
+     *
+     * where specToDesc() passes plain property descriptors through and can
+     * also construct property descriptors based on the specified 'p' or
+     * 'confidence' if s is one of the property-maker functions provided by
+     * Props.
+     *
+ * Additionally, getters and setters are checked for being frozen, and the
+     * syntax of the descriptor is checked.
+     */
+    function define(object, opt_confidence, propSpecs) {
+      var makeEnv = makeEnvOuter(object, opt_confidence);
+      for (var prop in propSpecs) {
+        var desc = specToDesc(makeEnv(prop), propSpecs[prop]);
+        if (desc !== NO_PROPERTY) {
+          Object.defineProperty(object, prop, desc);
+        }
+      }
+    }
+
+    /**
+     * Alias a property spec: it executes as if given the mapName.
+     */
+    function rename(mapName, propSpec) {
+      return markPropMaker(function(env) {
+        return specToDesc(
+          Object.create(env, {
+            prop: {value: mapName}
+          }),
+          propSpec);
+      });
+    }
+
+    /**
+     * Make this property a getter/setter which forwards to the other name.
+     */
+    function alias(enumerable, otherProp) {
+      return {
+        enumerable: enumerable,
+        get: innocuous(function aliasGetter() { return this[otherProp]; }),
+        set: innocuous(function aliasSetter(v) { this[otherProp] = v; })
+      };
+    }
+
+    /**
+     * A non-writable, possibly enumerable, overridable constant-valued
+     * property.
+     */
+    function overridable(enumerable, value) {
+      return markPropMaker(function overridablePropMaker(env) {
+        return allowNonWritableOverride(env.object, env.prop, {
+          enumerable: enumerable,
+          value: value
+        });
+      });
+    }
+
+    /**
+     * Add override to an accessor property.
+     */
+    function addOverride(spec) {
+      return markPropMaker(function overridablePropMaker(env) {
+        var desc = specToDesc(env, spec);
+        if ('set' in desc || 'value' in desc) {
+          throw new Error('bad addOverride');
+        } else {
+          desc.set = makeOverrideSetter(env.object, env.prop);
+        }
+        return desc;
+      });
+    }
+
+    /**
+     * An overridable, enumerable method.
+     *
+     * fn will have innocuous() applied to it (thus ending up with its
+     * .prototype removed).
+     *
+ * As a matter of style, fn should always be a function literal; think of + * this as a modifier to function literal syntax. This ensures that it is
+     * not misapplied to functions which have more complex circumstances.
+     */
+    function plainMethod(fn) {
+      return overridable(true, innocuous(fn));
+    }
+
+    /**
+     * An overridable, enumerable, amplifying (as in confidence.amplifying)
+     * method.
+     *
+     * The function should be a function literal.
+     */
+    function ampMethod(fn) {
+      return markPropMaker(function(env) {
+        return overridable(true, env.amplifying(fn));
+      });
+    }
+
+    /**
+ * A non-overridable, enumerable, amplifying (as in confidence.amplifying)
+     * getter.
+     *
+     * The function should be a function literal.
+     */
+    function ampGetter(fn) {
+      return markPropMaker(function(env) {
+        return {
+          enumerable: true,
+          get: env.amplifying(fn)
+        };
+      });
+    }
+
+    /**
+     * Checkable label for all property specs implemented functions.
+ * TODO(kpreid): Have fewer custom property makers outside of Props itself;
+     * provide tools to build them instead.
+     */
+    function markPropMaker(fn) {
+      fn.isPropMaker = true;
+      // causes a TypeError inside ToPropertyDescriptor
+      fn.get = '<PropMaker used as propdesc canary>';
+      return fn;
+    }
+
+    /**
+     * Only define the property if the condition is true, or choose between
+     * two specs based on the condition.
+     */
+    function cond(condition, specThen, specElse) {
+      if (condition) {
+        return specThen;
+      } else {
+        return specElse === undefined ? NO_PROPERTY : specElse;
+      }
+    }
+
+    return {
+      define: define,
+      rename: rename,
+      alias: alias,
+      overridable: overridable,
+      addOverride: addOverride,
+      plainMethod: plainMethod,
+      ampMethod: ampMethod,
+      ampGetter: ampGetter,
+      cond: cond,
+      markPropMaker: markPropMaker
+    };
+  })();
+
   var CollectionProxyHandler = (function() {
     /**
      * Handler for a proxy which presents value properties derived from an
@@ -712,7 +996,6 @@
       naiveUriPolicy,
       getBaseURL) {
     var Confidence = domitaModules.Confidence;
- var canHaveEnumerableAccessors = domitaModules.canHaveEnumerableAccessors;
     // See http://www.w3.org/TR/XMLHttpRequest/

     // TODO(ihab.awad): Improve implementation (interleaving, memory leaks)
@@ -738,9 +1021,9 @@
         Object.preventExtensions(privates);
       });
     }
-    Object.defineProperties(TameXMLHttpRequest.prototype, {
+    Props.define(TameXMLHttpRequest.prototype, TameXHRConf, {
       onreadystatechange: {
-        enumerable: canHaveEnumerableAccessors,
+        enumerable: true,
         set: amplifying(function(privates, handler) {
           // TODO(ihab.awad): Do we need more attributes of the event than
// 'target'? May need to implement full "tame event" wrapper similar
@@ -754,150 +1037,135 @@
           privates.handler = handler;
         })
       },
-      readyState: {
-        enumerable: canHaveEnumerableAccessors,
-        get: amplifying(function(privates) {
-          // The ready state should be a number
-          return Number(privates.feral.readyState);
-        })
-      },
-      responseText: {
-        enumerable: canHaveEnumerableAccessors,
-        get: amplifying(function(privates) {
-          var result = privates.feral.responseText;
-          return (result === undefined || result === null)
-              ? result : String(result);
-        })
-      },
-      responseXML: {
-        enumerable: canHaveEnumerableAccessors,
-        get: amplifying(function(privates) {
-          var feralXml = privates.feral.responseXML;
-          if (feralXml === null || feralXml === undefined) {
-            // null = 'The response did not parse as XML.'
-            return null;
-          } else {
-            // TODO(ihab.awad): Implement a taming layer for XML. Requires
- // generalizing the HTML node hierarchy as well so we have a unified
-            // implementation.
+ // TODO(kpreid): This are PT.ROView properties but our layering does not
+      // offer that here.
+      readyState: Props.ampGetter(function(privates) {
+        // The ready state should be a number
+        return Number(privates.feral.readyState);
+      }),
+      responseText: Props.ampGetter(function(privates) {
+        var result = privates.feral.responseText;
+        return (result === undefined || result === null)
+            ? result : String(result);
+      }),
+      responseXML: Props.ampGetter(function(privates) {
+        var feralXml = privates.feral.responseXML;
+        if (feralXml === null || feralXml === undefined) {
+          // null = 'The response did not parse as XML.'
+          return null;
+        } else {
+          // TODO(ihab.awad): Implement a taming layer for XML. Requires
+ // generalizing the HTML node hierarchy as well so we have a unified
+          // implementation.

- // This kludge is just enough to keep the jQuery tests from freezing.
-            var node = {nodeName: '#document'};
-            node.cloneNode = function () { return node; };
-            node.toString = function () {
-              return 'Caja does not support XML.';
-            };
-            return {documentElement: node};
-          }
-        })
-      },
-      status: {
-        enumerable: canHaveEnumerableAccessors,
-        get: amplifying(function(privates) {
-          var result = privates.feral.status;
-          return (result === undefined || result === null) ?
-            result : Number(result);
-        })
-      },
-      statusText: {
-        enumerable: canHaveEnumerableAccessors,
-        get: amplifying(function(privates) {
-          var result = privates.feral.statusText;
-          return (result === undefined || result === null) ?
-            result : String(result);
-        })
-      }
-    });
-    TameXMLHttpRequest.prototype.open = amplifying(function(
-        privates, method, URL, opt_async, opt_userName, opt_password) {
-      method = String(method);
-      URL = URI.utils.resolve(getBaseURL(), String(URL));
-      // The XHR interface does not tell us the MIME type in advance, so we
-      // must assume the broadest possible.
-      var safeUri = uriRewrite(
-          naiveUriPolicy,
-          URL, html4.ueffects.SAME_DOCUMENT, html4.ltypes.DATA,
-          {
-            "TYPE": "XHR",
-            "XHR_METHOD": method,
-            "XHR": true  // Note: this hint is deprecated
-          });
-      // If the uriPolicy rejects the URL, we throw an exception, but we do
- // not put the URI in the exception so as not to put the caller at risk
-      // of some code in its stack sniffing the URI.
- if ("string" !== typeof safeUri) { throw 'URI violates security policy'; }
-      switch (arguments.length) {
-      case 2:
-        privates.async = true;
-        privates.feral.open(method, safeUri);
-        break;
-      case 3:
-        privates.async = opt_async;
-        privates.feral.open(method, safeUri, Boolean(opt_async));
-        break;
-      case 4:
-        privates.async = opt_async;
-        privates.feral.open(
-            method, safeUri, Boolean(opt_async), String(opt_userName));
-        break;
-      case 5:
-        privates.async = opt_async;
-        privates.feral.open(
-            method, safeUri, Boolean(opt_async), String(opt_userName),
-            String(opt_password));
-        break;
-      default:
- throw 'XMLHttpRequest cannot accept ' + arguments.length + ' arguments';
-        break;
-      }
-    });
-    TameXMLHttpRequest.prototype.setRequestHeader = amplifying(
-        function(privates, label, value) {
-      privates.feral.setRequestHeader(String(label), String(value));
-    });
-    TameXMLHttpRequest.prototype.send = amplifying(function(
-        privates, opt_data) {
-      if (arguments.length === 0) {
- // TODO(ihab.awad): send()-ing an empty string because send() with no
-        // args does not work on FF3, others?
-        privates.feral.send('');
-      } else if (typeof opt_data === 'string') {
-        privates.feral.send(opt_data);
-      } else /* if XML document */ {
-        // TODO(ihab.awad): Expect tamed XML document; unwrap and send
-        privates.feral.send('');
-      }
+ // This kludge is just enough to keep the jQuery tests from freezing.
+          var node = {nodeName: '#document'};
+          node.cloneNode = function () { return node; };
+          node.toString = function () {
+            return 'Caja does not support XML.';
+          };
+          return {documentElement: node};
+        }
+      }),
+      status: Props.ampGetter(function(privates) {
+        var result = privates.feral.status;
+        return (result === undefined || result === null) ?
+          result : Number(result);
+      }),
+      statusText: Props.ampGetter(function(privates) {
+        var result = privates.feral.statusText;
+        return (result === undefined || result === null) ?
+          result : String(result);
+      }),
+      open: Props.ampMethod(function(
+          privates, method, URL, opt_async, opt_userName, opt_password) {
+        method = String(method);
+        URL = URI.utils.resolve(getBaseURL(), String(URL));
+ // The XHR interface does not tell us the MIME type in advance, so we
+        // must assume the broadest possible.
+        var safeUri = uriRewrite(
+            naiveUriPolicy,
+            URL, html4.ueffects.SAME_DOCUMENT, html4.ltypes.DATA,
+            {
+              "TYPE": "XHR",
+              "XHR_METHOD": method,
+              "XHR": true  // Note: this hint is deprecated
+            });
+ // If the uriPolicy rejects the URL, we throw an exception, but we do + // not put the URI in the exception so as not to put the caller at risk
+        // of some code in its stack sniffing the URI.
+        if ('string' !== typeof safeUri) {
+          throw 'URI violates security policy';
+        }
+        switch (arguments.length) {
+        case 2:
+          privates.async = true;
+          privates.feral.open(method, safeUri);
+          break;
+        case 3:
+          privates.async = opt_async;
+          privates.feral.open(method, safeUri, Boolean(opt_async));
+          break;
+        case 4:
+          privates.async = opt_async;
+          privates.feral.open(
+              method, safeUri, Boolean(opt_async), String(opt_userName));
+          break;
+        case 5:
+          privates.async = opt_async;
+          privates.feral.open(
+              method, safeUri, Boolean(opt_async), String(opt_userName),
+              String(opt_password));
+          break;
+        default:
+          throw 'XMLHttpRequest cannot accept ' + arguments.length +
+              ' arguments';
+          break;
+        }
+      }),
+      setRequestHeader: Props.ampMethod(function(privates, label, value) {
+        privates.feral.setRequestHeader(String(label), String(value));
+      }),
+      send: Props.ampMethod(function(privates, opt_data) {
+        if (arguments.length === 0) {
+ // TODO(ihab.awad): send()-ing an empty string because send() with no
+          // args does not work on FF3, others?
+          privates.feral.send('');
+        } else if (typeof opt_data === 'string') {
+          privates.feral.send(opt_data);
+        } else /* if XML document */ {
+          // TODO(ihab.awad): Expect tamed XML document; unwrap and send
+          privates.feral.send('');
+        }

-      // Firefox does not call the 'onreadystatechange' handler in
-      // the case of a synchronous XHR. We simulate this behavior by
-      // calling the handler explicitly.
-      if (privates.feral.overrideMimeType) {
-        // This is Firefox
-        if (!privates.async && privates.handler) {
-          var evt = { target: this };
-          privates.handler.call(void 0, evt);
+        // Firefox does not call the 'onreadystatechange' handler in
+        // the case of a synchronous XHR. We simulate this behavior by
+        // calling the handler explicitly.
+        if (privates.feral.overrideMimeType) {
+          // This is Firefox
+          if (!privates.async && privates.handler) {
+            var evt = { target: this };
+            privates.handler.call(void 0, evt);
+          }
         }
-      }
+      }),
+      abort: Props.ampMethod(function(privates) {
+        privates.feral.abort();
+      }),
+      getAllResponseHeaders: Props.ampMethod(function(privates) {
+        var result = privates.feral.getAllResponseHeaders();
+        return (result === undefined || result === null) ?
+          result : String(result);
+      }),
+      getResponseHeader: Props.ampMethod(function(privates, headerName) {
+        var result = privates.feral.getResponseHeader(String(headerName));
+        return (result === undefined || result === null) ?
+          result : String(result);
+      }),
+      toString: Props.overridable(false, innocuous(function() {
+        return 'Not a real XMLHttpRequest';
+      }))
     });
-    TameXMLHttpRequest.prototype.abort = amplifying(function(privates) {
-      privates.feral.abort();
-    });
-    TameXMLHttpRequest.prototype.getAllResponseHeaders =
-        amplifying(function(privates) {
-      var result = privates.feral.getAllResponseHeaders();
-      return (result === undefined || result === null) ?
-        result : String(result);
-    });
-    TameXMLHttpRequest.prototype.getResponseHeader =
-        amplifying(function(privates, headerName) {
-      var result = privates.feral.getResponseHeader(String(headerName));
-      return (result === undefined || result === null) ?
-        result : String(result);
-    });
-    setOwn(TameXMLHttpRequest.prototype, 'toString', innocuous(function() {
-      return 'Not a real XMLHttpRequest';
-    }));
-
     return cajaVM.def(TameXMLHttpRequest);
   };

@@ -1115,70 +1383,9 @@
     var makeFunctionAccessible = rulebreaker.makeFunctionAccessible;

     var Confidence = domitaModules.Confidence;
- var canHaveEnumerableAccessors = domitaModules.canHaveEnumerableAccessors;

-    /**
-     * For each enumerable p: d in propDescs, do the equivalent of
-     *
-     *   Object.defineProperty(object, p, d)
-     *
-     * except that the property descriptor d can have additional keys:
-     *
-     *    extendedAccessors:
-     *      If present and true, property accessor functions receive the
-     *      property name as an additional argument.
-     */
-    function definePropertiesAwesomely(object, propDescs) {
-      for (var prop in propDescs) {
-        var desc = {};
-        for (var k in propDescs[prop]) {
-          desc[k] = propDescs[prop][k];
-        }
-        if ('get' in desc || 'set' in desc) {
- // Firefox bug workaround; see comments on canHaveEnumerableAccessors.
-          desc.enumerable = desc.enumerable && canHaveEnumerableAccessors;
-        }
-        if (desc.extendedAccessors) {
-          delete desc.extendedAccessors;
- (function (prop, extGet, extSet) { // @*$#*;%#<$ non-lexical scoping
-            if (extGet) {
-              desc.get = cajaVM.constFunc(function() {
-                return extGet.call(this, prop);
-              });
-            }
-            if (desc.set) {
-              desc.set = cajaVM.constFunc(function(value) {
-                return extSet.call(this, value, prop);
-              });
-            }
-          })(prop, desc.get, desc.set);
-        }
-        if (desc.get && !Object.isFrozen(desc.get)) {
-          if (typeof console !== 'undefined') {
-            console.warn("Getter for ", prop, " of ", object,
-                " is not frozen; fixing.");
-          }
-          cajaVM.constFunc(desc.get);
-        }
-        if (desc.set && !Object.isFrozen(desc.set)) {
-          if (typeof console !== 'undefined') {
-            console.warn("Setter for ", prop, " of ", object,
-                " is not frozen; fixing.");
-          }
-          cajaVM.constFunc(desc.set);
-        }
-        Object.defineProperty(object, prop, desc);
-      }
-    }
-
-    function forOwnKeys(obj, fn) {
-      for (var i in obj) {
-        if (!Object.prototype.hasOwnProperty.call(obj, i)) { continue; }
-        fn(i, obj[i]);
-      }
-    }
-
-    // value transforming functions
+    // value transforming/trivial functions
+    function noop() { return undefined; }
     function identity(x) { return x; }
     function defaultToEmptyStr(x) { return x || ''; }

@@ -2317,10 +2524,10 @@
       }

       /**
- * Construct property descriptors suitable for taming objects which use
-       * the specified confidence, such that confidence.p(obj).feral is the
-       * feral object to forward to and confidence.p(obj).policy is a node
-       * policy object for writability decisions.
+ * Property specs (as for Props.define) suitable for taming wrappers where
+       * confidence.p(obj).feral is the feral object to forward to and
+       * confidence.p(obj).policy is a node policy object for writability
+       * decisions.
        *
        * Lowercase properties are property descriptors; uppercase ones are
        * constructors for parameterized property descriptors.
@@ -2330,197 +2537,211 @@
        * properties; we tame as a shortcut to protect against unexpected
        * behavior (or misuse) causing breaches.
        */
-      function PropertyTaming(confidence) {
-        var amplifying = confidence.amplifying;
-
-        return cajaVM.def({
-          /**
- * Ensure that a taming wrapper for the given underlying property is
-           * memoized via the taming membrane, but only if 'memo' is true.
-           */
-          TameMemoIf: function(memo, prop, tamer) {
-            assert(typeof memo === 'boolean');  // in case of bad data
+      var PT = cajaVM.def({
+        /**
+ * Ensure that a taming wrapper for the given underlying property is
+         * memoized via the taming membrane, but only if 'memo' is true.
+         */
+        TameMemoIf: function(memo, feralProp, tamer) {
+          assert(typeof memo === 'boolean');  // in case of bad data
+          return Props.markPropMaker(function(env) {
             return {
               enumerable: true,
-              extendedAccessors: false,
-              get: memo ? amplifying(function(privates) {
-                var feral = privates.feral[prop];
+              get: memo ? env.amplifying(function(privates) {
+                var feral = privates.feral[feralProp];
                 if (!taming.hasTameTwin(feral)) {
                   taming.tamesTo(feral, tamer.call(this, feral));
                 }
                 return taming.tame(feral);
-              }) : amplifying(function(privates) {
-                return tamer.call(this, privates.feral[prop]);
+              }) : env.amplifying(function(privates) {
+                return tamer.call(this, privates.feral[feralProp]);
               })
             };
-          },
+          });
+        },

-          /**
- * Property descriptor for properties which have the value the feral
-           * object does and are not assignable.
-           */
-          ro: {
+        /**
+ * Property descriptor for properties which have the value the feral
+         * object does and are not assignable.
+         */
+        ro: Props.markPropMaker(function(env) {
+          var prop = env.prop;
+          return {
             enumerable: true,
-            extendedAccessors: true,
-            get: amplifying(function(privates, prop) {
+            get: env.amplifying(function(privates) {
               return taming.tame(privates.feral[prop]);
             })
-          },
+          };
+        }),

-          /**
- * Property descriptor for properties which have the value the feral
-           * object does, and are assignable if the wrapper is editable.
-           */
-          rw: {
+        /**
+ * Property descriptor for properties which have a transformed view of
+         * the value the feral object does and are not assignable.
+         */
+        ROView: function(transform) {
+          return Props.markPropMaker(function(env) {
+            var prop = env.prop;
+            return {
+              enumerable: true,
+              get: env.amplifying(function(privates) {
+                return transform(privates.feral[prop]);
+              })
+            };
+          });
+        },
+
+        /**
+ * Property descriptor for properties which have the value the feral
+         * object does, and are assignable if the wrapper is editable.
+         */
+        rw: Props.markPropMaker(function(env) {
+          var prop = env.prop;
+          return {
             enumerable: true,
-            extendedAccessors: true,
-            get: amplifying(function(privates, prop) {
+            get: env.amplifying(function(privates) {
               return taming.tame(privates.feral[prop]);
             }),
-            set: amplifying(function(privates, value, prop) {
+            set: env.amplifying(function(privates, value) {
               privates.policy.requireEditable();
               privates.feral[prop] = taming.untame(value);
             })
-          },
+          };
+        }),

-          /**
- * Property descriptor for properties which have the value the feral - * object does, and are assignable (with a predicate restricting the
-           * values which may be assigned) if the wrapper is editable.
-           * TODO(kpreid): Use guards instead of predicates.
-           */
-          RWCond: function(predicate) {
+        /**
+ * Property descriptor for properties which have the value the feral + * object does, and are assignable (with a predicate restricting the
+         * values which may be assigned) if the wrapper is editable.
+         * TODO(kpreid): Use guards instead of predicates.
+         */
+        RWCond: function(predicate) {
+          return Props.markPropMaker(function(env) {
+            var prop = env.prop;
             return {
               enumerable: true,
-              extendedAccessors: true,
-              get: amplifying(function(privates, prop) {
+              get: env.amplifying(function(privates) {
                 return taming.tame(privates.feral[prop]);
               }),
-              set: amplifying(function(privates, value, prop) {
+              set: env.amplifying(function(privates, value) {
                 privates.policy.requireEditable();
                 if (predicate(value)) {
                   privates.feral[prop] = taming.untame(value);
                 }
               })
             };
-          },
+          });
+        },

-          /**
-           * Property descriptor for properties which have a different name
-           * than what they map to (e.g. labelElement.htmlFor vs.
-           * <label for=...>).
-           * This function changes the name the extended accessor property
-           * descriptor 'desc' sees.
-           */
-          Rename: function(mapName, desc) {
-            return {
-              enumerable: true,
-              extendedAccessors: true,
-              get: innocuous(function (prop) {
-                return desc.get.call(this, mapName);
-              }),
-              set: innocuous(function (value, prop) {
-                return desc.set.call(this, value, mapName);
-              })
-            };
-          },
-
-          /**
- * Property descriptor for forwarded properties which have node values - * which may be nodes that might be outside of the virtual document.
-           */
-          related: {
+        /**
+ * Property descriptor for forwarded properties which have node values + * which may be nodes that might be outside of the virtual document.
+         */
+        related: Props.markPropMaker(function(env) {
+          var prop = env.prop;
+          return {
             enumerable: true,
-            extendedAccessors: true,
-            get: amplifying(function(privates, prop) {
+            get: env.amplifying(function(privates) {
               if (privates.policy.upwardNavigation) {
- // TODO(kpreid): Can we move this check *into* tameRelatedNode? - return tameRelatedNode(privates.feral[prop], defaultTameNode);
+                // TODO(kpreid): Can we move this check *into*
+                // tameRelatedNode?
+                return tameRelatedNode(privates.feral[prop]);
               } else {
                 return null;
               }
             })
-          },
+          };
+        }),

-          /**
-           * Property descriptor which maps to an attribute or property, is
-           * assignable, and has the value transformed in some way.
- * @param {boolean} useAttrGetter true if the getter should delegate
-           *     to {@code this.getAttribute}.  That method is assumed to
- * already be trusted though {@code toValue} is still called on
***The diff for this file has been truncated for email.***
=======================================
--- /trunk/tests/com/google/caja/plugin/browser-test-case.js Fri May 17 17:46:31 2013 +++ /trunk/tests/com/google/caja/plugin/browser-test-case.js Thu May 23 10:22:00 2013
@@ -525,12 +525,6 @@

   standardImports.console = frame.tame(fakeConsole);

-  if (frame.div) {
-    standardImports.$ = frame.tame(frame.markFunction(function(id) {
-      return frame.untame(frame.imports.document.getElementById(id));
-    }));
-  }
-
   standardImports.inES5Mode = inES5Mode;
   standardImports.proxiesAvailableToTamingCode = inES5Mode
       // In ES5, Domado runs in the taming frame's real global env
=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-basic-functions-guest.html Mon Jun 4 10:44:13 2012 +++ /trunk/tests/com/google/caja/plugin/es53-test-basic-functions-guest.html Thu May 23 10:22:00 2013
@@ -15,6 +15,10 @@
  - limitations under the License.
 -->

+<script>
+  function $(id) { return document.getElementById(id); }
+</script>
+
 <div id="testStaticDom" class="testcontainer waiting">
   testStaticDom
   <p>Foo bar baz</p>
=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html Wed May 22 16:28:32 2013 +++ /trunk/tests/com/google/caja/plugin/es53-test-domado-dom-guest.html Thu May 23 10:22:00 2013
@@ -2303,8 +2303,8 @@
 <script type="text/javascript">
   jsunitRegister('testSizeProperties',
                  function testSizeProperties() {
-    assertEquals('number', typeof window.pageXOffset);
-    assertEquals('number', typeof window.pageYOffset);
+    assertEquals('pageXOffset', 'number', typeof window.pageXOffset);
+    assertEquals('pageYOffset', 'number', typeof window.pageYOffset);
     assertTrue('sign of pageXOffset', window.pageXOffset >= 0);
     assertTrue('sign of pageYOffset', window.pageYOffset >= 0);

=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-domado-forms-guest.html Wed May 1 10:22:42 2013 +++ /trunk/tests/com/google/caja/plugin/es53-test-domado-forms-guest.html Thu May 23 10:22:00 2013
@@ -14,6 +14,10 @@
  - limitations under the License.
 -->

+<script>
+  function $(id) { return document.getElementById(id); }
+</script>
+
 <!-- form element named 'id' triggers bug 1090. -->
 <!-- NOTE, this form must be first, otherwise ff3.5.x will produce
      false passes due to an optimizer bug.
=======================================
--- /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js Mon May 20 13:12:41 2013 +++ /trunk/tests/com/google/caja/plugin/es53-test-scan-guest.js Thu May 23 10:22:00 2013
@@ -160,7 +160,7 @@
         // work around SES patching frozen value properties into accessors
         /defProp\(this, name, \{/.exec(String(desc.set)) ||
         // or Domado doing the same, slightly differently
-        getFunctionName(desc.get) === 'setOwnGetter');
+        getFunctionName(desc.get) === 'overrideGetter');
   }

   function getFunctionName(fun) {

--

--- You received this message because you are subscribed to the Google Groups "Google Caja Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to