iterateOverPlayerMethods_(operation) {
    goog.asserts.assert(this.localPlayer_, 'Must have player!');
    const player = /** @type {!Object} */ (this.localPlayer_);
    // Avoid accessing any over-written methods in the prototype chain.
    const seenNames = new Set();

    /**
     * @param {string} name
     * @return {boolean}
     */
    function shouldAddToTheMap(name) {
      if (name == 'constructor') {
        // Don't proxy the constructor.
        return false;
      }

      const method = /** @type {Object} */(player)[name];
      if (typeof method != 'function') {
        // Don't proxy non-methods.
        return false;
      }

      // Add if the map does not already have it
      return !seenNames.has(name);
    }

    // First, look at the methods on the object itself, so this can properly
    // proxy any methods not on the prototype (for example, in the mock player).
    for (const key in player) {
      if (shouldAddToTheMap(key)) {
        seenNames.add(key);
        operation(key, player[key]);
      }
    }

    // The exact length of the prototype chain might vary; for resiliency, this
    // will just look at the entire chain, rather than assuming a set length.
    let proto = /** @type {!Object} */ (Object.getPrototypeOf(player));
    const objProto = /** @type {!Object} */ (Object.getPrototypeOf({}));
    while (proto && proto != objProto) { // Don't proxy Object methods.
      for (const name of Object.getOwnPropertyNames(proto)) {
        if (shouldAddToTheMap(name)) {
          seenNames.add(name);
          operation(name, (player)[name]);
        }
      }
      proto = /** @type {!Object} */ (Object.getPrototypeOf(proto));
    }
  }
Iterates over all of the methods of the player, including inherited methods
from FakeEventTarget.
@param {function(string, function())} operation
@private