<commit_before>// Copyright 2007 Bob Ippolito. All Rights Reserved.
// Modifications Copyright 2009 The Closure Library Authors. All Rights
// Reserved.

/**
 * @license Portions of this code are from MochiKit, received by
 * The Closure Authors under the MIT license. All other code is Copyright
 * 2005-2009 The Closure Authors. All Rights Reserved.
 */

/**
 * @fileoverview Classes for tracking asynchronous operations and handling the
 * results. The Deferred object here is patterned after the Deferred object in
 * the Twisted python networking framework.
 *
 * See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
 *
 * Based on the Dojo code which in turn is based on the MochiKit code.
 *
 */

goog.provide('goog.async.Deferred');
goog.provide('goog.async.Deferred.AlreadyCalledError');
goog.provide('goog.async.Deferred.CancelledError');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.debug.Error');

/**
 * Represents the results of an asynchronous operation. A Deferred object
 * starts with no result, and then gets a result at some point in the future.
 * @param {Function=} opt_canceller A function that will be called if the
 *     deferred is cancelled.
 * @param {Object=} opt_defaultScope The default scope to call callbacks with.
 * @constructor
 */
goog.async.Deferred = function(opt_canceller, opt_defaultScope) {
  /**
   * Entries in the chain are arrays containing a callback, errback, and
   * optional scope. Callbacks or errbacks may be null.
   * @type {!Array.<!Array>}
   * @private
   */
  this.chain_ = [];

  /**
   * If provided, this is the function to call when the deferred is cancelled.
   * @type {Function|undefined}
   * @private
   */
  this.canceller_ = opt_canceller;

  /**
   * The default scope to execute callbacks in.
   * @type {Object}
   * @private
   */
  this.defaultScope_ = opt_defaultScope || null;
};

/**
 * Whether the deferred has been fired.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.fired_ = false;

/**
 * Whether the last result in the callback chain was an error.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.hadError_ = false;

/**
 * The current Deferred result, updated by registered callbacks and errbacks.
 * @type {*}
 * @private
 */
goog.async.Deferred.prototype.result_;

/**
 * The number of times this deferred has been paused.
 * @type {number}
 * @private
 */
goog.async.Deferred.prototype.paused_ = 0;

/**
 * If the deferred was cancelled but it did not have a canceller then this gets
 * set to true.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.silentlyCancelled_ = false;

/**
 * If a callback returns a deferred then this deferred is considered a chained
 * deferred and once it is chained we cannot add more callbacks.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.chained_ = false;

/**
 * If an error is thrown during Deferred execution with no errback to catch it,
 * the error is rethrown after a timeout. Reporting the error after a timeout
 * allows execution to continue in the calling context.
 * @type {number}
 * @private
 */
goog.async.Deferred.prototype.unhandledExceptionTimeoutId_;

/**
 * Cancels a deferred that has not yet received a value. If this Deferred is
 * paused waiting for a chained Deferred to fire, the chained Deferred will also
 * be cancelled.
 */
goog.async.Deferred.prototype.cancel = function() {
  if (!this.hasFired()) {
    if (this.canceller_) {
      // Call in user-specified scope.
      this.canceller_.call(this.defaultScope_, this);
    } else {
      this.silentlyCancelled_ = true;
    }
    if (!this.hasFired()) {
      this.errback(new goog.async.Deferred.CancelledError(this));
    }
  } else if (this.result_ instanceof goog.async.Deferred) {
    this.result_.cancel();
  }
};

/**
 * Pauses the deferred.
 * @private
 */
goog.async.Deferred.prototype.pause_ = function() {
  this.paused_++;
};

/**
 * Resumes a paused deferred.
 * @private
 */
goog.async.Deferred.prototype.unpause_ = function() {
  // TODO(user): Rename
  this.paused_--;
  if (this.paused_ == 0 && this.hasFired()) {
    this.fire_();
  }
};

/**
 * Called when a dependent deferred fires.
 * @param {boolean} isSuccess Whether the result is a success or an error.
 * @param {*} res The result of the dependent deferred.
 * @private
 */
goog.async.Deferred.prototype.continue_ = function(isSuccess, res) {
  this.resback_(isSuccess, res);
  this.unpause_();
};

/**
 * Called when either a success or a failure happens.
 * @param {*} res The result.
 * @param {boolean} isSuccess Whether the result is a success or an error.
 * @private
 */
goog.async.Deferred.prototype.resback_ = function(isSuccess, res) {
  this.fired_ = true;
  this.result_ = res;
  this.hadError_ = !isSuccess;
  this.fire_();
};

/**
 * Verifies that the deferred has not yet been fired.
 * @private
 * @throws {Error} If this has already been fired.
 */
goog.async.Deferred.prototype.check_ = function() {
  if (this.hasFired()) {
    if (!this.silentlyCancelled_) {
      throw new goog.async.Deferred.AlreadyCalledError(this);
    }
    this.silentlyCancelled_ = false;
  }
};

/**
 * Record a successful result for this operation, and send the result
 * to all registered callback functions.
 * @param {*} result The result of the operation.
 */
goog.async.Deferred.prototype.callback = function(result) {
  this.check_();
  this.assertNotDeferred_(result);
  this.resback_(true /* isSuccess */, result);
};

/**
 * Record that this operation failed with an error, and send the error
 * to all registered errback functions.
 * @param {*} result The error result of the operation.
 */
goog.async.Deferred.prototype.errback = function(result) {
  this.check_();
  this.assertNotDeferred_(result);
  this.resback_(false /* isSuccess */, result);
};

/**
 * Asserts that an object is not a Deferred.
 * @param {*} obj The object to test.
 * @throws {Error} Throws an exception if the object is a Deferred.
 * @private
 */
goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) {
  goog.asserts.assert(
      !(obj instanceof goog.async.Deferred),
      'Deferred instances can only be chained if they are the result of a ' +
      'callback');
};

/**
 * Register a callback function, to be called when a successful result
 * is available.
 * @param {!Function} cb The function to be called on a successful result.
 * @param {Object=} opt_scope An optional scope to call the callback in.
 * @return {!goog.async.Deferred} The deferred object for chaining.
 */
goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) {
  return this.addCallbacks(cb, null, opt_scope);
};

/**
 * Register a callback function, to be called if this operation fails.
 * @param {!Function} eb The function to be called on an unsuccessful result.
 * @param {Object=} opt_scope An optional scope to call the errback in.
 * @return {!goog.async.Deferred} The deferred object for chaining.
 */
goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) {
  return this.addCallbacks(null, eb, opt_scope);
};

/**
 * Registers a callback function and errback function.
 * @param {Function} cb The function to be called on a successful result.
 * @param {Function} eb The function to be called on an unsuccessful result.
 * @param {Object=} opt_scope An optional scope to call the callbacks in.
 * @return {!goog.async.Deferred} The deferred object for chaining.
 */
goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) {
  goog.asserts.assert(!this.chained_, 'Chained Deferreds can not be re-used');
  this.chain_.push([cb, eb, opt_scope]);
  if (this.hasFired()) {
    this.fire_();
  }
  return this;
};

/**
 * Adds another deferred to the end of this deferred's processing chain.
 *
 * Use this when you want otherDeferred to be called at the end of
 * thisDeferred's previous callbacks.
 *
 * @param {!goog.async.Deferred} otherDeferred The Deferred to chain.
 * @return {!goog.async.Deferred} The deferred object for chaining.
 */
goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) {
  this.addCallbacks(
      otherDeferred.callback, otherDeferred.errback, otherDeferred);
  return this;
};

/**
 * Makes this Deferred wait for otherDeferred to be called, and its preceding
 * callbacks to be executed, before continuing with the callback sequence.
 *
 * This is equivalent to adding a callback that returns otherDeferred, but
 * doesn't prevent additional callbacks from being added to otherDeferred.
 *
 * @param {!goog.async.Deferred} otherDeferred The Deferred to wait for.
 * @return {!goog.async.Deferred} The deferred object for chaining.
 */
goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) {
  return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred));
};

/**
 * Create a branch off this Deferred's callback chain, and return it as a new
 * Deferred. This means that the return value will have the value at the current
 * point in the callback chain, regardless of any further callbacks added to
 * this Deferred.
 *
 * Additional callbacks added to the original Deferred will not affect the value
 * of any branches. All branches at the same stage in the callback chain will
 * receive the same starting value.
 *
 * @return {!goog.async.Deferred} The deferred value at this point in the
 *     callback chain.
 */
goog.async.Deferred.prototype.branch = function() {
  var d = new goog.async.Deferred();
  this.chainDeferred(d);
  return d;
};

/**
 * Registers a function as both callback and errback.
 * @param {!Function} f The function to be called on any result.
 * @param {Object=} opt_scope An optional scope to call the callbacks in.
 * @return {!goog.async.Deferred} The deferred object for chaining.
 */
goog.async.Deferred.prototype.addBoth = function(f, opt_scope) {
  return this.addCallbacks(f, f, opt_scope);
};

/**
 * @return {boolean} Whether callback or errback has been called on this
 *     deferred.
 */
goog.async.Deferred.prototype.hasFired = function() {
  return this.fired_;
};

/**
 * @param {*} res The current callback result.
 * @return {boolean} Whether the current result is an error that should cause
 *     registered errbacks to fire. May be overridden by subclasses to handle
 *     special error types.
 * @protected
 */
goog.async.Deferred.prototype.isError = function(res) {
  return res instanceof Error;
};

/**
 * @return {boolean} Whether an errback has been registered.
 */
goog.async.Deferred.prototype.hasErrback_ = function() {
  return goog.array.some(this.chain_, function(chainRow) {
    // The errback is the second element in the array.
    return goog.isFunction(chainRow[1]);
  });
};

/**
 * Exhausts the callback sequence once a result is available.
 * @private
 */
goog.async.Deferred.prototype.fire_ = function() {
  if (this.unhandledExceptionTimeoutId_ && this.hasFired() &&
      this.hasErrback_()) {
    // It is possible to add errbacks after the Deferred has fired. If a new
    // errback is added immediately after the Deferred encountered an unhandled
    // error, but before that error is rethrown, cancel the rethrow.
    goog.global.clearTimeout(this.unhandledExceptionTimeoutId_);
    delete this.unhandledExceptionTimeoutId_;
  }

  var res = this.result_;
  var unhandledException = false;
  var isChained = false;

  while (this.chain_.length && this.paused_ == 0) {
    var chainEntry = this.chain_.shift();

    var callback = chainEntry[0];
    var errback = chainEntry[1];
    var scope = chainEntry[2];

    var f = this.hadError_ ? errback : callback;
    if (f) {
      try {
        var ret = f.call(scope || this.defaultScope_, res);

        // If no result, then use previous result.
        if (goog.isDef(ret)) {
          // Bubble up the error as long as the return value hasn't changed.
          this.hadError_ = this.hadError_ && (ret == res || this.isError(ret));
          res = ret;
        }

        if (res instanceof goog.async.Deferred) {
          isChained = true;
          this.pause_();
        }

      } catch (ex) {
        res = ex;
        this.hadError_ = true;

        if (!this.hasErrback_()) {
          // If an error is thrown with no additional errbacks in the queue,
          // prepare to rethrow the error.
          unhandledException = true;
        }
      }
    }
  }

  this.result_ = res;

  if (isChained && this.paused_) {
    res.addCallbacks(
        goog.bind(this.continue_, this, true /* isSuccess */),
        goog.bind(this.continue_, this, false /* isSuccess */));
    res.chained_ = true;
  }

  if (unhandledException) {
    // Rethrow the unhandled error after a timeout. Execution will continue, but
    // the error will be seen by global handlers and the user. The rethrow will
    // be canceled if another errback is appended before the timeout executes.
    this.unhandledExceptionTimeoutId_ = goog.global.setTimeout(function() {
      throw res;
    }, 0);
  }
};

