deepFreezeInternal_ = function(arg, seenSet) {
  // Check for primitives and non-recursive object types to avoid adding to seen
  // set.
  switch (typeof arg) {
    case 'function':
      throw new Error('deepFreeze does not support functions');
    case 'object':
      if (arg === null) {
        return null;
      }
      break;
    default:
      // Primitives. Return them as they are effectively immutable.
      return arg;
  }

  if (seenSet.has(arg)) {
    throw new Error('deepFreeze does not support cyclic structures');
  }

  // Check and see if the arg is either an Array literal or an Object literal
  // (e.g isn't a class).
  const prototype = Object.getPrototypeOf(arg);
  if (prototype !== Object.prototype && prototype !== Array.prototype) {
    throw new Error('deepFreeze only supports literals (array or object).');
  }

  seenSet.add(arg);
  const dupe = prototype === Array.prototype ? new Array(arg.length) : {};
  const keys = [
    ...Object.getOwnPropertyNames(arg),
    ...Object.getOwnPropertySymbols(arg),
  ];

  for (const key of keys) {
    const descriptor = Object.getOwnPropertyDescriptor(arg, key);
    if (!descriptor.enumerable) {
      continue;
    }
    if (descriptor.get != null || descriptor.set != null) {
      throw new Error('deepFreeze does not support getters/setters');
    }
    const frozen = deepFreezeInternal_(arg[key], seenSet);
    dupe[key] = frozen;
  }

  seenSet.delete(arg);
  freeze(dupe);
  return dupe;
}
Deep freezes the given object, but only in debug mode and in browsers that
support freezing.

@param {T} arg The object to clone.
@param {!Set<?>} seenSet The set of objects seen so far while recursing into
    child objects. Used to detect cyclic objects.
@return {T}
@template T
@private