text: /**
   * @private
   */
  Ext.define('Ext.event.Controller', {
  
      isFiring: false,
  
      listenerStack: null,
  
      constructor: function(dispatcher) {
          this.firingListeners = [];
          this.firingArguments = [];
          this.dispatcher = dispatcher;
          return this;
      },
  
      setInfo: function(info) {
          this.info = info;
      },
  
      getInfo: function() {
          return this.info;
      },
  
      setListenerStacks: function(listenerStacks) {
          this.listenerStacks = listenerStacks;
      },
  
      fire: function(args, action) {
          var listenerStacks = this.listenerStacks,
              firingListeners = this.firingListeners,
              firingArguments = this.firingArguments,
              push = firingListeners.push,
              ln = listenerStacks.length,
              listeners, beforeListeners, currentListeners, afterListeners,
              isActionBefore = false,
              isActionAfter = false,
              i;
  
          firingListeners.length = 0;
  
          if (action) {
              if (action.order !== 'after') {
                  isActionBefore = true;
              }
              else {
                  isActionAfter = true;
              }
          }
  
          if (ln === 1) {
              listeners = listenerStacks[0].listeners;
              beforeListeners = listeners.before;
              currentListeners = listeners.current;
              afterListeners = listeners.after;
  
              if (beforeListeners.length > 0) {
                  push.apply(firingListeners, beforeListeners);
              }
  
              if (isActionBefore) {
                  push.call(firingListeners, action);
              }
  
              if (currentListeners.length > 0) {
                  push.apply(firingListeners, currentListeners);
              }
  
              if (isActionAfter) {
                  push.call(firingListeners, action);
              }
  
              if (afterListeners.length > 0) {
                  push.apply(firingListeners, afterListeners);
              }
          }
          else {
              for (i = 0; i < ln; i++) {
                  beforeListeners = listenerStacks[i].listeners.before;
                  if (beforeListeners.length > 0) {
                      push.apply(firingListeners, beforeListeners);
                  }
              }
  
              if (isActionBefore) {
                  push.call(firingListeners, action);
              }
  
              for (i = 0; i < ln; i++) {
                  currentListeners = listenerStacks[i].listeners.current;
                  if (currentListeners.length > 0) {
                      push.apply(firingListeners, currentListeners);
                  }
              }
  
              if (isActionAfter) {
                  push.call(firingListeners, action);
              }
  
              for (i = 0; i < ln; i++) {
                  afterListeners = listenerStacks[i].listeners.after;
                  if (afterListeners.length > 0) {
                      push.apply(firingListeners, afterListeners);
                  }
              }
          }
  
          if (firingListeners.length === 0) {
              return this;
          }
  
          if (!args) {
              args = [];
          }
  
          firingArguments.length = 0;
          firingArguments.push.apply(firingArguments, args);
  
          // Backwards compatibility
          firingArguments.push(null, this);
  
          this.doFire();
  
          return this;
      },
  
      doFire: function() {
          var me = this,
              firingListeners = me.firingListeners,
              firingArguments = me.firingArguments,
              arg1 = firingArguments[1],
              optionsArgumentIndex = firingArguments.length - 2,
              observable = firingListeners[0].observable,
              info = me.info,
              event, i, ln, listener, options, fn, firingFn,
              boundFn, isLateBinding, scope, args, result, type, beforeFn;
  
          if (observable && observable.isElement) {
              event = firingArguments[0];
  
              // this is to catch dom events that do have a underlying dom event to wrap (painted & resize)
              // In this case you will get an 'Ext.Element' as the first parameter to the listener
              if (event && !event.isEvent) {
                  event = null;
              }
          }
  
          me.isPausing = me.isPaused = me.isStopped = false;
          me.isFiring = true;
  
          for (i = 0,ln = firingListeners.length; i < ln; i++) {
              listener = firingListeners[i];
              options = listener.options;
              fn = listener.fn;
              firingFn = listener.firingFn;
              boundFn = listener.boundFn;
              isLateBinding = listener.isLateBinding;
              scope = listener.scope;
  
              // Re-bind the callback if it has changed since the last time it's bound (overridden)
              if (isLateBinding && boundFn && boundFn !== scope[fn]) {
                  boundFn = false;
                  firingFn = false;
              }
  
              if (!boundFn) {
                  if (isLateBinding) {
                      boundFn = scope[fn];
  
                      if (!boundFn) {
                          continue;
                      }
                  }
                  else {
                      boundFn = fn;
                  }
  
                  listener.boundFn = boundFn;
              }
  
              if (event) {
                  // For events that have been translated to provide device compatibility,
                  // e.g. mousedown -> touchstart, we want the event object to reflect the
                  // type that was originally listened for, not the type of the actual event
                  // that fired. The listener's "type" property reflects the original type.
                  type = listener.type;
                  if (type) {
                      // chain a new object to the event object before changing the type.
                      // This is more efficient than creating a new event object, and we
                      // don't want to change the type of the original event because it may
                      // be used asynchronously by other handlers
                      firingArguments[0] = event.chain({ type: type });
                  }
  
                  // In Ext4 Ext.EventObject was a singleton event object that was reused as events
                  // were fired.  Set Ext.EventObject to the last fired event for compatibility.
                  Ext.EventObject = firingArguments[0];
              }
  
              if (!firingFn) {
                  firingFn = boundFn;
  
                  if (options.delay) {
                      firingFn = Ext.Function.createDelayed(firingFn, options.delay, scope);
                  }
  
                  else if (options.buffer) {
                      firingFn = Ext.Function.createBuffered(firingFn, options.buffer, scope);
                  }
  
                  else if (options.onFrame) {
                      firingFn = Ext.Function.createAnimationFrame(firingFn, scope);
                  }
  
                  listener.firingFn = firingFn;
              }
  
              firingArguments[optionsArgumentIndex] = options;
  
              args = firingArguments;
  
              if (options.args) {
                  args = options.args.concat(args);
              }
  
              // If using the delegate option, fix the target to be the delegate element,
              // however we only want to do this for element delegates.
              if (options.delegate && me.info.targetType === 'element') {
                  firingArguments[1] = Ext.fly(arg1).findParent(info.target, arg1);
              }
  
              if (options.single) {
                  // need to call the dispatcher, instead of just removing from the stack
                  // here because we need to also tell the publisher to unsubscribe
                  me.dispatcher.removeListener(info.targetType, info.target, info.eventName,
                      fn, scope, options, listener.order, listener.observable);
              }
  
              beforeFn = options.beforeFn;
              // Currently, the usage for the beforeFn is used for normalizing DOM events, for example
              // mouseenter/mouseleave get transformed to mouseover/mouseout in browsers that don't support them.
              // We give those functions a chance to jump in here and veto if necessary. We need to do this late
              // because we have to do it just before resolving the real caller
              if (beforeFn) {
                  result = beforeFn.apply(Ext.global, args) !== false;
                  if (result !== false) {
                      result = firingFn.apply(scope, args);
                  }
              } else {
                  result = firingFn.apply(scope, args);
              }
  
              // Restore the second argument if it wss set to the delegated target
              if (options.delegate) {
                  firingArguments[1] = arg1;
              }
  
              if (result === false) {
                  me.stop();
              }
  
              if (me.isStopped) {
                  break;
              }
  
              if (me.isPausing) {
                  me.isPaused = true;
                  firingListeners.splice(0, i + 1);
                  return;
              }
  
              if (event) {
                  // reset the arguments' event object to the original non-translated one.
                  firingArguments[0] = event;
              }
          }
  
          me.isFiring = false;
          me.listenerStacks = null;
          firingListeners.length = firingArguments.length = 0;
          me.connectingController = null;
      },
  
      connect: function(controller) {
          this.connectingController = controller;
      },
  
      resume: function() {
          var connectingController = this.connectingController;
  
          this.isPausing = false;
  
          if (this.isPaused && this.firingListeners.length > 0) {
              this.isPaused = false;
              this.doFire();
          }
  
          if (connectingController) {
              connectingController.resume();
          }
  
          return this;
      },
  
      isInterrupted: function() {
          return this.isStopped || this.isPaused;
      },
  
      stop: function() {
          var connectingController = this.connectingController;
  
          this.isStopped = true;
  
          if (connectingController) {
              this.connectingController = null;
              connectingController.stop();
          }
  
          this.isFiring = false;
  
          this.listenerStacks = null;
  
          return this;
      },
  
      pause: function() {
          var connectingController = this.connectingController;
  
          this.isPausing = true;
  
          if (connectingController) {
              connectingController.pause();
          }
  
          return this;
      }
  });
  