/**
 * Creates a deferred that always succeeds.
 * @param {*} res The result.
 * @return {!goog.async.Deferred} The deferred object.
 */
goog.async.Deferred.succeed = function(res) {
  var d = new goog.async.Deferred();
  d.callback(res);
  return d;
};

/**
 * Creates a deferred that always fails.
 * @param {*} res The error result.
 * @return {!goog.async.Deferred} The deferred object.
 */
goog.async.Deferred.fail = function(res) {
  var d = new goog.async.Deferred();
  d.errback(res);
  return d;
};

/**
 * An error sub class that is used when a deferred has already been called.
 * @param {!goog.async.Deferred} deferred The deferred object.
 * @constructor
 * @extends {goog.debug.Error}
 */
goog.async.Deferred.AlreadyCalledError = function(deferred) {
  goog.debug.Error.call(this);

  /**
   * The deferred that raised this error.
   * @type {goog.async.Deferred}
   */
  this.deferred = deferred;
};
goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error);

/**
 * Message text.
 * @type {string}
 * @override
 */
goog.async.Deferred.AlreadyCalledError.prototype.message = 'Already called';

/**
 * An error sub class that is used when a deferred is cancelled.
 * @param {!goog.async.Deferred} deferred The deferred object.
 * @constructor
 * @extends {goog.debug.Error}
 */
goog.async.Deferred.CancelledError = function(deferred) {
  goog.debug.Error.call(this);

  /**
   * The deferred that raised this error.
   * @type {goog.async.Deferred}
   */
  this.deferred = deferred;
};
goog.inherits(goog.async.Deferred.CancelledError, goog.debug.Error);

/**
 * Message text.
 * @type {string}
 * @override
 */
goog.async.Deferred.CancelledError.prototype.message = 'Deferred was cancelled';
<commit_msg><commit_after>// Copyright 2007 Bob Ippolito. All Rights Reserved.
// Modifications Copyright 2009 The Closure Library Authors. All Rights
// Reserved.

/**
 * @license Portions of this code are from MochiKit, received by
 * The Closure Authors under the MIT license. All other code is Copyright
 * 2005-2009 The Closure Authors. All Rights Reserved.
 */

/**
 * @fileoverview Classes for tracking asynchronous operations and handling the
 * results. The Deferred object here is patterned after the Deferred object in
 * the Twisted python networking framework.
 *
 * See: http://twistedmatrix.com/projects/core/documentation/howto/defer.html
 *
 * Based on the Dojo code which in turn is based on the MochiKit code.
 *
 */

goog.provide('goog.async.Deferred');
goog.provide('goog.async.Deferred.AlreadyCalledError');
goog.provide('goog.async.Deferred.CancelledError');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.debug.Error');

/**
 * Represents the results of an asynchronous operation. A Deferred object
 * starts with no result, and then gets a result at some point in the future.
 * @param {Function=} opt_canceller A function that will be called if the
 *     deferred is cancelled.
 * @param {Object=} opt_defaultScope The default scope to call callbacks with.
 * @constructor
 */
goog.async.Deferred = function(opt_canceller, opt_defaultScope) {
  /**
   * Entries in the chain are arrays containing a callback, errback, and
   * optional scope. Callbacks or errbacks may be null.
   * @type {!Array.<!Array>}
   * @private
   */
  this.chain_ = [];

  /**
   * If provided, this is the function to call when the deferred is cancelled.
   * @type {Function|undefined}
   * @private
   */
  this.canceller_ = opt_canceller;

  /**
   * The default scope to execute callbacks in.
   * @type {Object}
   * @private
   */
  this.defaultScope_ = opt_defaultScope || null;
};

/**
 * Whether the deferred has been fired.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.fired_ = false;

/**
 * Whether the last result in the callback chain was an error.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.hadError_ = false;

/**
 * The current Deferred result, updated by registered callbacks and errbacks.
 * @type {*}
 * @private
 */
goog.async.Deferred.prototype.result_;

/**
 * The number of times this deferred has been paused.
 * @type {number}
 * @private
 */
goog.async.Deferred.prototype.paused_ = 0;

/**
 * If the deferred was cancelled but it did not have a canceller then this gets
 * set to true.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.silentlyCancelled_ = false;

/**
 * If a callback returns a deferred then this deferred is considered a chained
 * deferred and once it is chained we cannot add more callbacks.
 * @type {boolean}
 * @private
 */
goog.async.Deferred.prototype.chained_ = false;

/**
 * If an error is thrown during Deferred execution with no errback to catch it,
 * the error is rethrown after a timeout. Reporting the error after a timeout
 * allows execution to continue in the calling context.
 * @type {number}
 * @private
 */
goog.async.Deferred.prototype.unhandledExceptionTimeoutId_;

/**
 * Cancels a deferred that has not yet received a value. If this Deferred is
 * paused waiting for a chained Deferred to fire, the chained Deferred will also
 * be cancelled.
 */
goog.async.Deferred.prototype.cancel = function() {
  if (!this.hasFired()) {
    if (this.canceller_) {
      // Call in user-specified scope.
      this.canceller_.call(this.defaultScope_, this);
    } else {
      this.silentlyCancelled_ = true;
    }
    if (!this.hasFired()) {
      this.errback(new goog.async.Deferred.CancelledError(this));
    }
  } else if (this.result_ instanceof goog.async.Deferred) {
    this.result_.cancel();
  }
};

/**
 * Pauses the deferred.
 * @private
 */
goog.async.Deferred.prototype.pause_ = function() {
  this.paused_++;
};

/**
 * Resumes a paused deferred.
 * @private
 */
goog.async.Deferred.prototype.unpause_ = function() {
  // TODO(user): Rename
  this.paused_--;
  if (this.paused_ == 0 && this.hasFired()) {
    this.fire_();
  }
};

/**
 * Called when a dependent deferred fires.
 * @param {boolean} isSuccess Whether the result is a success or an error.
 * @param {*} res The result of the dependent deferred.
 * @private
 */
goog.async.Deferred.prototype.continue_ = function(isSuccess, res) {
  this.resback_(isSuccess, res);
  this.unpause_();
};

/**
 * Called when either a success or a failure happens.
 * @param {boolean} isSuccess Whether the result is a success or an error.
 * @param {*} res The result.
 * @private
 */
goog.async.Deferred.prototype.resback_ = function(isSuccess, res) {
  this.fired_ = true;
  this.result_ = res;
  this.hadError_ = !isSuccess;
  this.fire_();
};

/**
 * Verifies that the deferred has not yet been fired.
 * @private
 * @throws {Error} If this has already been fired.
 */
goog.async.Deferred.prototype.check_ = function() {
  if (this.hasFired()) {
    if (!this.silentlyCancelled_) {
      throw new goog.async.Deferred.AlreadyCalledError(this);
    }
    this.silentlyCancelled_ = false;
  }
};

/**
 * Record a successful result for this operation, and send the result
 * to all registered callback functions.
 * @param {*} result The result of the operation.
 */
goog.async.Deferred.prototype.callback = function(result) {
  this.check_();
  this.assertNotDeferred_(result);
  this.resback_(true /* isSuccess */, result);
};

/**
 * Record that this operation failed with an error, and send the error
 * to all registered errback functions.
 * @param {*} result The error result of the operation.
 */
goog.async.Deferred.prototype.errback = function(result) {
  this.check_();
  this.assertNotDeferred_(result);
  this.resback_(false /* isSuccess */, result);
};

/**
 * Asserts that an object is not a Deferred.
 * @param {*} obj The object to test.
 * @throws {Error} Throws an exception if the object is a Deferred.
 * @private
 */
goog.async.Deferred.prototype.assertNotDeferred_ = function(obj) {
  goog.asserts.assert(
      !(obj instanceof goog.async.Deferred),
      'Deferred instances can only be chained if they are the result of a ' +
      'callback');
};

/**
 * Register a callback function, to be called when a successful result
 * is available.
 * @param {!Function} cb The function to be called on a successful result.
 * @param {Object=} opt_scope An optional scope to call the callback in.
 * @return {!goog.async.Deferred} The deferred object for chaining.
 */
goog.async.Deferred.prototype.addCallback = function(cb, opt_scope) {
  return this.addCallbacks(cb, null, opt_scope);
};

/**
 * Register a callback function, to be called if this operation fails.
 * @param {!Function} eb The function to be called on an unsuccessful result.
 * @param {Object=} opt_scope An optional scope to call the errback in.
 * @return {!goog.async.Deferred} The deferred object for chaining.
 */
goog.async.Deferred.prototype.addErrback = function(eb, opt_scope) {
  return this.addCallbacks(null, eb, opt_scope);
};

/**
 * Registers a callback function and errback function.
 * @param {Function} cb The function to be called on a successful result.
 * @param {Function} eb The function to be called on an unsuccessful result.
 * @param {Object=} opt_scope An optional scope to call the callbacks in.
 * @return {!goog.async.Deferred} The deferred object for chaining.
 */
goog.async.Deferred.prototype.addCallbacks = function(cb, eb, opt_scope) {
  goog.asserts.assert(!this.chained_, 'Chained Deferreds can not be re-used');
  this.chain_.push([cb, eb, opt_scope]);
  if (this.hasFired()) {
    this.fire_();
  }
  return this;
};

/**
 * Adds another deferred to the end of this deferred's processing chain.
 *
 * Use this when you want otherDeferred to be called at the end of
 * thisDeferred's previous callbacks.
 *
 * @param {!goog.async.Deferred} otherDeferred The Deferred to chain.
 * @return {!goog.async.Deferred} The deferred object for chaining.
 */
goog.async.Deferred.prototype.chainDeferred = function(otherDeferred) {
  this.addCallbacks(
      otherDeferred.callback, otherDeferred.errback, otherDeferred);
  return this;
};

/**
 * Makes this Deferred wait for otherDeferred to be called, and its preceding
 * callbacks to be executed, before continuing with the callback sequence.
 *
 * This is equivalent to adding a callback that returns otherDeferred, but
 * doesn't prevent additional callbacks from being added to otherDeferred.
 *
 * @param {!goog.async.Deferred} otherDeferred The Deferred to wait for.
 * @return {!goog.async.Deferred} The deferred object for chaining.
 */
goog.async.Deferred.prototype.awaitDeferred = function(otherDeferred) {
  return this.addCallback(goog.bind(otherDeferred.branch, otherDeferred));
};

