/ cloudformation-templates / node_modules / jsdom / lib / jsdom / named-properties-tracker.js
named-properties-tracker.js
  1  "use strict";
  2  // https://heycam.github.io/webidl/#idl-named-properties
  3  
  4  const IS_NAMED_PROPERTY = Symbol("is named property");
  5  const TRACKER = Symbol("named property tracker");
  6  
  7  /**
  8   * Create a new NamedPropertiesTracker for the given `object`.
  9   *
 10   * Named properties are used in DOM to let you lookup (for example) a Node by accessing a property on another object.
 11   * For example `window.foo` might resolve to an image element with id "foo".
 12   *
 13   * This tracker is a workaround because the ES6 Proxy feature is not yet available.
 14   *
 15   * @param {Object} object Object used to write properties to
 16   * @param {Object} objectProxy Object used to check if a property is already defined
 17   * @param {Function} resolverFunc Each time a property is accessed, this function is called to determine the value of
 18   *        the property. The function is passed 3 arguments: (object, name, values).
 19   *        `object` is identical to the `object` parameter of this `create` function.
 20   *        `name` is the name of the property.
 21   *        `values` is a function that returns a Set with all the tracked values for this name. The order of these
 22   *        values is undefined.
 23   *
 24   * @returns {NamedPropertiesTracker}
 25   */
 26  exports.create = function (object, objectProxy, resolverFunc) {
 27    if (object[TRACKER]) {
 28      throw Error("A NamedPropertiesTracker has already been created for this object");
 29    }
 30  
 31    const tracker = new NamedPropertiesTracker(object, objectProxy, resolverFunc);
 32    object[TRACKER] = tracker;
 33    return tracker;
 34  };
 35  
 36  exports.get = function (object) {
 37    if (!object) {
 38      return null;
 39    }
 40  
 41    return object[TRACKER] || null;
 42  };
 43  
 44  function NamedPropertiesTracker(object, objectProxy, resolverFunc) {
 45    this.object = object;
 46    this.objectProxy = objectProxy;
 47    this.resolverFunc = resolverFunc;
 48    this.trackedValues = new Map(); // Map<Set<value>>
 49  }
 50  
 51  function newPropertyDescriptor(tracker, name) {
 52    const emptySet = new Set();
 53  
 54    function getValues() {
 55      return tracker.trackedValues.get(name) || emptySet;
 56    }
 57  
 58    const descriptor = {
 59      enumerable: true,
 60      configurable: true,
 61      get() {
 62        return tracker.resolverFunc(tracker.object, name, getValues);
 63      },
 64      set(value) {
 65        Object.defineProperty(tracker.object, name, {
 66          enumerable: true,
 67          configurable: true,
 68          writable: true,
 69          value
 70        });
 71      }
 72    };
 73  
 74    descriptor.get[IS_NAMED_PROPERTY] = true;
 75    descriptor.set[IS_NAMED_PROPERTY] = true;
 76    return descriptor;
 77  }
 78  
 79  /**
 80   * Track a value (e.g. a Node) for a specified name.
 81   *
 82   * Values can be tracked eagerly, which means that not all tracked values *have* to appear in the output. The resolver
 83   * function that was passed to the output may filter the value.
 84   *
 85   * Tracking the same `name` and `value` pair multiple times has no effect
 86   *
 87   * @param {String} name
 88   * @param {*} value
 89   */
 90  NamedPropertiesTracker.prototype.track = function (name, value) {
 91    if (name === undefined || name === null || name === "") {
 92      return;
 93    }
 94  
 95    let valueSet = this.trackedValues.get(name);
 96    if (!valueSet) {
 97      valueSet = new Set();
 98      this.trackedValues.set(name, valueSet);
 99    }
100  
101    valueSet.add(value);
102  
103    if (name in this.objectProxy) {
104      // already added our getter or it is not a named property (e.g. "addEventListener")
105      return;
106    }
107  
108    const descriptor = newPropertyDescriptor(this, name);
109    Object.defineProperty(this.object, name, descriptor);
110  };
111  
112  /**
113   * Stop tracking a previously tracked `name` & `value` pair, see track().
114   *
115   * Untracking the same `name` and `value` pair multiple times has no effect
116   *
117   * @param {String} name
118   * @param {*} value
119   */
120  NamedPropertiesTracker.prototype.untrack = function (name, value) {
121    if (name === undefined || name === null || name === "") {
122      return;
123    }
124  
125    const valueSet = this.trackedValues.get(name);
126    if (!valueSet) {
127      // the value is not present
128      return;
129    }
130  
131    if (!valueSet.delete(value)) {
132      // the value was not present
133      return;
134    }
135  
136    if (valueSet.size === 0) {
137      this.trackedValues.delete(name);
138    }
139  
140    if (valueSet.size > 0) {
141      // other values for this name are still present
142      return;
143    }
144  
145    // at this point there are no more values, delete the property
146  
147    const descriptor = Object.getOwnPropertyDescriptor(this.object, name);
148  
149    if (!descriptor || !descriptor.get || descriptor.get[IS_NAMED_PROPERTY] !== true) {
150      // Not defined by NamedPropertyTracker
151      return;
152    }
153  
154    // note: delete puts the object in dictionary mode.
155    // if this turns out to be a performance issue, maybe add:
156    // https://github.com/petkaantonov/bluebird/blob/3e36fc861ac5795193ba37935333eb6ef3716390/src/util.js#L177
157    delete this.object[name];
158  };