text: "use strict";
  const DOMException = require("../../web-idl/DOMException");
  const reportException = require("../helpers/runtime-script-errors");
  const domSymbolTree = require("../helpers/internal-constants").domSymbolTree;
  const idlUtils = require("../generated/utils");
  
  const EventImpl = require("./Event-impl").implementation;
  const Event = require("../generated/Event").interface;
  
  class EventTargetImpl {
    constructor() {
      this._eventListeners = Object.create(null);
    }
  
    addEventListener(type, callback, options) {
      // webidl2js currently can't handle neither optional arguments nor callback interfaces
      if (callback === undefined || callback === null) {
        callback = null;
      } else if (typeof callback !== "object" && typeof callback !== "function") {
        throw new TypeError("Only undefined, null, an object, or a function are allowed for the callback parameter");
      }
  
      options = normalizeEventHandlerOptions(options, ["capture", "once"]);
  
      if (callback === null) {
        return;
      }
  
      if (!this._eventListeners[type]) {
        this._eventListeners[type] = [];
      }
  
      for (let i = 0; i < this._eventListeners[type].length; ++i) {
        const listener = this._eventListeners[type][i];
        if (listener.options.capture === options.capture && listener.callback === callback) {
          return;
        }
      }
  
      this._eventListeners[type].push({
        callback,
        options
      });
    }
  
    removeEventListener(type, callback, options) {
      if (callback === undefined || callback === null) {
        callback = null;
      } else if (typeof callback !== "object" && typeof callback !== "function") {
        throw new TypeError("Only undefined, null, an object, or a function are allowed for the callback parameter");
      }
  
      options = normalizeEventHandlerOptions(options, ["capture"]);
  
      if (callback === null) {
        // Optimization, not in the spec.
        return;
      }
  
      if (!this._eventListeners[type]) {
        return;
      }
  
      for (let i = 0; i < this._eventListeners[type].length; ++i) {
        const listener = this._eventListeners[type][i];
        if (listener.callback === callback && listener.options.capture === options.capture) {
          this._eventListeners[type].splice(i, 1);
          break;
        }
      }
    }
  
    dispatchEvent(eventImpl) {
      if (!(eventImpl instanceof EventImpl)) {
        throw new TypeError("Argument to dispatchEvent must be an Event");
      }
  
      if (eventImpl._dispatchFlag || !eventImpl._initializedFlag) {
        throw new DOMException(DOMException.INVALID_STATE_ERR, "Tried to dispatch an uninitialized event");
      }
      if (eventImpl.eventPhase !== Event.NONE) {
        throw new DOMException(DOMException.INVALID_STATE_ERR, "Tried to dispatch a dispatching event");
      }
  
      eventImpl.isTrusted = false;
  
      return this._dispatch(eventImpl);
    }
  
    _dispatch(eventImpl, targetOverride) {
      eventImpl._dispatchFlag = true;
      eventImpl.target = targetOverride || this;
  
      const eventPath = [];
      let targetParent = domSymbolTree.parent(eventImpl.target);
      let target = eventImpl.target;
      while (targetParent) {
        eventPath.push(targetParent);
        target = targetParent;
        targetParent = domSymbolTree.parent(targetParent);
      }
      if (eventImpl.type !== "load" && target._defaultView) {
        // https://html.spec.whatwg.org/#events-and-the-window-object
        eventPath.push(idlUtils.implForWrapper(target._defaultView));
      }
  
      eventImpl.eventPhase = Event.CAPTURING_PHASE;
      for (let i = eventPath.length - 1; i >= 0; --i) {
        if (eventImpl._stopPropagationFlag) {
          break;
        }
  
        const object = eventPath[i];
        const objectImpl = idlUtils.implForWrapper(object) || object; // window :(
        const eventListeners = objectImpl._eventListeners[eventImpl.type];
        invokeEventListeners(eventListeners, object, eventImpl);
      }
  
      eventImpl.eventPhase = Event.AT_TARGET;
  
      if (!eventImpl._stopPropagationFlag) {
        invokeInlineListeners(eventImpl.target, eventImpl);
  
        if (this._eventListeners[eventImpl.type]) {
          const eventListeners = this._eventListeners[eventImpl.type];
          invokeEventListeners(eventListeners, eventImpl.target, eventImpl);
        }
      }
  
      if (eventImpl.bubbles) {
        eventImpl.eventPhase = Event.BUBBLING_PHASE;
        for (let i = 0; i < eventPath.length; ++i) {
          if (eventImpl._stopPropagationFlag) {
            break;
          }
  
          const object = eventPath[i];
          const objectImpl = idlUtils.implForWrapper(object) || object; // window :(
          const eventListeners = objectImpl._eventListeners[eventImpl.type];
          invokeInlineListeners(object, eventImpl);
          invokeEventListeners(eventListeners, object, eventImpl);
        }
      }
  
      eventImpl._dispatchFlag = false;
      eventImpl._stopPropagationFlag = false;
      eventImpl._stopImmediatePropagationFlag = false;
      eventImpl.eventPhase = Event.NONE;
      eventImpl.currentTarget = null;
      return !eventImpl._canceledFlag;
    }
  }
  
  module.exports = {
    implementation: EventTargetImpl
  };
  
  function invokeInlineListeners(object, event) {
    const wrapper = idlUtils.wrapperForImpl(object);
    const inlineListener = getListenerForInlineEventHandler(wrapper, event.type);
    if (inlineListener) {
      const document = object._ownerDocument || (wrapper && (wrapper._document || wrapper._ownerDocument));
  
      // Will be falsy for windows that have closed
      if (document && (!object.nodeName || document.implementation._hasFeature("ProcessExternalResources", "script"))) {
        invokeEventListeners([{
          callback: inlineListener,
          options: normalizeEventHandlerOptions(false, ["capture", "once"])
        }], object, event);
      }
    }
  }
  
  function invokeEventListeners(listeners, target, eventImpl) {
    const wrapper = idlUtils.wrapperForImpl(target);
    const document = target._ownerDocument || (wrapper && (wrapper._document || wrapper._ownerDocument));
    // Will be falsy for windows that have closed
    if (!document) {
      return;
    }
  
    // workaround for events emitted on window (window-proxy)
    // the wrapper is the root window instance, but we only want to expose the vm proxy at all times
    if (wrapper._document) {
      target = idlUtils.implForWrapper(wrapper._document)._defaultView;
    }
    eventImpl.currentTarget = target;
    if (!listeners) {
      return;
    }
  
    const handlers = listeners.slice();
    for (let i = 0; i < handlers.length; ++i) {
      if (eventImpl._stopImmediatePropagationFlag) {
        return;
      }
  
      const listener = handlers[i];
      const capture = listener.options.capture;
      const once = listener.options.once;
      // const passive = listener.options.passive;
  
      if (listeners.indexOf(listener) === -1 ||
          (eventImpl.eventPhase === Event.CAPTURING_PHASE && !capture) ||
          (eventImpl.eventPhase === Event.BUBBLING_PHASE && capture)) {
        continue;
      }
  
      if (once) {
        listeners.splice(listeners.indexOf(listener), 1);
      }
  
      try {
        if (typeof listener.callback === "object") {
          if (typeof listener.callback.handleEvent === "function") {
            listener.callback.handleEvent(idlUtils.wrapperForImpl(eventImpl));
          }
        } else {
          listener.callback.call(idlUtils.wrapperForImpl(eventImpl.currentTarget), idlUtils.wrapperForImpl(eventImpl));
        }
      } catch (e) {
        let window = null;
        if (wrapper && wrapper._document) {
          // Triggered by Window
          window = wrapper;
        } else if (target._ownerDocument) {
          // Triggered by most webidl2js'ed instances
          window = target._ownerDocument._defaultView;
        } else if (wrapper._ownerDocument) {
          // Currently triggered by XHR and some other non-webidl2js things
          window = wrapper._ownerDocument._defaultView;
        }
  
        if (window) {
          reportException(window, e);
        }
        // Errors in window-less documents just get swallowed... can you think of anything better?
      }
    }
  }
  
  const wrappedListener = Symbol("inline event listener wrapper");
  
  /**
   * Normalize the event listeners options argument in order to get always a valid options object
   * @param   {Object} options         - user defined options
   * @param   {Array} defaultBoolKeys  - boolean properties that should belong to the options object
   * @returns {Object} object containing at least the "defaultBoolKeys"
   */
  function normalizeEventHandlerOptions(options, defaultBoolKeys) {
    const returnValue = {};
  
    // no need to go further here
    if (typeof options === "boolean" || options === null || typeof options === "undefined") {
      returnValue.capture = Boolean(options);
      return returnValue;
    }
  
    // non objects options so we typecast its value as "capture" value
    if (typeof options !== "object") {
      returnValue.capture = Boolean(options);
      // at this point we don't need to loop the "capture" key anymore
      defaultBoolKeys = defaultBoolKeys.filter(k => k !== "capture");
    }
  
    for (const key of defaultBoolKeys) {
      returnValue[key] = Boolean(options[key]);
    }
  
    return returnValue;
  }
  
  function getListenerForInlineEventHandler(target, type) {
    const callback = target["on" + type];
  
    if (!callback) { // TODO event handlers: only check null
      return null;
    }
  
    if (!callback[wrappedListener]) {
      // https://html.spec.whatwg.org/multipage/webappapis.html#the-event-handler-processing-algorithm
      callback[wrappedListener] = function (E) {
        const isWindowError = E.constructor.name === "ErrorEvent" && type === "error"; // TODO branding
  
        let returnValue;
        if (isWindowError) {
          returnValue = callback.call(E.currentTarget, E.message, E.filename, E.lineno, E.colno, E.error);
        } else {
          returnValue = callback.call(E.currentTarget, E);
        }
  
        if (type === "mouseover" || isWindowError) {
          if (returnValue === true) {
            E.preventDefault();
          }
        } else if (returnValue === false) {
          E.preventDefault();
        }
      };
    }
  
    return callback[wrappedListener];
  }
  