/**
 * Create a branch off this Deferred's callback chain, and return it as a new
 * Deferred. This means that the return value will have the value at the current
 * point in the callback chain, regardless of any further callbacks added to
 * this Deferred.
 *
 * Additional callbacks added to the original Deferred will not affect the value
 * of any branches. All branches at the same stage in the callback chain will
 * receive the same starting value.
 *
 * @return {!goog.async.Deferred} The deferred value at this point in the
 *     callback chain.
 */
goog.async.Deferred.prototype.branch = function() {
  var d = new goog.async.Deferred();
  this.chainDeferred(d);
  return d;
};

/**
 * Registers a function as both callback and errback.
 * @param {!Function} f The function to be called on any result.
 * @param {Object=} opt_scope An optional scope to call the callbacks in.
 * @return {!goog.async.Deferred} The deferred object for chaining.
 */
goog.async.Deferred.prototype.addBoth = function(f, opt_scope) {
  return this.addCallbacks(f, f, opt_scope);
};

/**
 * @return {boolean} Whether callback or errback has been called on this
 *     deferred.
 */
goog.async.Deferred.prototype.hasFired = function() {
  return this.fired_;
};

/**
 * @param {*} res The current callback result.
 * @return {boolean} Whether the current result is an error that should cause
 *     registered errbacks to fire. May be overridden by subclasses to handle
 *     special error types.
 * @protected
 */
goog.async.Deferred.prototype.isError = function(res) {
  return res instanceof Error;
};

/**
 * @return {boolean} Whether an errback has been registered.
 * @private
 */
goog.async.Deferred.prototype.hasErrback_ = function() {
  return goog.array.some(this.chain_, function(chainRow) {
    // The errback is the second element in the array.
    return goog.isFunction(chainRow[1]);
  });
};

/**
 * Exhausts the callback sequence once a result is available.
 * @private
 */
goog.async.Deferred.prototype.fire_ = function() {
  if (this.unhandledExceptionTimeoutId_ && this.hasFired() &&
      this.hasErrback_()) {
    // It is possible to add errbacks after the Deferred has fired. If a new
    // errback is added immediately after the Deferred encountered an unhandled
    // error, but before that error is rethrown, cancel the rethrow.
    goog.global.clearTimeout(this.unhandledExceptionTimeoutId_);
    delete this.unhandledExceptionTimeoutId_;
  }

  var res = this.result_;
  var unhandledException = false;
  var isChained = false;

  while (this.chain_.length && this.paused_ == 0) {
    var chainEntry = this.chain_.shift();

    var callback = chainEntry[0];
    var errback = chainEntry[1];
    var scope = chainEntry[2];

    var f = this.hadError_ ? errback : callback;
    if (f) {
      try {
        var ret = f.call(scope || this.defaultScope_, res);

        // If no result, then use previous result.
        if (goog.isDef(ret)) {
          // Bubble up the error as long as the return value hasn't changed.
          this.hadError_ = this.hadError_ && (ret == res || this.isError(ret));
          res = ret;
        }

        if (res instanceof goog.async.Deferred) {
          isChained = true;
          this.pause_();
        }

      } catch (ex) {
        res = ex;
        this.hadError_ = true;

        if (!this.hasErrback_()) {
          // If an error is thrown with no additional errbacks in the queue,
          // prepare to rethrow the error.
          unhandledException = true;
        }
      }
    }
  }

  this.result_ = res;

  if (isChained && this.paused_) {
    res.addCallbacks(
        goog.bind(this.continue_, this, true /* isSuccess */),
        goog.bind(this.continue_, this, false /* isSuccess */));
    res.chained_ = true;
  }

  if (unhandledException) {
    // Rethrow the unhandled error after a timeout. Execution will continue, but
    // the error will be seen by global handlers and the user. The rethrow will
    // be canceled if another errback is appended before the timeout executes.
    this.unhandledExceptionTimeoutId_ = goog.global.setTimeout(function() {
      throw res;
    }, 0);
  }
};

/**
 * Creates a deferred that always succeeds.
 * @param {*} res The result.
 * @return {!goog.async.Deferred} The deferred object.
 */
goog.async.Deferred.succeed = function(res) {
  var d = new goog.async.Deferred();
  d.callback(res);
  return d;
};

/**
 * Creates a deferred that always fails.
 * @param {*} res The error result.
 * @return {!goog.async.Deferred} The deferred object.
 */
goog.async.Deferred.fail = function(res) {
  var d = new goog.async.Deferred();
  d.errback(res);
  return d;
};

/**
 * An error sub class that is used when a deferred has already been called.
 * @param {!goog.async.Deferred} deferred The deferred object.
 * @constructor
 * @extends {goog.debug.Error}
 */
goog.async.Deferred.AlreadyCalledError = function(deferred) {
  goog.debug.Error.call(this);

  /**
   * The deferred that raised this error.
   * @type {goog.async.Deferred}
   */
  this.deferred = deferred;
};
goog.inherits(goog.async.Deferred.AlreadyCalledError, goog.debug.Error);

/**
 * Message text.
 * @type {string}
 * @override
 */
goog.async.Deferred.AlreadyCalledError.prototype.message = 'Already called';

/**
 * An error sub class that is used when a deferred is cancelled.
 * @param {!goog.async.Deferred} deferred The deferred object.
 * @constructor
 * @extends {goog.debug.Error}
 */
goog.async.Deferred.CancelledError = function(deferred) {
  goog.debug.Error.call(this);

  /**
   * The deferred that raised this error.
   * @type {goog.async.Deferred}
   */
  this.deferred = deferred;
};
goog.inherits(goog.async.Deferred.CancelledError, goog.debug.Error);

/**
 * Message text.
 * @type {string}
 * @override
 */
goog.async.Deferred.CancelledError.prototype.message = 'Deferred was cancelled';
<|endoftext|>


