private.js 3.42 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
"use strict";

var originalObject = Object;
var originalDefProp = Object.defineProperty;
var originalCreate = Object.create;

function defProp(obj, name, value) {
  if (originalDefProp) try {
    originalDefProp.call(originalObject, obj, name, { value: value });
  } catch (definePropertyIsBrokenInIE8) {
    obj[name] = value;
  } else {
    obj[name] = value;
  }
}

// For functions that will be invoked using .call or .apply, we need to
// define those methods on the function objects themselves, rather than
// inheriting them from Function.prototype, so that a malicious or clumsy
// third party cannot interfere with the functionality of this module by
// redefining Function.prototype.call or .apply.
function makeSafeToCall(fun) {
  if (fun) {
    defProp(fun, "call", fun.call);
    defProp(fun, "apply", fun.apply);
  }
  return fun;
}

makeSafeToCall(originalDefProp);
makeSafeToCall(originalCreate);

var hasOwn = makeSafeToCall(Object.prototype.hasOwnProperty);
var numToStr = makeSafeToCall(Number.prototype.toString);
var strSlice = makeSafeToCall(String.prototype.slice);

var cloner = function(){};
function create(prototype) {
  if (originalCreate) {
    return originalCreate.call(originalObject, prototype);
  }
  cloner.prototype = prototype || null;
  return new cloner;
}

var rand = Math.random;
var uniqueKeys = create(null);

function makeUniqueKey() {
  // Collisions are highly unlikely, but this module is in the business of
  // making guarantees rather than safe bets.
  do var uniqueKey = internString(strSlice.call(numToStr.call(rand(), 36), 2));
  while (hasOwn.call(uniqueKeys, uniqueKey));
  return uniqueKeys[uniqueKey] = uniqueKey;
}

function internString(str) {
  var obj = {};
  obj[str] = true;
  return Object.keys(obj)[0];
}

// External users might find this function useful, but it is not necessary
// for the typical use of this module.
exports.makeUniqueKey = makeUniqueKey;

// Object.getOwnPropertyNames is the only way to enumerate non-enumerable
// properties, so if we wrap it to ignore our secret keys, there should be
// no way (except guessing) to access those properties.
var originalGetOPNs = Object.getOwnPropertyNames;
Object.getOwnPropertyNames = function getOwnPropertyNames(object) {
  for (var names = originalGetOPNs(object),
           src = 0,
           dst = 0,
           len = names.length;
       src < len;
       ++src) {
    if (!hasOwn.call(uniqueKeys, names[src])) {
      if (src > dst) {
        names[dst] = names[src];
      }
      ++dst;
    }
  }
  names.length = dst;
  return names;
};

function defaultCreatorFn(object) {
  return create(null);
}

function makeAccessor(secretCreatorFn) {
  var brand = makeUniqueKey();
  var passkey = create(null);

  secretCreatorFn = secretCreatorFn || defaultCreatorFn;

  function register(object) {
    var secret; // Created lazily.

    function vault(key, forget) {
      // Only code that has access to the passkey can retrieve (or forget)
      // the secret object.
      if (key === passkey) {
        return forget
          ? secret = null
          : secret || (secret = secretCreatorFn(object));
      }
    }

    defProp(object, brand, vault);
  }

  function accessor(object) {
    if (!hasOwn.call(object, brand))
      register(object);
    return object[brand](passkey);
  }

  accessor.forget = function(object) {
    if (hasOwn.call(object, brand))
      object[brand](passkey, true);
  };

  return accessor;
}

exports.makeAccessor = makeAccessor;