text: /* ************************************************************************
  
     qooxdoo - the new era of web development
  
     http://qooxdoo.org
  
     Copyright:
       2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
  
     License:
       MIT: https://opensource.org/licenses/MIT
       See the LICENSE file in the project's top-level directory for details.
  
     Authors:
       * Sebastian Werner (wpbasti)
       * Andreas Ecker (ecker)
       * Fabian Jakobs (fjakobs)
       * Christian Hagendorn (chris_schmidt)
  
  ************************************************************************ */
  
  /**
   * This class provides an unified mouse event handler for Internet Explorer,
   * Firefox, Opera and Safari
   *
   * NOTE: Instances of this class must be disposed of after use
   *
   * @require(qx.event.handler.UserAction)
   * @ignore(qx.event.handler.DragDrop)
   * @ignore(performance.now)
   */
  qx.Class.define("qx.event.handler.Mouse", {
    extend: qx.core.Object,
    implement: [qx.event.IEventHandler, qx.core.IDisposable],
  
    /*
    *****************************************************************************
       CONSTRUCTOR
    *****************************************************************************
    */
  
    /**
     * Create a new instance
     *
     * @param manager {qx.event.Manager} Event manager for the window to use
     */
    construct(manager) {
      super();
  
      // Define shorthands
      this.__manager = manager;
      this.__window = manager.getWindow();
      this.__root = this.__window.document;
      this.__onNativeListener = qx.lang.Function.listener(this._onNative, this);
  
      // Initialize observers
      this._initButtonObserver();
      this._initMoveObserver();
      this._initWheelObserver();
    },
  
    /*
    *****************************************************************************
       STATICS
    *****************************************************************************
    */
  
    statics: {
      /** @type {Integer} Priority of this handler */
      PRIORITY: qx.event.Registration.PRIORITY_NORMAL,
  
      /** @type {Map} Supported event types */
      SUPPORTED_TYPES: {
        auxclick: 1,
        click: 1,
        contextmenu: 1,
        dblclick: 1,
        mousedown: 1,
        mouseenter: 1,
        mouseleave: 1,
        mousemove: 1,
        mouseout: 1,
        mouseover: 1,
        mouseup: 1,
        mousewheel: 1
      },
  
      /** @type{Map} these event types cannot be attached to the root (the document), they must be attached to the element itself */
      NON_BUBBLING_EVENTS: {
        mouseenter: true,
        mouseleave: true
      },
  
      /** @type {Integer} Which target check to use */
      TARGET_CHECK:
        qx.event.IEventHandler.TARGET_DOMNODE +
        qx.event.IEventHandler.TARGET_DOCUMENT +
        qx.event.IEventHandler.TARGET_WINDOW,
  
      /** @type {Integer} Whether the method "canHandleEvent" must be called */
      IGNORE_CAN_HANDLE: true
    },
  
    /*
    *****************************************************************************
       MEMBERS
    *****************************************************************************
    */
  
    members: {
      __onButtonEventWrapper: null,
      __onMoveEventWrapper: null,
      __onWheelEventWrapper: null,
      __lastEventType: null,
      __lastMouseDownTarget: null,
      __manager: null,
      __window: null,
      __root: null,
      __preventNextClick: null,
  
      /** @type{Function} wrapper for `_onNative`, bound as a native listener */
      __onNativeListener: null,
  
      /*
      ---------------------------------------------------------------------------
        EVENT HANDLER INTERFACE
      ---------------------------------------------------------------------------
      */
  
      // interface implementation
      canHandleEvent(target, type) {},
  
      /**
       * @Override
       */
      registerEvent(target, type, capture) {
        if (qx.event.handler.Mouse.NON_BUBBLING_EVENTS[type]) {
          qx.bom.Event.addNativeListener(target, type, this.__onNativeListener);
        } else if (qx.core.Environment.get("os.name") === "ios") {
          // The iPhone requires for attaching mouse events natively to every element which
          // should react on mouse events. As of version 3.0 it also requires to keep the
          // listeners as long as the event should work. In 2.0 it was enough to attach the
          // listener once.
          target["on" + type] = function () {
            return null;
          };
        }
      },
  
      /**
       * @Override
       */
      unregisterEvent(target, type, capture) {
        if (qx.event.handler.Mouse.NON_BUBBLING_EVENTS[type]) {
          qx.bom.Event.removeNativeListener(
            target,
            type,
            this.__onNativeListener
          );
        } else if (qx.core.Environment.get("os.name") === "ios") {
          target["on" + type] = undefined;
        }
      },
  
      /**
       * Default event handler for events that do not bubble
       *
       * @signature function(domEvent, eventId)
       * @param domEvent {Event} Native event
       */
      _onNative: qx.event.GlobalError.observeMethod(function (domEvent) {
        let target = qx.bom.Event.getTarget(domEvent);
  
        qx.event.Registration.fireNonBubblingEvent(
          target,
          domEvent.type,
          qx.event.type.Mouse,
          [domEvent, target, undefined, undefined, domEvent.cancelable]
        );
      }),
  
      /*
      ---------------------------------------------------------------------------
        HELPER
      ---------------------------------------------------------------------------
      */
  
      /**
       * Fire a mouse event with the given parameters
       *
       * @param domEvent {Event} DOM event
       * @param type {String} type of the event
       * @param target {Element} event target
       */
      __fireEvent(domEvent, type, target) {
        if (!target) {
          target = qx.bom.Event.getTarget(domEvent);
        }
  
        // we need a true node for the fireEvent
        // e.g. when hovering over text of disabled textfields IE is returning
        // an empty object as "srcElement"
        if (target && target.nodeType) {
          qx.event.Registration.fireEvent(
            target,
            type || domEvent.type,
            type == "mousewheel" ? qx.event.type.MouseWheel : qx.event.type.Mouse,
            [domEvent, target, null, true, true]
          );
        }
  
        // Fire user action event
        qx.event.Registration.fireEvent(
          this.__window,
          "useraction",
          qx.event.type.Data,
          [type || domEvent.type]
        );
      },
  
      /**
       * Helper to prevent the next click.
       * @internal
       */
      preventNextClick() {
        this.__preventNextClick = true;
      },
  
      /*
      ---------------------------------------------------------------------------
        OBSERVER INIT
      ---------------------------------------------------------------------------
      */
  
      /**
       * Initializes the native mouse button event listeners.
       *
       * @signature function()
       */
      _initButtonObserver() {
        this.__onButtonEventWrapper = qx.lang.Function.listener(
          this._onButtonEvent,
          this
        );
  
        var Event = qx.bom.Event;
  
        Event.addNativeListener(
          this.__root,
          "mousedown",
          this.__onButtonEventWrapper
        );
  
        Event.addNativeListener(
          this.__root,
          "mouseup",
          this.__onButtonEventWrapper
        );
  
        Event.addNativeListener(
          this.__root,
          "click",
          this.__onButtonEventWrapper
        );
  
        Event.addNativeListener(
          this.__root,
          "auxclick",
          this.__onButtonEventWrapper
        );
  
        Event.addNativeListener(
          this.__root,
          "dblclick",
          this.__onButtonEventWrapper
        );
  
        Event.addNativeListener(
          this.__root,
          "contextmenu",
          this.__onButtonEventWrapper
        );
      },
  
      /**
       * Initializes the native mouse move event listeners.
       *
       * @signature function()
       */
      _initMoveObserver() {
        this.__onMoveEventWrapper = qx.lang.Function.listener(
          this._onMoveEvent,
          this
        );
  
        var Event = qx.bom.Event;
        Event.addNativeListener(
          this.__root,
          "mousemove",
          this.__onMoveEventWrapper
        );
  
        Event.addNativeListener(
          this.__root,
          "mouseout",
          this.__onMoveEventWrapper
        );
  
        Event.addNativeListener(
          this.__root,
          "mouseover",
          this.__onMoveEventWrapper
        );
      },
  
      /**
       * Initializes the native mouse wheel event listeners.
       *
       * @signature function()
       */
      _initWheelObserver() {
        this.__onWheelEventWrapper = qx.lang.Function.listener(
          this._onWheelEvent,
          this
        );
  
        var data = qx.bom.client.Event.getMouseWheel(this.__window);
        qx.bom.Event.addNativeListener(
          data.target,
          data.type,
          this.__onWheelEventWrapper
        );
      },
  
      /*
      ---------------------------------------------------------------------------
        OBSERVER STOP
      ---------------------------------------------------------------------------
      */
  
      /**
       * Disconnects the native mouse button event listeners.
       *
       * @signature function()
       */
      _stopButtonObserver() {
        var Event = qx.bom.Event;
  
        Event.removeNativeListener(
          this.__root,
          "mousedown",
          this.__onButtonEventWrapper
        );
  
        Event.removeNativeListener(
          this.__root,
          "mouseup",
          this.__onButtonEventWrapper
        );
  
        Event.removeNativeListener(
          this.__root,
          "click",
          this.__onButtonEventWrapper
        );
  
        Event.removeNativeListener(
          this.__root,
          "dblclick",
          this.__onButtonEventWrapper
        );
  
        Event.removeNativeListener(
          this.__root,
          "contextmenu",
          this.__onButtonEventWrapper
        );
      },
  
      /**
       * Disconnects the native mouse move event listeners.
       *
       * @signature function()
       */
      _stopMoveObserver() {
        var Event = qx.bom.Event;
  
        Event.removeNativeListener(
          this.__root,
          "mousemove",
          this.__onMoveEventWrapper
        );
  
        Event.removeNativeListener(
          this.__root,
          "mouseover",
          this.__onMoveEventWrapper
        );
  
        Event.removeNativeListener(
          this.__root,
          "mouseout",
          this.__onMoveEventWrapper
        );
      },
  
      /**
       * Disconnects the native mouse wheel event listeners.
       *
       * @signature function()
       */
      _stopWheelObserver() {
        var data = qx.bom.client.Event.getMouseWheel(this.__window);
        qx.bom.Event.removeNativeListener(
          data.target,
          data.type,
          this.__onWheelEventWrapper
        );
      },
  
      /*
      ---------------------------------------------------------------------------
        NATIVE EVENT OBSERVERS
      ---------------------------------------------------------------------------
      */
  
      /**
       * Global handler for all mouse move related events like "mousemove",
       * "mouseout" and "mouseover".
       *
       * @signature function(domEvent)
       * @param domEvent {Event} DOM event
       */
      _onMoveEvent: qx.event.GlobalError.observeMethod(function (domEvent) {
        this.__fireEvent(domEvent);
      }),
  
      /**
       * Global handler for all mouse button related events like "mouseup",
       * "mousedown", "click", "dblclick" and "contextmenu".
       *
       * @signature function(domEvent)
       * @param domEvent {Event} DOM event
       */
      _onButtonEvent: qx.event.GlobalError.observeMethod(function (domEvent) {
        var type = domEvent.type;
        var target = qx.bom.Event.getTarget(domEvent);
  
        if (type == "click" && this.__preventNextClick) {
          delete this.__preventNextClick;
          return;
        }
  
        // Safari (and maybe gecko) takes text nodes as targets for events
        // See: http://www.quirksmode.org/js/events_properties.html
        if (
          qx.core.Environment.get("engine.name") == "gecko" ||
          qx.core.Environment.get("engine.name") == "webkit"
        ) {
          if (target && target.nodeType == 3) {
            target = target.parentNode;
          }
        }
  
        // prevent click events on drop during Drag&Drop [BUG #6846]
        var isDrag =
          qx.event.handler.DragDrop &&
          this.__manager.getHandler(qx.event.handler.DragDrop).isSessionActive();
        if (isDrag && type == "click") {
          return;
        }
  
        if (this.__doubleClickFixPre) {
          this.__doubleClickFixPre(domEvent, type, target);
        }
  
        this.__fireEvent(domEvent, type, target);
  
        /*
         * In order to normalize middle button click events we
         * need to fire an artificial click event if the client
         * fires auxclick events for non primary buttons instead.
         *
         * See https://github.com/qooxdoo/qooxdoo/issues/9268
         */
        if (type == "auxclick" && domEvent.button == 1) {
          this.__fireEvent(domEvent, "click", target);
        }
  
        if (this.__rightClickFixPost) {
          this.__rightClickFixPost(domEvent, type, target);
        }
  
        if (this.__differentTargetClickFixPost && !isDrag) {
          this.__differentTargetClickFixPost(domEvent, type, target);
        }
  
        this.__lastEventType = type;
      }),
  
      /**
       * Global handler for the mouse wheel event.
       *
       * @signature function(domEvent)
       * @param domEvent {Event} DOM event
       */
      _onWheelEvent: qx.event.GlobalError.observeMethod(function (domEvent) {
        this.__fireEvent(domEvent, "mousewheel");
      }),
  
      /*
      ---------------------------------------------------------------------------
        CROSS BROWSER SUPPORT FIXES
      ---------------------------------------------------------------------------
      */
  
      /**
       * Normalizes the click sequence of right click events in Webkit and Opera.
       * The normalized sequence is:
       *
       *  1. mousedown  <- not fired by Webkit
       *  2. mouseup  <- not fired by Webkit
       *  3. contextmenu <- not fired by Opera
       *
       * @param domEvent {Event} original DOM event
       * @param type {String} event type
       * @param target {Element} event target of the DOM event.
       *
       * @signature function(domEvent, type, target)
       */
      __rightClickFixPost: qx.core.Environment.select("engine.name", {
        opera(domEvent, type, target) {
          if (type == "mouseup" && domEvent.button == 2) {
            this.__fireEvent(domEvent, "contextmenu", target);
          }
        },
  
        default: null
      }),
  
      /**
       * Normalizes the click sequence of double click event in the Internet
       * Explorer. The normalized sequence is:
       *
       *  1. mousedown
       *  2. mouseup
       *  3. click
       *  4. mousedown  <- not fired by IE
       *  5. mouseup
       *  6. click  <- not fired by IE
       *  7. dblclick
       *
       *  Note: This fix is only applied, when the IE event model is used, otherwise
       *  the fix is ignored.
       *
       * @param domEvent {Event} original DOM event
       * @param type {String} event type
       * @param target {Element} event target of the DOM event.
       *
       * @signature function(domEvent, type, target)
       */
      __doubleClickFixPre: qx.core.Environment.select("engine.name", {
        mshtml(domEvent, type, target) {
          // Do only apply the fix when the event is from the IE event model,
          // otherwise do not apply the fix.
          if (domEvent.target !== undefined) {
            return;
          }
  
          if (type == "mouseup" && this.__lastEventType == "click") {
            this.__fireEvent(domEvent, "mousedown", target);
          } else if (type == "dblclick") {
            this.__fireEvent(domEvent, "click", target);
          }
        },
  
        default: null
      }),
  
      /**
       * If the mouseup event happens on a different target than the corresponding
       * mousedown event the internet explorer dispatches a click event on the
       * first common ancestor of both targets. The presence of this click event
       * is essential for the qooxdoo widget system. All other browsers don't fire
       * the click event so it must be emulated.
       *
       * @param domEvent {Event} original DOM event
       * @param type {String} event type
       * @param target {Element} event target of the DOM event.
       *
       * @signature function(domEvent, type, target)
       */
      __differentTargetClickFixPost: qx.core.Environment.select("engine.name", {
        mshtml: null,
  
        default(domEvent, type, target) {
          switch (type) {
            case "mousedown":
              this.__lastMouseDownTarget = target;
              break;
  
            case "mouseup":
              if (target !== this.__lastMouseDownTarget) {
                var commonParent = qx.dom.Hierarchy.getCommonParent(
                  target,
                  this.__lastMouseDownTarget
                );
  
                if (commonParent) {
                  this.__fireEvent(domEvent, "click", commonParent);
                }
              }
          }
        }
      })
    },
  
    /*
    *****************************************************************************
       DESTRUCTOR
    *****************************************************************************
    */
  
    destruct() {
      this._stopButtonObserver();
      this._stopMoveObserver();
      this._stopWheelObserver();
  
      this.__manager =
        this.__window =
        this.__root =
        this.__lastMouseDownTarget =
          null;
    },
  
    /*
    *****************************************************************************
       DEFER
    *****************************************************************************
    */
  
    defer(statics) {
      qx.event.Registration.addHandler(statics);
    }
  });
  