<commit_before>define([
	"dojo/_base/array", // array.forEach
	"dojo/_base/lang", // lang.mixin, lang.hitch
	"dojo/on",
	"dojo/sniff" // has("ie")
], function(array, lang, on, has){

	// module:
	//		dijit/typematic

	return  {
		// summary:
		//		These functions are used to repetitively call a user specified callback
		//		method when a specific key or mouse click over a specific DOM node is
		//		held down for a specific amount of time.
		//		Only 1 such event is allowed to occur on the browser page at 1 time.

		_fireEventAndReload: function(){
			this._timer = null;
			this._callback(++this._count, this._node, this._evt);

			// Schedule next event, timer is at most minDelay (default 10ms) to avoid
			// browser overload (particularly avoiding starving DOH robot so it never gets to send a mouseup)
			this._currentTimeout = Math.max(
				this._currentTimeout < 0 ? this._initialDelay :
					(this._subsequentDelay > 1 ? this._subsequentDelay : Math.round(this._currentTimeout * this._subsequentDelay)),
				this._minDelay);
			this._timer = setTimeout(lang.hitch(this, "_fireEventAndReload"), this._currentTimeout);
		},

		trigger: function(/*Event*/ evt, /*Object*/ _this, /*DOMNode*/ node, /*Function*/ callback, /*Object*/ obj, /*Number?*/ subsequentDelay, /*Number?*/ initialDelay, /*Number?*/ minDelay){
			// summary:
			//		Start a timed, repeating callback sequence.
			//		If already started, the function call is ignored.
			//		This method is not normally called by the user but can be
			//		when the normal listener code is insufficient.
			// evt:
			//		key or mouse event object to pass to the user callback
			// _this:
			//		pointer to the user's widget space.
			// node:
			//		the DOM node object to pass the the callback function
			// callback:
			//		function to call until the sequence is stopped called with 3 parameters:
			// count:
			//		integer representing number of repeated calls (0..n) with -1 indicating the iteration has stopped
			// node:
			//		the DOM node object passed in
			// evt:
			//		key or mouse event object
			// obj:
			//		user space object used to uniquely identify each typematic sequence
			// subsequentDelay:
			//		if > 1, the number of milliseconds until the 3->n events occur
			//		or else the fractional time multiplier for the next event's delay, default=0.9
			// initialDelay:
			//		the number of milliseconds until the 2nd event occurs, default=500ms
			// minDelay:
			//		the maximum delay in milliseconds for event to fire, default=10ms
			if(obj != this._obj){
				this.stop();
				this._initialDelay = initialDelay || 500;
				this._subsequentDelay = subsequentDelay || 0.90;
				this._minDelay = minDelay || 10;
				this._obj = obj;
				this._node = node;
				this._currentTimeout = -1;
				this._count = -1;
				this._callback = lang.hitch(_this, callback);
				this._evt = { faux: true };
				for(var attr in evt){
					if(attr != "layerX" && attr != "layerY"){ // prevent WebKit warnings
						var v = evt[attr];
						if(typeof v != "function" && typeof v != "undefined"){
							this._evt[attr] = v
						}
					}
				}
				this._fireEventAndReload();
			}
		},

		stop: function(){
			// summary:
			//		Stop an ongoing timed, repeating callback sequence.
			if(this._timer){
				clearTimeout(this._timer);
				this._timer = null;
			}
			if(this._obj){
				this._callback(-1, this._node, this._evt);
				this._obj = null;
			}
		},

		addKeyListener: function(/*DOMNode*/ node, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
			// summary:
			//		Start listening for a specific typematic key.
			//		See also the trigger method for other parameters.
			// keyObject:
			//		an object defining the key to listen for:
			//
			//		- keyCode: the keyCode (number) to listen for, used for non-printable keys
			//		- charCode: the charCode (number) to listen for, used for printable keys
			//		- ctrlKey: desired ctrl key state to initiate the callback sequence:
			//			- pressed (true)
			//			- released (false)
			//			- either (unspecified)
			//		- altKey: same as ctrlKey but for the alt key
			//		- shiftKey: same as ctrlKey but for the shift key
			// returns:
			//		a connection handle

			// Setup keydown or keypress listener depending on whether keyCode or charCode was specified.
			var type = "keyCode" in keyObject ? "keydown" : "keypress",
				attr = "keyCode" in keyObject ? "keyCode" : "charCode";

			var handles = [
				on(node, type, lang.hitch(this, function(evt){
					if(evt[attr] == keyObject[attr] &&
						(keyObject.ctrlKey === undefined || keyObject.ctrlKey == evt.ctrlKey) &&
						(keyObject.altKey === undefined || keyObject.altKey == evt.altKey) &&
						(keyObject.metaKey === undefined || keyObject.metaKey == (evt.metaKey || false)) && // IE doesn't even set metaKey
						(keyObject.shiftKey === undefined || keyObject.shiftKey == evt.shiftKey)){
						evt.stopPropagation();
						evt.preventDefault();
						typematic.trigger(evt, _this, node, callback, keyObject, subsequentDelay, initialDelay, minDelay);
					}else if(typematic._obj == keyObject){
						typematic.stop();
					}
				})),
				on(node, "keyup", lang.hitch(this, function(){
					if(typematic._obj == keyObject){
						typematic.stop();
					}
				}))
			];
			return { remove: function(){
				array.forEach(handles, function(h){
					h.remove();
				});
			} };
		},

		addMouseListener: function(/*DOMNode*/ node, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
			// summary:
			//		Start listening for a typematic mouse click.
			//		See the trigger method for other parameters.
			// returns:
			//		a connection handle
			var handles = [
				on(node, "mousedown", lang.hitch(this, function(evt){
					evt.preventDefault();
					typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay);
				})),
				on(node, "mouseup", lang.hitch(this, function(evt){
					if(this._obj){
						evt.preventDefault();
					}
					typematic.stop();
				})),
				on(node, "mouseout", lang.hitch(this, function(evt){
					if(this._obj){
						evt.preventDefault();
					}
					typematic.stop();
				})),
				on(node, "dblclick", lang.hitch(this, function(evt){
					evt.preventDefault();
					if(has("ie") < 9){
						typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay);
						setTimeout(lang.hitch(this, typematic.stop), 50);
					}
				}))
			];
			return { remove: function(){
				array.forEach(handles, function(h){
					h.remove();
				});
			} };
		},

		addListener: function(/*Node*/ mouseNode, /*Node*/ keyNode, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
			// summary:
			//		Start listening for a specific typematic key and mouseclick.
			//		This is a thin wrapper to addKeyListener and addMouseListener.
			//		See the addMouseListener and addKeyListener methods for other parameters.
			// mouseNode:
			//		the DOM node object to listen on for mouse events.
			// keyNode:
			//		the DOM node object to listen on for key events.
			// returns:
			//		a connection handle
			var handles = [
				this.addKeyListener(keyNode, keyObject, _this, callback, subsequentDelay, initialDelay, minDelay),
				this.addMouseListener(mouseNode, _this, callback, subsequentDelay, initialDelay, minDelay)
			];
			return { remove: function(){
				array.forEach(handles, function(h){
					h.remove();
				});
			} };
		}
	};
});
<commit_msg>fix reference to undefined typematic variable<commit_after>define([
	"dojo/_base/array", // array.forEach
	"dojo/_base/lang", // lang.mixin, lang.hitch
	"dojo/on",
	"dojo/sniff" // has("ie")
], function(array, lang, on, has){

	// module:
	//		dijit/typematic

	var typematic = {
		// summary:
		//		These functions are used to repetitively call a user specified callback
		//		method when a specific key or mouse click over a specific DOM node is
		//		held down for a specific amount of time.
		//		Only 1 such event is allowed to occur on the browser page at 1 time.

		_fireEventAndReload: function(){
			this._timer = null;
			this._callback(++this._count, this._node, this._evt);

			// Schedule next event, timer is at most minDelay (default 10ms) to avoid
			// browser overload (particularly avoiding starving DOH robot so it never gets to send a mouseup)
			this._currentTimeout = Math.max(
				this._currentTimeout < 0 ? this._initialDelay :
					(this._subsequentDelay > 1 ? this._subsequentDelay : Math.round(this._currentTimeout * this._subsequentDelay)),
				this._minDelay);
			this._timer = setTimeout(lang.hitch(this, "_fireEventAndReload"), this._currentTimeout);
		},

		trigger: function(/*Event*/ evt, /*Object*/ _this, /*DOMNode*/ node, /*Function*/ callback, /*Object*/ obj, /*Number?*/ subsequentDelay, /*Number?*/ initialDelay, /*Number?*/ minDelay){
			// summary:
			//		Start a timed, repeating callback sequence.
			//		If already started, the function call is ignored.
			//		This method is not normally called by the user but can be
			//		when the normal listener code is insufficient.
			// evt:
			//		key or mouse event object to pass to the user callback
			// _this:
			//		pointer to the user's widget space.
			// node:
			//		the DOM node object to pass the the callback function
			// callback:
			//		function to call until the sequence is stopped called with 3 parameters:
			// count:
			//		integer representing number of repeated calls (0..n) with -1 indicating the iteration has stopped
			// node:
			//		the DOM node object passed in
			// evt:
			//		key or mouse event object
			// obj:
			//		user space object used to uniquely identify each typematic sequence
			// subsequentDelay:
			//		if > 1, the number of milliseconds until the 3->n events occur
			//		or else the fractional time multiplier for the next event's delay, default=0.9
			// initialDelay:
			//		the number of milliseconds until the 2nd event occurs, default=500ms
			// minDelay:
			//		the maximum delay in milliseconds for event to fire, default=10ms
			if(obj != this._obj){
				this.stop();
				this._initialDelay = initialDelay || 500;
				this._subsequentDelay = subsequentDelay || 0.90;
				this._minDelay = minDelay || 10;
				this._obj = obj;
				this._node = node;
				this._currentTimeout = -1;
				this._count = -1;
				this._callback = lang.hitch(_this, callback);
				this._evt = { faux: true };
				for(var attr in evt){
					if(attr != "layerX" && attr != "layerY"){ // prevent WebKit warnings
						var v = evt[attr];
						if(typeof v != "function" && typeof v != "undefined"){
							this._evt[attr] = v
						}
					}
				}
				this._fireEventAndReload();
			}
		},

		stop: function(){
			// summary:
			//		Stop an ongoing timed, repeating callback sequence.
			if(this._timer){
				clearTimeout(this._timer);
				this._timer = null;
			}
			if(this._obj){
				this._callback(-1, this._node, this._evt);
				this._obj = null;
			}
		},

		addKeyListener: function(/*DOMNode*/ node, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
			// summary:
			//		Start listening for a specific typematic key.
			//		See also the trigger method for other parameters.
			// keyObject:
			//		an object defining the key to listen for:
			//
			//		- keyCode: the keyCode (number) to listen for, used for non-printable keys
			//		- charCode: the charCode (number) to listen for, used for printable keys
			//		- ctrlKey: desired ctrl key state to initiate the callback sequence:
			//			- pressed (true)
			//			- released (false)
			//			- either (unspecified)
			//		- altKey: same as ctrlKey but for the alt key
			//		- shiftKey: same as ctrlKey but for the shift key
			// returns:
			//		a connection handle

			// Setup keydown or keypress listener depending on whether keyCode or charCode was specified.
			var type = "keyCode" in keyObject ? "keydown" : "keypress",
				attr = "keyCode" in keyObject ? "keyCode" : "charCode";

			var handles = [
				on(node, type, lang.hitch(this, function(evt){
					if(evt[attr] == keyObject[attr] &&
						(keyObject.ctrlKey === undefined || keyObject.ctrlKey == evt.ctrlKey) &&
						(keyObject.altKey === undefined || keyObject.altKey == evt.altKey) &&
						(keyObject.metaKey === undefined || keyObject.metaKey == (evt.metaKey || false)) && // IE doesn't even set metaKey
						(keyObject.shiftKey === undefined || keyObject.shiftKey == evt.shiftKey)){
						evt.stopPropagation();
						evt.preventDefault();
						typematic.trigger(evt, _this, node, callback, keyObject, subsequentDelay, initialDelay, minDelay);
					}else if(typematic._obj == keyObject){
						typematic.stop();
					}
				})),
				on(node, "keyup", lang.hitch(this, function(){
					if(typematic._obj == keyObject){
						typematic.stop();
					}
				}))
			];
			return { remove: function(){
				array.forEach(handles, function(h){
					h.remove();
				});
			} };
		},

		addMouseListener: function(/*DOMNode*/ node, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
			// summary:
			//		Start listening for a typematic mouse click.
			//		See the trigger method for other parameters.
			// returns:
			//		a connection handle
			var handles = [
				on(node, "mousedown", lang.hitch(this, function(evt){
					evt.preventDefault();
					typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay);
				})),
				on(node, "mouseup", lang.hitch(this, function(evt){
					if(this._obj){
						evt.preventDefault();
					}
					typematic.stop();
				})),
				on(node, "mouseout", lang.hitch(this, function(evt){
					if(this._obj){
						evt.preventDefault();
					}
					typematic.stop();
				})),
				on(node, "dblclick", lang.hitch(this, function(evt){
					evt.preventDefault();
					if(has("ie") < 9){
						typematic.trigger(evt, _this, node, callback, node, subsequentDelay, initialDelay, minDelay);
						setTimeout(lang.hitch(this, typematic.stop), 50);
					}
				}))
			];
			return { remove: function(){
				array.forEach(handles, function(h){
					h.remove();
				});
			} };
		},

		addListener: function(/*Node*/ mouseNode, /*Node*/ keyNode, /*Object*/ keyObject, /*Object*/ _this, /*Function*/ callback, /*Number*/ subsequentDelay, /*Number*/ initialDelay, /*Number?*/ minDelay){
			// summary:
			//		Start listening for a specific typematic key and mouseclick.
			//		This is a thin wrapper to addKeyListener and addMouseListener.
			//		See the addMouseListener and addKeyListener methods for other parameters.
			// mouseNode:
			//		the DOM node object to listen on for mouse events.
			// keyNode:
			//		the DOM node object to listen on for key events.
			// returns:
			//		a connection handle
			var handles = [
				this.addKeyListener(keyNode, keyObject, _this, callback, subsequentDelay, initialDelay, minDelay),
				this.addMouseListener(mouseNode, _this, callback, subsequentDelay, initialDelay, minDelay)
			];
			return { remove: function(){
				array.forEach(handles, function(h){
					h.remove();
				});
			} };
		}
	};

	return typematic;
});
<|endoftext|>


