chromium/chrome/test/data/chromeos/virtual_keyboard/default_extension/mock_timer.js

// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

/** Overrides timeout and interval callbacks to mock timing behavior. */
class MockTimer {
  constructor() {
    /**
     * Default versions of the timing functions.
     * @type {Object<string, !Function>}
     * @private
     */
    this.originals_ = [];

    /**
     * Key to assign on the next creation of a scheduled timer. Each call to
     * setTimeout or setInterval returns a unique key that can be used for
     * clearing the timer.
     * @type {number}
     * @private
     */
    this.nextTimerKey_ = 1;

    /**
     * Details for active timers.
     * @type {!Array<{callback: Function,
     *                delay: number,
     *                key: number,
     *                repeats: boolean}|undefined>}
     * @private
     */
    this.timers_ = [];

    /**
     * List of scheduled tasks.
     * @type {Array<{when: number, key: number}>}
     * @private
     */
    this.schedule_ = [];

    /**
     * Virtual elapsed time in milliseconds.
     * @type {number}
     * @private
     */
    this.now_ = 0;

    /**
     * Used to control when scheduled callbacks fire.  Calling the 'tick' method
     * inflates this parameter and triggers callbacks.
     * @type {number}
     * @private
     */
    this.until_ = 0;
  }

  /**
   * Replaces built-in functions for scheduled callbacks.
   */
  install() {
    this.replace_('setTimeout', this.setTimeout_.bind(this));
    this.replace_('clearTimeout', this.clearTimeout_.bind(this));
    this.replace_('setInterval', this.setInterval_.bind(this));
    this.replace_('clearInterval', this.clearInterval_.bind(this));
  }

  /**
   * Restores default behavior for scheduling callbacks.
   */
  uninstall() {
    if (this.originals_) {
      for (var key in this.originals_) {
        window[key] = this.originals_[key];
      }
    }
  }

  /**
   * Overrides a global function.
   * @param {string} functionName The name of the function.
   * @param {!Function} replacementFunction The function override.
   * @private
   */
  replace_(functionName, replacementFunction) {
    this.originals_[functionName] = window[functionName];
    window[functionName] = replacementFunction;
  }

  /**
   * Creates a virtual timer.
   * @param {!Function} callback The callback function.
   * @param {number} delayInMs The virtual delay in milliseconds.
   * @param {boolean} repeats Indicates if the timer repeats.
   * @return {number} Idetifier for the timer.
   * @private
   */
  createTimer_(callback, delayInMs, repeats) {
    var key = this.nextTimerKey_++;
    var task =
        {callback: callback, delay: delayInMs, key: key, repeats: repeats};
    this.timers_[key] = task;
    this.scheduleTask_(task);
    return key;
  }

  /**
   * Schedules a callback for execution after a virtual time delay. The tasks
   * are sorted in descending order of time delay such that the next callback
   * to fire is at the end of the list.
   * @param {{callback: Function,
   *          delay: number,
   *          key: number,
   *          repeats: boolean}} details The timer details.
   * @private
   */
  scheduleTask_(details) {
    var key = details.key;
    var when = this.now_ + details.delay;
    var index = this.schedule_.length;
    while (index > 0 && this.schedule_[index - 1].when < when) {
      index--;
    }
    this.schedule_.splice(index, 0, {when: when, key: key});
  }

  /**
   * Override of window.setInterval.
   * @param {!Function} callback The callback function.
   * @param {number} intervalInMs The repeat interval.
   * @private
   */
  setInterval_(callback, intervalInMs) {
    return this.createTimer_(callback, intervalInMs, true);
  }

  /**
   * Override of window.clearInterval.
   * @param {number} key The ID of the interval timer returned from
   *     setInterval.
   * @private
   */
  clearInterval_(key) {
    this.timers_[key] = undefined;
  }

  /**
   * Override of window.setTimeout.
   * @param {!Function} callback The callback function.
   * @param {number} delayInMs The scheduled delay.
   * @private
   */
  setTimeout_(callback, delayInMs) {
    return this.createTimer_(callback, delayInMs, false);
  }

  /**
   * Override of window.clearTimeout.
   * @param {number} key The ID of the schedule timeout callback returned
   *     from setTimeout.
   * @private
   */
  clearTimeout_(key) {
    this.timers_[key] = undefined;
  }

  /**
   * Simulates passage of time, triggering any scheduled callbacks whose timer
   * has elapsed.
   * @param {number} elapsedMs The simulated elapsed time in milliseconds.
   */
  tick(elapsedMs) {
    this.until_ += elapsedMs;
    this.fireElapsedCallbacks_();
  }

  /**
   * Triggers any callbacks that should have fired based in the simulated
   * timing.
   * @private
   */
  fireElapsedCallbacks_() {
    while (this.schedule_.length > 0) {
      var when = this.schedule_[this.schedule_.length - 1].when;
      if (when > this.until_) {
        break;
      }

      var task = this.schedule_.pop();
      var details = this.timers_[task.key];
      if (!details) {
        continue;
      }  // Cancelled task.

      this.now_ = when;
      details.callback.apply(window);
      if (details.repeats) {
        this.scheduleTask_(details);
      } else {
        this.clearTimeout_(details.key);
      }
    }
    this.now_ = this.until_;
  }
}