<commit_before>$(function() {
  const LENGTH_OF_WEEK = 6;
  const SUNDAY_NUMBER = 0;
  const SATURDAY_NUMBER = 6;

  var myAttendInfo = [];
  var selectedDate = moment();
  updateCurrentTime();
  getMyAttend();

  function getMyAttend() {
    $.ajax({
      type: "GET",
      url: '/attend',
      data: {},
      success: function(data) {
        myAttendInfo = _.map(data.result, function (record) {
          return moment().format(record);
        });

        getMyAttendSuccessCallback();
      },
      error: function(data) {
        alert('서버 에러' + JSON.stringify(data));
        location.href = "error.html";
      },
      dataType: 'json'
    });
  }

  function getMyAttendSuccessCallback() {
    makeAttendanceCalendar();
    addEvent();
  }

  function updateCurrentTime()
  {
  	var date = moment().format('MMMM Do YYYY, h:mm:ss a');
  	var currentTime = date;
  	$('#current_time').text(currentTime);
    setTimeout(updateCurrentTime, 1000);
  }

  function addEvent() {
    addRewardEvent();

    $('#attend_button').click(function() {
        $.ajax({
          type: "POST",
          url: '/attend',
          data: {},
          xhrFields: { withCredentials:true },
          success: function(data) {
            if(data.status === "ok") {
              alert('지각은 마음의 병 출첵 완료!!');
              location.reload();
            } else {
              alert('에러' + JSON.stringify(data));
            }
          },
          error: function(data) {
            alert('서버 에러' + JSON.stringify(data));
            location.href = "error.html";
          },
          dataType: 'json'
        });
    });

    $('#go_before_month_button').click(function() {
      selectedDate.subtract(1, 'month');
      makeAttendanceCalendar();
      addRewardEvent();
    });

    $('#go_next_month_button').click(function() {
      selectedDate.add(1, 'month');
      makeAttendanceCalendar();
      addRewardEvent();
    });

    function addRewardEvent() {
      $('.attendance_table_attend_reward').click(function() {
        alert("뿌듯함 +100");
      });
    }
  }

  function makeAttendanceCalendar() {
    removeAllRowsExceptTableFrame();

    var calendarTable = $('#attendance_table');
    var year = selectedDate.year();
    var month = selectedDate.month();
    var date = selectedDate.date();
    var query = '';

    query += createDummyColumnQueryBeforeFirstDay(year, month);
    query += createMonthColumnQuery(year, month);
    query += createDummyColumnQueryAfterLastDay(year, month);

    calendarTable.append(query);
    setCurrentYearAndMonthUI();
  }

  function removeAllRowsExceptTableFrame() {
    $('#attendance_table tr:not(.attendance_table_frame)').remove();
  }

  function setCurrentYearAndMonthUI () {
    var currentYearAndMonthUi = $('#current_year_month');
    currentYearAndMonthUi.text(selectedDate.format("YYYY년 MM월"));
  }

  function createMonthColumnQuery (year, month) {
    var query = '';
    for (var i = 1; i <= getLastDateOfMonth(year, month); i++) {
      if(isSunday(year, month, i)) {
        query += makeSundayColumnQuery(year, month, i);
      } else if (isSaturday(year, month, i)) {
        query += makeSaturdayColumnQuery(year, month, i);
      } else {
        query += makeWeekdayColumnQuery(year, month, i);
      }
    }
    return query;
  }

  function createDummyColumnQueryBeforeFirstDay(year, month) {
    var query = '';
    if (getFirstDayOfMonth(year, month) != SUNDAY_NUMBER) {
      query += '
';
    }

    for (var i = SUNDAY_NUMBER; i < getFirstDayOfMonth(year, month); i++) {
      query += '	';
    }

    return query;
  }

  function  createDummyColumnQueryAfterLastDay (year, month) {
    var query = '';
    for (var i = getLastDayOfMonth(year, month); i < LENGTH_OF_WEEK; i++) {
      query += '	';
    }

    if (getLastDayOfMonth(year, month) != LENGTH_OF_WEEK) {
      query += '
';
    }
    return query;
  }

  function makeSundayColumnQuery(year, month, date) {
    var createNewRowQuery = '
';
    if (isToday(year, month, date))
      return createNewRowQuery + '' + date + '	';
    else
      return createNewRowQuery + '' + date + '	';
  }

  function makeSaturdayColumnQuery(year, month, date) {
    var endRowQuery = '
';
    var rewardObject = '

';
    if (isToday(year, month, date))
      return '' + date + rewardObject + '	' + endRowQuery;
    else
      return '' + date + rewardObject + '	' + endRowQuery;
  }

  function makeWeekdayColumnQuery(year, month, date) {
    var attendanceRecordDate = getAttendanceRecordDate(year, month, date);
    if (isToday(year, month, date)) {
      if (attendanceRecordDate) {
        $('#attend_button').attr('disabled', 'disabled');
        var attenanceQuery = '' + attendanceRecordDate.format("hh:mm A") + '';
        return '' + date + attenanceQuery + '	';
      } else {
        return '' + date + '	';
      }
    }
    else if (attendanceRecordDate) {
      var attenanceQuery = '' + attendanceRecordDate.format("hh:mm A") + '';
      return '' + date + attenanceQuery + '	';
    }
    else {
      return '' + date + '	';
    }
  }

  function getAttendanceRecordDate (year, month, date) {
    var dateToFind = moment(new Date(year, month, date));
    var filteredData = _.filter(myAttendInfo, function(record) {
      return record.year() === dateToFind.year() && record.month() === dateToFind.month() && record.date() === dateToFind.date();
    });

    if (filteredData.length === 0)
      return null;
    else
      return filteredData[0];
  }

  //Sunday
  function isSunday(year, month, date) {
    return moment(new Date(year, month, date)).day() === SUNDAY_NUMBER;
  }

  //Saturday
  function isSaturday(year, month, date) {
    return moment(new Date(year, month, date)).day() === LENGTH_OF_WEEK;
  }

  function getFirstDayOfMonth(year, month) {
    return moment(new Date(year, month, 1)).day();
  }

  function getLastDayOfMonth(year, month) {
    return moment(new Date(year, month, getLastDateOfMonth(year, month))).day();
  }

  function getLastDateOfMonth(year, month) {
    return moment(new Date(year, month + 1, 1)).subtract(1, 'day').date();
  }

  function isToday(year, month, date) {
    var date = moment(new Date(year, month, date));
    var today = moment();
    return date.year() === today.year() && date.month() === today.month() && date.date() === today.date();
  }
});<commit_msg>Fix bugs that creating moment object is invalid.<commit_after>$(function() {
  const LENGTH_OF_WEEK = 6;
  const SUNDAY_NUMBER = 0;
  const SATURDAY_NUMBER = 6;

  var myAttendInfo = [];
  var selectedDate = moment();
  updateCurrentTime();
  getMyAttend();

  function getMyAttend() {
    $.ajax({
      type: "GET",
      url: '/attend',
      data: {},
      success: function(data) {
        myAttendInfo = _.map(data.result, function (record) {
          return moment(record);
        });

        getMyAttendSuccessCallback();
      },
      error: function(data) {
        alert('서버 에러' + JSON.stringify(data));
        location.href = "error.html";
      },
      dataType: 'json'
    });
  }

  function getMyAttendSuccessCallback() {
    makeAttendanceCalendar();
    addEvent();
  }

  function updateCurrentTime()
  {
  	var date = moment().format('MMMM Do YYYY, h:mm:ss a');
  	var currentTime = date;
  	$('#current_time').text(currentTime);
    setTimeout(updateCurrentTime, 1000);
  }

  function addEvent() {
    addRewardEvent();

    $('#attend_button').click(function() {
        $.ajax({
          type: "POST",
          url: '/attend',
          data: {},
          xhrFields: { withCredentials:true },
          success: function(data) {
            if(data.status === "ok") {
              alert('지각은 마음의 병 출첵 완료!!');
              location.reload();
            } else {
              alert('에러' + JSON.stringify(data));
            }
          },
          error: function(data) {
            alert('서버 에러' + JSON.stringify(data));
            location.href = "error.html";
          },
          dataType: 'json'
        });
    });

    $('#go_before_month_button').click(function() {
      selectedDate.subtract(1, 'month');
      makeAttendanceCalendar();
      addRewardEvent();
    });

    $('#go_next_month_button').click(function() {
      selectedDate.add(1, 'month');
      makeAttendanceCalendar();
      addRewardEvent();
    });

    function addRewardEvent() {
      $('.attendance_table_attend_reward').click(function() {
        alert("뿌듯함 +100");
      });
    }
  }

  function makeAttendanceCalendar() {
    removeAllRowsExceptTableFrame();

    var calendarTable = $('#attendance_table');
    var year = selectedDate.year();
    var month = selectedDate.month();
    var date = selectedDate.date();
    var query = '';

    query += createDummyColumnQueryBeforeFirstDay(year, month);
    query += createMonthColumnQuery(year, month);
    query += createDummyColumnQueryAfterLastDay(year, month);

    calendarTable.append(query);
    setCurrentYearAndMonthUI();
  }

  function removeAllRowsExceptTableFrame() {
    $('#attendance_table tr:not(.attendance_table_frame)').remove();
  }

  function setCurrentYearAndMonthUI () {
    var currentYearAndMonthUi = $('#current_year_month');
    currentYearAndMonthUi.text(selectedDate.format("YYYY년 MM월"));
  }

  function createMonthColumnQuery (year, month) {
    var query = '';
    for (var i = 1; i <= getLastDateOfMonth(year, month); i++) {
      if(isSunday(year, month, i)) {
        query += makeSundayColumnQuery(year, month, i);
      } else if (isSaturday(year, month, i)) {
        query += makeSaturdayColumnQuery(year, month, i);
      } else {
        query += makeWeekdayColumnQuery(year, month, i);
      }
    }
    return query;
  }

  function createDummyColumnQueryBeforeFirstDay(year, month) {
    var query = '';
    if (getFirstDayOfMonth(year, month) != SUNDAY_NUMBER) {
      query += '
';
    }

    for (var i = SUNDAY_NUMBER; i < getFirstDayOfMonth(year, month); i++) {
      query += '	';
    }

    return query;
  }

  function  createDummyColumnQueryAfterLastDay (year, month) {
    var query = '';
    for (var i = getLastDayOfMonth(year, month); i < LENGTH_OF_WEEK; i++) {
      query += '	';
    }

    if (getLastDayOfMonth(year, month) != LENGTH_OF_WEEK) {
      query += '
';
    }
    return query;
  }

  function makeSundayColumnQuery(year, month, date) {
    var createNewRowQuery = '
';
    if (isToday(year, month, date))
      return createNewRowQuery + '' + date + '	';
    else
      return createNewRowQuery + '' + date + '	';
  }

  function makeSaturdayColumnQuery(year, month, date) {
    var endRowQuery = '
';
    var rewardObject = '

';
    if (isToday(year, month, date))
      return '' + date + rewardObject + '	' + endRowQuery;
    else
      return '' + date + rewardObject + '	' + endRowQuery;
  }

  function makeWeekdayColumnQuery(year, month, date) {
    var attendanceRecordDate = getAttendanceRecordDate(year, month, date);
    if (isToday(year, month, date)) {
      if (attendanceRecordDate) {
        $('#attend_button').attr('disabled', 'disabled');
        var attenanceQuery = '' + attendanceRecordDate.format("hh:mm A") + '';
        return '' + date + attenanceQuery + '	';
      } else {
        return '' + date + '	';
      }
    }
    else if (attendanceRecordDate) {
      var attenanceQuery = '' + attendanceRecordDate.format("hh:mm A") + '';
      return '' + date + attenanceQuery + '	';
    }
    else {
      return '' + date + '	';
    }
  }

  function getAttendanceRecordDate (year, month, date) {
    var dateToFind = moment(new Date(year, month, date));
    var filteredData = _.filter(myAttendInfo, function(record) {
      return record.year() === dateToFind.year() && record.month() === dateToFind.month() && record.date() === dateToFind.date();
    });

    if (filteredData.length === 0)
      return null;
    else
      return filteredData[0];
  }

  //Sunday
  function isSunday(year, month, date) {
    return moment(new Date(year, month, date)).day() === SUNDAY_NUMBER;
  }

  //Saturday
  function isSaturday(year, month, date) {
    return moment(new Date(year, month, date)).day() === LENGTH_OF_WEEK;
  }

  function getFirstDayOfMonth(year, month) {
    return moment(new Date(year, month, 1)).day();
  }

  function getLastDayOfMonth(year, month) {
    return moment(new Date(year, month, getLastDateOfMonth(year, month))).day();
  }

  function getLastDateOfMonth(year, month) {
    return moment(new Date(year, month + 1, 1)).subtract(1, 'day').date();
  }

  function isToday(year, month, date) {
    var date = moment(new Date(year, month, date));
    var today = moment();
    return date.year() === today.year() && date.month() === today.month() && date.date() === today.date();
  }
});<|endoftext|>


<commit_before>// query autorepair module
if (typeof turbo === "undefined") turbo={};
turbo.autorepair = function(q, lng) {
  var repair = {};

  var comments = {};

  (function init() {
    // replace comments with placeholders
    // (we do not want to autorepair stuff which is commented out.)
    if (lng == "xml") {
      var cs = q.match(/<!--[\s\S]*?-->/g) || [];
      for (var i=0; i<cs.length; i++) {
        var placeholder = "<!--"+Base64.encode(Math.random().toString())+"-->"; //todo: use some kind of checksum or hash maybe?
        q = q.replace(cs[i],placeholder);
        comments[placeholder] = cs[i];
      }
    } else {
      var cs = q.match(/\/\*[\s\S]*?\*\//g) || []; // multiline comments: /*...*/
      for (var i=0; i<cs.length; i++) {
        var placeholder = "/*"+Base64.encode(Math.random().toString())+"*/"; //todo: use some kind of checksum or hash maybe?
        q = q.replace(cs[i],placeholder);
        comments[placeholder] = cs[i];
      }
      var cs = q.match(/\/\/[^\n]*/g) || []; // single line coments: //...
      for (var i=0; i<cs.length; i++) {
        var placeholder = "/*"+Base64.encode(Math.random().toString())+"*/"; //todo: use some kind of checksum or hash maybe?
        q = q.replace(cs[i],placeholder);
        comments[placeholder] = cs[i];
      }
    }
  })();

  repair.getQuery = function() {
    // expand placeholded comments
    for(var placeholder in comments) {
      q = q.replace(placeholder,comments[placeholder]);
    }
    return q;
  }

  repair.recurse = function() {
    if (lng == "xml") {
      // do some fancy mixture between regex magic and xml as html parsing :€
      var prints = q.match(/(\n?[^\S\n]*<print[\s\S]*?(\/>|<\/print>))/g) || [];
      for (var i=0;i<prints.length;i++) {
        var ws = prints[i].match(/^\n?(\s*)/)[1]; // amount of whitespace in front of each print statement
        var from = $("print",$.parseXML(prints[i])).attr("from");
        var add1,add2,add3;
        if (from) {
          add1 = ' into="'+from+'"'; add2 = ' set="'+from+'"'; add3 = ' from="'+from+'"';
        } else {
          add1 = ''; add2 = ''; add3 = '';
        }
        q = q.replace(prints[i],"\n"+ws+"<!-- added by auto repair -->\n"+ws+"<union"+add1+">\n"+ws+"  <item"+add2+"/>\n"+ws+"  <recurse"+add3+' type="down"/>\n'+ws+"</union>\n"+ws+"<!-- end of auto repair --><autorepair>"+i+"</autorepair>");
      }
      for (var i=0;i<prints.length;i++)
        q = q.replace("<autorepair>"+i+"</autorepair>", prints[i]);
    } else {
      var outs = q.match(/(\n?[^\S\n]*(\.[^.;]+)?out[^:;"\]]*;)/g) || [];
      for (var i=0;i<outs.length;i++) {
        var ws = outs[i].match(/^\n?(\s*)/)[0]; // amount of whitespace
        var from = outs[i].match(/\.([^;.]+?)\s+out/);
        var add;
        if (from)
          add = "(."+from[1]+";."+from[1]+" >;)->."+from[1]+";";
        else
          add = "(._;>;);";
        q = q.replace(outs[i],ws+"/*added by auto repair*/"+ws+add+ws+"/*end of auto repair*/<autorepair>"+i+"</autorepair>");
      }
      for (var i=0;i<outs.length;i++)
        q = q.replace("<autorepair>"+i+"</autorepair>", outs[i]);
    }
    return true;
  }

  repair.editors = function() {
    if (lng == "xml") {
      // 1. fix <osm-script output=*
      var src = q.match(/<osm-script([^>]*)>/);
      if (src) {
        var output = $("osm-script",$.parseXML(src[0]+"</osm-script>")).attr("output");
        if (output && output != "xml") {
          var new_src = src[0].replace(output,"xml");
          q = q.replace(src[0],new_src+"<!-- fixed by auto repair -->");
        }
      }
      // 2. fix <print mode=*
      var prints = q.match(/(<print[\s\S]*?(\/>|<\/print>))/g) || [];
      for (var i=0;i<prints.length;i++) {
        var print = $("print",$.parseXML(prints[i])),
            mode = print.attr("mode"),
            geometry = print.attr("geometry");
        var add = "",
            new_print,
            repaired = false;
        if (mode !== "meta") {
          print.attr("mode", "meta");
          repaired = true;
        }
        if (geometry && geometry !== "skeleton") {
          print.attr("geometry", null);
          var out_set = print.attr("from");
          if (!out_set) {
            add = '<union><item/><recurse type="down"/></union>';
          } else {
            add = '<union into="'+out_set+'"><item set="'+out_set+'"/><recurse from="'+out_set+'" type="down"/></union>';
          }
          repaired = true;
        }
        if (repaired) {
          new_print = add+(new XMLSerializer()).serializeToString(print[0]);
          new_print += "<!-- fixed by auto repair -->";
          q = q.replace(prints[i],new_print);
        }
      }
    } else {
      // 1. fix [out:*]
      var out = q.match(/\[\s*out\s*:\s*([^\]\s]+)\s*\]\s*;?/);
          ///^\s*\[\s*out\s*:\s*([^\]\s]+)/);
      if (out && out[1] != "xml")
        q = q.replace(/(\[\s*out\s*:\s*)([^\]\s]+)(\s*\]\s*;?)/,"$1xml$3/*fixed by auto repair*/");
      // 2. fix print statements: non meta output, overpass geometries
      var prints = q.match(/(\.([^;.]+?)\s+)?(out[^:;"\]]*;)/g) || [];
      for (var i=0;i<prints.length;i++) {
        var print = prints[i].match(/(\.([^;.]+?)\s+)?(out[^:;"\]]*;)/);
        var out_statement = print[3],
            out_set = print[2],
            print = print[0];
        var new_print = print;
        // non meta output
        if (out_statement.match(/\s(body|skel|ids|tags)/) || !out_statement.match(/\s(meta)/)) {
          var new_out_statement = out_statement.replace(/\s(body|skel|ids|tags|meta)/g,"").replace(/^out/,"out meta");
          new_print = new_print.replace(out_statement, new_out_statement);
          out_statement = new_out_statement;
        }
        // overpass geometry modes
        if (out_statement.match(/\s(center|bb|geom)/)) {
          var new_out_statement = out_statement.replace(/\s(center|bb|geom)/g,"");
          new_print = new_print.replace(out_statement, new_out_statement);
          out_statement = new_out_statement;
          if (out_set) {
            new_print = "(."+out_set+";."+out_set+" >;)->."+out_set+"; "+new_print;
          } else {
            new_print = "(._;>;); "+new_print;
          }
        }
        if (new_print != print)
          q = q.replace(print,new_print+"/*fixed by auto repair*/");
      }
    }
    return true;
  }

  return repair;
};

turbo.autorepair.detect = {};
turbo.autorepair.detect.editors = function(q, lng) {
  // todo: test this
  // todo: move into autorepair "module" /// todo. done?
  q = q.replace(/{{.*?}}/g,"");
  var err = {};
  if (lng == "xml") {
    try {
      var xml = $.parseXML("<x>"+q+"</x>");
      var out = $("osm-script",xml).attr("output");
      if (out !== undefined && out !== "xml")
        err.output = true;
      $("print",xml).each(function(i,p) { if($(p).attr("mode")!=="meta") err.meta=true; });
      $("print",xml).each(function(i,p) { if($(p).attr("geometry").match(/(center|bounds|full)/)) err.geometry=true; });
    } catch(e) {} // ignore xml syntax errors ?!
  } else {
    // ignore comments
    q=q.replace(/\/\*[\s\S]*?\*\//g,"");
    q=q.replace(/\/\/[^\n]*/g,"");
    var out = q.match(/\[\s*out\s*:\s*([^\]\s]+)\s*\]\s*;/);
    if (out && out[1] != "xml")
      err.output = true;
    var prints = q.match(/out([^:;]*);/g);
    $(prints).each(function(i,p) {if (p.match(/\s(body|skel|ids|tags)/) || !p.match(/meta/)) err.meta=true;});
    $(prints).each(function(i,p) {if (p.match(/\s(center|bb|geom)/)) err.geometry=true;});
  }
  return $.isEmptyObject(err);
}<commit_msg>fix autorepair with multiple initial statements<commit_after>// query autorepair module
if (typeof turbo === "undefined") turbo={};
turbo.autorepair = function(q, lng) {
  var repair = {};

  var comments = {};

  (function init() {
    // replace comments with placeholders
    // (we do not want to autorepair stuff which is commented out.)
    if (lng == "xml") {
      var cs = q.match(/<!--[\s\S]*?-->/g) || [];
      for (var i=0; i<cs.length; i++) {
        var placeholder = "<!--"+Base64.encode(Math.random().toString())+"-->"; //todo: use some kind of checksum or hash maybe?
        q = q.replace(cs[i],placeholder);
        comments[placeholder] = cs[i];
      }
    } else {
      var cs = q.match(/\/\*[\s\S]*?\*\//g) || []; // multiline comments: /*...*/
      for (var i=0; i<cs.length; i++) {
        var placeholder = "/*"+Base64.encode(Math.random().toString())+"*/"; //todo: use some kind of checksum or hash maybe?
        q = q.replace(cs[i],placeholder);
        comments[placeholder] = cs[i];
      }
      var cs = q.match(/\/\/[^\n]*/g) || []; // single line coments: //...
      for (var i=0; i<cs.length; i++) {
        var placeholder = "/*"+Base64.encode(Math.random().toString())+"*/"; //todo: use some kind of checksum or hash maybe?
        q = q.replace(cs[i],placeholder);
        comments[placeholder] = cs[i];
      }
    }
  })();

  repair.getQuery = function() {
    // expand placeholded comments
    for(var placeholder in comments) {
      q = q.replace(placeholder,comments[placeholder]);
    }
    return q;
  }

  repair.recurse = function() {
    if (lng == "xml") {
      // do some fancy mixture between regex magic and xml as html parsing :€
      var prints = q.match(/(\n?[^\S\n]*<print[\s\S]*?(\/>|<\/print>))/g) || [];
      for (var i=0;i<prints.length;i++) {
        var ws = prints[i].match(/^\n?(\s*)/)[1]; // amount of whitespace in front of each print statement
        var from = $("print",$.parseXML(prints[i])).attr("from");
        var add1,add2,add3;
        if (from) {
          add1 = ' into="'+from+'"'; add2 = ' set="'+from+'"'; add3 = ' from="'+from+'"';
        } else {
          add1 = ''; add2 = ''; add3 = '';
        }
        q = q.replace(prints[i],"\n"+ws+"<!-- added by auto repair -->\n"+ws+"<union"+add1+">\n"+ws+"  <item"+add2+"/>\n"+ws+"  <recurse"+add3+' type="down"/>\n'+ws+"</union>\n"+ws+"<!-- end of auto repair --><autorepair>"+i+"</autorepair>");
      }
      for (var i=0;i<prints.length;i++)
        q = q.replace("<autorepair>"+i+"</autorepair>", prints[i]);
    } else {
      var outs = q.match(/(\n?[^\S\n]*(\.[^.;]+)?out[^:;"\]]*;)/g) || [];
      for (var i=0;i<outs.length;i++) {
        var ws = outs[i].match(/^\n?(\s*)/)[0]; // amount of whitespace
        var from = outs[i].match(/\.([^;.]+?)\s+out/);
        var add;
        if (from)
          add = "(."+from[1]+";."+from[1]+" >;)->."+from[1]+";";
        else
          add = "(._;>;);";
        q = q.replace(outs[i],ws+"/*added by auto repair*/"+ws+add+ws+"/*end of auto repair*/<autorepair>"+i+"</autorepair>");
      }
      for (var i=0;i<outs.length;i++)
        q = q.replace("<autorepair>"+i+"</autorepair>", outs[i]);
    }
    return true;
  }

  repair.editors = function() {
    if (lng == "xml") {
      // 1. fix <osm-script output=*
      var src = q.match(/<osm-script([^>]*)>/);
      if (src) {
        var output = $("osm-script",$.parseXML(src[0]+"</osm-script>")).attr("output");
        if (output && output != "xml") {
          var new_src = src[0].replace(output,"xml");
          q = q.replace(src[0],new_src+"<!-- fixed by auto repair -->");
        }
      }
      // 2. fix <print mode=*
      var prints = q.match(/(<print[\s\S]*?(\/>|<\/print>))/g) || [];
      for (var i=0;i<prints.length;i++) {
        var print = $("print",$.parseXML(prints[i])),
            mode = print.attr("mode"),
            geometry = print.attr("geometry");
        var add = "",
            new_print,
            repaired = false;
        if (mode !== "meta") {
          print.attr("mode", "meta");
          repaired = true;
        }
        if (geometry && geometry !== "skeleton") {
          print.attr("geometry", null);
          var out_set = print.attr("from");
          if (!out_set) {
            add = '<union><item/><recurse type="down"/></union>';
          } else {
            add = '<union into="'+out_set+'"><item set="'+out_set+'"/><recurse from="'+out_set+'" type="down"/></union>';
          }
          repaired = true;
        }
        if (repaired) {
          new_print = add+(new XMLSerializer()).serializeToString(print[0]);
          new_print += "<!-- fixed by auto repair -->";
          q = q.replace(prints[i],new_print);
        }
      }
    } else {
      // 1. fix [out:*]
      var out = q.match(/\[\s*out\s*:\s*([^\]\s]+)\s*\]\s*;?/);
          ///^\s*\[\s*out\s*:\s*([^\]\s]+)/);
      if (out && out[1] != "xml")
        q = q.replace(/(\[\s*out\s*:\s*)([^\]\s]+)(\s*\]\s*;?)/,"$1xml$3/*fixed by auto repair*/");
      // 2. fix print statements: non meta output, overpass geometries
      var prints = q.match(/(\.([^;.]+?)\s+)?(out[^:;"\]]*;)/g) || [];
      for (var i=0;i<prints.length;i++) {
        var print = prints[i].match(/(\.([^;.]+?)\s+)?(out[^:;"\]]*;)/);
        var out_statement = print[3],
            out_set = print[2],
            print = print[0];
        var new_print = print;
        // non meta output
        if (out_statement.match(/\s(body|skel|ids|tags)/) || !out_statement.match(/\s(meta)/)) {
          var new_out_statement = out_statement.replace(/\s(body|skel|ids|tags|meta)/g,"").replace(/^out/,"out meta");
          new_print = new_print.replace(out_statement, new_out_statement);
          out_statement = new_out_statement;
        }
        // overpass geometry modes
        if (out_statement.match(/\s(center|bb|geom)/)) {
          var new_out_statement = out_statement.replace(/\s(center|bb|geom)/g,"");
          new_print = new_print.replace(out_statement, new_out_statement);
          out_statement = new_out_statement;
          if (out_set) {
            new_print = "(."+out_set+";."+out_set+" >;)->."+out_set+"; "+new_print;
          } else {
            new_print = "(._;>;); "+new_print;
          }
        }
        if (new_print != print)
          q = q.replace(print,new_print+"/*fixed by auto repair*/");
      }
    }
    return true;
  }

  return repair;
};

turbo.autorepair.detect = {};
turbo.autorepair.detect.editors = function(q, lng) {
  // todo: test this
  // todo: move into autorepair "module" /// todo. done?
  q = q.replace(/{{.*?}}/g,"");
  var err = {};
  if (lng == "xml") {
    try {
      var xml = $.parseXML("<x>"+q+"</x>");
      var out = $("osm-script",xml).attr("output");
      if (out !== undefined && out !== "xml")
        err.output = true;
      $("print",xml).each(function(i,p) { if($(p).attr("mode")!=="meta") err.meta=true; });
      $("print",xml).each(function(i,p) { if($(p).attr("geometry").match(/(center|bounds|full)/)) err.geometry=true; });
    } catch(e) {} // ignore xml syntax errors ?!
  } else {
    // ignore comments
    q=q.replace(/\/\*[\s\S]*?\*\//g,"");
    q=q.replace(/\/\/[^\n]*/g,"");
    var out = q.match(/\[\s*out\s*:\s*([^\]\s]+)\s*\]/);
    if (out && out[1] != "xml")
      err.output = true;
    var prints = q.match(/out([^:;]*);/g);
    $(prints).each(function(i,p) {if (p.match(/\s(body|skel|ids|tags)/) || !p.match(/meta/)) err.meta=true;});
    $(prints).each(function(i,p) {if (p.match(/\s(center|bb|geom)/)) err.geometry=true;});
  }
  return $.isEmptyObject(err);
}
<|endoftext|>


<commit_before>var options;

// Performs an ajax request
function ajaxRequest(request, callback) {

    var xhr = new XMLHttpRequest();
    var response = request.response;
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
            if (xhr.status == 200) {
                if (response === 'URL') {
                    callback(xhr.responseURL);
                }
                else {
                    callback(xhr.responseText);
                }
            } else {
                callback(null);
            }
        }
    }
    xhr.open(request.method, request.url, true);
    for (var i in request.headers) {
        xhr.setRequestHeader(request.headers[i].header, request.headers[i].value);
    }
    xhr.send(request.data);
}

function onMessage(message, sender, callback) {
    switch (message.action) {
        case 'downloadFile':
            chrome.permissions.request({
                permissions: ['downloads']
            }, function (granted) {
                if (granted) {
                    chrome.downloads.download({url: message.url, filename: message.filename});
                    return true;
                } else {
                    return false;
                }
            });
        case 'ajaxGet':
            ajaxRequest({url:message.url, response:message.response, method:'GET'}, callback);
            return true;
        case 'ajaxRequest':
            ajaxRequest(message, callback);
            return true;
        case 'showPageAction':
            showPageAction(sender.tab);
            callback();
            return true;
        case 'addUrlToHistory':
            chrome.permissions.contains({permissions: ['history']}, function(granted) {
                if (granted) {
                    chrome.history.addUrl({url:message.url});
                }
            });
            break;
        case 'getOptions':
            callback(options);
            return true;
        case 'setOption':
            options[message.name] = message.value;
            localStorage.options = JSON.stringify(options);
            sendOptions(message.options);
            break;
        case 'optionsChanged':
            options = message.options;
            break;
        case 'saveOptions':
            localStorage.options = JSON.stringify(message.options);
            sendOptions(message.options);
            break;
        case 'setItem':
            localStorage.setItem(message.id, message.data);
            break;
        case 'getItem':
            callback(localStorage.getItem(message.id));
            return true;
        case 'removeItem':
            localStorage.removeItem(message.id);
            break;
        case 'openViewWindow':
            var url = message.createData.url;
            if (url.indexOf('facebook.com/photo/download') !== -1) {
                message.createData.url = 'data:text/html,';
            }
            chrome.windows.create(message.createData, function (window) {
                chrome.tabs.executeScript(window.tabs[0].id, {file:'js/viewWindow.js'});
            });
            break;
        case 'openViewTab':
            chrome.tabs.query({active: true}, function (tabs) {
                message.createData.index = tabs[0].index;
                if (!message.createData.active)
                    message.createData.index++;
                var url = message.createData.url;
                if (url.indexOf('facebook.com/photo/download') !== -1) {
                    message.createData.url = 'data:text/html,';
                }
                chrome.tabs.create(message.createData, function (tab) {
                    chrome.tabs.executeScript(tab.id, {file:'js/viewTab.js'});
                });
            });
            break;
    }
}

function showPageAction(tab) {
    if (!tab) {
        return;
    }
    if (!options.extensionEnabled || isExcludedSite(tab.url)) {
        chrome.pageAction.setIcon({tabId:tab.id, path:'../images/icon19d.png'});
    } else {
        chrome.pageAction.setIcon({tabId:tab.id, path:'../images/icon19.png'});
    }
    chrome.pageAction.show(tab.id);
}

function init() {
    // Load options
    options = loadOptions();

    // Bind events
    chrome.runtime.onMessage.addListener(onMessage);
}

init();
<commit_msg>Fix firefox not showing page action icon (#640)<commit_after>var options;

// Performs an ajax request
function ajaxRequest(request, callback) {

    var xhr = new XMLHttpRequest();
    var response = request.response;
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
            if (xhr.status == 200) {
                if (response === 'URL') {
                    callback(xhr.responseURL);
                }
                else {
                    callback(xhr.responseText);
                }
            } else {
                callback(null);
            }
        }
    }
    xhr.open(request.method, request.url, true);
    for (var i in request.headers) {
        xhr.setRequestHeader(request.headers[i].header, request.headers[i].value);
    }
    xhr.send(request.data);
}

function onMessage(message, sender, callback) {
    switch (message.action) {
        case 'downloadFile':
            chrome.permissions.request({
                permissions: ['downloads']
            }, function (granted) {
                if (granted) {
                    chrome.downloads.download({url: message.url, filename: message.filename});
                    return true;
                } else {
                    return false;
                }
            });
        case 'ajaxGet':
            ajaxRequest({url:message.url, response:message.response, method:'GET'}, callback);
            return true;
        case 'ajaxRequest':
            ajaxRequest(message, callback);
            return true;
        case 'showPageAction':
            // Firefox url is located at sender.url, copy sender.url to sender.tab.url
            if (!sender.tab.url && sender.url)
                sender.tab.url = sender.url
            showPageAction(sender.tab);
            callback();
            return true;
        case 'addUrlToHistory':
            chrome.permissions.contains({permissions: ['history']}, function(granted) {
                if (granted) {
                    chrome.history.addUrl({url:message.url});
                }
            });
            break;
        case 'getOptions':
            callback(options);
            return true;
        case 'setOption':
            options[message.name] = message.value;
            localStorage.options = JSON.stringify(options);
            sendOptions(message.options);
            break;
        case 'optionsChanged':
            options = message.options;
            break;
        case 'saveOptions':
            localStorage.options = JSON.stringify(message.options);
            sendOptions(message.options);
            break;
        case 'setItem':
            localStorage.setItem(message.id, message.data);
            break;
        case 'getItem':
            callback(localStorage.getItem(message.id));
            return true;
        case 'removeItem':
            localStorage.removeItem(message.id);
            break;
        case 'openViewWindow':
            var url = message.createData.url;
            if (url.indexOf('facebook.com/photo/download') !== -1) {
                message.createData.url = 'data:text/html,';
            }
            chrome.windows.create(message.createData, function (window) {
                chrome.tabs.executeScript(window.tabs[0].id, {file:'js/viewWindow.js'});
            });
            break;
        case 'openViewTab':
            chrome.tabs.query({active: true}, function (tabs) {
                message.createData.index = tabs[0].index;
                if (!message.createData.active)
                    message.createData.index++;
                var url = message.createData.url;
                if (url.indexOf('facebook.com/photo/download') !== -1) {
                    message.createData.url = 'data:text/html,';
                }
                chrome.tabs.create(message.createData, function (tab) {
                    chrome.tabs.executeScript(tab.id, {file:'js/viewTab.js'});
                });
            });
            break;
    }
}

function showPageAction(tab) {
    if (!tab) {
        return;
    }
    if (!options.extensionEnabled || isExcludedSite(tab.url)) {
        chrome.pageAction.setIcon({tabId:tab.id, path:'../images/icon19d.png'});
    } else {
        chrome.pageAction.setIcon({tabId:tab.id, path:'../images/icon19.png'});
    }
    chrome.pageAction.show(tab.id);
}

function init() {
    // Load options
    options = loadOptions();

    // Bind events
    chrome.runtime.onMessage.addListener(onMessage);
}

init();
<|endoftext|>


<commit_before>
var textToRead = '';

var contextMenuAdded = false;

chrome.tabs.onUpdated.addListener(function(tabId, change, tab) {

	// If not complete or invalid URL
	if (change.status !== 'complete' || !tab.url.match(/^https?:\/\//)) {
		return false;
	}

	updateContextMenu();

	// Try to ping tab to check if script has already been injected
	chrome.tabs.sendMessage(tabId, {action: 'ping'}, function(response) {

		// Already injected, let's bail
		if(response) {
			return false;
		}

		// Check if domain is blacklisted
		//isBlacklisted(tab.url, function(isBlacklisted) {
		//	if(isBlacklisted) {
		//		return false;
		//	}

			// Show or hide toast for this tab
			getOption('hideToast', function(hideToast) {
				if(!hideToast) {
					chrome.tabs.insertCSS(tabId, {file: 'css/content.css'}, function() {
						chrome.tabs.executeScript(tabId, {file: 'js/libs/readability.js', runAt: 'document_end'});
						chrome.tabs.executeScript(tabId, {file: 'js/content.js', runAt: 'document_end'});
					});
				}
			});
		//});
	});

});

// When the toolbar icon is clicked
chrome.browserAction.onClicked.addListener(function(tab) {
});

// Listen for callbacks from injected code
chrome.extension.onMessage.addListener(function(data, sender, sendResponse) {
	if(typeof data === 'object') {

		// Add domain to blacklist
		if(data.action == 'blacklist') {
			var domain = new URL(data.url).hostname;

			// Get current value and append domain to it
			getOption('toastBlacklist', function(currVal) {
				var newVal = currVal + "\r\n" + domain;
				chrome.storage.sync.set({
					toastBlacklist: newVal
				});
			});

		// App is requesting text
		} else if(data.action == 'getText') {
			sendResponse(textToRead);

		// Document (content script) requested a read start
		} else if(data.action == 'requestRead') {
			textToRead = data.text;
			openApp();
		}

	}
});

// Sometimes they stay in memory after extension reload
// so let's just make sure we remove it completely on load.
chrome.contextMenus.removeAll();

function updateContextMenu() {
	getOption('hideContextMenu', function(hideContextMenu) {

		// Show menu
		if(!hideContextMenu) {

			// If already added, do nothing
			if(contextMenuAdded) {
				return false;
			}

			chrome.contextMenus.create({
				id: 'readSelection',
				title : 'Read selected text with Readio',
				contexts : ['selection']
			});

			chrome.contextMenus.create({
				id: 'readPage',
				title : 'Read page with Readio',
				contexts : ['all']
			});

			chrome.contextMenus.onClicked.addListener(function(info, tab) {

				if(info.menuItemId === 'readSelection') {

					chrome.tabs.executeScript( {
						code: "window.getSelection().toString();"
					}, function(selection) {
						var selected = selection[0];
						textToRead = selected;
						openApp();
					});

				} else if(info.menuItemId === 'readPage') {

					chrome.tabs.sendMessage(tab.id, {action: 'getParseData'}, function(text) {
						if(text) {
							textToRead = text;
							openApp();
						}
					});

				}

			});

			contextMenuAdded = true;

		// Hide menu if added
		} else {
			if(contextMenuAdded) {
				chrome.contextMenus.remove('readSelection');
				chrome.contextMenus.remove('readPage');
			}
			contextMenuAdded = false;
		}
	});
}

function openApp() {
	var width = 1200,
		height = 650/*,
		left = (screen.width/2) - (width/2),
		top = (screen.height/2) - (height/2)*/;

	chrome.windows.create({
		url: 'app.html',
		type: 'popup',
		width: width,
		height: height

		/*, Will center window but can't control which monitor it's displayed on, so skipping for now
		left: left,
		top: top*/
	});
}

function getOption(option, callback) {
	chrome.storage.sync.get(option, function(obj) {
		if(obj[option] !== undefined) {
			callback(obj[option]);
		} else {
			callback();
		}
	});
}

function isBlacklisted(url, callback) {
	var currDom = new URL(url).hostname;
	var stored = getOption('toastBlacklist', function(stored) {

		var split = stored.split(/\r|\n/g);

		if(split.length > 0) {
			for(var i=0; i < split.length; ++i) {
				var dom = split[i];
				if(dom.indexOf(currDom) !== -1) {
					callback(true);
					return true; // Prevent loop from continuing
				}
			}
		}
		callback(false);
	});
}<commit_msg>Plaintext (from selection) now parses correctly with line breaks<commit_after>
var textToRead = '';

var contextMenuAdded = false;

chrome.tabs.onUpdated.addListener(function(tabId, change, tab) {

	// If not complete or invalid URL
	if (change.status !== 'complete' || !tab.url.match(/^https?:\/\//)) {
		return false;
	}

	updateContextMenu();

	// Try to ping tab to check if script has already been injected
	chrome.tabs.sendMessage(tabId, {action: 'ping'}, function(response) {

		// Already injected, let's bail
		if(response) {
			return false;
		}

		// Check if domain is blacklisted
		//isBlacklisted(tab.url, function(isBlacklisted) {
		//	if(isBlacklisted) {
		//		return false;
		//	}

			// Show or hide toast for this tab
			getOption('hideToast', function(hideToast) {
				if(!hideToast) {
					chrome.tabs.insertCSS(tabId, {file: 'css/content.css'}, function() {
						chrome.tabs.executeScript(tabId, {file: 'js/libs/readability.js', runAt: 'document_end'});
						chrome.tabs.executeScript(tabId, {file: 'js/content.js', runAt: 'document_end'});
					});
				}
			});
		//});
	});

});

// When the toolbar icon is clicked
chrome.browserAction.onClicked.addListener(function(tab) {
});

// Listen for callbacks from injected code
chrome.extension.onMessage.addListener(function(data, sender, sendResponse) {
	if(typeof data === 'object') {

		// Add domain to blacklist
		if(data.action == 'blacklist') {
			var domain = new URL(data.url).hostname;

			// Get current value and append domain to it
			getOption('toastBlacklist', function(currVal) {
				var newVal = currVal + "\r\n" + domain;
				chrome.storage.sync.set({
					toastBlacklist: newVal
				});
			});

		// App is requesting text
		} else if(data.action == 'getText') {
			sendResponse(textToRead);

		// Document (content script) requested a read start
		} else if(data.action == 'requestRead') {
			textToRead = data.text;
			openApp();
		}

	}
});

// Sometimes they stay in memory after extension reload
// so let's just make sure we remove it completely on load.
chrome.contextMenus.removeAll();

function updateContextMenu() {
	getOption('hideContextMenu', function(hideContextMenu) {

		// Show menu
		if(!hideContextMenu) {

			// If already added, do nothing
			if(contextMenuAdded) {
				return false;
			}

			chrome.contextMenus.create({
				id: 'readSelection',
				title : 'Read selected text with Readio',
				contexts : ['selection']
			});

			chrome.contextMenus.create({
				id: 'readPage',
				title : 'Read page with Readio',
				contexts : ['all']
			});

			chrome.contextMenus.onClicked.addListener(function(info, tab) {

				if(info.menuItemId === 'readSelection') {

					chrome.tabs.executeScript( {
						code: "window.getSelection().toString();"
					}, function(text) {

						// App only accepts HTML, so let's convert plaintext line breaks into

						text = text[0].replace(/(\r\n|\n|\r)+/gm, '
');

						textToRead = text;
						openApp();
					});

				} else if(info.menuItemId === 'readPage') {

					chrome.tabs.sendMessage(tab.id, {action: 'getParseData'}, function(text) {
						if(text) {
							textToRead = text;
							openApp();
						}
					});

				}

			});

			contextMenuAdded = true;

		// Hide menu if added
		} else {
			if(contextMenuAdded) {
				chrome.contextMenus.remove('readSelection');
				chrome.contextMenus.remove('readPage');
			}
			contextMenuAdded = false;
		}
	});
}

function openApp() {
	var width = 1200,
		height = 650/*,
		left = (screen.width/2) - (width/2),
		top = (screen.height/2) - (height/2)*/;

	chrome.windows.create({
		url: 'app.html',
		type: 'popup',
		width: width,
		height: height

		/*, Will center window but can't control which monitor it's displayed on, so skipping for now
		left: left,
		top: top*/
	});
}

function getOption(option, callback) {
	chrome.storage.sync.get(option, function(obj) {
		if(obj[option] !== undefined) {
			callback(obj[option]);
		} else {
			callback();
		}
	});
}

function isBlacklisted(url, callback) {
	var currDom = new URL(url).hostname;
	var stored = getOption('toastBlacklist', function(stored) {

		var split = stored.split(/\r|\n/g);

		if(split.length > 0) {
			for(var i=0; i < split.length; ++i) {
				var dom = split[i];
				if(dom.indexOf(currDom) !== -1) {
					callback(true);
					return true; // Prevent loop from continuing
				}
			}
		}
		callback(false);
	});
}<|endoftext|>


<commit_before>chrome.privacy.network.webRTCMultipleRoutesEnabled.set({
	value: false,
	scope: 'regular'
});<commit_msg>Added check for IPHandling undefined<commit_after>/*	webRTCIPHandlingPolicy, if undefined, set to 'default_public_interface_only'.
	This setting is only supported from Chromium version 48. */
chrome.storage.local.get(null, function(items) {
	if (items.rtcIPHandling == undefined) {
		chrome.storage.local.set({
			rtcIPHandling: 'default_public_interface_only'
		}, function() {
			chrome.privacy.network.webRTCIPHandlingPolicy.set({
				value: 'default_public_interface_only'
			});
		})
	}
});

/*	webRTCMultipleRoutesEnabled statically set to false.
	This option is deprecated from Chromium version 48. */
chrome.privacy.network.webRTCMultipleRoutesEnabled.set({
	value: false,
	scope: 'regular'
});<|endoftext|>